def stripe_webhook(request): payload = request.body sig_header = request.META.get("HTTP_STRIPE_SIGNATURE") if not payload or not sig_header: return HttpResponse("[invalid payload]", status=400) try: event = stripe.Webhook.construct_event( payload, sig_header, settings.STRIPE_WEBHOOK_SECRET ) except ValueError: return HttpResponse("[invalid payload]", status=400) except stripe.error.SignatureVerificationError: return HttpResponse("[invalid signature]", status=400) if event["type"] == "checkout.session.completed": session = event["data"]["object"] payment = Payment.finish( reference=session["id"], status=Payment.PAYMENT_STATUS_SUCCESS, data=session, ) product = PRODUCTS[payment.product_code] product["activator"](product, payment, payment.user) return HttpResponse("[ok]", status=200)
def stripe_webhook(request): payload = request.body sig_header = request.META.get("HTTP_STRIPE_SIGNATURE") if not payload or not sig_header: return HttpResponse("[invalid payload]", status=400) try: event = stripe.Webhook.construct_event( payload, sig_header, settings.STRIPE_WEBHOOK_SECRET ) except ValueError: return HttpResponse("[invalid payload]", status=400) except stripe.error.SignatureVerificationError: return HttpResponse("[invalid signature]", status=400) log.info("Stripe webhook event: " + event["type"]) if event["type"] == "checkout.session.completed": session = event["data"]["object"] try: payment = Payment.finish( reference=session["id"], status=Payment.STATUS_SUCCESS, data=session, ) except PaymentException: return HttpResponse("[payment not found]", status=400) product = PRODUCTS[payment.product_code] product["activator"](product, payment, payment.user) return HttpResponse("[ok]", status=200) if event["type"] == "invoice.paid": invoice = event["data"]["object"] if invoice["billing_reason"] == "subscription_create": # already processed in "checkout.session.completed" event return HttpResponse("[ok]", status=200) user = User.objects.filter(stripe_id=invoice["customer"]).first() # todo: do we need throw error in case user not found? payment = Payment.create( reference=invoice["id"], user=user, product=find_by_stripe_id(invoice["lines"]["data"][0]["plan"]["id"]), data=invoice, status=Payment.STATUS_SUCCESS, ) product = PRODUCTS[payment.product_code] product["activator"](product, payment, user) return HttpResponse("[ok]", status=200) if event["type"] in {"customer.created", "customer.updated"}: customer = event["data"]["object"] User.objects.filter(email=customer["email"]).update(stripe_id=customer["id"]) return HttpResponse("[ok]", status=200) return HttpResponse("[unknown event]", status=400)
def test_finish_payment_positive(self): result: Payment = Payment.finish( reference=self.existed_payment.reference, status=Payment.STATUS_SUCCESS, data={"some": "data"}) self.assertIsNotNone(result) # check it persistent payment = Payment.get(reference=result.reference) self.assertIsNotNone(payment) self.assertEqual(payment.id, self.existed_payment.id) self.assertEqual(payment.status, Payment.STATUS_SUCCESS) self.assertEqual(payment.data, '{"some": "data"}')
def test_finis_payment_not_existed_payment(self): result = Payment.finish(reference="wrong-not-existed-reference", status=Payment.STATUS_FAILED, data={"some": "data"}) self.assertIsNone(result)
def coinbase_webhook(request): payload = request.body webhook_signature = request.META.get("HTTP_X_CC_WEBHOOK_SIGNATURE") if not payload or not webhook_signature: return HttpResponse("[invalid payload]", status=400) # verify webhook signature payload_signature = hmac.new( key=bytes(settings.COINBASE_WEBHOOK_SECRET, "utf-8"), msg=payload, # it's already in bytes digestmod=hashlib.sha256 ).hexdigest() if payload_signature.upper() != webhook_signature.upper(): return HttpResponse("[bad signature]", status=400) # load event data try: data = json.loads(payload) except json.JSONDecodeError: return HttpResponse("[payload is not json]", status=400) event = data.get("event") event_type = event.get("type") event_data = event.get("data") event_code = event_data.get("code") if not event or not event_type or not event_data or not event_code: return HttpResponse("[bad payload structure]", status=400) log.info(f"Coinbase webhook event: {event_type} ({event_code})") # find or create the user metadata_email = event_data.get("metadata", {}).get("email") if not metadata_email: return HttpResponse("[no email in payload]", status=400) now = datetime.utcnow() user, _ = User.objects.get_or_create( email=metadata_email, defaults=dict( membership_platform_type=User.MEMBERSHIP_PLATFORM_CRYPTO, full_name=metadata_email[:metadata_email.find("@")], membership_started_at=now, membership_expires_at=now, created_at=now, updated_at=now, moderation_status=User.MODERATION_STATUS_INTRO, ), ) # find product checkout_id = event_data.get("checkout", {}).get("id") product = find_by_coinbase_id(checkout_id) if not checkout_id or not product: return HttpResponse("[product not found]", status=404) # make actions for event_types if event_type == "charge:created": Payment.create( reference=event_code, user=user, product=product, data=event, status=Payment.STATUS_STARTED, ) return HttpResponse("[ok]", status=200) elif event_type == "charge:confirmed": try: payment = Payment.finish( reference=event_code, status=Payment.STATUS_SUCCESS, data=event, ) except PaymentNotFound: payment = Payment.create( reference=event_code, user=user, product=product, data=event, status=Payment.STATUS_SUCCESS, ) except PaymentAlreadyFinalized: return HttpResponse("[duplicate payment]", status=400) product["activator"](product, payment, user) return HttpResponse("[ok]", status=200) elif event_type == "charge:failed": Payment.finish( reference=event_code, status=Payment.STATUS_FAILED, data=event, ) return HttpResponse("[ok]", status=200) elif event_type == "charge:pending": return HttpResponse("[ok]", status=200) return HttpResponse("[unknown event]", status=400)
def test_finish_non_existent_payment_exception(self): with self.assertRaises(PaymentNotFound): result = Payment.finish(reference="wrong-not-existed-reference", status=Payment.STATUS_FAILED, data={"some": "data"})