diff --git a/paypal_report.py b/paypal_report.py index 4bda855..9ee5508 100755 --- a/paypal_report.py +++ b/paypal_report.py @@ -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 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 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: