|
|
|
@ -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 = '''
|
|
|
|
|
<form action="/payments/callback/stripe/{id}" method="POST">
|
|
|
|
|
<script
|
|
|
|
|
src="https://checkout.stripe.com/checkout.js" class="stripe-button"
|
|
|
|
|
data-key="test_public_key"
|
|
|
|
|
data-image=""
|
|
|
|
|
data-name="Test Name"
|
|
|
|
|
data-currency="EUR"
|
|
|
|
|
data-description="30 days, 0:00:00 for test"
|
|
|
|
|
data-amount="300"
|
|
|
|
|
data-email="test_user@example.com"
|
|
|
|
|
data-locale="auto"
|
|
|
|
|
data-zip-code="true"
|
|
|
|
|
data-alipay="true">
|
|
|
|
|
</script>
|
|
|
|
|
</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"
|
|
|
|
|
}
|
|
|
|
|