Stripe in Blender Studio #93018
@ -1,32 +1,4 @@
|
||||
[
|
||||
{
|
||||
"model": "looper.gateway",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "braintree",
|
||||
"is_default": false,
|
||||
"frontend_name": "Credit Card or PayPal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "looper.gateway",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "bank",
|
||||
"is_default": false,
|
||||
"frontend_name": "Bank Transfer",
|
||||
"form_description": "<p>This option requires you to manually perform a bank transfer before the membership can be activated. Automatic payment is not possible with bank transfer. Bank details will be given to you when you choose this option.</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "looper.gateway",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "stripe",
|
||||
"is_default": true,
|
||||
"frontend_name": "Checkout with Stripe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "looper.product",
|
||||
"pk": 1,
|
||||
|
30
looper/fixtures/gateways.json
Normal file
30
looper/fixtures/gateways.json
Normal file
@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"model": "looper.gateway",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "braintree",
|
||||
"is_default": false,
|
||||
"frontend_name": "Credit Card or PayPal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "looper.gateway",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "bank",
|
||||
"is_default": false,
|
||||
"frontend_name": "Bank Transfer",
|
||||
"form_description": "<p>This option requires you to manually perform a bank transfer before the membership can be activated. Automatic payment is not possible with bank transfer. Bank details will be given to you when you choose this option.</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "looper.gateway",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "stripe",
|
||||
"is_default": true,
|
||||
"frontend_name": "Checkout with Stripe"
|
||||
}
|
||||
}
|
||||
]
|
@ -25,10 +25,13 @@ class Migration(migrations.Migration):
|
||||
name='vat_number',
|
||||
field=models.CharField(blank=True, default='', max_length=255, verbose_name='VAT identification number'),
|
||||
),
|
||||
migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE;'),
|
||||
# Create address records for accounts without any
|
||||
migrations.RunSQL(
|
||||
"insert into looper_address (category, user_id, full_name, company, country, tax_exempt, vat_number)"
|
||||
"select 'billing', cu.user_id, cu.full_name, cu.company, '', false, '' "
|
||||
"insert into looper_address (category, user_id, full_name, company, country, tax_exempt, vat_number, "
|
||||
"street_address, extended_address, locality, postal_code, region)"
|
||||
"select 'billing', cu.user_id, cu.full_name, cu.company, '', false, '', "
|
||||
"'', '', '', '', '' "
|
||||
"from looper_customer as cu left join looper_address as addr using(user_id) "
|
||||
"where addr.user_id is null and cu.user_id is not null",
|
||||
),
|
||||
@ -44,6 +47,7 @@ class Migration(migrations.Migration):
|
||||
"full_name = addr.full_name, company = addr.company "
|
||||
"from looper_address as addr where cu.user_id = addr.user_id",
|
||||
),
|
||||
migrations.RunSQL('SET CONSTRAINTS ALL DEFERRED;'),
|
||||
migrations.AlterField(
|
||||
model_name='address',
|
||||
name='email',
|
||||
|
@ -181,8 +181,10 @@ class Taxable:
|
||||
is_business = False
|
||||
if user and user.is_authenticated:
|
||||
customer = user.customer
|
||||
country_code = customer.billing_address.country or country_code
|
||||
is_business = bool(customer.vat_number)
|
||||
billing_address = customer.billing_address
|
||||
country_code = billing_address.country or country_code
|
||||
vat_number = billing_address.vat_number
|
||||
is_business = bool(vat_number)
|
||||
tax_type, tax_rate = ProductType(product_type).get_tax(
|
||||
buyer_country_code=country_code, is_business=is_business
|
||||
)
|
||||
|
@ -27,7 +27,7 @@ class AbstractBaseTestCase(TestCase):
|
||||
|
||||
class AbstractLooperTestCase(AbstractBaseTestCase):
|
||||
log = log.getChild('AbstractLooperTestCase')
|
||||
fixtures = ['devfund', 'testuser', 'systemuser']
|
||||
fixtures = ['gateways', 'devfund', 'testuser', 'systemuser']
|
||||
valid_payload = {
|
||||
'gateway': 'stripe',
|
||||
'email': 'erik@example.com',
|
||||
@ -79,7 +79,7 @@ class AbstractLooperTestCase(AbstractBaseTestCase):
|
||||
self.pay_meth = self.user.customer.payment_method_default
|
||||
|
||||
def create_accountless_customer(self):
|
||||
self.accountless_customer = Customer.objects.create()
|
||||
self.accountless_customer = Customer.objects.create(user=None)
|
||||
self.accountless_customer.billing_address.save()
|
||||
gw = self._get_test_gateway()
|
||||
if gw:
|
||||
|
@ -10,6 +10,7 @@ import looper.taxes
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
@factory.django.mute_signals(signals.pre_save, signals.post_save)
|
||||
class UserFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = User
|
||||
@ -30,6 +31,8 @@ class CustomerFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = looper.models.Customer
|
||||
|
||||
user = factory.SubFactory(UserFactory)
|
||||
|
||||
|
||||
class PaymentMethodFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
@ -58,6 +61,7 @@ class SubscriptionFactory(DjangoModelFactory):
|
||||
class Meta:
|
||||
model = looper.models.Subscription
|
||||
|
||||
customer = factory.SubFactory(CustomerFactory)
|
||||
plan = factory.SubFactory(PlanFactory)
|
||||
payment_method = factory.SubFactory(PaymentMethodFactory)
|
||||
|
||||
|
@ -22,7 +22,7 @@ CHECKOUT = 'looper-braintree:checkout'
|
||||
# Prevent communication with Google's reCAPTCHA API.
|
||||
@override_settings(GOOGLE_RECAPTCHA_SECRET_KEY='')
|
||||
class AbstractCheckoutTestCase(AbstractLooperTestCase):
|
||||
fixtures = ['devfund', 'testuser', 'systemuser']
|
||||
fixtures = ['gateways', 'devfund', 'testuser', 'systemuser']
|
||||
checkout_url = reverse_lazy(CHECKOUT, kwargs={'plan_id': 2, 'plan_variation_id': 5})
|
||||
|
||||
def setUp(self):
|
||||
|
@ -15,7 +15,7 @@ User = get_user_model()
|
||||
|
||||
|
||||
class _BaseTestCase(AbstractLooperTestCase):
|
||||
fixtures = ['devfund', 'testuser', 'systemuser']
|
||||
fixtures = ['gateways', 'devfund', 'testuser', 'systemuser']
|
||||
checkout_url = reverse_lazy('looper:checkout', kwargs={'plan_id': 2, 'plan_variation_id': 5})
|
||||
gateway_name = 'stripe'
|
||||
|
||||
|
@ -19,7 +19,7 @@ from looper.tests.factories import (
|
||||
|
||||
@mock.patch('looper.gateways.stripe.webhook.WebhookSignature.verify_header', return_value=True)
|
||||
class ClockForStripe(ResponsesMixin, TestCase):
|
||||
fixtures = ['devfund', 'systemuser']
|
||||
fixtures = ['gateways', 'devfund', 'systemuser']
|
||||
responses_files = ['clock_stripe.yaml']
|
||||
|
||||
# @_recorder.record(file_path=f'{ResponsesMixin.responses_file_path}/clock_stripe.yaml')
|
||||
|
@ -116,7 +116,7 @@ class BraintreeErrorTest(AbstractBaseTestCase):
|
||||
|
||||
|
||||
class BankGatewayTest(AbstractBaseTestCase):
|
||||
fixtures = ['testuser', 'devfund']
|
||||
fixtures = ['testuser', 'gateways', 'devfund']
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
@ -10,7 +10,7 @@ from . import AbstractLooperTestCase, AbstractBaseTestCase
|
||||
|
||||
|
||||
class MoneyFieldTest(AbstractBaseTestCase):
|
||||
fixtures = ['testuser', 'devfund', 'systemuser']
|
||||
fixtures = ['testuser', 'gateways', 'devfund', 'systemuser']
|
||||
|
||||
def test_create_instance(self):
|
||||
pv = models.PlanVariation(plan_id=1, currency='EUR', price=Money('EUR', 155))
|
||||
|
@ -15,7 +15,7 @@ User = get_user_model()
|
||||
|
||||
|
||||
class _BaseTestCase(AbstractLooperTestCase):
|
||||
fixtures = ['devfund', 'testuser', 'systemuser']
|
||||
fixtures = ['gateways', 'devfund', 'testuser', 'systemuser']
|
||||
gateway_name = 'stripe'
|
||||
subscription = None
|
||||
|
||||
|
@ -6,7 +6,7 @@ from ..money import Money
|
||||
|
||||
|
||||
class PlanVariationModelTestCase(django.test.TestCase):
|
||||
fixtures = ['devfund']
|
||||
fixtures = ['gateways', 'devfund']
|
||||
|
||||
def setUp(self):
|
||||
product = Product.objects.create(name='Ежедневный Пророк')
|
||||
@ -29,7 +29,7 @@ class PlanVariationModelTestCase(django.test.TestCase):
|
||||
|
||||
|
||||
class PlanVariationTest(django.test.TestCase):
|
||||
fixtures = ['devfund']
|
||||
fixtures = ['gateways', 'devfund']
|
||||
|
||||
def test_variation_for_currency(self):
|
||||
plan = Plan.objects.get(pk=1)
|
||||
|
@ -35,7 +35,7 @@ event_payload_cc = {
|
||||
|
||||
@mock.patch('looper.gateways.stripe.webhook.WebhookSignature.verify_header', return_value=True)
|
||||
class UpsertOrderFromPaymentIntentAndProduct(ResponsesMixin, TestCase):
|
||||
fixtures = ['devfund', 'systemuser']
|
||||
fixtures = ['gateways', 'devfund', 'systemuser']
|
||||
responses_files = [
|
||||
'payment_intent__card.yaml',
|
||||
'payment_intent__ideal.yaml',
|
||||
@ -452,7 +452,7 @@ class UpsertOrderFromPaymentIntentAndProduct(ResponsesMixin, TestCase):
|
||||
|
||||
|
||||
class TestCheckoutPlanVariationAnonymous(ResponsesMixin, TestCase):
|
||||
fixtures = ['devfund', 'systemuser']
|
||||
fixtures = ['gateways', 'devfund', 'systemuser']
|
||||
responses_files = [
|
||||
'checkout_plan_variation_anonymous.yaml',
|
||||
'checkout_plan_variation_webhook.yaml',
|
||||
@ -535,7 +535,7 @@ class TestCheckoutPlanVariationAnonymous(ResponsesMixin, TestCase):
|
||||
|
||||
|
||||
class TestCheckoutPlanVariationLoggedIn(ResponsesMixin, TestCase):
|
||||
fixtures = ['devfund', 'systemuser']
|
||||
fixtures = ['gateways', 'devfund', 'systemuser']
|
||||
responses_files = [
|
||||
'checkout_plan_variation_logged_in.yaml',
|
||||
]
|
||||
|
@ -147,6 +147,9 @@ class CheckoutExistingOrderView(LoginRequiredMixin, ExpectReadableIPAddressMixin
|
||||
# should be passed from the app, this url name is defined in example_app
|
||||
cancel_url = 'settings_home'
|
||||
|
||||
def get_cancel_url(self):
|
||||
return reverse(self.cancel_url)
|
||||
|
||||
def get_object(self, queryset=None) -> models.Order:
|
||||
order_id: int = self.kwargs['order_id']
|
||||
order_q = models.Order.objects.filter(customer=self.request.user.customer)
|
||||
@ -171,7 +174,7 @@ class CheckoutExistingOrderView(LoginRequiredMixin, ExpectReadableIPAddressMixin
|
||||
# we have to do it to avoid uri-encoding of curly braces,
|
||||
# otherwise stripe doesn't do the template substitution
|
||||
success_url = success_url.replace('CHECKOUT_SESSION_ID', '{CHECKOUT_SESSION_ID}', 1)
|
||||
cancel_url = self.request.build_absolute_uri(reverse(self.cancel_url))
|
||||
cancel_url = self.request.build_absolute_uri(self.get_cancel_url())
|
||||
session = stripe_utils.create_stripe_checkout_session_for_order(
|
||||
order,
|
||||
success_url,
|
||||
|
@ -102,7 +102,10 @@ class PaymentMethodChangeView(LoginRequiredMixin, View):
|
||||
log = log.getChild('PaymentMethodChangeView')
|
||||
subscription: models.Subscription
|
||||
success_url = '/'
|
||||
cancel_url = '/'
|
||||
cancel_url = 'settings_home'
|
||||
|
||||
def get_cancel_url(self):
|
||||
return reverse(self.cancel_url)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.subscription = get_object_or_404(
|
||||
@ -120,7 +123,7 @@ class PaymentMethodChangeView(LoginRequiredMixin, View):
|
||||
# we have to do it to avoid uri-encoding of curly braces,
|
||||
# otherwise stripe doesn't do the template substitution
|
||||
success_url = success_url.replace('CHECKOUT_SESSION_ID', '{CHECKOUT_SESSION_ID}', 1)
|
||||
cancel_url = self.request.build_absolute_uri(reverse(self.cancel_url))
|
||||
cancel_url = self.request.build_absolute_uri(self.get_cancel_url())
|
||||
session = stripe_utils.setup_stripe_payment_method(
|
||||
self.subscription.currency.lower(),
|
||||
request.user.customer,
|
||||
|
14
poetry.lock
generated
14
poetry.lock
generated
@ -1785,6 +1785,18 @@ lxml = "*"
|
||||
reportlab = "*"
|
||||
tinycss2 = ">=0.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "tblib"
|
||||
version = "3.0.0"
|
||||
description = "Traceback serialization library."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tblib-3.0.0-py3-none-any.whl", hash = "sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129"},
|
||||
{file = "tblib-3.0.0.tar.gz", hash = "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinycss2"
|
||||
version = "1.2.1"
|
||||
@ -2060,4 +2072,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "cf5844e74a99155ee23a29cc3f711e0b8577264b72ff07acd858ce16a407e05d"
|
||||
content-hash = "00e67af2695baac5d34a3c9a67ac24286a6a218b30196cccc14c3a35461ffc05"
|
||||
|
@ -40,6 +40,7 @@ flake8-implicit-str-concat = "^0.1.0"
|
||||
psycopg2 = "*"
|
||||
mypy = "^0.991"
|
||||
factory-boy = "^3.0"
|
||||
tblib = "3.0.0"
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
|
Loading…
Reference in New Issue
Block a user