-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Background
New EU legislation means that online card payment has to go through the issuer's 2FA. This is known as "Strong Customer Authentication". Some background here: https://support.stripe.com/questions/strong-customer-authentication-sca-enforcement-date
Solution
AFAIK, in order to process payments for fundraising, Django needs to use newer API and patterns offered by Stripe. I have an existing implementation to refer to.
Firstly, a checkout session is created and the user goes through Stripe's pages for payments. This is almost the same as before, except there is no modal popup on the shop's own site, but you to through a branded Stripe page.
Following this, the main change is that now the backend processes the payment through an async callback to a webhook which it receives from Stripe.
In the frontend, the user is redirected to a custom success/failure page. But AFAIK, this page has to be generic because it cannot assume that the payment is successful until the webhook is called.
Here is an implementation of a webhook that processes the successful session:
import stripe
# ...
def stripe_config(currency):
"""
Sets configuration of stripe module according to the currency that we are
using - if for instance you have a Stripe account for Euro payments and
one for Dollar payments
"""
config = settings.STRIPE[currency]
if settings.DEBUG:
stripe.api_key = config["api_key_test"]
else:
stripe.api_key = config["api_key"]
return config
@csrf_exempt
def stripe_callback(request, currency):
"""
Handle callbacks from Stripe, for instance /payment/stripe/webhook/usd/
"""
payload = request.body
sig_header = request.META["HTTP_STRIPE_SIGNATURE"]
event = None
# Call stripe_config - in case
config = stripe_config(currency)
try:
# Notice that the endpoint itself has a secret known only to the endpoint and Stripe!
event = stripe.Webhook.construct_event(
payload, sig_header, config["endpoint_secret"]
)
except ValueError:
# Invalid payload
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError:
# Invalid signature
return HttpResponse(status=400)
# Handle the checkout.session.completed event
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
# Activate original language of this session
if "metadata" in session and "language" in session["metadata"]:
translation.activate(session["metadata"]["language"])
if settings.DEBUG:
order_id = 123
else:
# In this example, we stored an order ID with the payment
order_id = session["client_reference_id"]
order = models.Order.objects.get(pk=order_id)
# Create a payment object with information from the Stripe session and
# mark the order as paid, then notify customer and admins.
try:
payment = models.Payment.from_order(order)
payment.stripe_session_id = session["id"]
payment.save()
order.is_paid = True
order.save()
# Inform admins
mail_admins("Card payment success", "Payment ID: {}".format(payment.pk))
# Inform customer
m = mail.PaymentCreateMail(
request, payment=payment, to=[payment.order.email]
)
m.send()
except Exception as e:
# The card has been declined
mail_admins("Card payment err", str(e))
raise
else:
mail_admins("Card payment unknown payload", str(event))
return HttpResponse(status=200)
Reference
- Creating a checkout session: https://stripe.com/docs/payments/accept-a-payment
- Webhooks: https://stripe.com/docs/payments/handling-payment-events