Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
156 changes: 156 additions & 0 deletions thebook/integrations/paypal/services.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
123 changes: 0 additions & 123 deletions thebook/webhooks/paypal/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down