Stripe checkout #104411
59
stripe_create_checkout_session.yaml
Normal file
59
stripe_create_checkout_session.yaml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
responses:
|
||||||
|
- response:
|
||||||
|
auto_calculate_content_length: false
|
||||||
|
body: "{\n \"id\": \"cus_QFaxRrT6ZbD9NN\",\n \"object\": \"customer\",\n \"\
|
||||||
|
address\": null,\n \"balance\": 0,\n \"created\": 1717778124,\n \"currency\"\
|
||||||
|
: null,\n \"default_source\": null,\n \"delinquent\": false,\n \"description\"\
|
||||||
|
: null,\n \"discount\": null,\n \"email\": \"my.billing.email@example.com\"\
|
||||||
|
,\n \"invoice_prefix\": \"046F772E\",\n \"invoice_settings\": {\n \"custom_fields\"\
|
||||||
|
: null,\n \"default_payment_method\": null,\n \"footer\": null,\n \"\
|
||||||
|
rendering_options\": null\n },\n \"livemode\": false,\n \"metadata\": {},\n\
|
||||||
|
\ \"name\": \"New Full Name\",\n \"phone\": null,\n \"preferred_locales\"\
|
||||||
|
: [],\n \"shipping\": null,\n \"tax_exempt\": \"none\",\n \"test_clock\"\
|
||||||
|
: null\n}"
|
||||||
|
content_type: text/plain
|
||||||
|
method: POST
|
||||||
|
status: 200
|
||||||
|
url: https://api.stripe.com/v1/customers
|
||||||
|
- response:
|
||||||
|
auto_calculate_content_length: false
|
||||||
|
body: "{\n \"id\": \"cs_test_a1hoP4Yj4ZmfghAwGoUtWJngVt1XreEVLGAj2n7U5o9BlvqhnDimuA07zh\"\
|
||||||
|
,\n \"object\": \"checkout.session\",\n \"after_expiration\": null,\n \"\
|
||||||
|
allow_promotion_codes\": null,\n \"amount_subtotal\": 990,\n \"amount_total\"\
|
||||||
|
: 990,\n \"automatic_tax\": {\n \"enabled\": false,\n \"liability\":\
|
||||||
|
\ null,\n \"status\": null\n },\n \"billing_address_collection\": null,\n\
|
||||||
|
\ \"cancel_url\": \"http://testserver/join/plan-variation/2/billing/\",\n \
|
||||||
|
\ \"client_reference_id\": null,\n \"client_secret\": null,\n \"consent\"\
|
||||||
|
: null,\n \"consent_collection\": null,\n \"created\": 1717778125,\n \"currency\"\
|
||||||
|
: \"eur\",\n \"currency_conversion\": null,\n \"custom_fields\": [],\n \"\
|
||||||
|
custom_text\": {\n \"after_submit\": null,\n \"shipping_address\": null,\n\
|
||||||
|
\ \"submit\": null,\n \"terms_of_service_acceptance\": null\n },\n \"\
|
||||||
|
customer\": \"cus_QFaxRrT6ZbD9NN\",\n \"customer_creation\": null,\n \"customer_details\"\
|
||||||
|
: {\n \"address\": null,\n \"email\": \"my.billing.email@example.com\"\
|
||||||
|
,\n \"name\": null,\n \"phone\": null,\n \"tax_exempt\": \"none\",\n\
|
||||||
|
\ \"tax_ids\": null\n },\n \"customer_email\": null,\n \"expires_at\"\
|
||||||
|
: 1717864524,\n \"invoice\": null,\n \"invoice_creation\": {\n \"enabled\"\
|
||||||
|
: false,\n \"invoice_data\": {\n \"account_tax_ids\": null,\n \"\
|
||||||
|
custom_fields\": null,\n \"description\": null,\n \"footer\": null,\n\
|
||||||
|
\ \"issuer\": null,\n \"metadata\": {},\n \"rendering_options\"\
|
||||||
|
: null\n }\n },\n \"livemode\": false,\n \"locale\": null,\n \"metadata\"\
|
||||||
|
: {},\n \"mode\": \"payment\",\n \"payment_intent\": null,\n \"payment_link\"\
|
||||||
|
: null,\n \"payment_method_collection\": \"if_required\",\n \"payment_method_configuration_details\"\
|
||||||
|
: null,\n \"payment_method_options\": {\n \"card\": {\n \"request_three_d_secure\"\
|
||||||
|
: \"automatic\"\n }\n },\n \"payment_method_types\": [\n \"card\",\n\
|
||||||
|
\ \"link\",\n \"paypal\"\n ],\n \"payment_status\": \"unpaid\",\n \"\
|
||||||
|
phone_number_collection\": {\n \"enabled\": false\n },\n \"recovered_from\"\
|
||||||
|
: null,\n \"saved_payment_method_options\": {\n \"allow_redisplay_filters\"\
|
||||||
|
: [\n \"always\"\n ],\n \"payment_method_remove\": null,\n \"\
|
||||||
|
payment_method_save\": null\n },\n \"setup_intent\": null,\n \"shipping_address_collection\"\
|
||||||
|
: null,\n \"shipping_cost\": null,\n \"shipping_details\": null,\n \"shipping_options\"\
|
||||||
|
: [],\n \"status\": \"open\",\n \"submit_type\": \"pay\",\n \"subscription\"\
|
||||||
|
: null,\n \"success_url\": \"http://testserver/looper/stripe_success/1/{CHECKOUT_SESSION_ID}\"\
|
||||||
|
,\n \"total_details\": {\n \"amount_discount\": 0,\n \"amount_shipping\"\
|
||||||
|
: 0,\n \"amount_tax\": 0\n },\n \"ui_mode\": \"hosted\",\n \"url\": \"\
|
||||||
|
https://checkout.stripe.com/c/pay/cs_test_a1hoP4Yj4ZmfghAwGoUtWJngVt1XreEVLGAj2n7U5o9BlvqhnDimuA07zh#fidkdWxOYHwnPyd1blpxYHZxWjA0VUpnNzNAMU5EUEcwYW92dWIwclxRMzQ8ZkxsUDRET2dPbTVidnBCNEJTdlBJQTRJYFF2c09BMEFBdlxVT19USGpWSXRSXFJwdm5UQXRpdVw2Rmp%2FZ11NNTU3fHdHUl1JTCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl\"\
|
||||||
|
\n}"
|
||||||
|
content_type: text/plain
|
||||||
|
method: POST
|
||||||
|
status: 200
|
||||||
|
url: https://api.stripe.com/v1/checkout/sessions
|
@ -1,5 +1,4 @@
|
|||||||
"""Views handling subscription management."""
|
"""Views handling subscription management."""
|
||||||
from decimal import Decimal
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@ -43,6 +42,24 @@ class JoinView(LoginRequiredMixin, FormView):
|
|||||||
)
|
)
|
||||||
return existing_subscriptions.first()
|
return existing_subscriptions.first()
|
||||||
|
|
||||||
|
def _set_preferred_currency_and_redirect(self):
|
||||||
|
# If no country is set in the existing address, use GeoIP's
|
||||||
|
geoip_country = self.request.session.get(looper.middleware.COUNTRY_CODE_SESSION_KEY)
|
||||||
|
if geoip_country and (not self.customer or not self.customer.billing_address.country):
|
||||||
|
country = geoip_country
|
||||||
|
else:
|
||||||
|
country = self.customer.billing_address.country
|
||||||
|
currency = preferred_currency_for_country_code(country)
|
||||||
|
if self.plan_variation.currency != currency:
|
||||||
|
# If variation's currency doesn't match, redirect to another plan variation
|
||||||
|
plan_variation = self.plan_variation.in_other_currency(currency)
|
||||||
|
self.request.session[looper.middleware.PREFERRED_CURRENCY_SESSION_KEY] = currency
|
||||||
|
self.request.session.modified = True
|
||||||
|
return redirect(
|
||||||
|
'subscriptions:join-billing-details', plan_variation_id=plan_variation.pk
|
||||||
|
)
|
||||||
|
return None # nothing to do, no need to redirect
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
"""Redirect to login or to billing, or prepare plan variation."""
|
"""Redirect to login or to billing, or prepare plan variation."""
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
@ -62,6 +79,10 @@ class JoinView(LoginRequiredMixin, FormView):
|
|||||||
self.user = request.user
|
self.user = request.user
|
||||||
self.customer = self.user.customer
|
self.customer = self.user.customer
|
||||||
self.subscription = self._get_existing_subscription()
|
self.subscription = self._get_existing_subscription()
|
||||||
|
|
||||||
|
response_redirect = self._set_preferred_currency_and_redirect()
|
||||||
|
if response_redirect:
|
||||||
|
return response_redirect
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_form_kwargs(self, *args, **kwargs):
|
def get_form_kwargs(self, *args, **kwargs):
|
||||||
@ -164,27 +185,21 @@ class JoinView(LoginRequiredMixin, FormView):
|
|||||||
form.save()
|
form.save()
|
||||||
|
|
||||||
msg = 'Pricing has been updated to reflect changes to your billing details'
|
msg = 'Pricing has been updated to reflect changes to your billing details'
|
||||||
new_country = self.customer.billing_address.country
|
response_redirect = self._set_preferred_currency_and_redirect()
|
||||||
new_currency = preferred_currency_for_country_code(new_country)
|
if response_redirect:
|
||||||
# Compare currency before and after the billing address is updated
|
|
||||||
if self.plan_variation.currency != new_currency:
|
|
||||||
# If currency has changed, find a matching plan variation for this new currency
|
|
||||||
plan_variation = self.plan_variation.in_other_currency(new_currency)
|
|
||||||
self.request.session[looper.middleware.PREFERRED_CURRENCY_SESSION_KEY] = new_currency
|
|
||||||
messages.add_message(self.request, messages.INFO, msg)
|
messages.add_message(self.request, messages.INFO, msg)
|
||||||
return redirect(
|
return response_redirect
|
||||||
'subscriptions:join-billing-details', plan_variation_id=plan_variation.pk
|
|
||||||
)
|
|
||||||
|
|
||||||
# Compare tax before and after the billing address is updated
|
# Compare tax before and after the billing address is updated
|
||||||
new_tax = self.customer.get_tax(product_type=product_type)
|
new_tax = self.customer.get_tax(product_type=product_type)
|
||||||
new_taxable = looper.taxes.Taxable(self.plan_variation.price, *new_tax)
|
new_taxable = looper.taxes.Taxable(self.plan_variation.price, *new_tax)
|
||||||
if old_taxable != new_taxable:
|
if old_taxable != new_taxable:
|
||||||
# If price has changed, stay on the same page and display a notification
|
# If price has changed, stay on the same page and display a notification
|
||||||
|
messages.add_message(self.request, messages.INFO, msg)
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
gateway = self.gateway_from_form(form)
|
gateway = self.gateway_from_form(form)
|
||||||
price_cents = int(Decimal(form.cleaned_data['price']) * 100)
|
price_cents = new_taxable.price.cents
|
||||||
subscription = self._get_or_create_subscription(gateway)
|
subscription = self._get_or_create_subscription(gateway)
|
||||||
# Update the tax info stored on the subscription
|
# Update the tax info stored on the subscription
|
||||||
subscription.update_tax()
|
subscription.update_tax()
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
from typing import Tuple
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
import responses
|
import responses
|
||||||
|
|
||||||
|
# from responses import _recorder
|
||||||
|
|
||||||
from looper.tests.test_preferred_currency import EURO_IPV4, USA_IPV4, SINGAPORE_IPV4
|
from looper.tests.test_preferred_currency import EURO_IPV4, USA_IPV4, SINGAPORE_IPV4
|
||||||
from looper.money import Money
|
from looper.money import Money
|
||||||
import looper.models
|
import looper.models
|
||||||
@ -41,7 +43,7 @@ def _get_default_variation(currency='USD'):
|
|||||||
|
|
||||||
|
|
||||||
@freeze_time('2023-05-19 11:41:11')
|
@freeze_time('2023-05-19 11:41:11')
|
||||||
class TestGETBillingDetailsView(BaseSubscriptionTestCase):
|
class TestGETJoinView(BaseSubscriptionTestCase):
|
||||||
url_usd = reverse('subscriptions:join-billing-details', kwargs={'plan_variation_id': 1})
|
url_usd = reverse('subscriptions:join-billing-details', kwargs={'plan_variation_id': 1})
|
||||||
url = reverse('subscriptions:join-billing-details', kwargs={'plan_variation_id': 2})
|
url = reverse('subscriptions:join-billing-details', kwargs={'plan_variation_id': 2})
|
||||||
|
|
||||||
@ -65,7 +67,8 @@ class TestGETBillingDetailsView(BaseSubscriptionTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_get_displays_total_and_billing_details_to_logged_in_nl(self):
|
def test_get_displays_total_and_billing_details_to_logged_in_nl(self):
|
||||||
user = create_customer_with_billing_address(vat_number='', country='NL')
|
customer = create_customer_with_billing_address(vat_number='', country='NL')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
response = self.client.get(self.url, REMOTE_ADDR=EURO_IPV4)
|
response = self.client.get(self.url, REMOTE_ADDR=EURO_IPV4)
|
||||||
@ -76,7 +79,8 @@ class TestGETBillingDetailsView(BaseSubscriptionTestCase):
|
|||||||
self._assert_total_default_variation_selected_tax_21_eur(response)
|
self._assert_total_default_variation_selected_tax_21_eur(response)
|
||||||
|
|
||||||
def test_get_displays_total_and_billing_details_to_logged_in_de(self):
|
def test_get_displays_total_and_billing_details_to_logged_in_de(self):
|
||||||
user = create_customer_with_billing_address(vat_number='', country='DE')
|
customer = create_customer_with_billing_address(vat_number='', country='DE')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
response = self.client.get(self.url, REMOTE_ADDR=EURO_IPV4)
|
response = self.client.get(self.url, REMOTE_ADDR=EURO_IPV4)
|
||||||
@ -86,9 +90,10 @@ class TestGETBillingDetailsView(BaseSubscriptionTestCase):
|
|||||||
self._assert_total_default_variation_selected_tax_19_eur(response)
|
self._assert_total_default_variation_selected_tax_19_eur(response)
|
||||||
|
|
||||||
def test_get_displays_total_and_billing_details_to_logged_in_us(self):
|
def test_get_displays_total_and_billing_details_to_logged_in_us(self):
|
||||||
user = create_customer_with_billing_address(
|
customer = create_customer_with_billing_address(
|
||||||
vat_number='', country='US', region='NY', postal_code='12001'
|
vat_number='', country='US', region='NY', postal_code='12001'
|
||||||
)
|
)
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
response = self.client.get(self.url_usd)
|
response = self.client.get(self.url_usd)
|
||||||
@ -99,16 +104,23 @@ class TestGETBillingDetailsView(BaseSubscriptionTestCase):
|
|||||||
self._assert_total_default_variation_selected_usd(response)
|
self._assert_total_default_variation_selected_usd(response)
|
||||||
|
|
||||||
@unittest.skipUnless(os.path.exists(settings.GEOIP2_DB), 'GeoIP database file is required')
|
@unittest.skipUnless(os.path.exists(settings.GEOIP2_DB), 'GeoIP database file is required')
|
||||||
def test_get_detects_country_us_sets_preferred_currency_usd_invalid_variation(self):
|
def test_get_detects_country_us_sets_preferred_currency_usd_and_redirects(self):
|
||||||
user = create_customer_with_billing_address()
|
customer = create_customer_with_billing_address()
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
self.assertTrue(looper.middleware.PREFERRED_CURRENCY_SESSION_KEY not in self.client.session)
|
||||||
|
|
||||||
response = self.client.get(self.url, REMOTE_ADDR=USA_IPV4)
|
response = self.client.get(self.url, REMOTE_ADDR=USA_IPV4)
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['Location'], self.url_usd)
|
||||||
|
self.assertEqual(
|
||||||
|
self.client.session[looper.middleware.PREFERRED_CURRENCY_SESSION_KEY], 'USD'
|
||||||
|
)
|
||||||
|
|
||||||
@unittest.skipUnless(os.path.exists(settings.GEOIP2_DB), 'GeoIP database file is required')
|
@unittest.skipUnless(os.path.exists(settings.GEOIP2_DB), 'GeoIP database file is required')
|
||||||
def test_get_detects_country_us_sets_preferred_currency_usd(self):
|
def test_get_detects_country_us_sets_preferred_currency_usd(self):
|
||||||
user = create_customer_with_billing_address()
|
customer = create_customer_with_billing_address()
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
response = self.client.get(self.url_usd, REMOTE_ADDR=USA_IPV4)
|
response = self.client.get(self.url_usd, REMOTE_ADDR=USA_IPV4)
|
||||||
@ -125,7 +137,8 @@ class TestGETBillingDetailsView(BaseSubscriptionTestCase):
|
|||||||
|
|
||||||
@unittest.skipUnless(os.path.exists(settings.GEOIP2_DB), 'GeoIP database file is required')
|
@unittest.skipUnless(os.path.exists(settings.GEOIP2_DB), 'GeoIP database file is required')
|
||||||
def test_get_detects_country_sg_sets_preferred_currency_eur(self):
|
def test_get_detects_country_sg_sets_preferred_currency_eur(self):
|
||||||
user = create_customer_with_billing_address()
|
customer = create_customer_with_billing_address()
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
response = self.client.get(self.url, REMOTE_ADDR=SINGAPORE_IPV4)
|
response = self.client.get(self.url, REMOTE_ADDR=SINGAPORE_IPV4)
|
||||||
@ -142,7 +155,8 @@ class TestGETBillingDetailsView(BaseSubscriptionTestCase):
|
|||||||
|
|
||||||
@unittest.skipUnless(os.path.exists(settings.GEOIP2_DB), 'GeoIP database file is required')
|
@unittest.skipUnless(os.path.exists(settings.GEOIP2_DB), 'GeoIP database file is required')
|
||||||
def test_get_detects_country_nl_sets_preferred_currency_eur_displays_correct_vat(self):
|
def test_get_detects_country_nl_sets_preferred_currency_eur_displays_correct_vat(self):
|
||||||
user = create_customer_with_billing_address()
|
customer = create_customer_with_billing_address()
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
response = self.client.get(self.url, REMOTE_ADDR=EURO_IPV4)
|
response = self.client.get(self.url, REMOTE_ADDR=EURO_IPV4)
|
||||||
@ -158,12 +172,15 @@ class TestGETBillingDetailsView(BaseSubscriptionTestCase):
|
|||||||
|
|
||||||
|
|
||||||
@freeze_time('2023-05-19 11:41:11')
|
@freeze_time('2023-05-19 11:41:11')
|
||||||
class TestPOSTBillingDetailsView(BaseSubscriptionTestCase):
|
@responses.activate
|
||||||
|
class TestPOSTJoinView(BaseSubscriptionTestCase):
|
||||||
url_usd = reverse('subscriptions:join-billing-details', kwargs={'plan_variation_id': 1})
|
url_usd = reverse('subscriptions:join-billing-details', kwargs={'plan_variation_id': 1})
|
||||||
url = reverse('subscriptions:join-billing-details', kwargs={'plan_variation_id': 2})
|
url = reverse('subscriptions:join-billing-details', kwargs={'plan_variation_id': 2})
|
||||||
|
responses._add_from_file('stripe_create_checkout_session.yaml')
|
||||||
|
|
||||||
def test_post_updates_billing_address_and_customer_renders_next_form_de(self):
|
def test_post_updates_billing_address_and_customer_renders_next_form_de(self):
|
||||||
user = create_customer_with_billing_address(vat_number='', country='DE')
|
customer = create_customer_with_billing_address(vat_number='', country='DE')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
selected_variation = (
|
selected_variation = (
|
||||||
@ -197,33 +214,21 @@ class TestPOSTBillingDetailsView(BaseSubscriptionTestCase):
|
|||||||
self.assertContains(response, 'Manual ')
|
self.assertContains(response, 'Manual ')
|
||||||
self.assertContains(response, '/ <span class="x-price-period">1 year</span>', html=True)
|
self.assertContains(response, '/ <span class="x-price-period">1 year</span>', html=True)
|
||||||
|
|
||||||
|
# @_recorder.record(file_path='stripe_create_checkout_session.yaml')
|
||||||
def test_post_has_correct_price_field_value(self):
|
def test_post_has_correct_price_field_value(self):
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
default_variation = _get_default_variation('EUR')
|
|
||||||
data = required_address_data
|
data = required_address_data
|
||||||
response = self.client.post(self.url, data, REMOTE_ADDR=EURO_IPV4)
|
response = self.client.post(self.url, data, REMOTE_ADDR=EURO_IPV4)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response['Location'],
|
response['Location'],
|
||||||
reverse(
|
'https://checkout.stripe.com/c/pay/cs_test_a1hoP4Yj4ZmfghAwGoUtWJngVt1XreEVLGAj2'
|
||||||
'subscriptions:join-confirm-and-pay',
|
'n7U5o9BlvqhnDimuA07zh#fidkdWxOYHwnPyd1blpxYHZxWjA0VUpnNzNAMU5EUEcwYW92dWIwclxRMzQ8Zkx'
|
||||||
kwargs={'plan_variation_id': default_variation.pk},
|
'sUDRET2dPbTVidnBCNEJTdlBJQTRJYFF2c09BMEFBdlxVT19USGpWSXRSXFJwdm5UQXRpdVw2Rmp'
|
||||||
),
|
'%2FZ11NNTU3fHdHUl1JTCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabH'
|
||||||
)
|
'FgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl',
|
||||||
|
|
||||||
# Follow the redirect to avoid "Couldn't retrieve content: Response code was 302 (expected 200)"
|
|
||||||
response = self.client.get(response['Location'])
|
|
||||||
# Check that we are no longer on the billing details page
|
|
||||||
self._assert_payment_form_displayed(response)
|
|
||||||
|
|
||||||
self._assert_total_default_variation_selected_tax_21_eur(response)
|
|
||||||
# The hidden price field must also be set to a matching amount
|
|
||||||
self.assertContains(
|
|
||||||
response,
|
|
||||||
'<input type="hidden" name="price" value="9.90" class="form-control" id="id_price">',
|
|
||||||
html=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_post_updates_billing_address_and_customer_applies_reverse_charged_tax(self):
|
def test_post_updates_billing_address_and_customer_applies_reverse_charged_tax(self):
|
||||||
@ -240,8 +245,8 @@ class TestPOSTBillingDetailsView(BaseSubscriptionTestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
self.user.refresh_from_db()
|
self.user.refresh_from_db()
|
||||||
self.assertEqual(self.user.customer.vat_number, 'DE260543043')
|
|
||||||
address = self.user.customer.billing_address
|
address = self.user.customer.billing_address
|
||||||
|
self.assertEqual(address.vat_number, 'DE260543043')
|
||||||
self.assertEqual(address.full_name, 'New Full Name')
|
self.assertEqual(address.full_name, 'New Full Name')
|
||||||
self.assertEqual(address.postal_code, '11111')
|
self.assertEqual(address.postal_code, '11111')
|
||||||
self.assertEqual(address.country, 'DE')
|
self.assertEqual(address.country, 'DE')
|
||||||
@ -269,9 +274,10 @@ class TestPOSTBillingDetailsView(BaseSubscriptionTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_post_changing_address_from_with_region_to_without_region_clears_region(self):
|
def test_post_changing_address_from_with_region_to_without_region_clears_region(self):
|
||||||
user = create_customer_with_billing_address(
|
customer = create_customer_with_billing_address(
|
||||||
vat_number='', country='US', region='NY', postal_code='12001'
|
vat_number='', country='US', region='NY', postal_code='12001'
|
||||||
)
|
)
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
response = self.client.get(self.url_usd)
|
response = self.client.get(self.url_usd)
|
||||||
@ -304,22 +310,10 @@ class TestPOSTBillingDetailsView(BaseSubscriptionTestCase):
|
|||||||
self.assertEqual(user.customer.billing_address.country, 'DE')
|
self.assertEqual(user.customer.billing_address.country, 'DE')
|
||||||
self.assertEqual(user.customer.billing_address.postal_code, '11111')
|
self.assertEqual(user.customer.billing_address.postal_code, '11111')
|
||||||
|
|
||||||
|
|
||||||
@freeze_time('2023-05-19 11:41:11')
|
|
||||||
class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|
||||||
def _get_url_for(self, **filter_params) -> Tuple[str, looper.models.PlanVariation]:
|
|
||||||
plan_variation = looper.models.PlanVariation.objects.active().get(**filter_params)
|
|
||||||
return (
|
|
||||||
reverse(
|
|
||||||
'subscriptions:join-confirm-and-pay',
|
|
||||||
kwargs={'plan_variation_id': plan_variation.pk},
|
|
||||||
),
|
|
||||||
plan_variation,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_plan_variation_does_not_match_detected_currency_usd_euro_ip(self):
|
def test_plan_variation_does_not_match_detected_currency_usd_euro_ip(self):
|
||||||
url, _ = self._get_url_for(currency='USD', price=11900)
|
url, _ = self._get_url_for(currency='USD', price=11900)
|
||||||
user = create_customer_with_billing_address(country='NL')
|
customer = create_customer_with_billing_address(country='NL')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
data = required_address_data
|
data = required_address_data
|
||||||
@ -329,7 +323,8 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
|
|
||||||
def test_plan_variation_matches_detected_currency_eur_non_eea_ip(self):
|
def test_plan_variation_matches_detected_currency_eur_non_eea_ip(self):
|
||||||
url, _ = self._get_url_for(currency='EUR', price=990)
|
url, _ = self._get_url_for(currency='EUR', price=990)
|
||||||
user = create_customer_with_billing_address()
|
customer = create_customer_with_billing_address()
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
data = required_address_data
|
data = required_address_data
|
||||||
@ -341,7 +336,8 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
|
|
||||||
def test_billing_address_country_takes_precedence_over_geo_ip(self):
|
def test_billing_address_country_takes_precedence_over_geo_ip(self):
|
||||||
url, _ = self._get_url_for(currency='EUR', price=990)
|
url, _ = self._get_url_for(currency='EUR', price=990)
|
||||||
user = create_customer_with_billing_address(country='NL')
|
customer = create_customer_with_billing_address(country='NL')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
data = required_address_data
|
data = required_address_data
|
||||||
@ -352,7 +348,8 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
|
|
||||||
def test_invalid_missing_required_fields(self):
|
def test_invalid_missing_required_fields(self):
|
||||||
url, _ = self._get_url_for(currency='EUR', price=990)
|
url, _ = self._get_url_for(currency='EUR', price=990)
|
||||||
user = create_customer_with_billing_address(country='NL')
|
customer = create_customer_with_billing_address(country='NL')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
data = required_address_data
|
data = required_address_data
|
||||||
@ -371,7 +368,8 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
|
|
||||||
def test_invalid_price_does_not_match_selected_plan_variation(self):
|
def test_invalid_price_does_not_match_selected_plan_variation(self):
|
||||||
url, selected_variation = self._get_url_for(currency='EUR', price=990)
|
url, selected_variation = self._get_url_for(currency='EUR', price=990)
|
||||||
user = create_customer_with_billing_address(country='NL')
|
customer = create_customer_with_billing_address(country='NL')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -391,7 +389,8 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
|
|
||||||
def test_invalid_bank_transfer_cannot_be_selected_for_automatic_payments(self):
|
def test_invalid_bank_transfer_cannot_be_selected_for_automatic_payments(self):
|
||||||
url, selected_variation = self._get_url_for(currency='EUR', price=990)
|
url, selected_variation = self._get_url_for(currency='EUR', price=990)
|
||||||
user = create_customer_with_billing_address(country='NL')
|
customer = create_customer_with_billing_address(country='NL')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
@ -423,7 +422,8 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
)
|
)
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_pay_with_bank_transfer_creates_order_subscription_on_hold(self):
|
def test_pay_with_bank_transfer_creates_order_subscription_on_hold(self):
|
||||||
user = create_customer_with_billing_address(country='NL', full_name='Jane Doe')
|
customer = create_customer_with_billing_address(country='NL', full_name='Jane Doe')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
util.mock_blender_id_badger_badger_response(
|
util.mock_blender_id_badger_badger_response(
|
||||||
'grant', 'cloud_has_subscription', user.oauth_info.oauth_user_id
|
'grant', 'cloud_has_subscription', user.oauth_info.oauth_user_id
|
||||||
@ -500,9 +500,10 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
def test_pay_with_bank_transfer_creates_order_subscription_on_hold_shows_reverse_charged_price(
|
def test_pay_with_bank_transfer_creates_order_subscription_on_hold_shows_reverse_charged_price(
|
||||||
self,
|
self,
|
||||||
):
|
):
|
||||||
user = create_customer_with_billing_address(
|
customer = create_customer_with_billing_address(
|
||||||
country='ES', full_name='Jane Doe', vat_number='DE260543043'
|
country='ES', full_name='Jane Doe', vat_number='DE260543043'
|
||||||
)
|
)
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
util.mock_blender_id_badger_badger_response(
|
util.mock_blender_id_badger_badger_response(
|
||||||
'grant', 'cloud_has_subscription', user.oauth_info.oauth_user_id
|
'grant', 'cloud_has_subscription', user.oauth_info.oauth_user_id
|
||||||
@ -584,7 +585,8 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
@responses.activate
|
@responses.activate
|
||||||
def test_pay_with_credit_card_creates_order_subscription_active(self):
|
def test_pay_with_credit_card_creates_order_subscription_active(self):
|
||||||
url, selected_variation = self._get_url_for(currency='EUR', price=990)
|
url, selected_variation = self._get_url_for(currency='EUR', price=990)
|
||||||
user = create_customer_with_billing_address(country='NL', full_name='Jane Doe')
|
customer = create_customer_with_billing_address(country='NL', full_name='Jane Doe')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
util.mock_blender_id_badger_badger_response(
|
util.mock_blender_id_badger_badger_response(
|
||||||
'grant', 'cloud_has_subscription', user.oauth_info.oauth_user_id
|
'grant', 'cloud_has_subscription', user.oauth_info.oauth_user_id
|
||||||
@ -636,7 +638,8 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
price=9000,
|
price=9000,
|
||||||
plan__name='Automatic renewal, 15 seats',
|
plan__name='Automatic renewal, 15 seats',
|
||||||
)
|
)
|
||||||
user = create_customer_with_billing_address(country='NL', full_name='Jane Doe')
|
customer = create_customer_with_billing_address(country='NL', full_name='Jane Doe')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
util.mock_blender_id_badger_badger_response(
|
util.mock_blender_id_badger_badger_response(
|
||||||
'grant', 'cloud_has_subscription', user.oauth_info.oauth_user_id
|
'grant', 'cloud_has_subscription', user.oauth_info.oauth_user_id
|
||||||
@ -655,7 +658,7 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
|
|
||||||
self._assert_done_page_displayed(response)
|
self._assert_done_page_displayed(response)
|
||||||
|
|
||||||
subscription = user.customer.subscription_set.first()
|
subscription = customer.subscription_set.first()
|
||||||
order = subscription.latest_order()
|
order = subscription.latest_order()
|
||||||
self.assertEqual(subscription.status, 'active')
|
self.assertEqual(subscription.status, 'active')
|
||||||
self.assertEqual(subscription.price, Money('EUR', 9000))
|
self.assertEqual(subscription.price, Money('EUR', 9000))
|
||||||
@ -670,7 +673,8 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
self._assert_team_subscription_activated_email_is_sent(subscription)
|
self._assert_team_subscription_activated_email_is_sent(subscription)
|
||||||
|
|
||||||
def test_pay_with_credit_card_creates_order_subscription_active_business_de(self):
|
def test_pay_with_credit_card_creates_order_subscription_active_business_de(self):
|
||||||
user = create_customer_with_billing_address(country='DE', vat_number='DE260543043')
|
customer = create_customer_with_billing_address(country='DE', vat_number='DE260543043')
|
||||||
|
user = customer.user
|
||||||
self.client.force_login(user)
|
self.client.force_login(user)
|
||||||
|
|
||||||
url, selected_variation = self._get_url_for(
|
url, selected_variation = self._get_url_for(
|
||||||
@ -716,15 +720,17 @@ class TestPOSTConfirmAndPayView(BaseSubscriptionTestCase):
|
|||||||
self.assertIsNotNone(order.number)
|
self.assertIsNotNone(order.number)
|
||||||
|
|
||||||
|
|
||||||
class TestJoinConfirmAndPayLoggedInUserOnlyView(BaseSubscriptionTestCase):
|
class TestJoinViewLoggedInUserOnly(TestCase):
|
||||||
url = reverse('subscriptions:join-confirm-and-pay', kwargs={'plan_variation_id': 8})
|
url = reverse('subscriptions:join-billing-details', kwargs={'plan_variation_id': 2})
|
||||||
|
|
||||||
def test_get_anonymous_403(self):
|
def test_get_anonymous_redirects_to_login_with_next(self):
|
||||||
response = self.client.get(self.url)
|
response = self.client.get(self.url)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['Location'], '/oauth/login?next=/join/plan-variation/2/billing/')
|
||||||
|
|
||||||
def test_join_post_anonymous_403(self):
|
def test_post_anonymous_redirects_to_login_with_next(self):
|
||||||
response = self.client.post(self.url)
|
response = self.client.post(self.url)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['Location'], '/oauth/login?next=/join/plan-variation/2/billing/')
|
||||||
|
Loading…
Reference in New Issue
Block a user