def delete_stripe_customer(self) -> bool: if self.user.plan != Plan.free(): self.user.plan = Plan.free() self.unlink_from_plan(self.user.plan) stripe.Customer.delete(self.user.stripe_id) self.user.stripe_id = None self.user.stripe_payment_method = None return True
def link_to_plan(self, plan: Plan) -> None: uns = UserNotification(self.user) subscription: stripe.Subscription = None # this is an unrecoverable situation - we will need to set # up the users stripe id before this point - they should # not be able to get here without it. assert self.user.stripe_id is not None subscription = stripe.Subscription.create( customer=self.user.stripe_id, default_payment_method=self.user.stripe_payment_method, items=[{"plan": plan.stripe_id}], expand=["latest_invoice.payment_intent"], ) if subscription.latest_invoice.payment_intent.status == "succeeded": uns.subscribed_successfully() return if subscription.latest_invoice.payment_intent.status == "requires_action": uns.subscription_requires_action() else: uns.subscribed_failed() self.user.plan = Plan.free()
def test_user_unsubscribed_is_run_when_user_tier_is_changed_to_free( self, user_unsubscribed, session): user = UserFactory() user.plan = Plan.free() session.add(user) session.commit() user_unsubscribed.assert_called_with(user.id)
def test_user_unlink_from_plan_via_subscription(self): """Unlink a User from a plan based on change in their stripe subscription""" user = StripedUserFactory(tier="paid", stripe__payment_method="pm_card_visa") subscription = UserStripe(user)._retrieve_users_subscriptions( Plan.paid()) UserStripe(user).unlink_from_plan_via_subscription(subscription.id) assert UserStripe(user)._retrieve_users_subscriptions( Plan.paid()) is None assert user.plan == Plan.free()
def test_user_unsubscribed(self, unsubscribe_successful, unsubscribe_not_required, session): """ User unsubscribes successfully """ user = SubscribedUserFactory(tier="paid", stripe__payment_method="pm_card_visa") user.plan = Plan.free() session.add(user) session.flush() UserStripe(user).unlink_from_plan(Plan.paid()) assert unsubscribe_successful.called_once assert not unsubscribe_not_required.called
def test_user_unsubscribed_is_run_when_user_tier_is_changed_to_free( self, user_unsubscribed, session ): user = UserFactory() db.session.add(user) db.session.flush() UserUpdate( user=user, rels={"plan": {"data": {"type": "plan", "id": str(Plan.free().id)}}}, ).update() db.session.commit() user_unsubscribed.assert_called_with(user.id, Plan.paid().id)
def test_user_unsubscribed_but_didnt_need_to(self, unsubscribe_successful, unsubscribe_not_required, session): """User can't unsubscribe if they're not subscribed in the first place <taps forehead>""" user = StripedUserFactory(tier="free", stripe__payment_method="pm_card_visa") user.plan = Plan.free() session.add(user) session.flush() UserStripe(user).unlink_from_plan(Plan.paid()) assert not unsubscribe_successful.called assert unsubscribe_not_required.called_once
def test_delete_customer(self, app): """ Delete a Stripe Customer """ user = StripedUserFactory(tier="paid", email="*****@*****.**", stripe__payment_method="pm_card_visa") uss = UserStripe(user) uss.link_to_plan(Plan.paid()) customer_id = user.stripe_id UserStripe(user).delete_stripe_customer() cus = stripe.Customer.retrieve(customer_id) assert cus.deleted is True assert user.plan == Plan.free()
def test_user_fails_to_subscribe_decline( self, subscribed_successfully, subscribed_failed, subscription_requires_action, session, ): """ User's card was declined when paying """ # This is the 40000000341 card that will fail when a charge is initiated user = StripedUserFactory( tier="paid", stripe__payment_method="pm_card_chargeCustomerFail") session.add(user) session.flush() UserStripe(user).link_to_plan(user.plan) assert not subscribed_successfully.called assert not subscription_requires_action.called assert subscribed_failed.called_once assert user.plan == Plan.free()
def test_user_fails_to_subscribe_security( self, subscribed_failed, subscribed_successfully, subscription_requires_action, session, ): """ User payment failed becaues of additional security measures on their account """ user = StripedUserFactory( tier="paid", stripe__payment_method="pm_card_threeDSecure2Required") session.add(user) session.flush() UserStripe(user).link_to_plan(user.plan) assert not subscribed_successfully.called assert not subscribed_failed.called assert subscription_requires_action.called assert user.plan == Plan.free()
def update_stripe_customer(self, updated_field) -> bool: if self.user.plan == Plan.free(): # we don't need to do anything return True uns = UserNotification(self.user) subscription = self._retrieve_users_subscriptions(self.user.plan) if subscription: if updated_field == "stripe_payment_method": stripe.Subscription.modify( subscription.id, default_payment_method=self.user.stripe_payment_method, ) return True else: uns.unsubscribe_required() return False else: raise UserError( detail="User #{user_id} has no subscription and is not on free plan" )
def test_after_commit_hooks_are_specific_to_a_session( self, user_subscribed, session, app ): # What does this test prove? Well - we start by showing # the thing works and by setting up a an "after_commit" hook # on the session. (There SHOULD BE one session per request.) # The next request should NOT fire the "after_commit" hook # because it has a new session. IE - we are not leaking # Model instances across requests, and not firing subscribe # events for instances we didn't change free_user = UserFactory(tier="free") UserUpdate( user=free_user, rels={"plan": {"data": {"type": "plan", "id": str(Plan.paid().id)}}}, ).update() session.commit() session.expire_all() user_subscribed.assert_called_with(free_user.id, Plan.paid().id) user_subscribed.reset_mock() sess = db.create_scoped_session() free_user2 = UserFactory(tier="free", email="*****@*****.**") UserUpdate( user=free_user2, rels={"plan": {"data": {"type": "plan", "id": str(Plan.free().id)}}}, ).update() session.commit() user_subscribed.assert_not_called() sess.remove()
def unlink_from_plan_via_subscription(self, sub_id): # This is a "stripe to database" subscription - so we don't need to # fire the callbacks that sync stripe stripe.Subscription.delete(sub_id, expand=["plan"]) self.user.plan = Plan.free()