mirror of https://github.com/zulip/zulip.git
stripe: Add page to show list of past customer invoices.
This commit is contained in:
parent
d8ec141de2
commit
de33aa4b7b
|
@ -753,6 +753,37 @@ class BillingSession(ABC):
|
|||
def org_name(self) -> str:
|
||||
pass
|
||||
|
||||
def get_past_invoices_session_url(self) -> str:
|
||||
headline = "List of past invoices"
|
||||
customer = self.get_customer()
|
||||
assert customer is not None and customer.stripe_customer_id is not None
|
||||
|
||||
# Check if customer has any $0 invoices.
|
||||
if stripe.Invoice.list(
|
||||
customer=customer.stripe_customer_id,
|
||||
limit=1,
|
||||
status="paid",
|
||||
total=0,
|
||||
).data: # nocoverage
|
||||
# These are payment for upgrades which were paid directly by the customer and then we
|
||||
# created an invoice for them resulting in `$0` invoices since there was no amount due.
|
||||
headline += " ($0 invoices include payment)"
|
||||
|
||||
configuration = stripe.billing_portal.Configuration.create(
|
||||
business_profile={
|
||||
"headline": headline,
|
||||
},
|
||||
features={
|
||||
"invoice_history": {"enabled": True},
|
||||
},
|
||||
)
|
||||
|
||||
return stripe.billing_portal.Session.create(
|
||||
customer=customer.stripe_customer_id,
|
||||
configuration=configuration.id,
|
||||
return_url=f"{self.billing_session_url}/billing/",
|
||||
).url
|
||||
|
||||
def get_data_for_stripe_payment_intent(
|
||||
self,
|
||||
customer: Customer,
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -295,6 +295,8 @@ def normalize_fixture_data(
|
|||
MOCKED_STRIPE_FUNCTION_NAMES = [
|
||||
f"stripe.{name}"
|
||||
for name in [
|
||||
"billing_portal.Configuration.create",
|
||||
"billing_portal.Session.create",
|
||||
"checkout.Session.create",
|
||||
"checkout.Session.list",
|
||||
"Charge.create",
|
||||
|
@ -785,6 +787,15 @@ class StripeTest(StripeTestCase):
|
|||
response = self.client_get("/upgrade/", follow=True)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@mock_stripe()
|
||||
def test_get_past_invoices_session_url(self, *mocks: Mock) -> None:
|
||||
user = self.example_user("hamlet")
|
||||
self.login_user(user)
|
||||
self.add_card_and_upgrade(user)
|
||||
response = self.client_get("/invoices/")
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertTrue(response["Location"].startswith("https://billing.stripe.com"))
|
||||
|
||||
@mock_stripe(tested_timestamp_fields=["created"])
|
||||
def test_upgrade_by_card(self, *mocks: Mock) -> None:
|
||||
user = self.example_user("hamlet")
|
||||
|
@ -1944,6 +1955,10 @@ class StripeTest(StripeTestCase):
|
|||
response,
|
||||
)
|
||||
|
||||
response = self.client_get("/invoices/")
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response["Location"], "/billing/")
|
||||
|
||||
user.realm.plan_type = Realm.PLAN_TYPE_PLUS
|
||||
user.realm.save()
|
||||
response = self.client_get("/sponsorship/")
|
||||
|
@ -5927,6 +5942,25 @@ class TestRemoteRealmBillingFlow(StripeTestCase, RemoteRealmBillingTestCase):
|
|||
]:
|
||||
self.assert_in_response(substring, response)
|
||||
|
||||
@responses.activate
|
||||
@mock_stripe()
|
||||
def test_get_past_invoices_session_url_for_remote_realm(self, *mocks: Mock) -> None:
|
||||
self.login("hamlet")
|
||||
hamlet = self.example_user("hamlet")
|
||||
|
||||
self.add_mock_response()
|
||||
with time_machine.travel(self.now, tick=False):
|
||||
send_server_data_to_push_bouncer(consider_usage_statistics=False)
|
||||
|
||||
self.execute_remote_billing_authentication_flow(hamlet)
|
||||
self.add_card_and_upgrade()
|
||||
|
||||
response = self.client_get(
|
||||
f"{self.billing_session.billing_base_url}/invoices/", subdomain="selfhosting"
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertTrue(response["Location"].startswith("https://billing.stripe.com"))
|
||||
|
||||
@responses.activate
|
||||
@mock_stripe()
|
||||
def test_upgrade_user_to_basic_plan_free_trial(self, *mocks: Mock) -> None:
|
||||
|
@ -7544,6 +7578,25 @@ class TestRemoteServerBillingFlow(StripeTestCase, RemoteServerTestCase):
|
|||
self.billing_session.min_licenses_for_plan(CustomerPlan.TIER_SELF_HOSTED_BASIC), 1
|
||||
)
|
||||
|
||||
@responses.activate
|
||||
@mock_stripe()
|
||||
def test_get_past_invoices_session_url_for_remote_server(self, *mocks: Mock) -> None:
|
||||
self.login("hamlet")
|
||||
hamlet = self.example_user("hamlet")
|
||||
|
||||
self.add_mock_response()
|
||||
with time_machine.travel(self.now, tick=False):
|
||||
send_server_data_to_push_bouncer(consider_usage_statistics=False)
|
||||
|
||||
self.execute_remote_billing_authentication_flow(hamlet.delivery_email, hamlet.full_name)
|
||||
self.add_card_and_upgrade()
|
||||
|
||||
response = self.client_get(
|
||||
f"{self.billing_session.billing_base_url}/invoices/", subdomain="selfhosting"
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertTrue(response["Location"].startswith("https://billing.stripe.com"))
|
||||
|
||||
@responses.activate
|
||||
@mock_stripe()
|
||||
def test_upgrade_server_to_fixed_price_monthly_basic_plan(self, *mocks: Mock) -> None:
|
||||
|
|
|
@ -30,9 +30,12 @@ from corporate.views.portico import (
|
|||
apps_view,
|
||||
communities_view,
|
||||
hello_view,
|
||||
invoices_page,
|
||||
landing_view,
|
||||
plans_view,
|
||||
remote_realm_invoices_page,
|
||||
remote_realm_plans_page,
|
||||
remote_server_invoices_page,
|
||||
remote_server_plans_page,
|
||||
team_view,
|
||||
)
|
||||
|
@ -83,6 +86,7 @@ i18n_urlpatterns: Any = [
|
|||
path("jobs/", TemplateView.as_view(template_name="corporate/jobs.html")),
|
||||
# Billing
|
||||
path("billing/", billing_page, name="billing_page"),
|
||||
path("invoices/", invoices_page, name="invoices_page"),
|
||||
path("sponsorship/", sponsorship_page, name="sponsorship_request"),
|
||||
path("upgrade/", upgrade_page, name="upgrade_page"),
|
||||
path("support/", support_request),
|
||||
|
@ -281,6 +285,16 @@ urlpatterns += [
|
|||
remote_server_event_status_page,
|
||||
name="remote_server_event_status_page",
|
||||
),
|
||||
path(
|
||||
"realm/<realm_uuid>/invoices/",
|
||||
remote_realm_invoices_page,
|
||||
name="remote_realm_invoices_page",
|
||||
),
|
||||
path(
|
||||
"server/<server_uuid>/invoices/",
|
||||
remote_server_invoices_page,
|
||||
name="remote_server_invoices_page",
|
||||
),
|
||||
# Remote variants of above API endpoints.
|
||||
path("json/realm/<realm_uuid>/billing/sponsorship", remote_realm_sponsorship),
|
||||
path("json/server/<server_uuid>/billing/sponsorship", remote_server_sponsorship),
|
||||
|
|
|
@ -14,6 +14,7 @@ from corporate.lib.decorator import (
|
|||
authenticated_remote_server_management_endpoint,
|
||||
)
|
||||
from corporate.lib.stripe import (
|
||||
RealmBillingSession,
|
||||
RemoteRealmBillingSession,
|
||||
RemoteServerBillingSession,
|
||||
get_configured_fixed_price_plan_offer,
|
||||
|
@ -21,7 +22,7 @@ from corporate.lib.stripe import (
|
|||
)
|
||||
from corporate.models import CustomerPlan, get_current_plan_by_customer, get_customer_by_realm
|
||||
from zerver.context_processors import get_realm_from_request, latest_info_context
|
||||
from zerver.decorator import add_google_analytics
|
||||
from zerver.decorator import add_google_analytics, zulip_login_required
|
||||
from zerver.lib.github import (
|
||||
InvalidPlatformError,
|
||||
get_latest_github_release_download_link_for_platform,
|
||||
|
@ -359,3 +360,32 @@ def communities_view(request: HttpRequest) -> HttpResponse:
|
|||
"org_types": org_types,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@zulip_login_required
|
||||
def invoices_page(request: HttpRequest) -> HttpResponseRedirect:
|
||||
user = request.user
|
||||
assert user.is_authenticated
|
||||
|
||||
if not user.has_billing_access:
|
||||
return HttpResponseRedirect(reverse("billing_page"))
|
||||
|
||||
billing_session = RealmBillingSession(user=user, realm=user.realm)
|
||||
list_invoices_session_url = billing_session.get_past_invoices_session_url()
|
||||
return HttpResponseRedirect(list_invoices_session_url)
|
||||
|
||||
|
||||
@authenticated_remote_realm_management_endpoint
|
||||
def remote_realm_invoices_page(
|
||||
request: HttpRequest, billing_session: RemoteRealmBillingSession
|
||||
) -> HttpResponseRedirect:
|
||||
list_invoices_session_url = billing_session.get_past_invoices_session_url()
|
||||
return HttpResponseRedirect(list_invoices_session_url)
|
||||
|
||||
|
||||
@authenticated_remote_server_management_endpoint
|
||||
def remote_server_invoices_page(
|
||||
request: HttpRequest, billing_session: RemoteServerBillingSession
|
||||
) -> HttpResponseRedirect:
|
||||
list_invoices_session_url = billing_session.get_past_invoices_session_url()
|
||||
return HttpResponseRedirect(list_invoices_session_url)
|
||||
|
|
|
@ -275,6 +275,14 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-box billing-page-field no-validation">
|
||||
<label class="inline-block label-title">Invoices</label>
|
||||
<div class="not-editable-realm-field">
|
||||
<a href="{{ billing_base_url }}/invoices/">
|
||||
View past invoices
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="planchange-error" class="alert alert-danger"></div>
|
||||
<div id="planchange-input-section">
|
||||
{% if free_trial and not downgrade_at_end_of_free_trial %}
|
||||
|
|
Loading…
Reference in New Issue