from datetime import timedelta from django.test import TestCase, RequestFactory from django.contrib.auth.models import User from payments.models import Payment, Subscription from payments.backends import StripeBackend def MockSession(id): class MockSession(dict): def __init__(self, args): args['id'] = id super().__init__(args) @classmethod def create(cls, **kwargs): c = MockSession(kwargs) cls._INST = c return c return MockSession class MockWebhook: @classmethod def construct_event(cls, payload, sig, key): assert sig == "hewwo", sig assert key == "test_wh_key", key assert payload == b"opak", payload return cls.evt class StripeBackendTest(TestCase): def setUp(self): self.user = User.objects.create_user('test', 'test_user@example.com', None) def test_stripe(self): payment = Payment.objects.create( user=self.user, time=timedelta(days=30), backend_id='stripe', amount=300 ) payment.save() sess_id = 'cs_test_UKHmmetjaiperdu0wxGppeerrdduuhSslYaaaaaa0eU6aaaaaaaaaWSX' settings = dict( secret_key='test_secret_key', public_key='test_public_key', wh_key='test_wh_key', currency='EUR', name='Test Name', ) backend = StripeBackend(settings) assert backend.backend_enabled backend.stripe = type('Stripe', (object, ), { 'checkout': type('checkout', (object, ), { 'Session': MockSession(sess_id), }), 'Webhook': MockWebhook, 'error': type('error', (object, ), { 'InvalidRequestError': type('', (Exception, ), {}), 'SignatureVerificationError': type('', (Exception, ), {}), 'CardError': type('CardError', (Exception, ), {}), }), }) with self.settings(ROOT_URL='root'): form_html = backend.new_payment(payment) assert 'test_public_key' in form_html sess = backend.stripe.checkout.Session._INST self.assertEqual(sess['success_url'], 'root/payments/view/%d' % payment.id) self.assertEqual(sess['line_items'][0]['amount'], payment.amount) request = RequestFactory().post('', 'opak', content_type='text/plain', HTTP_STRIPE_SIGNATURE="hewwo") backend.stripe.Webhook.evt = CHECKOUT_SESSION_COMPLETED__ONE backend.webhook(request) payment = Payment.objects.get(id=payment.id) self.assertEqual(payment.status, 'confirmed') self.assertEqual(payment.paid_amount, 300) self.assertEqual(payment.backend_extid, sess_id) def test_stripe_sub(self): sub = Subscription.objects.create( user=self.user, period='3m', backend_id='stripe', ) settings = dict( secret_key='test_secret_key', public_key='test_public_key', wh_key='test_wh_key', currency='EUR', name='Test Name', ) backend = StripeBackend(settings) assert backend.backend_enabled backend.stripe = type('Stripe', (object, ), { 'checkout': type('checkout', (object, ), { 'Session': MockSession("in_FjSMAw7ufaEBOG"), }), 'Webhook': MockWebhook, 'error': type('error', (object, ), { 'InvalidRequestError': type('', (Exception, ), {}), 'SignatureVerificationError': type('', (Exception, ), {}), 'CardError': type('CardError', (Exception, ), {}), }), }) with self.settings(ROOT_URL='root'): form_html = backend.new_subscription(sub) assert 'test_public_key' in form_html sess = backend.stripe.checkout.Session._INST self.assertEqual(sess['success_url'], 'root/payments/return_subscr/%d' % sub.id) self.assertEqual(sess['subscription_data']['items'][0]['plan'], 'ccvpn_3m') request = RequestFactory().post('', 'opak', content_type='text/plain', HTTP_STRIPE_SIGNATURE="hewwo") backend.stripe.Webhook.evt = checkout_session_completed__sub(sub) backend.webhook(request) backend.stripe.Webhook.evt = INVOICE_PAYMENT_SUCCEEDED backend.webhook(request) sub = Subscription.objects.get(id=sub.id) payment = sub.payment_set.all()[0] self.assertEqual(sub.status, 'active') self.assertEqual(payment.status, 'confirmed') self.assertEqual(payment.paid_amount, 900) self.assertEqual(payment.backend_extid, 'in_FjSMAw7ufaEBOG') CHECKOUT_SESSION_COMPLETED__ONE = { "id": "evt_FkzwrsBSh0da66", "object": "event", "api_version": "2019-08-14", "created": 1567705834, "data": { "object": { "id": "cs_test_UKHmmetjaiperdu0wxGppeerrdduuhSslYaaaaaa0eU6aaaaaaaaaWSX", "object": "checkout.session", "billing_address_collection": None, "cancel_url": "https://test/payments/cancel/19", "client_reference_id": None, "customer": "cus_FgusdomerZ8gdP", "customer_email": None, "display_items": [ { "amount": 300, "currency": "eur", "custom": { "description": "One month for admin", "images": None, "name": "VPN Payment" }, "quantity": 1, "type": "custom" } ], "livemode": False, "locale": None, "mode": "payment", "payment_intent": "pi_0FmmmmmmmmmmmmmmmpyplM53", "payment_method_types": [ "card" ], "setup_intent": None, "submit_type": None, "subscription": None, "success_url": "https://test/payments/view/19" } }, "livemode": False, "pending_webhooks": 1, "request": { "id": None, "idempotency_key": None }, "type": "checkout.session.completed" } def checkout_session_completed__sub(sub): return { "id": "evt_FjSIjKT3MvONr8", "object": "event", "api_version": "2019-08-14", "created": 1567368634, "data": { "object": { "id": "cs_test_03aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "object": "checkout.session", "billing_address_collection": None, "cancel_url": "https://test/payments/view/2", "client_reference_id": "sub_%d" % sub.id, "customer": "cus_FkSIg5BNIdrv6v", "customer_email": None, "display_items": [ { "amount": 900, "currency": "eur", "plan": { "id": "ccvpn_3m", "object": "plan", "active": True, "aggregate_usage": None, "amount": 900, "amount_decimal": "900", "billing_scheme": "per_unit", "created": 1557830755, "currency": "eur", "interval": "month", "interval_count": 3, "livemode": False, "metadata": { }, "nickname": None, "product": "prod_F062eB2kCukpUX", "tiers": None, "tiers_mode": None, "transform_usage": None, "trial_period_days": None, "usage_type": "licensed" }, "quantity": 1, "type": "plan" } ], "livemode": False, "locale": None, "mode": "subscription", "payment_intent": None, "payment_method_types": [ "card" ], "setup_intent": None, "submit_type": None, "subscription": "sub_Fjmeowmeowmeow", "success_url": "https://test/payments/return/2" } }, "livemode": False, "pending_webhooks": 1, "request": { "id": "req_xQY51312421312", "idempotency_key": None }, "type": "checkout.session.completed" } INVOICE_PAYMENT_SUCCEEDED = { "id": "evt_FjSMpT5zIn8G9z", "object": "event", "api_version": "2019-08-14", "created": 1567368905, "data": { "object": { "id": "in_FjSMAw7ufaEBOG", "object": "invoice", "account_country": "FR", "account_name": "Cognitive Cryptography", "amount_due": 900, "amount_paid": 900, "amount_remaining": 0, "application_fee_amount": None, "attempt_count": 1, "attempted": True, "auto_advance": False, "billing": "charge_automatically", "billing_reason": "subscription_create", "charge": "ch_FjSMFp6fbsc45t", "collection_method": "charge_automatically", "created": 1567368904, "currency": "eur", "custom_fields": None, "customer": "cus_FjSMgbAnXmL4Go", "customer_address": None, "customer_email": "test2@test.test", "customer_name": None, "customer_phone": None, "customer_shipping": None, "customer_tax_exempt": "none", "customer_tax_ids": [ ], "default_payment_method": None, "default_source": None, "default_tax_rates": [ ], "description": None, "discount": None, "due_date": None, "ending_balance": 0, "footer": None, "hosted_invoice_url": "https://pay.stripe.com/invoice/invst_wmhbGo7OofOofOofOofOofOofd", "invoice_pdf": "https://pay.stripe.com/invoice/invst_wmhbGo7OofOofOofOofOofOofd/pdf", "lines": { "object": "list", "data": [ { "id": "sli_c20baf5dcf8472", "object": "line_item", "amount": 900, "currency": "eur", "description": "1 × VPN Subscription (3m) (at €9.00 / every 3 months)", "discountable": True, "livemode": False, "metadata": { }, "period": { "end": 1575231304, "start": 1567368904 }, "plan": { "id": "ccvpn_3m", "object": "plan", "active": True, "aggregate_usage": None, "amount": 900, "amount_decimal": "900", "billing_scheme": "per_unit", "created": 1557830755, "currency": "eur", "interval": "month", "interval_count": 3, "livemode": False, "metadata": { }, "nickname": None, "product": "prod_F46LeBpkCuRpUX", "tiers": None, "tiers_mode": None, "transform_usage": None, "trial_period_days": None, "usage_type": "licensed" }, "proration": False, "quantity": 1, "subscription": "sub_Fjmeowmeowmeow", "subscription_item": "si_FjSMgsAsZURoWf", "tax_amounts": [ ], "tax_rates": [ ], "type": "subscription" } ], "has_more": False, "total_count": 1, "url": "/v1/invoices/in_FjSMAw7ufaEBOG/lines" }, "livemode": False, "metadata": { }, "next_payment_attempt": None, "number": "AD4E509A-0001", "paid": True, "payment_intent": "pi_0FDzS0UmBGT3Of2k4ol85K2G", "period_end": 1567368904, "period_start": 1567368904, "post_payment_credit_notes_amount": 0, "pre_payment_credit_notes_amount": 0, "receipt_number": None, "starting_balance": 0, "statement_descriptor": None, "status": "paid", "status_transitions": { "finalized_at": 1567368904, "marked_uncollectible_at": None, "paid_at": 1567368905, "voided_at": None }, "subscription": "sub_Fjmeowmeowmeow", "subtotal": 900, "tax": None, "tax_percent": None, "total": 900, "total_tax_amounts": [ ], "webhooks_delivered_at": None } }, "livemode": False, "pending_webhooks": 1, "request": { "id": "req_6O128256512102", "idempotency_key": None }, "type": "invoice.payment_succeeded" }