Add --verbose flag to print full transaction details

Also document that the format is based on previous Perl code that used the (now
non-working) SOAP API.
This commit is contained in:
Ben Sturmfels 2025-06-18 00:34:41 +10:00
parent 24862460ca
commit 8fd968c6f9
Signed by: bsturmfels
GPG key ID: 023C05E2C9C068F0

View file

@ -4,6 +4,7 @@
import argparse
import collections
import datetime
import pprint
import os
import sys
@ -26,9 +27,24 @@ Run them like this:
$ export PAYPAL_CLIENT_ID=XXX
$ export PAYPAL_CLIENT_SECRET=YYY
$ python3 paypal_report.py transactions 2021-11-01T00:00:00-07:00 2021-11-30T23:59:59-07:00 > transactions.txt
Format is:
TRANSACTION_ID <TAB> REFERENCE_ID
Where REFERENCE_ID is equivalent to what PayPal previously called "Profile ID".
$ python3 paypal_report.py profiles 2021-11-01T00:00:00-07:00 2021-11-30T23:59:59-07:00 > profiles.txt
Format is:
REFERENCE_ID <TAB> TRANSACTION_SUBJECT
$ python3 paypal_report.py suspend 2022-10-01T00:00:00-07:00 2022-10-31T23:59:59-07:00 homebrew
The output format of these reports is based on the prior Perl code by bkuhn that
used the SOAP APIs. These APIs stoped working around 2021, hence the need for
this rewrite. Unfortunately, the current APIs don't allow us to directly query
for subscriptions, so these are instead extracted from the list of transactions.
NOTE: Newly created PayPal "apps"/credentials initially authenticate, but can
then return PERMISSION_DENIED for ~ 40 mins.
@ -111,9 +127,9 @@ def main():
access_token = paypal_login(client_id, client_secret)
transactions = get_all_transactions(access_token, args.start_date, args.end_date)
if args.report == 'profiles':
report_on_unique_profiles(transactions)
report_on_unique_profiles(transactions, verbose=args.verbose)
elif args.report == 'transactions':
report_on_transactions(transactions)
report_on_transactions(transactions, verbose=args.verbose)
else:
suspend_subscriptions_interactively(transactions, args.pattern, access_token)
except PayPalException as e:
@ -126,6 +142,11 @@ def parse_args():
description="""Download or cancel subscribers or subscriber transactions from PayPal.""",
epilog=HELP,
)
parser.add_argument(
'-v',
'--verbose',
action='store_true',
)
parser.add_argument(
'report', help='report or action"', choices=['profiles', 'transactions', 'suspend']
)
@ -200,31 +221,34 @@ def get_transactions_for_period(access_token, start_date, end_date, page=1):
return data['transaction_details']
def report_on_unique_profiles(transactions):
def report_on_unique_profiles(transactions, verbose=False):
"""Print a list of subscribers from a set of transactions.
PayPal doesn't provide a way to query for subscribers directly, so we build
this by scanning through the list of transactions and finding the unique
subscribers.
"""
records = set()
records = {}
for t in transactions:
transaction_info = t['transaction_info']
if 'paypal_reference_id' in transaction_info:
records.add(
records[
(
transaction_info['paypal_reference_id'],
transaction_info.get('transaction_subject', 'NO TRANSACTION SUBJECT'),
),
)
)
] = t
else:
print(
f'Skipping transaction {transaction_info["transaction_id"]} with no PayPal Reference ID.',
file=sys.stderr,
)
for ref_id, desc in sorted(records):
for key, trans in sorted(records.items()):
ref_id, desc = key
print(f'{ref_id} {desc}')
if verbose:
pprint.pprint(trans)
count = collections.Counter([ref_id for ref_id, _ in records])
dupes = [id for id, total in count.items() if total > 1]
print(f'Reference IDs with changing subject: {dupes}', file=sys.stderr)
@ -251,7 +275,7 @@ def get_subscriptions_matching_subject(transactions, pattern):
return records
def report_on_transactions(transactions):
def report_on_transactions(transactions, verbose=False):
"""Print a formatted list of transactions."""
for t in transactions:
transaction_info = t['transaction_info']
@ -270,6 +294,8 @@ def report_on_transactions(transactions):
transaction_info['paypal_reference_id'],
)
)
if verbose:
pprint.pprint(t)
else:
print(
f'Skipping transaction {transaction_info["transaction_id"]} with no PayPal Reference ID.',
@ -317,3 +343,7 @@ def suspend_subscriptions_interactively(transactions, pattern, access_token):
if __name__ == '__main__':
main()
# Local Variables:
# fill-column: 80
# End: