diff --git a/payments/tests/stripe.py b/payments/tests/stripe.py index 91e7603..773550c 100644 --- a/payments/tests/stripe.py +++ b/payments/tests/stripe.py @@ -3,10 +3,32 @@ from datetime import timedelta from django.test import TestCase, RequestFactory from django.contrib.auth.models import User -from payments.models import Payment +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) @@ -18,6 +40,9 @@ class StripeBackendTest(TestCase): backend_id='stripe', amount=300 ) + payment.save() + + sess_id = 'cs_test_UKHmmetjaiperdu0wxGppeerrdduuhSslYaaaaaa0eU6aaaaaaaaaWSX' settings = dict( secret_key='test_secret_key', @@ -27,56 +52,349 @@ class StripeBackendTest(TestCase): 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'): - backend = StripeBackend(settings) form_html = backend.new_payment(payment) - expected_form = ''' -
- -
- '''.format(id=payment.id) - self.maxDiff = None - self.assertEqual(expected_form, form_html) - - def create_charge(**kwargs): - self.assertEqual(kwargs, { - 'amount': 300, - 'currency': 'EUR', - 'card': 'TEST_TOKEN', - 'description': "1 months for test", - }) - return { - 'id': 'TEST_CHARGE_ID', - 'refunded': False, - 'paid': True, - 'amount': 300, - } - - # Replace the Stripe api instance + 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, ), { - 'Charge': type('Charge', (object, ), { - 'create': create_charge, + 'checkout': type('checkout', (object, ), { + 'Session': MockSession("in_FjSMAw7ufaEBOG"), }), + 'Webhook': MockWebhook, + 'error': type('error', (object, ), { + 'InvalidRequestError': type('', (Exception, ), {}), + 'SignatureVerificationError': type('', (Exception, ), {}), 'CardError': type('CardError', (Exception, ), {}), }), }) - request = RequestFactory().post('', {'stripeToken': 'TEST_TOKEN'}) - backend.callback(payment, request) + 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" +} - self.assertEqual(payment.backend_extid, 'TEST_CHARGE_ID') +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" +}