Stripe checkout #104411

Merged
Anna Sirota merged 61 commits from stripe into main 2024-06-17 18:08:41 +02:00
4 changed files with 47 additions and 32 deletions
Showing only changes of commit 7fc6a334c4 - Show all commits

View File

@ -152,7 +152,9 @@ def _on_subscription_expired(sender: looper.models.Subscription, **kwargs):
assert sender.status == 'expired', f'Expected expired, got "{sender.status} (pk={sender.pk})"' assert sender.status == 'expired', f'Expected expired, got "{sender.status} (pk={sender.pk})"'
# Only send a "subscription expired" email when there are no other active subscriptions # Only send a "subscription expired" email when there are no other active subscriptions
if not queries.has_active_subscription(sender.user): customer = sender.customer
user = customer.user
if user and not queries.has_active_subscription(user):
tasks.send_mail_subscription_expired(subscription_id=sender.pk) tasks.send_mail_subscription_expired(subscription_id=sender.pk)

View File

@ -44,8 +44,9 @@ def _construct_subscription_mail(mail_name: str, context: Dict[str, Any]) -> Tup
def send_mail_bank_transfer_required(subscription_id: int): def send_mail_bank_transfer_required(subscription_id: int):
"""Send out an email notifying about the required bank transfer payment.""" """Send out an email notifying about the required bank transfer payment."""
subscription = looper.models.Subscription.objects.get(pk=subscription_id) subscription = looper.models.Subscription.objects.get(pk=subscription_id)
user = subscription.user customer = subscription.customer
email = user.customer.billing_address.email or user.email user = customer.user
email = customer.billing_address.email or user.email
assert ( assert (
email email
), f'Cannot send notification about bank payment for subscription {subscription.pk}: no email' ), f'Cannot send notification about bank payment for subscription {subscription.pk}: no email'
@ -65,7 +66,7 @@ def send_mail_bank_transfer_required(subscription_id: int):
assert order, "Can't send a notificaton about bank transfer without an existing order" assert order, "Can't send a notificaton about bank transfer without an existing order"
context = { context = {
'user': subscription.user, 'user': user,
'subscription': subscription, 'subscription': subscription,
'order': order, 'order': order,
**get_template_context(), **get_template_context(),
@ -134,8 +135,8 @@ def send_mail_automatic_payment_performed(order_id: int, transaction_id: int):
"""Send out an email notifying about the soft-failed payment.""" """Send out an email notifying about the soft-failed payment."""
order = looper.models.Order.objects.get(pk=order_id) order = looper.models.Order.objects.get(pk=order_id)
transaction = looper.models.Transaction.objects.get(pk=transaction_id) transaction = looper.models.Transaction.objects.get(pk=transaction_id)
user = order.user customer = order.customer
customer = user.customer user = customer.user
email = customer.billing_address.email or user.email email = customer.billing_address.email or user.email
logger.debug('Sending %r notification to %s', order.status, email) logger.debug('Sending %r notification to %s', order.status, email)
@ -149,7 +150,7 @@ def send_mail_automatic_payment_performed(order_id: int, transaction_id: int):
receipt_url = absolute_url('subscriptions:receipt', kwargs={'order_id': order.pk}) receipt_url = absolute_url('subscriptions:receipt', kwargs={'order_id': order.pk})
context = { context = {
'user': subscription.user, 'user': user,
'email': email, 'email': email,
'order': order, 'order': order,
'subscription': subscription, 'subscription': subscription,
@ -186,7 +187,8 @@ def send_mail_managed_subscription_notification(subscription_id: int):
subscription.pk, subscription.pk,
) )
user = subscription.user customer = subscription.customer
user = customer.user
admin_url = absolute_url( admin_url = absolute_url(
'admin:looper_subscription_change', 'admin:looper_subscription_change',
kwargs={'object_id': subscription.id}, kwargs={'object_id': subscription.id},
@ -221,7 +223,8 @@ def send_mail_managed_subscription_notification(subscription_id: int):
def send_mail_subscription_expired(subscription_id: int): def send_mail_subscription_expired(subscription_id: int):
"""Send out an email notifying about an expired subscription.""" """Send out an email notifying about an expired subscription."""
subscription = looper.models.Subscription.objects.get(pk=subscription_id) subscription = looper.models.Subscription.objects.get(pk=subscription_id)
user = subscription.user customer = subscription.customer
user = customer.user
assert ( assert (
subscription.status == 'expired' subscription.status == 'expired'
@ -230,7 +233,7 @@ def send_mail_subscription_expired(subscription_id: int):
if queries.has_active_subscription(user): if queries.has_active_subscription(user):
logger.error( logger.error(
'Not sending subscription-expired notification: pk=%s has other active subscriptions', 'Not sending subscription-expired notification: pk=%s has other active subscriptions',
subscription.user_id, user.pk,
) )
return return
@ -249,7 +252,7 @@ def send_mail_subscription_expired(subscription_id: int):
logger.debug('Sending subscription-expired notification to %s', email) logger.debug('Sending subscription-expired notification to %s', email)
context = { context = {
'user': subscription.user, 'user': user,
'subscription': subscription, 'subscription': subscription,
'latest_trainings': get_latest_trainings_and_production_lessons(), 'latest_trainings': get_latest_trainings_and_production_lessons(),
'latest_posts': Post.objects.filter(is_published=True)[:5], 'latest_posts': Post.objects.filter(is_published=True)[:5],
@ -281,8 +284,8 @@ def send_mail_no_payment_method(order_id: int):
), 'send_mail_no_payment_method expects automatic subscription' ), 'send_mail_no_payment_method expects automatic subscription'
assert 'fail' in order.status, f'Unexpected order pk={order_id} status: {order.status}' assert 'fail' in order.status, f'Unexpected order pk={order_id} status: {order.status}'
user = order.user customer = order.customer
customer = user.customer user = customer.user
email = customer.billing_address.email or user.email email = customer.billing_address.email or user.email
logger.debug('Sending %r notification to %s', order.status, email) logger.debug('Sending %r notification to %s', order.status, email)
@ -296,7 +299,7 @@ def send_mail_no_payment_method(order_id: int):
receipt_url = absolute_url('subscriptions:receipt', kwargs={'order_id': order.pk}) receipt_url = absolute_url('subscriptions:receipt', kwargs={'order_id': order.pk})
context = { context = {
'user': subscription.user, 'user': user,
'email': email, 'email': email,
'order': order, 'order': order,
'subscription': subscription, 'subscription': subscription,

View File

@ -440,7 +440,8 @@ class BaseSubscriptionTestCase(TestCase):
self.assertIn('Blender Studio Team', email_body) self.assertIn('Blender Studio Team', email_body)
def _assert_payment_soft_failed_email_is_sent(self, subscription): def _assert_payment_soft_failed_email_is_sent(self, subscription):
user = subscription.user customer = subscription.customer
user = customer.user
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
_write_mail(mail) _write_mail(mail)
email = mail.outbox[0] email = mail.outbox[0]
@ -470,7 +471,8 @@ class BaseSubscriptionTestCase(TestCase):
self.assertIn('Blender Studio Team', email_body) self.assertIn('Blender Studio Team', email_body)
def _assert_payment_failed_email_is_sent(self, subscription): def _assert_payment_failed_email_is_sent(self, subscription):
user = subscription.user customer = subscription.customer
user = customer.user
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
_write_mail(mail) _write_mail(mail)
email = mail.outbox[0] email = mail.outbox[0]
@ -497,7 +499,8 @@ class BaseSubscriptionTestCase(TestCase):
self.assertIn('Blender Studio Team', email_body) self.assertIn('Blender Studio Team', email_body)
def _assert_payment_paid_email_is_sent(self, subscription): def _assert_payment_paid_email_is_sent(self, subscription):
user = subscription.user customer = subscription.customer
user = customer.user
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
_write_mail(mail) _write_mail(mail)
email = mail.outbox[0] email = mail.outbox[0]
@ -523,7 +526,8 @@ class BaseSubscriptionTestCase(TestCase):
self.assertIn('Blender Studio Team', email_body) self.assertIn('Blender Studio Team', email_body)
def _assert_managed_subscription_notification_email_is_sent(self, subscription): def _assert_managed_subscription_notification_email_is_sent(self, subscription):
user = subscription.user customer = subscription.customer
user = customer.user
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
_write_mail(mail) _write_mail(mail)
email = mail.outbox[0] email = mail.outbox[0]
@ -542,11 +546,12 @@ class BaseSubscriptionTestCase(TestCase):
) )
def _assert_subscription_expired_email_is_sent(self, subscription): def _assert_subscription_expired_email_is_sent(self, subscription):
user = subscription.user customer = subscription.customer
user = customer.user
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
_write_mail(mail) _write_mail(mail)
email = mail.outbox[0] email = mail.outbox[0]
self.assertEqual(email.to, [subscription.user.email]) self.assertEqual(email.to, [user.email])
self.assertEqual(email.from_email, 'webmaster@localhost') self.assertEqual(email.from_email, 'webmaster@localhost')
self.assertEqual(email.subject, 'We miss you at Blender Studio') self.assertEqual(email.subject, 'We miss you at Blender Studio')
self.assertEqual(email.alternatives[0][1], 'text/html') self.assertEqual(email.alternatives[0][1], 'text/html')

View File

@ -17,18 +17,20 @@ from subscriptions.tests.base import BaseSubscriptionTestCase
import subscriptions.tasks import subscriptions.tasks
import users.tasks import users.tasks
import users.tests.util as util import users.tests.util as util
from common.tests.factories.users import OAuthUserInfoFactory
class TestClock(BaseSubscriptionTestCase): class TestClockBraintree(BaseSubscriptionTestCase):
def _create_subscription_due_now(self) -> Subscription: def _create_subscription_due_now(self) -> Subscription:
user = create_customer_with_billing_address(country='NL', full_name='Jane Doe') customer = create_customer_with_billing_address(country='NL', full_name='Jane Doe')
OAuthUserInfoFactory(user=customer.user, oauth_user_id=554433)
now = timezone.now() now = timezone.now()
with mock.patch('django.utils.timezone.now') as mock_now: with mock.patch('django.utils.timezone.now') as mock_now:
mock_now.return_value = now + relativedelta(months=-1) mock_now.return_value = now + relativedelta(months=-1)
# print('fake now:', mock_now.return_value) # print('fake now:', mock_now.return_value)
subscription = SubscriptionFactory( subscription = SubscriptionFactory(
user=user, customer=customer,
payment_method__customer_id=user.customer.pk, payment_method__customer_id=customer.pk,
payment_method__recognisable_name='Test payment method', payment_method__recognisable_name='Test payment method',
payment_method__gateway=Gateway.objects.get(name='braintree'), payment_method__gateway=Gateway.objects.get(name='braintree'),
currency='USD', currency='USD',
@ -111,7 +113,7 @@ class TestClock(BaseSubscriptionTestCase):
# Tick the clock and check that order and transaction were created # Tick the clock and check that order and transaction were created
util.mock_blender_id_badger_badger_response( util.mock_blender_id_badger_badger_response(
'revoke', 'cloud_subscriber', self.subscription.user.oauth_info.oauth_user_id 'revoke', 'cloud_subscriber', self.subscription.customer.user.oauth_info.oauth_user_id
) )
Clock().tick() Clock().tick()
@ -164,7 +166,7 @@ class TestClock(BaseSubscriptionTestCase):
# Create another active subscription for the same user # Create another active subscription for the same user
SubscriptionFactory( SubscriptionFactory(
user=self.subscription.user, customer=self.subscription.customer,
payment_method=self.subscription.payment_method, payment_method=self.subscription.payment_method,
currency='USD', currency='USD',
price=Money('USD', 1110), price=Money('USD', 1110),
@ -187,10 +189,12 @@ class TestClock(BaseSubscriptionTestCase):
) )
def test_automated_payment_paid_email_is_sent(self): def test_automated_payment_paid_email_is_sent(self):
now = timezone.now() now = timezone.now()
self.assertEqual(self.subscription.collection_method, 'automatic')
# Tick the clock and check that subscription renews, order and transaction were created # Tick the clock and check that subscription renews, order and transaction were created
with patch( with patch(
'looper.gateways.BraintreeGateway.transact_sale', return_value='mock-transaction-id' 'looper.gateways.BraintreeGateway.transact_sale',
return_value={'transaction_id': 'mock-transaction-id'},
): ):
Clock().tick() Clock().tick()
@ -251,9 +255,9 @@ class TestClock(BaseSubscriptionTestCase):
class TestClockExpiredSubscription(BaseSubscriptionTestCase): class TestClockExpiredSubscription(BaseSubscriptionTestCase):
def test_subscription_on_hold_not_long_enough(self): def test_subscription_on_hold_not_long_enough(self):
now = timezone.now() now = timezone.now()
user = create_customer_with_billing_address(country='NL', full_name='Jane Doe') customer = create_customer_with_billing_address(country='NL', full_name='Jane Doe')
self.subscription = SubscriptionFactory( self.subscription = SubscriptionFactory(
user=user, customer=customer,
status='on-hold', status='on-hold',
# payment date has passed, but not long enough ago # payment date has passed, but not long enough ago
next_payment=now - timedelta(weeks=4), next_payment=now - timedelta(weeks=4),
@ -280,15 +284,16 @@ class TestClockExpiredSubscription(BaseSubscriptionTestCase):
@responses.activate @responses.activate
def test_subscription_on_hold_too_long_status_changed_to_expired_email_sent(self): def test_subscription_on_hold_too_long_status_changed_to_expired_email_sent(self):
now = timezone.now() now = timezone.now()
user = create_customer_with_billing_address(country='NL', full_name='Jane Doe') customer = create_customer_with_billing_address(country='NL', full_name='Jane Doe')
OAuthUserInfoFactory(user=customer.user, oauth_user_id=223344)
self.subscription = SubscriptionFactory( self.subscription = SubscriptionFactory(
user=user, customer=customer,
status='on-hold', status='on-hold',
# payment date has passed a long long time ago # payment date has passed a long long time ago
next_payment=now - timedelta(weeks=4 * 10), next_payment=now - timedelta(weeks=4 * 10),
) )
util.mock_blender_id_badger_badger_response( util.mock_blender_id_badger_badger_response(
'revoke', 'cloud_subscriber', user.oauth_info.oauth_user_id 'revoke', 'cloud_subscriber', customer.user.oauth_info.oauth_user_id
) )
Clock().tick() Clock().tick()