def test_detach(self): original_detach = PaymentMethodDict.detach def mocked_detach(*args, **kwargs): return original_detach(*args, **kwargs) with patch( "stripe.PaymentMethod.retrieve", return_value=deepcopy(FAKE_PAYMENT_METHOD_I), autospec=True, ): PaymentMethod.sync_from_stripe_data( deepcopy(FAKE_PAYMENT_METHOD_I)) self.assertEqual(1, self.customer.payment_methods.count()) payment_method = self.customer.payment_methods.first() with patch("tests.PaymentMethodDict.detach", side_effect=mocked_detach, autospec=True) as mock_detach, patch( "stripe.PaymentMethod.retrieve", return_value=deepcopy(FAKE_PAYMENT_METHOD_I), autospec=True, ): self.assertTrue(payment_method.detach()) self.assertEqual(0, self.customer.payment_methods.count()) self.assertIsNone(self.customer.default_payment_method) self.assertIsNone(payment_method.customer) if sys.version_info >= (3, 6): # this mock isn't working on py34, py35, but it's not strictly necessary # for the test mock_detach.assert_called() self.assert_fks(payment_method, expected_blank_fks={"djstripe.PaymentMethod.customer"}) with patch( "tests.PaymentMethodDict.detach", side_effect=InvalidRequestError( message="A source must be attached to a customer to be used " "as a `payment_method`", param="payment_method", ), autospec=True, ) as mock_detach, patch( "stripe.PaymentMethod.retrieve", return_value=deepcopy(FAKE_PAYMENT_METHOD_I), autospec=True, ) as payment_method_retrieve_mock: payment_method_retrieve_mock.return_value["customer"] = None self.assertFalse(payment_method.detach(), "Second call to detach should return false")
def test_attach_obj(self, attach_mock): pm = PaymentMethod.sync_from_stripe_data(FAKE_PAYMENT_METHOD_I) payment_method = PaymentMethod.attach(pm, customer=self.customer) self.assert_fks( payment_method, expected_blank_fks={ "djstripe.Customer.coupon", "djstripe.Customer.default_payment_method", }, )
def test_attach_synced(self, attach_mock): fake_payment_method = deepcopy(FAKE_PAYMENT_METHOD_I) fake_payment_method["customer"] = None payment_method = PaymentMethod.sync_from_stripe_data( fake_payment_method) self.assert_fks(payment_method, expected_blank_fks={"djstripe.PaymentMethod.customer"}) payment_method = PaymentMethod.attach(payment_method.id, stripe_customer=FAKE_CUSTOMER) self.assert_fks(payment_method, expected_blank_fks={"djstripe.Customer.coupon"})
def test_sync_null_customer(self): payment_method = PaymentMethod.sync_from_stripe_data( deepcopy(FAKE_PAYMENT_METHOD_I)) self.assertIsNotNone(payment_method.customer) # simulate remote detach fake_payment_method_no_customer = deepcopy(FAKE_PAYMENT_METHOD_I) fake_payment_method_no_customer["customer"] = None payment_method = PaymentMethod.sync_from_stripe_data( fake_payment_method_no_customer) self.assertIsNone(payment_method.customer) self.assert_fks(payment_method, expected_blank_fks={"djstripe.PaymentMethod.customer"})
def setUp(self): self.user = get_user_model().objects.create_user(username="******", email="*****@*****.**") self.customer = FAKE_CUSTOMER.create_for_user(self.user) self.payment_method, _ = PaymentMethod._get_or_create_source(FAKE_CARD, "card") self.card = self.payment_method.resolve() self.customer.default_source = self.payment_method self.customer.save() self.account = Account.objects.create()
def test_attach(self, attach_mock): payment_method = PaymentMethod.attach(FAKE_PAYMENT_METHOD_I["id"], customer=FAKE_CUSTOMER["id"]) self.assert_fks( payment_method, expected_blank_fks={ "djstripe.Customer.coupon", "djstripe.Customer.default_payment_method", }, )
def new_customer(request): body = json.loads(request.body) # Retrieve payment method from Stripe data payment_method = stripe.PaymentMethod.retrieve(body['payment_method']) PaymentMethod.sync_from_stripe_data(payment_method) user = NewUser.objects.get(username=body['user']) if not getattr(user, 'owner'): # If customer doesn't exist, create new Customer object customer = stripe.Customer.create( email=getattr(user, 'email'), payment_method=payment_method, invoice_settings={"default_payment_method": payment_method}, ) else: # Attach new payment method to customer customer = stripe.Customer.retrieve(getattr(user, 'owner').id) stripe.PaymentMethod.attach(payment_method, customer=customer.id) stripe.Customer.modify( customer.id, invoice_settings={"default_payment_method": payment_method}, ) # Create new Subscription and attach to customer subscription = stripe.Subscription.create( customer=customer.id, items=[{ 'price': body['price_id'] }], expand=['latest_invoice.payment_intent'], trial_period_days=7) # Save in Django DB django_customer = Customer.sync_from_stripe_data(customer) user.owner = django_customer django_subscription = Subscription.sync_from_stripe_data(subscription) user.subscription = django_subscription user.save() return JsonResponse(data={ 'customer': customer, 'subscription': subscription })
def test_detach_card(self): original_detach = PaymentMethodDict.detach # "card_" payment methods are deleted after detach deleted_card_exception = InvalidRequestError( message="No such payment_method: card_xxxx", param="payment_method", code="resource_missing", ) def mocked_detach(*args, **kwargs): return original_detach(*args, **kwargs) with patch( "stripe.PaymentMethod.retrieve", return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD), autospec=True, ): PaymentMethod.sync_from_stripe_data( deepcopy(FAKE_CARD_AS_PAYMENT_METHOD)) self.assertEqual(1, self.customer.payment_methods.count()) payment_method = self.customer.payment_methods.first() self.assertTrue(payment_method.id.startswith("card_"), "We expect this to be a 'card_'") with patch("tests.PaymentMethodDict.detach", side_effect=mocked_detach, autospec=True) as mock_detach, patch( "stripe.PaymentMethod.retrieve", return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD), autospec=True, ): self.assertTrue(payment_method.detach()) self.assertEqual(0, self.customer.payment_methods.count()) self.assertIsNone(self.customer.default_payment_method) self.assertEqual( PaymentMethod.objects.filter(id=payment_method.id).count(), 0, "We expect PaymentMethod id = card_* to be deleted", ) if sys.version_info >= (3, 6): # this mock isn't working on py34, py35, but it's not strictly necessary # for the test mock_detach.assert_called() with patch( "tests.PaymentMethodDict.detach", side_effect=InvalidRequestError( message="A source must be attached to a customer to be used " "as a `payment_method`", param="payment_method", ), autospec=True, ) as mock_detach, patch( "stripe.PaymentMethod.retrieve", side_effect=deleted_card_exception, autospec=True, ) as payment_method_retrieve_mock: payment_method_retrieve_mock.return_value["customer"] = None self.assertFalse(payment_method.detach(), "Second call to detach should return false")
def post(self, request: HttpRequest, account_name: str) -> HttpResponse: self.request_permissions_guard(request, account_name) data = json.loads(request.body) customer_id = data.get("customer_id") coupon_code = data.get("coupon_code") try: plan = Plan.objects.get(id=data["plan"], active=True) except Plan.DoesNotExist: raise ValueError("TODO: Raise better error for missing Plan") if not plan.product.extension.is_purchasable and not coupon_code: raise ValueError("This product is only purchasable with a coupon.") if not customer_id: email = request.user.emailaddress_set.filter( email=data["email"], verified=True).first() if not email: raise ValueError( "Validated email address {} was not found for the customer." .format(email.email)) dj_customer, created = Customer.get_or_create( subscriber=request.user) if not coupon_code: PaymentMethod.attach(data["payment_method"], dj_customer) customer = dj_customer.api_retrieve() customer["invoice_settings"] = { "default_payment_method": data["payment_method"] } customer.save() dj_customer.sync_from_stripe_data(customer) else: try: dj_customer = Customer.objects.get(djstripe_id=customer_id) except Customer.DoesNotExist: raise ValueError( "TODO: Raise better error for missing Customer.") if dj_customer.subscriber != request.user: raise PermissionDenied( "The current user does not own this Customer.") try: subscription = dj_customer.subscribe(plan, coupon=coupon_code) except InvalidRequestError as e: return JsonResponse({"success": False, "error": e.user_message}) AccountSubscription.objects.create(account=self.account, subscription=subscription) messages.success( request, "Sign up to {} subscription was successful.".format(plan.name)) post_redirect = reverse("account_subscription_detail", args=(account_name, subscription.id)) return JsonResponse({ "success": True, "customer_id": dj_customer.djstripe_id, "redirect": post_redirect, })