Skip to content

Commit

Permalink
Automated PayPal payouts
Browse files Browse the repository at this point in the history
  • Loading branch information
davidfischer committed Jan 31, 2025
1 parent bafaf6f commit 9a8e6f7
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 0 deletions.
71 changes: 71 additions & 0 deletions adserver/staff/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Views for the administrator actions."""

import datetime
import json
import logging

import requests
import stripe
from django.conf import settings
from django.contrib import messages
Expand All @@ -23,6 +25,7 @@
from adserver.utils import generate_publisher_payout_data

from ..constants import PAID
from ..constants import PAYOUT_PAYPAL
from ..constants import PAYOUT_STRIPE
from ..constants import PUBLISHER_PAYOUT_METHODS
from ..mixins import StaffUserMixin
Expand Down Expand Up @@ -293,6 +296,12 @@ def post(self, request, *args, **kwargs):
transfer = self.pay_via_stripe_connect(self.payout)
self.payout.note = f"Stripe Transfer: { transfer.id }"
messages.success(self.request, _("Successfully paid via Stripe Connect"))
elif self.payout.method == PAYOUT_PAYPAL and self.request.POST.get(
"paypal-payout-confirm"
):
transfer_id = self.pay_via_paypal(self.payout)
self.payout.note = f"Paypal Transfer: { transfer_id }"
messages.success(self.request, _("Successfully paid via PayPal"))

self.payout.status = PAID
self.payout.save()
Expand All @@ -314,3 +323,65 @@ def pay_via_stripe_connect(self, payout):
transfer_group=f"PublisherPayout-{ self.payout.id }",
)
return Transfer.sync_from_stripe_data(xfer)

def pay_via_paypal(self, payout):
"""
Perform a PayPal payout.
See: https://developer.paypal.com/docs/api/payments.payouts-batch/v1/
"""
publisher = payout.publisher
paypal_email = publisher.paypal_email
amount = str(round(payout.amount, 2)) # PayPal wants the amount as a string

if settings.DEBUG:
paypal_api_root = "https://api.sandbox.paypal.com"
else:
paypal_api_root = "https://api.paypal.com"

# https://developer.paypal.com/api/rest/authentication/
oauth_url = f"{paypal_api_root}/v1/oauth2/token"
payload = "grant_type=client_credentials"
headers = {
"accept": "application/json",
"accept-language": "en_US",
"content-type": "application/x-www-form-urlencoded",
}
auth = (settings.PAYPAL_CLIENT_ID, settings.PAYPAL_SECRET_KEY)

# Get an OAuth token
# Raises an error on a PayPal failure
resp = requests.post(oauth_url, data=payload, headers=headers, auth=auth)
resp.raise_for_status()
access_token = resp.json()["access_token"]

# Make the actual payout
# https://developer.paypal.com/docs/api/payments.payouts-batch/v1/
payout_url = "{paypal_api_root}/v1/payments/payouts"
headers = {
"accept": "application/json",
"authorization": f"Bearer {access_token}",
"content-type": "application/json",
}
payload = {
"sender_batch_header": {
"email_subject": f"EthicalAds Payout - {publisher}",
"sender_batch_id": f"payout-{str(payout.id)}",
},
"items": [
{
"recipient_type": "EMAIL",
"amount": {
"value": amount,
"currency": "USD",
},
"receiver": paypal_email,
"note": f"EthicalAds Payout - {publisher}",
"purpose": "SERVICES",
},
],
}
resp = requests.post(payout_url, data=json.dumps(payload), headers=headers)
resp.raise_for_status()

return resp.json()["batch_header"]["payout_batch_id"]
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ <h1>{% block heading %}{% trans 'Finish Payout' %}{% endblock heading %}</h1>
</label>
</div>

<a href="{{ payout.publisher.payout_url }}" class="btn btn-sm btn-outline-secondary">{% trans "Or Send Manually" %}</a>
{% elif payout.method == "paypal" %}
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="paypal-payout-confirm" name="paypal-payout-confirm">
<label class="form-check-label" for="paypal-payout-confirm">
<span>{% blocktrans with amount=payout.amount|floatformat:2|intcomma %}Send ${{ amount }} via PayPal ($0.25 fee){% endblocktrans %}</span>
</label>
</div>

<a href="{{ payout.publisher.payout_url }}" class="btn btn-sm btn-outline-secondary">{% trans "Or Send Manually" %}</a>
{% else %}
<a href="{{ payout.publisher.payout_url }}" class="btn btn-sm btn-outline-secondary">{% trans "Send Money" %}</a>
Expand Down
6 changes: 6 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,12 @@
stripe.api_key = None
stripe.api_version = "2020-08-27"

# PayPal
# Used for automated PayPal payouts only
# --------------------------------------------------------------------------
PAYPAL_CLIENT_ID = env("PAYPAL_CLIENT_ID", default=None)
PAYPAL_SECRET_KEY = env("PAYPAL_SECRET_KEY", default=None)


# Slack
# Sending slack notifications
Expand Down

0 comments on commit 9a8e6f7

Please sign in to comment.