diff --git a/thebook/integrations/paypal/__init__.py b/thebook/integrations/paypal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/thebook/integrations/paypal/services.py b/thebook/integrations/paypal/services.py new file mode 100644 index 0000000..ea278f4 --- /dev/null +++ b/thebook/integrations/paypal/services.py @@ -0,0 +1,156 @@ +import datetime +import decimal + +import jmespath +import requests + +from django.conf import settings +from django.contrib.auth import get_user_model + +from thebook.bookkeeping.models import BankAccount, Category, Transaction + + +def _get_paypal_access_token(): + response = requests.post( + f"{settings.PAYPAL_API_BASE_URL}/v1/oauth2/token", + data={ + "grant_type": "client_credentials", + }, + auth=(settings.PAYPAL_CLIENT_ID, settings.PAYPAL_CLIENT_SECRET), + ) + auth_data = response.json() + return response.json().get("access_token") or "" + + +def _get_subscription(billing_agreement_id, access_token=None): + if access_token is None: + access_token = _get_paypal_access_token() + response = requests.get( + f"{settings.PAYPAL_API_BASE_URL}/v1/billing/subscriptions/{billing_agreement_id}", + headers={"Authorization": f"Bearer {access_token}"}, + ) + subscription = response.json() + return subscription + + +def fetch_transactions(start_date: datetime.date, end_date: datetime.date): + results = [] + + bank_account, _ = BankAccount.objects.get_or_create( + name=settings.PAYPAL_BANK_ACCOUNT + ) + bank_fee_category, _ = Category.objects.get_or_create( + name=settings.BANK_FEE_CATEGORY_NAME + ) + bank_account_transfer_category, _ = Category.objects.get_or_create( + name="Transferência entre contas bancárias" + ) + + user = get_user_model().objects.get_or_create_automation_user() + access_token = _get_paypal_access_token() + + params = { + "start_date": start_date.strftime("%Y-%m-%dT00:00:00-00:00"), + "end_date": end_date.strftime("%Y-%m-%dT23:59:59-00:00"), + } + url = f"{settings.PAYPAL_API_BASE_URL}/v1/reporting/transactions" + response = requests.get( + url, + headers={"Authorization": f"Bearer {access_token}"}, + params=params, + ) + data = response.json() + + for transaction in jmespath.search("transaction_details", data) or []: + transaction_status = jmespath.search( + "transaction_info.transaction_status", transaction + ) + if transaction_status != "S": + # https://developer.paypal.com/docs/api/transaction-search/v1/#search_get!in=query&path=transaction_status&t=request + continue + + transaction_id = jmespath.search("transaction_info.transaction_id", transaction) + if Transaction.objects.filter(reference=transaction_id).exists(): + continue + + transaction_currency_code = jmespath.search( + "transaction_info.transaction_amount.currency_code", transaction + ) + if transaction_currency_code == "USD": + # TODO - Process USD transactions + continue + transaction_amount = decimal.Decimal( + jmespath.search("transaction_info.transaction_amount.value", transaction) + ) + + raw_date = jmespath.search( + "transaction_info.transaction_initiation_date", transaction + ) + transaction_date = datetime.datetime.strptime( + raw_date, "%Y-%m-%dT%H:%M:%SZ" + ).date() + transaction_fee_amount = decimal.Decimal( + jmespath.search("transaction_info.fee_amount.value", transaction) or "0" + ) + + transaction_type = jmespath.search( + "transaction_info.transaction_event_code", transaction + ) + is_bank_account_transfer = transaction_type in ("T0400", "T0403") + if is_bank_account_transfer: + transaction_category = bank_account_transfer_category + transaction_description = ( + f"Transferência entre contas bancárias - {transaction_id}" + ) + else: + transaction_category = None + transaction_description = jmespath.search( + "transaction_info.transaction_subject", transaction + ) + + if transaction_type == "T0002": + # This flow only applies to Subscription payment + # https://developer.paypal.com/docs/transaction-search/transaction-event-codes/ + paypal_reference_id = jmespath.search( + "transaction_info.paypal_reference_id", transaction + ) + subscription = _get_subscription(paypal_reference_id, access_token) + given_name = ( + jmespath.search("subscriber.name.given_name", subscription) or "" + ) + surname = jmespath.search("subscriber.name.surname", subscription) or "" + full_name = " ".join([given_name, surname]).strip() + payer_id = jmespath.search("subscriber.payer_id", subscription) or "" + description_parts = (full_name, payer_id) + transaction_description = " - ".join( + [part for part in description_parts if part] + ) + + results.append( + Transaction( + reference=transaction_id, + date=transaction_date, + description=transaction_description, + amount=transaction_amount, + bank_account=bank_account, + category=transaction_category, + source="paypal-fetch-transactions", + created_by=user, + ) + ) + + if not is_bank_account_transfer: + results.append( + Transaction( + reference=f"{transaction_id}-T", + date=transaction_date, + description=f"Taxa Paypal - {transaction_description}", + amount=transaction_fee_amount, + bank_account=bank_account, + category=bank_fee_category, + source="paypal-fetch-transactions", + created_by=user, + ) + ) + + return results diff --git a/thebook/webhooks/tests/paypal/sample_payloads/paypal__brl_payload__subscription.json b/thebook/integrations/tests/paypal/sample_payloads/paypal__brl_payload__subscription.json similarity index 100% rename from thebook/webhooks/tests/paypal/sample_payloads/paypal__brl_payload__subscription.json rename to thebook/integrations/tests/paypal/sample_payloads/paypal__brl_payload__subscription.json diff --git a/thebook/webhooks/tests/paypal/sample_payloads/paypal__usd_payload__subscription.json b/thebook/integrations/tests/paypal/sample_payloads/paypal__usd_payload__subscription.json similarity index 100% rename from thebook/webhooks/tests/paypal/sample_payloads/paypal__usd_payload__subscription.json rename to thebook/integrations/tests/paypal/sample_payloads/paypal__usd_payload__subscription.json diff --git a/thebook/webhooks/tests/paypal/sample_payloads/reporting_transactions__multiple_transactions.json b/thebook/integrations/tests/paypal/sample_payloads/reporting_transactions__multiple_transactions.json similarity index 100% rename from thebook/webhooks/tests/paypal/sample_payloads/reporting_transactions__multiple_transactions.json rename to thebook/integrations/tests/paypal/sample_payloads/reporting_transactions__multiple_transactions.json diff --git a/thebook/webhooks/tests/paypal/sample_payloads/reporting_transactions__one_bank_account_transfer_transaction.json b/thebook/integrations/tests/paypal/sample_payloads/reporting_transactions__one_bank_account_transfer_transaction.json similarity index 100% rename from thebook/webhooks/tests/paypal/sample_payloads/reporting_transactions__one_bank_account_transfer_transaction.json rename to thebook/integrations/tests/paypal/sample_payloads/reporting_transactions__one_bank_account_transfer_transaction.json diff --git a/thebook/webhooks/tests/paypal/sample_payloads/reporting_transactions__one_common_transaction.json b/thebook/integrations/tests/paypal/sample_payloads/reporting_transactions__one_common_transaction.json similarity index 100% rename from thebook/webhooks/tests/paypal/sample_payloads/reporting_transactions__one_common_transaction.json rename to thebook/integrations/tests/paypal/sample_payloads/reporting_transactions__one_common_transaction.json diff --git a/thebook/webhooks/tests/paypal/sample_payloads/reporting_transactions__one_transaction.json b/thebook/integrations/tests/paypal/sample_payloads/reporting_transactions__one_transaction.json similarity index 100% rename from thebook/webhooks/tests/paypal/sample_payloads/reporting_transactions__one_transaction.json rename to thebook/integrations/tests/paypal/sample_payloads/reporting_transactions__one_transaction.json diff --git a/thebook/webhooks/tests/paypal/sample_payloads/reporting_transactions__usd_transaction.json b/thebook/integrations/tests/paypal/sample_payloads/reporting_transactions__usd_transaction.json similarity index 100% rename from thebook/webhooks/tests/paypal/sample_payloads/reporting_transactions__usd_transaction.json rename to thebook/integrations/tests/paypal/sample_payloads/reporting_transactions__usd_transaction.json diff --git a/thebook/webhooks/tests/paypal/sample_payloads/webhook__brl_payload.json b/thebook/integrations/tests/paypal/sample_payloads/webhook__brl_payload.json similarity index 100% rename from thebook/webhooks/tests/paypal/sample_payloads/webhook__brl_payload.json rename to thebook/integrations/tests/paypal/sample_payloads/webhook__brl_payload.json diff --git a/thebook/webhooks/tests/paypal/sample_payloads/webhook__usd_payload.json b/thebook/integrations/tests/paypal/sample_payloads/webhook__usd_payload.json similarity index 100% rename from thebook/webhooks/tests/paypal/sample_payloads/webhook__usd_payload.json rename to thebook/integrations/tests/paypal/sample_payloads/webhook__usd_payload.json diff --git a/thebook/webhooks/tests/paypal/test_services__fetch_transactions.py b/thebook/integrations/tests/paypal/test_services__fetch_transactions.py similarity index 99% rename from thebook/webhooks/tests/paypal/test_services__fetch_transactions.py rename to thebook/integrations/tests/paypal/test_services__fetch_transactions.py index 5ad7b0a..05deef7 100644 --- a/thebook/webhooks/tests/paypal/test_services__fetch_transactions.py +++ b/thebook/integrations/tests/paypal/test_services__fetch_transactions.py @@ -10,7 +10,7 @@ from django.contrib.auth import get_user_model from thebook.bookkeeping.models import BankAccount, Category, Transaction -from thebook.webhooks.paypal.services import fetch_transactions +from thebook.integrations.paypal.services import fetch_transactions SAMPLE_PAYLOADS_DIR = Path(__file__).parent / "sample_payloads" diff --git a/thebook/webhooks/management/commands/fetch_paypal_transactions.py b/thebook/webhooks/management/commands/fetch_paypal_transactions.py index e418d40..aeae538 100644 --- a/thebook/webhooks/management/commands/fetch_paypal_transactions.py +++ b/thebook/webhooks/management/commands/fetch_paypal_transactions.py @@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand -from thebook.webhooks.paypal.services import fetch_transactions +from thebook.integrations.paypal.services import fetch_transactions class Command(BaseCommand): diff --git a/thebook/webhooks/paypal/services.py b/thebook/webhooks/paypal/services.py index 728eb67..4e591f5 100644 --- a/thebook/webhooks/paypal/services.py +++ b/thebook/webhooks/paypal/services.py @@ -39,129 +39,6 @@ def _get_subscription(billing_agreement_id, access_token=None): return subscription -def fetch_transactions(start_date: datetime.date, end_date: datetime.date): - results = [] - - bank_account, _ = BankAccount.objects.get_or_create( - name=settings.PAYPAL_BANK_ACCOUNT - ) - bank_fee_category, _ = Category.objects.get_or_create( - name=settings.BANK_FEE_CATEGORY_NAME - ) - bank_account_transfer_category, _ = Category.objects.get_or_create( - name="Transferência entre contas bancárias" - ) - - user = get_user_model().objects.get_or_create_automation_user() - access_token = _get_paypal_access_token() - - params = { - "start_date": start_date.strftime("%Y-%m-%dT00:00:00-00:00"), - "end_date": end_date.strftime("%Y-%m-%dT23:59:59-00:00"), - } - url = f"{settings.PAYPAL_API_BASE_URL}/v1/reporting/transactions" - response = requests.get( - url, - headers={"Authorization": f"Bearer {access_token}"}, - params=params, - ) - data = response.json() - - for transaction in jmespath.search("transaction_details", data) or []: - transaction_status = jmespath.search( - "transaction_info.transaction_status", transaction - ) - if transaction_status != "S": - # https://developer.paypal.com/docs/api/transaction-search/v1/#search_get!in=query&path=transaction_status&t=request - continue - - transaction_id = jmespath.search("transaction_info.transaction_id", transaction) - if Transaction.objects.filter(reference=transaction_id).exists(): - continue - - transaction_currency_code = jmespath.search( - "transaction_info.transaction_amount.currency_code", transaction - ) - if transaction_currency_code == "USD": - # TODO - Process USD transactions - continue - transaction_amount = decimal.Decimal( - jmespath.search("transaction_info.transaction_amount.value", transaction) - ) - - raw_date = jmespath.search( - "transaction_info.transaction_initiation_date", transaction - ) - transaction_date = datetime.datetime.strptime( - raw_date, "%Y-%m-%dT%H:%M:%SZ" - ).date() - transaction_fee_amount = decimal.Decimal( - jmespath.search("transaction_info.fee_amount.value", transaction) or "0" - ) - - transaction_type = jmespath.search( - "transaction_info.transaction_event_code", transaction - ) - is_bank_account_transfer = transaction_type in ("T0400", "T0403") - if is_bank_account_transfer: - transaction_category = bank_account_transfer_category - transaction_description = ( - f"Transferência entre contas bancárias - {transaction_id}" - ) - else: - transaction_category = None - transaction_description = jmespath.search( - "transaction_info.transaction_subject", transaction - ) - - if transaction_type == "T0002": - # This flow only applies to Subscription payment - # https://developer.paypal.com/docs/transaction-search/transaction-event-codes/ - paypal_reference_id = jmespath.search( - "transaction_info.paypal_reference_id", transaction - ) - subscription = _get_subscription(paypal_reference_id, access_token) - given_name = ( - jmespath.search("subscriber.name.given_name", subscription) or "" - ) - surname = jmespath.search("subscriber.name.surname", subscription) or "" - full_name = " ".join([given_name, surname]).strip() - payer_id = jmespath.search("subscriber.payer_id", subscription) or "" - description_parts = (full_name, payer_id) - transaction_description = " - ".join( - [part for part in description_parts if part] - ) - - results.append( - Transaction( - reference=transaction_id, - date=transaction_date, - description=transaction_description, - amount=transaction_amount, - bank_account=bank_account, - category=transaction_category, - source="paypal-fetch-transactions", - created_by=user, - ) - ) - - if not is_bank_account_transfer: - results.append( - Transaction( - reference=f"{transaction_id}-T", - date=transaction_date, - description=f"Taxa Paypal - {transaction_description}", - amount=transaction_fee_amount, - bank_account=bank_account, - category=bank_fee_category, - source="paypal-fetch-transactions", - created_by=user, - ) - ) - - return results - - def _extract_amount(payload): currency = jmespath.search("resource.amount.currency", payload)