Skip to content

Commit

Permalink
Merge pull request #4640 from kobotoolbox/plans-api-updates
Browse files Browse the repository at this point in the history
Stripe endpoint improvements
  • Loading branch information
bufke authored Sep 20, 2023
2 parents d124e85 + 2ca2ba4 commit 9ca2af7
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 16 deletions.
23 changes: 18 additions & 5 deletions kobo/apps/stripe/tests/test_link_creation_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from kpi.tests.kpi_test_case import BaseTestCase


@patch("djstripe.models.Customer.sync_from_stripe_data")
@patch("stripe.Customer.modify")
@patch("djstripe.models.Customer.get_or_create")
@patch("stripe.checkout.Session.create")
class TestCheckoutLinkAPITestCase(BaseTestCase):
Expand All @@ -24,7 +26,7 @@ def setUp(self):
self.client.force_login(self.someuser)
product = baker.prepare(Product, active=True)
product.save()
self.price = baker.make(Price, active=True, id='price_1LsSOSAR39rDI89svTKog9Hq', product=product)
self.price = baker.make(Price, active=True, id='price_1LsSOSAR39rDI89svTKog9Hq', product=product)

@staticmethod
def _get_url(query_params):
Expand All @@ -37,8 +39,10 @@ def _create_customer_organization(self):
return customer, organization

def test_generates_url(
self, stripe_checkout_session_create_mock, customer_get_or_create_mock
self, stripe_checkout_session_create_mock, customer_get_or_create_mock, modify_customer_mock, stripe_sync_mock
):
stripe_sync_mock.return_value = None
modify_customer_mock.return_value = Customer
customer, organization = self._create_customer_organization()
organization.add_user(self.someuser, is_admin=True)
customer_get_or_create_mock.return_value = (Customer, False)
Expand All @@ -49,14 +53,21 @@ def test_generates_url(
assert response.data['url'].startswith('https://checkout.stripe.com')

def test_rejects_invalid_query_params(
self, stripe_checkout_session_create_mock, customer_get_or_create_mock
self, stripe_checkout_session_create_mock, customer_get_or_create_mock, modify_customer_mock, stripe_sync_mock
):
stripe_sync_mock.return_value = None
modify_customer_mock.return_value = Customer
customer_get_or_create_mock.return_value = (Customer, False)
stripe_checkout_session_create_mock.return_value = {'url': 'https://checkout.stripe.com/c/pay/cs_test_a1NbsdWp'}
url = self._get_url({'price_id': 'test', 'organization_id': 'test'})
response = self.client.post(url)
assert response.status_code == status.HTTP_400_BAD_REQUEST

def test_creates_organization(self, stripe_checkout_session_create_mock, customer_get_or_create_mock):
def test_creates_organization(
self, stripe_checkout_session_create_mock, customer_get_or_create_mock, modify_customer_mock, stripe_sync_mock
):
stripe_sync_mock.return_value = None
modify_customer_mock.return_value = Customer
customer_get_or_create_mock.return_value = (Customer, False)
stripe_checkout_session_create_mock.return_value = {'url': 'https://checkout.stripe.com/c/pay/cs_test_a1NbsdWp'}
url = self._get_url({'price_id': self.price.id})
Expand All @@ -65,7 +76,9 @@ def test_creates_organization(self, stripe_checkout_session_create_mock, custome
assert response.data['url'].startswith('https://checkout.stripe.com')
assert Organization.objects.filter(organization_users__user_id=self.someuser).last()

def test_anonymous_user(self, stripe_checkout_session_create_mock, customer_get_or_create_mock):
def test_anonymous_user(
self, stripe_checkout_session_create_mock, customer_get_or_create_mock, modify_customer_mock, stripe_sync_mock
):
self.client.logout()
response = self.client.post(reverse('checkoutlinks'))
assert response.status_code == status.HTTP_403_FORBIDDEN
47 changes: 36 additions & 11 deletions kobo/apps/stripe/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Max, Prefetch
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from djstripe.models import (
Customer,
Price,
Expand Down Expand Up @@ -190,30 +192,50 @@ def generate_payment_link(price, user, organization_id):
customer, _ = Customer.get_or_create(
subscriber=organization, livemode=settings.STRIPE_LIVE_MODE
)
# Add the name and organization to the customer if not present.
# Update the customer's name and organization name in Stripe.
# djstripe doesn't let us do this on customer creation, so modify the customer on Stripe and then fetch locally.
if not customer.name and user.extra_details.data['name']:
stripe_customer = stripe.Customer.modify(
customer.id,
name=user.extra_details.data['name'],
description=organization.name,
api_key=djstripe_settings.STRIPE_SECRET_KEY,
)
customer.sync_from_stripe_data(stripe_customer)
stripe_customer = stripe.Customer.modify(
customer.id,
name=customer.name or user.extra_details.data.get('name', user.username),
description=organization.name,
api_key=djstripe_settings.STRIPE_SECRET_KEY,
metadata={
'kpi_owner_username': user.username,
'kpi_owner_user_id': user.id,
'request_url': settings.KOBOFORM_URL,
'organization_id': organization_id,
},
)
customer.sync_from_stripe_data(stripe_customer)
session = CheckoutLinkView.start_checkout_session(
customer.id, price, organization.id
customer.id, price, organization.id, user,
)
return session['url']

@staticmethod
def start_checkout_session(customer_id, price, organization_id):
def start_checkout_session(customer_id, price, organization_id, user):
checkout_mode = (
'payment' if price.type == 'one_time' else 'subscription'
)
kwargs = {}
if checkout_mode == 'subscription':
kwargs['subscription_data'] = {
'metadata': {
'kpi_owner_username': user.username,
'kpi_owner_user_id': user.id,
'request_url': settings.KOBOFORM_URL,
'organization_id': organization_id,
},
}
return stripe.checkout.Session.create(
api_key=djstripe_settings.STRIPE_SECRET_KEY,
automatic_tax={'enabled': False},
billing_address_collection='required',
customer=customer_id,
customer_update={
'address': 'auto',
'name': 'auto',
},
line_items=[
{
'price': price.id,
Expand All @@ -223,9 +245,11 @@ def start_checkout_session(customer_id, price, organization_id):
metadata={
'organization_id': organization_id,
'price_id': price.id,
'kpi_owner_username': user.username,
},
mode=checkout_mode,
success_url=f'{settings.KOBOFORM_URL}/#/account/plan?checkout={price.id}',
**kwargs,
)

def post(self, request):
Expand Down Expand Up @@ -283,6 +307,7 @@ def get_queryset(self):
)


@method_decorator(cache_page(60 * 30), name='list')
class ProductViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
"""
Returns Product and Price Lists, sorted from the product with the lowest price to highest
Expand Down

0 comments on commit 9ca2af7

Please sign in to comment.