mirror of
https://github.com/zulip/zulip.git
synced 2025-10-30 11:33:51 +00:00
billing: Update /billing to work with new subscription model.
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"account_balance": 0,
|
||||||
|
"created": 1000000000,
|
||||||
|
"currency": "usd",
|
||||||
|
"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_NORMALIZED0001",
|
||||||
|
"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": "hamlet@zulip.com",
|
||||||
|
"id": "cus_NORMALIZED0001",
|
||||||
|
"invoice_prefix": "NORMA01",
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {
|
||||||
|
"realm_id": "1",
|
||||||
|
"realm_str": "zulip"
|
||||||
|
},
|
||||||
|
"object": "customer",
|
||||||
|
"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_NORMALIZED0001",
|
||||||
|
"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_NORMALIZED0001/sources"
|
||||||
|
},
|
||||||
|
"subscriptions": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||||
|
},
|
||||||
|
"tax_info": null,
|
||||||
|
"tax_info_verification": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"account_balance": 0,
|
||||||
|
"created": 1000000000,
|
||||||
|
"currency": "usd",
|
||||||
|
"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_NORMALIZED0001",
|
||||||
|
"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": "hamlet@zulip.com",
|
||||||
|
"id": "cus_NORMALIZED0001",
|
||||||
|
"invoice_prefix": "NORMA01",
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {
|
||||||
|
"realm_id": "1",
|
||||||
|
"realm_str": "zulip"
|
||||||
|
},
|
||||||
|
"object": "customer",
|
||||||
|
"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_NORMALIZED0001",
|
||||||
|
"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_NORMALIZED0001/sources"
|
||||||
|
},
|
||||||
|
"subscriptions": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||||
|
},
|
||||||
|
"tax_info": null,
|
||||||
|
"tax_info_verification": null
|
||||||
|
}
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
"due_date": 1000000000,
|
"due_date": 1000000000,
|
||||||
"ending_balance": 0,
|
"ending_balance": 0,
|
||||||
"finalized_at": 1000000000,
|
"finalized_at": 1000000000,
|
||||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_daf4CWkp5EbV5fMjyaF0P8Yu3h",
|
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||||
"id": "in_NORMALIZED00000000000001",
|
"id": "in_NORMALIZED00000000000001",
|
||||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_daf4CWkp5EbV5fMjyaF0P8Yu3h/pdf",
|
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||||
"lines": {
|
"lines": {
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"account_balance": 0,
|
||||||
|
"created": 1010000002,
|
||||||
|
"currency": "usd",
|
||||||
|
"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_NORMALIZED0001",
|
||||||
|
"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": "hamlet@zulip.com",
|
||||||
|
"id": "cus_NORMALIZED0001",
|
||||||
|
"invoice_prefix": "NORMA01",
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {
|
||||||
|
"realm_id": "1",
|
||||||
|
"realm_str": "zulip"
|
||||||
|
},
|
||||||
|
"object": "customer",
|
||||||
|
"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_NORMALIZED0001",
|
||||||
|
"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_NORMALIZED0001/sources"
|
||||||
|
},
|
||||||
|
"subscriptions": {
|
||||||
|
"data": [],
|
||||||
|
"has_more": false,
|
||||||
|
"object": "list",
|
||||||
|
"total_count": 0,
|
||||||
|
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||||
|
},
|
||||||
|
"tax_info": null,
|
||||||
|
"tax_info_verification": null
|
||||||
|
}
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
"due_date": 1000000000,
|
"due_date": 1000000000,
|
||||||
"ending_balance": 0,
|
"ending_balance": 0,
|
||||||
"finalized_at": 1000000000,
|
"finalized_at": 1000000000,
|
||||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_sh6wGq3YCdgkwFxDfHonbxi1JB",
|
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||||
"id": "in_NORMALIZED00000000000001",
|
"id": "in_NORMALIZED00000000000001",
|
||||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_sh6wGq3YCdgkwFxDfHonbxi1JB/pdf",
|
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||||
"lines": {
|
"lines": {
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
"due_date": 1000000000,
|
"due_date": 1000000000,
|
||||||
"ending_balance": 0,
|
"ending_balance": 0,
|
||||||
"finalized_at": 1000000000,
|
"finalized_at": 1000000000,
|
||||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_sh6wGq3YCdgkwFxDfHonbxi1JB",
|
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||||
"id": "in_NORMALIZED00000000000001",
|
"id": "in_NORMALIZED00000000000001",
|
||||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_sh6wGq3YCdgkwFxDfHonbxi1JB/pdf",
|
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||||
"lines": {
|
"lines": {
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"account_balance": 0,
|
||||||
|
"created": 1010000001,
|
||||||
|
"currency": "usd",
|
||||||
|
"default_source": null,
|
||||||
|
"delinquent": false,
|
||||||
|
"description": "zulip (Zulip Dev)",
|
||||||
|
"discount": null,
|
||||||
|
"email": "hamlet@zulip.com",
|
||||||
|
"id": "cus_NORMALIZED0001",
|
||||||
|
"invoice_prefix": "NORMA01",
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {
|
||||||
|
"realm_id": "1",
|
||||||
|
"realm_str": "zulip"
|
||||||
|
},
|
||||||
|
"object": "customer",
|
||||||
|
"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_info": null,
|
||||||
|
"tax_info_verification": null
|
||||||
|
}
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
"due_date": 1000000000,
|
"due_date": 1000000000,
|
||||||
"ending_balance": 0,
|
"ending_balance": 0,
|
||||||
"finalized_at": 1000000000,
|
"finalized_at": 1000000000,
|
||||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_O7KxfsK8GxMVb8EGiyIlGF9OUe",
|
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||||
"id": "in_NORMALIZED00000000000001",
|
"id": "in_NORMALIZED00000000000001",
|
||||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_O7KxfsK8GxMVb8EGiyIlGF9OUe/pdf",
|
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||||
"lines": {
|
"lines": {
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
"due_date": 1000000000,
|
"due_date": 1000000000,
|
||||||
"ending_balance": 0,
|
"ending_balance": 0,
|
||||||
"finalized_at": 1000000000,
|
"finalized_at": 1000000000,
|
||||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_O7KxfsK8GxMVb8EGiyIlGF9OUe",
|
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||||
"id": "in_NORMALIZED00000000000001",
|
"id": "in_NORMALIZED00000000000001",
|
||||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_O7KxfsK8GxMVb8EGiyIlGF9OUe/pdf",
|
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||||
"lines": {
|
"lines": {
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ def normalize_fixture_data(decorated_function: CallableT,
|
|||||||
id_lengths = [
|
id_lengths = [
|
||||||
('cus', 14), ('sub', 14), ('si', 14), ('sli', 14), ('req', 14), ('tok', 24), ('card', 24),
|
('cus', 14), ('sub', 14), ('si', 14), ('sli', 14), ('req', 14), ('tok', 24), ('card', 24),
|
||||||
('txn', 24), ('ch', 24), ('in', 24), ('ii', 24), ('test', 12), ('src_client_secret', 24),
|
('txn', 24), ('ch', 24), ('in', 24), ('ii', 24), ('test', 12), ('src_client_secret', 24),
|
||||||
('src', 24)]
|
('src', 24), ('invst', 26)]
|
||||||
# We'll replace cus_D7OT2jf5YAtZQ2 with something like cus_NORMALIZED0001
|
# We'll replace cus_D7OT2jf5YAtZQ2 with something like cus_NORMALIZED0001
|
||||||
pattern_translations = {
|
pattern_translations = {
|
||||||
"%s_[A-Za-z0-9]{%d}" % (prefix, length): "%s_NORMALIZED%%0%dd" % (prefix, length - 10)
|
"%s_[A-Za-z0-9]{%d}" % (prefix, length): "%s_NORMALIZED%%0%dd" % (prefix, length - 10)
|
||||||
@@ -399,12 +399,15 @@ class StripeTest(ZulipTestCase):
|
|||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual('/billing/', response.url)
|
self.assertEqual('/billing/', response.url)
|
||||||
|
|
||||||
# TODO: Check /billing has the correct information
|
# Check /billing has the correct information
|
||||||
# response = self.client_get("/billing/")
|
response = self.client_get("/billing/")
|
||||||
# self.assert_not_in_success_response(['Pay annually'], response)
|
self.assert_not_in_success_response(['Pay annually'], response)
|
||||||
# for substring in ['Your plan will renew on', '$%s.00' % (80 * self.seat_count,),
|
for substring in [
|
||||||
# 'Card ending in 4242', 'Update card']:
|
'Zulip Standard', str(self.seat_count),
|
||||||
# self.assert_in_response(substring, response)
|
'Your plan will renew on', 'January 2, 2013', '$%s.00' % (80 * self.seat_count,),
|
||||||
|
'Visa ending in 4242',
|
||||||
|
'Update card']:
|
||||||
|
self.assert_in_response(substring, response)
|
||||||
|
|
||||||
@mock_stripe(tested_timestamp_fields=["created"])
|
@mock_stripe(tested_timestamp_fields=["created"])
|
||||||
def test_upgrade_by_invoice(self, *mocks: Mock) -> None:
|
def test_upgrade_by_invoice(self, *mocks: Mock) -> None:
|
||||||
@@ -477,12 +480,14 @@ class StripeTest(ZulipTestCase):
|
|||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual('/billing/', response.url)
|
self.assertEqual('/billing/', response.url)
|
||||||
|
|
||||||
# TODO: Check /billing has the correct information
|
# Check /billing has the correct information
|
||||||
# response = self.client_get("/billing/")
|
response = self.client_get("/billing/")
|
||||||
# self.assert_not_in_success_response(['Pay annually'], response)
|
self.assert_not_in_success_response(['Pay annually', 'Update card'], response)
|
||||||
# for substring in ['Your plan will renew on', '$%s.00' % (80 * self.seat_count,),
|
for substring in [
|
||||||
# 'Card ending in 4242', 'Update card']:
|
'Zulip Standard', str(123),
|
||||||
# self.assert_in_response(substring, response)
|
'Your plan will renew on', 'January 2, 2013', '$9,840.00', # 9840 = 80 * 123
|
||||||
|
'Billed by invoice']:
|
||||||
|
self.assert_in_response(substring, response)
|
||||||
|
|
||||||
@mock_stripe()
|
@mock_stripe()
|
||||||
def test_billing_page_permissions(self, *mocks: Mock) -> None:
|
def test_billing_page_permissions(self, *mocks: Mock) -> None:
|
||||||
@@ -727,10 +732,6 @@ class StripeTest(ZulipTestCase):
|
|||||||
# card on file, and should show it
|
# card on file, and should show it
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
# If you signup via invoice, and then downgrade immediately, the
|
|
||||||
# default_source is in a weird intermediate state.
|
|
||||||
# TODO
|
|
||||||
|
|
||||||
@mock_stripe()
|
@mock_stripe()
|
||||||
def test_attach_discount_to_realm(self, *mocks: Mock) -> None:
|
def test_attach_discount_to_realm(self, *mocks: Mock) -> None:
|
||||||
# Attach discount before Stripe customer exists
|
# Attach discount before Stripe customer exists
|
||||||
|
|||||||
@@ -54,24 +54,20 @@ def check_upgrade_parameters(
|
|||||||
raise BillingError('not enough licenses',
|
raise BillingError('not enough licenses',
|
||||||
_("You must invoice for at least {} users.".format(min_licenses)))
|
_("You must invoice for at least {} users.".format(min_licenses)))
|
||||||
|
|
||||||
# TODO
|
# Should only be called if the customer is being charged automatically
|
||||||
def payment_method_string(stripe_customer: stripe.Customer) -> str: # nocoverage: TODO
|
def payment_method_string(stripe_customer: stripe.Customer) -> str:
|
||||||
subscription = None # extract_current_subscription(stripe_customer)
|
|
||||||
if subscription is not None and subscription.billing == "send_invoice":
|
|
||||||
return _("Billed by invoice")
|
|
||||||
stripe_source = stripe_customer.default_source
|
stripe_source = stripe_customer.default_source
|
||||||
# In case of e.g. an expired card
|
# In case of e.g. an expired card
|
||||||
if stripe_source is None: # nocoverage
|
if stripe_source is None: # nocoverage
|
||||||
return _("No payment method on file")
|
return _("No payment method on file")
|
||||||
if stripe_source.object == "card":
|
if stripe_source.object == "card":
|
||||||
return _("Card ending in %(last4)s" % {'last4': cast(stripe.Card, stripe_source).last4})
|
return _("%(brand)s ending in %(last4)s" % {
|
||||||
# You can get here if e.g. you sign up to pay by invoice, and then
|
'brand': cast(stripe.Card, stripe_source).brand,
|
||||||
# immediately downgrade. In that case, stripe_source.object == 'source',
|
'last4': cast(stripe.Card, stripe_source).last4})
|
||||||
# and stripe_source.type = 'ach_credit_transfer'.
|
# There might be one-off stuff we do for a particular customer that
|
||||||
# Using a catch-all error message here since there might be one-off stuff we
|
# would land them here. E.g. by default we don't support ACH for
|
||||||
# do for a particular customer that would land them here. E.g. by default we
|
# automatic payments, but in theory we could add it for a customer via
|
||||||
# don't support ACH for automatic payments, but in theory we could add it for
|
# the Stripe dashboard.
|
||||||
# a customer via the Stripe dashboard.
|
|
||||||
return _("Unknown payment method. Please contact %s." % (settings.ZULIP_ADMINISTRATOR,)) # nocoverage
|
return _("Unknown payment method. Please contact %s." % (settings.ZULIP_ADMINISTRATOR,)) # nocoverage
|
||||||
|
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
@@ -163,7 +159,7 @@ def billing_home(request: HttpRequest) -> HttpResponse:
|
|||||||
return render(request, 'corporate/billing.html', context=context)
|
return render(request, 'corporate/billing.html', context=context)
|
||||||
context = {'admin_access': True}
|
context = {'admin_access': True}
|
||||||
|
|
||||||
charge_automatically = False
|
stripe_customer = stripe_get_customer(customer.stripe_customer_id)
|
||||||
plan = get_active_plan(customer)
|
plan = get_active_plan(customer)
|
||||||
if plan is not None:
|
if plan is not None:
|
||||||
plan_name = {
|
plan_name = {
|
||||||
@@ -171,16 +167,14 @@ def billing_home(request: HttpRequest) -> HttpResponse:
|
|||||||
CustomerPlan.PLUS: 'Zulip Plus',
|
CustomerPlan.PLUS: 'Zulip Plus',
|
||||||
}[plan.tier]
|
}[plan.tier]
|
||||||
licenses = plan.licenses
|
licenses = plan.licenses
|
||||||
# Need user's timezone to do this properly
|
# Should do this in javascript, using the user's timezone
|
||||||
renewal_date = '{dt:%B} {dt.day}, {dt.year}'.format(dt=next_renewal_date(plan))
|
renewal_date = '{dt:%B} {dt.day}, {dt.year}'.format(dt=next_renewal_date(plan))
|
||||||
renewal_cents = renewal_amount(plan)
|
renewal_cents = renewal_amount(plan)
|
||||||
charge_automatically = plan.charge_automatically
|
charge_automatically = plan.charge_automatically
|
||||||
if charge_automatically: # nocoverage: TODO
|
if charge_automatically:
|
||||||
# TODO get last4
|
payment_method = payment_method_string(stripe_customer)
|
||||||
payment_method = 'Card on file'
|
else:
|
||||||
else: # nocoverage: TODO
|
|
||||||
payment_method = 'Billed by invoice'
|
payment_method = 'Billed by invoice'
|
||||||
billed_by_invoice = not plan.charge_automatically
|
|
||||||
# Can only get here by subscribing and then downgrading. We don't support downgrading
|
# Can only get here by subscribing and then downgrading. We don't support downgrading
|
||||||
# yet, but keeping this code here since we will soon.
|
# yet, but keeping this code here since we will soon.
|
||||||
else: # nocoverage
|
else: # nocoverage
|
||||||
@@ -189,6 +183,7 @@ def billing_home(request: HttpRequest) -> HttpResponse:
|
|||||||
renewal_date = ''
|
renewal_date = ''
|
||||||
renewal_cents = 0
|
renewal_cents = 0
|
||||||
payment_method = ''
|
payment_method = ''
|
||||||
|
charge_automatically = False
|
||||||
|
|
||||||
context.update({
|
context.update({
|
||||||
'plan_name': plan_name,
|
'plan_name': plan_name,
|
||||||
@@ -196,13 +191,10 @@ def billing_home(request: HttpRequest) -> HttpResponse:
|
|||||||
'renewal_date': renewal_date,
|
'renewal_date': renewal_date,
|
||||||
'renewal_amount': '{:,.2f}'.format(renewal_cents / 100.),
|
'renewal_amount': '{:,.2f}'.format(renewal_cents / 100.),
|
||||||
'payment_method': payment_method,
|
'payment_method': payment_method,
|
||||||
# TODO: Rename to charge_automatically
|
'charge_automatically': charge_automatically,
|
||||||
'billed_by_invoice': billed_by_invoice,
|
|
||||||
'publishable_key': STRIPE_PUBLISHABLE_KEY,
|
'publishable_key': STRIPE_PUBLISHABLE_KEY,
|
||||||
# TODO: get actual stripe email?
|
'stripe_email': stripe_customer.email,
|
||||||
'stripe_email': user.email,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return render(request, 'corporate/billing.html', context=context)
|
return render(request, 'corporate/billing.html', context=context)
|
||||||
|
|
||||||
@require_billing_access
|
@require_billing_access
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ class Source:
|
|||||||
|
|
||||||
class Card:
|
class Card:
|
||||||
id: str
|
id: str
|
||||||
|
brand: str
|
||||||
last4: str
|
last4: str
|
||||||
object: str
|
object: str
|
||||||
|
|
||||||
|
|||||||
@@ -29,16 +29,15 @@
|
|||||||
|
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane active" id="overview">
|
<div class="tab-pane active" id="overview">
|
||||||
<p>Your current plan is <strong>{{ plan_name }}</strong></p>
|
<p>Your current plan is <strong>{{ plan_name }}</strong>.</p>
|
||||||
<p>You are paying for <strong>{{ licenses }} users</strong>.</p>
|
<p>You are paying for <strong>{{ licenses }} users</strong>.</p>
|
||||||
<p>Your plan will renew on <strong>{{ renewal_date }}</strong> for <strong>${{ renewal_amount }}</strong>.</p>
|
<p>
|
||||||
{% if account_charges %}
|
Your plan will renew on <strong>{{ renewal_date }}</strong> for
|
||||||
<p>You have <strong>${{ account_charges }}</strong> in charges that will be added to your next bill.</p>
|
<strong>${{ renewal_amount }}</strong>.
|
||||||
{% elif account_credits %}
|
</p>
|
||||||
<p>You have <strong>${{ account_credits }}</strong> in credits that will be automatically applied to your next bill.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="payment-method" data-email="{{stripe_email}}" data-csrf="{{csrf_token}}" data-key="{{publishable_key}}">
|
<div class="tab-pane" id="payment-method" data-email="{{stripe_email}}"
|
||||||
|
data-csrf="{{csrf_token}}" data-key="{{publishable_key}}">
|
||||||
<div id="payment-section">
|
<div id="payment-section">
|
||||||
<p>Current payment method: <strong>{{ payment_method }}</strong></p>
|
<p>Current payment method: <strong>{{ payment_method }}</strong></p>
|
||||||
{% if charge_automatically %}
|
{% if charge_automatically %}
|
||||||
@@ -67,13 +66,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="support-link">
|
<div class="support-link">
|
||||||
<p>Contact <a href="mailto:support@zulipchat.com">support@zulipchat.com</a> for billing history or to make changes to your subscription.</p>
|
<p>
|
||||||
|
Contact <a href="mailto:support@zulipchat.com">support@zulipchat.com</a>
|
||||||
|
for billing history or to make changes to your subscription.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>
|
<p>
|
||||||
You must be an organization administrator or a
|
You must be an organization administrator or a billing administrator to view this page.
|
||||||
billing administrator to view this page.
|
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user