mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 13:33:24 +00:00
billing: Create customer_has_credit_card_as_default_source function.
This commit is contained in:
@@ -338,6 +338,19 @@ def do_replace_payment_source(
|
|||||||
return updated_stripe_customer
|
return updated_stripe_customer
|
||||||
|
|
||||||
|
|
||||||
|
def stripe_customer_has_credit_card_as_default_source(stripe_customer: stripe.Customer) -> bool:
|
||||||
|
if not stripe_customer.default_source:
|
||||||
|
return False
|
||||||
|
return stripe_customer.default_source.object == "card"
|
||||||
|
|
||||||
|
|
||||||
|
def customer_has_credit_card_as_default_source(customer: Customer) -> bool:
|
||||||
|
if not customer.stripe_customer_id:
|
||||||
|
return False
|
||||||
|
stripe_customer = stripe_get_customer(customer.stripe_customer_id)
|
||||||
|
return stripe_customer_has_credit_card_as_default_source(stripe_customer)
|
||||||
|
|
||||||
|
|
||||||
# event_time should roughly be timezone_now(). Not designed to handle
|
# event_time should roughly be timezone_now(). Not designed to handle
|
||||||
# event_times in the past or future
|
# event_times in the past or future
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"account_balance": 0,
|
||||||
|
"address": null,
|
||||||
|
"balance": 0,
|
||||||
|
"created": 1000000000,
|
||||||
|
"currency": null,
|
||||||
|
"default_source": null,
|
||||||
|
"delinquent": false,
|
||||||
|
"description": "zulip (Zulip Dev)",
|
||||||
|
"discount": null,
|
||||||
|
"email": "iago@zulip.com",
|
||||||
|
"id": "cus_NORMALIZED0001",
|
||||||
|
"invoice_prefix": "NORMA01",
|
||||||
|
"invoice_settings": {
|
||||||
|
"custom_fields": null,
|
||||||
|
"default_payment_method": null,
|
||||||
|
"footer": null
|
||||||
|
},
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {
|
||||||
|
"realm_id": "1",
|
||||||
|
"realm_str": "zulip"
|
||||||
|
},
|
||||||
|
"name": null,
|
||||||
|
"next_invoice_sequence": 1,
|
||||||
|
"object": "customer",
|
||||||
|
"phone": null,
|
||||||
|
"preferred_locales": [],
|
||||||
|
"shipping": null,
|
||||||
|
"sources": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||||
|
},
|
||||||
|
"subscriptions": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||||
|
},
|
||||||
|
"tax_exempt": "none",
|
||||||
|
"tax_ids": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0001/tax_ids"
|
||||||
|
},
|
||||||
|
"tax_info": null,
|
||||||
|
"tax_info_verification": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"account_balance": 0,
|
||||||
|
"address": null,
|
||||||
|
"balance": 0,
|
||||||
|
"created": 1000000000,
|
||||||
|
"currency": null,
|
||||||
|
"default_source": "card_NORMALIZED00000000000001",
|
||||||
|
"delinquent": false,
|
||||||
|
"description": "zulip (Zulip Dev)",
|
||||||
|
"discount": null,
|
||||||
|
"email": "iago@zulip.com",
|
||||||
|
"id": "cus_NORMALIZED0002",
|
||||||
|
"invoice_prefix": "NORMA02",
|
||||||
|
"invoice_settings": {
|
||||||
|
"custom_fields": null,
|
||||||
|
"default_payment_method": null,
|
||||||
|
"footer": null
|
||||||
|
},
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {
|
||||||
|
"realm_id": "1",
|
||||||
|
"realm_str": "zulip"
|
||||||
|
},
|
||||||
|
"name": null,
|
||||||
|
"next_invoice_sequence": 1,
|
||||||
|
"object": "customer",
|
||||||
|
"phone": null,
|
||||||
|
"preferred_locales": [],
|
||||||
|
"shipping": null,
|
||||||
|
"sources": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"address_city": "Pacific",
|
||||||
|
"address_country": "United States",
|
||||||
|
"address_line1": "Under the sea,",
|
||||||
|
"address_line1_check": "pass",
|
||||||
|
"address_line2": null,
|
||||||
|
"address_state": null,
|
||||||
|
"address_zip": "33333",
|
||||||
|
"address_zip_check": "pass",
|
||||||
|
"brand": "Visa",
|
||||||
|
"country": "US",
|
||||||
|
"customer": "cus_NORMALIZED0002",
|
||||||
|
"cvc_check": "pass",
|
||||||
|
"dynamic_last4": null,
|
||||||
|
"exp_month": 3,
|
||||||
|
"exp_year": 2033,
|
||||||
|
"fingerprint": "NORMALIZED000001",
|
||||||
|
"funding": "credit",
|
||||||
|
"id": "card_NORMALIZED00000000000001",
|
||||||
|
"last4": "4242",
|
||||||
|
"metadata": {},
|
||||||
|
"name": "Ada Starr",
|
||||||
|
"object": "card",
|
||||||
|
"tokenization_method": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 1,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0002/sources"
|
||||||
|
},
|
||||||
|
"subscriptions": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0002/subscriptions"
|
||||||
|
},
|
||||||
|
"tax_exempt": "none",
|
||||||
|
"tax_ids": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0002/tax_ids"
|
||||||
|
},
|
||||||
|
"tax_info": null,
|
||||||
|
"tax_info_verification": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"account_balance": 0,
|
||||||
|
"address": null,
|
||||||
|
"balance": 0,
|
||||||
|
"created": 1000000000,
|
||||||
|
"currency": null,
|
||||||
|
"default_source": null,
|
||||||
|
"delinquent": false,
|
||||||
|
"description": "zulip (Zulip Dev)",
|
||||||
|
"discount": null,
|
||||||
|
"email": "iago@zulip.com",
|
||||||
|
"id": "cus_NORMALIZED0001",
|
||||||
|
"invoice_prefix": "NORMA01",
|
||||||
|
"invoice_settings": {
|
||||||
|
"custom_fields": null,
|
||||||
|
"default_payment_method": null,
|
||||||
|
"footer": null
|
||||||
|
},
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {
|
||||||
|
"realm_id": "1",
|
||||||
|
"realm_str": "zulip"
|
||||||
|
},
|
||||||
|
"name": null,
|
||||||
|
"next_invoice_sequence": 1,
|
||||||
|
"object": "customer",
|
||||||
|
"phone": null,
|
||||||
|
"preferred_locales": [],
|
||||||
|
"shipping": null,
|
||||||
|
"sources": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||||
|
},
|
||||||
|
"subscriptions": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||||
|
},
|
||||||
|
"tax_exempt": "none",
|
||||||
|
"tax_ids": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0001/tax_ids"
|
||||||
|
},
|
||||||
|
"tax_info": null,
|
||||||
|
"tax_info_verification": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
{
|
||||||
|
"account_balance": 0,
|
||||||
|
"address": null,
|
||||||
|
"balance": 0,
|
||||||
|
"created": 1000000000,
|
||||||
|
"currency": null,
|
||||||
|
"default_source": {
|
||||||
|
"address_city": "Pacific",
|
||||||
|
"address_country": "United States",
|
||||||
|
"address_line1": "Under the sea,",
|
||||||
|
"address_line1_check": "pass",
|
||||||
|
"address_line2": null,
|
||||||
|
"address_state": null,
|
||||||
|
"address_zip": "33333",
|
||||||
|
"address_zip_check": "pass",
|
||||||
|
"brand": "Visa",
|
||||||
|
"country": "US",
|
||||||
|
"customer": "cus_NORMALIZED0002",
|
||||||
|
"cvc_check": "pass",
|
||||||
|
"dynamic_last4": null,
|
||||||
|
"exp_month": 3,
|
||||||
|
"exp_year": 2033,
|
||||||
|
"fingerprint": "NORMALIZED000001",
|
||||||
|
"funding": "credit",
|
||||||
|
"id": "card_NORMALIZED00000000000001",
|
||||||
|
"last4": "4242",
|
||||||
|
"metadata": {},
|
||||||
|
"name": "Ada Starr",
|
||||||
|
"object": "card",
|
||||||
|
"tokenization_method": null
|
||||||
|
},
|
||||||
|
"delinquent": false,
|
||||||
|
"description": "zulip (Zulip Dev)",
|
||||||
|
"discount": null,
|
||||||
|
"email": "iago@zulip.com",
|
||||||
|
"id": "cus_NORMALIZED0002",
|
||||||
|
"invoice_prefix": "NORMA02",
|
||||||
|
"invoice_settings": {
|
||||||
|
"custom_fields": null,
|
||||||
|
"default_payment_method": null,
|
||||||
|
"footer": null
|
||||||
|
},
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {
|
||||||
|
"realm_id": "1",
|
||||||
|
"realm_str": "zulip"
|
||||||
|
},
|
||||||
|
"name": null,
|
||||||
|
"next_invoice_sequence": 1,
|
||||||
|
"object": "customer",
|
||||||
|
"phone": null,
|
||||||
|
"preferred_locales": [],
|
||||||
|
"shipping": null,
|
||||||
|
"sources": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"address_city": "Pacific",
|
||||||
|
"address_country": "United States",
|
||||||
|
"address_line1": "Under the sea,",
|
||||||
|
"address_line1_check": "pass",
|
||||||
|
"address_line2": null,
|
||||||
|
"address_state": null,
|
||||||
|
"address_zip": "33333",
|
||||||
|
"address_zip_check": "pass",
|
||||||
|
"brand": "Visa",
|
||||||
|
"country": "US",
|
||||||
|
"customer": "cus_NORMALIZED0002",
|
||||||
|
"cvc_check": "pass",
|
||||||
|
"dynamic_last4": null,
|
||||||
|
"exp_month": 3,
|
||||||
|
"exp_year": 2033,
|
||||||
|
"fingerprint": "NORMALIZED000001",
|
||||||
|
"funding": "credit",
|
||||||
|
"id": "card_NORMALIZED00000000000001",
|
||||||
|
"last4": "4242",
|
||||||
|
"metadata": {},
|
||||||
|
"name": "Ada Starr",
|
||||||
|
"object": "card",
|
||||||
|
"tokenization_method": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 1,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0002/sources"
|
||||||
|
},
|
||||||
|
"subscriptions": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0002/subscriptions"
|
||||||
|
},
|
||||||
|
"tax_exempt": "none",
|
||||||
|
"tax_ids": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0002/tax_ids"
|
||||||
|
},
|
||||||
|
"tax_info": null,
|
||||||
|
"tax_info_verification": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"card": {
|
||||||
|
"address_city": "Pacific",
|
||||||
|
"address_country": "United States",
|
||||||
|
"address_line1": "Under the sea,",
|
||||||
|
"address_line1_check": "unchecked",
|
||||||
|
"address_line2": null,
|
||||||
|
"address_state": null,
|
||||||
|
"address_zip": "33333",
|
||||||
|
"address_zip_check": "unchecked",
|
||||||
|
"brand": "Visa",
|
||||||
|
"country": "US",
|
||||||
|
"cvc_check": "unchecked",
|
||||||
|
"dynamic_last4": null,
|
||||||
|
"exp_month": 3,
|
||||||
|
"exp_year": 2033,
|
||||||
|
"fingerprint": "NORMALIZED000001",
|
||||||
|
"funding": "credit",
|
||||||
|
"id": "card_NORMALIZED00000000000001",
|
||||||
|
"last4": "4242",
|
||||||
|
"metadata": {},
|
||||||
|
"name": "Ada Starr",
|
||||||
|
"object": "card",
|
||||||
|
"tokenization_method": null
|
||||||
|
},
|
||||||
|
"client_ip": "0.0.0.0",
|
||||||
|
"created": 1000000000,
|
||||||
|
"id": "tok_NORMALIZED00000000000001",
|
||||||
|
"livemode": false,
|
||||||
|
"object": "token",
|
||||||
|
"type": "card",
|
||||||
|
"used": false
|
||||||
|
}
|
||||||
@@ -29,6 +29,8 @@ from corporate.lib.stripe import (
|
|||||||
attach_discount_to_realm,
|
attach_discount_to_realm,
|
||||||
catch_stripe_errors,
|
catch_stripe_errors,
|
||||||
compute_plan_parameters,
|
compute_plan_parameters,
|
||||||
|
customer_has_credit_card_as_default_source,
|
||||||
|
do_create_stripe_customer,
|
||||||
get_discount_for_realm,
|
get_discount_for_realm,
|
||||||
get_latest_seat_count,
|
get_latest_seat_count,
|
||||||
get_price_per_license,
|
get_price_per_license,
|
||||||
@@ -40,6 +42,7 @@ from corporate.lib.stripe import (
|
|||||||
next_month,
|
next_month,
|
||||||
process_initial_upgrade,
|
process_initial_upgrade,
|
||||||
sign_string,
|
sign_string,
|
||||||
|
stripe_customer_has_credit_card_as_default_source,
|
||||||
stripe_get_customer,
|
stripe_get_customer,
|
||||||
unsign_string,
|
unsign_string,
|
||||||
update_billing_method_of_current_plan,
|
update_billing_method_of_current_plan,
|
||||||
@@ -502,6 +505,7 @@ class StripeTest(StripeTestCase):
|
|||||||
Customer.objects.get(realm=user.realm).stripe_customer_id
|
Customer.objects.get(realm=user.realm).stripe_customer_id
|
||||||
)
|
)
|
||||||
self.assertEqual(stripe_customer.default_source.id[:5], "card_")
|
self.assertEqual(stripe_customer.default_source.id[:5], "card_")
|
||||||
|
self.assertTrue(stripe_customer_has_credit_card_as_default_source(stripe_customer))
|
||||||
self.assertEqual(stripe_customer.description, "zulip (Zulip Dev)")
|
self.assertEqual(stripe_customer.description, "zulip (Zulip Dev)")
|
||||||
self.assertEqual(stripe_customer.discount, None)
|
self.assertEqual(stripe_customer.discount, None)
|
||||||
self.assertEqual(stripe_customer.email, user.email)
|
self.assertEqual(stripe_customer.email, user.email)
|
||||||
@@ -662,6 +666,7 @@ class StripeTest(StripeTestCase):
|
|||||||
stripe_customer = stripe_get_customer(
|
stripe_customer = stripe_get_customer(
|
||||||
Customer.objects.get(realm=user.realm).stripe_customer_id
|
Customer.objects.get(realm=user.realm).stripe_customer_id
|
||||||
)
|
)
|
||||||
|
self.assertFalse(stripe_customer_has_credit_card_as_default_source(stripe_customer))
|
||||||
# It can take a second for Stripe to attach the source to the customer, and in
|
# It can take a second for Stripe to attach the source to the customer, and in
|
||||||
# particular it may not be attached at the time stripe_get_customer is called above,
|
# particular it may not be attached at the time stripe_get_customer is called above,
|
||||||
# causing test flakes.
|
# causing test flakes.
|
||||||
@@ -2684,6 +2689,18 @@ class StripeTest(StripeTestCase):
|
|||||||
self.assertEqual(realm_audit_log.acting_user, iago)
|
self.assertEqual(realm_audit_log.acting_user, iago)
|
||||||
self.assertEqual(realm_audit_log.extra_data, str(expected_extra_data))
|
self.assertEqual(realm_audit_log.extra_data, str(expected_extra_data))
|
||||||
|
|
||||||
|
@mock_stripe()
|
||||||
|
def test_customer_has_credit_card_as_default_source(self, *mocks: Mock) -> None:
|
||||||
|
iago = self.example_user("iago")
|
||||||
|
customer = Customer.objects.create(realm=iago.realm)
|
||||||
|
self.assertFalse(customer_has_credit_card_as_default_source(customer))
|
||||||
|
|
||||||
|
customer = do_create_stripe_customer(iago)
|
||||||
|
self.assertFalse(customer_has_credit_card_as_default_source(customer))
|
||||||
|
|
||||||
|
customer = do_create_stripe_customer(iago, stripe_token=stripe_create_token().id)
|
||||||
|
self.assertTrue(customer_has_credit_card_as_default_source(customer))
|
||||||
|
|
||||||
|
|
||||||
class RequiresBillingAccessTest(ZulipTestCase):
|
class RequiresBillingAccessTest(ZulipTestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user