Compare commits

..

916 Commits

Author SHA1 Message Date
Tim Abbott
7b80af61e4 Update version following 8.0 release. 2023-12-15 13:14:29 -08:00
Tim Abbott
bc13f0ea37 Release Zulip Server 8.0. 2023-12-15 11:16:19 -08:00
Tim Abbott
bbd79120b5 i18n: Update translation data from Transifex. 2023-12-15 11:16:19 -08:00
Alya Abbott
910a8169bc billing: Don't ask about subscriptions when deactivating registration. 2023-12-15 14:04:20 -05:00
Karl Stolley
e44ba3b1f1 portico: Add new self-hosted plans.
Co-authored-by: Alya Abbott <alya@zulip.com>
2023-12-15 11:03:42 -08:00
Karl Stolley
92de3d11d2 portico: Add self-hosted plans comparison table.
Co-authored-by: Vlad Korobov <vlad.korobov@zulip.com>
Co-authored-by: Alya Abbott <alya@zulip.com>
2023-12-15 11:03:42 -08:00
Alya Abbott
fc9cfc03e6 help: Update Contact support documentation for new paid plans. 2023-12-15 11:03:42 -08:00
Alya Abbott
eb2882d464 help: Adjust Zulip Cloud vs. self-hosting help page. 2023-12-15 11:03:42 -08:00
Karl Stolley
d25c375be9 billing: Show self-hosted help link for licenses. 2023-12-15 11:03:42 -08:00
Alya Abbott
f65478fb1f help: New page to document self-hosted billing. 2023-12-15 11:03:42 -08:00
Karl Stolley
6d48e08ff7 portico: Update modal for canceling scheduled legacy upgrade. 2023-12-15 11:03:42 -08:00
Karl Stolley
5d7010c8a0 billing: Update copy, modal for self-hosted downgrades and trials. 2023-12-15 11:03:42 -08:00
Karl Stolley
356d775d31 billing: Clarify minimum purchase license numbers. 2023-12-15 11:03:42 -08:00
Aman Agrawal
b377673190 sponsorship: Add help link redirect for org_type dropdown.
Only visible for self hosted orgs.
2023-12-15 11:03:42 -08:00
Aman Agrawal
8fb126505c sponsorship: Show different discount text for self hosted orgs. 2023-12-15 11:03:42 -08:00
Alex Vandiver
48ee7506ba zilencer: Log the can_push value that we returned. 2023-12-15 11:03:42 -08:00
Sahil Batra
03323b0124 push_notifications: Enforce max user count on self managed plan.
We do not support sending push notifications for realms having
more than 10 users on self managed plan.
2023-12-15 11:03:42 -08:00
Tim Abbott
10862451ef billing: Refactor code to compute push status.
This moves the function which computes can_push and
expected_end_timestamp outside RemoteRealmBillingSession
because we might use this function for RemoteZulipServer
as well and also renames it.
2023-12-15 11:03:42 -08:00
Tim Abbott
9423ccecd4 docs: Document plan management login tips. 2023-12-15 11:03:42 -08:00
Tim Abbott
97799b279b billing: Enable billing system in production. 2023-12-15 11:03:42 -08:00
Aman Agrawal
3aed22dcd0 gear_menu_popover: Enable Plan management in production. 2023-12-15 11:03:42 -08:00
Alya Abbott
2090a10aad portico: Remove development environment testing plan. 2023-12-15 11:03:42 -08:00
Tim Abbott
ace8344b5a docs: Document manual update_analytics_counts. 2023-12-15 10:13:35 -08:00
Mateusz Mandera
fb5137f8b5 zilencer: Handle deleted realms nicely at server/analytics. 2023-12-15 09:18:26 -08:00
Alex Vandiver
8102519242 send_email: Remove List-Unsubscribe-Post from remote-server emails.
For remote servers, we cannot advertise `List-Unsubscribe=One-Click`,
which is specified in RFC 8058[^1] to mean that the `List-Unsubscribe`
URL supports a POST request with no arguments to unsubscribe.  Because
we show an interstitial and confirmation page, as this is not just a
mailing list which is disabled if you click the link, it does not
support the mail system performing the unsubscribe for the user.

Remove the inaccurate header for remote servers.

[^1]: https://datatracker.ietf.org/doc/html/rfc8058
2023-12-15 09:12:25 -08:00
Aman Agrawal
0fe725fdbd test_stripe: Add tests to check sponsorship approval. 2023-12-15 09:08:48 -08:00
Aman Agrawal
4b5e8971a3 sponsorship_approval: Fix punctuation. 2023-12-15 09:08:48 -08:00
Prakhar Pratyush
1588f49b4f test_stripe: Add end-to-end test for RemoteRealm billing flow. 2023-12-15 09:08:48 -08:00
Prakhar Pratyush
33e04362e1 test_stripe: Add 'billing_session" to StripeTest.
This prep commit adds a 'billing_session' field to StripeTest class.

Creates 'client_billing_get', 'client_billing_post', and
'client_billing_patch' helper functions.

This will help in reusing code for RemoteRealm and
RemoteZulipServer end-to-end tests.
2023-12-15 09:08:48 -08:00
Aman Agrawal
53b7e956ea test_remote_billing: Extract remote server / realm login methods. 2023-12-15 09:08:48 -08:00
evykassirer
1f7b3d7a2d stream edit toggler: Update select_tab when changing hash."
This fixes a bug in #26717 where the hash would flash to #personal
before #subscribers. Sometimes `setup_toggler` is called after
the hash change, and it needs to know which tab to show.

Ideally in the future we'd clean up this code so that the hash
changing function isn't called multiple times.
2023-12-15 08:47:45 -08:00
Alex Vandiver
0f2cc383f6 send_email: Support add_context for remote servers.
612f2c73d6 started passing add_context to
`send_custom_server_email`, but did not make it make use of it.

Also add the `hostname` as a built-in value, since that is most likely
the most useful property.
2023-12-15 08:37:02 -08:00
Tim Abbott
63e5712837 push_notifications: Improve logging when not contacting bouncer. 2023-12-15 08:15:36 -08:00
Aman Agrawal
f5aa88e165 billing: Don't show current license for legacy plan customers. 2023-12-15 08:10:16 -08:00
Prakhar Pratyush
c12a9eeb9c stripe: Fix 'get_billable_licenses_for_customer' not using event_time.
The call to 'get_billable_licenses_for_customer' during the
'sync_license_ledger_if_needed' step should use the audit_log's
event_time while calculating 'current_count_for_billed_licenses'.

Earlier, it used timezone_now(), resulting in the latest user count
recorded corresponding to each audit log.
2023-12-15 08:09:31 -08:00
Mateusz Mandera
d351353126 zilencer: Fix server/register failing to check if server is deactivated.
A deactivated server shouldn't be updated like that - we want to return
an error.
2023-12-15 08:06:58 -08:00
Tim Abbott
53f40133eb remote_server: Handle JsonableErrors and connection failures better.
This fixes the exception case on the initial
`/api/v1/remotes/server/analytics/status` case.  Other exceptions from
`send_to_push_bouncer` are allowed to escape.

Co-authored-by: Alex Vandiver <alexmv@zulip.com>
2023-12-14 15:08:52 -08:00
Anders Kaseorg
b472d769d1 change_user_role: List role choices in --help output.
Fixes #28227.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-14 15:06:18 -08:00
Evy Kassirer
6e902defba node tests: Use noop helper function pattern in test files.
Some files already were using `noop` in place of `() => {}`.
It's both clearer what it means and is easier to type.
This updates all test files to fully use `noop`, and
adds a shared import from the test lib file.
2023-12-14 14:51:33 -08:00
Aman Agrawal
11dff6b4d8 zilencer: Add tests for migrating customer from server to realm. 2023-12-14 14:31:28 -08:00
Aman Agrawal
b4e92ecc48 zilencer: Don't consider deactivated realms to transfer server plan. 2023-12-14 14:31:28 -08:00
Alya Abbott
ba80084ea7 remote_billing_page: Deny login for server / remote realm.
If server has plan, deny login for realm.
If realm has plan, deny login for server.

Co-authored-by: Aman Agrawal <amanagr@zulip.com>
Co-authored-by: Alya Abbott <alya@zulip.com>
2023-12-14 14:25:12 -08:00
Tim Abbott
8965f011fb i18n: Update translation data from Transifex. 2023-12-14 13:22:21 -08:00
Sahil Batra
94f0837b9c settings: Restrict length of realm Jitsi url custom input.
We have a restriction of 200 characters for jitsi_server_url
realm setting custom input element.

Fixes part of #27355.
2023-12-14 12:11:59 -08:00
Sahil Batra
633ec698f5 realm: Enfore length restriction on jitsi_server_url at API level.
Previously, passing a url longer than 200 characters for
jitsi_server_url caused a low-level failure at DB level. This
commit adds this restriction at API level.

Fixes part of #27355.
2023-12-14 12:11:59 -08:00
Alex Vandiver
bedb68b2fe registration: Prevent injecting arbitrary strings via query param.
While the query parameter is properly excaped when inlined into the
template (and thus is not an XSS), it can still produce content which
misleads the user via carefully-crafted query parameter.

Validate that the parameter looks like an email address.

Thanks to jinjo2 for reporting this, via HackerOne.
2023-12-14 12:00:16 -08:00
Alex Vandiver
9067220af6 signup: Send status code 400 on invalid emails. 2023-12-14 12:00:16 -08:00
Lauryn Menard
83d6481f6f support: Add the remote realm's plan type to the support view.
The plan type for the remote realm is useful to compare to any
current plan attached to the same remote realm.
2023-12-14 11:38:02 -08:00
Alya Abbott
b8d05aea1b portico: Update count of Tim's daughters on /team page. 2023-12-14 11:33:57 -08:00
Tim Abbott
1757b88760 billing: Offer release announcement subscriptions.
Also avoid prompting for full name time more than once.
Adds TOS version field to Remote server user.

Co-authored-by: Karl Stolley <karl@zulip.com>
Co-authored-by: Aman Agrawal <amanagr@zulip.com>
2023-12-14 10:51:16 -08:00
Alex Vandiver
4ef93de128 zilencer: Set the remote server as the user in Sentry. 2023-12-14 09:46:21 -08:00
Alex Vandiver
863c1c28f7 zilencer: Log why the server got a 401. 2023-12-14 09:46:21 -08:00
Tim Abbott
036b1156f2 zilencer: Avoid using stale last_audit_log_update. 2023-12-14 09:26:45 -08:00
Aman Agrawal
e02e0cd37e models: Make it easier to debug current plan name and status.
`print(plan)` now works to know the plan tier and status.
2023-12-14 08:22:25 -08:00
Aman Agrawal
d795400b21 billing: Allow upgrade scheduled legacy orgs to view billing page.
Don't redirect them to sponsorship page.
2023-12-14 08:22:25 -08:00
Aman Agrawal
34a1be80a4 model: Use same name for legacy plan across the app. 2023-12-14 08:22:25 -08:00
Anders Kaseorg
c7245d5f3b models: Make ScheduledMessage.read_by_sender non-nullable.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-14 08:16:31 -08:00
Anders Kaseorg
77a6f44455 message_send: Add read_by_sender API parameter.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-14 08:16:31 -08:00
Anders Kaseorg
d7d5b6c73e models: Move Message.sent_by_human to Client.default_read_by_sender.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-14 08:16:31 -08:00
Anders Kaseorg
d893ff5ba8 digest: Exclude bots with sender.is_bot, not sent_by_human.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-14 08:16:31 -08:00
Sahil Batra
92c1dfc248 message_send: Optimize checking access to DM recipients.
We previously used get_accessible_user_ids to check whether the
sender can access all DM recipients, which was not efficient as
it queries the Message table. This commit updates the code to
make sure we use get_inaccessible_user_ids which is much more
efficient as it limits the queries to only DM recipients and
also queries the Message table only if needed.

This can still be optimized further as mentioned in #27835 but
this commit is a nice first step.
2023-12-14 08:14:09 -08:00
Lauryn Menard
8bce83709f corporate: Add billing support email constant.
Adds BILLING_SUPPORT_EMAIL to `stripe.py` with a value of
"sales@zulip.com" so that it can be consistently used in
billing code.
2023-12-14 08:12:01 -08:00
Mateusz Mandera
651590c49a remote_billing: Store acting users in remote user audit logs. 2023-12-14 08:11:04 -08:00
Anders Kaseorg
a13e42f18a docs: Add missing spaces around code spans.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-14 00:05:44 -08:00
Aman Agrawal
10ef308848 migrate_server_to_realm: Reset server tier during migration. 2023-12-13 22:29:30 -08:00
Aman Agrawal
a5311c8032 stripe: End legacy plan before approving full sponsorship. 2023-12-13 22:29:30 -08:00
Alex Vandiver
612f2c73d6 send_custom_email: Support JSON extra context with remote servers. 2023-12-13 22:28:20 -08:00
Alex Vandiver
2863b5954e send_custom_email: Make --json an additional filter, not a target. 2023-12-13 22:28:20 -08:00
Alex Vandiver
2de1a23721 send_custom_email: Provide an unsubscribe link for remote servers. 2023-12-13 22:28:20 -08:00
Alex Vandiver
791d66fe28 send_custom_email: Split out the sending to remote servers. 2023-12-13 22:28:20 -08:00
Alex Vandiver
62e6b10ecd send_email: Pull admins_only back into send_custom_email filtering.
There is no reason for this to be an explicit argument to
send_custom_email, when the rest of the filtering already happens
upstream.
2023-12-13 22:28:20 -08:00
Alex Vandiver
1229e38a0d send_custom_email: Stop lying about the type of options.
Saying `**options: str` is a lie, since it contains bools.  We pluck
out the two bools that we need properly typed because we will be
pushing them into function calls, and type them explicitly as bools.
2023-12-13 22:28:20 -08:00
Tim Abbott
bfa3d144eb zilencer: Don't try to migrate ended plans. 2023-12-13 17:55:00 -08:00
Tim Abbott
b8deb44a05 zilencer: Fix mismigrated plan type assertion. 2023-12-13 17:55:00 -08:00
Tim Abbott
d040664952 billing: Set the legacy plan type when migrating. 2023-12-13 16:40:44 -08:00
Tim Abbott
6308e07e53 billing: Standardize remote server plan type IDs.
This will likely save us at least one headache.
2023-12-13 16:40:44 -08:00
Lauryn Menard
89545891f6 support: Filter remote realm data query for customer realm None. 2023-12-13 14:36:23 -08:00
Lauryn Menard
a897d68d93 support: Add active plan information to remote server activity.
Adds three columns to the remote server activity chart and updates
the chart key for the third of those columns.

The first is the plan name. If there are multiple plans with a
status under the live threshhold, then we send "See support view".

The second is the plan status. If there are multiple plans, then
we send "Multiple plans".

The third is the estimated annual revenue for the plan. Note that
for free trials, this will be calculated as if the plan was paid
for 12 months (so a full year).

If there is no plan for the server under the live threshold or at
all then "---" is inserted into the table row. Note that 100%
sponsored servers/realms would fall into this category.
2023-12-13 13:43:00 -08:00
Lauryn Menard
484c0df076 corporate: Move renewal_amount to BillingSession framework. 2023-12-13 13:43:00 -08:00
Tim Abbott
1abe22b0a1 mailmap: Add entry for Rohan Gudimetla. 2023-12-13 13:28:19 -08:00
Sahil Batra
f0d429d0f9 remote_server: Handle data of non existent realms.
This commit adds code to make sure that the push
notification does not crash on receiving data for
a non-existent realm.
2023-12-13 13:13:12 -08:00
Tim Abbott
c43eb10224 mailmap: Add entry for Viktor Illmer. 2023-12-13 12:54:54 -08:00
Karl Stolley
09c32cbe30 Revert "portico: Disable self-hosted tab for logged-in cloud users."
This reverts commit 857630707a.
2023-12-13 12:23:02 -08:00
Lauryn Menard
68d117e60a support: Add sponsorship request information to remote support view. 2023-12-13 10:28:11 -08:00
Lauryn Menard
e8500fbdb0 support: Include sponsorship request for remote server support view.
Preparation for updating the sponsorship forms template to include
information about the latest sponsorship request if sponsorship is
pending.
2023-12-13 10:28:11 -08:00
Aman Agrawal
35b644c564 populate_billing_realms: Don't create plan for sponsored remote realm. 2023-12-13 08:13:33 -08:00
Aman Agrawal
9efb236c35 remote_billing_page: Fix redirects for self hosted login. 2023-12-13 08:13:33 -08:00
Aman Agrawal
081d94c4e8 stripe: Add user and org metadata to stripe payments. 2023-12-13 08:13:33 -08:00
Aman Agrawal
039f6af79e billing: Link plan name to /plans page. 2023-12-13 08:13:33 -08:00
Aman Agrawal
998685f2e7 sponsorship: Show sponsored plan name for sponsored orgs.
The `plan_name` is incorrect here since the customer will not
have an active plan for approved sponsorship.
2023-12-13 08:13:33 -08:00
Aman Agrawal
0c99420ceb stripe: Don't approve sponsorship for customers on paid plan.
This would avoid us accidentally approving sponsorship for customers
on paid plan.
2023-12-13 08:13:33 -08:00
Sayam Samal
c61b353b4b gear_menu_popover: Update the rocket icon used for plan and pricing.
Fixes #28163.
2023-12-13 07:59:13 -08:00
Sahil Batra
27d236fb52 popover: Fix user popover and profile for bot with inaccessible users.
We incorrectly passed bot owner ID to get_bot_owner_user instead of
the bot object.
2023-12-13 00:45:20 -08:00
Tim Abbott
95f4b31ce7 billing: Don't require data to add to legacy plan. 2023-12-13 00:44:50 -08:00
Tim Abbott
af83093a65 billing: Remove some stale TODOs. 2023-12-13 00:33:37 -08:00
Aman Agrawal
45a2f7eac1 portico: Pass requested sponsorship context to plan page. 2023-12-12 23:27:39 -08:00
Sayam Samal
7f26905fe0 compose_actions: Hide compose box when navigating to inbox/recent view.
Currently, given that the compose box is already open, it stays open
when the user navigates to the inbox/recent view, even when there is
no modifications to the message or recipients.

This commit adds conditions to close the compose box when navigating
to the inbox/recent view, except when we can reasonably infer that it
was the intended action.
2023-12-12 22:56:53 -08:00
Tim Abbott
f281199d2f billing_session: Push nocoverage comments down a layer. 2023-12-12 22:03:24 -08:00
Aman Agrawal
63f4fc51de server_deactivate: Show error message for server on active plan. 2023-12-12 21:00:42 -08:00
Aman Agrawal
c2636354a5 remote_billing_login: Show different title for deactivate login. 2023-12-12 21:00:42 -08:00
Alex Vandiver
19575b46f7 remote_activity: Hide deactivated remote servers. 2023-12-12 20:36:03 -08:00
Aman Agrawal
0d08fe90a8 remote_billing_email_confirm: Minor text changes.
* Move email early in text to reduce change of it wrapping to
  next line.

* Change 2 hours to 24 hours.
2023-12-12 20:32:42 -08:00
Karl Stolley
180dd00d5f portico: Update strings for RemoteRealm login flow. 2023-12-12 20:32:42 -08:00
Alex Vandiver
44e9171a46 register_server: POST requests take data, not params.
This currently _works_, but only because we are very forgiving about
how we parse requests.
2023-12-12 19:58:19 -08:00
Lauryn Menard
aebafcc43f corporate: Show error page if mobile push data has not been uploaded.
Adds a template that displays an error message when the billing
or upgrade context for a remote server or remote realm raises a
MissingDataError.
2023-12-12 19:36:06 -08:00
Aman Agrawal
e9ce69aaf6 populate_billing_realms: Call sessions with billing_user. 2023-12-12 17:31:41 -08:00
Aman Agrawal
da74b9ade7 billing: Apply common styles to billing pages. 2023-12-12 17:31:41 -08:00
Karl Stolley
9cc0ded092 portico: Correct layout on deactivation pages. 2023-12-12 17:31:41 -08:00
Karl Stolley
2b879e646c portico: Move billing contact to above payment method. 2023-12-12 16:24:56 -08:00
Alya Abbott
a2f72edc68 email: Update strings in remote_realm log in confirmation email. 2023-12-12 14:51:00 -08:00
Mateusz Mandera
e515574b3e remote_billing: Add endpoint and a helper to make deactivation links.
This is a general link for logging into the billing system on behalf of
a server, but it's tied to the .contact_email and takes the user
straight to the /deactivate/ page via the next_page mechanism.
2023-12-12 13:31:59 -08:00
Tim Abbott
fa9e3fb35c version: Update version after 8.0-beta2 release. 2023-12-12 12:28:02 -08:00
Tim Abbott
555cdd0a57 Release Zulip Server 8.0-beta2. 2023-12-12 12:17:56 -08:00
Prakhar Pratyush
4ad03804cf stripe: Add sync_license_ledger... for RemoteServerBillingSession.
This commit implements the 'sync_license_ledger_if_needed' method
for RemoteServerBillingSession.
2023-12-12 12:15:32 -08:00
Prakhar Pratyush
1d515a57cd stripe: Make 'sync_license_ledger_if_needed' abstract method.
This prep commit makes 'sync_license_ledger_if_needed'
function a 'BillingSession' abstract method.

We'll override the method for RemoteServerBillingSession
in the next commit.
2023-12-12 12:15:32 -08:00
Prakhar Pratyush
88fb3b735a stripe: Add 'get_last_ledger_for_automanaged_plan_if_exists' method.
This prep commit extracts out the code block that determines the
last license ledger for the customer plan having automanage_licenses
set to True into a new BillingSession method named
'get_last_ledger_for_automanaged_plan_if_exists'.

We'll be using this function while implementing the
'sync_license_ledger_if_needed' method for RemoteServerBillingSession.
2023-12-12 12:15:31 -08:00
Prakhar Pratyush
88fe0a7561 zilencer: Update last_audit_log_update before sync_license_ledger...
We need to update 'last_audit_log_update' before calling the
'sync_license_ledger_if_needed' method to avoid 'MissingDataError'
due to 'has_stale_audit_log' being True.

Also, we made the code block that creates audit logs,
updates 'last_audit_log_update', and syncs LicenseLedger in
an atomic operation.

This helps to rely on 'last_audit_log_update' to assume
'RemoteRealmAuditLog' and 'LicenseLedger' are up-to-date.
2023-12-12 12:15:31 -08:00
Anders Kaseorg
cae11a60b6 help: Fix “community norms” link.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-12 12:14:21 -08:00
Anders Kaseorg
cd65849f7e tests: Fix “inaccesible” typo.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-12 12:13:51 -08:00
Alex Vandiver
6d7037e867 analytics: Show latest number in "Analytics users", not max. 2023-12-12 12:09:51 -08:00
Aman Agrawal
fba49ca33d stripe: Implement various TODOs. 2023-12-12 11:45:14 -08:00
Aman Agrawal
2e8aba2e83 billing: Show special success msg for legacy upgrade scheduled server. 2023-12-12 11:45:14 -08:00
Aman Agrawal
ccaef86e20 stripe: Fix manual licenses not being propagated to next plan.
We already calculate the correct `billed_licenses` early in the
function, so just used that to fix the bug where a legacy server
scheduled for upgrade doesn't respect the manual license count
set by the user.
2023-12-12 11:45:14 -08:00
Aman Agrawal
7587ee2289 Revert "stripe: Remove non-relevant context override."
This reverts commit 4bf018f09f.

While `payment_method` is attached to customer, the type of
payment method used is attached to plan.
2023-12-12 11:45:14 -08:00
Tim Abbott
bd539d7d12 docs: Update changelog with latest changes. 2023-12-12 11:38:19 -08:00
Anders Kaseorg
ed02b568e5 personal_menu: Handle undefined user_time.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-12 11:37:14 -08:00
Karl Stolley
857630707a portico: Disable self-hosted tab for logged-in cloud users. 2023-12-12 11:30:57 -08:00
Tim Abbott
b0b6716892 i18n: Update translation data from Transifex. 2023-12-12 10:37:31 -08:00
Alex Vandiver
7e1f212366 tornado: Handle the handler having been cleared by connection close.
As premonitioned in c741c527d7, it is
indeed possible for `get_handler_by_id` to error out by cause the
handler has been unset elsewhere.

Protect the callsites of `get_handler_by_id` to be able to gracefully
handle when the handler has already done away.
2023-12-12 10:29:37 -08:00
Aman Agrawal
20a1037b92 stripe: Fix incorrect licenses at next renewal for legacy customers.
When a legacy plan is scheduled for an upgrade, licenses at
next renewal should reflect the licenses according to the next plan.
2023-12-12 09:30:17 -08:00
Aman Agrawal
4bf018f09f stripe: Remove non-relevant context override.
`payment_method` is attached to customer so we don't need to
override it here.
2023-12-12 09:30:17 -08:00
Aman Agrawal
689aa12fce stripe: Enforce min license for plan throughout.
I missed a some places to check on last pass:
* For automanaged licenses when the license updates.
* When plan is changed.
* When migrating existing customers to legacy plan.
2023-12-12 09:30:17 -08:00
Aman Agrawal
89a28f6577 stripe: Allow customers without a plan to upgrade.
I am not sure why I added this silly assert statement. Probably
related to legacy customers always having a plan.
2023-12-12 09:30:17 -08:00
Aman Agrawal
fd7b61065d gear_menu_popover: Remove development scaffolding links. 2023-12-12 09:30:17 -08:00
Aman Agrawal
c61275cab2 upgrade: Show sponsorship requested banner like billing page. 2023-12-12 09:30:17 -08:00
Aman Agrawal
e829e52c56 stripe: Allow sponsorship pending user to upgrade to business plan. 2023-12-12 09:30:17 -08:00
Tim Abbott
f60312b2da realm_settings: Clear sessions outside transaction.
This fixes a bug introduced in
6f93ab72c0 where deactivating a realm
would fail with an exception that sessions cannot be cleared inside
database transactions.
2023-12-12 09:20:47 -08:00
Alex Vandiver
2b37a35f71 queue: Only NAK the events if the channel is still open.
If the exception was because the channel closed, attempting to NAK the
events will just raise another error, and is pointless, as the server
already marked the pending events as NAK'd.
2023-12-12 09:20:29 -08:00
Anders Kaseorg
55b26da82b run-dev: Rewrite development proxy with aiohttp.
This allows request cancellation to be propagated to the server.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-12 08:07:41 -08:00
Mateusz Mandera
c1988a14a7 zilencer: Return can_push info at the push/notify endpoint.
This provides the remote server this information to refresh it on its
Realm attributes whenever it sends a push notification.

Fixes #27483.
2023-12-12 08:06:12 -08:00
Mateusz Mandera
b09f3a2da1 do_set_realm_property: Noop if value isn't really changing.
It makes no sense to do operations if the value isn't changing. In
particular, this creates RealmAuditLog entries and sends useless events.
2023-12-12 08:06:12 -08:00
Aman Agrawal
cf68f8ae24 legacy_server_logic: Rename id and key variable names.
Rename server_org_id to zulip_org_id.
Rename server_org_secret/server_org_key to zulip_org_key.
2023-12-12 08:05:45 -08:00
Aman Agrawal
ab1a8a0151 legacy_server_login: Do better error handling.
Show better error messages and validate zulip_org_id in JS before
sending it to the server and give an appropriate error message.
2023-12-12 08:05:45 -08:00
Lauryn Menard
449714245e support: Exclude system bot realm from remote realm query. 2023-12-12 08:04:02 -08:00
Lauryn Menard
89c2740342 support: Fix references to remote server in remote_realm_details.html. 2023-12-12 08:04:02 -08:00
Alya Abbott
59dc5d1f53 help: Document setting to automatically follow topics when mentioned. 2023-12-11 23:31:54 -08:00
Vector73
2e71ec78e3 settings: Add "Automatically follow topics where I'm mentioned" setting.
Fixes: #26795
2023-12-11 23:26:11 -08:00
Alya Abbott
c7c0b871c5 help: Document limited guest user API access to presence updates.
Fixes #28131.
2023-12-11 22:59:24 -08:00
Tim Abbott
9d0375cac1 topic_generator: Fix navigating to next normal topic in muted streams. 2023-12-11 22:32:56 -08:00
Prakhar Pratyush
82191872a4 hotkey: Fix go to unread unmuted or followed topic in muted stream.
The N shortcut was not working to go to a unread unmuted or
followed topic in a muted stream.

This commit fixes the incorrect behavior.
2023-12-11 22:32:56 -08:00
Prakhar Pratyush
4df1692294 hotkey: Fix go to unread followed topic in muted stream.
The Shift+N shortcut was not working to go to unread
followed topic in muted stream.

This commit fixes the incorrect behavior.
2023-12-11 22:32:56 -08:00
Alya Abbott
5e1d94f996 help: Tweak user group documentation. 2023-12-11 22:15:48 -08:00
David Rosa
d0317a1792 help: Update "User groups".
- Documents user groups new UI.
- Splits documentation into "User groups" and "Manage User groups".

Fixes #18583.
2023-12-11 22:15:48 -08:00
Alex Vandiver
c741c527d7 tornado: Support clearing a handler more than once.
4af00f61a8 claimed that `on_finish` and
`on_connection_close` were mutually exclusive.  In cases where a
`DELETE` is called on the queue while a longpoll is in progress, this
can cause _both_ to happen:

- The `DELETE` pushes a `cleanup_queue` event, which triggers
`finish_handler` to begin pushing out an empty event response to the
longpoll connection.

- In the midst of that, in an `await`, the longpoll connection drops,
and `on_connection_close` clears the handler.

- The `await` resumes, calls `finish`, and attempts to clear the
handler.

The easiest solution is to make `clear_handler_by_id` tolerant to
multiple attempts to clear it.  Since these processes run in parallel,
it means that parts may have a `handler_id` but `get_handler_by_id`
may error in attempting to look it up.  We have not observed this in
testing, and I cannot currently prove it is impossible.
2023-12-11 21:05:50 -08:00
Aman Agrawal
2da1bfcbd0 remote_billing: Fix strings and styling for remote login pages. 2023-12-11 20:16:37 -08:00
Mateusz Mandera
3bcfb9c005 push_notifs: Order device args to send_notifications_to_bouncer by id.
This ensures determinism in these tests doing mock_send.assert_called
with - avoids producing test flakes due to a different order of
retrieval of these objects from the database.
2023-12-11 18:36:23 -08:00
Mateusz Mandera
2916a601b3 push_notifs: Don't send request to bouncer if no devices found. 2023-12-11 18:36:23 -08:00
Mateusz Mandera
b82ea179ac zilencer: Have push/notify endpoint return registrations to delete.
- The server sends the list of registrations it believes to have with
  the bouncer.
- The bouncer includes in the response the registrations that it doesn't
  actually have and therefore the server should delete.
2023-12-11 18:36:23 -08:00
Prakhar Pratyush
dd8a33f03e import_realm: Create audit log with user count data.
This commit creates a RealmAuditlog entry with a new event_type
'RealmAuditLog.REALM_IMPORTED' after the realm is reactivated.

It contains user count data (using realm_user_count_by_role)
stored in extra_data.

This helps to have an accurate user count data for the billing
system if someone tries to signup just after doing an import.
2023-12-11 15:03:24 -08:00
Alex Vandiver
4af00f61a8 tornado: Explicitly remove handler when clients disconnect.
This partially reverts 579bdc18f85ea8599c8cf1f53ddb02fd41d97993; it
assumed (based on its documentation) that `on_finish` was called for
all requests, even client-terminated ones.  This is not accurate; it
is only called when the request calls `finish`, which only happens for
successful requests.  This caused every client-closed connection to
leak a handler (ironically, exactly re-introducing the bug previously
fixed in 12a5a3a6e1).

This behaviour was obscured by the development environment's proxy;
see comment added in the previous commit.

Instead of replacing the `clear_handler_by_id` call into
`ClientDescriptor.disconnect_handler`, we instead place it on
`AsyncDjangoHandler.on_connection_close`.  This is more correct for
a few reasons:

- `on_connection_close` will be called if the client goes away during
a request without a client descriptor.  If the handler garbage
collection of handlers runs inside the ClientDescriptor, we leak
handlers.

- `disconnect_handler` also runs when successfully sending an event,
which already calls `on_finish`.  We avoid double-calling
`clear_handler_by_id` by doing it in two clearly exclusive cases,
`on_finish` and `on_connection_close`.

- It combines the creation and garbage collection logic into one
file, decreasing action at a distance which causes memory leaks.
2023-12-11 14:10:39 -08:00
Alex Vandiver
b032b2a4da tornado: Replace a TODO comment with an explanation. 2023-12-11 14:10:39 -08:00
Prakhar Pratyush
886a86866d register_server: Send server data just after registering server.
We call 'send_server_data_to_push_bouncer' just after registering
server for push notification.

This helps to have a current state of the user counts when first
logging in after the RemoteRealm flow.
2023-12-11 14:07:39 -08:00
Prakhar Pratyush
c1daabd3c0 remote_server: Rename to 'send_server_data_to_push_bouncer'.
This commit renames 'send_analytics_to_push_bouncer'
to 'send_server_data_to_push_bouncer'.
2023-12-11 14:07:39 -08:00
Prakhar Pratyush
41ceae9c31 remote_server: Immediately send analytics on user count change.
Actions that change the number of user counts adds a deferred_work
queue processor job immediately update the billing service about your
change.

This helps to avoid having users see stale state for how many
users they have when trying to pay.
2023-12-11 14:07:39 -08:00
Tim Abbott
5c1a5a816f remote_server: Rename register_realm_with_push_bouncer.
We plan to have this potentially happen more than once for a given
realm.
2023-12-11 14:07:39 -08:00
Tim Abbott
4fe02be825 remote_server: Rename maybe_enqueue_audit_log_upload.
This is a rename of the previous
enqueue_register_realm_with_push_bouncer_if_needed but is clearer
about the fact that this will also upload audit logs if available.
2023-12-11 14:07:39 -08:00
Tim Abbott
b9af6c7962 remote_server: Queue deferred work after commit. 2023-12-11 14:07:39 -08:00
Prakhar Pratyush
d763fae9d0 remote_server: Eliminate separate realms-only code path.
Given that most of the use cases for realms-only code path would
really like to upload audit logs too, and the others would likely
produce a better user experience if they upoaded audit logs, we
should just have a single main code path here i.e.
'send_analytics_to_push_bouncer'.

We still only upload usage statistics according to documented
option, and only from the analytics cron job.

The error handling takes place in 'send_analytics_to_push_bouncer'
itself.
2023-12-11 14:07:39 -08:00
Tim Abbott
6f93ab72c0 realm: Deactivate realms inside a transaction.
This is the only operating editing audit logs not already using a
transaction, and having it do so will simplify an upcoming interface
to be able to assume it is always inside a transaction.
2023-12-11 14:07:39 -08:00
Tim Abbott
629492e63b realm: Use delete_realm_user_sessions bulk query.
The previous logic was accidentally quadratic in a horrible way
involving querying all sessions on the server in a loop.
2023-12-11 14:07:39 -08:00
Tim Abbott
1100d3be51 sessions: Make delete_realm_user_sessions not quadratic.
This is exactly the scenario that Set objects exist for.
2023-12-11 14:07:39 -08:00
Prakhar Pratyush
688c011a80 test_realm: Remove invalid dummy-uuid used.
Earlier, it was passing tests because the deffered_work queue
that calls send_realms_only_to_push_bouncer didn't update the
realms propery based on response received from bouncer.

This prep commit removes the invalid "dummy-uuid" used, as any
call to send_realms_only_to_push_bouncer will update realms
properties too.

We return an empty realms array as the realm is created midway in
do_create_realm, so the uuid is not already available. Also, our
intent here is not to verify the behaviour of the
send_realms_only_to_push_bouncer function because we'll have
separate tests for that. Here, we verify that deffered_work event
was sent and eventually it made call to send_to_push_bouncer
with appropriate data.
2023-12-11 14:07:39 -08:00
Aman Agrawal
eb92b31e1d stripe: Disable free trial for self hosted customers. 2023-12-11 13:23:49 -08:00
Aman Agrawal
23d712391e post_analytics: Migrate plan from server to realm after upgrade. 2023-12-11 13:23:49 -08:00
Aman Agrawal
64517a7ad3 stripe: Move migrate_customer_to_legacy_plan to BillingSession.
This will he helpful to call it for both remote realm and remote
server billing sessions.
2023-12-11 13:23:49 -08:00
Aman Agrawal
b4e4ca14d5 models: Store is_system_bot_realm information for RemoteRealm.
This will help us filter out system bot realm and control
feature access to it.
2023-12-11 13:23:49 -08:00
Aman Agrawal
8e617f5df8 stripe: Fix customer charged for upgrade during free trial.
I accidentally free trials for both cloud and self hosted
enabled while testing, hence didn't catch it.

This mostly involves fixing `is_free_trial_offer_enabled` to
return the correct value and providing it the correct input.
2023-12-11 13:23:49 -08:00
Aman Agrawal
df23701475 stripe: Rename add_server_to_legacy_plan.
This function will not also be used to migrate remote realms
to legacy plan.
2023-12-11 13:23:49 -08:00
Aman Agrawal
a8fd350630 stripe: Use session specific method to create customer. 2023-12-11 13:23:49 -08:00
Sayam Samal
e3878cf64a popovers: Add structural changes and fix CSS overlap.
The paragraph tag was being used along with the bold tag to style the
topic and stream in the popovers. The semantic meaning of these tags
are more specialized and should not be used in this context. Replaced
the <p> tags with the more general <div> tags and accounted for the
bold text via the `font-weight` CSS property.

The `.topic-name` class, used to style the topic/stream name in the
topic list in the left sidebar, was also being used to the style the
topic/stream name in the topic/stream popover. This lead to irrelevant
properties being applied to the popover > topic/stream name. Through
this commit, the styling for both of these different cases are now
separated using the appropriate selectors.

Fixes #27562.
2023-12-11 12:55:25 -08:00
Sayam Samal
be31f7a5e6 streams_popover: Change stream name alignment from center to left.
Introduced `display: flex` to help with the align of the stream privacy
icon with the stream name text.
2023-12-11 12:55:25 -08:00
Sayam Samal
10aad0a9f3 streams_popover: Allow soft wrap opportunities for stream name.
When a stream name becomes very long, it may stretch the popover menu
by a lot. We prevent this by allowing the stream name to wrap onto
multiple lines.
2023-12-11 12:55:25 -08:00
Sayam Samal
a2733c7bc3 streams_popover: Set popover width equal to width of longest menu item.
We set the streams popover width to be equal to the width of the longest
menu item, via the min-content intrinsic sizing. We also set all the
menu items to `white-space: nowrap` to prevent them wrapping. These
two changes allow the menu items to dictate the width of the popover
instead of a static max-width property, and thus helps with languages
where the length of the translated item may be long enough to wrap.
2023-12-11 12:55:25 -08:00
Sayam Samal
27ca8b0360 topics_popover: Remove '>' icon adjacent to the topic name. 2023-12-11 12:55:25 -08:00
Sayam Samal
a9e0871f59 topics_popover: Change topic name alignment from center to left. 2023-12-11 12:55:25 -08:00
Sayam Samal
b8fee4f176 topics_popover: Allow soft wrap opportunities for topic name.
When a topic name becomes very long, it may stretch the popover menu
by a lot. We prevent this by allowing the topic name to wrap onto
multiple lines.
2023-12-11 12:55:25 -08:00
Sayam Samal
8b128f4b59 topics_popover: Set popover width equal to width of longest menu item.
We set the topics popover width to be equal to the width of the longest
menu item, via the min-content intrinsic sizing. We also set all the
menu items to `white-space: nowrap` to prevent them wrapping. These
two changes allow the menu items to dictate the width of the popover
instead of a static max-width property, and thus helps with languages
where the length of the translated item may be long enough to wrap.
2023-12-11 12:55:25 -08:00
Anders Kaseorg
069925b9a7 requirements: Remove direct requirement on Twisted.
It’s still used indirectly via Scrapy, but we haven’t used it directly
since commit 09e17fbe17 (#2002).

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-11 12:52:20 -08:00
Sayam Samal
e5f2211397 gear_menu_popover: Align org info menu items to the left. 2023-12-11 12:25:09 -08:00
Sayam Samal
1f417b6a43 gear_menu_popover: Fix font size of org plan.
As per the intended design, the font size of the org plan should
be equal to the font size of other similar links such as org version.

It should not be one size smaller than it, such as those of the
upgrade links.
2023-12-11 12:25:09 -08:00
Sayam Samal
48bbf1cc6c gear_menu_popover: Add text wrapping to long org names.
Some organizations may have long names that could blow up the gear
menu width, in those cases we want to prevent it by wrapping the
org name by explicitly setting the white-space property, and thus
enabling any soft-wrap opportunities.
2023-12-11 12:25:09 -08:00
Sayam Samal
ca9b1060b7 navbar_dropdowns: Let menu items control the width of dropdown menus.
Through this commit, we set the width of the navbar dropdowns to be
equal to the longest menu item, via the min-content intrinsic sizing.

Note, that the min-content width takes into account all soft-wrapping
opportunities, which could result in the wrapping of the menu items in
many cases. To prevent this, we use the white-space property to prevent
menu items from wrapping in any case.
2023-12-11 12:25:09 -08:00
Sayam Samal
ada73eb6e0 gear_menu_popover: Fix padding of items in org info section.
We currently don't have appropriate padding applied to the lists and
links in the org info section of the gear menu popover. This becomes
an issue when we have a long org name, org url or org version, as
text starts touching the edges of the popover.

This commit adds the missing padding to the items in the org info
section of the gear menu popover.
2023-12-11 12:25:09 -08:00
Alya Abbott
581d4ce3c5 help: Improve Contact support help page. 2023-12-11 11:23:21 -08:00
Alya Abbott
fd97d87491 help: Update development community stream descriptions. 2023-12-11 11:23:21 -08:00
Tim Abbott
81a04b5bd8 mailmap: Combine authors for Nehal Sharma. 2023-12-11 11:08:39 -08:00
Lauryn Menard
144b2a9988 support: Add forms for current plan actions to remote realms.
Pulls the forms for updating the billing collection method and
generally modifying the current plan to a separate template.

Uses the shared template to render these forms for both the
remote server and any attached remote realms.
2023-12-11 10:33:50 -08:00
Lauryn Menard
6f3e1cf653 support: Add remote realm current plan data to support view. 2023-12-11 10:33:50 -08:00
Lauryn Menard
7186f75b58 support: Add sponsorship/discount support forms to remote realms.
If there are remote realms attached to the remote server, adds
the forms to be able to update sponsorship requests, approve
sponsorship and update a discount.
2023-12-11 10:33:50 -08:00
Lauryn Menard
2684c21cca support: Create separate template for remote sponsorship/discounts.
Creates a separate template for the forms related to sponsorship
and discounts on the remote support view.
2023-12-11 10:33:50 -08:00
Lauryn Menard
c2a76ccb3e support: Clean up template variables for remote server support. 2023-12-11 10:33:50 -08:00
Tim Abbott
f1ffb1f56e emails: Extend expiration for login confirmation links. 2023-12-11 10:16:40 -08:00
Alya Abbott
eb57b4c4f7 emails: Adjust wording in remote realm login link email. 2023-12-11 10:16:40 -08:00
Alya Abbott
9e90afc504 emails: Adjust wording in legacy server login link email. 2023-12-11 10:16:40 -08:00
Alya Abbott
da5e1146f5 help: Add a help page on plans for self-hosted servers. 2023-12-11 10:16:40 -08:00
Mateusz Mandera
c800951966 remote_billing: Add some useful fields to Remote...User models.
These are useful for auditing and follow what we have for UserProfile.
And is_active will be used in the future when we add user deactivation.
2023-12-11 09:39:24 -08:00
Karl Stolley
9c66b4a660 portico: Make Cloud tab inactive for self-hosters.
Copy here also points to the Cloud plans as described on zulip.com.
2023-12-11 09:03:00 -08:00
Karl Stolley
851f69c3ba portico: Point Zulip logo to zulip.com for self-hosters. 2023-12-11 09:03:00 -08:00
Karl Stolley
c9d2ac1e1b portico: Don't show Find accounts or New org for self-hosters. 2023-12-11 09:03:00 -08:00
Sahil Batra
d30b00d9f2 message_edit: Fix alignment of loading spinner.
This commit updates the CSS to fix the alignment of loading
spinner in "Save" button and also to make sure that button's
width does not change after clicking on the button.
2023-12-11 08:30:12 -08:00
Sahil Batra
5792895c10 message_edit: Always show white spinner on "Save" button.
We should show "white" spinner on "Save" button in message edit
form irrespective of the theme, as it looks better with the
button having blue background.
2023-12-11 08:30:12 -08:00
Sahil Batra
d6e30ee3e8 settings: Add help link for "can_access_all_users_group" setting. 2023-12-11 08:29:14 -08:00
Alya Abbott
e525160021 help: Document new UI for testing mobile push notifications.
Co-authored-by: Chris Bobbe <cbobbe@zulip.com>
2023-12-10 23:42:27 -08:00
N-Shar-ma
d365c8739d compose: Use new custom icon for the compose edit control button. 2023-12-10 22:30:03 -08:00
Aman Agrawal
ac8d5a5f0b remote_billing_page: Show error page for registration mismatch.
When a self-hosted Zulip server does a data export and then import
process into a different hosting environment (i.e. not sharing the
RemoteZulipServer with the original, we'll have various things that
fail where we look up the RemoteRealm by UUID and find it but the
RemoteZulipServer it is associated with is the wrong one.

Right now, we ask user to contact support via an error page but
might develop UI to help user do the migration directly.
2023-12-10 19:33:48 -08:00
Alya Abbott
82db8e7ac5 help: Document more clearly how guests experience unknown users. 2023-12-10 19:32:53 -08:00
Alya Abbott
0abcfb7b72 help: Update for Zulip Cloud Plus upgrades being self-serve. 2023-12-10 19:32:53 -08:00
Alya Abbott
93f53fa7fa help: Document new limited guests feature. 2023-12-10 19:32:53 -08:00
Alya Abbott
fa6b1764cd help: Switch to a common Zulip Cloud Plus banner. 2023-12-10 19:32:53 -08:00
Tim Abbott
3dc886a1cd settings: Simplify testing push bouncer.
We already override PUSH_NOTIFICATION_BOUNCER_URL in
test_extra_settings.py, so making this change should have as its only
impact making it a bit easier to test the push notifications bouncer
manually in a development environment.

I submitted a related PR to the mobile app documentation for testing
the push notifications software against a production server motivated
by this.
2023-12-10 17:36:25 -08:00
Tim Abbott
f78db57c6b corporate: Remove temporary billing scaffolding. 2023-12-10 16:46:39 -08:00
Mateusz Mandera
7d62471d0b remote_realm: Plumb RemoteRealmBillingUser into BillingSession.
Adds the RemoteRealmBillingUser object to the BillingSession in the
views decorated by authenticated_remote_realm_management_endpoint.
2023-12-10 16:15:28 -08:00
Mateusz Mandera
a0ea14bdb1 remote_billing: Fold the separate .ts files into remote_billing_auth.ts.
The separation of files no longer makes any sense, with some of these
forms being used by the RemoteRealm and legacy server flows together.
And in general we don't need to scatter this stuff across files.

Also, the unifying of the class of loader on the buttons, fixes a visual
bug on the final "Confirm login" page where you would see it spinning
for half a second upon loading the page, until the .hide() code
triggered.
2023-12-10 16:15:28 -08:00
Mateusz Mandera
1e6d9d28f8 remote_billing: Fix up templates and HTML names in them.
These pages are generally used by both remoterealm and legacy server
flows, so should have general names.
2023-12-10 16:15:28 -08:00
Mateusz Mandera
423aebf98e remote_billing: Implement confirmation flow for RemoteRealm auth.
The way the flow goes now is this:
1. The user initiaties login via "Billing" in the gear menu.
2. That takes them to `/self-hosted-billing/` (possibly with a
   `next_page` param if we use that for some gear menu options).
3. The server queries the bouncer to give the user a link with a signed
   access token.
4. The user is redirected to that link (on `selfhosting.zulipchat.com`).
Now we have two cases, either the user is logging in for the first time
and already did in the past.
If this is the first time, we have:
5. The user is asked to fill in their email in a form that's shown,
   pre-filled with the value provided inside the signed access token.
   They POST this to the next endpoint.
6. The next endpoint sends a confirmation email to that address and asks
   the user to go check their email.
7. The user clicks the link in their email is taken to the
   from_confirmation endpoint.
8. Their initial RemoteBillingUser is created, a new signed link like in
   (3) is generated and they're transparently taken back to (4),
   where now that they have a RemoteBillingUser, they're handled
   just like a user who already logged in before:
If the user already logged in before, they go straight here:
9. "Confirm login" page - they're shown their information (email and
   full_name), can update
   their full name in the form if they want. They also accept ToS here
   if necessary. They POST this form back to
   the endpoint and finally have a logged in session.
10. They're redirected to billing (or `next_page`) now that they have
    access.
2023-12-10 16:15:28 -08:00
Tim Abbott
18ec4cd198 i18n: Update translation data from Transifex. 2023-12-10 15:48:05 -08:00
Tim Abbott
50b7c5ad85 test_signup: Fix sender check in test_wrong_subdomain_i18n.
We now potentially translate the sender of account security emails.
2023-12-10 15:48:05 -08:00
Mateusz Mandera
a45438619d register_server: Update all the text. 2023-12-10 15:39:10 -08:00
Sahil Batra
49470af46e setting: Allow changing "can_access_all_users_group" setting in prod.
We now allow changing "can_access_all_users_group" setting in
production.
2023-12-10 15:20:07 -08:00
Sahil Batra
b2b36ff37b settings: Update the label for can_access_all_users_group setting. 2023-12-10 15:20:07 -08:00
Sahil Batra
d1b60f322c user_groups: Enable "#groups" UI in production.
We previously restricted "#groups" UI to development server
only due to it not being completed.
2023-12-10 15:20:07 -08:00
Sahil Batra
9fd5ed27fe user_groups: Fix live-update of data when renaming groups.
The "user_group_name_dict" value for the old group name key
was not deleted and this led to a bug where the stream creation
UI was incorrectly showing about a user group already existing
with the old group name.

Fixes #28108.
2023-12-10 15:20:07 -08:00
Aman Agrawal
ce56e19d1c stripe: Separate activation of free trial for remote realm/server.
Add a separate setting to only enable free trial for remote
realm / server.
2023-12-10 15:18:01 -08:00
Aman Agrawal
ccd60bc7e2 settings: Use original FREE_TRIAL_DAYS for cloud free trials.
* Renamed FREE_TRIAL_DAYS to CLOUD_FREE_TRIAL_DAYS.
* Used `cloud_free_trial_days` to get free trial days secret.
2023-12-10 15:18:01 -08:00
Aman Agrawal
34704daee7 stripe: Use a function to get free trial days. 2023-12-10 15:18:01 -08:00
Tim Abbott
367afbb5be mailmap: Deduplicate and update a few contributors. 2023-12-09 18:01:27 -08:00
Sahil Batra
198568522a message: Do not include details of inaccessible users in message data.
This commit adds code to not include original details of senders like
name, email and avatar url in the message objects sent through events
and in the response of endpoint used to fetch messages.

This is the last major commit for the project to add support for
limiting guest access to an entire organization.

Fixes #10970.
2023-12-09 17:23:16 -08:00
Tim Abbott
22d59f1132 typing notifications: Increase various time constants.
See https://chat.zulip.org/#narrow/stream/6-frontend/topic/typing.20notifications.20efficiency/near/1664991.

As detailed in that discussion,
`TYPING_STARTED_EXPIRY_PERIOD_MILLISECONDS` and
`TYPING_STARTED_WAIT_PERIOD_MILLISECONDS` are coupled constants, and
the impact of them being large is mainly that if a user closes their
computer or loses network in the middle of typing something (not
exactly a common occasion), then the client will suggest they kept on
typing longer than they in fact did.

There's a substantial decrease in resources consumed by this feature
associated with raising `TYPING_STARTED_WAIT_PERIOD_MILLISECONDS`, so
that at least seems worth doing.

Meanwhile, because TYPING_STOPPED_WAIT_PERIOD_MILLISECONDS measures
how long we should wait before deciding to stop suggesting a user is
still typing if they were previously typing a message but paused doing
so without closing the compose box (example causes being stepping away
from the computer, tabbing to go look something up, or just thinking
for a bit).

On the one hand, even the original 5 seconds is a fairly long time to
pause to think without touching the keyboard; on the other hand,
sitting with text you've written in the compose box is likely still a
quite high intent-to-send-soon state. Increasing this to 12 seconds
seems like a reasonable balance between being too trigger-happy here
here and avoiding someone who left their computer appearing like they
are still typing for a long time afterwards.
2023-12-09 17:05:59 -08:00
Sahil Batra
72aa4b256d message: Do not allow guest to mention inaccessible users. 2023-12-09 16:59:38 -08:00
Tim Abbott
ac441b77fa users: Add get_inaccessible_user_ids helper. 2023-12-09 16:59:38 -08:00
Sahil Batra
7e12674e8c static: Make unknown user avatar work on mobile.
Clients like mobile add "-medium.png" to the url to get the
medium avatar if the avatar_url field of user object is set
to some string other than the gravatar URL.

This commit adds a symlink to offer the unknown user avatar at
unknown-user-avatar-medium.png as well so that mobile clients
can correctly render the medium avatars for inaccessible users.

Fixes #28071.
2023-12-09 16:59:38 -08:00
Anders Kaseorg
cf16ddd4d6 tornado: Fix autoreload for Tornado 6.4 changes.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-09 14:24:45 -08:00
Aman Agrawal
a1fa969693 populate_billing_realms: Add temporary fix for MissingDataError.
We need to add proper logs here, which can probably be copied from
test_current_count_for_billed_licenses.
2023-12-09 12:50:05 -08:00
Aman Agrawal
3ff14dc018 gear_menu_popover: Rename billing to plan management for remote billing. 2023-12-09 12:50:05 -08:00
Aman Agrawal
ec86114330 stripe: Fix discounts for legacy server with upgrade scheduled.
We simply apply discount to both the plans.

Since the discount is saved in `customer.default_discount` it
will applied now to any future plans as well, even if customer
downgrades and the upgrades again.
2023-12-09 12:50:05 -08:00
Aman Agrawal
121e5ad3dc stripe: Extract function to get next plan for legacy customers. 2023-12-09 12:50:05 -08:00
Aman Agrawal
e44f3d448d stripe: Use next_plan instead of new_plan for legacy upgrades. 2023-12-09 12:50:05 -08:00
Aman Agrawal
5d3eafcdd4 support: Fix error on discount applied on legacy server.
This is for legacy server **not** scheduled for an upgrade.
2023-12-09 12:50:05 -08:00
Aman Agrawal
dca7e654ca billing: Enforce min license for plan on upgrade and billing page. 2023-12-09 12:50:05 -08:00
Aman Agrawal
67d4e8456d models: Fix upgrade scheduled legacy org error on support page.
Add `SWITCH_PLAN_TIER_AT_PLAN_END`'s text so that it can render.

We still need to fix applying a discount to them.
2023-12-09 12:50:05 -08:00
Tim Abbott
01c3c4dd4b template_parser: Handle else/elif with whitespace control. 2023-12-09 12:50:05 -08:00
Anders Kaseorg
f86becfc94 remote_server: Send API feature level along with Zulip version.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-09 12:01:22 -08:00
Anders Kaseorg
0400614a48 remote_server: Serialize analytics requests with Pydantic.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-09 12:01:22 -08:00
Mateusz Mandera
abdfdeffe4 remote_billing: Implement confirmation flow for legacy servers.
For the last form (with Full Name and ToS consent field), this pretty
shamelessly re-uses and directly renders the
corporate/remote_realm_billing_finalize_login_confirmation.html
template. That's probably good in terms of re-use, but calls for a
clean-up commit that will generalize the name of this template and the
classes/ids in the HTML.
2023-12-08 23:49:10 -08:00
Mateusz Mandera
bba02044f5 confirmation: Rename create_confirmation_link realm_creation arg. 2023-12-08 23:49:10 -08:00
Tim Abbott
b4b9f29e21 docs: Tweak mobile push notifications privacy text.
The "nothing else" line is accurate at a high level but more ambigious
than I'd like for sensitive documentation -- we're not trying to make
an extreme claim that we've disabled all forms of short-term logging.
2023-12-08 23:37:21 -08:00
David Rosa
91ddcd4e1d help: Tweak stream/group settings instructions for logged in users.
Adds gear menu instructions for how to navigate to "All streams"
and "All groups".
2023-12-08 23:36:24 -08:00
David Rosa
9bb90b7358 help: Update gear menu icons.
- Documents new gear menu icons.
2023-12-08 23:36:24 -08:00
Tim Abbott
f355b76024 zilencer: Only accept SYNCED_BILLING_EVENTS.
I expect we would ignore extra events anyway, but this just makes it
easier to reason about how the system works.
2023-12-08 23:12:29 -08:00
Karl Stolley
9f98e572f1 message_list: Suppress reaction button tooltip with picker open. 2023-12-08 15:58:49 -08:00
Karl Stolley
8f74d4529e message_list: Add separate handler, longer delay on reaction button. 2023-12-08 15:58:49 -08:00
Sayam Samal
c2dec1e152 visibility_policy_popover: Set cursor to pointer for the whole option.
The cursor should be a pointer for the whole visibility policy options;
since the whole option is selectable and not just for the anchor tag
within it.
2023-12-08 13:00:12 -08:00
Prakhar Pratyush
bf4fdbff12 stripe: Update LicenseLedger for remote realms.
When a remote server uploads statistics, we update the
LicenseLedger using the audit logs uploaded.

We iterate over the RemoteRealmAuditlog data for the concerned
realm starting from the event_time of the last LicenseLedger
created for that customer and update the ledger based on each event.
2023-12-08 12:58:21 -08:00
Prakhar Pratyush
ed9b0d330d stripe: Raise 'MissingDataError' while fetching license count.
If the RemoteRealmAuditLog has stale data, it means the server
stopped or never uploaded data. We raise MissingDataError in such
cases when a user action led to calculating licenses count from
stale data.
2023-12-08 12:58:21 -08:00
Prakhar Pratyush
40621478cb zilencer: Add get_remote_realm_guest_and_non_guest_count.
We add a 'get_remote_realm_guest_and_non_guest_count'
function that queries 'RemoteRealmAuditLog' to get
the guest and non_guest count for that remote_realm.

This function is used in 'RemoteRealmBillingSession'
to calculate the current count of billed licenses.
2023-12-08 12:58:21 -08:00
Prakhar Pratyush
bd99e37910 stripe: Use get_remote_server_guest...count for billing licenses count.
Use 'get_remote_server_guest_and_non_guest_count' function
for the current count of billing licenses in
RemoteServerBillingSession.
2023-12-08 12:58:21 -08:00
Aman Agrawal
111df40e05 context_processors: Add common context to be used in corporate pages.
Context is only available for functions inside corporate app.
2023-12-08 12:26:12 -08:00
Aman Agrawal
c5bfdaaeeb push_notifications: Return after receiving an error. 2023-12-08 12:25:46 -08:00
Aman Agrawal
3d25c7372a stripe: Remove unused context parameter. 2023-12-08 11:24:15 -08:00
Aman Agrawal
12dec4234e billing: Minor adjustments across the billing system.
* Reformat "This is a legacy plan" notice on billing page.
* Add a link to the plan name on upgrade page title.
* Tweak discount style on billing page.
* Add line break to server login page title.
* Match server login page title and tab title.
2023-12-08 11:24:15 -08:00
Aman Agrawal
76d9aff5a6 sponsorship: Allow remote orgs to request a plan type.
Sponsorship and billing pages modified to reflect the correctly
requested sponsorship plan name.
Add a line break before "Contact Zulip support".
2023-12-08 11:24:15 -08:00
Aman Agrawal
c49fc8c6fe populate_billing_realms: Delete existing remote realms.
Running script multiple times created multiple `RemoteRealm`
objects resulting in processing error.
2023-12-08 11:24:15 -08:00
Tim Abbott
0f163cedbc corporate: Tighten coverage checks in decorator.py. 2023-12-08 09:48:15 -08:00
Alex Vandiver
579bdc18f8 tornado: Consistently clear handler, via on_finish.
initialize() is called on every request, and stored the
`RequestHandler` (and thus `HTTPServerRequest`) in a global shared
dict.  However, the object is only removed from that structure if the
request was successful.  This means that failed requests (such as 405
Method Not Allowed) leaked `RequestHandler`s and
`HTTPServerRequest`s.

Move the cleanup to `on_finish`, which is called at the close of all
requests, async and not, successful or not.
2023-12-08 09:23:30 -08:00
Alex Vandiver
4989221b9e nginx: Limit the methods that we proxy to Tornado.
While the Tornado server supports POST requests, those are only used
by internal endpoints.  We only support OPTIONS, GET, and DELETE
methods from clients, so filter everything else out at the nginx
level.

We set `Accepts` header on both `OPTIONS` requests and 405 responses,
and the CORS headers on `OPTIONS` requests.
2023-12-08 09:23:30 -08:00
Alex Vandiver
8eccb3af20 tornado: Stop supporting HEAD requests.
These are not part of the API, and lead to moderately confusing
behaviour -- they block (or not) requested, but of course send no
actual data in the body.
2023-12-08 09:23:30 -08:00
Alex Vandiver
875622fe57 tornado: Set an explicit SUPPORTED_METHODS. 2023-12-08 09:23:30 -08:00
Alex Vandiver
0109658238 event_queue: Switch finish_current_handler to early-abort via return. 2023-12-08 09:23:30 -08:00
Aman Agrawal
9ab5f65cc4 stripe: Handle schedule change audit log type for remote realm/server.
This seems to have been accidentally skipped when adding
audit logs for free trials when changing billing schedule.
2023-12-08 09:21:18 -08:00
Aman Agrawal
134a05ff5e billing: Hide license management fields for free trial states.
* For free trial, don't show number of licenses for current billing period.
* For free trial scheduled to downgrade, don't show number of
  licenses for next billing period.
2023-12-08 09:21:18 -08:00
Aman Agrawal
9594e635cd populate_billing_realms: Simulate 2 months free trial.
Makes the billing page for realms on free trial more realistic.
2023-12-08 09:21:18 -08:00
Sahil Batra
2639408171 scheduled_message: Fix editing msg scheduled to inaccessible stream.
We now pass empty string as stream name instead of undefined when
passing the narrow operands to make sure we show an invalid narrow
instead of error. We do same thing for drafts.

We also need to add a check to make sure narrow_state.stream_sub
is defined before accessing stream_id field in the code to filter
scheduled messages for a narrow.
2023-12-08 08:48:32 -08:00
Sahil Batra
386cef1b7e user_pill: Do not show deactivated for inaccessible users.
This commit adds code to consider inaccessible users as active
for user pills as we do not have any information about whether
they are active or not. We do not want to incorrectly show
"deactivated" for inaccessible users.
2023-12-07 19:34:07 -08:00
Sahil Batra
75ceba6600 markdown: Do not show mention pills for inaccessible users.
This commit adds code to not show mention pills for inaccessible
users while local echoing the message and in the drafts UI.
2023-12-07 19:34:07 -08:00
Sahil Batra
55fd312379 typing_events: Do not show typing notifications for inaccessible users.
This commit adds code to not show typing notifications for inaccessible
users as the user would not have any information even if we show them
as the name shown would be "Unknown user".
2023-12-07 19:34:07 -08:00
Sahil Batra
cd94837d6b drafts: Handle drafts with inaccessible users.
Inaccessible users are now shown as "Unknown user" in drafts UI.
2023-12-07 19:34:07 -08:00
Sahil Batra
4695661ebd user_group_popover: Handle unknown users in user group popovers.
Inaccessible users are shown as "Unknown user" in user group
popovers.
2023-12-07 19:34:07 -08:00
Sahil Batra
2364060a7c message_edit_history: Handle cases for messages edited by inaccessible user.
Inaccessible users are shown as "Unknown user" in message edit history
modal.
2023-12-07 19:34:07 -08:00
Sahil Batra
fef833008f server_events_dispatch: Handle "realm_user/remove" event.
This commit adds code to handle "realm_user/remove" event which
is received when a guest loses access to a user.
2023-12-07 19:34:07 -08:00
Sahil Batra
bd1d0e6b46 settings: Handle inaccessible users in muted users list.
Inaccessible users are shown as "Unknown user" in muted
users list.
2023-12-07 19:34:07 -08:00
Sahil Batra
a3495d4103 compose: Consider inaccessible users as invalid recipients.
We do not allow users to send messages to inaccessible users.
We already do not show them in DM recipient typeahead and this
commit makes sure that an appropriate error is shown if user
somehow creates the pill for inaccessible user by typing the
email.
2023-12-07 19:34:07 -08:00
Sahil Batra
57c6d0323b read_receipts: Support inaccessible users in read receipts list.
Inaccessible users are shown as "Unknown users" in read receipts
list.
2023-12-07 19:34:07 -08:00
Sahil Batra
abf7b9225f search: Do not show inaccessible users in search suggestions.
We do not show inaccessible users in search suggestions.
2023-12-07 19:34:07 -08:00
Sahil Batra
2da3bd2813 reactions: Handle reactions by inaccessible users.
Inaccessible user names are shown as "Unknown user" in the
reacted users list and tooltip.

This commit removes the call to `is_known_user_id` since we
may have reactions data from inaccessible users. And it is
fine to remove the `is_known_user_id` call anyways, because
it was added to ignore showing reactions from deactivated
users when the client did not have data about them, but now
the client has data about deactivated users.

So we anyways do not expect unknown user IDs in cases
other than inaccessible users for which we have to show
the reactions by showing their name as "Unknown user".

This commit also updates the comment for "is_known_user_id"
function accordingly.
2023-12-07 19:34:07 -08:00
Sahil Batra
ce6dc40a42 markdown: Handle mention pill behavior for inaccessible users.
We do not update the name for mention pills of inaccessible users
and keep it same as it was in the message content, and we show
the "Unknown user" popover on clicking the pill.
2023-12-07 19:34:07 -08:00
Sahil Batra
d67717c0d7 scheduled_messages: Handle scheduled messages to inaccessible users.
Inaccessible users are shown as "Unknown user" in the scheduled
messages list.
2023-12-07 19:34:07 -08:00
Sahil Batra
0fb89e41f6 compose: Show only groups with all accessible users in DM typeahead. 2023-12-07 19:34:07 -08:00
Sahil Batra
99f4f914e2 users: Handle UI for bots when bot owner is inaccessible. 2023-12-07 19:34:07 -08:00
Sahil Batra
2c331f0664 custom_profile_fields: Handle user type fields set to unknown users. 2023-12-07 19:34:07 -08:00
Sahil Batra
30910a3403 settings_emoji: Handle unknown users as emoji authors.
Inaccessible emoji authors are shown as "Unknown user" in
emoji list.
2023-12-07 19:34:07 -08:00
Sahil Batra
8376728218 popover: Update popovers shown for unknown users. 2023-12-07 19:34:07 -08:00
Sahil Batra
380ff91c0e users: Do not pass unknown users data for webapp.
This commit sets the client capability value to not pass
unknown users data in the webapp and also does some changes
to avoid errors while loading the web-app home page.

This commit only does some basic webapp changes to not show
inaccessible users in sidebar and we would need need more
changes to make the web-app work as expected which will be
done in further commits.
2023-12-07 19:34:07 -08:00
Karl Stolley
8d76231dda portico: Hide Cloud-plan buttons for self-hosters. 2023-12-07 19:19:21 -08:00
Karl Stolley
60a4300db9 portico: Set standalone 'Choose a plan' header for self-hosters. 2023-12-07 19:19:21 -08:00
Karl Stolley
e1fab4a55a portico: Hide header/footer on self-hosted /plans. 2023-12-07 19:19:21 -08:00
Alex Vandiver
ca57d360e6 puppet: Update dependencies. 2023-12-07 18:45:10 -08:00
Anders Kaseorg
396e995502 requirements: Upgrade Python requirements.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-07 14:41:01 -08:00
Anders Kaseorg
202d00ebc6 dependencies: Upgrade JavaScript dependencies.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-07 14:29:32 -08:00
Lauryn Menard
a018f2611b push-notifications: Update payload for realm name and event string.
Adds `user.realm.string_id` as the realm name to the base payload
for notifications. Uses this realm name in the body of the alert
in the `apns_data`.

Changes the event string from "test-by-device-token" to "test".

Fixes #28075.
2023-12-07 14:14:37 -08:00
Aman Agrawal
2165486c88 upgrade: Allow showing downgrade success message for legacy server. 2023-12-07 13:55:39 -08:00
Aman Agrawal
825986ac3a billing: Allow legacy servers to cancel upgrade. 2023-12-07 13:55:39 -08:00
Aman Agrawal
00b7424d11 legacy_server_login: Restyle page. 2023-12-07 13:55:39 -08:00
Aman Agrawal
21e64887f1 plans: Add current plan icon to cloud free current plan. 2023-12-07 13:55:39 -08:00
Aman Agrawal
bbacee7fe9 decorator: Pass patch parameters in request.POST.
We need to manually process the parameters from request.body
since PATCH parameters are present in body and pass it in
`request.POST` to allow PATCH requests via `update_plan_for..` to
work.
2023-12-07 13:55:39 -08:00
Tim Abbott
d29d132e8a push_notifications: Set more reasonable timeouts. 2023-12-07 13:45:57 -08:00
Tim Abbott
19ac558d5f push_notifications: Fix handling of 500s from bouncer.
The comments explain the context, but we shouldn't mark our access to
push notifications as disabled incorrectly here.
2023-12-07 13:45:57 -08:00
Lauryn Menard
c94c194ea7 corporate: Send email community plan sponsorship approved. 2023-12-07 13:17:14 -08:00
Prakhar Pratyush
25899b1f7e left_sidebar: Decrease follow-icon opacity. 2023-12-07 11:54:36 -08:00
Aman Agrawal
1987894a2d billing: Remove min input validation for license input.
Fixes #28040
2023-12-07 08:34:39 -08:00
Aman Agrawal
7cad1f80be sponsorship: Set page title based on sponsorship status.
Fixes #27999
2023-12-07 08:34:39 -08:00
Sahil Batra
3d181a8ee1 settings: Remove "User groups" panel from settings overlay.
The "User groups" panel is now removed from settings overlay
and we instead use new "#groups" UI.

This commit also makes some changes to tests to ensure coverage
for pill_typeahead.js which was previously done by
settings_user_group_legacy.test.js. We have still not got
complete coverage on user_pill.ts as we have removed
settings_user_group_legacy.test.js, but we just add the file
to EXEMPT_FILS list for now and will handle it in future.

Fixes #28012.
2023-12-07 06:35:38 -08:00
Sahil Batra
17d25284a2 help: Update documentation to use new "#groups" UI. 2023-12-07 06:35:38 -08:00
Sahil Batra
d43244313d user_groups: Fix joining user group when group row not visible.
There was a bug when a user tries to join user group if the
there was not a corresponding group row in left panel.
This happend when a user tried to open group settings page
from group popover.

This commit fixes the bug by adding code to check whether the
group row is present before calling the functions to show or
hide the loading spinners.
2023-12-07 06:35:38 -08:00
Sahil Batra
a3af0250a0 compose: Do not include deactivated users for group as DM recipient.
We do not select deactivated members of group as DM recipient when
selecting a user group as option from typeahead.
2023-12-07 06:35:38 -08:00
Sahil Batra
c4ec18bc61 popovers: Improve user group popovers.
This commit updates the user group popovers to change the icon
and text of ".manage-group" option to match what we have in
gear menu and also change the heading and ".manage-group"
option to be left-aligned.
2023-12-07 06:35:38 -08:00
Sahil Batra
a97cf5d107 user_groups: Do not show deactivated users in members list.
This commit adds code to now show deactivated users in the
members list in "#groups" UI and the user group popover.
2023-12-07 06:35:38 -08:00
Sahil Batra
381670d2b6 user_group_popover: Fix scrollbar present outside the popover.
We already had the code to override the margin but it was not
being applied due to CSS specificity. This commit fixes it.

Due to this change, there was too much space on the left of
member names, so this commit also decreases the left padding.
2023-12-07 06:35:38 -08:00
Sahil Batra
34fd68c6b2 user_group_popover: Hide "Manage group" option for guests.
We do not allow guests to access the "#groups" UI.
2023-12-07 06:35:38 -08:00
Sahil Batra
7d217f2ede group_settings: Make new "#groups" UI accessible.
We now add a "Group settings" option in the gear menu to open
the new "#groups" UI and the "Manage group" option in user
group popover also opens the new UI.
2023-12-07 06:35:38 -08:00
Prakhar Pratyush
bbfcb2dcb3 banner: Make banner about automatic follow/unmute topics one-time only.
Fixes: #27847.
2023-12-06 18:19:20 -08:00
Prakhar Pratyush
83bd9955e3 events: Add 'onboarding_steps' event deprecating 'hotspots'.
Earlier, the event sent when an onboarding step (hotspot till now)
is marked as read generated an event with type='hotspots' and
'hotspots' named array in it.

This commit renames the type to 'onboarding_steps' and the array
to 'onboarding_steps' to reflect the fact that it'll also contain
data for elements other than hotspots.
2023-12-06 18:19:20 -08:00
Prakhar Pratyush
dde3d72100 onboarding_steps: Add 'OneTimeNotice' dataclass.
This commit adds a 'OneTimeNotice' dataclass to
support one time banner and similar UI elements.
2023-12-06 18:19:20 -08:00
Prakhar Pratyush
df379b5e86 hotspots: Add 'type' field to objects in 'hotspots' array.
This commit adds a 'type' field to the objects
in 'hotspots' array sent in 'hotspots' events.

We have explicitly added this field as we eventually
plan to have two type of onboarding steps, 'hotspots'
and 'one_time_notice'.

This will help clients to easily identify them.
2023-12-06 18:19:20 -08:00
Prakhar Pratyush
ac8af3d6de urls: Add a new endpoint for hotspot and deprecate the old one.
This commit adds a new endpoint 'users/me/onboarding_steps'
deprecating the older 'users/me/hotspots' to mark hotspot as read.

We also renamed the view `mark_hotspot_as_read` to
`mark_onboarding_step_as_read`.

Reason: Our plan is to make this endpoint flexible to support
other types of UI elements not just restricted to hotspots.
2023-12-06 18:19:20 -08:00
Prakhar Pratyush
62bfc20ebc models: Rename 'UserHotspot' model to 'OnboardingStep'.
This commit renames the 'UserHotspot' model to 'OnboardingStep'.

Also, it renames the 'hotspot' field in that model
to 'onboarding_step'.
2023-12-06 18:19:20 -08:00
Prakhar Pratyush
32a5c422e9 migration: Make 'rename_indexes_constraints' a lib function.
This prep commit moves the 'rename_indexes_constraints'
function to 'lib/migrate' as we're going to re-use it for
the 'UserHotspot' to 'OnboardingStep' table rename operation.

In general, this function would be helpful in migrations
involving table rename operations, subject to the caution
mentioned in the function via comments.
2023-12-06 18:19:20 -08:00
Prakhar Pratyush
777398fc82 test_helpers: Maintain alphabetical order of models in 'use_db_models'. 2023-12-06 18:19:20 -08:00
Divyansh Seth
ec36fc5605 docs: Update devlogin screenshot and url in development setup.
This commit replaces the old dev login screenshot with the new one.
This also replaces the default path that we mention in the docs to
localhost:9991/devlogin.
2023-12-06 18:04:54 -08:00
Alya Abbott
6b64929523 help: Make minor clarifications for spoiler and quote formatting. 2023-12-06 16:17:35 -08:00
Alya Abbott
ffb9261eaf help: Fully document use of Enter for list formatting. 2023-12-06 16:17:35 -08:00
Alya Abbott
f1f41ab234 help: Advertise compose box buttons on message formatting page. 2023-12-06 16:17:35 -08:00
Alya Abbott
1062b64149 help: Explain smart formatting buttons in more detail. 2023-12-06 16:17:35 -08:00
David Rosa
6b94b1183f help: Document new formatting buttons.
Updates icons and adds instructions for formatting text using the new
formatting buttons in the compose box.

Fixes #27850.
2023-12-06 16:17:35 -08:00
Alex Vandiver
8d5573b395 narrow: Prevent contradicting DM and stream narrows.
These confused `ok_to_include_history` and caused exceptions looking
for the "flags" column.
2023-12-06 16:06:15 -08:00
Aman Agrawal
b8f364a345 test-backend: Ignore coverage for custom_dev_settings. 2023-12-06 12:04:10 -08:00
Aman Agrawal
1a60697bd5 populate_billing_realms: Use correct plan tier for sponsored realms.
In cloud:

Sponsored organizations have plan_type=STANDARD_FREE, don't have
a CustomerPlan object and thus no tier value.

With self-hosting:
Sponsored organizations have a CustomerPlan object with tier
TIER_SELF_HOSTED_COMMUNITY and a plan_type of PLAN_TYPE_COMMUNITY.
2023-12-06 12:04:10 -08:00
Aman Agrawal
9f1f292462 populate_billing_realms: Add some helpful comments for testing. 2023-12-06 12:04:10 -08:00
Aman Agrawal
a15499fb98 upgrade: Fix upgrade links. 2023-12-06 12:04:10 -08:00
Aman Agrawal
860e4f6060 stripe: Don't change plan tier before the plan becomes live.
For new plans that have not started, the tier change should only
happen when they become live.
2023-12-06 12:04:10 -08:00
Aman Agrawal
ec7fd94782 billing: Show correct info for to be upgraded legacy plan server. 2023-12-06 12:04:10 -08:00
Aman Agrawal
f5a96cba05 stripe: Don't modify non-status details of legacy-server plan.
The new plan already has the details for pricing and how to
charge customer and ideally should be used to show information
about it on billing page.
2023-12-06 12:04:10 -08:00
Aman Agrawal
30f7d5e8df stripe: Don't create LicenseLedger entries for non-live plans. 2023-12-06 12:04:10 -08:00
Aman Agrawal
fec155ea9c stripe: Extract method to get billing page context. 2023-12-06 12:04:10 -08:00
Aman Agrawal
0d3c68cefb stripe: Rearrange code. 2023-12-06 12:04:10 -08:00
Aman Agrawal
74ef619412 stripe: Active plans always have last_ledger_entry. 2023-12-06 12:04:10 -08:00
Aman Agrawal
f99214e866 stripe: Plan is always defined for get_billing_page_context calls. 2023-12-06 12:04:10 -08:00
Aman Agrawal
f381fb1afd billing_page: Check for live plan before redirecting to upgrade page.
Instead of checking for any `CustomerPlan`, we need to check if
the customer has any live plan.
2023-12-06 12:04:10 -08:00
Aman Agrawal
b8c0265d3a billing_page: Directly used billing_session method get base url. 2023-12-06 12:04:10 -08:00
Sayam Samal
027df67fdc help_link_widget: Replace blue with inherit color on hover in dark mode.
This is a follow up commit to 2f1ea2fe4e,
for dark mode. The previously mentioned commit fixed the issue of help
link widget turning to blue instead of inheriting the color in light
mode only.
2023-12-06 11:22:44 -08:00
Lauryn Menard
5bae7e5a33 emails: Add new context data to sponsorship requested templates.
Adds the new information from the sponsorship form to the email
sent to support: user counts and description of paid users.
2023-12-06 11:04:54 -08:00
Lauryn Menard
906e667a70 corporate: Implement support_url for all BillingSession child classes.
Adds a helper since there are only a few different parameters for
all BillingSession child clases, `build_support_url`.

Also, renames `get_support_url` to more explicitly note that it
is for realms: `get_realm_support_url`.
2023-12-06 11:04:54 -08:00
Lauryn Menard
46dab1beb9 emails: Replace string_id from sponsorship request email context.
Use of `string_id` in the sponsorship request email content was
removed in commit d3834f8b9, but it is still used in the email
subject.

Updates the email subject to use the billing_entity_display_name,
which is still the Realm.string_id for Zulip Cloud organizations.

Sets this string as "billing_entity" in the context and subject
template.
2023-12-06 11:04:54 -08:00
Lauryn Menard
ea725aaaf3 support: Handle missing current licenses data for support views. 2023-12-06 11:01:26 -08:00
Lauryn Menard
d079a13760 support: Use shared template for current plan details on support views.
Moves the section in support views for any current plan details
to a new template: `templates/analytics/current_plan_details.html`.

Also, updates the PlanData dataclass to have a boolean that checks
if the current plan tier is the self-hosted legacy plan.
2023-12-06 11:01:26 -08:00
Aman Agrawal
771f8a3542 populate_db: Add contact email for RemoteZulipServer.
We need it `get_email` for remote billing sessions.
2023-12-06 10:55:50 -08:00
Mateusz Mandera
24d6ae83c2 populate_db: Clean up existing RemoteRealm, RemoteZulipServer objects.
As of c9b0602320 and
8b55d60f9e we create a default
registration for the dev env "RemoteZulipServer" (and also RemoteRealms
for the dev realms) in populate_db. However these models weren't listed
in clear_database, meaning the state wasn't properly cleaned up at the
start of the command.

Most important, this would manifest in getting:
```
django.db.utils.IntegrityError: duplicate key value violates unique
constraint "zilencer_remotezulipserver_uuid_key"
DETAIL:  Key (uuid)=(......) already exists
```

if you re-run populate_db.
2023-12-06 10:55:50 -08:00
Sahil Batra
578f9fb77e typing_events: Remove unnecessary code.
There is no need to set name field for event.sender since we
do not use it anywhere.
2023-12-06 00:09:53 -08:00
Sahil Batra
6c3fbcc383 push_notifications: Do not include details of inaccessible users.
This commit adds code to include original name, email and avatar
for inaccessible users which can happen when a user sends message
to an unsubscribed stream.
2023-12-06 00:09:53 -08:00
Sahil Batra
965869d3f8 register: Add client capability to not receive unknown users data.
This commit adds a new client capability to decide whether the
client needs unknown users data or not.
2023-12-06 00:09:53 -08:00
Sahil Batra
3697df1971 realm: Allow enabling restricted user access for guests only on plus plans.
This commit adds code to not allow Zulip Cloud organizations that are not
on the Plus plan to change the "can_access_all_users_group" setting.

Fixes #27877.
2023-12-06 00:09:53 -08:00
Tim Abbott
244b150920 portico: Fix buggy plans gradient in light theme. 2023-12-06 00:02:47 -08:00
Tim Abbott
731799b726 zilencer: Fix audit log handling of missing 0s. 2023-12-06 00:00:27 -08:00
Aman Agrawal
2c7afa19ba populate_billing_realms: Add method to populate remote realms.
We also avoid deleting all RemoteZulipServer at start so that
we don't delete the registered server for remote realms.
2023-12-05 23:44:29 -08:00
Aman Agrawal
cfb62e7ffa populate_billing_realms: Extract method to create customer plan. 2023-12-05 23:44:29 -08:00
Aman Agrawal
ca3569165a billing_page: Fix redirect URLs for remote_realm billing page. 2023-12-05 23:44:29 -08:00
Aman Agrawal
34730203b3 plans: Show special text for legacy orgs scheduled for upgrade. 2023-12-05 23:44:29 -08:00
Aman Agrawal
8d9a7679bc plans: Show buttons as per current context.
Also show correct tab based on remote / cloud user.
2023-12-05 23:44:29 -08:00
Aman Agrawal
49908ba166 sponsorship: Populate sponsorship page with correct context.
Fixes sponsorship page to work for remote realm and server.
2023-12-05 23:44:29 -08:00
Aman Agrawal
044cb820f8 stripe: Fix legacy server upgrade to business plan.
I had pushed a similar change in #28017 but seems to have been lost.
2023-12-05 23:44:29 -08:00
Aman Agrawal
8165f718d6 populate_billing_realms: Add different remote servers for testing. 2023-12-05 23:44:29 -08:00
Aman Agrawal
750f5c07b7 populate_billing_realms: Extract method to add card to customer. 2023-12-05 23:44:29 -08:00
Tim Abbott
5e721f4605 migrations: Add recently added indexes concurrently. 2023-12-05 18:22:23 -08:00
Sayam Samal
bcc58edfda hotspots: Fix misplaced onboarding hotspots.
We rename "intro_gear" to "intro_personal" because after the menu
was split into help menu, main menu and personal menu, the "Settings"
option now resides inside the personal menu.

Fixes #27878.
2023-12-05 17:37:47 -08:00
Mateusz Mandera
d631c76747 zilencer: Add some indexes on Remote* models.
These are for making fix_remote_realm_foreign_keys more efficient.
2023-12-05 16:49:00 -08:00
Mateusz Mandera
39b0628f09 zilencer: Automatically migrate Remote* objects to link to RemoteRealm.
1. When we get data and it includes realm info, we should automatically
   link the new records with the appropriate RemoteRealm.
2. For old records, when we receive realm data, we have an opportunity
   to update those old record to link them to the right RemoteRealm.
   This logic doesn't need to always run, just after a remote server
   upgrade, since that's when this shift in remote server behavior will
   occur.
2023-12-05 16:49:00 -08:00
Tim Abbott
937abb8ba2 compose: Fix broken voice call button.
This CSS class was accidentally lost in
82895ff535.
2023-12-05 13:10:46 -08:00
Anders Kaseorg
223b626256 python: Use urlsplit instead of urlparse.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-05 13:03:07 -08:00
Anders Kaseorg
3853fa875a python: Consistently use from…import for urllib.parse.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-05 13:03:07 -08:00
Prakhar Pratyush
e5d71fe5ac stripe: Move update_license_ledger_if_needed to BillingSession.
This commit moves the 'update_license_ledger_if_needed' and its
helper function 'update_license_ledger_for_automanaged_plan'
to the 'BillingSession' abstract class.

This refactoring will help in minimizing duplicate code while
supporting both realm and remote_server customers.
2023-12-05 12:51:41 -08:00
Prakhar Pratyush
133291ec2d stripe: Move update_license_ledger_for_manual_plan to BillingSession.
Moves the 'update_license_ledger_for_manual_plan' function
to the 'BillingSession' abstract class.

This refactoring will help in minimizing duplicate code while
supporting both realm and remote_server customers.
2023-12-05 12:51:41 -08:00
Lauryn Menard
ba3279e959 support: Add remote realm stub to remote server information.
Adds a small section for any remote realms attached to the remote
server in the support view.
2023-12-05 12:48:42 -08:00
Lauryn Menard
cfd61669e0 support: Add plan type to remote server information.
Updates `get_plan_type_string` for RemoteZulipServer plan types and
capitalizes the strings used for Realm plan types.

Also changes the string for Realm.PLAN_TYPE_STANDARD_FREE to be
"Standard free" instead of "open source" as that is used for any
100% sponsored organization, which is not restricted to open-source
projects.
2023-12-05 12:48:42 -08:00
Lauryn Menard
6c5b419267 support: Add downgrade current plan actions to remote servers. 2023-12-05 12:48:42 -08:00
Lauryn Menard
f5ab9419d2 support: Update billing modality for remote server current plan. 2023-12-05 12:48:42 -08:00
Lauryn Menard
9f8fe7989a support: Add current plan information for remote servers.
For remote servers with a current legacy customer plan,
only the plan name, status and end date are displayed.
2023-12-05 12:48:42 -08:00
Tim Abbott
9d3ef7f589 settings: Print about custom settings from runtornado. 2023-12-05 12:33:34 -08:00
Anders Kaseorg
8a7916f21a python: Consistently use from…import for datetime.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-05 12:01:18 -08:00
Mateusz Mandera
250b52e3dc remote_billing: Add a "confirm login" page in RemoteRealm auth flow. 2023-12-05 11:34:57 -08:00
Mateusz Mandera
04bb60a05e remote_billing: Increase signed link validity to 2 hours.
This cannot be so short if we're adding an intermittent "check your
details, agree to ToS and confirm login" page. We're also considering
having users potentially share these links.
2023-12-05 11:34:57 -08:00
Mateusz Mandera
8b55d60f9e populate_db: Create RemoteZulipServer with proper details from settings.
This creates a valid registration, for two reasons:
1. Avoid the need to run "manage.py register_server" in dev env to
   register, when wanting to to test stuff with
   `PUSH_NOTIFICATION_BOUNCER_URL = "http://localhost:9991"`.
2. Avoid breaking RemoteRealm syncing, due to duplicate registrations
   (first set of registrations that gets set up with the dummy
   RemoteZulipServer in populate_db, and the second that gets set up via
   the regular syncing mechanism with the new RemoteZulipServer created
   during register_server).
2023-12-05 11:34:57 -08:00
Mateusz Mandera
c23339f295 remote_billing: Rename the _billing_entry and _finalize_login functions.
These names were picked when I still thought these endpoints would serve
both the RemoteRealm and RemoteZulipServer based flows. Now that it's
known these are RemoteRealm-only endpoints, the _server in the names no
longer makes sense.
2023-12-05 11:34:57 -08:00
Anders Kaseorg
ebee57813c populate_billing_realms: Fix PLW0127 Self-assignment of variable.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-05 11:13:49 -08:00
Karl Stolley
acbe843b09 portico: Update pricing buttons on business, self-hosting.
This also defaults to the Zulip Cloud plans tab on the business
page.
2023-12-05 11:09:41 -08:00
Karl Stolley
2ea37a54a6 portico: Update plans tab on other pages.
This adds the new tabs from /plans to:

* /for/business
* /self-hosting

And it isolates legacy styles to preserve the tabs on
/for/education

Fixes: #28013
2023-12-05 11:09:41 -08:00
Tim Abbott
95b39bc8bf analytics: More fixes to zilencer urls import logic. 2023-12-05 10:52:00 -08:00
Tim Abbott
28d736d63b analytics: Don't import zilencer-specific code unconditionally.
This fixes the production server not working when zilencer is not
enabled.
2023-12-05 10:36:35 -08:00
Prakhar Pratyush
14b1539647 remote_activity: Add 'guest users' and 'non guest users' count column.
This commit adds two columns named 'Guest users' and
'Non guest users' to respresent count of such users.

We query 'RemoteRealmAuditLog' to get the data.
2023-12-05 10:04:41 -08:00
Sayam Samal
35ddb994e9 stream_settings: Fix cursor not being disabled due to specificity issue. 2023-12-04 16:38:19 -08:00
Sayam Samal
2f1ea2fe4e help_link_widget: Fix blue on hover by inheriting the text color instead.
In this commit, we add css to help link widgets so that they can inherit
the color property on hover,

Changing `.help_link_widget` selector to `a.help_link_widget` allows
us to increase the specificity of the selector, thus enabling us to
override the on hover property set by the bootstrap classes.
2023-12-04 16:38:19 -08:00
Sayam Samal
68ccb022ca push_notifications: Add help links to mobile notifications options.
Fixes #27830.
2023-12-04 16:38:19 -08:00
Sayam Samal
44fa30e481 css: Add .help_link_widget class to consolidate help widget styling. 2023-12-04 16:38:19 -08:00
Sayam Samal
db753f7f90 notification_settings: Remove extra param being sent to checkboxes.
The show_push_notifications_tooltip param is not being used inside
`web/templates/settings/settings_checkbox.hbs` and thus should not be
passed.
2023-12-04 16:38:19 -08:00
Sayam Samal
e09761c0f9 notification_settings: Disable push notification opts when not configured. 2023-12-04 16:38:19 -08:00
Sayam Samal
c7e2306380 notification_settings: Add banner when push notification not configured. 2023-12-04 16:38:19 -08:00
Sayam Samal
e2485b7ee8 notification_settings: Add tooltip to checkboxes in the triggers table. 2023-12-04 16:38:19 -08:00
Sayam Samal
c01e9b8071 notification_settings: Move tooltip from help icon to "Mobile" heading. 2023-12-04 16:38:19 -08:00
Sayam Samal
14bb8708c6 stream_settings: Move tooltip from help icon to label and checkbox. 2023-12-04 16:38:19 -08:00
Sayam Samal
885cb8dab9 push_notification: Reword from "configured" to "enabled" in tooltip. 2023-12-04 16:38:19 -08:00
Sayam Samal
31cc47cbd7 settings: Migrate mobile notification tooltips to a common template. 2023-12-04 16:38:19 -08:00
N-Shar-ma
15b3ad7466 compose: Show tooltip instantly for a .disabled-on-hover button.
We have some delay for tooltips for all compose buttons, but we now show
tooltips instantly for buttons that are disabled on hover, to emphasize
that the button is disabled, and why.
2023-12-04 16:26:53 -08:00
N-Shar-ma
ab3650343f compose: Fix bug where poll button was disabled on opening compose box.
On opening the compose box right after closing it with some text in it,
the poll button would wrongly be disabled.

This is now fixed by ensuring to reset the button state on opening the
compose box.
2023-12-04 16:26:53 -08:00
roanster007
b676df0efe stream_settings: Fix stream settings dropdown of user profile.
Previously, when the user modal is opened, and a stream is
selected from the streams tab, and then, when a new tab
is selected and you navigate back to this streams tab and
try to pick a new stream, it wouldn't allow you to do so.

This is because, preivously once a stream is picked,
and you go out of the tab and come back, then the
previous object of the subscribe widget was invalidated
and a new object was created. However, the options
still corresponded to the old object. Hence, the error.

This is fixed by checking out if a stream widget object
exists, before creating a new one. The object is cleared
out from the on_user_profile_hide method.

This error is not seen for other tabs even though they
reconstruct their entire tabs when re-visited
without saving previous state is that they don't make
modifications to the user profile from the modal itself.
They rather open some other modal to do it.
Hence, they don't need to store any state for their objects,
and hence no need to retain it.
their state.

Fixes #27422
2023-12-04 16:06:48 -08:00
roanster007
bd6886929a user_profile: Move the widget clearing logic to open hook of modals.
Previously, the logic for clearing of user_streams_list_widget
was present in a separate method, calling which would  clear
out the object. However, there is no guarantee that this
method would always be called, but it is essential for us to
clear out our object every time we close the modal.

Hence, we move this to a new on_user_profile_hide method,
which is provided as a call back to the modals.open hook
so that the object is cleaned out everytime we close the
modal.
2023-12-04 16:06:48 -08:00
N-Shar-ma
43d0965555 popover: Improve computation of elements at reference elements.
So far, we were only considering elements at the the reference element's
bounding box top-left corner, to check if the reference element was
covered by other elements. This led to a bug where the popover wouldn't
show up even if a very small part of the reference element was covered.

Now we consider elements that are at both the top-left and bottom-right
of the reference element's bounding box, as those covering the reference
element.
2023-12-04 16:03:29 -08:00
Karl Stolley
f020f9eee0 pygments: Place Default (light) and Monokai (dark) schemes.
These were generated and captured from the following commands:

$ pygmentize -f html -S default

$ pygmentize -f html -S monokai
2023-12-04 13:03:34 -08:00
Karl Stolley
6696a47d3d pygments: Place GitHub-style red and green for diffs. 2023-12-04 13:03:34 -08:00
Karl Stolley
78d767c1ee pygments: Place Quansight Labs accessible GitHub themes. 2023-12-04 13:03:34 -08:00
Karl Stolley
1c88cc481c pygments: Isolate non-color Zulip custom styles. 2023-12-04 13:03:34 -08:00
Karl Stolley
7817e358f4 markdown: Place redesigned colors and borders on code spans and blocks.
Prior to merging, this commit and others in the PR should be adjusted
and squashed for a cleaner history.

Co-Authored-By: Vlad Korobov <terpimost@gmail.com>
2023-12-04 13:03:34 -08:00
Karl Stolley
6d4f852dc4 markdown: Remove background colors from code blocks.
It appears as though we're still setting a background color,
but that is only to push back against the background set by
Pygments.

However, code blocks in mention messages get the same color
background as ordinary messages, preserving contrast on syntax
highlighting.
2023-12-04 13:03:34 -08:00
Karl Stolley
78d4232fc4 markdown: Present code spans without borders. 2023-12-04 13:03:34 -08:00
Karl Stolley
2811f2fe0f markdown: Implement redesigned Markdown code styles.
Fixes a part of #22022.
2023-12-04 13:03:34 -08:00
Karl Stolley
f1a5fffae9 markdown: Express Markdown code/pre colors as CSS vars.
This ensures that all colors (text, background, and border) are
explicitly declared for Markdown-rendered pre elements, even when
the colors replicate values already declared, e.g., with Pygments.
2023-12-04 13:03:34 -08:00
Karl Stolley
a8612ee9bc markdown: Remove unreachable color properties. 2023-12-04 13:03:34 -08:00
N-Shar-ma
4d590f1f93 compose: Make the Preview button the first, and Upload files the second.
We exchange the positions of the preview and upload buttons to make the
preview button the first one, as the preview button is different from
the other buttons in that it does not manipulate the contents of the
ompose box.
2023-12-04 13:00:35 -08:00
Aman Agrawal
201a2bb9c8 urls: Add / to _page URLs to avoid redirects. 2023-12-04 12:36:24 -08:00
Aman Agrawal
00f4f8cb04 billing: Use PATCH requests for update plan for remote instances.
This fixes the update requests for billing frequency and other
requests not working on billing page for cloud customers.
2023-12-04 12:36:24 -08:00
Aman Agrawal
1a063986e3 billing: Render page for legacy server scheduled for upgrade. 2023-12-04 12:36:24 -08:00
Aman Agrawal
cd45b6f6f8 upgrade: Allow legacy servers to upgrade to business plan. 2023-12-04 12:36:24 -08:00
Aman Agrawal
9935f002ec stripe: Fill get_type_of_plan_tier_change for remote realm/server. 2023-12-04 12:36:24 -08:00
Aman Agrawal
f22ccd3125 stripe: Render upgrade page for remote servers on legacy plan. 2023-12-04 12:36:24 -08:00
Aman Agrawal
7d83508235 commands: Add script to create servers on legacy plan.
Also adds `SWITCH_PLAN_TIER_AT_PLAN_END` for `CustomerPlan`
which will be used to mark status of remote server legacy
plans which are scheduled for an upgrade.
2023-12-04 12:36:24 -08:00
Karl Stolley
c651c4f668 icons: Place new log-in and log-out icons across UI. 2023-12-04 12:11:00 -08:00
Karl Stolley
c3adfa2679 top_navbar: Apply current icon styles to narrow login icon. 2023-12-04 12:11:00 -08:00
Karl Stolley
d85dd6bf2b top_navbar: Set new spectator button styles. 2023-12-04 12:11:00 -08:00
Karl Stolley
b53836de03 top_navbar: Allow spectator items to participate in flexbox. 2023-12-04 12:11:00 -08:00
Lauryn Menard
dc0b8bcb6b analytics: Update query for remote_push_devices in remote activity.
Updates query to count the distinct pairs of user_id and user_uuid
on the RemotePushDeviceToken table.
2023-12-04 11:50:18 -08:00
Tim Abbott
a7c40748fd dev_settings: Allow user to override settings in a git-ignored file.
This should make doing an ongoing development project related to a
settings variant significantly more convenient.
2023-12-04 09:50:14 -08:00
Tim Abbott
2d1b217441 settings: Refactor how TEST_SUITE is configured.
This is designed to allow us to access the value inside
configured_settings.py
2023-12-04 09:50:14 -08:00
Lauryn Menard
45df5750ae corporate: Remove now unused is_sponsored_realm.
This has been replaced with the is_sponsored method in
the RealmBillingSession class.
2023-12-04 07:15:15 -08:00
Lauryn Menard
5eabd51702 corporate: Make is_sponsored_or_pending not abstract in BillingSession.
The logic for BillingSession.is_sponsored_or_pending would be the
same for all three child classes of BillingSession, so this should
be a method on the BillingSession abstract class.
2023-12-04 07:15:15 -08:00
Tim Abbott
fbd8ed1425 import_realm: Move push bouncer ping after realm reactivation. 2023-12-04 07:08:39 -08:00
Tim Abbott
f6c7eaf1e5 models: Add push_notifications_enabled & corresponding end_timestamp.
Add two fields to Realm model:
*push_notifications_enabled
*push_notifications_enabled_end_timestamp

Co-authored-by: Prakhar Pratyush <prakhar@zulip.com>
2023-12-04 07:08:39 -08:00
Prakhar Pratyush
6aa911a9b2 remote_server_post_analytics: Return remote realms data in response.
This is a prep commit to return, for each remote realm, the 'uuid',
'can_push', and 'expected_end_timestamp'.

This data will be used in 'initialize_push_notifications'.
2023-12-04 07:08:39 -08:00
Aman Agrawal
895d76f6f0 hello: Add getapp badge on landing page. 2023-12-03 20:43:07 -08:00
Alya Abbott
271faaaa06 help: Tweak documentation on configuring new user settings.
- Add follow topic confiuration.
- Remove email notifications configuration, as most orgs should be
happy with the defaults.
- Reduce how much text is inside the link titles.
2023-12-03 20:06:02 -08:00
Anders Kaseorg
ec44722252 timerender: Move display_time_zone fallback to initialize.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-03 20:05:25 -08:00
Aman Agrawal
8a1630ee42 remote_billing_page: Redirect servers to correct URL after login. 2023-12-03 15:15:04 -08:00
Aman Agrawal
ff368e3240 populate_billing_realms: Extract function to populate realms. 2023-12-03 15:15:04 -08:00
Aman Agrawal
a59245e932 billing: Make various buttons on billing page work.
We pass billing_base_url to the template and use it to construct
session specific URLs. Also, add corresponding function on server
to support them.
2023-12-03 15:15:04 -08:00
Aman Agrawal
7e7af6266d stripe: Rename get_metadata to use for both billing and upgrade. 2023-12-03 15:15:04 -08:00
Aman Agrawal
f942bbd70f stripe: Show tier information correctly on billing and upgrade page. 2023-12-03 15:15:04 -08:00
Aman Agrawal
785444b2b8 test_stripe: Keep same context after seat_count change during upgrade.
We need to emulate seat_count change after we rendered
the upgrade page. To do that, we need to render the upgrade
page with old seat count.
2023-12-03 15:15:04 -08:00
Aman Agrawal
36532e9180 models: Rename tiers to pass stripe 22 character limit. 2023-12-03 15:15:04 -08:00
Aman Agrawal
5835ef44fe stripe: Add session specific get_email method.
This looks nicer and it will help us have a common method for
get_data_for_stripe_payment_intent.
2023-12-03 15:15:04 -08:00
Lauryn Menard
c457ef2a07 support: Remove discount/sponsorship actions if remote server sponsored.
Removes the support actions for changing a remote server's default
discount and sponsorship pending states if the remote server is on
the sponsored plan type.
2023-12-03 14:27:07 -08:00
Lauryn Menard
69f6d3dcb1 support: Approve sponsorship for remote server customer.
Adds ability to approve a sponsorship request for a customer
attached to a remote server via the remote server support view.
2023-12-03 14:27:07 -08:00
Lauryn Menard
2218e667a6 support: Update remote server customer sponsorship pending status.
Adds ability to update the sponsorship pending status of a customer
attached to a remote server via the remote server support view.
2023-12-03 14:27:07 -08:00
Lauryn Menard
4250e9c1c9 support: Update remote server customer default discount.
Adds ability to update the default discount for a customer attached
to a remote server via the remote server support view.
2023-12-03 14:27:07 -08:00
Lauryn Menard
38eaa4b958 support: Add plan data to remote server support view context. 2023-12-03 14:27:07 -08:00
Lauryn Menard
1d685bd198 support: Add success message to remote servers support view context. 2023-12-03 14:27:07 -08:00
Lauryn Menard
6d66248d3d support: Get plan data via BillingSession for support views. 2023-12-03 14:27:07 -08:00
Lauryn Menard
8d992405a6 analytics: Get customer discount for support views. 2023-12-03 14:27:07 -08:00
Lauryn Menard
6621de6f07 analytics: Add copy email button for remote server support view. 2023-12-03 14:27:07 -08:00
Lauryn Menard
5135acd9e3 support: Use process_support_view_request for plan modifications.
Updates the support view to use process_support_view_request to
process upgrade or downgrade modifications currently implemented
for active plans.
2023-12-03 14:27:07 -08:00
Lauryn Menard
4fb564026d corporate: Move void_all_open_invoices to BillingSession. 2023-12-03 14:27:07 -08:00
Lauryn Menard
97d33a4363 support: Update billing modality via process_support_view_request. 2023-12-03 14:27:07 -08:00
Lauryn Menard
5d25cab42b analytics: Create process_support_view_request BillingSession method.
Creates a process_support_view_request method for BillingSession
to process the various support requests that relate to the billing
system.

Moves approve_realm_sponsorship, update_realm_sponsorship_status,
and attach_discount_to_realm to this new BillingSession method.

Adds a new abstract property to BillingSession to have a string
value, billing_entity_display_name, to use for support messages
sent when these requests are processed.
2023-12-03 14:27:07 -08:00
Lauryn Menard
0679bc044a corporate: Make references to billing modality consistent.
The "send_invoice" and "charge_automatically" strings used by stripe
for the `collection_method` are referred to both as the "billing
method" and "billing modality" in the billing code.

Because we send this as data to stripe as either `collection_method`
or `billing_modality`, renames any references that are any form of
"billing method".
2023-12-03 14:27:07 -08:00
Alya Abbott
42edb4416a help: Update documentation on followed topics in recent conversations. 2023-12-03 13:42:43 -08:00
Mateusz Mandera
544482eefb remote_billing: Add tests for the legacy server flow.
This doesn't provide 100% coverage, but does test the important cases.
2023-12-03 10:39:56 -08:00
Mateusz Mandera
134e3bfa68 remote_billing: Add redirects to login for unauthed user in legacy flow.
Analogical to the more complex mechanism implemented for the RemoteRealm
flow in a previous commit in
authenticated_remote_realm_management_endpoint.

As explained in the code comment, this is much easier because:

In this flow, we can only redirect to our local "legacy server flow
login" page. That means that we can do it universally whether the user
has an expired
identity_dict, or just lacks any form of authentication info at all -
there are no security concerns since this is just a local redirect.
2023-12-03 10:39:56 -08:00
Mateusz Mandera
44ac99b8fc remote_billing: Redirect via next_page param in legacy server flow.
Analogical to 1df8e00d7c which implemented
this for the RemoteRealm auth flow.
Except here we don't need to add next_page to the IdentityDict
(LegacyServerIdentityDict in this flow), because the redirect happens
immediately at remote_billing_legacy_server_login upon login - so no
need to have a structure to carry the info through intermediate steps.
2023-12-03 10:39:56 -08:00
Mateusz Mandera
3d6863b5b9 remote_billing_legacy_server_login: Only accept credentials via POST.
This is an obvious standard practice.
2023-12-03 10:39:56 -08:00
Mateusz Mandera
ec7245d4e1 remote_billing: Add redirect flow for users with expired session.
Implements a nice redirect flow to give a good UX for users attempting
to access a remote billing page with an expired RemoteRealm session e.g.
/realm/some-uuid/sponsorship - perhaps through their browser
history or just their session expired while they were doing things in
this billing system.

The logic has a few pieces:
1. get_remote_realm_from_session, if the user doesn't have a
   identity_dict will raise RemoteBillingAuthenticationError.
2. If the user has an identity_dict, but it's expired, then
   get_identity_dict_from_session inside of get_remote_realm_from_session
   will raise RemoteBillingIdentityExpiredError.
3. The decorator authenticated_remote_realm_management_endpoint
   catches that exception and uses some general logic, described in more
   detail in the comments in the code, to figure out the right URL to
   redirect them to. Something like:
   https://theirserver.example.com/self-hosted-billing/?next_page=...
   where the next_page param is determined based on parsing request.path
   to see what kind of endpoint they're trying to access.
4. The remote_server_billing_entry endpoint is tweaked to also send
   its uri scheme to the bouncer, so that the bouncer can know whether
   to do the redirect on http or https.
2023-12-03 10:39:56 -08:00
Karl Stolley
4987600edc message_edit: Don't hide spinner on save success.
Hiding the spinner confusingly flashes the Save button before the
edit view closes. This just prevents that from happening, so that
the sign of success is the rendered, edited message.
2023-12-03 10:32:43 -08:00
Mateusz Mandera
7f33d6f0ea zilencer: Tie RemotePushDeviceToken to RemoteRealm at registration.
This consists of the following pieces:
1. Makes servers using the bouncer send realm_uuid in requests for token
   registration. (Sidenote: realm_uuid is already sent in the "send
   notification" codepath as of
   48db4bf854)
2. This allows the bouncer to tie RemotePushDeviceToken to the
   RemoteRealm with matching realm_uuid at registration time.
3. Introduce handling of some potential weird edge cases around the
   realm_uuid and RemoteRealm objects in get_remote_realm_helper.
2023-12-03 09:51:45 -08:00
Mateusz Mandera
c9b0602320 tests: Create default RemoteRealms in populate_db.
This default setup will be more realistic, matching the ordinary
conditions for a modern server.
Especially needed as we add bouncer code that will expect to have
RemoteRealm entries for realm_uuid values for which it receives
requests.
2023-12-03 09:51:45 -08:00
Mateusz Mandera
a67dd6dc1f realms: Call send_realms_only_to_push_bouncer at realm creation/import. 2023-12-03 08:49:58 -08:00
Karl Stolley
780d60a9ee left_sidebar: Remove tabindex from correct home-view element. 2023-12-03 08:44:54 -08:00
Karl Stolley
0e4dfa04b3 top_navbar: Contain stream, description elements. 2023-12-03 08:40:45 -08:00
evykassirer
875b7f1377 padded widget: Unabbreviate content_sel to content_selector. 2023-12-03 08:39:07 -08:00
Anders Kaseorg
1efc6efd23 requirements: Upgrade Python requirements.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-12-01 18:52:00 -08:00
Karl Stolley
871054bd1e plans_page: Allow different max-widths on cloud, self-hosted panes. 2023-12-01 15:12:31 -08:00
Karl Stolley
5eb86f3fbb plans_page: Add responsive non-JS plan-pane scrolling. 2023-12-01 15:12:31 -08:00
Karl Stolley
056dde8618 plans_page: Place responsive styles, other than plans pane. 2023-12-01 15:12:31 -08:00
Karl Stolley
94cf3b57a3 plans_page: Add dashed separators. 2023-12-01 15:12:31 -08:00
Karl Stolley
5bfe45b244 plans_page: Implement button styles and icons. 2023-12-01 15:12:31 -08:00
Karl Stolley
c5b4b452c1 plans_page: Place swooshes on question headers. 2023-12-01 15:12:31 -08:00
Karl Stolley
6fb2e28cd2 plans_page: Add curvature to pricing tabs. 2023-12-01 15:12:31 -08:00
Karl Stolley
abbf511322 plans_page: Adjust spacing, curved borders. 2023-12-01 15:12:31 -08:00
Karl Stolley
854d2820d5 plans_page: Place pricing styles. 2023-12-01 15:12:31 -08:00
Karl Stolley
8529d86fc6 plans_page: Place custom bullets with flexbox. 2023-12-01 15:12:31 -08:00
Alya Abbott
6f796e0cbd plans_page: Adjust /plans content.
[squash]: Update sponsorsip and question boxes for Cloud.

[squash]: Update tabs subtitles.

[squash]: Content for info boxes for self-hosted plans.

[squash]: Adjust content to fit design.

portico: Tweak /plans text.
2023-12-01 15:12:31 -08:00
Karl Stolley
dbaef860bf plans_page: Place non-button link colors. 2023-12-01 15:12:31 -08:00
Karl Stolley
4f8cc13417 plans_page: Place text, background colors. 2023-12-01 15:12:31 -08:00
Karl Stolley
b0db397ec6 plans_page: Add independent additional info sections for cloud, self-hosted. 2023-12-01 15:12:31 -08:00
Karl Stolley
29a09ca793 plans_page: Add cloud Plus plan. 2023-12-01 15:12:31 -08:00
Karl Stolley
c9f33344e2 plans_page: Add JS toggle for showing cloud or self-hosted plans. 2023-12-01 15:12:31 -08:00
Karl Stolley
eb36256d7a plans_page: Handle typesetting and vertical alignment. 2023-12-01 15:12:31 -08:00
Karl Stolley
52f8f56286 plans_page: Rough out structures and grid layout. 2023-12-01 15:12:31 -08:00
Karl Stolley
a6498ad666 plans_page: Simplify structures and present page header. 2023-12-01 15:12:31 -08:00
Karl Stolley
9e4cda6ecc plans_page: Align content with Vlad's mock. 2023-12-01 15:12:31 -08:00
Karl Stolley
93aa5747d3 plans_page: Duplicate existing pricing for business page. 2023-12-01 15:12:31 -08:00
N-Shar-ma
82895ff535 compose: Update icons for formatting buttons.
Also made the buttons bigger, and updated breakpoints accordingly.

Fixes: #27845.
2023-12-01 13:51:39 -08:00
Aman Agrawal
5d49e54d33 upgrade: Show tier corresponding to session on upgrade page. 2023-12-01 08:55:58 -08:00
Aman Agrawal
6b70be38b3 models: Add method to get CustomerPlan name from tier. 2023-12-01 08:55:58 -08:00
Aman Agrawal
b35ea18829 stripe: Make get_price_per_license more readable. 2023-12-01 08:55:58 -08:00
Aman Agrawal
8500eae87e billing_page: Successfully render for remote realms and server. 2023-12-01 08:55:58 -08:00
Aman Agrawal
42c0e2ca3e billing_page: Use URL name redirect instead of importing it. 2023-12-01 08:55:58 -08:00
Aman Agrawal
0888608fcc billing_page: Remove completed TODO.
See 003b29ba14
2023-12-01 08:55:58 -08:00
Aman Agrawal
e949fb47ff billing: Rename billing_home to billing_page. 2023-12-01 08:55:58 -08:00
Aman Agrawal
c822e953be urls: Rename function names to match naming syntax of remote pages. 2023-12-01 08:55:58 -08:00
Aman Agrawal
a2c1a2f4bb urls: Use /billing URL to direct user to upgrade page.
This is more about keeping the same syntax for all the pages.
2023-12-01 08:55:58 -08:00
Aman Agrawal
d0c0b11fbf upgrade: Make purchase upgrade work for remove servers and realms.
We are upgrading them to cloud standard right now, we can easily
change tiers in future while adding pricing and configuration for
them.
2023-12-01 08:55:58 -08:00
Aman Agrawal
e9bbb67035 upgrade: Make card add / update work for remote servers. 2023-12-01 08:55:58 -08:00
Aman Agrawal
9889dc38fe event_status: Pass billing_base_url to calculate realm specific URLs. 2023-12-01 08:55:58 -08:00
Aman Agrawal
bb7b0b6731 upgrade: Provide billing_base_url in page_params.
This makes it cleaner to calculate URLs for the current session type.
2023-12-01 08:55:58 -08:00
Aman Agrawal
222077804b upgrade: Simplify getting session data for card update. 2023-12-01 08:55:58 -08:00
Aman Agrawal
0286f10816 stripe: Move non class specific function outside.
Doesn't seem to benefit from being defined inside the class.
2023-12-01 08:55:58 -08:00
Aman Agrawal
25cf0f71a3 event_status: Remove unused variables in context. 2023-12-01 08:55:58 -08:00
Aman Agrawal
953f0f436e stripe_event_handler: Rename get_billing_session. 2023-12-01 08:55:58 -08:00
Prakhar Pratyush
b32950d790 notifications: Revert API changes for push_notifications_enabled.
This commit reverts the API changes in 56ec1c2.
2023-12-01 08:14:14 -08:00
Alex Vandiver
569c364392 users: Fetch and lock the user row before updating its role.
We want to both (a) take a lock on the UserProfile row, and (b)
modify the passed-in UserProfile object, so that callers see the
changes in the object they hold.  Unfortunately,
`select_for_update` cannot be combined with `refresh_from_db`
(https://code.djangoproject.com/ticket/28344).  Call
`select_for_update` and throw away the result, so that we know we have
the lock on the row, then re-fill the `user_profile` object with the
values now that the lock exists.
2023-11-30 16:15:23 -08:00
Alex Vandiver
9b1bdfefcd nagios: Use a better index on UserActivity for zephyr alerting.
Limiting only by client_name and query leads to a very poorly-indexed
lookup on `query` which throws out nearly all of its rows:

```
Nested Loop  (cost=50885.64..60522.96 rows=821 width=8)
  ->  Index Scan using zerver_client_name_key on zerver_client  (cost=0.28..2.49 rows=1 width=4)
        Index Cond: ((name)::text = 'zephyr_mirror'::text)
  ->  Bitmap Heap Scan on zerver_useractivity  (cost=50885.37..60429.95 rows=9052 width=12)
        Recheck Cond: ((client_id = zerver_client.id) AND ((query)::text = ANY ('{get_events,/api/v1/events}'::text[])))
        ->  BitmapAnd  (cost=50885.37..50885.37 rows=9052 width=0)
              ->  Bitmap Index Scan on zerver_useractivity_2bfe9d72  (cost=0.00..16631.82 rows=..large.. width=0)
                    Index Cond: (client_id = zerver_client.id)
              ->  Bitmap Index Scan on zerver_useractivity_1b1cc7f0  (cost=0.00..34103.95 rows=..large.. width=0)
                    Index Cond: ((query)::text = ANY ('{get_events,/api/v1/events}'::text[]))
```

A partial index on the client and query list is extremely effective
here in reducing PostgreSQL's workload; however, we cannot easily
write it as a migration, since it depends on the value of the ID of
the `zephyr_mirror` client.

Since this is only relevant for Zulip Cloud, we manually create the
index:

```sql
CREATE INDEX CONCURRENTLY zerver_useractivity_zehpyr_liveness
    ON zerver_useractivity(last_visit)
 WHERE client_id = 1005
   AND query IN ('get_events', '/api/v1/events');
```

We rewrite the query to do the time limit, distinct, and count in SQL,
instead of Python, and make use of this index.  This turns a 20-second
query into two 10ms queries.
2023-11-30 16:01:55 -08:00
Anders Kaseorg
3b9bb7b2d2 zulip-icons: Use WOFF2 format for icon font.
We’re currently generating the icon font in five formats: Embedded
OpenType, WOFF, WOFF2, TrueType, and SVG.  But they’re misordered by
webfonts-loader such that modern browsers always select the WOFF
version.  WOFF2 is supported by all modern browsers, so just use that
exclusively.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-30 16:00:53 -08:00
Mateusz Mandera
7fad8f1f54 remote_billing: Implement session expiry mechanism.
We still need to add better UX than these JSON errors. We'll want to
utilize the next parameter and redirect the user back to login.
2023-11-30 15:51:10 -08:00
Mateusz Mandera
ea9e2ece49 remote_billing: Extract RemoteBillingUserDict sub-dict. 2023-11-30 15:51:10 -08:00
Mateusz Mandera
5a198c639e remote_billing: Sort out remote_billing_identities typing.
This does two important things:
1. Fix return type of get_identity_dict_from_session to correctly be
   Optional[Union[RemoteBillingIdentityDict, LegacyServerIdentityDict]].
   RemoteBillingIdentityDict is the type in the 8.0+ auth flow,
   LegacyServerIdentityDict is the type in old servers flow, where only
   the server uuid info is available.
2. The uuid key used in request.session["remote_billing_identities"]
   should be explicitly namespaced depending on which flow and type
   we're
   dealing with - to avoid confusion in case of collisions between a
   realm and server that have the same UUID. Such a situation should not
   occur naturally and I haven't come up with any actual exploitation
   ideas that could utilize this by manipulating your server/realm
   uuids, but it's much easier to just not think about such collision
   security implications by making them impossible.
2023-11-30 15:51:10 -08:00
Sayam Samal
8370268f89 upload: Prevent drag-and-drop of an image onto itself.
Previously, dragging an image and dropping it in on itself led to the
image being re-uploaded, which is probably not the intent of a user.

This commit prevents this reuploading of the same image by explicitly
checking if the image is being dragged onto itself, and then rejecting
this action.
2023-11-30 15:39:26 -08:00
Anders Kaseorg
04a6696e33 timerender: Add fallbacks for browser time zone detection.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-30 12:44:31 -08:00
Anders Kaseorg
9c7453c11e people: Downgrade get_user_time error to warning.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-30 12:36:06 -08:00
Sahil Batra
bc2f1ab68c settings: Fix user-access setting dropdown in dark mode.
This commit fixes the design of user access setting
dropdown in dark mode including the case when the
dropdown is disabled.
2023-11-30 12:33:44 -08:00
Sahil Batra
728737ef0e user-groups: Remove banner shown on successful group creation.
There is no need to show the banner on successful group creation
as we anyways open the settings page of newly created group.
2023-11-30 12:33:44 -08:00
Sahil Batra
f84857959c user-groups: Hide "+" button if user is not allowed to create groups. 2023-11-30 12:33:44 -08:00
Sahil Batra
d8b3c5581c settings: Fix "Actions" column width for subscribers and members list.
We reduced the width of "Actions" column too much in stream subscribers
and group members list when there were no users matching the text in
search input and it did not look good because of "Actions" heading
being shifted to extreme right.

This commit fixes it by removing the "actions" class on the heading,
which was used to set the width to "1%" which is needed for tables
with only icons in buttons to avoid unused space but not here.

As a result of removing this class, the CSS of "min-width: 100px"
is being applied to the column, but that's fine atleast for stream
subscribers list as it did not look good before due to scrollbar
overlapping the buttons and it looks better now.

For the group members list, we set min-width to 80px, as we do
not require 100px width and it is enough to avoid overlapping
scrollbars to an extent.

The overlapping scrollbars problem is still not fixed completely
but that will handled in a separate commit. This commit was
just to make the heading row look better when there are no users
in the list.
2023-11-30 12:33:44 -08:00
Alex Vandiver
7f96bed17b stream_traffic: Use the realm_id to get a much better-indexed query.
This reduces the query time by an order of magnitude, since it is able
to switch from a raw `stream_id` index to an index over all of
`realm_id, property, end_time`.
2023-11-30 12:32:30 -08:00
Aman Agrawal
867ca61e86 upgrade: Remove impossible case.
/billing/upgrade no longer returns stripe_session_url after
splitting up the add card and purchase part.
2023-11-30 11:22:19 -08:00
Aman Agrawal
8d485726e4 upgrade: Make add card workflow functional.
Add / update card for remote realms on /upgrade page works now.
2023-11-30 11:22:19 -08:00
Aman Agrawal
a39cb2bda3 session: Migrate to typed endpoint. 2023-11-30 11:22:19 -08:00
Aman Agrawal
d05315b051 event_status: Migrate to typed_endpoint. 2023-11-30 11:22:19 -08:00
Aman Agrawal
e493d998ff event_status: Remove unused retry payment message.
Non-success payments already return an error in backend, so
we will never get here for card payments.
2023-11-30 11:22:19 -08:00
Aman Agrawal
05f2ad5299 event_status: Migrate to typed_endpoint. 2023-11-30 11:22:19 -08:00
Aman Agrawal
4d60c3a96c models: Allow realm_id to be blank.
We cannot provide realm_id for some remote session logs.
2023-11-30 11:22:19 -08:00
Aman Agrawal
5c9a10da31 stripe: Call log create method once.
Makes it easier to look at.
2023-11-30 11:22:19 -08:00
Tim Abbott
cab0215f3f decorator: Pass RemoteServerBillingSession to views. 2023-11-30 11:22:19 -08:00
Aman Agrawal
7540e70cc8 decorator: Pass remote billing session instead of remote realm.
Since endpoints using the
`authenticated_remote_realm_management_endpoint` decorator
want to initialize a billing session and if need be remote_realm
is accessible to via the session variable.
2023-11-30 11:22:19 -08:00
Aman Agrawal
1df8e00d7c remote_billing: Redirect to upgrade/sponsorship page based on next.
We pass `next` parameter with /self-hosted-billing to redirect
users to the intended page after login.

Fixed realm_uuid incorrectly required in remote_realm_upgrade_page.
2023-11-30 11:22:19 -08:00
Lauryn Menard
2c34dcf7dc corporate: Use enum value for type of plan tier change.
Updates do_change_plan_to_new_tier in BillingSession to use an
enum for the value returned when checking for a valid change
between two plan tier types. This makes it more explicit that
the implementation for a valid upgrade in plan tier will be
different from a valid downgrade in plan tier.
2023-11-30 09:43:55 -08:00
Lauryn Menard
4eea4d4717 corporate: Move invoice_plan to BillingSession abstract class. 2023-11-30 09:43:55 -08:00
Karl Stolley
08eb971523 left_sidebar: Adjust STREAMS header grid for spectators. 2023-11-30 08:38:26 -08:00
Alya Abbott
fc83fc8017 help: Document license management and downgrades. 2023-11-30 08:36:15 -08:00
David Rosa
2b0476f5d0 help: Document new polls UI.
Updates article to follow current help center documentation patterns,
and documents the new polls UI.

Fixes #27849.
2023-11-30 08:32:38 -08:00
Prakhar Pratyush
0e575a491f version: Fix incorrect API_FEATURE_LEVEL.
This should have been updated in 56ec1c2.
2023-11-30 08:28:52 -08:00
Alya Abbott
7b71ea3314 help: Add tab for Zulip 8.0+ features. 2023-11-29 23:44:00 -08:00
Tim Abbott
ebb02bad8f billing: Add INVOICING_STATUS_ prefix to values. 2023-11-29 23:32:56 -08:00
Tim Abbott
610338d192 billing: Add BILLING_SCHEDULE_ prefix to values. 2023-11-29 23:32:56 -08:00
Tim Abbott
5d6b635efe billing: Use better variable names for plan tiers.
The existing values didn't have our standard type-prefixing naming
scheme.

Add some extra unused placeholder values while we're at it.
2023-11-29 23:32:56 -08:00
Sayam Samal
408a273ba0 message_view_header: Rename variables and function to specify context.
In this commit, we rename the variables `message_view_header` and
`message_view_header_data` with `context`. We also rename function
`make_message_view_header` with `get_message_view_header_context`.

This new naming convention provides better context about the use
cases of the variables and functions.
2023-11-29 22:14:58 -08:00
Sayam Samal
91cf7ca36f message_view_header: Update tooltip when user is not logged in.
In this commit, we hide the subscriber count on the message view
header tooltip for spectators, since this information is not available
to such users.
2023-11-29 22:14:58 -08:00
David Rosa
af3956e1a9 help: Document upgrade flow, billing, and sponsorship requests.
Documents how to upgrade to a paid plan, manage billing, and
apply for sponsorship.

Fixes #27946.
2023-11-29 21:50:44 -08:00
Aman Agrawal
7997af675b recent_view: Fix filter dropdown enabled after search for spectators.
This is because we render the filters again after search and
hence any events or classes that were attached to widget were reset.
2023-11-29 21:47:36 -08:00
Aman Agrawal
47cdffb5fb recent_view: Drop unread header for spectators. 2023-11-29 21:47:36 -08:00
Aman Agrawal
7834748dd7 css: Fix modal exit button colors not working on billing pages.
This was due to color variables not being accessible as `zulip.css`
is not a file we import on billing page.
2023-11-29 21:46:13 -08:00
Alex Vandiver
c4b619af15 puppet: Change /etc/rabbitmq to be owned by rabbitmq.
The Ubuntu and Debian package installation scripts for
`rabbitmq-server` install `/etc/rabbitmq` (and its contents) owned by
the `rabbitmq` user -- not `root` as Puppet does.  This means that
Puppet and `rabbitmq-server` unnecessarily fight over the ownership.

Create the `rabbitmq` user and group, to the same specifications that
the Debian package install scripts do, so that we can properly declare
the ownership of `/etc/rabbitmq`.
2023-11-29 21:45:35 -08:00
Sayam Samal
329370305f topic_sidebar_actions: Remove extra line in spectators view. 2023-11-29 21:45:21 -08:00
Tim Abbott
a01618d633 billing: Add BillingSession support for requesting sponsorship. 2023-11-29 19:04:32 -08:00
Tim Abbott
1691205306 billing: Remove unnecessary sponsorship request variables. 2023-11-29 19:04:32 -08:00
Aman Agrawal
e43b51b01e stripe: Extract common helper function. 2023-11-29 19:04:32 -08:00
Aman Agrawal
2795f11e3f models: Add org_type to RemoteZulipServer.
This is required to save sponsorship data for remote servers.
2023-11-29 19:04:32 -08:00
Tim Abbott
405c28252a decorator: Don't pass processed args/kwargs onwards.
We've already processed the only URL parameters we intend to support
to determine which RemoteRealm or RemoteZulipServer is involved, so
there should be nothing further to do here.

And it's cleaner to not have to write the downstream code to expect
these unnecessary parameters.
2023-11-29 19:04:32 -08:00
Aman Agrawal
ba11d0fe5d decorator: Add decorator to provide remote_server to endpoint. 2023-11-29 19:04:32 -08:00
Aman Agrawal
5277ebb268 decorator: Remove wrong positional argument remote_realm.
Not sure how this even worked with this required argument.
2023-11-29 19:04:32 -08:00
Aman Agrawal
d8f8152f9b models: Make requested_by optional in sponsorship request model.
`requested_by` needs to be a UserProfile which is not available
for remote server sponsorship requests.
2023-11-29 19:04:32 -08:00
Aman Agrawal
d999d18476 upgrade: Use valid form data to fill email context. 2023-11-29 19:04:32 -08:00
Aman Agrawal
cd7893b1bf sponsorship: Use organization_type as field name for simplification. 2023-11-29 19:04:32 -08:00
evykassirer
fd57a9033b emoji: Use individual images for fallback emoji instead of spritesheet. 2023-11-29 16:04:07 -08:00
Mateusz Mandera
63618e93f0 zilencer: Sync new RemoteRealm fields also for existing registrations. 2023-11-29 15:54:38 -08:00
Mateusz Mandera
9b1a495e2c zilencer: Sync name and authentication_methods on RemoteRealm. 2023-11-29 15:54:38 -08:00
Tim Abbott
4ef6b7cc44 notifications: Rename apns_enabled and gcm_enabled.
The new names are a lot clearer when thinking about self-hosted
systems that might indirectly use these via a bouncer.
2023-11-29 15:06:46 -08:00
Tim Abbott
b71c5746ab notifications: Rename push_notifications_enabled for clarity.
This doesn't actually check if push notifications are working, just
whether there is configuration for them.
2023-11-29 15:06:46 -08:00
Tim Abbott
7db15176f3 push bouncer: Submit basic metadata unconditionally.
These metadata are essentially all publicily available anyway, and
making uploading them unconditional will simplify some things.

The documentation is not quite accurate in that it claims the server
will upload some metadata that is not actually uploaded yet (but will
by soon). This seems harmless.
2023-11-29 14:45:53 -08:00
Mateusz Mandera
2765c63f56 remote_billing: Add flow for legacy servers. 2023-11-29 14:40:27 -08:00
Prakhar Pratyush
d8cf12eaaa send_email: Improve configurability for outgoing email sender name.
Currently, the sender names for outgoing emails sent by Zulip
are hardcoded. It should be configurable for self-hosted systems.

This commit makes the 'Zulip' part a variable in the following
email sender names: 'Zulip Account Security', 'Zulip Digest',
and 'Zulip Notifications' by introducing a settings variable
'SERVICE_NAME' with the default value as f"{EXTERNAL_HOST} Zulip".

Fixes: #23857
2023-11-29 14:20:01 -08:00
Prakhar Pratyush
44d8dc66d2 send_email: Update 'from_name' for non user-facing emails.
This commit performs a minor update in 'from_name' text for
'support' and 'sponsorship' emails.

Removes capitalization and adds a comment specifying that the
emails are not user-facing.
2023-11-29 14:20:01 -08:00
Prakhar Pratyush
78a75ab28c digest: Fix the _ used for an unused variable.
This prep commit replaces `_` with `ignored` to represent
an unused variable.

In later commits, we are going to use `_` for translation,
which leads to a lint error.
2023-11-29 14:20:01 -08:00
Alex Vandiver
3cba6c4303 ci: Add a test for upgrading from 7.0.
We build this image on bookworm, for diversity.
2023-11-29 13:57:33 -08:00
Karl Stolley
20fc71dd87 left_sidebar: Update streams header with unread masking. 2023-11-29 13:33:19 -08:00
Karl Stolley
3c5ea3895f left_sidebar: Place a dark-mode color for masked unreads. 2023-11-29 13:33:19 -08:00
Karl Stolley
a376954a7a left_sidebar: Respect unread display settings on STREAMS header.
Fixes: #27762
2023-11-29 13:33:19 -08:00
Sahil Batra
a6fa875c23 models: Refactor get_fake_email_domain to take realm.host as arg.
This commit updates get_fake_email_domain to accept realm.host as
argument instead of the Realm object since we only use realm.host
to get the fake email domain.

This is a preparatory commit for the limited guest feature as we
would be sending the fake email of the message sender in message
event object to a guest user who cannot access the sender and
there we would need to compute the fake email.
2023-11-29 12:01:37 -08:00
Sahil Batra
7da68259a6 presence: Do not raise blueslip error for presence data of unknown user.
We have already fixed the actual bugs that were resulting in client
receiving presence data for unknown user, so we can remove the
blueslip logging and just skip the unknown users.
2023-11-29 12:01:37 -08:00
Sahil Batra
d5d1540118 settings: Fix width of can-access-all-users-group widget container.
This commit moves dropdown widget for "can_access_all_users_group"
setting inside ".organization-permissions-parent" element which
makes it consistent with other settings and also reduces the
width of its container which is important in further commits to
show the tooltip about plan change on this setting correctly.
2023-11-29 12:01:37 -08:00
Lauryn Menard
81a5977a6e analytics: Adjust width and margin CSS for activity pages. 2023-11-29 11:52:36 -08:00
Lauryn Menard
26168728ca analytics: Add chart key to remote activity page.
Adds a chart key to the top of the remote activity page.

Also, combines the support and stats links for a remote server into
one column.
2023-11-29 11:41:35 -08:00
Lauryn Menard
9180819a9e analytics: Adds count of mobile pushes forwarded for remote server.
Adds a column to the remote servers activity page for a 7 day
count of forwarded mobile push notifications from
RemoteInstallationCount for each server, which will be `None`
if there is no data for the remote server with that ID.
2023-11-29 11:41:35 -08:00
Sahil Batra
355114c976 subscriptions: Add bottom border for subscribers table.
This commit adds bottom border for stream subscribers and
user group members table.
2023-11-29 10:58:04 -08:00
Sahil Batra
550fb3022b subscriptions: Fix border related CSS for subscribers table.
This commit removes unnecessary border related CSS for stream
subscribers and user group members table. The border properties
set on tr elements are not required as we set borders on td
elements and we also remove the border-radius set on table
element as we set border on the required th (and td elements
in further commits) elements and the border-radius was anyways
set to 6px when the border-radius on th element is set to 4px.
2023-11-29 10:58:04 -08:00
Sahil Batra
5ece914377 settings: Update empty message for subscribers and members list.
This commit updates the message shown for empty group members
list to use "members" instead of "subscribers" and also updates
the wording of messages in both stream subscribers and group
members list to match with the one shown in right sidebar users
list.

We also show different message when there are no subscribers or
members matching the filter text.
2023-11-29 10:58:04 -08:00
Sahil Batra
4fc547cfdc user_groups: Rename "Unsubscribe" to "Remove" in members list.
This commit renames "Unsubscribe" button in group members list
to "Remove".
2023-11-29 10:58:04 -08:00
Sahil Batra
2968eb2b04 user_groups: Add UI to set and update can_mention_group setting.
This commit adds support to set can_mention_group setting
when creating user group and also update the setting for
existing user groups.
2023-11-29 10:58:04 -08:00
Sahil Batra
db0476622a user_groups: Disable "Add" button for members list if input is empty.
This commit adds code to disable "Add" button in the members list
if the input is empty by adding a common class to "Add" button
for stream subscribers in group members and using the existing
code in add_subscribers_pill.js.
2023-11-29 10:58:04 -08:00
Sahil Batra
7bdafed635 user_groups: Show spinner on checkmark in left section.
This commit adds code to show spinner when joining or
leaving a group using plus or checkmark icon in the
group rows on left section.

Fixes #25538.
2023-11-29 10:58:04 -08:00
Sahil Batra
c5ac75ce60 user_groups: Reset right panel on changing toggle.
We now reset right panel when toggling from "All groups" to
"Your groups" if the group opened in right section is not
present in the "Your groups" list, i.e. user is not a member
of that group.
2023-11-29 10:58:04 -08:00
Sahil Batra
3b5bbbc5ee user_groups: Show text when there are no groups in the list.
We now show some text when there are no groups in the left
section, both in "Your groups" and "All groups" list like
we do for streams.
2023-11-29 10:58:04 -08:00
Sahil Batra
9b958b2634 user_groups: Allow joining and leaving groups from left section.
This commit adds support to join and leave buttons from the
left section using the plus/checkmark icon present towards
left in each group row.

Fixes part of #25538.
2023-11-29 10:58:04 -08:00
Sahil Batra
a2da85631c user_groups: Add button to join or leave a group.
This commit adds a button to join or leave a user group
in the right section of stream settings overlay to the
right of tabs like we have for streams.

Fixes part of #25538.
2023-11-29 10:58:04 -08:00
Sahil Batra
db99b3f7fe user_groups: Remove unnecessary "hide" class.
There is no need for "hide" class in ".user_group_settings_wrapper"
element as we hide the ".right .settings" element in JS using jquery
"hide" and we also do not remove the "hide" class while showing the
group settings because we use the jquery "show" function for it.
2023-11-29 10:58:04 -08:00
Sahil Batra
e7dffc7cbe user_groups: Rename sub_unsub_button.
This commit renames sub_unsub_button class and related
variables to join_leave_button in new groups UI.
2023-11-29 10:58:04 -08:00
Sahil Batra
4c04675965 settings: Fix disabled look of dropdown widgets.
This commit adds code to change the background color of the
disabled dropdown widget to be same as we have for "select"
elements and also updates cursor to be set to "not-allowed".
2023-11-29 10:58:04 -08:00
Sahil Batra
f9d2ab8bf3 subscriptions: Remove unused styles.
This CSS style was added to ".unsubscribed" selector inside
".stream-header" and ".group-header" elements but the
".unsubscribed" class is only added to the subscription
button which is not inside ".stream-header" element.
2023-11-29 10:58:04 -08:00
Anders Kaseorg
9b03f0c1c8 timerender: Avoid another timeZoneName: "longOffset".
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-29 10:53:56 -08:00
Aman Agrawal
311f72dd56 footer: Add social links with icons.
Fixes #27886

Removed old twitter link in the same column.
2023-11-29 10:37:16 -08:00
Sayam Samal
9191f1a7eb uploads: Extend drag and drop upload area to the navbar. 2023-11-29 10:35:18 -08:00
Sayam Samal
def4cf7f93 uploads: Extend drag and drop upload area to blank areas after sidebars.
To extend the drag and drop upload area to blank areas after sidebars,
we now detect the drag/drop event on the entire ".app" division.

We also change replace `width` and `height` css properties with
`min-width` and `min-height` properties respectively, to make sure
that the ".app" div spans the entire width and height of the viewport.

Fixes: #27550.
2023-11-29 10:35:18 -08:00
Sayam Samal
db6246fcc7 logout: Move logout form from the ".app" div to the body.
The hidden logout form facilitates logging out from the app by providing
a form with the CSRF token required for the csrf_token protection in
Jinja2.

This commit moves this form from the ".app" div to the body,
where it is more appropriately placed in the DOM.

This is a prep commit for #27550.
2023-11-29 10:35:18 -08:00
Sayam Samal
929d34ccd7 modals: Move "About Zulip" modal from ".app" div to body.
The "About Zulip" modal should be appended to the body, like the other
modals and not inside the ".app" div.

This is a prep commit for #27550.
2023-11-29 10:35:18 -08:00
David Rosa
4416f2002b help: Document "(guest)" indicator setting.
- Adds instructions to block to document new setting.
- Adds links to guest users page from relevant docs.
- Cross-links related articles.
2023-11-29 09:58:01 -08:00
Anders Kaseorg
07bd1f28d1 time_zone_util: Avoid relying on timeZoneName: "longOffset".
Safari doesn’t support it yet.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-29 09:57:10 -08:00
Alex Vandiver
3eb5b20c41 register_server: Redact the key when printing it. 2023-11-29 09:56:14 -08:00
evykassirer
32c730e530 emoji: Fallback to Google Modern for unsupported new Twitter emoji.
The Twitter emoji team was laid off in 2022, so new emoji aren't supported.
https://github.com/twitter/twemoji/issues/570#issuecomment-1303422143.
The "twitter" sprite sheet we’re using does have images in those locations,
but they’re fallback images that emoji-datasource fills in from the Apple
sprite sheet, which has unclear licensing implications.

To be able to support newer emoji, we fallback to Google Modern for any emoji
not covered by Twemoji.

CZO conversation:
https://chat.zulip.org/#narrow/stream/107-kandra/topic/emoji.20changes.20in.208.2E0/near/1689918
2023-11-28 23:46:49 -08:00
Alex Vandiver
737de6d4cd user_settings: Re-verify email addresses when enacting them. 2023-11-28 22:11:41 -08:00
Alex Vandiver
037eaa07e2 user_settings: Revoke previous email changes on new one. 2023-11-28 22:11:41 -08:00
Alex Vandiver
f7990ad175 user_settings: Lock user row before changing email address. 2023-11-28 22:11:41 -08:00
Alex Vandiver
6ecfbd2ae8 create_realm: Fix copy/paste error in assertion message. 2023-11-28 18:15:30 -08:00
evykassirer
b68cb5b049 navbar: Show shorter navbar for short screens not narrow screens.
Fixes #27366.
2023-11-28 18:11:56 -08:00
evykassirer
e158877365 spectator sign in buttons: Use media queries instead of hide-xl. 2023-11-28 18:11:56 -08:00
N-Shar-ma
9d3a606bfb compose: Add banner to go to conversation when replying in search view.
When a user is replying to a message while in a search view, we now warn
them that the full conversation is not visible and urge them to go to
the conversation they are composing to, so they can see the complete
conversation. On narrowing to that conversation, the banner is removed.

Fixes: #25893.
2023-11-28 16:55:05 -08:00
Tim Abbott
6cdf3d638b timerender: Reuse Intl.DateTimeFormat objects.
This improves performance of code paths that process larger numbers of
date/time objects, such as the main message feed, dramatically

build_message_groups is about 4x faster following this change, with
timestamp rendering now a negligible portion of the overall work.
2023-11-28 16:54:00 -08:00
Anders Kaseorg
06c2b89525 timerender: Use an explicit time zone for all rendering.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-28 16:54:00 -08:00
Anders Kaseorg
33e335dbd1 time_zone_util: Add zoned date/time utility functions.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-28 16:54:00 -08:00
Sayam Samal
ff9c15ea83 gear_menu_popover: Fix extra space when no invite permission.
This commit fixes the extra space and additional border that was being
added to the gear menu popover when the user did not have permission to
invite users.
2023-11-28 16:50:39 -08:00
Alex Vandiver
61fc838179 python: Switch mocking of timezone_now to time_machine. 2023-11-28 15:10:39 -08:00
Alex Vandiver
82c08dd153 python: Prevent bare timedelta(), which defaults to days. 2023-11-28 15:10:39 -08:00
Alex Vandiver
0f132cef4d test_presence: Fix test which moved days, not seconds. 2023-11-28 15:10:39 -08:00
Tim Abbott
39fb1a0f70 message_fetch: Fix get_frontfill_anchor fallback.
This fixes a very rare exception seen in production, which the
previous assertion allowed us to understand was possible in a rare
race, now documented in comments.
2023-11-28 15:08:00 -08:00
Alex Vandiver
8c8dbb3d66 markdown: Stop attempting to expand/collapse re2 regex.
549dd8a4c4 changed the regex that we build to contain whitespace for
readability, and strip that back out before returning it.
Unfortunately, this also serves to strip out whitespace in the source
linkifier, causing it to not match expected strings.

Revert 549dd8a4c4.

Fixes: #27854.
2023-11-28 15:07:23 -08:00
Karl Stolley
319cfc7d7f left_sidebar: Establish better-centered DM row height.
Despite the existing comment in the CSS, the previous DM row was
22.5px tall. Adjusting the padding makes for 22px tall box, which
greatly improves the centering of the unread count within its
bounding box.
2023-11-28 15:04:38 -08:00
Alex Vandiver
98b68d7034 zilencer: Remove duplicates before adding unique indexes.
The recent #27818 naïvely added unique indexes, despite there being a
large number of existing violations.  This makes the migration
impossible to deploy.

Update the migration to de-duplicate rows, dropping all but the
first-by-id of each unique set.  This is equivalent to what
dd954749be does with `ignore_conflicts`.  We update the migration,
rather than making a new one, as any server which has somehow
successfully applied the migration apparently did not need to
de-duplicate anything.
2023-11-28 15:01:10 -08:00
Mateusz Mandera
02d5740f0f remote_realm: Add syncing of org_type. 2023-11-28 14:41:16 -08:00
Mateusz Mandera
e276812e42 models: Create OrgTypeDict for Realm.ORG_TYPES typing. 2023-11-28 14:41:16 -08:00
Mateusz Mandera
6f2d4f1af2 zilencer: Use proper typing for update_remote_realm_data_for_server.
This is an obvious improvement to the typing and more natural than the
forced dict() conversion.
2023-11-28 14:41:16 -08:00
Lauryn Menard
2f91179c1e api-docs: Add tip for finding a stream or user ID.
Adds a tip to the section on webhook URLs in the incoming webhook
overview and to the section on stream and user IDs in the construct
a narrow documentation. The tip links to relevant articles in the
help center.

Part of work on #19067.
2023-11-28 10:05:16 -08:00
Lauryn Menard
276ceb46e2 emails: Add organization details to confirm registration for new org.
Adds details about the requested organization URL and type to the
registration confirmation email that's sent when creating a new
Zulip organization.

Fixes #25899.
2023-11-28 09:52:31 -08:00
Alex Vandiver
150c64ddd0 zilencer: Enforce uniqueness of server_id + remote_id.
This was previously just an index (not a unique one).  Enforce this
data constraint.
2023-11-28 09:46:48 -08:00
Alex Vandiver
f4cbb494ac test_remote_counts: Insert in date order.
Inserting these in order means that they have dates which ascend with
their ids and remote_ids, for a more accurate simulation.
2023-11-28 09:46:48 -08:00
Alex Vandiver
82960d9bc2 upload: Redirect unauthorized anonymous requests to login.
Note that this also redirects rate-limited anonymous requests to the
login page, as we do not currently differentiate the cases.
2023-11-28 09:44:55 -08:00
Alex Vandiver
f9884af114 upload: Return images for 404/403 responses with image Accept: headers.
If the request's `Accept:` header signals a preference for serving
images over text, return an image representing the 404/403 instead of
serving a `text/html` response.

Fixes: #23739.
2023-11-28 09:44:55 -08:00
Prakhar Pratyush
451ddf4c84 typeahead: Show @-topic typeahead whenever it might be possible to use.
Earlier, when a topic had less than 15 participants,
the @-topic typeahead was not visible. It should be visible
irrespective of the 'realm_wildcard_mention_policy' setting
when the participant count is not greater than 15.

The participant count for a topic can't always be calculated
accurately in the client as some of the messages might still
be loading.

We show @-topic in the typeahead whenever it might be possible
to use it.

We will give an error later if you aren't allowed to use it.

Fixes #27852.
2023-11-28 09:24:18 -08:00
Prakhar Pratyush
590d43f475 mentions: Rename 'stream_wildcard_mention_allowed_in_large_stream'.
This prep commit renames:
* 'stream_wildcard_mention_allowed_in_large_stream' to
'wildcard_mention_policy_authorizes_user' because the function
checks if the sender is allowed to use wildcard mentions based
on the 'wildcard_mention_policy' setting, and we plan to use that
for topic wildcard mentions too.

* 'stream_wildcard_mention_large_stream_threshold' to
'wildcard_mention_threshold' because this value is going to be
used as a threshold value for the max number of subscribers and
participant count allowed for stream and topic wildcard mention
respectively.
2023-11-28 09:24:18 -08:00
Lauryn Menard
b86654d10f analytics: Add column for Zulip version in remote installations page. 2023-11-28 09:20:45 -08:00
Lauryn Menard
5f48abd57a analytics: Add support link to remote installations page.
Refactors get_page helper function so that the updates to the
query data for each row is done in the function that processes
the request.

Adds columns to the remote installation page for both the support
and analytics links.

Adds `analytics/views/remote_activity.py` to the files without
100% backend test coverage.
2023-11-28 09:20:45 -08:00
Lauryn Menard
4fc639cead analytics: Add remote installation suport link function. 2023-11-28 09:20:45 -08:00
Lauryn Menard
4e577119b4 analytics: Use gear icon as realm support link on installation activity. 2023-11-28 09:20:45 -08:00
Aman Agrawal
e8fed998ce test_stripe: Add method to better mock stripe invoice creation. 2023-11-28 08:58:19 -08:00
Aman Agrawal
28a49be161 get_initial_upgrade_context: Use tier passed as part of request. 2023-11-28 08:58:19 -08:00
Alya Abbott
fcd0783202 upgrade: Simplify free trial upgrade page. 2023-11-28 08:57:27 -08:00
David Rosa
de95c0eb42 help: Document voice calls option.
Documents the new audio call option (video call with video muted by
default) to the compose box.

Fixes #26895.
2023-11-28 08:56:46 -08:00
s-bose7
cf038eeba1 docs: Fix outdated number of backend tests.
The current number is about 4500, but the precise value isn't important
at this point.
2023-11-28 08:55:17 -08:00
Sahil Batra
78440033dc invite: Fix label for new user's role dropdown.
This commit updates label for new user's role dropdown to use
"Users" instead of "User(s)" because that caused some problems
in translating the text.

We should ideally be using the ICU plural syntax for such cases,
but since it is used as label, we cannot use the plural syntax
there. So we instead just change it to always have "Users"
plural like we do at some other places in that modal.
2023-11-27 22:18:04 -08:00
Tim Abbott
06ebc39e04 docs: Clarify the sync_ldap_user_data semantics.
The other option would be to run the cron job ourselves, but I feel
like different organizations with different policies might prefer very
different frequencies; daily/hourly, and it's not easy to make that
configurable with a cron file declared in puppet.

Fixes #27866.
2023-11-27 16:34:31 -08:00
roanster007
8bd92fe801 bots: Fix muting of cross realm bots.
Previously, we weren't able to mute the cross realm bots. This was
because, for muting the users, we access only those profiles which are
in realm, excluding the cross realm system bots.

This is fixed by replacing the access_user_by_id method with a new
method access_user_by_id_including_cross_realm for this specific test.

Fixes #27823
2023-11-27 16:16:23 -08:00
Alya Abbott
a3f5332844 help: Add more permissions info to Guest users help page. 2023-11-27 15:40:42 -08:00
Aman Agrawal
cc6ab527b4 upgrade: Match width of license count input with billing page input. 2023-11-27 14:17:18 -08:00
Aman Agrawal
0ef78c793e upgrade: Only show generic help error text for card errors. 2023-11-27 14:17:18 -08:00
Aman Agrawal
15d92225fd billing: Show success message after upgrade from free trial. 2023-11-27 14:17:18 -08:00
Alya Abbott
06fd0c9c4b billing: Edit text of free trial end confirmation banner. 2023-11-27 14:17:18 -08:00
Alya Abbott
9788e7514f billing: Edit text of free trial upgrade page. 2023-11-27 14:17:18 -08:00
Alya Abbott
585ed6c569 billing: Tweak text of free trial downgrade confirmation modal. 2023-11-27 14:17:18 -08:00
Aman Agrawal
fa699032ca upgrade: Drop option to select billing frequency in free trail.
We set monthly as default for billing frequency and let users
change it on the billing page.
2023-11-27 14:17:18 -08:00
Aman Agrawal
b35a792623 billing: Allow free trial orgs to switch billing frequency.
Fixes #27855
2023-11-27 14:17:18 -08:00
Aman Agrawal
1f8d3fc48f stripe: Extract method to get next billing cycle. 2023-11-27 14:17:18 -08:00
Aman Agrawal
482b5d8871 upgrade: Minor text changes to free trial page.
Fixes #27684
2023-11-27 14:17:18 -08:00
Aman Agrawal
fe1a40279c billing: Downgrade realm at the end of free trial.
Fixes #27875

Instead of immediately ending the free trial, we end the free trial
at the end of the cycle.
2023-11-27 14:17:18 -08:00
Aman Agrawal
22c333d135 stripe: Don't allow free trial users to have paid plan states.
Free trial users can only switch to `ACTIVE` or `ENDED`
as possible states. To switch to any other state, they need to
be `ACTIVE` first.
2023-11-27 14:17:18 -08:00
Aman Agrawal
d43a60eb9d models: Add status text for some plan status states. 2023-11-27 14:17:18 -08:00
Alya Abbott
30891bfc54 help: Add dedicated page about guest users. 2023-11-27 13:52:22 -08:00
Alex Vandiver
9f6d340536 footer: Add https://status.zulip.com link.
Fixes: #14417.
2023-11-27 12:09:04 -08:00
roanster007
6397df8b5b lightbox: Fix the panzoom access for non image.
Previously, when we load for the first time, the panzoom
control is binded by default to the image class.

This causes various problems like when a non image class
is opened first in lightbox, and some panzoom function
is performed on it, even though no panzoom object
is binded to them it would perform the function with
the object binded to image class, causing it to throw
errors when no image has been opened yet in the lightbox.

This is fixed by checking if the image class has an img tag
in it before performing any functions of the the panzoom
object.
2023-11-27 10:15:27 -08:00
kimry02
adc11e5ba2 email: Add a space after the time and AM/PM in the login email.
This is more typical formatting for times.

Fixes #27727.
2023-11-27 09:47:30 -08:00
Pratik Chanda
d6a2bbc2ef keyboard_shortcut: Add feedback for N/P shortcut after no more message.
Earlier navigating to the next unread topic or DM with N / P and reaching
last unread topic or dm, nothing happened when shortcut was pressed again.

This commit changes the behaviour when shortcut reaches the last topic or
dm and pressed again. Now it notifies the user that there are no more
unread messages.

Fixes zulip#27862.
2023-11-27 09:45:58 -08:00
Vector73
72d46c71fb message_feed_ui: Fix focus when clicking on message feed buttons.
Fixes: #27820
2023-11-27 09:44:13 -08:00
Aman Agrawal
a96619639c events_register: Set home view for spectators as recent view. 2023-11-27 09:41:52 -08:00
Aman Agrawal
6c4af3478f left_sidebar: Always show VIEWS in expanded state for spectators.
This fixes no views selector visible to the spectator when the
user is not narrowed to one.
2023-11-27 09:41:52 -08:00
Aman Agrawal
603aa3db01 user_base_settings: Change web_home_view default to inbox view.
It would probably provide a better introduction to Zulip
than Recent conversations.
2023-11-27 09:41:52 -08:00
Tim Abbott
7de061cf10 billing: Inline is_realm_on_paid_plan.
This helps simplify the BillingSession interface to have less
realm-specific functions outside RealmBillingSesssion.
2023-11-27 09:01:56 -08:00
Prakhar Pratyush
f8b0e16ff2 stripe: Add get_sponsorship_request_context method to BillingSession.
This commit refactors 'sponsorship_request' view and adds
'BillingSession.get_sponsorship_request' method.

This refactoring will help in minimizing duplicate code
while supporting both realm and remote_Server customers.
2023-11-27 09:01:56 -08:00
Prakhar Pratyush
1b17626327 stripe: Add 'is_sponsored' method to 'BillingSession' class.
This prep commit adds a 'is_sponsored' method as we need
to explicitly check this in 'event_status' view.
2023-11-27 09:01:56 -08:00
Prakhar Pratyush
29f77bfd31 stripe: Add 'get_event_status' method to the 'BillingSession' class.
This commit refactors 'event_status' view and adds
'BillingSession.get_event_status' method.

This refactoring will help in minimizing duplicate code
while supporting both realm and remote_server customers.
2023-11-27 09:01:56 -08:00
Aman Agrawal
20cae359d1 sponsorship: Show correct plan for sponsored without CustomerPlan.
For users on limited plan, if approved for full sponsorship
via support page, we don't create a CustomerPlan for them, but
they are fully sponsored.
2023-11-27 08:23:36 -08:00
Aman Agrawal
3615fa4745 sponsorship: Use customer instead of realm to store sponsorship data.
Since Customer already stores the realm it is linked to and
customer is always created to store sponsorship request, we directly
use customer to link to the sponsorship data for the realm.
2023-11-27 08:23:36 -08:00
Aman Agrawal
fce35fc53b recent_view: Show checkbox icon for unread filter.
I accidentally removed the filter_unread parameter passed to the
template.
2023-11-27 07:57:42 -08:00
Prakhar Pratyush
9c5cfe83ba desktop_notification: Fix bad rendering of math formulas.
Earlier, for the desktop notifications having latex math
like "$$1 \oplus 0 = 1$$, the notification had the math
included multiple times.

This commit fixes the incorrect behavior by replacing
the KaTeX with the raw LaTeX source.

Fixes #25289.
2023-11-26 23:30:24 -08:00
Prakhar Pratyush
6f3b25d749 push_notification: Fix bad rendering of math formulas.
Earlier, for the push notifications having latex math
like "$$1 \oplus 0 = 1$$, the notification had the math
included multiple times.

This commit fixes the incorrect behavior by replacing
the KaTeX with the raw LaTeX source.

Fixes part of #25289.
2023-11-26 23:30:24 -08:00
Aman Agrawal
9fe1e38f98 billing: Remove unused input field. 2023-11-26 20:24:25 -08:00
Aman Agrawal
79d1a1850c billing: Remove card change success message.
This message is barely visible to the user so removed. Now, the
card change behaviour is same as on upgrade page.
2023-11-26 20:24:25 -08:00
Aman Agrawal
f526b7ba58 helpers: Remove non-relevant calls.
`zulip-limited-section` is not longer present and
we don't want `free-trial-alert-message` to hide on any form
submission on the page.
2023-11-26 20:24:25 -08:00
Aman Agrawal
ede73fc2c6 decorator: Add wrapper to directly pass remote_realm to view_func. 2023-11-26 20:23:24 -08:00
Aman Agrawal
354330d81b decorator: Move self_hosting_management_endpoint wrapper to corporate. 2023-11-26 20:23:24 -08:00
Mateusz Mandera
c79d116f8c tests: Add basic tests for remote billing auth flow. 2023-11-26 19:57:12 -08:00
Mateusz Mandera
2f935290f6 tests: Extract BouncerTestCase to test_classes.
This allows re-use in other test_*.py files, which may also want to test
bouncer-reliant logic.
2023-11-26 19:57:12 -08:00
Mateusz Mandera
860f94e599 tests: Remove useless line from test_send_realms_only_to_push_bouncer. 2023-11-26 19:57:12 -08:00
Lauryn Menard
b167eeff08 corporate: Create change plan tier function for BillingSession.
Moves and generalizes `switch_realm_from_standard_to_plus_plan`
in stripe.py to be a more general function for changing a
CustomerPlan to a new and valid tier, `do_change_plan_to_new_tier`.

Adds a helper function with the previous function name to be used
for the support view and management command for changing a realm
from the Standard plan tier to the Plus plan tier.
2023-11-26 19:39:52 -08:00
Lauryn Menard
a00e687d02 corporate: Update stripe create_balance_transaction description. 2023-11-26 19:39:52 -08:00
Lauryn Menard
1f1f1b913b corporate: Simplify initial plan tier switch check.
Simplifies the initial check for switching a customer plan tier
for an existing and active plan, so that it is more generic.
2023-11-26 19:39:52 -08:00
Lauryn Menard
39bfab0ec4 corporate: Move plan tier change to separate helper function.
Renames CustomerPlan.SWITCH_NOW_FROM_STANDARD_TO_PLUS to be more
generic, CustomerPlan.SWITCH_PLAN_TIER_NOW.

Because the plan tier change is immediate, moves the code to end
the current plan with the old tier and create the new plan with
the new tier from `make_end_of_cycle_updates_if_needed` to instead
be in a separate helper function, `switch_plan_tier`.
2023-11-26 19:39:52 -08:00
Lauryn Menard
30f0005799 corporate: Extract calculation of amount to credit for tier change. 2023-11-26 19:39:52 -08:00
Mateusz Mandera
2a65183991 tests: Add test for nocoverage userAccountControl case in ldap auth.
This logic has been #nocoverage since its implementation, but since this
is an authentication codepath, it seems important for it to have a test.
2023-11-24 13:38:23 -08:00
Aman Agrawal
3cfcd0efee remote_upgrade_page: Add /upgrade URL for self hosted realms. 2023-11-24 09:22:02 -08:00
Aman Agrawal
caf2b7da26 stripe: Use remote_billing_url for redirecting to sponsorship page.
The will make redirects to sponsorship page work for all
BillingSession child classes.
2023-11-24 09:22:02 -08:00
Aman Agrawal
6ad9217fbf stripe: Set discount to None for 0 discount. 2023-11-24 09:22:02 -08:00
Aman Agrawal
6040ffb94b stripe: Verify context via TypedDict for upgrade page. 2023-11-24 09:22:02 -08:00
Aman Agrawal
e3abd57dce stripe: Rename update_context_initial_upgrade. 2023-11-24 09:22:02 -08:00
Aman Agrawal
053f30ca25 stripe: Sort upgrade page context keys.
This will help us verify changes easily.
2023-11-24 09:22:02 -08:00
Aman Agrawal
4844ef9810 upgrade: Pass customer_name instead of realm object to upgrade context.
This will help simplify things for remote realms.
2023-11-24 09:22:02 -08:00
Aman Agrawal
ace14e5a7d remote_billing_page: Extract method to get RemoteBillingIdentityDict.
This will likely be used in other files.
2023-11-24 09:22:02 -08:00
Aman Agrawal
f006be0cdf initial_upgrade: Rename to upgrade_page.
Replaced for "(initial_upgrade)", " initial_upgrade"
`'initial_upgrade'` and `"initial_upgrade"`.
2023-11-24 09:22:02 -08:00
Lalit
1089e13529 hotspots: Refactor hotspots system to use a new type Hotspot.
This commit refactors the current hotspot subsytem to use a more
robust dataclass `Hotspot` defined in `lib/hotspots.py`. This fixes
mypy errors as well as make code more readable.
2023-11-24 07:49:24 -08:00
Riken Shah
5659cc1b97 refactor: Replace hotspot open overlay method to function.
Generally, hotspots popover are depended on
the `?` icon to click to activate.

As we have introduced non-intro hotspots, the (non
intro) hotspot popover will open immediately after a
specific event occurs, they won't wait for `?` to click to
activate.

This function helps non-intro hotspots popover to open
easily.
2023-11-24 07:49:24 -08:00
Riken Shah
b74f6afeb1 hotspot: Add frontend changes for non-intro hotspots.
This commit also solves a bug where it displayed
multiple copies of the hotspots when
`ALWAYS_SEND_ALL_HOTSPOTS` is set to true.
2023-11-24 07:49:24 -08:00
Riken Shah
8d633cc368 hotspot: Add backend changes for non-intro hotspots.
This commit introduces non-intro hotspots.
They are a bit different than intro hotspots in the
following ways:

* All the non-intro hotspots are sent at once instead of
sending them one by one like intro hotspots.

* They only activate when a specific event occurs,
unlike intro hotspot where they activate after the
previous hotspot is read.
2023-11-24 07:49:24 -08:00
Prakhar Pratyush
0c159c5f47 mention: Fix mention highlighting in unsubscribed streams.
Rules followed:
1. Bold and highlighted background if the mention was processed
as a mention that includes you.
2. Bold personal mention (but not highlighted) if you were mentioned
but not subscribed at the time.
3. Otherwise not bold, no highlighting.

As we plan to keep the mention pill CSS the same if a user
was mentioned via that personal/wildcard/usergroup mention
irrespective of whether the user is subscribed or not, we use
usermessage flags to determine when to add 'user-mention-me' class.

Fixes #27654.
2023-11-24 07:10:20 -08:00
Alya Abbott
73152671e3 help: Document updated setting to restrict wildcard mentions.
Also drop extraneous details.
2023-11-23 14:29:58 -08:00
Prakhar Pratyush
49388d5d3d topic_mentions: Fix restriction rule for @-topic mentions.
Now, the topic wildcard mention follows the following
rules:
* If the topic has less than 15 participants , anyone
can use @ topic mentions.
* For more than 15, the org setting 'wildcard_mention_policy'
determines who can use @ topic mentions.

Earlier, topic wildcard mentions followed the same restriction
as stream wildcard mentions, which was incorrect.

Fixes part of #27700.
2023-11-23 12:52:25 -08:00
Prakhar Pratyush
31a731469d stream_mentions: Update compose banner text when @-stream restricted.
We simplify the banner message by replacing the
"stream wildcard mentions" text with `"@stream mentions`,
`"@-all mentions"`, or `"@-everyone mentions"` text.
2023-11-23 12:52:25 -08:00
Tim Abbott
a2e6d6c7c4 compose_banner: Document recommended HTML banner code path. 2023-11-23 12:52:25 -08:00
Sahil Batra
189718dc64 settings: Add support to change user-access setting in development.
This commit updates the backend code to allow changing
can_access_all_users_group setting in development environment
and also adds a dropdown in webapp UI which is only shown in
development environment.
2023-11-23 10:40:42 -08:00
Sahil Batra
c90d00faea settings: Rearrange settings in "Organization permissions" section.
This commit re-arranges the settings in "Organization permissions"
section -
- A new section "Guests" is added which now contains the guest
indicator setting checkbox.
- Moved "User identity" and "Guests" sections above "Other permissions"
section.
2023-11-23 10:40:42 -08:00
David Rosa
28bc2b9bf9 help: Document "video player" mobile feature. 2023-11-23 10:37:31 -08:00
David Rosa
26252fb821 help: Document "image viewer" mobile feature. 2023-11-23 10:37:31 -08:00
David Rosa
fc92636bea help: Document video player.
- Adds a new section to help/view-and-browse-images describing the
  video player features.
- Clarifies viewer support for browsing both images and videos.
2023-11-23 10:37:31 -08:00
David Rosa
505a591179 help: Rename view-and-browse-images.md -> view-images-and-videos.md
- Updates filename and adds URL redirect.
- Crosslinks related articles.
2023-11-23 10:37:31 -08:00
David Rosa
f107cf654f help: Update "View and browse images".
- Changes page title to View images and videos.
- Tweaks intro paragraph and adds desktop/web instructions.
- Splits out browsing other images in the view into its own section.
- Updates page to describe interactions with videos too.
- Tweaks documentation on other pages.

Fixes #27801.
2023-11-23 10:37:31 -08:00
Aman Agrawal
c55eaf2bec css: Set tippy-arrow color same as background in light theme. 2023-11-23 10:33:23 -08:00
Aman Agrawal
258c20564d css: Remove .dropdown styles.
We no longer have elements with `dropdown` class in the main app.
It is used in landing page though.
2023-11-23 10:33:23 -08:00
Aman Agrawal
95f5d8bdb8 billing: Note applied discount on upgrade and billing pages.
Fixes #27526
2023-11-23 10:32:39 -08:00
Aman Agrawal
07d29126fd sponsorship: Improve page for sponsorship pending orgs.
Fixes #27686
2023-11-23 10:32:39 -08:00
Aman Agrawal
003b29ba14 billing_page: Redirect orgs on paid plans with sponsorship pending.
Redirect sponsorship pending realms on a paid plan to billing page
with banner which reflects the current status of their request.
2023-11-23 10:32:39 -08:00
Aman Agrawal
74d8a050e4 billing_page: Extract method to check if realm is on paid plan. 2023-11-23 10:32:39 -08:00
Aman Agrawal
9edee65ea0 css: Minor adjustments to sponsorship form.
* Align org sponsorship description with the input fields.
* Make submit button full width.
* Restrict title width to 600px like on other pages.
2023-11-23 10:32:39 -08:00
Aman Agrawal
5422dd3661 sponsorship: Improve sponsorship page for sponsored realms. 2023-11-23 10:32:39 -08:00
Aman Agrawal
ff19dda71c dropdown_widget: Style cannot view stream similar to disabled state. 2023-11-23 09:09:58 -08:00
Aman Agrawal
2acf3cbfa4 dropdown_widget: Allow showing custom text if value is not in options.
If the current value is not in the calculated options,
`text_if_current_value_not_in_options` can be provided to
the widget to show custom text in that case.

Used by stream / user announcement settings if user doesn't
have access to information about the currently selected stream.
2023-11-23 09:09:58 -08:00
Prakhar Pratyush
51b39cb682 stripe: Add 'do_update_plan' method to the 'BillingSession' class.
This commit moves a major portion of the 'update_plan`
view to a new shared 'BillingSession.do_update_plan' method.

This refactoring will help in minimizing duplicate code
while supporting both realm and remote_server customers.
2023-11-23 09:01:45 -08:00
Tim Abbott
efa423395f billing: Make support_session an explicit parameter. 2023-11-23 09:01:45 -08:00
Aman Agrawal
df9c1e085d left_sidebar: Preserve views collapsed / expanded state.
Fixes #27731
2023-11-23 08:26:03 -08:00
Aman Agrawal
d5306334d5 recent_view: Minor alignment changes to search bar.
Center align clear search icon and remove extra space from right.
2023-11-22 23:38:52 -08:00
Aman Agrawal
a872dbb260 recent_view: Disable dropdown widget for spectators. 2023-11-22 23:38:52 -08:00
Aman Agrawal
1e7b4ae160 recent_view: Add drodown widget to filter topics. 2023-11-22 23:38:52 -08:00
Aman Agrawal
de767cc9ad inbox_ui: Move common widget params to views_util. 2023-11-22 23:38:52 -08:00
Aman Agrawal
861ac92747 inbox: Add description to FILTERS dropdown. 2023-11-22 23:38:52 -08:00
Aman Agrawal
1e4f938d82 css: Reduce specificity of dropdown-list-item-common-styles.
This helps override CSS for it more easily.
2023-11-22 23:38:52 -08:00
Aman Agrawal
44d0e5e23b inbox_ui: Rearrange dropdown options. 2023-11-22 23:38:52 -08:00
Aman Agrawal
dca032dd75 inbox_ui: Rename unmuted topics to standard view. 2023-11-22 23:38:52 -08:00
Aman Agrawal
2c76ef9a73 inbox_ui: Extract FILTERS to views_util.
Same FILTERS will also be used in recent view.
2023-11-22 23:38:52 -08:00
David Rosa
59d56c5615 help: Document compose box UI changes.
Updates relevant articles to reflect the changes to the send button
and compose box menu.

Fixes #27848.
2023-11-22 23:00:50 -08:00
Mateusz Mandera
3958743b33 corporate: Add prototype authentication system for self-hosters.
This makes it possible for a self-hosted realm administrator to
directly access a logged-page on the push notifications bouncer
service, enabling billing, support contacts, and other administrator
for enterprise customers to be managed without manual setup.
2023-11-22 17:03:47 -08:00
Mateusz Mandera
1ec0d5bd9d requests: Add SELF_HOSTING_MANAGEMENT_SUBDOMAIN. 2023-11-22 14:22:26 -08:00
Mateusz Mandera
2149cd236f settings: Add new SIGNED_ACCESS_TOKEN_VALIDITY_IN_SECONDS setting. 2023-11-22 14:22:26 -08:00
Mateusz Mandera
8187d6b963 home: Remove redundant condition in get_billing_info.
has_billing_access already has the is_realm_owner check:

    @property
    def has_billing_access(self) -> bool:
        return self.is_realm_owner or self.is_billing_admin
2023-11-22 14:22:26 -08:00
Mateusz Mandera
8695ffba49 remote_server_post_analytics: Change RealmDataForAnalytics.uuid type.
pydantic allows us to use UUID4 as a more accurate type.
2023-11-22 14:22:26 -08:00
Aman Agrawal
c79f667116 user_status: Track focused element by using the same color as hover. 2023-11-22 13:36:27 -08:00
Sayam Samal
b9202ee1db user_status: Fix press enter to open status emoji picker.
As reported on #27270, the emoji picker in the set status modal was
not opening when pressing enter. This commit adds a keypress event
listener to the emoji picker in the set status modal, and opens the
emoji picker when the enter key is pressed.

Fixes part of #27270.
2023-11-22 13:36:27 -08:00
Sayam Samal
bcb7c0a65a invite: Remove redundant param "banner_html".
This was introduced in eecb611789
but was never used due to a change in the implementation to use
partial blocks instead of passing HTML strings.
2023-11-22 13:23:58 -08:00
Sayam Samal
bc8809d45c invite: Change banner type from warning to info.
Also rename "setup_tips_warning" classname to "setup_tips_banner".
2023-11-22 13:23:58 -08:00
Alya Abbott
f012e6dab0 help: Add intro to "Generate URL for an integration" page. 2023-11-22 13:10:23 -08:00
David Rosa
411d0d7863 help: Document modal to generate bot URL.
Adds a help center article to document the new modal for generating
an integration URL, and links /integrations docs to this article.

Fixes #27744.
2023-11-22 10:36:39 -08:00
Sayam Samal
7bb1007884 message_view_header: Update tooltip over stream name in top bar.
In this commit, we update the formatting of the tooltip over the stream
name to match other two line tooltips in the app. We also remove the
stream privacy icon from the tooltip, as it is already displayed in
the message view header.
2023-11-22 10:35:18 -08:00
Aman Agrawal
9a6cfc1b82 recent_view: Extract method to get filters data for render. 2023-11-22 10:31:26 -08:00
Aman Agrawal
26f7f9d3d8 recent_view: Remove All filter.
Fixes #27588

Co-authored-by: cherish2003 <saicherissh90@gmail.com>
2023-11-22 10:31:26 -08:00
Sahil Batra
b1d5cd6bf6 realm: Allow setting notification settings to unsubscribed private streams.
We previously did not allow setting signup_notifications_stream and
notifications_stream settings to private streams that admin is not
subscribed to, even when admins have access to metadata of all the
streams in the realm and can see them in the dropdown options as well.

This commit fixes it to allow admins to set these settings to private
streams that the admin is not subscribed to.
2023-11-22 10:01:19 -08:00
David Rosa
b392655cdb help: Document updated UI for subscribing/unsubscribing from streams.
Updates instructions for subscribing/unsubscribing to streams via
Stream settings on desktop/web, adding new SVG images for the icons.

Fixes #27753.
2023-11-22 09:06:09 -08:00
David Rosa
ad9d1c5380 help: Rename CSS .mobile-icon -> .help-center-icon.
Renames CSS rule that styles Zulip UI icons in the help center
so that it makes sense to use it regardless of whether we are
documenting a mobile or desktop/web feature.
2023-11-22 09:06:09 -08:00
Sayam Samal
2cb4210319 invite: Add setup tips to user invite modal.
We include setup tips to the user invite modal for the following
cases:

At the top of the invite modal
- If the org description is missing.
- If the org profile picture is missing.
- If the custom profile fields have not been added, where we also
  provide a link to the default user settings for the time being,
  until we have a better way to compare if the default user settings
  have been changed.

We also use the new banner ui to display these tips. In doing so, we
extract the banner component from `compose_banner.hbs` to
`popover_banner.hbs`, removing any compose specific code from the
banner component.

Fixes: #24262

Co-authored-by: Lalit <lalitkumarsingh3716@gmail.com>
2023-11-22 09:03:19 -08:00
David Rosa
08d0516b4b help: Document "Manage a user".
Adds a dedicated page to document getting to the user management UI
from the user's profile and from Settings / Users.

Fixes #27748.
2023-11-22 08:33:52 -08:00
Satyam Bansal
bf2d216100 integrations: Add action to GitHub discussion comment notifications.
Previously, the notifications had "commented" as the action word
for every event.

As part of these changes, we extract a shared comment action function
in GitHub Integration that's used for both issue and discussion
comment events.
2023-11-22 08:28:03 -08:00
Satyam Bansal
62ec51f715 integrations: Add GitHub discussion comment edit fixture. 2023-11-22 08:28:03 -08:00
Satyam Bansal
9f01876de8 integrations: Update GitHub discussion related fixtures. 2023-11-22 08:28:03 -08:00
Satyam Bansal
22fa5a7ee3 integrations: Use consistent code boundaries in GitHub notifications.
In other templates we have used "~~~" to start and end a code block.
2023-11-22 08:28:03 -08:00
Lauryn Menard
a4d1211ec6 integrations: Reformat Github pull request assigned message body.
Instead of adding the assignee to the end of the message body,
we update the message body where the verb is so that the link
formatting at the end of the message is not broken, for example:
"user_a assigned user_b to [issue #XXX title text is here](link)."

This matches the formatting for the issue assigned message body.
2023-11-22 08:26:09 -08:00
Lauryn Menard
f6e17fa972 integrations: Reformat Github issue assigned message body.
Instead of adding the assignee to the end of the message body,
we update the message body where the verb is so that the link
formatting at the end of the message is not broken, for example:
"user_a assigned user_b to [issue #XXX title text is here](link)."

Also updates the issue title in the test fixture so that it tests
that only the first instance of "assigned" or "unassigned" in the
issue title is updated for the assignee text.

Also adds punctuation to the issue title in the test fixture to
test the expected behavior for titles that end in a value from
`string.punctuation`.
2023-11-22 08:26:09 -08:00
Aman Agrawal
d82efbd503 free_trial: Remove extra onboarding flow.
We still redirect free trial users to upgrade page on first
signup but no longer pass the onboarding param.
2023-11-22 08:06:22 -08:00
Aman Agrawal
2218c49244 billing: Minor changes to text for free trial.
Fixes #27685
2023-11-22 08:06:22 -08:00
Sahil Batra
f75b4f65c1 streams: Send user remove events when deactivating streams. 2023-11-21 23:58:45 -08:00
Sahil Batra
4f58733d82 events: Remove deactivated streams from subscriptions field.
We did not remove the objects for deactivated streams from
subscriptions field in apply_event. We need to do this because
we do not send "subscription/remove" events to subscribers
when deactivating streams.
2023-11-21 23:58:45 -08:00
Sahil Batra
45e1b32447 users: Send user remove events on user deactivation.
Guests might lose access to deactivated users if the user
is not involved in any DM with guest. This commit adds
code to send "realm_user/remove" events for such cases.
2023-11-21 23:58:45 -08:00
Sahil Batra
58461660c3 users: Restrict accessing avatar for inaccessible users.
We now return the special avatar used for inaccessible users
when a guest user tries to access avatar of an inaccessibe
user using "/avatar" endpoint.
2023-11-21 23:58:45 -08:00
Sahil Batra
3d96969398 avatar: Update avatar used for inaccessible users.
This commit adds a new avatar image which will be shown for
all inaccessible users.
2023-11-21 23:58:45 -08:00
Sahil Batra
32c15d67b5 users: Send user creation events when sending DMs.
We now send user creation events to recipient users
when sending DMs if recipients gain access to either
sender or other pariticpating users in the DM.
2023-11-21 23:58:45 -08:00
Sahil Batra
e4a97dd3ac message: Restrict sending DMs to inaccessible users.
This commit adds code to not allow guest users to send DMs
to users they cannot access.
2023-11-21 23:58:45 -08:00
Sahil Batra
39a31170ee streams: Send event when guest loses access to a user.
This commit adds code to send "realm_user/remove" event
when a guest user loses access to a user due to the user
being unsubscribed from one or more streams.
2023-11-21 23:58:45 -08:00
Sahil Batra
d394cfc4db streams: Send user creation events on adding subscribers.
This commit adds code to send user creation events to
guests who gain access to new subscribers and to the
new guest subscribers who gain access to existing
stream subscribers.
2023-11-21 23:58:45 -08:00
Sahil Batra
dbcc9ea826 users: Update presence and user status code to support restricted users.
The presence and user status update events are only sent to accessible
users, i.e. guests do not receive presence and user status updates for
users they cannot access.
2023-11-21 23:58:45 -08:00
Sahil Batra
650e55fef8 users: Send events only to users who can access the modified user.
This commit adds code to make sure that update events for changing
a user's role, email, etc. are not sent to guests who cannot access
the modified user.
2023-11-21 23:58:45 -08:00
Sahil Batra
6f14d105a7 create_user: Update data in user creation events for guests.
We do not send the original user data in user creation events
to guests if user access is restricted in realm, as they would
receive the information about user if user is subscribed to some
common streams after account creation.
2023-11-21 23:58:45 -08:00
Sahil Batra
a23eff20fe users: Restrict read access to users in access_user_by_id.
This commit adds code to update access_user_by_id to raise
error if guest tries to access an inaccessible user.

One notable behavioral change due to this is that we do
not allow guest to mute or unmute a deactivated user if
that user was not involved in DMs.
2023-11-21 23:58:45 -08:00
Prakhar Pratyush
476b44ae67 stripe: Use 'get_price_per_license' in 'get_initial_upgrade_context'.
This commit updates the 'get_initial_upgrade_context' method
to use 'get_price_per_license' for determining 'annual_price'
and 'monthly_price' based on tier and discount instead of hardcoding.

Also, removed the 'percent_off' page_params as
'get_price_per_license' already performs the price calculation
taking discount into consideration.
2023-11-21 23:39:18 -08:00
Satyam Bansal
cda7ed7101 integrations: Use correct type for GitHub pull request comment messages. 2023-11-21 21:05:26 -08:00
Satyam Bansal
d2589a5bd1 integrations: Send GitHub pull request comment alerts to correct topic.
Pull request comment alerts were previously sent to a topic for an issue,
which resulted in two different topics for the same PR.

Fixes: #26086.

Co-authored-by: Lauryn Menard <lauryn@zulip.com>
2023-11-21 21:05:26 -08:00
Satyam Bansal
7ebf572b8e integrations: Add pull request comment fixture to GitHub Integration.
Updated the repo name and pull request number/title for the new
pull request commit fixture to be the same as the one used for the
other pull request test fixtures (e.g. pull_request__opened) so
that the TOPIC_PR can be used in the subsequent updates.

Co-authored-by: Lauryn Menard <lauryn@zulip.com>
2023-11-21 21:05:26 -08:00
N-Shar-ma
9eeeabf877 compose: Move buttons to popover in 2 batches instead of all at once.
This ensures that there is never too much awkward empty space in the
row of buttons below the compose box.
2023-11-21 21:01:17 -08:00
N-Shar-ma
143db56992 polls: Add option for modal to create polls.
Earlier the `/poll` slash command was the only way to create polls.
To increase user friendliness with a GUI, a button to launch a modal
to create a poll, has been added to the compose box. This button is
enabled only when the compose box is empty, to avoid complexities with
losing / having to save as draft any message already being composed.

The modal has a form which on submission frames a message using the
`/poll` syntax and the data input in the form, and sets the content of
the compose box to that message, which the user can then send. The
question field is mandatory for form submission.

Fixes: #20304.
2023-11-21 21:01:17 -08:00
N-Shar-ma
084718b776 popovers: Wrap modal submission button in a container div.
This is a prep commit for the next commit, which will add a modal for
creating polls. The container div allows a tooltip to be added to the
button in a disabled state (which is needed for the poll modal).
2023-11-21 21:01:17 -08:00
Julia Bichler
f74721d926 compose: Allow un-toggle for links.
When toggling off link formatting, it is assumed that the description
does not contain [ and ] characters, and the url does not contain ( and
) characters.

Co-authored-by: N-Shar-ma <bablinaneh@gmail.com>
2023-11-21 21:01:17 -08:00
Julia Bichler
2db8563a7e compose: Format button for spoilers.
Note that toggling off spoiler formatting works if either all the
content inside, or the header (if it exists) or both are selected.

Co-authored-by: N-Shar-ma <bablinaneh@gmail.com>
2023-11-21 21:01:17 -08:00
Julia Bichler
df143137ef compose: Format button for code.
Note that toggling off, only works for code blocks without a specified
language. So toggling formatting off only works for code blocks like:
```
code
```
and not:
```javascript
code
```

Co-authored-by: N-Shar-ma <bablinaneh@gmail.com>
2023-11-21 21:01:17 -08:00
Julia Bichler
a872ab2a1a compose: Format button for quotes.
Co-authored-by: N-Shar-ma <bablinaneh@gmail.com>
2023-11-21 21:01:17 -08:00
Julia Bichler
c83af7c304 compose: Format button for latex.
Co-authored-by: N-Shar-ma <bablinaneh@gmail.com>
2023-11-21 21:01:17 -08:00
Julia Bichler
63e5e05643 compose: Format button for strikethrough. 2023-11-21 21:01:17 -08:00
N-Shar-ma
4ccbde23cc compose: Reorder and divide compose control buttons into more sections. 2023-11-21 21:01:17 -08:00
N-Shar-ma
30933c5145 compose: Remove gaps between formatting buttons, and make all same size.
Also, added a slight background color change on hover.
2023-11-21 21:01:17 -08:00
N-Shar-ma
42fa4c0011 compose: Refine breakpoints for new compose control buttons.
This is a preparatory commit for new formatting buttons which are added
in the following commits.

Earlier we used multiple classes, each of which handled the hiding or
showing of the element it was applied to, at each breakpoint. Now all
the media queries of those classes have been combined into a new class,
for cleaner and more reusable code. This new combined media query is
also updated to accommodate the new formatting buttons.
2023-11-21 21:01:17 -08:00
Aman Agrawal
3471e84d7f hotkey: Fix emoji popover not triggered in a narrow width range.
We don't show emoji icon on message on width range even if the
logged in user is not the sender, which causes popover to not
be displayed since the reference is not visible.

To avoid such case in future, we just check if the emoji icon is
visible and if not fallback to the ellipsis icon for reference.
2023-11-21 20:56:31 -08:00
Sayam Samal
5c82a923a9 message-editing: Make default "Move messages" form context-dependent.
In the previous menu for moving messages, the default option was
"Move this and all following messages." However, this default choice
was not always aligned with user intentions, particularly when moving
the first or last message in a topic. In such cases, the desired
behavior often corresponds to "Move all messages in this topic" for the
first message and "Move only this message" for the last message.

To address this, we have updated the default options as follows:

1. **When moving the first message in a topic:** The default option is
now "Move all messages in this topic." This change better represents
the user's intention when moving the initial message in a topic.

2. **When moving the last message in a topic:** The default option has
been adjusted to "Move only this message." This change ensures that
users can easily move the last message without affecting other messages
in the topic.

These changes are designed to enhance the user experience and
facilitate the management of topics, especially when users follow or
unmute topics.

Fixes: #27298.
2023-11-21 18:15:11 -08:00
Alex Vandiver
49263ba69f migrations: Keep the existing constraints until the new ones are made.
This removes a window where more violations could enter, and also a
period where indexes which may be useful are lacking.
2023-11-21 21:02:37 -05:00
Alex Vandiver
8b0cecc7e4 migrations: Fix revert migration to not lose all preferences.
Renumbering 4 -> 3, and then 3 -> 2 leads to everyone having their
preferences set to 2.  Swap the order, so that we renumber 3 -> 2,
then 4 -> 3.
2023-11-21 17:56:53 -08:00
M1gue11
2e818a071e popovers: Insert silent mentions for deactivated users.
Fixes #26858.
2023-11-21 12:28:40 -08:00
Tim Abbott
8a0428ffa5 gear_menu: Fix offer of sponsorship when self-hosting.
This had a logic bug, displaying notices intended for Zulip Cloud to
non-business organizations that were self-hosting.
2023-11-21 12:23:21 -08:00
Tim Abbott
2702e09a98 gear_menu: Fix display of version for forks of betas. 2023-11-21 12:23:21 -08:00
Aman Agrawal
b19f407569 views: Set focus back to views after closing compose box.
Fixes #27498
2023-11-21 12:22:32 -08:00
Sahil Batra
ada0fcf299 popovers: Increase popover width to fit avatar inside it.
The user avatar flowed out of the popover and this commit
fixes it by increasing the popover width by 2px and making
it 242px (240px for avatar and 2px for borders). This
also changes the width of user group popovers, but it is
only a slight change so should be fine.
2023-11-21 12:20:25 -08:00
Alex Vandiver
dd954749be zilencer: Log, and drop, duplicated rows from remote servers.
This may happen if there are multiple servers with the same UUID
submitting data (e.g. if they were cloned after initial creation), or
if there is one server, but `./manage.py clear_analytics_tables` was
used to truncate the analytics tables.

In the case of `clear_analytics_tables`, the data submitted likely has
identical historical values with new remote `id` values; preserving
the originally-submitted contemporaneous data is the best option.  For
the case of submissions from multiple servers, there is no completely
sensible outcome, so the best we can do is detect the case and move
on.

Since we have a lock on the RemoteZulipServer, we know that no other
inserts are happening, so counting before and after will return the
true number of rows inserted (which `bulk_create` cannot do in the
face of `ignore_conflicts`[^1]).  We compare this to the expected
number of new inserted rows to detect dropped duplicates.

[^1]: See https://code.djangoproject.com/ticket/30138.
2023-11-21 11:44:55 -08:00
Alex Vandiver
c6ae3e7242 zilencer: Lock the RemoteZulipServer row when inserting data.
This does not ensure that we do not mix data from multiple servers
sharing a UUID -- if one has more `RemoteRealmCount` rows,
and the other has more `RemoteInstalltionCount` rows, the end result
will still be some rows from each server, across the two tables.

It does ensure that we will not alternate rows between two servers
if both requests are processed at the same time.

It also causes submissions to be all-or-nothing in the event of
integrity errors.  This is not necessarily beneficial, as forward
progress is generally useful -- but the integrity errors are resolved
in the subsequent commit.
2023-11-21 11:44:55 -08:00
Alex Vandiver
ae836ae007 zilencer: Apply partial unique constraints for null subgroups.
This applies f299f31340 but for the push bouncer receiving side.
This is particularly important as we start relying on the unique
constraints, via `ON CONFLICT ... IGNORE`, in subsequent commits.

Fixes: #12362.
2023-11-21 11:44:55 -08:00
Aman Agrawal
4f5a9d6a06 event_status: Remove success messages from event status page. 2023-11-21 11:44:04 -08:00
Aman Agrawal
de267b964c event_status: Return user back to same license management after session.
If the update / add card session is successful, return user to
manual license management page if user was on it before clicking
the add / update card button.
2023-11-21 11:44:04 -08:00
Aman Agrawal
554907d9ff upgrade: Restore license count for manual license management page. 2023-11-21 11:44:04 -08:00
Aman Agrawal
221096fed1 upgrade: Extract method to update licenses. 2023-11-21 11:44:04 -08:00
N-Shar-ma
2c318b680b compose: When editing message/s, quote into the last focused edit box.
Until now, when a user quoted and replied to a message, even while
editing another, the quote would be inserted into the compose box. There
was no way to quote into the edit box.

Detecting the edit box to add content too was tricky, since on opening
the message actions popover, that message would be selected, while the
edit box would lose focus.

Now we don't shift focus on opening the message actions popover, keep
track of the last focused textarea and add the quote content to it if
if it's still in the DOM (if the user has not cancelled the editing).

Fixes: #20380.
2023-11-21 11:20:13 -08:00
N-Shar-ma
4610c1a257 compose: Keep track of the last compose type textarea focused on.
This is a prep commit for the next commit, which will quote a message
into the last focused compose type textarea, which we track in a new
`compose_state` variable in this commit.
2023-11-21 11:20:13 -08:00
N-Shar-ma
04b7095c28 compose: Do not select message row on opening message actions popover.
Now since all actions available in the message actions popover operate
on that message itself, we don't need to select the message row when
opening the popover.

This is a prep commit for allowing quoting and replying while editing a
message sent earlier.
2023-11-21 11:20:13 -08:00
N-Shar-ma
19281b584e compose: Pass in the message_id when quoting from the message's popover.
This is a prep commit for allowing quoting and replying while editing a
message sent earlier.
2023-11-21 11:20:13 -08:00
N-Shar-ma
0d4a74b2c2 compose: Allow message_id to be passed into quote_and_reply.
Up until now, the currently selected message was the one that was always
quoted. Now if there's a message_id passed in, we'll quote that message
instead, otherwise we'll fall back on the selected message.

This is a prep commit for allowing quoting and replying while editing a
message sent earlier.
2023-11-21 11:20:13 -08:00
N-Shar-ma
5ba178a54f compose: Allow message_id to be passed to respond_to_message.
Up until now, the currently selected message was the one that was always
responded to. Now if there's a message_id passed in, we'll use that
message instead, otherwise we'll fall back to the selected message.

This is a prep commit for allowing quoting and replying while editing
a message sent earlier.
2023-11-21 11:20:13 -08:00
Alex Vandiver
b363999d19 analytics: Slew record reporting by up to 10 minutes.
This reduces the giant load spike at 5 minute past the hour, when all
remote servers currently attempt to submit their records.

We do not wish to slew over a full hour, because we want to ensure
that we do not hold the lock when the next hour's analytics runs.  It
is also not necessary to have that much variation; 10 minutes is
picked as an arbitrary "long enough" time to spread requests over.
2023-11-21 10:49:57 -08:00
Alex Vandiver
85cc8b6a20 remote_server: Use analytics logger when reporting analytics. 2023-11-21 10:49:57 -08:00
Alex Vandiver
636afa0102 computed_settings: Provide a helper for configuring a new log file. 2023-11-21 10:49:57 -08:00
Alex Vandiver
7233841171 analytics: Move logging config into LOGGING, use "zulip.analytics".
This should not reuse (and reconfigure!) the "zulip.management"
logger.
2023-11-21 10:49:57 -08:00
Alex Vandiver
efa9bf36eb analytics: Factor out UserCount / StreamCount common checks. 2023-11-21 10:49:57 -08:00
Aman Agrawal
626768f626 upgrade: Remove dead error message block. 2023-11-21 10:42:12 -08:00
Tim Abbott
e4d3d4b31d upgrade: Avoid duplicate errors accumulating. 2023-11-21 10:42:12 -08:00
Aman Agrawal
2b68b300a1 upgrade: Add some recommendation for user on payment failure. 2023-11-21 10:42:12 -08:00
Aman Agrawal
69d8442ab4 billing: Allow user to switch between billing frequencies. 2023-11-21 10:42:12 -08:00
Aman Agrawal
6d80460425 billing: Convert some underline separated names to dashes. 2023-11-21 10:42:12 -08:00
Prakhar Pratyush
768be7d46d topic_mentions: Fix the incorrect large @-mention notification warning.
Earlier, a 'large @-mention notification' warning that pops up
for stream wildcard mentions was shown for topic wildcard mentions
too, which is incorrect.

This commit fixes the incorrect behavior. We no longer show the
banner for @-topic mentions.

We don't need a banner for @-topic mentions, as those are much less
likely to be used without thinking about it and would rarely be spammy
for a lot of people.

Fixes #27767.
2023-11-21 09:20:56 -08:00
Karl Stolley
33b164f63a left_sidebar: Remove padding from streams header in narrow windows. 2023-11-21 09:16:43 -08:00
Karl Stolley
ae146c3df7 left_sidebar: Grid 'Back to streams' with unread count.
This also hides the usual STREAMS header when zoomed in to more
topics.
2023-11-21 09:16:43 -08:00
Karl Stolley
bfaa328bd7 left_sidebar: Make streams header sticky. 2023-11-21 09:16:43 -08:00
evykassirer
02845a1d59 buddy list: Rename key to more accurate user_id. 2023-11-21 08:49:52 -08:00
evykassirer
1ddb38e583 buddy list: Rename some variables in insert_new_html for clarity. 2023-11-21 08:49:52 -08:00
evykassirer
6481771301 activity_ui: Pass narrow_by_email during initialization.
Like with the left sidebar, this helps avoid circular imports when we
make this module's behavior dependant on narrowing state.
2023-11-21 08:49:10 -08:00
Anders Kaseorg
23ab667bbb timerender: Avoid dateStyle option missing in Safari < 14.1.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-20 17:19:35 -08:00
evykassirer
4f06fc6a84 padded widget: Use padding_selector instead of padding_sel.
Followup to #27807, fixing a bug it will cause without this
change.
2023-11-20 17:19:02 -08:00
evykassirer
74e66b71ac buddy list: Rename sel variables to selector to avoid abbreviations. 2023-11-20 15:40:42 -08:00
evykassirer
aa9d69c728 stream settings: Include section for right panel in the URL.
Fixes #21017.
2023-11-20 15:32:29 -08:00
evykassirer
87e824d43e stream subscribers: Name the tabs with a single word, removing _settings.
This is preparation for #27637, where it will be nice to be able
to be able to parse a url such as `/#streams/1/announce/general`
and use the `general` string to directly open a stream subscription
tab without converting it to `general_settings`.
2023-11-20 15:32:29 -08:00
evykassirer
5020c48e17 stream settings: Open row with function call instead of simulated click. 2023-11-20 15:32:29 -08:00
Alex Vandiver
9bc41ca040 zilencer: Store the last-reported server version when storing analytics.
Servers since 216d2ec1bf (version 2.0.0)
have submitted this, but we have never stored it.
2023-11-20 14:36:27 -08:00
Tim Abbott
d404febb29 billing: Clean up overly long event_status strings.
The "would be redirected" isn't proper American English grammar too.
2023-11-20 12:04:56 -08:00
Tim Abbott
e3a3f36225 billing: Rename do_initial_upgrade.
This function gets context without doing anything, and so deserves a
name that hints that.
2023-11-20 12:04:56 -08:00
Aman Agrawal
09009ab03a upgrade: Separate add card and purchase upgrade flow.
We now let user add / update card in a separate session and then
charge users after clicking on the purchase button.
2023-11-20 12:04:56 -08:00
Aman Agrawal
a9e9f54962 upgrade: Show loading spinner after clicking on upgrade org button. 2023-11-20 12:04:56 -08:00
Prakhar Pratyush
f7f5131aa8 email_notification: Fix bad rendering of math formulas.
Earlier, for the emails having latex math like
"$$d^* = +\infty$$", the bad rendering led to the math
being included multiple times in the email body.

This was due to displaying KaTeX HTML without the CSS.

This commit fixes the incorrect behavior by replacing
the KaTeX with the raw LaTex source.

Fixes part of #25289.
2023-11-20 10:41:02 -08:00
Aman Agrawal
c85c333c90 narrow: Fix near:ID narrows with invalid stream name in hash.
Fixes #27622

If target message ID is valid but the stream name is invalid, we
fix the stream name in the URL and narrow the target message.
2023-11-20 08:58:49 -08:00
Aman Agrawal
71ea6e8863 realm_inline_image_preview: Use it to toggle video previews too.
This setting now also works to decide whether to show previews of
uploaded or linked videos.
2023-11-20 08:48:39 -08:00
Tim Abbott
08b5bcbdb1 message_list: Remove stale group_date_html calculation.
This has been dead code since b746823e2e
and consumed nontrivial resources.
2023-11-20 08:33:29 -08:00
Prakhar Pratyush
26c5149d31 stripe: Add 'do_initial_upgrade' method to the 'BillingSession' class.
This commit moves a major portion of the 'initial_upgrade`
view to a new shared 'BillingSession.do_initial_upgrade' method.

This refactoring will help in minimizing duplicate code
while supporting both realm and remote_server customers.
2023-11-20 08:00:05 -08:00
Anders Kaseorg
8a875b119f people: Remove final use of date-fns-tz.
date-fns-tz does not handle daylight saving time correctly, and can be
replaced with modern browser APIs.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-19 15:21:34 -08:00
Anders Kaseorg
b897be76b4 message_edit_history: Replace date-fns format with Intl.DateTimeFormat.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-19 15:21:34 -08:00
Anders Kaseorg
64a7810065 timerender: Replace date-fns format with Intl.DateTimeFormat.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-19 15:21:34 -08:00
Anders Kaseorg
581092a964 timerender: Respect the user’s language setting in get_tz_with_UTC_offset.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-19 15:21:34 -08:00
Anders Kaseorg
100f91e1c9 timerender: Remove unnecessary get_user_locale function.
Subscripting an empty array does not throw an exception in JavaScript,
"default" is not a valid locale, and the browser APIs already fall
back on unrecognized locales anyway.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-19 15:21:34 -08:00
Prakhar Pratyush
c539b8ee46 watchdog: Handle exception in callback.
Earlier, in the 'check_for_unsuspend' function, we didn't
handle the exception, if any, during callback execution,
resulting in 'watchdog_time' not always being updated.

This commit handles the exception using the try/catch block.

Fixes #27723.
2023-11-19 15:04:58 -08:00
Karl Stolley
295b4fc337 message_feed: Size visibility policy icons the same as others. 2023-11-18 05:35:06 -08:00
Karl Stolley
3e4f7108a0 message_feed: Vertically align link icon in recipient bar. 2023-11-18 05:35:06 -08:00
Mateusz Mandera
357dceb05a typing: Rewrite remote_server_post_analytics to use @typed_endpoint.
The main point is the RealmDataForAnalytics structure, which we can next
re-use for other endpoints that will take it in in their params.
2023-11-17 18:32:49 -08:00
Mateusz Mandera
8254b74019 auth: Allow easier bouncer testing in dev env. 2023-11-17 18:32:49 -08:00
Mateusz Mandera
ab633f4557 analytics: Add send_realms_only_to_push_bouncer function.
This is a useful helper using the same API as
send_analytics_to_push_bouncer(), but uploading only realms info. This
is useful to upload realms info without the risk of taking a long time
to process the request due to too much of the *Count analytics data.
2023-11-17 18:32:49 -08:00
Alya Abbott
f0e9ab3447 help: Improve unread count badge documentation.
Also mention it on /help/follow-a-topic.
2023-11-17 18:08:55 -08:00
retsambew
9a0a7c1779 message_view_header: Add tooltip to stream name in top navbar.
At present, it's not obvious that clicking on the stream name in the
top navbar will take the user to stream settings. To make it more
apparent, we add a tooltip to the stream name, explicitly indicating its
functionality.

We also add a second line to the tooltip thar displays the number of
subscribers to the stream and remove the tooltip from the number of
subscribers indicator on the top navbar. These changes are in preparation
for removing the number of subscribers indicator from the top navbar.

Fixes #27360.

Co-authored-by: Sayam Samal <samal.sayam@gmail.com>
2023-11-17 17:55:51 -08:00
Karl Stolley
4740a58318 top_navbar: Match navbar icons to left sidebar navigation. 2023-11-17 17:28:54 -08:00
Karl Stolley
fa8504fffb top_navbar: Space navbar title with padding only. 2023-11-17 17:28:54 -08:00
Karl Stolley
cabbff73fd top_navbar: Properly align icons horizontally and vertically.
This adds 3px of margin where previously whitespace separated the
icon from the title.
2023-11-17 17:28:54 -08:00
Karl Stolley
6d452dbe6e top_navbar: Extend new flex-based styles to navbar titles. 2023-11-17 17:28:54 -08:00
Karl Stolley
6271f4d6ba top_navbar: Use flexbox to align icon, stream name, and description. 2023-11-17 17:28:54 -08:00
Karl Stolley
000299a323 top_navbar: Consolidate selectors, replicate styles. 2023-11-17 17:28:54 -08:00
Karl Stolley
1337b60738 top_navbar: Remove unecessary styles and vertical padding. 2023-11-17 17:28:54 -08:00
Karl Stolley
74018c16be top_navbar: Structure navbar titles with a span.
Having an actual DOM node, instead of a text node, will make flex
boxes and other layout mechanisms work more reliably.
2023-11-17 17:28:54 -08:00
aryan
d707f10bb2 message_view_header: Remove subscribers count from the top bar.
Removed the sub_count element along with its styles and dependent
functions.

Fixes #27361.
2023-11-17 17:28:54 -08:00
Mateusz Mandera
1800b2c797 ldap: Tweak AUTH_LDAP_ADVANCED_REALM_ACCESS_CONTROL behavior.
The original behavior of this setting was to disable LDAP
authentication for any realms not configured to use it. This was an
arbitrary choice, and its only value was to potentially help catch
typos for users who are lazy about testing their configuration.

Since it makes it a very inconvenient to potentially host multiple
organizations with different LDAP configurations, remove that
behavior.
2023-11-17 14:40:26 -08:00
Prakhar Pratyush
cc934429fe settings: Add option for followed topics to unread count badge setting.
This commit adds a new option 'DMs, mentions, and followed topics'
to 'desktop_icon_count_display' setting.

The total unread count of DMs, mentions, and followed topics appears
in desktop sidebar and browser tab when this option is configured.

Some existing options are relabeled and renumbered. We finally have:
* All unread messages
* DMs, mentions, and followed topics
* DMs and mentions
* None

Fixes #27503.
2023-11-17 14:07:20 -08:00
Tim Abbott
34f4622091 api_docs: Add missing followed topic visibility policy.
While the server implementation has accepted this value for a few
months as part of building the feature, following topics was not a
fully supported feature of the Zulip server before
3f2ab44f94, just before feature
level 219.

So that's probably the correct level to document as the first feature
level at which we recommend that clients supporting the followed
topics feature process the value.
2023-11-17 13:47:08 -08:00
Lauryn Menard
11cb37c9a4 billing: Add prototype remote billing sessions.
These new models are incomplete and totally untested, but merging this
will provide valuable scaffolding for doing smaller PRs working on
individual gaps, and reveals a clear set of TODOs/refactoring/model
changes needed to support where want to end up.

Co-authored-by: Tim Abbott <tabbott@zulip.com>
2023-11-17 12:58:37 -08:00
Tim Abbott
f916385cab corporate: Adjust models for RemoteRealm customers. 2023-11-17 12:58:37 -08:00
Tim Abbott
c5940bd68f zilencer: Make plan types less weird. 2023-11-17 12:58:37 -08:00
Alya Abbott
c86ad857ac help: Document how to change font size with zoom. 2023-11-17 12:32:44 -08:00
Vector73
95fdf82a53 style: Fix overflow of text.
Fixes: #27282
2023-11-17 10:33:42 -08:00
N-Shar-ma
cec11be7c1 compose: Disable send button when upload/s are in progress.
Earlier, the send button's state was determined independently by the
message length and the upload status, and its containing `div`'s state
was separately determined on recipient change by whether the user had
permission to post in that stream, or if direct messages were allowed
in the organisation. The main problem was that as the message length
check was run on each character input, it often overrode the upload
status check.

Now for consistency, the code has been refactored to always disable the
send button by accordingly styling its containing `div`; and using the
observer pattern we update it anytime the message length, upload status,
or the recipient changes, considering **all** of them together.

(The Save button when editing a message still works like before -- only
dependent on upload status.)

Fixes: #21135.
2023-11-17 10:31:58 -08:00
N-Shar-ma
6bc9092786 compose: Fix bug where extra line breaks were pasted between blocks.
Due to the way turndown pads every block element with 2 new lines, and
makes `br` double space by default, we would get 3 blank lines pasted
when there's just 1 line break between 2 paragraphs.

Now we set `br` to an empty string, and since turndown collapses
sequences of multiple new lines to `\n\n` (1 blank line), so any 2 block
elements will now always have 1 blank line between them, irrespective of
if and how many line breaks there are between them in the copied HTML.
2023-11-17 08:55:55 -08:00
Alya Abbott
34fcb3a393 help: Fix copy-paste bug in wildcard mention documentation.
Bug recently introduced in 8264b8cde0.
2023-11-17 08:55:15 -08:00
David
81f99dde55 gear_menu: Rename "Manage streams" -> "Stream settings".
Fixes #27754.
2023-11-17 08:53:41 -08:00
Tim Abbott
c71f62089b api_docs: Clarify found_oldest/newest descriptions. 2023-11-16 18:00:37 -08:00
Mateusz Mandera
13aa24a56a drafts: Update some stale docstrings regarding draft data validation.
These were written before the draft endpoints were converted to use
@typed_endpoint and pydantic-based DraftData(BaseModel) for param
validation. Update them to avoid the confusion of talking about dicts
and dict_validator functions when those are no longer a thing.
2023-11-16 17:29:12 -08:00
Anders Kaseorg
1dd386a65a tippyjs: Convert module to TypeScript.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-16 17:26:54 -08:00
Anders Kaseorg
a8bc7ceb15 tippyjs: Remove wrong $ prefix for non-jQuery variable.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-16 17:26:54 -08:00
Tim Abbott
044f65a052 version: Update version after 8.0-beta1 release. 2023-11-16 17:26:19 -08:00
1679 changed files with 113282 additions and 72502 deletions

View File

@@ -25,3 +25,4 @@ forin
uper
slac
couldn
ges

View File

@@ -254,6 +254,9 @@ jobs:
- docker_image: zulip/ci:bullseye-6.0
name: 6.0 Version Upgrade
os: bullseye
- docker_image: zulip/ci:bookworm-7.0
name: 7.0 Version Upgrade
os: bookworm
name: ${{ matrix.name }}
container:

1
.gitignore vendored
View File

@@ -20,6 +20,7 @@
/zproject/apns-dev.pem
/zproject/apns-dev-key.p8
/zproject/dev-secrets.conf
/zproject/custom_dev_settings.py
/tools/conf.ini
/tools/custom_provision
/tools/droplets/conf.ini

View File

@@ -29,6 +29,8 @@ Aman Agrawal <amanagr@zulip.com> <f2016561@pilani.bits-pilani.ac.in>
Anders Kaseorg <anders@zulip.com> <anders@zulipchat.com>
Anders Kaseorg <anders@zulip.com> <andersk@mit.edu>
aparna-bhatt <aparnabhatt2001@gmail.com> <86338542+aparna-bhatt@users.noreply.github.com>
Aryan Bhokare <aryan1bhokare@gmail.com>
Aryan Bhokare <aryan1bhokare@gmail.com> <92683836+aryan-bhokare@users.noreply.github.com>
Aryan Shridhar <aryanshridhar7@gmail.com>
Aryan Shridhar <aryanshridhar7@gmail.com> <53977614+aryanshridhar@users.noreply.github.com>
Ashwat Kumar Singh <ashwat.kumarsingh.met20@itbhu.ac.in>
@@ -68,6 +70,7 @@ Kevin Mehall <km@kevinmehall.net> <kevin@humbughq.com>
Kevin Mehall <km@kevinmehall.net> <kevin@zulip.com>
Kevin Scott <kevin.scott.98@gmail.com>
Lalit Kumar Singh <lalitkumarsingh3716@gmail.com>
Lalit Kumar Singh <lalitkumarsingh3716@gmail.com> <lalits01@smartek21.com>
Lauryn Menard <lauryn@zulip.com> <63245456+laurynmm@users.noreply.github.com>
Lauryn Menard <lauryn@zulip.com> <lauryn.menard@gmail.com>
m-e-l-u-h-a-n <purushottam.tiwari.cd.cse19@itbhu.ac.in>
@@ -75,6 +78,8 @@ m-e-l-u-h-a-n <purushottam.tiwari.cd.cse19@itbhu.ac.in> <pururshottam.tiwari.cd.
Mateusz Mandera <mateusz.mandera@zulip.com> <mateusz.mandera@protonmail.com>
Matt Keller <matt@zulip.com>
Matt Keller <matt@zulip.com> <m@cognusion.com>
Nehal Sharma <bablinaneh@gmail.com>
Nehal Sharma <bablinaneh@gmail.com> <68962290+N-Shar-ma@users.noreply.github.com>
Noble Mittal <noblemittal@outlook.com> <62551163+beingnoble03@users.noreply.github.com>
nzai <nzaih18@gmail.com> <70953556+nzaih1999@users.noreply.github.com>
Palash Baderia <palash.baderia@outlook.com>
@@ -92,6 +97,7 @@ Rishi Gupta <rishig@zulipchat.com> <rishig@users.noreply.github.com>
Rixant Rokaha <rixantrokaha@gmail.com>
Rixant Rokaha <rixantrokaha@gmail.com> <rishantrokaha@gmail.com>
Rixant Rokaha <rixantrokaha@gmail.com> <rrokaha@caldwell.edu>
Rohan Gudimetla <rohan.gudimetla07@gmail.com>
Sahil Batra <sahil@zulip.com> <35494118+sahil839@users.noreply.github.com>
Sahil Batra <sahil@zulip.com> <sahilbatra839@gmail.com>
Satyam Bansal <sbansal1999@gmail.com>
@@ -99,6 +105,7 @@ Sayam Samal <samal.sayam@gmail.com>
Scott Feeney <scott@oceanbase.org> <scott@humbughq.com>
Scott Feeney <scott@oceanbase.org> <scott@zulip.com>
Shlok Patel <shlokcpatel2001@gmail.com>
Shu Chen <shu@zulip.com>
Somesh Ranjan <somesh.ranjan.met20@itbhu.ac.in> <77766761+somesh202@users.noreply.github.com>
Steve Howell <showell@zulip.com> <showell30@yahoo.com>
Steve Howell <showell@zulip.com> <showell@yahoo.com>
@@ -114,6 +121,7 @@ Tim Abbott <tabbott@zulip.com> <tabbott@zulipchat.com>
Ujjawal Modi <umodi2003@gmail.com> <99073049+Ujjawal3@users.noreply.github.com>
umkay <ukhan@zulipchat.com> <umaimah.k@gmail.com>
umkay <ukhan@zulipchat.com> <umkay@users.noreply.github.com>
Viktor Illmer <1476338+v-ji@users.noreply.github.com>
Vishnu KS <vishnu@zulip.com> <hackerkid@vishnuks.com>
Vishnu KS <vishnu@zulip.com> <yo@vishnuks.com>
Waseem Daher <wdaher@zulip.com> <wdaher@dropbox.com>

View File

@@ -19,7 +19,6 @@ from analytics.models import (
UserCount,
installation_epoch,
)
from zerver.lib.logging_util import log_to_file
from zerver.lib.timestamp import ceiling_to_day, ceiling_to_hour, floor_to_hour, verify_UTC
from zerver.models import Message, Realm, RealmAuditLog, Stream, UserActivityInterval, UserProfile
@@ -31,10 +30,9 @@ if settings.ZILENCER_ENABLED:
RemoteZulipServer,
)
## Logging setup ##
logger = logging.getLogger("zulip.management")
log_to_file(logger, settings.ANALYTICS_LOG_PATH)
logger = logging.getLogger("zulip.analytics")
# You can't subtract timedelta.max from a datetime, so use this instead
TIMEDELTA_MAX = timedelta(days=365 * 1000)
@@ -111,6 +109,9 @@ class DataCollector:
self.output_table = output_table
self.pull_function = pull_function
def depends_on_realm(self) -> bool:
return self.output_table in (UserCount, StreamCount)
## CountStat-level operations ##
@@ -199,7 +200,7 @@ def do_fill_count_stat_at_hour(
def do_delete_counts_at_hour(stat: CountStat, end_time: datetime) -> None:
if isinstance(stat, LoggingCountStat):
InstallationCount.objects.filter(property=stat.property, end_time=end_time).delete()
if stat.data_collector.output_table in [UserCount, StreamCount]:
if stat.data_collector.depends_on_realm():
RealmCount.objects.filter(property=stat.property, end_time=end_time).delete()
else:
UserCount.objects.filter(property=stat.property, end_time=end_time).delete()
@@ -220,7 +221,7 @@ def do_aggregate_to_summary_table(
else:
realm_clause = SQL("")
if output_table in (UserCount, StreamCount):
if stat.data_collector.depends_on_realm():
realmcount_query = SQL(
"""
INSERT INTO analytics_realmcount

View File

@@ -1,3 +1,4 @@
import hashlib
import os
import time
from argparse import ArgumentParser
@@ -12,7 +13,7 @@ from typing_extensions import override
from analytics.lib.counts import ALL_COUNT_STATS, logger, process_count_stat
from scripts.lib.zulip_tools import ENDC, WARNING
from zerver.lib.remote_server import send_analytics_to_push_bouncer
from zerver.lib.remote_server import send_server_data_to_push_bouncer
from zerver.lib.timestamp import floor_to_hour
from zerver.models import Realm
@@ -95,5 +96,14 @@ class Command(BaseCommand):
)
logger.info("Finished updating analytics counts through %s", fill_to_time)
if settings.PUSH_NOTIFICATION_BOUNCER_URL and settings.SUBMIT_USAGE_STATISTICS:
send_analytics_to_push_bouncer()
if settings.PUSH_NOTIFICATION_BOUNCER_URL:
# Skew 0-10 minutes based on a hash of settings.ZULIP_ORG_ID, so
# that each server will report in at a somewhat consistent time.
assert settings.ZULIP_ORG_ID
delay = int.from_bytes(
hashlib.sha256(settings.ZULIP_ORG_ID.encode()).digest(), byteorder="big"
) % (60 * 10)
logger.info("Sleeping %d seconds before reporting...", delay)
time.sleep(delay)
send_server_data_to_push_bouncer(consider_usage_statistics=True)

View File

@@ -1,7 +1,7 @@
# https://github.com/typeddjango/django-stubs/issues/1698
# mypy: disable-error-code="explicit-override"
import datetime
from datetime import datetime
from django.db import models
from django.db.models import Q, UniqueConstraint
@@ -27,7 +27,7 @@ class FillState(models.Model):
# The earliest/starting end_time in FillState
# We assume there is at least one realm
def installation_epoch() -> datetime.datetime:
def installation_epoch() -> datetime:
earliest_realm_creation = Realm.objects.aggregate(models.Min("date_created"))[
"date_created__min"
]

View File

@@ -1,9 +1,80 @@
import uuid
from datetime import timedelta
from unittest import mock
from django.utils.timezone import now as timezone_now
from corporate.lib.stripe import add_months
from corporate.models import Customer, CustomerPlan, LicenseLedger
from zerver.lib.test_classes import ZulipTestCase
from zerver.models import Client, UserActivity, UserProfile
from zilencer.models import (
RemoteRealmAuditLog,
RemoteZulipServer,
get_remote_server_guest_and_non_guest_count,
)
event_time = timezone_now() - timedelta(days=3)
data_list = [
{
"server_id": 1,
"realm_id": 1,
"event_type": RemoteRealmAuditLog.USER_CREATED,
"event_time": event_time,
"extra_data": {
RemoteRealmAuditLog.ROLE_COUNT: {
RemoteRealmAuditLog.ROLE_COUNT_HUMANS: {
UserProfile.ROLE_REALM_ADMINISTRATOR: 10,
UserProfile.ROLE_REALM_OWNER: 10,
UserProfile.ROLE_MODERATOR: 10,
UserProfile.ROLE_MEMBER: 10,
UserProfile.ROLE_GUEST: 10,
}
}
},
},
{
"server_id": 1,
"realm_id": 1,
"event_type": RemoteRealmAuditLog.USER_ROLE_CHANGED,
"event_time": event_time,
"extra_data": {
RemoteRealmAuditLog.ROLE_COUNT: {
RemoteRealmAuditLog.ROLE_COUNT_HUMANS: {
UserProfile.ROLE_REALM_ADMINISTRATOR: 20,
UserProfile.ROLE_REALM_OWNER: 0,
UserProfile.ROLE_MODERATOR: 0,
UserProfile.ROLE_MEMBER: 20,
UserProfile.ROLE_GUEST: 10,
}
}
},
},
{
"server_id": 1,
"realm_id": 2,
"event_type": RemoteRealmAuditLog.USER_CREATED,
"event_time": event_time,
"extra_data": {
RemoteRealmAuditLog.ROLE_COUNT: {
RemoteRealmAuditLog.ROLE_COUNT_HUMANS: {
UserProfile.ROLE_REALM_ADMINISTRATOR: 10,
UserProfile.ROLE_REALM_OWNER: 10,
UserProfile.ROLE_MODERATOR: 0,
UserProfile.ROLE_MEMBER: 10,
UserProfile.ROLE_GUEST: 5,
}
}
},
},
{
"server_id": 1,
"realm_id": 2,
"event_type": RemoteRealmAuditLog.USER_CREATED,
"event_time": event_time,
"extra_data": {},
},
]
class ActivityTest(ZulipTestCase):
@@ -35,7 +106,32 @@ class ActivityTest(ZulipTestCase):
result = self.client_get("/activity")
self.assertEqual(result.status_code, 200)
with self.assert_database_query_count(4):
# Add data for remote activity page
RemoteRealmAuditLog.objects.bulk_create([RemoteRealmAuditLog(**data) for data in data_list])
remote_server = RemoteZulipServer.objects.get(id=1)
customer = Customer.objects.create(remote_server=remote_server)
plan = CustomerPlan.objects.create(
customer=customer,
billing_cycle_anchor=timezone_now(),
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
tier=CustomerPlan.TIER_SELF_HOSTED_BUSINESS,
price_per_license=8000,
next_invoice_date=add_months(timezone_now(), 12),
)
LicenseLedger.objects.create(
licenses=10,
licenses_at_next_renewal=10,
event_time=timezone_now(),
is_renewal=True,
plan=plan,
)
RemoteZulipServer.objects.create(
uuid=str(uuid.uuid4()),
api_key="magic_secret_api_key",
hostname="demo.example.com",
contact_email="email@example.com",
)
with self.assert_database_query_count(10):
result = self.client_get("/activity/remote")
self.assertEqual(result.status_code, 200)
@@ -51,3 +147,12 @@ class ActivityTest(ZulipTestCase):
with self.assert_database_query_count(5):
result = self.client_get(f"/user_activity/{iago.id}/")
self.assertEqual(result.status_code, 200)
def test_get_remote_server_guest_and_non_guest_count(self) -> None:
RemoteRealmAuditLog.objects.bulk_create([RemoteRealmAuditLog(**data) for data in data_list])
remote_server_counts = get_remote_server_guest_and_non_guest_count(
server_id=1, event_time=timezone_now()
)
self.assertEqual(remote_server_counts.non_guest_user_count, 70)
self.assertEqual(remote_server_counts.guest_user_count, 15)

View File

@@ -116,6 +116,10 @@ class AnalyticsTestCase(ZulipTestCase):
# used as defaults in self.assert_table_count
self.current_property: Optional[str] = None
# Delete RemoteRealm registrations to have a clean slate - the relevant
# tests want to construct this from scratch.
RemoteRealm.objects.all().delete()
# Lightweight creation of users, streams, and messages
def create_user(self, **kwargs: Any) -> UserProfile:
self.name_counter += 1
@@ -130,7 +134,7 @@ class AnalyticsTestCase(ZulipTestCase):
for key, value in defaults.items():
kwargs[key] = kwargs.get(key, value)
kwargs["delivery_email"] = kwargs["email"]
with mock.patch("zerver.lib.create_user.timezone_now", return_value=kwargs["date_joined"]):
with time_machine.travel(kwargs["date_joined"], tick=False):
pass_kwargs: Dict[str, Any] = {}
if kwargs["is_bot"]:
pass_kwargs["bot_type"] = UserProfile.DEFAULT_BOT
@@ -1455,8 +1459,9 @@ class TestLoggingCountStats(AnalyticsTestCase):
now = timezone_now()
with time_machine.travel(now, tick=False), mock.patch(
"zilencer.views.send_android_push_notification", return_value=1
), mock.patch(
"zilencer.views.send_apple_push_notification", return_value=1
), mock.patch("zilencer.views.send_apple_push_notification", return_value=1), mock.patch(
"corporate.lib.stripe.RemoteServerBillingSession.current_count_for_billed_licenses",
return_value=10,
), self.assertLogs(
"zilencer.views", level="INFO"
):
@@ -1515,8 +1520,9 @@ class TestLoggingCountStats(AnalyticsTestCase):
}
with time_machine.travel(now, tick=False), mock.patch(
"zilencer.views.send_android_push_notification", return_value=1
), mock.patch(
"zilencer.views.send_apple_push_notification", return_value=1
), mock.patch("zilencer.views.send_apple_push_notification", return_value=1), mock.patch(
"corporate.lib.stripe.RemoteServerBillingSession.current_count_for_billed_licenses",
return_value=10,
), self.assertLogs(
"zilencer.views", level="INFO"
):
@@ -1574,8 +1580,9 @@ class TestLoggingCountStats(AnalyticsTestCase):
with time_machine.travel(now, tick=False), mock.patch(
"zilencer.views.send_android_push_notification", return_value=1
), mock.patch(
"zilencer.views.send_apple_push_notification", return_value=1
), mock.patch("zilencer.views.send_apple_push_notification", return_value=1), mock.patch(
"corporate.lib.stripe.RemoteServerBillingSession.current_count_for_billed_licenses",
return_value=10,
), self.assertLogs(
"zilencer.views", level="INFO"
):

View File

@@ -1,14 +1,23 @@
from datetime import datetime, timedelta, timezone
from typing import TYPE_CHECKING, Optional
from decimal import Decimal
from typing import TYPE_CHECKING, Any, Optional
from unittest import mock
import orjson
import time_machine
from django.utils.timezone import now as timezone_now
from typing_extensions import override
from corporate.lib.stripe import add_months
from corporate.lib.support import update_realm_sponsorship_status
from corporate.models import Customer, CustomerPlan, LicenseLedger, get_customer_by_realm
from corporate.lib.stripe import RealmBillingSession, add_months
from corporate.models import (
Customer,
CustomerPlan,
LicenseLedger,
SponsoredPlanTypes,
ZulipSponsorshipRequest,
get_current_plan_by_realm,
get_customer_by_realm,
)
from zerver.actions.invites import do_create_multiuse_invite_link
from zerver.actions.realm_settings import do_change_realm_org_type, do_send_realm_reactivation_email
from zerver.actions.user_settings import do_change_user_setting
@@ -16,6 +25,7 @@ from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import reset_email_visibility_to_everyone_in_zulip_realm
from zerver.models import (
MultiuseInvite,
OrgTypeEnum,
PreregistrationUser,
Realm,
UserMessage,
@@ -36,6 +46,24 @@ from zilencer.models import RemoteZulipServer
class TestRemoteServerSupportEndpoint(ZulipTestCase):
@override
def setUp(self) -> None:
def add_sponsorship_request(
hostname: str, org_type: int, website: str, paid_users: str, plan: str
) -> None:
remote_server = RemoteZulipServer.objects.get(hostname=hostname)
customer = Customer.objects.create(
remote_server=remote_server, sponsorship_pending=True
)
ZulipSponsorshipRequest.objects.create(
customer=customer,
org_type=org_type,
org_website=website,
org_description="We help people.",
expected_total_users="20-35",
paid_users_count=paid_users,
paid_users_description="",
requested_plan=plan,
)
super().setUp()
# Set up some initial example data.
@@ -45,6 +73,23 @@ class TestRemoteServerSupportEndpoint(ZulipTestCase):
hostname=hostname, contact_email=f"admin@{hostname}", plan_type=1, uuid=uuid.uuid4()
)
# Add example sponsorship request data
add_sponsorship_request(
hostname="zulip-1.example.com",
org_type=OrgTypeEnum.Community.value,
website="",
paid_users="None",
plan=SponsoredPlanTypes.BUSINESS.value,
)
add_sponsorship_request(
hostname="zulip-2.example.com",
org_type=OrgTypeEnum.OpenSource.value,
website="example.org",
paid_users="",
plan=SponsoredPlanTypes.COMMUNITY.value,
)
def test_search(self) -> None:
self.login("cordelia")
@@ -70,6 +115,19 @@ class TestRemoteServerSupportEndpoint(ZulipTestCase):
self.assert_in_success_response(["<b>Max monthly messages</b>: 1000"], result)
self.assert_not_in_success_response(["<h3>zulip-2.example.com</h3>"], result)
# Sponsorship request information
self.assert_in_success_response(["<li><b>Organization type</b>: Community</li>"], result)
self.assert_in_success_response(
["<li><b>Organization website</b>: No website submitted</li>"], result
)
self.assert_in_success_response(["<li><b>Paid users</b>: None</li>"], result)
self.assert_in_success_response(["<li><b>Requested plan</b>: Business</li>"], result)
self.assert_in_success_response(
["<li><b>Organization description</b>: We help people.</li>"], result
)
self.assert_in_success_response(["<li><b>Estimated total users</b>: 20-35</li>"], result)
self.assert_in_success_response(["<li><b>Description of paid users</b>: </li>"], result)
with mock.patch(
"analytics.views.support.compute_max_monthly_messages", side_effect=MissingDataError
):
@@ -89,6 +147,35 @@ class TestRemoteServerSupportEndpoint(ZulipTestCase):
self.assert_in_success_response(["<b>Contact email</b>: admin@zulip-2.example.com"], result)
self.assert_not_in_success_response(["<h3>zulip-1.example.com</h3>"], result)
# Sponsorship request information
self.assert_in_success_response(
["<li><b>Organization type</b>: Open-source project</li>"], result
)
self.assert_in_success_response(
["<li><b>Organization website</b>: example.org</li>"], result
)
self.assert_in_success_response(["<li><b>Paid users</b>: </li>"], result)
self.assert_in_success_response(["<li><b>Requested plan</b>: Community</li>"], result)
self.assert_in_success_response(
["<li><b>Organization description</b>: We help people.</li>"], result
)
self.assert_in_success_response(["<li><b>Estimated total users</b>: 20-35</li>"], result)
self.assert_in_success_response(["<li><b>Description of paid users</b>: </li>"], result)
result = self.client_get("/activity/remote/support", {"q": "admin@zulip-3.example.com"})
self.assert_in_success_response(["<h3>zulip-3.example.com</h3>"], result)
self.assert_in_success_response(["<b>Contact email</b>: admin@zulip-3.example.com"], result)
self.assert_not_in_success_response(["<h3>zulip-1.example.com</h3>"], result)
# Sponsorship request information
self.assert_not_in_success_response(
["<li><b>Organization description</b>: We help people.</li>"], result
)
self.assert_not_in_success_response(
["<li><b>Estimated total users</b>: 20-35</li>"], result
)
self.assert_not_in_success_response(["<li><b>Description of paid users</b>: </li>"], result)
class TestSupportEndpoint(ZulipTestCase):
def test_search(self) -> None:
@@ -191,7 +278,7 @@ class TestSupportEndpoint(ZulipTestCase):
'<option value="deactivated" >Deactivated</option>',
'scrub-realm-button">',
'data-string-id="lear"',
"<b>Name</b>: Zulip Cloud Standard",
"<b>Plan name</b>: Zulip Cloud Standard",
"<b>Status</b>: Active",
"<b>Billing schedule</b>: Annual",
"<b>Licenses</b>: 2/10 (Manual)",
@@ -291,8 +378,8 @@ class TestSupportEndpoint(ZulipTestCase):
plan = CustomerPlan.objects.create(
customer=customer,
billing_cycle_anchor=now,
billing_schedule=CustomerPlan.ANNUAL,
tier=CustomerPlan.STANDARD,
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
tier=CustomerPlan.TIER_CLOUD_STANDARD,
price_per_license=8000,
next_invoice_date=add_months(now, 12),
)
@@ -356,55 +443,56 @@ class TestSupportEndpoint(ZulipTestCase):
check_zulip_realm_query_result(result)
check_lear_realm_query_result(result)
with mock.patch(
"analytics.views.support.timezone_now",
return_value=timezone_now() - timedelta(minutes=50),
):
self.client_post("/accounts/home/", {"email": self.nonreg_email("test")})
self.login("iago")
result = get_check_query_result(self.nonreg_email("test"), 1)
check_preregistration_user_query_result(result, self.nonreg_email("test"))
check_zulip_realm_query_result(result)
self.client_post("/accounts/home/", {"email": self.nonreg_email("test")})
self.login("iago")
create_invitation("Denmark", self.nonreg_email("test1"))
result = get_check_query_result(self.nonreg_email("test1"), 1)
check_preregistration_user_query_result(result, self.nonreg_email("test1"), invite=True)
check_zulip_realm_query_result(result)
def query_result_from_before(*args: Any) -> "TestHttpResponse":
with time_machine.travel((timezone_now() - timedelta(minutes=50)), tick=False):
return get_check_query_result(*args)
email = self.nonreg_email("alice")
self.submit_realm_creation_form(
email, realm_subdomain="custom-test", realm_name="Zulip test"
)
result = get_check_query_result(email, 1)
check_realm_creation_query_result(result, email)
result = query_result_from_before(self.nonreg_email("test"), 1)
check_preregistration_user_query_result(result, self.nonreg_email("test"))
check_zulip_realm_query_result(result)
invite_expires_in_minutes = 10 * 24 * 60
do_create_multiuse_invite_link(
self.example_user("hamlet"),
invited_as=1,
invite_expires_in_minutes=invite_expires_in_minutes,
)
result = get_check_query_result("zulip", 2)
check_multiuse_invite_link_query_result(result)
check_zulip_realm_query_result(result)
MultiuseInvite.objects.all().delete()
create_invitation("Denmark", self.nonreg_email("test1"))
result = query_result_from_before(self.nonreg_email("test1"), 1)
check_preregistration_user_query_result(result, self.nonreg_email("test1"), invite=True)
check_zulip_realm_query_result(result)
do_send_realm_reactivation_email(get_realm("zulip"), acting_user=None)
result = get_check_query_result("zulip", 2)
check_realm_reactivation_link_query_result(result)
check_zulip_realm_query_result(result)
email = self.nonreg_email("alice")
self.submit_realm_creation_form(
email, realm_subdomain="custom-test", realm_name="Zulip test"
)
result = query_result_from_before(email, 1)
check_realm_creation_query_result(result, email)
lear_nonreg_email = "newguy@lear.org"
self.client_post("/accounts/home/", {"email": lear_nonreg_email}, subdomain="lear")
result = get_check_query_result(lear_nonreg_email, 1)
check_preregistration_user_query_result(result, lear_nonreg_email)
check_lear_realm_query_result(result)
invite_expires_in_minutes = 10 * 24 * 60
do_create_multiuse_invite_link(
self.example_user("hamlet"),
invited_as=1,
invite_expires_in_minutes=invite_expires_in_minutes,
)
result = query_result_from_before("zulip", 2)
check_multiuse_invite_link_query_result(result)
check_zulip_realm_query_result(result)
MultiuseInvite.objects.all().delete()
self.login_user(lear_user)
create_invitation("general", "newguy2@lear.org", lear_realm)
result = get_check_query_result("newguy2@lear.org", 1, lear_realm.string_id)
check_preregistration_user_query_result(result, "newguy2@lear.org", invite=True)
check_lear_realm_query_result(result)
do_send_realm_reactivation_email(get_realm("zulip"), acting_user=None)
result = query_result_from_before("zulip", 2)
check_realm_reactivation_link_query_result(result)
check_zulip_realm_query_result(result)
lear_nonreg_email = "newguy@lear.org"
self.client_post("/accounts/home/", {"email": lear_nonreg_email}, subdomain="lear")
result = query_result_from_before(lear_nonreg_email, 1)
check_preregistration_user_query_result(result, lear_nonreg_email)
check_lear_realm_query_result(result)
self.login_user(lear_user)
create_invitation("general", "newguy2@lear.org", lear_realm)
result = query_result_from_before("newguy2@lear.org", 1, lear_realm.string_id)
check_preregistration_user_query_result(result, "newguy2@lear.org", invite=True)
check_lear_realm_query_result(result)
def test_get_org_type_display_name(self) -> None:
self.assertEqual(get_org_type_display_name(Realm.ORG_TYPES["business"]["id"]), "Business")
@@ -432,38 +520,50 @@ class TestSupportEndpoint(ZulipTestCase):
result,
)
@mock.patch("analytics.views.support.update_realm_billing_method")
def test_change_billing_method(self, m: mock.Mock) -> None:
def test_change_billing_modality(self) -> None:
realm = get_realm("zulip")
cordelia = self.example_user("cordelia")
self.login_user(cordelia)
result = self.client_post(
"/activity/support", {"realm_id": f"{cordelia.realm_id}", "plan_type": "2"}
"/activity/support",
{"realm_id": f"{realm.id}", "billing_method": "charge_automatically"},
)
self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], "/login/")
customer = Customer.objects.create(realm=realm, stripe_customer_id="cus_12345")
CustomerPlan.objects.create(
customer=customer,
status=CustomerPlan.ACTIVE,
billing_cycle_anchor=timezone_now(),
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
tier=CustomerPlan.TIER_CLOUD_STANDARD,
)
iago = self.example_user("iago")
self.login_user(iago)
result = self.client_post(
"/activity/support",
{"realm_id": f"{iago.realm_id}", "billing_method": "charge_automatically"},
{"realm_id": f"{realm.id}", "billing_modality": "charge_automatically"},
)
m.assert_called_once_with(get_realm("zulip"), charge_automatically=True, acting_user=iago)
self.assert_in_success_response(
["Billing method of zulip updated to charge automatically"], result
["Billing collection method of zulip updated to charge automatically"], result
)
m.reset_mock()
plan = get_current_plan_by_realm(realm)
assert plan is not None
self.assertEqual(plan.charge_automatically, True)
result = self.client_post(
"/activity/support", {"realm_id": f"{iago.realm_id}", "billing_method": "send_invoice"}
"/activity/support", {"realm_id": f"{realm.id}", "billing_modality": "send_invoice"}
)
m.assert_called_once_with(get_realm("zulip"), charge_automatically=False, acting_user=iago)
self.assert_in_success_response(
["Billing method of zulip updated to pay by invoice"], result
["Billing collection method of zulip updated to send invoice"], result
)
realm.refresh_from_db()
plan = get_current_plan_by_realm(realm)
assert plan is not None
self.assertEqual(plan.charge_automatically, False)
def test_change_realm_plan_type(self) -> None:
cordelia = self.example_user("cordelia")
@@ -484,7 +584,7 @@ class TestSupportEndpoint(ZulipTestCase):
)
m.assert_called_once_with(get_realm("zulip"), 2, acting_user=iago)
self.assert_in_success_response(
["Plan type of zulip changed from self-hosted to limited"], result
["Plan type of zulip changed from Self-hosted to Limited"], result
)
with mock.patch("analytics.views.support.do_change_realm_plan_type") as m:
@@ -493,7 +593,7 @@ class TestSupportEndpoint(ZulipTestCase):
)
m.assert_called_once_with(get_realm("zulip"), 10, acting_user=iago)
self.assert_in_success_response(
["Plan type of zulip changed from self-hosted to plus"], result
["Plan type of zulip changed from Self-hosted to Plus"], result
)
def test_change_org_type(self) -> None:
@@ -530,14 +630,15 @@ class TestSupportEndpoint(ZulipTestCase):
self.assertEqual(result["Location"], "/login/")
iago = self.example_user("iago")
self.login("iago")
self.login_user(iago)
with mock.patch("analytics.views.support.attach_discount_to_realm") as m:
result = self.client_post(
"/activity/support", {"realm_id": f"{lear_realm.id}", "discount": "25"}
)
m.assert_called_once_with(get_realm("lear"), 25, acting_user=iago)
self.assert_in_success_response(["Discount of lear changed to 25% from 0%"], result)
result = self.client_post(
"/activity/support", {"realm_id": f"{lear_realm.id}", "discount": "25"}
)
self.assert_in_success_response(["Discount for lear changed to 25% from 0%"], result)
customer = get_customer_by_realm(lear_realm)
assert customer is not None
self.assertEqual(customer.default_discount, Decimal(25))
def test_change_sponsorship_status(self) -> None:
lear_realm = get_realm("lear")
@@ -574,7 +675,10 @@ class TestSupportEndpoint(ZulipTestCase):
def test_approve_sponsorship(self) -> None:
support_admin = self.example_user("iago")
lear_realm = get_realm("lear")
update_realm_sponsorship_status(lear_realm, True, acting_user=support_admin)
billing_session = RealmBillingSession(
user=support_admin, realm=lear_realm, support_session=True
)
billing_session.update_customer_sponsorship_status(True)
king_user = self.lear_user("king")
king_user.role = UserProfile.ROLE_REALM_OWNER
king_user.save()
@@ -689,73 +793,84 @@ class TestSupportEndpoint(ZulipTestCase):
["Subdomain reserved. Please choose a different one."], result
)
def test_downgrade_realm(self) -> None:
def test_modify_plan_for_downgrade_at_end_of_billing_cycle(self) -> None:
realm = get_realm("zulip")
cordelia = self.example_user("cordelia")
self.login_user(cordelia)
result = self.client_post(
"/activity/support", {"realm_id": f"{cordelia.realm_id}", "plan_type": "2"}
"/activity/support",
{"realm_id": f"{realm.id}", "modify_plan": "downgrade_at_billing_cycle_end"},
)
self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], "/login/")
customer = Customer.objects.create(realm=realm, stripe_customer_id="cus_12345")
CustomerPlan.objects.create(
customer=customer,
status=CustomerPlan.ACTIVE,
billing_cycle_anchor=timezone_now(),
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
tier=CustomerPlan.TIER_CLOUD_STANDARD,
)
iago = self.example_user("iago")
self.login_user(iago)
with mock.patch("analytics.views.support.downgrade_at_the_end_of_billing_cycle") as m:
with self.assertLogs("corporate.stripe", "INFO") as m:
result = self.client_post(
"/activity/support",
{
"realm_id": f"{iago.realm_id}",
"realm_id": f"{realm.id}",
"modify_plan": "downgrade_at_billing_cycle_end",
},
)
m.assert_called_once_with(get_realm("zulip"))
self.assert_in_success_response(
["zulip marked for downgrade at the end of billing cycle"], result
)
plan = get_current_plan_by_realm(realm)
assert plan is not None
self.assertEqual(plan.status, CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE)
expected_log = f"INFO:corporate.stripe:Change plan status: Customer.id: {customer.id}, CustomerPlan.id: {plan.id}, status: {CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE}"
self.assertEqual(m.output[0], expected_log)
with mock.patch(
"analytics.views.support.downgrade_now_without_creating_additional_invoices"
) as m:
result = self.client_post(
"/activity/support",
{
"realm_id": f"{iago.realm_id}",
"modify_plan": "downgrade_now_without_additional_licenses",
},
)
m.assert_called_once_with(get_realm("zulip"))
self.assert_in_success_response(
["zulip downgraded without creating additional invoices"], result
)
def test_modify_plan_for_downgrade_now_without_additional_licenses(self) -> None:
realm = get_realm("zulip")
cordelia = self.example_user("cordelia")
self.login_user(cordelia)
result = self.client_post(
"/activity/support",
{"realm_id": f"{realm.id}", "modify_plan": "downgrade_now_without_additional_licenses"},
)
self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], "/login/")
with mock.patch(
"analytics.views.support.downgrade_now_without_creating_additional_invoices"
) as m1:
with mock.patch("analytics.views.support.void_all_open_invoices", return_value=1) as m2:
result = self.client_post(
"/activity/support",
{
"realm_id": f"{iago.realm_id}",
"modify_plan": "downgrade_now_void_open_invoices",
},
)
m1.assert_called_once_with(get_realm("zulip"))
m2.assert_called_once_with(get_realm("zulip"))
self.assert_in_success_response(
["zulip downgraded and voided 1 open invoices"], result
)
customer = Customer.objects.create(realm=realm, stripe_customer_id="cus_12345")
plan = CustomerPlan.objects.create(
customer=customer,
status=CustomerPlan.ACTIVE,
billing_cycle_anchor=timezone_now(),
billing_schedule=CustomerPlan.BILLING_SCHEDULE_ANNUAL,
tier=CustomerPlan.TIER_CLOUD_STANDARD,
)
with mock.patch("analytics.views.support.switch_realm_from_standard_to_plus_plan") as m:
result = self.client_post(
"/activity/support",
{
"realm_id": f"{iago.realm_id}",
"modify_plan": "upgrade_to_plus",
},
)
m.assert_called_once_with(get_realm("zulip"))
self.assert_in_success_response(["zulip upgraded to Plus"], result)
iago = self.example_user("iago")
self.login_user(iago)
result = self.client_post(
"/activity/support",
{
"realm_id": f"{iago.realm_id}",
"modify_plan": "downgrade_now_without_additional_licenses",
},
)
self.assert_in_success_response(
["zulip downgraded without creating additional invoices"], result
)
plan.refresh_from_db()
self.assertEqual(plan.status, CustomerPlan.ENDED)
realm.refresh_from_db()
self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_LIMITED)
def test_scrub_realm(self) -> None:
cordelia = self.example_user("cordelia")

View File

@@ -1,5 +1,6 @@
from typing import List, Union
from django.conf import settings
from django.conf.urls import include
from django.urls import path
from django.urls.resolvers import URLPattern, URLResolver
@@ -9,43 +10,47 @@ from analytics.views.installation_activity import (
get_integrations_activity,
)
from analytics.views.realm_activity import get_realm_activity
from analytics.views.remote_activity import get_remote_server_activity
from analytics.views.stats import (
get_chart_data,
get_chart_data_for_installation,
get_chart_data_for_realm,
get_chart_data_for_remote_installation,
get_chart_data_for_remote_realm,
get_chart_data_for_stream,
stats,
stats_for_installation,
stats_for_realm,
stats_for_remote_installation,
stats_for_remote_realm,
)
from analytics.views.support import remote_servers_support, support
from analytics.views.support import support
from analytics.views.user_activity import get_user_activity
from zerver.lib.rest import rest_path
i18n_urlpatterns: List[Union[URLPattern, URLResolver]] = [
# Server admin (user_profile.is_staff) visible stats pages
path("activity", get_installation_activity),
path("activity/remote", get_remote_server_activity),
path("activity/integrations", get_integrations_activity),
path("activity/support", support, name="support"),
path("activity/remote/support", remote_servers_support, name="remote_servers_support"),
path("realm_activity/<realm_str>/", get_realm_activity),
path("user_activity/<user_profile_id>/", get_user_activity),
path("stats/realm/<realm_str>/", stats_for_realm),
path("stats/installation", stats_for_installation),
path("stats/remote/<int:remote_server_id>/installation", stats_for_remote_installation),
path(
"stats/remote/<int:remote_server_id>/realm/<int:remote_realm_id>/", stats_for_remote_realm
),
# User-visible stats page
path("stats", stats, name="stats"),
]
if settings.ZILENCER_ENABLED:
from analytics.views.remote_activity import get_remote_server_activity
from analytics.views.stats import stats_for_remote_installation, stats_for_remote_realm
from analytics.views.support import remote_servers_support
i18n_urlpatterns += [
path("activity/remote", get_remote_server_activity),
path("stats/remote/<int:remote_server_id>/installation", stats_for_remote_installation),
path(
"stats/remote/<int:remote_server_id>/realm/<int:remote_realm_id>/",
stats_for_remote_realm,
),
path("activity/remote/support", remote_servers_support, name="remote_servers_support"),
]
# These endpoints are a part of the API (V1), which uses:
# * REST verbs
# * Basic auth (username:password is email:apiKey)
@@ -60,16 +65,25 @@ v1_api_and_json_patterns = [
rest_path("analytics/chart_data/stream/<stream_id>", GET=get_chart_data_for_stream),
rest_path("analytics/chart_data/realm/<realm_str>", GET=get_chart_data_for_realm),
rest_path("analytics/chart_data/installation", GET=get_chart_data_for_installation),
rest_path(
"analytics/chart_data/remote/<int:remote_server_id>/installation",
GET=get_chart_data_for_remote_installation,
),
rest_path(
"analytics/chart_data/remote/<int:remote_server_id>/realm/<int:remote_realm_id>",
GET=get_chart_data_for_remote_realm,
),
]
if settings.ZILENCER_ENABLED:
from analytics.views.stats import (
get_chart_data_for_remote_installation,
get_chart_data_for_remote_realm,
)
v1_api_and_json_patterns += [
rest_path(
"analytics/chart_data/remote/<int:remote_server_id>/installation",
GET=get_chart_data_for_remote_installation,
),
rest_path(
"analytics/chart_data/remote/<int:remote_server_id>/realm/<int:remote_realm_id>",
GET=get_chart_data_for_remote_realm,
),
]
i18n_urlpatterns += [
path("api/v1/", include(v1_api_and_json_patterns)),
path("json/", include(v1_api_and_json_patterns)),

View File

@@ -48,46 +48,22 @@ def make_table(
return content
def get_page(
query: Composable, cols: Sequence[str], title: str, totals_columns: Sequence[int] = []
) -> Dict[str, str]:
def fix_rows(
rows: List[List[Any]],
i: int,
fixup_func: Union[Callable[[str], Markup], Callable[[datetime], str]],
) -> None:
for row in rows:
row[i] = fixup_func(row[i])
def get_query_data(query: Composable) -> List[List[Any]]:
cursor = connection.cursor()
cursor.execute(query)
rows = cursor.fetchall()
rows = list(map(list, rows))
cursor.close()
def fix_rows(
i: int, fixup_func: Union[Callable[[str], Markup], Callable[[datetime], str]]
) -> None:
for row in rows:
row[i] = fixup_func(row[i])
total_row = []
for i, col in enumerate(cols):
if col == "Realm":
fix_rows(i, realm_activity_link)
elif col in ["Last time", "Last visit"]:
fix_rows(i, format_date_for_activity_reports)
elif col == "Hostname":
for row in rows:
row[i] = remote_installation_stats_link(row[0], row[i])
if len(totals_columns) > 0:
if i == 0:
total_row.append("Total")
elif i in totals_columns:
total_row.append(str(sum(row[i] for row in rows if row[i] is not None)))
else:
total_row.append("")
if len(totals_columns) > 0:
rows.insert(0, total_row)
content = make_table(title, cols, rows)
return dict(
content=content,
title=title,
)
return rows
def dictfetchall(cursor: CursorWrapper) -> List[Dict[str, Any]]:
@@ -128,7 +104,7 @@ def realm_support_link(realm_str: str) -> Markup:
support_url = reverse("support")
query = urlencode({"q": realm_str})
url = append_url_query_string(support_url, query)
return Markup('<a href="{url}">{realm_str}</a>').format(url=url, realm_str=realm_str)
return Markup('<a href="{url}"><i class="fa fa-gear"></i></a>').format(url=url)
def realm_url_link(realm_str: str) -> Markup:
@@ -137,13 +113,18 @@ def realm_url_link(realm_str: str) -> Markup:
return Markup('<a href="{url}"><i class="fa fa-home"></i></a>').format(url=url)
def remote_installation_stats_link(server_id: int, hostname: str) -> Markup:
def remote_installation_stats_link(server_id: int) -> Markup:
from analytics.views.stats import stats_for_remote_installation
url = reverse(stats_for_remote_installation, kwargs=dict(remote_server_id=server_id))
return Markup('<a href="{url}"><i class="fa fa-pie-chart"></i></a> {hostname}').format(
url=url, hostname=hostname
)
return Markup('<a href="{url}"><i class="fa fa-pie-chart"></i></a>').format(url=url)
def remote_installation_support_link(hostname: str) -> Markup:
support_url = reverse("remote_servers_support")
query = urlencode({"q": hostname})
url = append_url_query_string(support_url, query)
return Markup('<a href="{url}"><i class="fa fa-gear"></i></a>').format(url=url)
def get_user_activity_summary(records: Collection[UserActivity]) -> Dict[str, Any]:

View File

@@ -13,13 +13,16 @@ from psycopg2.sql import SQL
from analytics.lib.counts import COUNT_STATS
from analytics.views.activity_common import (
dictfetchall,
get_page,
fix_rows,
format_date_for_activity_reports,
get_query_data,
make_table,
realm_activity_link,
realm_stats_link,
realm_support_link,
realm_url_link,
)
from analytics.views.support import get_plan_name
from analytics.views.support import get_plan_type_string
from zerver.decorator import require_server_admin
from zerver.lib.request import has_request_variables
from zerver.models import Realm, get_org_type_display_name
@@ -201,7 +204,7 @@ def realm_summary_table() -> str:
realms_with_default_discount = get_realms_with_default_discount_dict()
for row in rows:
row["plan_type_string"] = get_plan_name(row["plan_type"])
row["plan_type_string"] = get_plan_type_string(row["plan_type"])
string_id = row["string_id"]
@@ -327,14 +330,20 @@ def get_integrations_activity(request: HttpRequest) -> HttpResponse:
"Last time",
]
integrations_activity = get_page(query, cols, title)
rows = get_query_data(query)
for i, col in enumerate(cols):
if col == "Realm":
fix_rows(rows, i, realm_activity_link)
elif col == "Last time":
fix_rows(rows, i, format_date_for_activity_reports)
content = make_table(title, cols, rows)
return render(
request,
"analytics/activity_details_template.html",
context=dict(
data=integrations_activity["content"],
title=integrations_activity["title"],
data=content,
title=title,
is_home=False,
),
)

View File

@@ -2,8 +2,17 @@ from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
from psycopg2.sql import SQL
from analytics.views.activity_common import get_page
from analytics.views.activity_common import (
fix_rows,
format_date_for_activity_reports,
get_query_data,
make_table,
remote_installation_stats_link,
remote_installation_support_link,
)
from corporate.lib.analytics import get_plan_data_by_remote_server
from zerver.decorator import require_server_admin
from zilencer.models import get_remote_server_guest_and_non_guest_count
@require_server_admin
@@ -12,32 +21,57 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse:
query = SQL(
"""
with icount as (
with icount_id as (
select
server_id,
max(value) as max_value,
max(end_time) as max_end_time
max(id) as max_count_id
from zilencer_remoteinstallationcount
where
property='active_users:is_bot:day'
and subgroup='false'
group by server_id
),
),
icount as (
select
icount_id.server_id,
value as latest_value,
end_time as latest_end_time
from icount_id
join zilencer_remoteinstallationcount
on max_count_id = zilencer_remoteinstallationcount.id
),
mobile_push_forwarded_count as (
select
server_id,
sum(coalesce(value, 0)) as push_forwarded_count
from zilencer_remoteinstallationcount
where
property = 'mobile_pushes_forwarded::day'
and end_time >= current_timestamp(0) - interval '7 days'
group by server_id
),
remote_push_devices as (
select server_id, count(distinct(user_id)) as push_user_count from zilencer_remotepushdevicetoken
select
server_id,
count(distinct(user_id, user_uuid)) as push_user_count
from zilencer_remotepushdevicetoken
group by server_id
)
select
rserver.id,
rserver.hostname,
rserver.contact_email,
max_value,
rserver.last_version,
latest_value,
push_user_count,
max_end_time
latest_end_time,
push_forwarded_count
from zilencer_remotezulipserver rserver
left join icount on icount.server_id = rserver.id
left join mobile_push_forwarded_count on mobile_push_forwarded_count.server_id = rserver.id
left join remote_push_devices on remote_push_devices.server_id = rserver.id
order by max_value DESC NULLS LAST, push_user_count DESC NULLS LAST
where not deactivated
order by latest_value DESC NULLS LAST, push_user_count DESC NULLS LAST
"""
)
@@ -45,15 +79,58 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse:
"ID",
"Hostname",
"Contact email",
"Zulip version",
"Analytics users",
"Mobile users",
"Last update time",
"Mobile pushes forwarded",
"Plan name",
"Plan status",
"ARR",
"Non guest users",
"Guest users",
"Links",
]
remote_servers = get_page(query, cols, title, totals_columns=[3, 4])
rows = get_query_data(query)
total_row = []
totals_columns = [4, 5]
plan_data_by_remote_server = get_plan_data_by_remote_server()
for row in rows:
# Add estimated revenue for server
server_plan_data = plan_data_by_remote_server.get(row[0])
if server_plan_data is None:
row.append("---")
row.append("---")
row.append("---")
else:
row.append(server_plan_data.current_plan_name)
row.append(server_plan_data.current_status)
row.append(server_plan_data.annual_revenue)
# Add user counts
remote_server_counts = get_remote_server_guest_and_non_guest_count(row[0])
row.append(remote_server_counts.non_guest_user_count)
row.append(remote_server_counts.guest_user_count)
# Add links
stats = remote_installation_stats_link(row[0])
support = remote_installation_support_link(row[1])
links = stats + " " + support
row.append(links)
for i, col in enumerate(cols):
if col == "Last update time":
fix_rows(rows, i, format_date_for_activity_reports)
if i == 0:
total_row.append("Total")
elif i in totals_columns:
total_row.append(str(sum(row[i] for row in rows if row[i] is not None)))
else:
total_row.append("")
rows.insert(0, total_row)
content = make_table(title, cols, rows)
return render(
request,
"analytics/activity_details_template.html",
context=dict(data=remote_servers["content"], title=remote_servers["title"], is_home=False),
context=dict(data=content, title=title, is_home=False),
)

View File

@@ -1,10 +1,8 @@
import urllib
from contextlib import suppress
from dataclasses import dataclass
from datetime import timedelta
from decimal import Decimal
from typing import Any, Dict, Iterable, List, Optional, Union
from urllib.parse import urlencode
from urllib.parse import urlencode, urlsplit
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -50,39 +48,40 @@ from zerver.views.invite import get_invitee_emails_set
if settings.ZILENCER_ENABLED:
from zilencer.lib.remote_counts import MissingDataError, compute_max_monthly_messages
from zilencer.models import RemoteZulipServer
from zilencer.models import RemoteRealm, RemoteZulipServer
if settings.BILLING_ENABLED:
from corporate.lib.stripe import (
RealmBillingSession,
downgrade_at_the_end_of_billing_cycle,
downgrade_now_without_creating_additional_invoices,
get_latest_seat_count,
switch_realm_from_standard_to_plus_plan,
void_all_open_invoices,
RemoteRealmBillingSession,
RemoteServerBillingSession,
SupportType,
SupportViewRequest,
)
from corporate.lib.support import (
approve_realm_sponsorship,
attach_discount_to_realm,
get_discount_for_realm,
update_realm_billing_method,
update_realm_sponsorship_status,
)
from corporate.models import (
Customer,
CustomerPlan,
get_current_plan_by_realm,
get_customer_by_realm,
PlanData,
SupportData,
get_current_plan_data_for_support_view,
get_customer_discount_for_support_view,
get_data_for_support_view,
)
from corporate.models import CustomerPlan
def get_plan_name(plan_type: int) -> str:
def get_plan_type_string(plan_type: int) -> str:
return {
Realm.PLAN_TYPE_SELF_HOSTED: "self-hosted",
Realm.PLAN_TYPE_LIMITED: "limited",
Realm.PLAN_TYPE_STANDARD: "standard",
Realm.PLAN_TYPE_STANDARD_FREE: "open source",
Realm.PLAN_TYPE_PLUS: "plus",
Realm.PLAN_TYPE_SELF_HOSTED: "Self-hosted",
Realm.PLAN_TYPE_LIMITED: "Limited",
Realm.PLAN_TYPE_STANDARD: "Standard",
Realm.PLAN_TYPE_STANDARD_FREE: "Standard free",
Realm.PLAN_TYPE_PLUS: "Plus",
RemoteZulipServer.PLAN_TYPE_SELF_MANAGED: "Self-managed",
RemoteZulipServer.PLAN_TYPE_SELF_MANAGED_LEGACY: CustomerPlan.name_from_tier(
CustomerPlan.TIER_SELF_HOSTED_LEGACY
),
RemoteZulipServer.PLAN_TYPE_COMMUNITY: "Community",
RemoteZulipServer.PLAN_TYPE_BUSINESS: "Business",
RemoteZulipServer.PLAN_TYPE_ENTERPRISE: "Enterprise",
}[plan_type]
@@ -135,7 +134,7 @@ VALID_MODIFY_PLAN_METHODS = [
"downgrade_at_billing_cycle_end",
"downgrade_now_without_additional_licenses",
"downgrade_now_void_open_invoices",
"upgrade_to_plus",
"upgrade_plan_tier",
]
VALID_STATUS_VALUES = [
@@ -143,20 +142,12 @@ VALID_STATUS_VALUES = [
"deactivated",
]
VALID_BILLING_METHODS = [
VALID_BILLING_MODALITY_VALUES = [
"send_invoice",
"charge_automatically",
]
@dataclass
class PlanData:
customer: Optional["Customer"] = None
current_plan: Optional["CustomerPlan"] = None
licenses: Optional[int] = None
licenses_used: Optional[int] = None
@require_server_admin
@has_request_variables
def support(
@@ -166,8 +157,8 @@ def support(
discount: Optional[Decimal] = REQ(default=None, converter=to_decimal),
new_subdomain: Optional[str] = REQ(default=None),
status: Optional[str] = REQ(default=None, str_validator=check_string_in(VALID_STATUS_VALUES)),
billing_method: Optional[str] = REQ(
default=None, str_validator=check_string_in(VALID_BILLING_METHODS)
billing_modality: Optional[str] = REQ(
default=None, str_validator=check_string_in(VALID_BILLING_MODALITY_VALUES)
),
sponsorship_pending: Optional[bool] = REQ(default=None, json_validator=check_bool),
approve_sponsorship: bool = REQ(default=False, json_validator=check_bool),
@@ -199,22 +190,42 @@ def support(
assert realm_id is not None
realm = Realm.objects.get(id=realm_id)
if plan_type is not None:
support_view_request = None
if approve_sponsorship:
support_view_request = SupportViewRequest(support_type=SupportType.approve_sponsorship)
elif sponsorship_pending is not None:
support_view_request = SupportViewRequest(
support_type=SupportType.update_sponsorship_status,
sponsorship_status=sponsorship_pending,
)
elif discount is not None:
support_view_request = SupportViewRequest(
support_type=SupportType.attach_discount,
discount=discount,
)
elif billing_modality is not None:
support_view_request = SupportViewRequest(
support_type=SupportType.update_billing_modality,
billing_modality=billing_modality,
)
elif modify_plan is not None:
support_view_request = SupportViewRequest(
support_type=SupportType.modify_plan,
plan_modification=modify_plan,
)
if modify_plan == "upgrade_plan_tier":
support_view_request["new_plan_tier"] = CustomerPlan.TIER_CLOUD_PLUS
elif plan_type is not None:
current_plan_type = realm.plan_type
do_change_realm_plan_type(realm, plan_type, acting_user=acting_user)
msg = f"Plan type of {realm.string_id} changed from {get_plan_name(current_plan_type)} to {get_plan_name(plan_type)} "
msg = f"Plan type of {realm.string_id} changed from {get_plan_type_string(current_plan_type)} to {get_plan_type_string(plan_type)} "
context["success_message"] = msg
elif org_type is not None:
current_realm_type = realm.org_type
do_change_realm_org_type(realm, org_type, acting_user=acting_user)
msg = f"Org type of {realm.string_id} changed from {get_org_type_display_name(current_realm_type)} to {get_org_type_display_name(org_type)} "
context["success_message"] = msg
elif discount is not None:
current_discount = get_discount_for_realm(realm) or 0
attach_discount_to_realm(realm, discount, acting_user=acting_user)
context[
"success_message"
] = f"Discount of {realm.string_id} changed to {discount}% from {current_discount}%."
elif new_subdomain is not None:
old_subdomain = realm.string_id
try:
@@ -238,51 +249,6 @@ def support(
elif status == "deactivated":
do_deactivate_realm(realm, acting_user=acting_user)
context["success_message"] = f"{realm.string_id} deactivated."
elif billing_method is not None:
if billing_method == "send_invoice":
update_realm_billing_method(
realm, charge_automatically=False, acting_user=acting_user
)
context[
"success_message"
] = f"Billing method of {realm.string_id} updated to pay by invoice."
elif billing_method == "charge_automatically":
update_realm_billing_method(
realm, charge_automatically=True, acting_user=acting_user
)
context[
"success_message"
] = f"Billing method of {realm.string_id} updated to charge automatically."
elif sponsorship_pending is not None:
if sponsorship_pending:
update_realm_sponsorship_status(realm, True, acting_user=acting_user)
context["success_message"] = f"{realm.string_id} marked as pending sponsorship."
else:
update_realm_sponsorship_status(realm, False, acting_user=acting_user)
context["success_message"] = f"{realm.string_id} is no longer pending sponsorship."
elif approve_sponsorship:
approve_realm_sponsorship(realm, acting_user=acting_user)
context["success_message"] = f"Sponsorship approved for {realm.string_id}"
elif modify_plan is not None:
if modify_plan == "downgrade_at_billing_cycle_end":
downgrade_at_the_end_of_billing_cycle(realm)
context[
"success_message"
] = f"{realm.string_id} marked for downgrade at the end of billing cycle"
elif modify_plan == "downgrade_now_without_additional_licenses":
downgrade_now_without_creating_additional_invoices(realm)
context[
"success_message"
] = f"{realm.string_id} downgraded without creating additional invoices"
elif modify_plan == "downgrade_now_void_open_invoices":
downgrade_now_without_creating_additional_invoices(realm)
voided_invoices_count = void_all_open_invoices(realm)
context[
"success_message"
] = f"{realm.string_id} downgraded and voided {voided_invoices_count} open invoices"
elif modify_plan == "upgrade_to_plus":
switch_realm_from_standard_to_plus_plan(realm)
context["success_message"] = f"{realm.string_id} upgraded to Plus"
elif scrub_realm:
do_scrub_realm(realm, acting_user=acting_user)
context["success_message"] = f"{realm.string_id} scrubbed."
@@ -293,6 +259,13 @@ def support(
do_delete_user_preserving_messages(user_profile_for_deletion)
context["success_message"] = f"{user_email} in {realm.subdomain} deleted."
if support_view_request is not None:
billing_session = RealmBillingSession(
user=acting_user, realm=realm, support_session=True
)
success_message = billing_session.process_support_view_request(support_view_request)
context["success_message"] = success_message
if query:
key_words = get_invitee_emails_set(query)
@@ -305,7 +278,7 @@ def support(
for key_word in key_words:
try:
URLValidator()(key_word)
parse_result = urllib.parse.urlparse(key_word)
parse_result = urlsplit(key_word)
hostname = parse_result.hostname
assert hostname is not None
if parse_result.port:
@@ -369,23 +342,9 @@ def support(
)
plan_data: Dict[int, PlanData] = {}
for realm in all_realms:
current_plan = get_current_plan_by_realm(realm)
plan_data[realm.id] = PlanData(
customer=get_customer_by_realm(realm),
current_plan=current_plan,
)
if current_plan is not None:
billing_session = RealmBillingSession(user=None, realm=realm)
new_plan, last_ledger_entry = billing_session.make_end_of_cycle_updates_if_needed(
current_plan, timezone_now()
)
if last_ledger_entry is not None:
if new_plan is not None:
plan_data[realm.id].current_plan = new_plan
else:
plan_data[realm.id].current_plan = current_plan
plan_data[realm.id].licenses = last_ledger_entry.licenses
plan_data[realm.id].licenses_used = get_latest_seat_count(realm)
billing_session = RealmBillingSession(user=None, realm=realm)
realm_plan_data = get_current_plan_data_for_support_view(billing_session)
plan_data[realm.id] = realm_plan_data
context["plan_data"] = plan_data
def get_realm_owner_emails_as_string(realm: Realm) -> str:
@@ -404,7 +363,7 @@ def support(
context["get_realm_owner_emails_as_string"] = get_realm_owner_emails_as_string
context["get_realm_admin_emails_as_string"] = get_realm_admin_emails_as_string
context["get_discount_for_realm"] = get_discount_for_realm
context["get_discount"] = get_customer_discount_for_support_view
context["get_org_type_display_name"] = get_org_type_display_name
context["realm_icon_url"] = realm_icon_url
context["Confirmation"] = Confirmation
@@ -421,7 +380,9 @@ def get_remote_servers_for_support(
if not email_to_search and not hostname_to_search:
return []
remote_servers_query = RemoteZulipServer.objects.order_by("id")
remote_servers_query = RemoteZulipServer.objects.order_by("id").prefetch_related(
"remoterealm_set"
)
if email_to_search:
remote_servers_query = remote_servers_query.filter(contact_email__iexact=email_to_search)
elif hostname_to_search:
@@ -433,8 +394,81 @@ def get_remote_servers_for_support(
@require_server_admin
@has_request_variables
def remote_servers_support(
request: HttpRequest, query: Optional[str] = REQ("q", default=None)
request: HttpRequest,
query: Optional[str] = REQ("q", default=None),
remote_server_id: Optional[int] = REQ(default=None, converter=to_non_negative_int),
remote_realm_id: Optional[int] = REQ(default=None, converter=to_non_negative_int),
discount: Optional[Decimal] = REQ(default=None, converter=to_decimal),
sponsorship_pending: Optional[bool] = REQ(default=None, json_validator=check_bool),
approve_sponsorship: bool = REQ(default=False, json_validator=check_bool),
billing_modality: Optional[str] = REQ(
default=None, str_validator=check_string_in(VALID_BILLING_MODALITY_VALUES)
),
modify_plan: Optional[str] = REQ(
default=None, str_validator=check_string_in(VALID_MODIFY_PLAN_METHODS)
),
) -> HttpResponse:
context: Dict[str, Any] = {}
if "success_message" in request.session:
context["success_message"] = request.session["success_message"]
del request.session["success_message"]
acting_user = request.user
assert isinstance(acting_user, UserProfile)
if settings.BILLING_ENABLED and request.method == "POST":
# We check that request.POST only has two keys in it:
# either the remote_server_id or a remote_realm_id,
# and a field to change.
keys = set(request.POST.keys())
if "csrfmiddlewaretoken" in keys:
keys.remove("csrfmiddlewaretoken")
if len(keys) != 2:
raise JsonableError(_("Invalid parameters"))
if remote_realm_id is not None:
remote_realm_support_request = True
remote_realm = RemoteRealm.objects.get(id=remote_realm_id)
else:
assert remote_server_id is not None
remote_realm_support_request = False
remote_server = RemoteZulipServer.objects.get(id=remote_server_id)
support_view_request = None
if approve_sponsorship:
support_view_request = SupportViewRequest(support_type=SupportType.approve_sponsorship)
elif sponsorship_pending is not None:
support_view_request = SupportViewRequest(
support_type=SupportType.update_sponsorship_status,
sponsorship_status=sponsorship_pending,
)
elif discount is not None:
support_view_request = SupportViewRequest(
support_type=SupportType.attach_discount,
discount=discount,
)
elif billing_modality is not None:
support_view_request = SupportViewRequest(
support_type=SupportType.update_billing_modality,
billing_modality=billing_modality,
)
elif modify_plan is not None:
support_view_request = SupportViewRequest(
support_type=SupportType.modify_plan,
plan_modification=modify_plan,
)
if support_view_request is not None:
if remote_realm_support_request:
success_message = RemoteRealmBillingSession(
support_staff=acting_user, remote_realm=remote_realm
).process_support_view_request(support_view_request)
else:
success_message = RemoteServerBillingSession(
support_staff=acting_user, remote_server=remote_server
).process_support_view_request(support_view_request)
context["success_message"] = success_message
email_to_search = None
hostname_to_search = None
if query:
@@ -447,7 +481,25 @@ def remote_servers_support(
email_to_search=email_to_search, hostname_to_search=hostname_to_search
)
remote_server_to_max_monthly_messages: Dict[int, Union[int, str]] = dict()
server_support_data: Dict[int, SupportData] = {}
realm_support_data: Dict[int, SupportData] = {}
remote_realms: Dict[int, List[RemoteRealm]] = {}
for remote_server in remote_servers:
# Get remote realms attached to remote server
remote_realms_for_server = list(
remote_server.remoterealm_set.exclude(is_system_bot_realm=True)
)
remote_realms[remote_server.id] = remote_realms_for_server
# Get plan data for remote realms
for remote_realm in remote_realms[remote_server.id]:
realm_billing_session = RemoteRealmBillingSession(remote_realm=remote_realm)
remote_realm_data = get_data_for_support_view(realm_billing_session)
realm_support_data[remote_realm.id] = remote_realm_data
# Get plan data for remote server
server_billing_session = RemoteServerBillingSession(remote_server=remote_server)
remote_server_data = get_data_for_support_view(server_billing_session)
server_support_data[remote_server.id] = remote_server_data
# Get max monthly messages
try:
remote_server_to_max_monthly_messages[remote_server.id] = compute_max_monthly_messages(
remote_server
@@ -455,11 +507,17 @@ def remote_servers_support(
except MissingDataError:
remote_server_to_max_monthly_messages[remote_server.id] = "Recent data missing"
context["remote_servers"] = remote_servers
context["remote_servers_support_data"] = server_support_data
context["remote_server_to_max_monthly_messages"] = remote_server_to_max_monthly_messages
context["remote_realms"] = remote_realms
context["remote_realms_support_data"] = realm_support_data
context["get_plan_type_name"] = get_plan_type_string
context["get_org_type_display_name"] = get_org_type_display_name
context["SPONSORED_PLAN_TYPE"] = RemoteZulipServer.PLAN_TYPE_COMMUNITY
return render(
request,
"analytics/remote_server_support.html",
context=dict(
remote_servers=remote_servers,
remote_server_to_max_monthly_messages=remote_server_to_max_monthly_messages,
),
context=context,
)

View File

@@ -18,8 +18,136 @@ clients should check the `zulip_feature_level` field, present in the
/register`](/api/register-queue) responses, to determine the API
format used by the Zulip server that they are interacting with.
## Changes in Zulip 9.0
Feature levels 238-239 are reserved for future use in 8.x maintenance
releases.
## Changes in Zulip 8.0
**Feature level 237**
No changes; feature level used for Zulip 8.0 release.
**Feature level 236**
* [`POST /messages`](/api/send-message), [`POST
/scheduled_messages`](/api/create-scheduled-message): The new
`read_by_sender` parameter lets the client override the heuristic
that determines whether the new message will be initially marked
read by its sender.
**Feature level 235**
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),
[`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings):
Added a new user setting, `automatically_follow_topics_where_mentioned`,
that allows the user to automatically follow topics where the user is mentioned.
**Feature level 234**
* Mobile push notifications now include a `realm_name` field.
* [`POST /mobile_push/test_notification`](/api/test-notify) now sends
a test notification with `test` rather than `test-by-device-token`
in the `event` field.
**Feature level 233**
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events):
Renamed the event type `hotspots` and the `hotspots` array field in it
to `onboarding_steps` as this event is sent to clients with remaining
onboarding steps data that includes hotspots and one-time notices to display.
Earlier, we had hotspots only. Added a `type` field to the objects in
the renamed `onboarding_steps` array to distinguish between the two type
of onboarding steps.
* `POST /users/me/onboarding_steps`: Added a new endpoint that
deprecates the `/users/me/hotspots` endpoint. Added support for
displaying one-time notices in addition to existing hotspots.
This is now used as a common endpoint to mark both types of
onboarding steps, i.e., 'hotspot' and 'one_time_notice'.
There is no compatibility support for `/users/me/hotspots` as
no client other than web app has this feature currently.
**Feature level 232**
* [`POST /register`](/api/register-queue): Added a new
`user_list_incomplete` [client
capability](/api/register-queue#parameter-client_capabilities)
controlling whether `realm_users` contains "Unknown user"
placeholder objects for users that the current user cannot access
due to a `can_access_all_users_group` policy.
* [`GET /events`](/api/get-events): The new `user_list_incomplete`
[client
capability](/api/register-queue#parameter-client_capabilities)
controls whether to send `realm_user` events with `op: "add"`
containing "Unknown user" placeholder objects to clients when a new
user is created that the client does not have access to due to a
`can_access_all_users_group` policy.
**Feature level 231**
* [`POST /register`](/api/register-queue):
`realm_push_notifications_enabled` now represents more accurately
whether push notifications are actually enabled via the mobile push
notifications service. Added
`realm_push_notifications_enabled_end_timestamp` field to realm
data.
* [`GET /events`](/api/get-events): A `realm` update event is now sent
whenever `push_notifications_enabled` or
`push_notifications_enabled_end_timestamp` changes.
**Feature level 230**
* [`GET /events`](/api/get-events): Added `has_trigger` field in
hotspots events to identify if a hotspot will activate only when
some specific event occurs.
**Feature level 229**
* [`PATCH /messages/{message_id}`](/api/update-message), [`POST
/messages`](/api/send-message): Topic wildcard mentions involving
large numbers of participants are now restricted by
`wildcard_mention_policy`. The server now uses the
`STREAM_WILDCARD_MENTION_NOT_ALLOWED` and
`TOPIC_WILDCARD_MENTION_NOT_ALLOWED` error codes when a message is
rejected because of `wildcard_mention_policy`.
**Feature level 228**
* [`GET /events`](/api/get-events): `realm_user` events with `op: "update"`
are now only sent to users who can access the modified user.
* [`GET /events`](/api/get-events): `presence` events are now only sent to
users who can access the user who comes back online if the
`CAN_ACCESS_ALL_USERS_GROUP_LIMITS_PRESENCE` server setting is set
to `true`.
* [`GET /events`](/api/get-events): `user_status` events are now only
sent to users who can access the modified user.
* [`GET /realm/presence`](/api/get-presence): The endpoint now returns
presence information of accessible users only if the
`CAN_ACCESS_ALL_USERS_GROUP_LIMITS_PRESENCE` server setting is set
to `true`.
* [`GET /events`](/api/get-events): `realm_user` events with `op: "add"`
are now also sent when a guest user gains access to a user.
* [`GET /events`](/api/get-events): `realm_user` events with `op: "remove"`
are now also sent when a guest user loses access to a user.
**Feature level 227**
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),
[`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings):
Added `DMs, mentions, and followed topics` option for `desktop_icon_count_display`
setting, and renumbered the options.
The total unread count of DMs, mentions, and followed topics appears in
desktop sidebar and browser tab when this option is configured.
**Feature level 226**
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events),
@@ -96,6 +224,10 @@ format used by the Zulip server that they are interacting with.
[`PATCH /settings`](/api/update-settings): Renamed `default_view` and
`escape_navigates_to_default_view` settings to `web_home_view` and
`web_escape_navigates_to_home_view` respectively.
* [`POST /user_topics`](/api/update-user-topic), [`POST
register`](/api/register-queue), [`GET /events`](/api/get-events):
Added followed as a supported value for visibility policies in
`user_topic` objects.
**Feature level 218**

View File

@@ -105,6 +105,12 @@ help center because they are primarily useful to API clients:
* `dm-including:1234`: Search all direct messages (1-on-1 and group)
that include you and user ID `1234`.
!!! tip ""
A user ID can be found by [viewing a user's profile][view-profile]
in the web or desktop apps. A stream ID can be found when [browsing
streams][browse-streams] in the web app via the URL.
The operands for these search options must be encoded either as an
integer ID or a JSON list of integer IDs. For example, to query
messages sent by a user 1234 to a direct message thread with yourself,
@@ -122,3 +128,6 @@ user 1234, and user 5678, the correct JSON-encoded query is:
}
]
```
[view-profile]: /help/view-someones-profile
[browse-streams]: /help/browse-and-subscribe-to-streams

View File

@@ -179,6 +179,11 @@ Parameters accepted in the URL include:
name. By default the integration will send direct messages to the
bot's owner.
!!! tip ""
A stream ID can be found when [browsing streams][browse-streams]
in the web app via the URL.
* `topic`: The topic in the specified stream for the integration to
send notifications to. The topic should also be
[URL-encoded][url-encoder]. By default the integration will have a
@@ -189,5 +194,6 @@ Parameters accepted in the URL include:
notification. For details, see the integration's [integration
documentation](/integrations) page.
[browse-streams]: /help/browse-and-subscribe-to-streams
[add-bot]: /help/add-a-bot-or-integration
[url-encoder]: https://www.urlencoder.org/

View File

@@ -1,10 +1,10 @@
# Copyright: (c) 2008, Jarek Zgoda <jarek.zgoda@gmail.com>
__revision__ = "$Id: models.py 28 2009-10-22 15:03:02Z jarek.zgoda $"
import datetime
import secrets
from base64 import b32encode
from typing import List, Mapping, Optional, Union
from datetime import timedelta
from typing import List, Mapping, Optional, Union, cast
from urllib.parse import urljoin
from django.conf import settings
@@ -30,6 +30,12 @@ from zerver.models import (
UserProfile,
)
if settings.ZILENCER_ENABLED:
from zilencer.models import (
PreregistrationRemoteRealmBillingUser,
PreregistrationRemoteServerBillingUser,
)
class ConfirmationKeyError(Exception):
WRONG_LENGTH = 1
@@ -56,7 +62,7 @@ def generate_key() -> str:
return b32encode(secrets.token_bytes(15)).decode().lower()
ConfirmationObjT: TypeAlias = Union[
NoZilencerConfirmationObjT: TypeAlias = Union[
MultiuseInvite,
PreregistrationRealm,
PreregistrationUser,
@@ -64,6 +70,13 @@ ConfirmationObjT: TypeAlias = Union[
UserProfile,
RealmReactivationStatus,
]
ZilencerConfirmationObjT: TypeAlias = Union[
NoZilencerConfirmationObjT,
"PreregistrationRemoteServerBillingUser",
"PreregistrationRemoteRealmBillingUser",
]
ConfirmationObjT = Union[NoZilencerConfirmationObjT, ZilencerConfirmationObjT]
def get_object_from_key(
@@ -117,15 +130,20 @@ def create_confirmation_link(
*,
validity_in_minutes: Union[Optional[int], UnspecifiedValue] = UnspecifiedValue(),
url_args: Mapping[str, str] = {},
realm_creation: bool = False,
no_associated_realm_object: bool = False,
) -> str:
# validity_in_minutes is an override for the default values which are
# determined by the confirmation_type - its main purpose is for use
# in tests which may want to have control over the exact expiration time.
key = generate_key()
if realm_creation:
# Some confirmation objects, like those for realm creation or those used
# for the self-hosted management flows, are not associated with a realm
# hosted by this Zulip server.
if no_associated_realm_object:
realm = None
else:
obj = cast(NoZilencerConfirmationObjT, obj)
assert not isinstance(obj, PreregistrationRealm)
realm = obj.realm
@@ -136,11 +154,9 @@ def create_confirmation_link(
expiry_date = None
else:
assert validity_in_minutes is not None
expiry_date = current_time + datetime.timedelta(minutes=validity_in_minutes)
expiry_date = current_time + timedelta(minutes=validity_in_minutes)
else:
expiry_date = current_time + datetime.timedelta(
days=_properties[confirmation_type].validity_in_days
)
expiry_date = current_time + timedelta(days=_properties[confirmation_type].validity_in_days)
Confirmation.objects.create(
content_object=obj,
@@ -185,6 +201,8 @@ class Confirmation(models.Model):
MULTIUSE_INVITE = 6
REALM_CREATION = 7
REALM_REACTIVATION = 8
REMOTE_SERVER_BILLING_LEGACY_LOGIN = 9
REMOTE_REALM_BILLING_LEGACY_LOGIN = 10
type = models.PositiveSmallIntegerField()
class Meta:
@@ -221,6 +239,13 @@ _properties = {
Confirmation.REALM_CREATION: ConfirmationType("get_prereg_key_and_redirect"),
Confirmation.REALM_REACTIVATION: ConfirmationType("realm_reactivation"),
}
if settings.ZILENCER_ENABLED:
_properties[Confirmation.REMOTE_SERVER_BILLING_LEGACY_LOGIN] = ConfirmationType(
"remote_billing_legacy_server_from_login_confirmation_link"
)
_properties[Confirmation.REMOTE_REALM_BILLING_LEGACY_LOGIN] = ConfirmationType(
"remote_realm_billing_from_login_confirmation_link"
)
def one_click_unsubscribe_link(user_profile: UserProfile, email_type: str) -> str:

View File

@@ -1,13 +1,25 @@
from dataclasses import dataclass
from decimal import Decimal
from typing import Any, Dict
from django.utils.timezone import now as timezone_now
from corporate.lib.stripe import renewal_amount
from corporate.lib.stripe import (
RealmBillingSession,
RemoteRealmBillingSession,
RemoteServerBillingSession,
)
from corporate.models import Customer, CustomerPlan
from zerver.lib.utils import assert_is_not_none
@dataclass
class RemoteActivityPlanData:
current_status: str
current_plan_name: str
annual_revenue: int
def get_realms_with_default_discount_dict() -> Dict[str, Decimal]:
realms_with_default_discount: Dict[str, Any] = {}
customers = (
@@ -31,9 +43,52 @@ def estimate_annual_recurring_revenue_by_realm() -> Dict[str, int]: # nocoverag
if plan.customer.realm is not None:
# TODO: figure out what to do for plans that don't automatically
# renew, but which probably will renew
renewal_cents = renewal_amount(plan, timezone_now())
if plan.billing_schedule == CustomerPlan.MONTHLY:
renewal_cents = RealmBillingSession(
realm=plan.customer.realm
).get_customer_plan_renewal_amount(plan, timezone_now())
if plan.billing_schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY:
renewal_cents *= 12
# TODO: Decimal stuff
annual_revenue[plan.customer.realm.string_id] = int(renewal_cents / 100)
return annual_revenue
def get_plan_data_by_remote_server() -> Dict[int, RemoteActivityPlanData]: # nocoverage
remote_server_plan_data: Dict[int, RemoteActivityPlanData] = {}
for plan in CustomerPlan.objects.filter(
status__lt=CustomerPlan.LIVE_STATUS_THRESHOLD, customer__realm__isnull=True
).select_related("customer__remote_server", "customer__remote_realm"):
renewal_cents = 0
server_id = None
if plan.customer.remote_server is not None:
server_id = plan.customer.remote_server.id
renewal_cents = RemoteServerBillingSession(
remote_server=plan.customer.remote_server
).get_customer_plan_renewal_amount(plan, timezone_now())
elif plan.customer.remote_realm is not None:
server_id = plan.customer.remote_realm.server.id
renewal_cents = RemoteRealmBillingSession(
remote_realm=plan.customer.remote_realm
).get_customer_plan_renewal_amount(plan, timezone_now())
assert server_id is not None
if plan.billing_schedule == CustomerPlan.BILLING_SCHEDULE_MONTHLY:
renewal_cents *= 12
current_data = remote_server_plan_data.get(server_id)
if current_data is not None:
current_revenue = remote_server_plan_data[server_id].annual_revenue
remote_server_plan_data[server_id] = RemoteActivityPlanData(
current_status="Multiple plans",
current_plan_name="See support view",
annual_revenue=current_revenue + int(renewal_cents / 100),
)
else:
remote_server_plan_data[server_id] = RemoteActivityPlanData(
current_status=plan.get_plan_status_as_text(),
current_plan_name=plan.name,
annual_revenue=int(renewal_cents / 100),
)
return remote_server_plan_data

187
corporate/lib/decorator.py Normal file
View File

@@ -0,0 +1,187 @@
from functools import wraps
from typing import Callable, Optional
from urllib.parse import urlencode, urljoin
from django.conf import settings
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from typing_extensions import Concatenate, ParamSpec
from corporate.lib.remote_billing_util import (
RemoteBillingIdentityExpiredError,
get_remote_realm_and_user_from_session,
get_remote_server_and_user_from_session,
)
from corporate.lib.stripe import RemoteRealmBillingSession, RemoteServerBillingSession
from zerver.lib.exceptions import RemoteBillingAuthenticationError
from zerver.lib.subdomains import get_subdomain
from zerver.lib.url_encoding import append_url_query_string
from zilencer.models import RemoteRealm
ParamT = ParamSpec("ParamT")
def is_self_hosting_management_subdomain(request: HttpRequest) -> bool:
subdomain = get_subdomain(request)
return subdomain == settings.SELF_HOSTING_MANAGEMENT_SUBDOMAIN
def self_hosting_management_endpoint(
view_func: Callable[Concatenate[HttpRequest, ParamT], HttpResponse]
) -> Callable[Concatenate[HttpRequest, ParamT], HttpResponse]:
@wraps(view_func)
def _wrapped_view_func(
request: HttpRequest, /, *args: ParamT.args, **kwargs: ParamT.kwargs
) -> HttpResponse:
if not is_self_hosting_management_subdomain(request): # nocoverage
return render(request, "404.html", status=404)
return view_func(request, *args, **kwargs)
return _wrapped_view_func
def authenticated_remote_realm_management_endpoint(
view_func: Callable[Concatenate[HttpRequest, RemoteRealmBillingSession, ParamT], HttpResponse]
) -> Callable[Concatenate[HttpRequest, ParamT], HttpResponse]:
@wraps(view_func)
def _wrapped_view_func(
request: HttpRequest,
/,
*args: ParamT.args,
**kwargs: ParamT.kwargs,
) -> HttpResponse:
if not is_self_hosting_management_subdomain(request): # nocoverage
return render(request, "404.html", status=404)
realm_uuid = kwargs.get("realm_uuid")
if realm_uuid is not None and not isinstance(realm_uuid, str): # nocoverage
raise TypeError("realm_uuid must be a string or None")
try:
remote_realm, remote_billing_user = get_remote_realm_and_user_from_session(
request, realm_uuid
)
except RemoteBillingIdentityExpiredError as e:
# The user had an authenticated session with an identity_dict,
# but it expired.
# We want to redirect back to the start of their login flow
# at their {realm.host}/self-hosted-billing/ with a proper
# next parameter to take them back to what they're trying
# to access after re-authing.
# Note: Theoretically we could take the realm_uuid from the request
# path or params to figure out the remote_realm.host for the redirect,
# but that would mean leaking that .host value to anyone who knows
# the uuid. Therefore we limit ourselves to taking the realm_uuid
# from the identity_dict - since that proves that the user at least
# previously was successfully authenticated as a billing admin of that
# realm.
realm_uuid = e.realm_uuid
server_uuid = e.server_uuid
uri_scheme = e.uri_scheme
if realm_uuid is None:
# This doesn't make sense - if get_remote_realm_and_user_from_session
# found an expired identity dict, it should have had a realm_uuid.
raise AssertionError
assert server_uuid is not None, "identity_dict with realm_uuid must have server_uuid"
assert uri_scheme is not None, "identity_dict with realm_uuid must have uri_scheme"
try:
remote_realm = RemoteRealm.objects.get(uuid=realm_uuid, server__uuid=server_uuid)
except RemoteRealm.DoesNotExist:
# This should be impossible - unless the RemoteRealm existed and somehow the row
# was deleted.
raise AssertionError
# Using EXTERNAL_URI_SCHEME means we'll do https:// in production, which is
# the sane default - while having http:// in development, which will allow
# these redirects to work there for testing.
url = urljoin(uri_scheme + remote_realm.host, "/self-hosted-billing/")
page_type = get_next_page_param_from_request_path(request)
if page_type is not None:
query = urlencode({"next_page": page_type})
url = append_url_query_string(url, query)
return HttpResponseRedirect(url)
billing_session = RemoteRealmBillingSession(
remote_realm, remote_billing_user=remote_billing_user
)
return view_func(request, billing_session)
return _wrapped_view_func
def get_next_page_param_from_request_path(request: HttpRequest) -> Optional[str]:
# Our endpoint URLs in this subsystem end with something like
# /sponsorship or /plans etc.
# Therefore we can use this nice property to figure out easily what
# kind of page the user is trying to access and find the right value
# for the `next` query parameter.
path = request.path
if path.endswith("/"):
path = path[:-1]
page_type = path.split("/")[-1]
from corporate.views.remote_billing_page import (
VALID_NEXT_PAGES as REMOTE_BILLING_VALID_NEXT_PAGES,
)
if page_type in REMOTE_BILLING_VALID_NEXT_PAGES:
return page_type
# Should be impossible to reach here. If this is reached, it must mean
# we have a registered endpoint that doesn't have a VALID_NEXT_PAGES entry
# or the parsing logic above is failing.
raise AssertionError(f"Unknown page type: {page_type}")
def authenticated_remote_server_management_endpoint(
view_func: Callable[Concatenate[HttpRequest, RemoteServerBillingSession, ParamT], HttpResponse]
) -> Callable[Concatenate[HttpRequest, ParamT], HttpResponse]:
@wraps(view_func)
def _wrapped_view_func(
request: HttpRequest,
/,
*args: ParamT.args,
**kwargs: ParamT.kwargs,
) -> HttpResponse:
if not is_self_hosting_management_subdomain(request): # nocoverage
return render(request, "404.html", status=404)
server_uuid = kwargs.get("server_uuid")
if not isinstance(server_uuid, str):
raise TypeError("server_uuid must be a string") # nocoverage
try:
remote_server, remote_billing_user = get_remote_server_and_user_from_session(
request, server_uuid=server_uuid
)
if remote_billing_user is None:
# This should only be possible if the user hasn't finished the confirmation flow
# and doesn't have a fully authenticated session yet. They should not be attempting
# to access this endpoint yet.
raise RemoteBillingAuthenticationError
except (RemoteBillingIdentityExpiredError, RemoteBillingAuthenticationError):
# In this flow, we can only redirect to our local "legacy server flow login" page.
# That means that we can do it universally whether the user has an expired
# identity_dict, or just lacks any form of authentication info at all - there
# are no security concerns since this is just a local redirect.
url = reverse("remote_billing_legacy_server_login")
page_type = get_next_page_param_from_request_path(request)
if page_type is not None:
query = urlencode({"next_page": page_type})
url = append_url_query_string(url, query)
return HttpResponseRedirect(url)
assert remote_billing_user is not None
billing_session = RemoteServerBillingSession(
remote_server, remote_billing_user=remote_billing_user
)
return view_func(request, billing_session)
return _wrapped_view_func

View File

@@ -0,0 +1,182 @@
import logging
from typing import Literal, Optional, Tuple, TypedDict, Union, cast
from django.http import HttpRequest
from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _
from zerver.lib.exceptions import JsonableError, RemoteBillingAuthenticationError
from zerver.lib.timestamp import datetime_to_timestamp
from zilencer.models import (
RemoteRealm,
RemoteRealmBillingUser,
RemoteServerBillingUser,
RemoteZulipServer,
)
billing_logger = logging.getLogger("corporate.stripe")
# The sessions are relatively short-lived, so that we can avoid issues
# with users who have their privileges revoked on the remote server
# maintaining access to the billing page for too long.
REMOTE_BILLING_SESSION_VALIDITY_SECONDS = 2 * 60 * 60
class RemoteBillingUserDict(TypedDict):
user_uuid: str
user_email: str
user_full_name: str
class RemoteBillingIdentityDict(TypedDict):
user: RemoteBillingUserDict
remote_server_uuid: str
remote_realm_uuid: str
remote_billing_user_id: Optional[int]
authenticated_at: int
uri_scheme: Literal["http://", "https://"]
next_page: Optional[str]
class LegacyServerIdentityDict(TypedDict):
# Currently this has only one field. We can extend this
# to add more information as appropriate.
remote_server_uuid: str
remote_billing_user_id: Optional[int]
authenticated_at: int
class RemoteBillingIdentityExpiredError(Exception):
def __init__(
self,
*,
realm_uuid: Optional[str] = None,
server_uuid: Optional[str] = None,
uri_scheme: Optional[Literal["http://", "https://"]] = None,
) -> None:
self.realm_uuid = realm_uuid
self.server_uuid = server_uuid
self.uri_scheme = uri_scheme
def get_identity_dict_from_session(
request: HttpRequest,
*,
realm_uuid: Optional[str],
server_uuid: Optional[str],
) -> Optional[Union[RemoteBillingIdentityDict, LegacyServerIdentityDict]]:
if not (realm_uuid or server_uuid):
return None
identity_dicts = request.session.get("remote_billing_identities")
if identity_dicts is None:
return None
if realm_uuid is not None:
result = identity_dicts.get(f"remote_realm:{realm_uuid}")
else:
assert server_uuid is not None
result = identity_dicts.get(f"remote_server:{server_uuid}")
if result is None:
return None
if (
datetime_to_timestamp(timezone_now()) - result["authenticated_at"]
> REMOTE_BILLING_SESSION_VALIDITY_SECONDS
):
# In this case we raise, because callers want to catch this as an explicitly
# different scenario from the user not being authenticated, to handle it nicely
# by redirecting them to their login page.
raise RemoteBillingIdentityExpiredError(
realm_uuid=result.get("remote_realm_uuid"),
server_uuid=result.get("remote_server_uuid"),
uri_scheme=result.get("uri_scheme"),
)
return result
def get_remote_realm_and_user_from_session(
request: HttpRequest,
realm_uuid: Optional[str],
) -> Tuple[RemoteRealm, RemoteRealmBillingUser]:
# Cannot use isinstance with TypeDicts, to make mypy know
# which of the TypedDicts in the Union this is - so just cast it.
identity_dict = cast(
Optional[RemoteBillingIdentityDict],
get_identity_dict_from_session(request, realm_uuid=realm_uuid, server_uuid=None),
)
if identity_dict is None:
raise RemoteBillingAuthenticationError
remote_server_uuid = identity_dict["remote_server_uuid"]
remote_realm_uuid = identity_dict["remote_realm_uuid"]
try:
remote_realm = RemoteRealm.objects.get(
uuid=remote_realm_uuid, server__uuid=remote_server_uuid
)
except RemoteRealm.DoesNotExist:
raise AssertionError(
"The remote realm is missing despite being in the RemoteBillingIdentityDict"
)
if (
remote_realm.registration_deactivated
or remote_realm.realm_deactivated
or remote_realm.server.deactivated
):
raise JsonableError(_("Registration is deactivated"))
remote_billing_user_id = identity_dict["remote_billing_user_id"]
# We only put IdentityDicts with remote_billing_user_id in the session in this flow,
# because the RemoteRealmBillingUser already exists when this is inserted into the session
# at the end of authentication.
assert remote_billing_user_id is not None
try:
remote_billing_user = RemoteRealmBillingUser.objects.get(
id=remote_billing_user_id, remote_realm=remote_realm
)
except RemoteRealmBillingUser.DoesNotExist:
raise AssertionError
return remote_realm, remote_billing_user
def get_remote_server_and_user_from_session(
request: HttpRequest,
server_uuid: str,
) -> Tuple[RemoteZulipServer, Optional[RemoteServerBillingUser]]:
identity_dict: Optional[LegacyServerIdentityDict] = get_identity_dict_from_session(
request, realm_uuid=None, server_uuid=server_uuid
)
if identity_dict is None:
raise RemoteBillingAuthenticationError
remote_server_uuid = identity_dict["remote_server_uuid"]
try:
remote_server = RemoteZulipServer.objects.get(uuid=remote_server_uuid)
except RemoteZulipServer.DoesNotExist:
raise JsonableError(_("Invalid remote server."))
if remote_server.deactivated:
raise JsonableError(_("Registration is deactivated"))
remote_billing_user_id = identity_dict.get("remote_billing_user_id")
if remote_billing_user_id is None:
return remote_server, None
try:
remote_billing_user = RemoteServerBillingUser.objects.get(
id=remote_billing_user_id, remote_server=remote_server
)
except RemoteServerBillingUser.DoesNotExist:
remote_billing_user = None
return remote_server, remote_billing_user

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,18 @@
import logging
from contextlib import suppress
from typing import Any, Callable, Dict, Union
from typing import Any, Callable, Dict, Optional, Union
import stripe
from django.conf import settings
from corporate.lib.stripe import (
BillingError,
InvalidPlanUpgradeError,
RealmBillingSession,
RemoteRealmBillingSession,
RemoteServerBillingSession,
UpgradeWithExistingPlanError,
ensure_customer_does_not_have_active_plan,
)
from corporate.models import CustomerPlan, Event, PaymentIntent, Session
from corporate.models import Customer, CustomerPlan, Event, PaymentIntent, Session
from zerver.models import get_active_user_profile_by_id_in_realm
billing_logger = logging.getLogger("corporate.stripe")
@@ -62,6 +63,20 @@ def error_handler(
return wrapper
def get_billing_session_for_stripe_webhook(
customer: Customer, user_id: Optional[str]
) -> Union[RealmBillingSession, RemoteRealmBillingSession, RemoteServerBillingSession]:
if customer.remote_realm is not None: # nocoverage
return RemoteRealmBillingSession(customer.remote_realm)
elif customer.remote_server is not None: # nocoverage
return RemoteServerBillingSession(customer.remote_server)
else:
assert user_id is not None
assert customer.realm is not None
user = get_active_user_profile_by_id_in_realm(int(user_id), customer.realm)
return RealmBillingSession(user)
@error_handler
def handle_checkout_session_completed_event(
stripe_session: stripe.checkout.Session, session: Session
@@ -70,47 +85,18 @@ def handle_checkout_session_completed_event(
session.save()
assert isinstance(stripe_session.setup_intent, str)
stripe_setup_intent = stripe.SetupIntent.retrieve(stripe_session.setup_intent)
assert session.customer.realm is not None
assert stripe_session.metadata is not None
user_id = stripe_session.metadata.get("user_id")
assert user_id is not None
user = get_active_user_profile_by_id_in_realm(int(user_id), session.customer.realm)
billing_session = RealmBillingSession(user)
stripe_setup_intent = stripe.SetupIntent.retrieve(stripe_session.setup_intent)
billing_session = get_billing_session_for_stripe_webhook(
session.customer, stripe_session.metadata.get("user_id")
)
payment_method = stripe_setup_intent.payment_method
assert isinstance(payment_method, (str, type(None)))
if session.type in [
Session.UPGRADE_FROM_BILLING_PAGE,
Session.RETRY_UPGRADE_WITH_ANOTHER_PAYMENT_METHOD,
Session.CARD_UPDATE_FROM_BILLING_PAGE,
Session.CARD_UPDATE_FROM_UPGRADE_PAGE,
]:
ensure_customer_does_not_have_active_plan(session.customer)
billing_session.update_or_create_stripe_customer(payment_method)
assert session.payment_intent is not None
session.payment_intent.status = PaymentIntent.PROCESSING
session.payment_intent.last_payment_error = ()
session.payment_intent.save(update_fields=["status", "last_payment_error"])
with suppress(stripe.error.CardError):
stripe.PaymentIntent.confirm(
session.payment_intent.stripe_payment_intent_id,
payment_method=payment_method,
off_session=True,
)
elif session.type in [
Session.FREE_TRIAL_UPGRADE_FROM_BILLING_PAGE,
Session.FREE_TRIAL_UPGRADE_FROM_ONBOARDING_PAGE,
]:
ensure_customer_does_not_have_active_plan(session.customer)
billing_session.update_or_create_stripe_customer(payment_method)
billing_session.process_initial_upgrade(
CustomerPlan.STANDARD,
int(stripe_session.metadata["licenses"]),
stripe_session.metadata["license_management"] == "automatic",
int(stripe_session.metadata["billing_schedule"]),
charge_automatically=True,
free_trial=True,
)
elif session.type in [Session.CARD_UPDATE_FROM_BILLING_PAGE]:
billing_session.update_or_create_stripe_customer(payment_method)
@@ -121,10 +107,6 @@ def handle_payment_intent_succeeded_event(
payment_intent.status = PaymentIntent.SUCCEEDED
payment_intent.save()
metadata: Dict[str, Any] = stripe_payment_intent.metadata
assert payment_intent.customer.realm is not None
user_id = metadata.get("user_id")
assert user_id is not None
user = get_active_user_profile_by_id_in_realm(user_id, payment_intent.customer.realm)
description = ""
charge: stripe.Charge
@@ -141,50 +123,30 @@ def handle_payment_intent_succeeded_event(
description=description,
discountable=False,
)
billing_session = get_billing_session_for_stripe_webhook(
payment_intent.customer, metadata.get("user_id")
)
plan_tier = int(metadata["plan_tier"])
try:
ensure_customer_does_not_have_active_plan(payment_intent.customer)
except UpgradeWithExistingPlanError as e:
billing_session.ensure_current_plan_is_upgradable(payment_intent.customer, plan_tier)
except (UpgradeWithExistingPlanError, InvalidPlanUpgradeError) as e:
stripe_invoice = stripe.Invoice.create(
auto_advance=True,
collection_method="charge_automatically",
customer=stripe_payment_intent.customer,
days_until_due=None,
statement_descriptor="Cloud Standard Credit",
statement_descriptor=CustomerPlan.name_from_tier(plan_tier).replace("Zulip ", "")
+ " Credit",
)
stripe.Invoice.finalize_invoice(stripe_invoice)
raise e
billing_session = RealmBillingSession(user)
billing_session.process_initial_upgrade(
CustomerPlan.STANDARD,
plan_tier,
int(metadata["licenses"]),
metadata["license_management"] == "automatic",
int(metadata["billing_schedule"]),
True,
False,
billing_session.get_remote_server_legacy_plan(payment_intent.customer),
)
@error_handler
def handle_payment_intent_payment_failed_event(
stripe_payment_intent: stripe.PaymentIntent, payment_intent: PaymentIntent
) -> None:
assert stripe_payment_intent.last_payment_error is not None
payment_intent.status = PaymentIntent.get_status_integer_from_status_text(
stripe_payment_intent.status
)
assert payment_intent.customer.realm is not None
billing_logger.info(
"Stripe payment intent failed: %s %s %s %s",
payment_intent.customer.realm.string_id,
stripe_payment_intent.last_payment_error.get("type"),
stripe_payment_intent.last_payment_error.get("code"),
stripe_payment_intent.last_payment_error.get("param"),
)
payment_intent.last_payment_error = {
"description": stripe_payment_intent.last_payment_error.get("type"),
}
payment_intent.last_payment_error["message"] = stripe_payment_intent.last_payment_error.get(
"message"
)
payment_intent.save(update_fields=["status", "last_payment_error"])

View File

@@ -1,16 +1,58 @@
from dataclasses import dataclass
from decimal import Decimal
from typing import Optional
from typing import Optional, TypedDict
from urllib.parse import urlencode, urljoin, urlunsplit
from django.conf import settings
from django.urls import reverse
from django.utils.timezone import now as timezone_now
from corporate.lib.stripe import RealmBillingSession
from corporate.models import get_customer_by_realm
from zerver.models import Realm, UserProfile, get_realm
from corporate.lib.stripe import BillingSession
from corporate.models import (
Customer,
CustomerPlan,
ZulipSponsorshipRequest,
get_current_plan_by_customer,
)
from zerver.models import Realm, get_org_type_display_name, get_realm
from zilencer.lib.remote_counts import MissingDataError
def get_support_url(realm: Realm) -> str:
class SponsorshipRequestDict(TypedDict):
org_type: str
org_website: str
org_description: str
total_users: str
paid_users: str
paid_users_description: str
requested_plan: str
@dataclass
class SponsorshipData:
sponsorship_pending: bool = False
default_discount: Optional[Decimal] = None
latest_sponsorship_request: Optional[SponsorshipRequestDict] = None
@dataclass
class PlanData:
customer: Optional["Customer"] = None
current_plan: Optional["CustomerPlan"] = None
licenses: Optional[int] = None
licenses_used: Optional[int] = None
is_legacy_plan: bool = False
has_fixed_price: bool = False
warning: Optional[str] = None
@dataclass
class SupportData:
plan_data: PlanData
sponsorship_data: SponsorshipData
def get_realm_support_url(realm: Realm) -> str:
support_realm_uri = get_realm(settings.STAFF_SUBDOMAIN).uri
support_url = urljoin(
support_realm_uri,
@@ -19,32 +61,87 @@ def get_support_url(realm: Realm) -> str:
return support_url
def get_discount_for_realm(realm: Realm) -> Optional[Decimal]:
customer = get_customer_by_realm(realm)
def get_customer_discount_for_support_view(
customer: Optional[Customer] = None,
) -> Optional[Decimal]:
if customer is None:
return None
return customer.default_discount
def get_customer_sponsorship_data(customer: Customer) -> SponsorshipData:
pending = customer.sponsorship_pending
discount = customer.default_discount
sponsorship_request = None
if pending:
last_sponsorship_request = (
ZulipSponsorshipRequest.objects.filter(customer=customer).order_by("id").last()
)
if last_sponsorship_request is not None:
org_type_name = get_org_type_display_name(last_sponsorship_request.org_type)
if (
last_sponsorship_request.org_website is None
or last_sponsorship_request.org_website == ""
):
website = "No website submitted"
else:
website = last_sponsorship_request.org_website
sponsorship_request = SponsorshipRequestDict(
org_type=org_type_name,
org_website=website,
org_description=last_sponsorship_request.org_description,
total_users=last_sponsorship_request.expected_total_users,
paid_users=last_sponsorship_request.paid_users_count,
paid_users_description=last_sponsorship_request.paid_users_description,
requested_plan=last_sponsorship_request.requested_plan,
)
return SponsorshipData(
sponsorship_pending=pending,
default_discount=discount,
latest_sponsorship_request=sponsorship_request,
)
def get_current_plan_data_for_support_view(billing_session: BillingSession) -> PlanData:
customer = billing_session.get_customer()
plan = None
if customer is not None:
return customer.default_discount
return None
plan = get_current_plan_by_customer(customer)
plan_data = PlanData(
customer=customer,
current_plan=plan,
)
if plan is not None:
new_plan, last_ledger_entry = billing_session.make_end_of_cycle_updates_if_needed(
plan, timezone_now()
)
if last_ledger_entry is not None:
if new_plan is not None:
plan_data.current_plan = new_plan # nocoverage
plan_data.licenses = last_ledger_entry.licenses
try:
plan_data.licenses_used = billing_session.current_count_for_billed_licenses()
except MissingDataError: # nocoverage
plan_data.warning = "Recent data missing: No information for used licenses"
assert plan_data.current_plan is not None # for mypy
plan_data.is_legacy_plan = (
plan_data.current_plan.tier == CustomerPlan.TIER_SELF_HOSTED_LEGACY
)
plan_data.has_fixed_price = plan_data.current_plan.fixed_price is not None
return plan_data
def attach_discount_to_realm(realm: Realm, discount: Decimal, *, acting_user: UserProfile) -> None:
billing_session = RealmBillingSession(acting_user, realm)
billing_session.attach_discount_to_customer(discount)
def get_data_for_support_view(billing_session: BillingSession) -> SupportData:
plan_data = get_current_plan_data_for_support_view(billing_session)
customer = billing_session.get_customer()
if customer is not None:
sponsorship_data = get_customer_sponsorship_data(customer)
else:
sponsorship_data = SponsorshipData()
def approve_realm_sponsorship(realm: Realm, *, acting_user: UserProfile) -> None:
billing_session = RealmBillingSession(acting_user, realm)
billing_session.approve_sponsorship()
def update_realm_sponsorship_status(
realm: Realm, sponsorship_pending: bool, *, acting_user: UserProfile
) -> None:
billing_session = RealmBillingSession(acting_user, realm)
billing_session.update_customer_sponsorship_status(sponsorship_pending)
def update_realm_billing_method(
realm: Realm, charge_automatically: bool, *, acting_user: UserProfile
) -> None:
billing_session = RealmBillingSession(acting_user, realm)
billing_session.update_billing_method_of_current_plan(charge_automatically)
return SupportData(
plan_data=plan_data,
sponsorship_data=sponsorship_data,
)

View File

@@ -0,0 +1,37 @@
# Generated by Django 4.2.7 on 2023-11-17 20:11
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zilencer", "0035_remoterealmcount_remote_realm_and_more"),
("corporate", "0019_zulipsponsorshiprequest_expected_total_users_and_more"),
]
operations = [
migrations.RemoveConstraint(
model_name="customer",
name="cloud_xor_self_hosted",
),
migrations.AddField(
model_name="customer",
name="remote_realm",
field=models.OneToOneField(
null=True, on_delete=django.db.models.deletion.CASCADE, to="zilencer.remoterealm"
),
),
migrations.AddConstraint(
model_name="customer",
constraint=models.CheckConstraint(
check=models.Q(
("realm__isnull", False),
("remote_server__isnull", False),
("remote_realm__isnull", False),
_connector="OR",
),
name="has_associated_model_object",
),
),
]

View File

@@ -0,0 +1,16 @@
# Generated by Django 4.2.7 on 2023-11-18 14:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("corporate", "0020_add_remote_realm_customers"),
]
operations = [
migrations.RemoveField(
model_name="session",
name="payment_intent",
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 4.2.7 on 2023-11-21 11:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("corporate", "0021_remove_session_payment_intent"),
]
operations = [
migrations.AddField(
model_name="session",
name="is_manual_license_management_upgrade_session",
field=models.BooleanField(default=False),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 4.2.7 on 2023-11-26 16:00
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("corporate", "0022_session_is_manual_license_management_upgrade_session"),
]
operations = [
migrations.AddField(
model_name="zulipsponsorshiprequest",
name="customer",
field=models.ForeignKey(
null=True, on_delete=django.db.models.deletion.CASCADE, to="corporate.customer"
),
),
]

View File

@@ -0,0 +1,18 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("corporate", "0023_zulipsponsorshiprequest_customer"),
]
operations = [
migrations.RunSQL(
"""
UPDATE corporate_zulipsponsorshiprequest
SET customer_id = (
SELECT id FROM corporate_customer WHERE corporate_customer.realm_id = corporate_zulipsponsorshiprequest.realm_id
)
"""
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 4.2.7 on 2023-11-26 16:13
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("corporate", "0024_zulipsponsorshiprequest_fill_customer_data"),
]
operations = [
migrations.AlterField(
model_name="zulipsponsorshiprequest",
name="customer",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="corporate.customer"
),
),
]

View File

@@ -0,0 +1,16 @@
# Generated by Django 4.2.7 on 2023-11-26 16:14
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("corporate", "0025_alter_zulipsponsorshiprequest_customer"),
]
operations = [
migrations.RemoveField(
model_name="zulipsponsorshiprequest",
name="realm",
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 4.2.7 on 2023-11-28 16:00
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("corporate", "0026_remove_zulipsponsorshiprequest_realm"),
]
operations = [
migrations.AlterField(
model_name="zulipsponsorshiprequest",
name="requested_by",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
]

View File

@@ -0,0 +1,21 @@
# Generated by Django 4.2.8 on 2023-12-08 18:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("corporate", "0027_alter_zulipsponsorshiprequest_requested_by"),
]
operations = [
migrations.AddField(
model_name="zulipsponsorshiprequest",
name="requested_plan",
field=models.CharField(
choices=[("", "UNSPECIFIED"), ("Community", "COMMUNITY"), ("Business", "BUSINESS")],
default="",
max_length=50,
),
),
]

View File

@@ -1,3 +1,4 @@
from enum import Enum
from typing import Any, Dict, Optional, Union
from django.contrib.contenttypes.fields import GenericForeignKey
@@ -7,7 +8,7 @@ from django.db.models import CASCADE, Q
from typing_extensions import override
from zerver.models import Realm, UserProfile
from zilencer.models import RemoteZulipServer
from zilencer.models import RemoteRealm, RemoteZulipServer
class Customer(models.Model):
@@ -17,8 +18,12 @@ class Customer(models.Model):
and the active plan, if any.
"""
# The actual model object that this customer is associated
# with. Exactly one of the following will be non-null.
realm = models.OneToOneField(Realm, on_delete=CASCADE, null=True)
remote_realm = models.OneToOneField(RemoteRealm, on_delete=CASCADE, null=True)
remote_server = models.OneToOneField(RemoteZulipServer, on_delete=CASCADE, null=True)
stripe_customer_id = models.CharField(max_length=255, null=True, unique=True)
sponsorship_pending = models.BooleanField(default=False)
# A percentage, like 85.
@@ -30,10 +35,13 @@ class Customer(models.Model):
exempt_from_license_number_check = models.BooleanField(default=False)
class Meta:
# Enforce that at least one of these is set.
constraints = [
models.CheckConstraint(
check=Q(realm__isnull=False) ^ Q(remote_server__isnull=False),
name="cloud_xor_self_hosted",
check=Q(realm__isnull=False)
| Q(remote_server__isnull=False)
| Q(remote_realm__isnull=False),
name="has_associated_model_object",
)
]
@@ -49,6 +57,14 @@ def get_customer_by_realm(realm: Realm) -> Optional[Customer]:
return Customer.objects.filter(realm=realm).first()
def get_customer_by_remote_server(remote_server: RemoteZulipServer) -> Optional[Customer]:
return Customer.objects.filter(remote_server=remote_server).first()
def get_customer_by_remote_realm(remote_realm: RemoteRealm) -> Optional[Customer]: # nocoverage
return Customer.objects.filter(remote_realm=remote_realm).first()
class Event(models.Model):
stripe_event_id = models.CharField(max_length=255)
@@ -91,29 +107,25 @@ def get_last_associated_event_by_type(
class Session(models.Model):
customer = models.ForeignKey(Customer, on_delete=CASCADE)
stripe_session_id = models.CharField(max_length=255, unique=True)
payment_intent = models.ForeignKey("PaymentIntent", null=True, on_delete=CASCADE)
UPGRADE_FROM_BILLING_PAGE = 1
RETRY_UPGRADE_WITH_ANOTHER_PAYMENT_METHOD = 10
FREE_TRIAL_UPGRADE_FROM_BILLING_PAGE = 20
FREE_TRIAL_UPGRADE_FROM_ONBOARDING_PAGE = 30
CARD_UPDATE_FROM_BILLING_PAGE = 40
CARD_UPDATE_FROM_UPGRADE_PAGE = 50
type = models.SmallIntegerField()
CREATED = 1
COMPLETED = 10
status = models.SmallIntegerField(default=CREATED)
# Did the user opt to manually manage licenses before clicking on update button?
is_manual_license_management_upgrade_session = models.BooleanField(default=False)
def get_status_as_string(self) -> str:
return {Session.CREATED: "created", Session.COMPLETED: "completed"}[self.status]
def get_type_as_string(self) -> str:
return {
Session.UPGRADE_FROM_BILLING_PAGE: "upgrade_from_billing_page",
Session.RETRY_UPGRADE_WITH_ANOTHER_PAYMENT_METHOD: "retry_upgrade_with_another_payment_method",
Session.FREE_TRIAL_UPGRADE_FROM_BILLING_PAGE: "free_trial_upgrade_from_billing_page",
Session.FREE_TRIAL_UPGRADE_FROM_ONBOARDING_PAGE: "free_trial_upgrade_from_onboarding_page",
Session.CARD_UPDATE_FROM_BILLING_PAGE: "card_update_from_billing_page",
Session.CARD_UPDATE_FROM_UPGRADE_PAGE: "card_update_from_upgrade_page",
}[self.type]
def to_dict(self) -> Dict[str, Any]:
@@ -121,8 +133,9 @@ class Session(models.Model):
session_dict["status"] = self.get_status_as_string()
session_dict["type"] = self.get_type_as_string()
if self.payment_intent:
session_dict["stripe_payment_intent_id"] = self.payment_intent.stripe_payment_intent_id
session_dict[
"is_manual_license_management_upgrade_session"
] = self.is_manual_license_management_upgrade_session
event = self.get_last_associated_event()
if event is not None:
session_dict["event_handler"] = event.get_event_handler_details_as_dict()
@@ -167,18 +180,15 @@ class PaymentIntent(models.Model):
def get_last_associated_event(self) -> Optional[Event]:
if self.status == PaymentIntent.SUCCEEDED:
event_type = "payment_intent.succeeded"
elif self.status == PaymentIntent.REQUIRES_PAYMENT_METHOD:
event_type = "payment_intent.payment_failed"
else:
return None
# TODO: Add test for this case. Not sure how to trigger naturally.
else: # nocoverage
return None # nocoverage
return get_last_associated_event_by_type(self, event_type)
def to_dict(self) -> Dict[str, Any]:
payment_intent_dict: Dict[str, Any] = {}
payment_intent_dict["status"] = self.get_status_as_string()
event = self.get_last_associated_event()
if self.last_payment_error:
payment_intent_dict["last_payment_error"] = self.last_payment_error
if event is not None:
payment_intent_dict["event_handler"] = event.get_event_handler_details_as_dict()
return payment_intent_dict
@@ -214,11 +224,11 @@ class CustomerPlan(models.Model):
# billing_cycle_anchor.
billing_cycle_anchor = models.DateTimeField()
ANNUAL = 1
MONTHLY = 2
BILLING_SCHEDULE_ANNUAL = 1
BILLING_SCHEDULE_MONTHLY = 2
BILLING_SCHEDULES = {
ANNUAL: "Annual",
MONTHLY: "Monthly",
BILLING_SCHEDULE_ANNUAL: "Annual",
BILLING_SCHEDULE_MONTHLY: "Monthly",
}
billing_schedule = models.SmallIntegerField()
@@ -239,24 +249,36 @@ class CustomerPlan(models.Model):
)
end_date = models.DateTimeField(null=True)
DONE = 1
STARTED = 2
INITIAL_INVOICE_TO_BE_SENT = 3
INVOICING_STATUS_DONE = 1
INVOICING_STATUS_STARTED = 2
INVOICING_STATUS_INITIAL_INVOICE_TO_BE_SENT = 3
# This status field helps ensure any errors encountered during the
# invoicing process do not leave our invoicing system in a broken
# state.
invoicing_status = models.SmallIntegerField(default=DONE)
invoicing_status = models.SmallIntegerField(default=INVOICING_STATUS_DONE)
STANDARD = 1
PLUS = 2 # not available through self-serve signup
ENTERPRISE = 10
TIER_CLOUD_STANDARD = 1
TIER_CLOUD_PLUS = 2
# Reserved tier IDs for future use
TIER_CLOUD_COMMUNITY = 3
TIER_CLOUD_ENTERPRISE = 4
TIER_SELF_HOSTED_BASE = 100
TIER_SELF_HOSTED_LEGACY = 101
TIER_SELF_HOSTED_COMMUNITY = 102
TIER_SELF_HOSTED_BUSINESS = 103
TIER_SELF_HOSTED_PLUS = 104
TIER_SELF_HOSTED_ENTERPRISE = 105
tier = models.SmallIntegerField()
ACTIVE = 1
DOWNGRADE_AT_END_OF_CYCLE = 2
FREE_TRIAL = 3
SWITCH_TO_ANNUAL_AT_END_OF_CYCLE = 4
SWITCH_NOW_FROM_STANDARD_TO_PLUS = 5
SWITCH_PLAN_TIER_NOW = 5
SWITCH_TO_MONTHLY_AT_END_OF_CYCLE = 6
DOWNGRADE_AT_END_OF_FREE_TRIAL = 7
SWITCH_PLAN_TIER_AT_PLAN_END = 8
# "Live" plans should have a value < LIVE_STATUS_THRESHOLD.
# There should be at most one live plan per customer.
LIVE_STATUS_THRESHOLD = 10
@@ -267,19 +289,37 @@ class CustomerPlan(models.Model):
# TODO maybe override setattr to ensure billing_cycle_anchor, etc
# are immutable.
@override
def __str__(self) -> str:
return f"{self.name} (status: {self.get_plan_status_as_text()})"
@staticmethod
def name_from_tier(tier: int) -> str:
# NOTE: Check `statement_descriptor` values after updating this.
# Stripe has a 22 character limit on the statement descriptor length.
# https://stripe.com/docs/payments/account/statement-descriptors
return {
CustomerPlan.TIER_CLOUD_STANDARD: "Zulip Cloud Standard",
CustomerPlan.TIER_CLOUD_PLUS: "Zulip Cloud Plus",
CustomerPlan.TIER_CLOUD_ENTERPRISE: "Zulip Enterprise",
CustomerPlan.TIER_SELF_HOSTED_LEGACY: "Self-managed (legacy plan)",
CustomerPlan.TIER_SELF_HOSTED_BUSINESS: "Zulip Business",
CustomerPlan.TIER_SELF_HOSTED_COMMUNITY: "Community",
}[tier]
@property
def name(self) -> str:
return {
CustomerPlan.STANDARD: "Zulip Cloud Standard",
CustomerPlan.PLUS: "Zulip Plus",
CustomerPlan.ENTERPRISE: "Zulip Enterprise",
}[self.tier]
return self.name_from_tier(self.tier)
def get_plan_status_as_text(self) -> str:
return {
self.ACTIVE: "Active",
self.DOWNGRADE_AT_END_OF_CYCLE: "Scheduled for downgrade at end of cycle",
self.FREE_TRIAL: "Free trial",
self.SWITCH_TO_ANNUAL_AT_END_OF_CYCLE: "Scheduled for switch to annual at end of cycle",
self.SWITCH_TO_MONTHLY_AT_END_OF_CYCLE: "Scheduled for switch to monthly at end of cycle",
self.DOWNGRADE_AT_END_OF_FREE_TRIAL: "Scheduled for downgrade at end of free trial",
self.SWITCH_PLAN_TIER_AT_PLAN_END: "Scheduled for switch to new plan at the end of plan",
self.ENDED: "Ended",
self.NEVER_STARTED: "Never started",
}[self.status]
@@ -290,7 +330,10 @@ class CustomerPlan(models.Model):
return ledger_entry.licenses
def licenses_at_next_renewal(self) -> Optional[int]:
if self.status == CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE:
if self.status in (
CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE,
CustomerPlan.DOWNGRADE_AT_END_OF_FREE_TRIAL,
):
return None
ledger_entry = LicenseLedger.objects.filter(plan=self).order_by("id").last()
assert ledger_entry is not None
@@ -347,9 +390,16 @@ class LicenseLedger(models.Model):
licenses_at_next_renewal = models.IntegerField(null=True)
class SponsoredPlanTypes(Enum):
# unspecified used for cloud sponsorship requests
UNSPECIFIED = ""
COMMUNITY = "Community"
BUSINESS = "Business"
class ZulipSponsorshipRequest(models.Model):
realm = models.ForeignKey(Realm, on_delete=CASCADE)
requested_by = models.ForeignKey(UserProfile, on_delete=CASCADE)
customer = models.ForeignKey(Customer, on_delete=CASCADE)
requested_by = models.ForeignKey(UserProfile, on_delete=CASCADE, null=True, blank=True)
org_type = models.PositiveSmallIntegerField(
default=Realm.ORG_TYPES["unspecified"]["id"],
@@ -363,3 +413,9 @@ class ZulipSponsorshipRequest(models.Model):
expected_total_users = models.TextField(default="")
paid_users_count = models.TextField(default="")
paid_users_description = models.TextField(default="")
requested_plan = models.CharField(
max_length=50,
choices=[(plan.value, plan.name) for plan in SponsoredPlanTypes],
default=SponsoredPlanTypes.UNSPECIFIED.value,
)

View File

@@ -42,6 +42,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -63,7 +64,7 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
@@ -74,7 +75,7 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
@@ -103,9 +104,10 @@
},
"type": "card"
},
"radar_options": {},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKLmktKoGMgYrcqQJ_XQ6LBZLSpoFwx3AcKTCrmps2vSpTYEuUPpKK37EcqScvxgnj4SR9xDWiSPE9-Ni",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKIr9qqsGMgaM-GKNpmc6LBZoWb7DJvCIVhczLJKOYXKROrzmBYTFtupIgmreHcTL1xLwXB0pQP-R7gBA",
"refunded": false,
"refunds": {
"data": [],

View File

@@ -42,6 +42,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
@@ -63,7 +64,7 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_details": {
"card": {
"amount_authorized": 36000,
@@ -74,7 +75,7 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
@@ -103,9 +104,10 @@
},
"type": "card"
},
"radar_options": {},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKMyktKoGMgY7jXPALx46LBZ93Aicbpv_SAI_VTk6csq3v1o-6Isr2CHYie17adWgts9dL_uZ8bLzxthw",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKJT9qqsGMgbmGcE2CPg6LBYt6tPCI7CnKaxaLVq3zjSaM7atoyWnbp6xDOd4JEAbIIuFCeKskEzYWUyb",
"refunded": false,
"refunds": {
"data": [],
@@ -166,6 +168,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -187,7 +190,7 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
@@ -198,7 +201,7 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
@@ -227,9 +230,10 @@
},
"type": "card"
},
"radar_options": {},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKMyktKoGMgYMbX-GnOM6LBagBjW6OMx9HAU5uMfOZ8snKne1PHYDc42XAxy7IdyNE7BH38b7vW-rIqLk",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKJT9qqsGMgZmMGKxY746LBarTbqnnWLLGRzmrQAWL3_ig-D1uUENWBVpjxcDGfuFmMRWLxqSgEkXJuqq",
"refunded": false,
"refunds": {
"data": [],

View File

@@ -13,7 +13,7 @@
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"default_payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"footer": null,
"rendering_options": null
},

View File

@@ -13,7 +13,7 @@
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"default_payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"footer": null,
"rendering_options": null
},

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -5,83 +5,53 @@
"created": 1000000000,
"data": {
"object": {
"amount": 6400,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 0,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": 1000000000,
"cancellation_reason": "void_invoice",
"capture_method": "automatic",
"charges": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000003"
},
"client_secret": "pi_NORMALIZED00000000000003_secret_fYThlYzmA6ZihKmrZLnmWcTvW",
"confirmation_method": "automatic",
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0002",
"description": "Payment for Invoice",
"id": "pi_NORMALIZED00000000000003",
"invoice": "in_NORMALIZED00000000000001",
"last_payment_error": null,
"latest_charge": null,
"livemode": false,
"metadata": {},
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
},
"cashapp": {},
"wechat_pay": {
"app_id": null,
"client": null
}
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"footer": null,
"rendering_options": null
},
"payment_method_types": [
"ach_credit_transfer",
"card",
"cashapp",
"wechat_pay"
],
"processing": null,
"receipt_email": "king@lear.org",
"review": null,
"setup_future_usage": null,
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"source": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "canceled",
"transfer_data": null,
"transfer_group": null
"tax_exempt": "none",
"test_clock": null
},
"previous_attributes": {
"invoice_settings": {
"default_payment_method": null
}
}
},
"id": "evt_3OAbimDEQaroqDjs0BaEBsNX",
"id": "evt_1OIlcWDEQaroqDjsDYC5kGA8",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0001",
"idempotency_key": "25effa1f-8fed-45ea-a660-fd0b33abc23c"
"idempotency_key": "ec0d8ceb-bc0b-4141-9ecf-9f91d42f5a7e"
},
"type": "payment_intent.canceled"
"type": "customer.updated"
}
],
"has_more": true,

View File

@@ -61,6 +61,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -82,7 +83,7 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
@@ -93,7 +94,7 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
@@ -122,9 +123,10 @@
},
"type": "card"
},
"radar_options": {},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKLKktKoGMgZfETNIDN86LBY50xSk957U6AlRoP5-YOxSRkjaDyWZxvhpX5Lq7EfiLZlknEWX_xe2Db-Q",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKIf9qqsGMgZb20Gi7do6LBb8Nk4YXcMYuHylkNS09JAb63jzjtrknnoa60TiX-hHWdshgcroMLKDWN5c",
"refunded": false,
"refunds": {
"data": [],
@@ -149,7 +151,7 @@
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000001"
},
"client_secret": "pi_NORMALIZED00000000000001_secret_U9fLWaQ5foMLX6li0E7nIl4wX",
"client_secret": "pi_NORMALIZED00000000000001_secret_TT71wq3meHmzTEd7mpZbqrDnP",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -165,6 +167,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -176,7 +179,7 @@
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
@@ -202,13 +205,13 @@
"transfer_group": null
}
},
"id": "evt_3OAbiwDEQaroqDjs1KG0PpkK",
"id": "evt_3OIlcYDEQaroqDjs16WfiUYV",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0002",
"idempotency_key": "a6fbc05f-71a7-41dd-89d9-d83491088de5"
"idempotency_key": "89f4d29d-4c41-4c1d-baf7-b33a1c7d25ff"
},
"type": "payment_intent.succeeded"
},
@@ -258,6 +261,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -279,7 +283,7 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
@@ -290,7 +294,7 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
@@ -319,9 +323,10 @@
},
"type": "card"
},
"radar_options": {},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKLKktKoGMgYmLI2wbBc6LBayDyiusyUGbOxDSVFnS4jnIBbaP5HhJCOa-jwmTSISDTAtkNQlQs2ntZKo",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKIf9qqsGMgaRG54HB6k6LBYbdkPPJRe4ZuuV5tR6SrHKPTGj9DFYvf8ZlOiAmw8lVKk8NDaGGFlsjRM8",
"refunded": false,
"refunds": {
"data": [],
@@ -341,412 +346,15 @@
"transfer_group": null
}
},
"id": "evt_3OAbiwDEQaroqDjs1MgkdMZv",
"id": "evt_3OIlcYDEQaroqDjs1OA8t8hC",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0002",
"idempotency_key": "a6fbc05f-71a7-41dd-89d9-d83491088de5"
"idempotency_key": "89f4d29d-4c41-4c1d-baf7-b33a1c7d25ff"
},
"type": "charge.succeeded"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"address": null,
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
},
"previous_attributes": {
"invoice_settings": {
"default_payment_method": null
}
}
},
"id": "evt_1OAbj2DEQaroqDjsdHXrdECt",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0003",
"idempotency_key": "ba127619-d57d-424c-bc0d-e1b5d6e75b4f"
},
"type": "customer.updated"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbiyDEQaroqDjsEnpR1Onp_secret_OyYqFrkey3OlMfxWtyreL2AbO9RHVB4",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbiyDEQaroqDjsEnpR1Onp",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbiyDEQaroqDjsnLExjW56",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "succeeded",
"usage": "off_session"
}
},
"id": "evt_1OAbizDEQaroqDjsMSKWCeS4",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "b367784c-9178-4e50-ad4b-33271f44771f"
},
"type": "setup_intent.succeeded"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
}
},
"id": "evt_1OAbizDEQaroqDjs7C1ESMag",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "b367784c-9178-4e50-ad4b-33271f44771f"
},
"type": "payment_method.attached"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbiyDEQaroqDjsEnpR1Onp_secret_OyYqFrkey3OlMfxWtyreL2AbO9RHVB4",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbiyDEQaroqDjsEnpR1Onp",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbiyDEQaroqDjsnLExjW56",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "succeeded",
"usage": "off_session"
}
},
"id": "evt_1OAbizDEQaroqDjs12Wkj59b",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "b367784c-9178-4e50-ad4b-33271f44771f"
},
"type": "setup_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbixDEQaroqDjsZ13bFTzk_secret_OyYqTKT3uNo6hXb6nycNN82HUV4tLVa",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbixDEQaroqDjsZ13bFTzk",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "requires_payment_method",
"usage": "off_session"
}
},
"id": "evt_1OAbixDEQaroqDjswQf5925l",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0005",
"idempotency_key": "2f531396-87a0-4a7a-85f2-4cbeae89359b"
},
"type": "setup_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"amount": 7200,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 0,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000001"
},
"client_secret": "pi_NORMALIZED00000000000001_secret_U9fLWaQ5foMLX6li0E7nIl4wX",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $12.0 x 6",
"id": "pi_NORMALIZED00000000000001",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "requires_payment_method",
"transfer_data": null,
"transfer_group": null
}
},
"id": "evt_3OAbiwDEQaroqDjs19zAFXo5",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0006",
"idempotency_key": "dcb3c560-eb12-48b6-a8b1-c3781dc8dc47"
},
"type": "payment_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"address": null,
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": null,
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}
},
"id": "evt_1OAbivDEQaroqDjsBYwBWKGq",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0007",
"idempotency_key": "d7e3039e-deb3-4b01-b0d4-cb3365326a0e"
},
"type": "customer.created"
}
],
"has_more": false,

View File

@@ -46,9 +46,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjA502000hPybKLo?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjA502000hPybKLo/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -67,8 +67,8 @@
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -157,7 +157,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
@@ -210,230 +210,7 @@
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
}
},
"id": "evt_1OAbjADEQaroqDjswTbvBGJ7",
"livemode": false,
"object": "event",
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "da0bb2e0-e63b-4ef2-8190-e4a898a67895"
},
"type": "invoice.finalized"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_reason": "manual",
"charge": null,
"collection_method": "charge_automatically",
"created": 1000000000,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
"customer_tax_exempt": "none",
"customer_tax_ids": [],
"default_payment_method": null,
"default_source": null,
"default_tax_rates": [],
"description": null,
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUx0200otAxmBG3?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUx0200otAxmBG3/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 7200,
"amount_excluding_tax": 7200,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 1200,
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "1200"
},
{
"amount": -7200,
"amount_excluding_tax": -7200,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0001",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
"period_end": 1000000000,
"period_start": 1000000000,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "letter"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "paid",
"status_transitions": {
"finalized_at": 1000000000,
"marked_uncollectible_at": null,
"paid_at": 1000000000,
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
},
"previous_attributes": {
"attempted": false,
@@ -457,13 +234,13 @@
}
}
},
"id": "evt_1OAbjADEQaroqDjsSI8lFXJv",
"id": "evt_1OIlcbDEQaroqDjs0RgNo7tS",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "da0bb2e0-e63b-4ef2-8190-e4a898a67895"
"id": "req_NORMALIZED0003",
"idempotency_key": "d8040535-2a29-417e-a8cd-313f534cb633"
},
"type": "invoice.updated"
},
@@ -514,7 +291,7 @@
"footer": null,
"from_invoice": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000002",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": null,
"last_finalization_error": null,
"latest_revision": null,
@@ -534,8 +311,8 @@
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -624,7 +401,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
@@ -680,13 +457,13 @@
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbj8DEQaroqDjskFTdkB1y",
"id": "evt_1OIlcaDEQaroqDjsgW8xhHxB",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0009",
"idempotency_key": "54d61ab3-cd30-4b7a-ad40-0dce334dd232"
"id": "req_NORMALIZED0004",
"idempotency_key": "e1bdf299-f588-4704-ae2c-82d0debf6534"
},
"type": "invoice.created"
},
@@ -708,8 +485,8 @@
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -742,13 +519,13 @@
"unit_amount_decimal": "1200"
}
},
"id": "evt_1OAbj7DEQaroqDjs4SWJFkYg",
"id": "evt_1OIlcaDEQaroqDjsCma6I5rr",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0010",
"idempotency_key": "2ae230b6-12fb-4c3a-b8b7-6ecd041a313c"
"id": "req_NORMALIZED0005",
"idempotency_key": "cfb4b441-f25a-4d95-a2f0-f0f950bd40fe"
},
"type": "invoiceitem.created"
},
@@ -804,13 +581,13 @@
"unit_amount_decimal": "-7200"
}
},
"id": "evt_1OAbj5DEQaroqDjsNe3rtI3x",
"id": "evt_1OIlcZDEQaroqDjs6GyIO3FO",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0011",
"idempotency_key": "22eeb342-33df-4bcb-8750-7c8649ef72e3"
"id": "req_NORMALIZED0006",
"idempotency_key": "13255827-b4d3-42fe-9f18-fbcb67305a04"
},
"type": "invoiceitem.created"
},
@@ -833,7 +610,7 @@
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"default_payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"footer": null,
"rendering_options": null
},
@@ -856,13 +633,13 @@
"default_currency": null
}
},
"id": "evt_1OAbj5DEQaroqDjsGpvAlLYX",
"id": "evt_1OIlcZDEQaroqDjs2IZnZELZ",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0011",
"idempotency_key": "22eeb342-33df-4bcb-8750-7c8649ef72e3"
"id": "req_NORMALIZED0006",
"idempotency_key": "13255827-b4d3-42fe-9f18-fbcb67305a04"
},
"type": "customer.updated"
}

View File

@@ -46,9 +46,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjEw0200uGqIV7uQ?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjEw0200uGqIV7uQ/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -67,8 +67,8 @@
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -157,7 +157,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
@@ -210,16 +210,16 @@
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbjADEQaroqDjsRFLyV68r",
"id": "evt_1OIlccDEQaroqDjsArfDmoTy",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "da0bb2e0-e63b-4ef2-8190-e4a898a67895"
"id": "req_NORMALIZED0003",
"idempotency_key": "d8040535-2a29-417e-a8cd-313f534cb633"
},
"type": "invoice.payment_succeeded"
},
@@ -269,9 +269,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjA502000hPybKLo?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjA502000hPybKLo/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -290,8 +290,8 @@
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -380,7 +380,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
@@ -433,18 +433,241 @@
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbjADEQaroqDjsHfm4lKFu",
"id": "evt_1OIlcbDEQaroqDjsn0Gh2ZXB",
"livemode": false,
"object": "event",
"pending_webhooks": 1,
"request": {
"id": "req_NORMALIZED0003",
"idempotency_key": "d8040535-2a29-417e-a8cd-313f534cb633"
},
"type": "invoice.paid"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_reason": "manual",
"charge": null,
"collection_method": "charge_automatically",
"created": 1000000000,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
"customer_tax_exempt": "none",
"customer_tax_ids": [],
"default_payment_method": null,
"default_source": null,
"default_tax_rates": [],
"description": null,
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjA502000hPybKLo?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjA502000hPybKLo/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 7200,
"amount_excluding_tax": 7200,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 1200,
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "1200"
},
{
"amount": -7200,
"amount_excluding_tax": -7200,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
"period_end": 1000000000,
"period_start": 1000000000,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "letter"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "paid",
"status_transitions": {
"finalized_at": 1000000000,
"marked_uncollectible_at": null,
"paid_at": 1000000000,
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": null
}
},
"id": "evt_1OIlcbDEQaroqDjshdrWm9Tg",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "da0bb2e0-e63b-4ef2-8190-e4a898a67895"
"id": "req_NORMALIZED0003",
"idempotency_key": "d8040535-2a29-417e-a8cd-313f534cb633"
},
"type": "invoice.paid"
"type": "invoice.finalized"
}
],
"has_more": false,

View File

@@ -5,223 +5,53 @@
"created": 1000000000,
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_reason": "manual",
"charge": null,
"collection_method": "charge_automatically",
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
"customer_tax_exempt": "none",
"customer_tax_ids": [],
"default_payment_method": null,
"default_currency": "usd",
"default_source": null,
"default_tax_rates": [],
"description": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 7200,
"amount_excluding_tax": 7200,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 1200,
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "1200"
},
{
"amount": -7200,
"amount_excluding_tax": -7200,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"period_end": 1000000000,
"period_start": 1000000000,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "letter"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "paid",
"status_transitions": {
"finalized_at": 1000000000,
"marked_uncollectible_at": null,
"paid_at": 1000000000,
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"name": null,
"next_invoice_sequence": 2,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
},
"previous_attributes": {
"invoice_settings": {
"default_payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK"
}
}
},
"id": "evt_1OAbjADEQaroqDjsRFLyV68r",
"id": "evt_1OIlcgDEQaroqDjsPE8wVcBq",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "da0bb2e0-e63b-4ef2-8190-e4a898a67895"
"id": "req_NORMALIZED0007",
"idempotency_key": "6e41d51b-0e25-4d1f-9da2-4472878eb45f"
},
"type": "invoice.payment_succeeded"
"type": "customer.updated"
}
],
"has_more": true,

View File

@@ -61,6 +61,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
@@ -82,7 +83,7 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_details": {
"card": {
"amount_authorized": 36000,
@@ -93,7 +94,7 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
@@ -122,9 +123,10 @@
},
"type": "card"
},
"radar_options": {},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKMWktKoGMgbBejqty7k6LBay1D-S4hg39Q9UDIOGUp79dAR3ZWzowgod2EPK3yoyXY0CYXWsROxc7cuw",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKJH9qqsGMgbQjtDZqQM6LBZPz4stEfKrXRPEFZYCyclDAo2Dez80I8lUHYTVeT16LQpU7Ou6wGCFugiR",
"refunded": false,
"refunds": {
"data": [],
@@ -149,7 +151,7 @@
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_IM9QK0XPBRsrtvMFpF1HVQGr3",
"client_secret": "pi_NORMALIZED00000000000002_secret_6P4eLudA3uvQYCOoNiDTkNQEI",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -165,6 +167,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
@@ -176,7 +179,7 @@
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
@@ -202,13 +205,13 @@
"transfer_group": null
}
},
"id": "evt_3OAbjEDEQaroqDjs0Fdo8Gmm",
"id": "evt_3OIlciDEQaroqDjs14Vw2Reo",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0012",
"idempotency_key": "ead047f4-390c-4143-bca4-b560cb2c76af"
"id": "req_NORMALIZED0008",
"idempotency_key": "f7ce8212-6677-40df-912b-4ac6c3765065"
},
"type": "payment_intent.succeeded"
},
@@ -258,6 +261,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
@@ -279,7 +283,7 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_details": {
"card": {
"amount_authorized": 36000,
@@ -290,7 +294,7 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
@@ -319,9 +323,10 @@
},
"type": "card"
},
"radar_options": {},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKMWktKoGMgYpVepO4vY6LBbu8Gdq5swMUKpN609lyeB_YsbAnOp7XAL-DHUMj7-nEPl0juPLx6Hv11vd",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKJH9qqsGMgYqjd-cSvE6LBaZocBeNc1j6HmSljaGjK9coB5-xRAyLYhQxqOON0JTW8MYWAJtnxrRxAiL",
"refunded": false,
"refunds": {
"data": [],
@@ -341,364 +346,15 @@
"transfer_group": null
}
},
"id": "evt_3OAbjEDEQaroqDjs0Jr4G9pD",
"id": "evt_3OIlciDEQaroqDjs1zZA7DlT",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0012",
"idempotency_key": "ead047f4-390c-4143-bca4-b560cb2c76af"
"id": "req_NORMALIZED0008",
"idempotency_key": "f7ce8212-6677-40df-912b-4ac6c3765065"
},
"type": "charge.succeeded"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
},
"previous_attributes": {
"invoice_settings": {
"default_payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ"
}
}
},
"id": "evt_1OAbjJDEQaroqDjsiVYBYR7z",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0013",
"idempotency_key": "4ebda7fb-5c3a-4ce9-8abc-9cade78757d3"
},
"type": "customer.updated"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjHDEQaroqDjsiyGUfkGg_secret_OyYq1ljcbx3AZiCSK143sZdt7S0n6pN",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjHDEQaroqDjsiyGUfkGg",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbjHDEQaroqDjswrjSFGn9",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "succeeded",
"usage": "off_session"
}
},
"id": "evt_1OAbjIDEQaroqDjsAq5yUHSA",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0014",
"idempotency_key": "703bb85b-e5cd-4cde-9b55-b712437a5113"
},
"type": "setup_intent.succeeded"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
}
},
"id": "evt_1OAbjHDEQaroqDjs5X4eBOmU",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0014",
"idempotency_key": "703bb85b-e5cd-4cde-9b55-b712437a5113"
},
"type": "payment_method.attached"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjHDEQaroqDjsiyGUfkGg_secret_OyYq1ljcbx3AZiCSK143sZdt7S0n6pN",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjHDEQaroqDjsiyGUfkGg",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbjHDEQaroqDjswrjSFGn9",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "succeeded",
"usage": "off_session"
}
},
"id": "evt_1OAbjHDEQaroqDjsj1Jcj4L7",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0014",
"idempotency_key": "703bb85b-e5cd-4cde-9b55-b712437a5113"
},
"type": "setup_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjFDEQaroqDjsOtWXOh3z_secret_OyYqDwZSEotDZcPqC8EEXDlXcWpwH0i",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjFDEQaroqDjsOtWXOh3z",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "requires_payment_method",
"usage": "off_session"
}
},
"id": "evt_1OAbjFDEQaroqDjslF0bckh4",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0015",
"idempotency_key": "ab40363f-ed16-49a3-abcb-e0050fe97641"
},
"type": "setup_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"amount": 36000,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 0,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_IM9QK0XPBRsrtvMFpF1HVQGr3",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $60.0 x 6",
"id": "pi_NORMALIZED00000000000002",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "requires_payment_method",
"transfer_data": null,
"transfer_group": null
}
},
"id": "evt_3OAbjEDEQaroqDjs04u0RIvp",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0016",
"idempotency_key": "5e88c770-f3aa-4e2f-83fd-fa3bfb251535"
},
"type": "payment_intent.created"
}
],
"has_more": false,

View File

@@ -46,9 +46,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6?s=ap",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjE502004xw8zwol?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjE502004xw8zwol/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -157,7 +157,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
@@ -213,459 +213,13 @@
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbjSDEQaroqDjsSMoBdCTk",
"id": "evt_1OIlclDEQaroqDjslxJqiQFY",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0017",
"idempotency_key": "f5b1be17-0d94-4f03-b53e-ca7abb7ea36c"
},
"type": "invoice.payment_succeeded"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_reason": "manual",
"charge": null,
"collection_method": "charge_automatically",
"created": 1000000000,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
"customer_tax_exempt": "none",
"customer_tax_ids": [],
"default_payment_method": null,
"default_source": null,
"default_tax_rates": [],
"description": null,
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6?s=ap",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 36000,
"amount_excluding_tax": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 6000,
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "6000"
},
{
"amount": -36000,
"amount_excluding_tax": -36000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0003",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
"period_end": 1000000000,
"period_start": 1000000000,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "letter"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "paid",
"status_transitions": {
"finalized_at": 1000000000,
"marked_uncollectible_at": null,
"paid_at": 1000000000,
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbjSDEQaroqDjsHvqUweFf",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0017",
"idempotency_key": "f5b1be17-0d94-4f03-b53e-ca7abb7ea36c"
},
"type": "invoice.paid"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_reason": "manual",
"charge": null,
"collection_method": "charge_automatically",
"created": 1000000000,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
"customer_tax_exempt": "none",
"customer_tax_ids": [],
"default_payment_method": null,
"default_source": null,
"default_tax_rates": [],
"description": null,
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6?s=ap",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 36000,
"amount_excluding_tax": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 6000,
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "6000"
},
{
"amount": -36000,
"amount_excluding_tax": -36000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0003",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
"period_end": 1000000000,
"period_start": 1000000000,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "letter"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "paid",
"status_transitions": {
"finalized_at": 1000000000,
"marked_uncollectible_at": null,
"paid_at": 1000000000,
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbjSDEQaroqDjs52kQBIea",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0017",
"idempotency_key": "f5b1be17-0d94-4f03-b53e-ca7abb7ea36c"
"id": "req_NORMALIZED0009",
"idempotency_key": "5fb5bcd9-f037-402f-b67a-6f8eb89f3c11"
},
"type": "invoice.finalized"
},
@@ -715,9 +269,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6?s=ap",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjE502004xw8zwol?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjE502004xw8zwol/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -826,7 +380,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
@@ -903,13 +457,13 @@
}
}
},
"id": "evt_1OAbjSDEQaroqDjsthbEfwPu",
"id": "evt_1OIlclDEQaroqDjsbyHu8WF8",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 1,
"request": {
"id": "req_NORMALIZED0017",
"idempotency_key": "f5b1be17-0d94-4f03-b53e-ca7abb7ea36c"
"id": "req_NORMALIZED0009",
"idempotency_key": "5fb5bcd9-f037-402f-b67a-6f8eb89f3c11"
},
"type": "invoice.updated"
},
@@ -960,7 +514,7 @@
"footer": null,
"from_invoice": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000003",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": null,
"last_finalization_error": null,
"latest_revision": null,
@@ -1070,7 +624,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
@@ -1126,13 +680,13 @@
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbjRDEQaroqDjsNjOr65Yz",
"id": "evt_1OIlckDEQaroqDjsUsuf3ZOp",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0018",
"idempotency_key": "361427f6-9d7d-4641-a46d-57e3878004d6"
"id": "req_NORMALIZED0010",
"idempotency_key": "a4d2ad76-c98a-446a-8236-ec43f0faf5fa"
},
"type": "invoice.created"
},
@@ -1188,13 +742,13 @@
"unit_amount_decimal": "6000"
}
},
"id": "evt_1OAbjRDEQaroqDjsFxUR9lEq",
"id": "evt_1OIlckDEQaroqDjsYzRhmX8V",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0019",
"idempotency_key": "d1af4863-6833-4171-81e4-d0d90b29ba5b"
"id": "req_NORMALIZED0011",
"idempotency_key": "0c11698d-33dd-4270-81bd-05e97ff35e0e"
},
"type": "invoiceitem.created"
},
@@ -1250,13 +804,13 @@
"unit_amount_decimal": "-36000"
}
},
"id": "evt_1OAbjPDEQaroqDjsuOddcwy8",
"id": "evt_1OIlcjDEQaroqDjsWfKiiWQ1",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0020",
"idempotency_key": "3120bbe1-9648-4af0-a7cb-70ac5bfc04a2"
"id": "req_NORMALIZED0012",
"idempotency_key": "e0683c69-8080-4a6a-b9e7-7750eba12988"
},
"type": "invoiceitem.created"
}

View File

@@ -1,5 +1,452 @@
{
"data": [],
"data": [
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_reason": "manual",
"charge": null,
"collection_method": "charge_automatically",
"created": 1000000000,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
"customer_tax_exempt": "none",
"customer_tax_ids": [],
"default_payment_method": null,
"default_source": null,
"default_tax_rates": [],
"description": null,
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjE502004xw8zwol?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjE502004xw8zwol/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 36000,
"amount_excluding_tax": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 6000,
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "6000"
},
{
"amount": -36000,
"amount_excluding_tax": -36000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0003",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
"period_end": 1000000000,
"period_start": 1000000000,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "letter"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "paid",
"status_transitions": {
"finalized_at": 1000000000,
"marked_uncollectible_at": null,
"paid_at": 1000000000,
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": null
}
},
"id": "evt_1OIlclDEQaroqDjsYQluU75w",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0009",
"idempotency_key": "5fb5bcd9-f037-402f-b67a-6f8eb89f3c11"
},
"type": "invoice.payment_succeeded"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_reason": "manual",
"charge": null,
"collection_method": "charge_automatically",
"created": 1000000000,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
"customer_tax_exempt": "none",
"customer_tax_ids": [],
"default_payment_method": null,
"default_source": null,
"default_tax_rates": [],
"description": null,
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjE502004xw8zwol?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjE502004xw8zwol/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 36000,
"amount_excluding_tax": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 6000,
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "6000"
},
{
"amount": -36000,
"amount_excluding_tax": -36000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0003",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
"period_end": 1000000000,
"period_start": 1000000000,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "letter"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "paid",
"status_transitions": {
"finalized_at": 1000000000,
"marked_uncollectible_at": null,
"paid_at": 1000000000,
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": null
}
},
"id": "evt_1OIlclDEQaroqDjsWkTK3kjn",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0009",
"idempotency_key": "5fb5bcd9-f037-402f-b67a-6f8eb89f3c11"
},
"type": "invoice.paid"
}
],
"has_more": false,
"object": "list",
"url": "/v1/events"

View File

@@ -41,7 +41,7 @@
"footer": null,
"from_invoice": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000002",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": null,
"last_finalization_error": null,
"latest_revision": null,
@@ -61,8 +61,8 @@
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -151,7 +151,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},

View File

@@ -41,7 +41,7 @@
"footer": null,
"from_invoice": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000003",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": null,
"last_finalization_error": null,
"latest_revision": null,
@@ -151,7 +151,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},

View File

@@ -41,7 +41,7 @@
"footer": null,
"from_invoice": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000004",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": null,
"last_finalization_error": null,
"latest_revision": null,
@@ -101,7 +101,7 @@
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000004/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},

View File

@@ -40,9 +40,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUx0200otAxmBG3?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUx0200otAxmBG3/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjA502000hPybKLo?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjA502000hPybKLo/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -61,8 +61,8 @@
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -151,7 +151,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
@@ -204,5 +204,5 @@
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}

View File

@@ -40,9 +40,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6?s=ap",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjE502004xw8zwol?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjE502004xw8zwol/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -151,7 +151,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},

View File

@@ -40,9 +40,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyM1ZjWEROSGJMNWtrMHFKNFY5ZW1DekRtM2cxLDkwMDkwNTc3020054DiyClm?s=ap",
"id": "in_NORMALIZED00000000000004",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyM1ZjWEROSGJMNWtrMHFKNFY5ZW1DekRtM2cxLDkwMDkwNTc3020054DiyClm/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpjd2l4VnY1aGVsY1Ztb3BRODN5dGRHbGhpNk02LDkyMDM1MjIy0200dqk7fzRz?s=ap",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpjd2l4VnY1aGVsY1Ztb3BRODN5dGRHbGhpNk02LDkyMDM1MjIy0200dqk7fzRz/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -101,7 +101,7 @@
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000004/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},
@@ -111,7 +111,7 @@
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": "pi_NORMALIZED00000000000004",
"payment_intent": "pi_NORMALIZED00000000000003",
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
@@ -154,5 +154,5 @@
"total_excluding_tax": 24000,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}

View File

@@ -42,9 +42,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTU00200ozOzXCq5?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTU00200ozOzXCq5/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjEx0200eb8tuh13?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjEx0200eb8tuh13/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -63,8 +63,8 @@
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -153,7 +153,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},

View File

@@ -42,9 +42,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcz0200NuJEg74w?s=ap",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcz0200NuJEg74w/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjIx0200OMBLJkxA?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjIx0200OMBLJkxA/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -153,7 +153,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
@@ -250,9 +250,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTcz0200z2u9pNME?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTcz0200z2u9pNME/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjIx02006mE2ERoT?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjIx02006mE2ERoT/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -271,8 +271,8 @@
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -361,7 +361,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},

View File

@@ -42,9 +42,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyM1ZjWEROSGJMNWtrMHFKNFY5ZW1DekRtM2cxLDkwMDkwNTc40200Sj4IFpmz?s=ap",
"id": "in_NORMALIZED00000000000004",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyM1ZjWEROSGJMNWtrMHFKNFY5ZW1DekRtM2cxLDkwMDkwNTc40200Sj4IFpmz/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpjd2l4VnY1aGVsY1Ztb3BRODN5dGRHbGhpNk02LDkyMDM1MjIz02008PWTxXb7?s=ap",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpjd2l4VnY1aGVsY1Ztb3BRODN5dGRHbGhpNk02LDkyMDM1MjIz02008PWTxXb7/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -103,7 +103,7 @@
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000004/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},
@@ -113,7 +113,7 @@
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": "pi_NORMALIZED00000000000004",
"payment_intent": "pi_NORMALIZED00000000000003",
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
@@ -156,7 +156,7 @@
"total_excluding_tax": 24000,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
},
{
"account_country": "US",
@@ -200,9 +200,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTc40200bk3mcbnW?s=ap",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTc40200bk3mcbnW/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjIz0200JbHnRP6C?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiZG84bnRuUUtrVXRnN0pvOEFlUkJqY0ZBZFhCLDkyMDM1MjIz0200JbHnRP6C/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -311,7 +311,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
@@ -408,9 +408,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTc40200iIpWcfWk?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTc40200iIpWcfWk/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjIz0200ykr4PqiR?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpiMHNFNHp4OGxtTUNIclNiWWdZa0NQNmpqMVRJLDkyMDM1MjIz0200ykr4PqiR/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -429,8 +429,8 @@
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -519,7 +519,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},

View File

@@ -12,8 +12,8 @@
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {

View File

@@ -1,197 +0,0 @@
{
"amount": 7200,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 7200,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"data": [
{
"amount": 7200,
"amount_captured": 7200,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_NORMALIZED00000000000001",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $12.0 x 6",
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
"id": "ch_NORMALIZED00000000000001",
"invoice": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"object": "charge",
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 0,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"incremental_authorization": {
"status": "unavailable"
},
"installments": null,
"last4": "4242",
"mandate": null,
"multicapture": {
"status": "unavailable"
},
"network": "visa",
"network_token": {
"used": false
},
"overcapture": {
"maximum_amount_capturable": 7200,
"status": "unavailable"
},
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKLKktKoGMgYimrXpHqA6LBbX51UEvwndRXqUkshrzze-vtY1kaBfu3kQ_cy4eOwD7ZOJ0MTItvKdslAN",
"refunded": false,
"refunds": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges/ch_NORMALIZED00000000000001/refunds"
},
"review": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000001"
},
"client_secret": "pi_NORMALIZED00000000000001_secret_U9fLWaQ5foMLX6li0E7nIl4wX",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $12.0 x 6",
"id": "pi_NORMALIZED00000000000001",
"invoice": null,
"last_payment_error": null,
"latest_charge": "ch_NORMALIZED00000000000001",
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}

View File

@@ -1,197 +0,0 @@
{
"amount": 36000,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 36000,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"data": [
{
"amount": 36000,
"amount_captured": 36000,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_NORMALIZED00000000000002",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $60.0 x 6",
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
"id": "ch_NORMALIZED00000000000002",
"invoice": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"object": "charge",
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 0,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method_details": {
"card": {
"amount_authorized": 36000,
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"incremental_authorization": {
"status": "unavailable"
},
"installments": null,
"last4": "4242",
"mandate": null,
"multicapture": {
"status": "unavailable"
},
"network": "visa",
"network_token": {
"used": false
},
"overcapture": {
"maximum_amount_capturable": 36000,
"status": "unavailable"
},
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKMWktKoGMgb3nDVqmxw6LBbhC0PW9T-U53VSB9Q3DZCtbnQ72IGDALfDXWC-jcTkpT6EUGI9HGa9d-fo",
"refunded": false,
"refunds": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges/ch_NORMALIZED00000000000002/refunds"
},
"review": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_IM9QK0XPBRsrtvMFpF1HVQGr3",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $60.0 x 6",
"id": "pi_NORMALIZED00000000000002",
"invoice": null,
"last_payment_error": null,
"latest_charge": "ch_NORMALIZED00000000000002",
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}

View File

@@ -4,7 +4,7 @@
"amount_details": {
"tip": {}
},
"amount_received": 0,
"amount_received": 7200,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
@@ -12,13 +12,140 @@
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"data": [],
"data": [
{
"amount": 7200,
"amount_captured": 7200,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_NORMALIZED00000000000001",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $12.0 x 6",
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
"id": "ch_NORMALIZED00000000000001",
"invoice": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"object": "charge",
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 0,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"incremental_authorization": {
"status": "unavailable"
},
"installments": null,
"last4": "4242",
"mandate": null,
"multicapture": {
"status": "unavailable"
},
"network": "visa",
"network_token": {
"used": false
},
"overcapture": {
"maximum_amount_capturable": 7200,
"status": "unavailable"
},
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"radar_options": {},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKIf9qqsGMgYln1CvRQg6LBas64aXppcCqDXm8lE0Sbd6BM7omStVTXKhathHXWOU3wC8kyUuXRRePlvZ",
"refunded": false,
"refunds": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges/ch_NORMALIZED00000000000001/refunds"
},
"review": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
],
"has_more": false,
"object": "list",
"total_count": 0,
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000001"
},
"client_secret": "pi_NORMALIZED00000000000001_secret_U9fLWaQ5foMLX6li0E7nIl4wX",
"client_secret": "pi_NORMALIZED00000000000001_secret_TT71wq3meHmzTEd7mpZbqrDnP",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -27,13 +154,14 @@
"id": "pi_NORMALIZED00000000000001",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"latest_charge": "ch_NORMALIZED00000000000001",
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -45,7 +173,7 @@
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
@@ -66,7 +194,7 @@
"source": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "requires_payment_method",
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}

View File

@@ -4,7 +4,7 @@
"amount_details": {
"tip": {}
},
"amount_received": 0,
"amount_received": 36000,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
@@ -12,13 +12,140 @@
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"data": [],
"data": [
{
"amount": 36000,
"amount_captured": 36000,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_NORMALIZED00000000000002",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $60.0 x 6",
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
"id": "ch_NORMALIZED00000000000002",
"invoice": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"object": "charge",
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 0,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_details": {
"card": {
"amount_authorized": 36000,
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"incremental_authorization": {
"status": "unavailable"
},
"installments": null,
"last4": "4242",
"mandate": null,
"multicapture": {
"status": "unavailable"
},
"network": "visa",
"network_token": {
"used": false
},
"overcapture": {
"maximum_amount_capturable": 36000,
"status": "unavailable"
},
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"radar_options": {},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKJD9qqsGMgahoOxs2tY6LBYE_7mVLMS2-_xQ_C_j9KSayaWsbJ6jjdpSQjsrQImBFFAZjT-6A-PbNsDv",
"refunded": false,
"refunds": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges/ch_NORMALIZED00000000000002/refunds"
},
"review": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
],
"has_more": false,
"object": "list",
"total_count": 0,
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_IM9QK0XPBRsrtvMFpF1HVQGr3",
"client_secret": "pi_NORMALIZED00000000000002_secret_6P4eLudA3uvQYCOoNiDTkNQEI",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -27,13 +154,14 @@
"id": "pi_NORMALIZED00000000000002",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"latest_charge": "ch_NORMALIZED00000000000002",
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
@@ -45,7 +173,7 @@
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
@@ -66,7 +194,7 @@
"source": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "requires_payment_method",
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}

View File

@@ -2,21 +2,21 @@
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbiyDEQaroqDjsEnpR1Onp_secret_OyYqFrkey3OlMfxWtyreL2AbO9RHVB4",
"client_secret": "seti_1OIlcUDEQaroqDjsvk7SRRcH_secret_P6zbAgeO1YNtM2cmjYChSjYCWBsUOAB",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbiyDEQaroqDjsEnpR1Onp",
"id": "seti_1OIlcUDEQaroqDjsvk7SRRcH",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbiyDEQaroqDjsnLExjW56",
"latest_attempt": "setatt_1OIlcUDEQaroqDjsLAiOPmHx",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {

View File

@@ -2,21 +2,21 @@
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjHDEQaroqDjsiyGUfkGg_secret_OyYq1ljcbx3AZiCSK143sZdt7S0n6pN",
"client_secret": "seti_1OIlceDEQaroqDjsF69R2WG0_secret_P6zbftiDh6C2B0Le8GK6ALVcHby9Gyu",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjHDEQaroqDjsiyGUfkGg",
"id": "seti_1OIlceDEQaroqDjsF69R2WG0",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbjHDEQaroqDjswrjSFGn9",
"latest_attempt": "setatt_1OIlceDEQaroqDjs1hVqql0s",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {

View File

@@ -4,12 +4,12 @@
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbixDEQaroqDjsZ13bFTzk_secret_OyYqTKT3uNo6hXb6nycNN82HUV4tLVa",
"client_secret": "seti_1OIlcTDEQaroqDjs7K1pjsyq_secret_P6zb4X2b8va5VZDxRMR3CgIYMwreYzC",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbixDEQaroqDjsZ13bFTzk",
"id": "seti_1OIlcTDEQaroqDjs7K1pjsyq",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,

View File

@@ -4,12 +4,12 @@
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjFDEQaroqDjsOtWXOh3z_secret_OyYqDwZSEotDZcPqC8EEXDlXcWpwH0i",
"client_secret": "seti_1OIlcdDEQaroqDjsB4ugOdVp_secret_P6zbipSmx7qqSUGMRGqVxyYPXoN42bP",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjFDEQaroqDjsOtWXOh3z",
"id": "seti_1OIlcdDEQaroqDjsB4ugOdVp",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,

View File

@@ -2,21 +2,21 @@
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbiyDEQaroqDjsEnpR1Onp_secret_OyYqFrkey3OlMfxWtyreL2AbO9RHVB4",
"client_secret": "seti_1OIlcUDEQaroqDjsvk7SRRcH_secret_P6zbAgeO1YNtM2cmjYChSjYCWBsUOAB",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbiyDEQaroqDjsEnpR1Onp",
"id": "seti_1OIlcUDEQaroqDjsvk7SRRcH",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbiyDEQaroqDjsnLExjW56",
"latest_attempt": "setatt_1OIlcUDEQaroqDjsLAiOPmHx",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {

View File

@@ -2,21 +2,21 @@
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjHDEQaroqDjsiyGUfkGg_secret_OyYq1ljcbx3AZiCSK143sZdt7S0n6pN",
"client_secret": "seti_1OIlceDEQaroqDjsF69R2WG0_secret_P6zbftiDh6C2B0Le8GK6ALVcHby9Gyu",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjHDEQaroqDjsiyGUfkGg",
"id": "seti_1OIlceDEQaroqDjsF69R2WG0",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbjHDEQaroqDjswrjSFGn9",
"latest_attempt": "setatt_1OIlceDEQaroqDjs1hVqql0s",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {

View File

@@ -8,7 +8,7 @@
"status": null
},
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/billing/",
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
@@ -34,22 +34,13 @@
},
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED02Z5ZWoaj9BNmgBmGSOWkzCIOee3905mO0A4AaeSETi2qfDk",
"id": "cs_test_NORMALIZED02sb4yHaq28VHOZscOtqKWTLoGQJALldTi43GUE0P56ay9sU",
"invoice": null,
"invoice_creation": null,
"livemode": false,
"locale": null,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"type": "card_update",
"user_id": "10"
},
"mode": "setup",
@@ -67,7 +58,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1OAbixDEQaroqDjsZ13bFTzk",
"setup_intent": "seti_1OIlcTDEQaroqDjs7K1pjsyq",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -78,5 +69,5 @@
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED02Z5ZWoaj9BNmgBmGSOWkzCIOee3905mO0A4AaeSETi2qfDk#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED02sb4yHaq28VHOZscOtqKWTLoGQJALldTi43GUE0P56ay9sU#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}

View File

@@ -8,7 +8,7 @@
"status": null
},
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/billing/",
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
@@ -34,22 +34,13 @@
},
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED03LS5wbvnBYGdnCEW5t1ot0jYJyM0nR7L9zyMjaPa4nlbim5",
"id": "cs_test_NORMALIZED03vZmJUBP0ybiRb1yrtpVKGaX0cm1V0nhnpM6e5pcI1y8CkG",
"invoice": null,
"invoice_creation": null,
"livemode": false,
"locale": null,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"type": "card_update",
"user_id": "10"
},
"mode": "setup",
@@ -67,7 +58,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1OAbjFDEQaroqDjsOtWXOh3z",
"setup_intent": "seti_1OIlcdDEQaroqDjsB4ugOdVp",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -78,5 +69,5 @@
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED03LS5wbvnBYGdnCEW5t1ot0jYJyM0nR7L9zyMjaPa4nlbim5#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED03vZmJUBP0ybiRb1yrtpVKGaX0cm1V0nhnpM6e5pcI1y8CkG#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}

View File

@@ -10,7 +10,7 @@
"status": null
},
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/billing/",
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
@@ -36,22 +36,13 @@
},
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED02Z5ZWoaj9BNmgBmGSOWkzCIOee3905mO0A4AaeSETi2qfDk",
"id": "cs_test_NORMALIZED02sb4yHaq28VHOZscOtqKWTLoGQJALldTi43GUE0P56ay9sU",
"invoice": null,
"invoice_creation": null,
"livemode": false,
"locale": null,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"type": "card_update",
"user_id": "10"
},
"mode": "setup",
@@ -69,7 +60,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1OAbixDEQaroqDjsZ13bFTzk",
"setup_intent": "seti_1OIlcTDEQaroqDjs7K1pjsyq",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -80,7 +71,7 @@
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED02Z5ZWoaj9BNmgBmGSOWkzCIOee3905mO0A4AaeSETi2qfDk#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED02sb4yHaq28VHOZscOtqKWTLoGQJALldTi43GUE0P56ay9sU#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}
],
"has_more": true,

View File

@@ -10,7 +10,7 @@
"status": null
},
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/billing/",
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
@@ -36,22 +36,13 @@
},
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED03LS5wbvnBYGdnCEW5t1ot0jYJyM0nR7L9zyMjaPa4nlbim5",
"id": "cs_test_NORMALIZED03vZmJUBP0ybiRb1yrtpVKGaX0cm1V0nhnpM6e5pcI1y8CkG",
"invoice": null,
"invoice_creation": null,
"livemode": false,
"locale": null,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"type": "card_update",
"user_id": "10"
},
"mode": "setup",
@@ -69,7 +60,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1OAbjFDEQaroqDjsOtWXOh3z",
"setup_intent": "seti_1OIlcdDEQaroqDjsB4ugOdVp",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -80,7 +71,7 @@
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED03LS5wbvnBYGdnCEW5t1ot0jYJyM0nR7L9zyMjaPa4nlbim5#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED03vZmJUBP0ybiRb1yrtpVKGaX0cm1V0nhnpM6e5pcI1y8CkG#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}
],
"has_more": true,

View File

@@ -8,7 +8,7 @@
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"email": "othello@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {

View File

@@ -8,12 +8,12 @@
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"email": "othello@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"default_payment_method": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"footer": null,
"rendering_options": null
},

View File

@@ -2,13 +2,13 @@
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"email": "othello@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
@@ -35,7 +35,7 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
@@ -54,7 +54,7 @@
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"id": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"livemode": false,
"metadata": {},
"object": "payment_method",
@@ -69,7 +69,7 @@
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],

View File

@@ -2,13 +2,13 @@
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"email": "othello@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
@@ -35,7 +35,7 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
@@ -54,7 +54,7 @@
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"id": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"livemode": false,
"metadata": {},
"object": "payment_method",
@@ -69,7 +69,7 @@
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "othello@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "othello@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "othello@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -0,0 +1,79 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "othello@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -5,273 +5,53 @@
"created": 1000000000,
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 80697,
"amount_paid": 0,
"amount_remaining": 80697,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": true,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_reason": "manual",
"charge": null,
"collection_method": "charge_automatically",
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0002",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
"customer_tax_exempt": "none",
"customer_tax_ids": [],
"default_payment_method": null,
"currency": null,
"default_currency": null,
"default_source": null,
"default_tax_rates": [],
"description": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqaGQ3elJQNENCaVZHMVZqdGVyMzFmWFJZck1MLDkwMDkwMTE002002tYuWb47?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqaGQ3elJQNENCaVZHMVZqdGVyMzFmWFJZck1MLDkwMDkwMTE002002tYuWb47/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 7255,
"amount_excluding_tax": 7255,
"currency": "usd",
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1388631845,
"start": 1360033445
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 7255,
"unit_amount_decimal": "7255"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "7255"
},
{
"amount": 56000,
"amount_excluding_tax": 56000,
"currency": "usd",
"description": "Zulip Cloud Standard - renewal",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1388631845,
"start": 1357095845
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 8000,
"unit_amount_decimal": "8000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 7,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "8000"
},
{
"amount": 17442,
"amount_excluding_tax": 17442,
"currency": "usd",
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1357095845,
"start": 1334113445
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 5814,
"unit_amount_decimal": "5814"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 3,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "5814"
}
],
"has_more": false,
"object": "list",
"total_count": 3,
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
"email": "othello@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {},
"next_payment_attempt": 1000000000,
"number": "NORMALI-0001",
"object": "invoice",
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"period_end": 1000000000,
"period_start": 1000000000,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "letter"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "open",
"status_transitions": {
"finalized_at": 1000000000,
"marked_uncollectible_at": null,
"paid_at": null,
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 80697,
"subtotal_excluding_tax": 80697,
"tax": null,
"test_clock": null,
"total": 80697,
"total_discount_amounts": [],
"total_excluding_tax": 80697,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
},
"previous_attributes": {
"invoice_settings": {
"default_payment_method": null
}
}
},
"id": "evt_1OAbc7DEQaroqDjsSb1CrgmS",
"id": "evt_1OIlXjDEQaroqDjsLhrUEUe3",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0001",
"idempotency_key": "7cd9d940-ede9-458e-b191-b3310133d9da"
"idempotency_key": "12b7d34b-4958-462b-8fda-e2f6c17957bd"
},
"type": "invoice.finalized"
"type": "customer.updated"
}
],
"has_more": true,

View File

@@ -61,13 +61,14 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "8000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
"user_email": "othello@zulip.com",
"user_id": "12"
},
"object": "charge",
"on_behalf_of": null,
@@ -81,8 +82,8 @@
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"payment_method_details": {
"card": {
"amount_authorized": 48000,
@@ -93,7 +94,7 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
@@ -122,9 +123,10 @@
},
"type": "card"
},
"receipt_email": "hamlet@zulip.com",
"radar_options": {},
"receipt_email": "othello@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKI6htKoGMgZEJv0Gk_M6LBbsy-cE95lt_YeePEQfQ75WiLgzUKjcC64s0_AWS2B4Sh8s6V3ZVbiZxriI",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKN76qqsGMgYgrE0XQWw6LBau_ilRx6BPEf5zlgCHW2ojzwBSlxtCQxmlmII5DkhOSXuAsc1VGTmDFZd6",
"refunded": false,
"refunds": {
"data": [],
@@ -147,15 +149,15 @@
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000001"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_GjSC2H9KhGusuka7dHcnSXXMq",
"client_secret": "pi_NORMALIZED00000000000001_secret_gaaXxocbBjOepNzIQyoHlMcVE",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $80.0 x 6",
"id": "pi_NORMALIZED00000000000002",
"id": "pi_NORMALIZED00000000000001",
"invoice": null,
"last_payment_error": null,
"latest_charge": "ch_NORMALIZED00000000000001",
@@ -165,18 +167,19 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "8000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
"user_email": "othello@zulip.com",
"user_id": "12"
},
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"payment_method": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
@@ -190,7 +193,7 @@
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"receipt_email": "othello@zulip.com",
"review": null,
"setup_future_usage": null,
"shipping": null,
@@ -202,13 +205,13 @@
"transfer_group": null
}
},
"id": "evt_3OAbcCDEQaroqDjs00CvOf1K",
"id": "evt_3OIlXlDEQaroqDjs0FGYzTr9",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0002",
"idempotency_key": "c74e41ba-0d59-462f-901d-3653b9691ddd"
"idempotency_key": "899e9bd5-f82b-4ed6-a09b-e04dba8c0edd"
},
"type": "payment_intent.succeeded"
},
@@ -258,13 +261,14 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "8000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
"user_email": "othello@zulip.com",
"user_id": "12"
},
"object": "charge",
"on_behalf_of": null,
@@ -278,8 +282,8 @@
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"payment_method_details": {
"card": {
"amount_authorized": 48000,
@@ -290,7 +294,7 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
@@ -319,9 +323,10 @@
},
"type": "card"
},
"receipt_email": "hamlet@zulip.com",
"radar_options": {},
"receipt_email": "othello@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKI6htKoGMgZcUtV8GJI6LBaeeZaDfZ9Pvd8H6r0A8x9w31_WpGcCgXdwg87kKfcDvl-uO4ENXJjqxjaC",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKN76qqsGMgYp5rJn4Pg6LBYAjHCItgf9iiKG_mmqSy1FuggXUU5fXaGzNuMWlKsIBYlUVnaYe37Fid2s",
"refunded": false,
"refunds": {
"data": [],
@@ -341,412 +346,15 @@
"transfer_group": null
}
},
"id": "evt_3OAbcCDEQaroqDjs0gbReOHa",
"id": "evt_3OIlXlDEQaroqDjs0F5rvmUM",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0002",
"idempotency_key": "c74e41ba-0d59-462f-901d-3653b9691ddd"
"idempotency_key": "899e9bd5-f82b-4ed6-a09b-e04dba8c0edd"
},
"type": "charge.succeeded"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"address": null,
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
},
"previous_attributes": {
"invoice_settings": {
"default_payment_method": null
}
}
},
"id": "evt_1OAbcGDEQaroqDjs0wggukPI",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0003",
"idempotency_key": "f914e846-173a-4145-b71c-c2deddb69419"
},
"type": "customer.updated"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbcFDEQaroqDjsQfMm42ws_secret_OyYjgcW1HC7Fs8iRLfn8iQ35FmvPJmH",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbcFDEQaroqDjsQfMm42ws",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbcFDEQaroqDjsJ6wKO7Zx",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "succeeded",
"usage": "off_session"
}
},
"id": "evt_1OAbcGDEQaroqDjsPyjXOCAc",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "e1214b92-0e5f-42b2-a9ad-fa7d7b905542"
},
"type": "setup_intent.succeeded"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
}
},
"id": "evt_1OAbcFDEQaroqDjs39qXJFeQ",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "e1214b92-0e5f-42b2-a9ad-fa7d7b905542"
},
"type": "payment_method.attached"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbcFDEQaroqDjsQfMm42ws_secret_OyYjgcW1HC7Fs8iRLfn8iQ35FmvPJmH",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbcFDEQaroqDjsQfMm42ws",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbcFDEQaroqDjsJ6wKO7Zx",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "succeeded",
"usage": "off_session"
}
},
"id": "evt_1OAbcGDEQaroqDjsOY11fmt3",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "e1214b92-0e5f-42b2-a9ad-fa7d7b905542"
},
"type": "setup_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbcDDEQaroqDjs6URoWzLY_secret_OyYj6KwwzRa2rP52fvEQaNH3OWcEQfU",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbcDDEQaroqDjs6URoWzLY",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "requires_payment_method",
"usage": "off_session"
}
},
"id": "evt_1OAbcDDEQaroqDjs40IfyFuo",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0005",
"idempotency_key": "ef53c743-c734-4676-afd4-496344b54399"
},
"type": "setup_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"amount": 48000,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 0,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_GjSC2H9KhGusuka7dHcnSXXMq",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $80.0 x 6",
"id": "pi_NORMALIZED00000000000002",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "8000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "requires_payment_method",
"transfer_data": null,
"transfer_group": null
}
},
"id": "evt_3OAbcCDEQaroqDjs0HuorQuk",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0006",
"idempotency_key": "7c94e31e-d95d-4c8a-870f-222f7c739459"
},
"type": "payment_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"address": null,
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": null,
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
}
},
"id": "evt_1OAbcADEQaroqDjs9BctiGlP",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0007",
"idempotency_key": "ed7dbb33-9d63-48ca-a4a8-d77bb716bb9f"
},
"type": "customer.created"
}
],
"has_more": false,

View File

@@ -29,7 +29,7 @@
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_email": "othello@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
@@ -46,9 +46,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqR2NXRUhCcElNVjQxUkMxTVhsa3NTaTNqVklULDkwMDkwMTMy02005Nx2aWJ6?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqR2NXRUhCcElNVjQxUkMxTVhsa3NTaTNqVklULDkwMDkwMTMy02005Nx2aWJ6/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpXWlpYcFFGQklGamtRUExacnBFdXlvU0I2dlZTLDkyMDM0OTEz0200nMuZd38e?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpXWlpYcFFGQklGamtRUExacnBFdXlvU0I2dlZTLDkyMDM0OTEz0200nMuZd38e/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -61,14 +61,14 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -77,13 +77,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -111,8 +111,8 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -127,13 +127,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -157,12 +157,12 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"number": "NORMALI-0001",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
@@ -210,7 +210,7 @@
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
},
"previous_attributes": {
"attempted": false,
@@ -234,13 +234,13 @@
}
}
},
"id": "evt_1OAbcODEQaroqDjs31hyuOmh",
"id": "evt_1OIlXpDEQaroqDjsFjFkC07g",
"livemode": false,
"object": "event",
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "2541b338-2700-4407-bd3e-79c1c2740aca"
"id": "req_NORMALIZED0003",
"idempotency_key": "8b119345-6159-4607-9010-e8237d20823b"
},
"type": "invoice.updated"
},
@@ -273,7 +273,7 @@
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_email": "othello@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
@@ -291,7 +291,7 @@
"footer": null,
"from_invoice": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000002",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": null,
"last_finalization_error": null,
"latest_revision": null,
@@ -305,14 +305,14 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -321,13 +321,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -355,8 +355,8 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -371,13 +371,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -401,7 +401,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
@@ -457,13 +457,13 @@
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbcNDEQaroqDjsmnQWayuN",
"id": "evt_1OIlXoDEQaroqDjsll2CgAs8",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0009",
"idempotency_key": "39ea7e83-6ae7-458b-9e77-9cd5f6990425"
"id": "req_NORMALIZED0004",
"idempotency_key": "294b5729-e1c0-42e1-b77a-2545f8f7006c"
},
"type": "invoice.created"
},
@@ -479,14 +479,14 @@
"description": "Zulip Cloud Standard",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000004",
"id": "ii_NORMALIZED00000000000001",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -495,13 +495,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -519,13 +519,13 @@
"unit_amount_decimal": "8000"
}
},
"id": "evt_1OAbcMDEQaroqDjstamLiIzo",
"id": "evt_1OIlXnDEQaroqDjsoAX0aCzf",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0010",
"idempotency_key": "c7cee662-36c4-4ef2-886d-4091ee4762fa"
"id": "req_NORMALIZED0005",
"idempotency_key": "4ead5392-1ff9-44e0-943e-cd705accc213"
},
"type": "invoiceitem.created"
},
@@ -541,7 +541,7 @@
"description": "Payment (Card ending in 4242)",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000005",
"id": "ii_NORMALIZED00000000000002",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -557,13 +557,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -581,13 +581,13 @@
"unit_amount_decimal": "-48000"
}
},
"id": "evt_1OAbcKDEQaroqDjsEApMF8PG",
"id": "evt_1OIlXnDEQaroqDjsQ5wMmZEb",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0011",
"idempotency_key": "a5d39edf-e1d3-4ece-88e6-3ec255393b99"
"id": "req_NORMALIZED0006",
"idempotency_key": "e5d2c5ec-9f6f-4f28-b5b9-e43e3f58b653"
},
"type": "invoiceitem.created"
},
@@ -605,12 +605,12 @@
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"email": "othello@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"default_payment_method": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"footer": null,
"rendering_options": null
},
@@ -633,13 +633,13 @@
"default_currency": null
}
},
"id": "evt_1OAbcKDEQaroqDjsJsD9kXvM",
"id": "evt_1OIlXnDEQaroqDjsKXCIWpVq",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0011",
"idempotency_key": "a5d39edf-e1d3-4ece-88e6-3ec255393b99"
"id": "req_NORMALIZED0006",
"idempotency_key": "e5d2c5ec-9f6f-4f28-b5b9-e43e3f58b653"
},
"type": "customer.updated"
}

View File

@@ -29,7 +29,7 @@
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_email": "othello@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
@@ -46,9 +46,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqR2NXRUhCcElNVjQxUkMxTVhsa3NTaTNqVklULDkwMDkwMTMz0200p4BwL0Y9?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqR2NXRUhCcElNVjQxUkMxTVhsa3NTaTNqVklULDkwMDkwMTMz0200p4BwL0Y9/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpXWlpYcFFGQklGamtRUExacnBFdXlvU0I2dlZTLDkyMDM0OTEz0200nMuZd38e?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpXWlpYcFFGQklGamtRUExacnBFdXlvU0I2dlZTLDkyMDM0OTEz0200nMuZd38e/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -61,14 +61,14 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -77,13 +77,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -111,8 +111,8 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -127,13 +127,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -157,12 +157,12 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0003",
"number": "NORMALI-0002",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
@@ -210,16 +210,16 @@
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbcPDEQaroqDjsb2wdOfq8",
"id": "evt_1OIlXpDEQaroqDjsMH9F6Vqj",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "2541b338-2700-4407-bd3e-79c1c2740aca"
"id": "req_NORMALIZED0003",
"idempotency_key": "8b119345-6159-4607-9010-e8237d20823b"
},
"type": "invoice.payment_succeeded"
},
@@ -252,7 +252,7 @@
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_email": "othello@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
@@ -269,9 +269,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqR2NXRUhCcElNVjQxUkMxTVhsa3NTaTNqVklULDkwMDkwMTMz0200p4BwL0Y9?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqR2NXRUhCcElNVjQxUkMxTVhsa3NTaTNqVklULDkwMDkwMTMz0200p4BwL0Y9/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpXWlpYcFFGQklGamtRUExacnBFdXlvU0I2dlZTLDkyMDM0OTEz0200nMuZd38e?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpXWlpYcFFGQklGamtRUExacnBFdXlvU0I2dlZTLDkyMDM0OTEz0200nMuZd38e/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -284,14 +284,14 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -300,13 +300,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -334,8 +334,8 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -350,13 +350,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -380,12 +380,12 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0003",
"number": "NORMALI-0002",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
@@ -433,16 +433,16 @@
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbcPDEQaroqDjstFj6KICr",
"id": "evt_1OIlXpDEQaroqDjsuTbOhFCj",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "2541b338-2700-4407-bd3e-79c1c2740aca"
"id": "req_NORMALIZED0003",
"idempotency_key": "8b119345-6159-4607-9010-e8237d20823b"
},
"type": "invoice.paid"
},
@@ -475,7 +475,7 @@
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_email": "othello@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
@@ -492,9 +492,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqR2NXRUhCcElNVjQxUkMxTVhsa3NTaTNqVklULDkwMDkwMTMz0200p4BwL0Y9?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqR2NXRUhCcElNVjQxUkMxTVhsa3NTaTNqVklULDkwMDkwMTMz0200p4BwL0Y9/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpXWlpYcFFGQklGamtRUExacnBFdXlvU0I2dlZTLDkyMDM0OTEz0200nMuZd38e?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpXWlpYcFFGQklGamtRUExacnBFdXlvU0I2dlZTLDkyMDM0OTEz0200nMuZd38e/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -507,14 +507,14 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -523,13 +523,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -557,8 +557,8 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -573,13 +573,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -603,12 +603,12 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0003",
"number": "NORMALI-0002",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
@@ -656,16 +656,16 @@
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbcPDEQaroqDjs83DzADF2",
"id": "evt_1OIlXpDEQaroqDjs9DRA4TlG",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "2541b338-2700-4407-bd3e-79c1c2740aca"
"id": "req_NORMALIZED0003",
"idempotency_key": "8b119345-6159-4607-9010-e8237d20823b"
},
"type": "invoice.finalized"
}

View File

@@ -23,7 +23,7 @@
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_email": "othello@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
@@ -41,7 +41,7 @@
"footer": null,
"from_invoice": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000002",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": null,
"last_finalization_error": null,
"latest_revision": null,
@@ -55,14 +55,14 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -71,13 +71,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -105,8 +105,8 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -121,13 +121,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -151,7 +151,7 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},

View File

@@ -23,7 +23,7 @@
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_email": "othello@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
@@ -40,9 +40,9 @@
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqR2NXRUhCcElNVjQxUkMxTVhsa3NTaTNqVklULDkwMDkwMTMy02005Nx2aWJ6?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlqR2NXRUhCcElNVjQxUkMxTVhsa3NTaTNqVklULDkwMDkwMTMy02005Nx2aWJ6/pdf?s=ap",
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpXWlpYcFFGQklGamtRUExacnBFdXlvU0I2dlZTLDkyMDM0OTEy0200M1ADhNeL?s=ap",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9QNnpXWlpYcFFGQklGamtRUExacnBFdXlvU0I2dlZTLDkyMDM0OTEy0200M1ADhNeL/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
@@ -55,14 +55,14 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -71,13 +71,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -105,8 +105,8 @@
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -121,13 +121,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -151,12 +151,12 @@
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0003",
"number": "NORMALI-0002",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
@@ -204,5 +204,5 @@
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}

View File

@@ -6,7 +6,7 @@
"description": "Payment (Card ending in 4242)",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000005",
"id": "ii_NORMALIZED00000000000002",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -22,13 +22,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,

View File

@@ -6,14 +6,14 @@
"description": "Zulip Cloud Standard",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000004",
"id": "ii_NORMALIZED00000000000001",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -22,13 +22,13 @@
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,

View File

@@ -1,197 +0,0 @@
{
"amount": 48000,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 48000,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"data": [
{
"amount": 48000,
"amount_captured": 48000,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_NORMALIZED00000000000001",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $80.0 x 6",
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
"id": "ch_NORMALIZED00000000000001",
"invoice": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "8000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"object": "charge",
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 0,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"payment_method_details": {
"card": {
"amount_authorized": 48000,
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"incremental_authorization": {
"status": "unavailable"
},
"installments": null,
"last4": "4242",
"mandate": null,
"multicapture": {
"status": "unavailable"
},
"network": "visa",
"network_token": {
"used": false
},
"overcapture": {
"maximum_amount_capturable": 48000,
"status": "unavailable"
},
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKI6htKoGMgZrRJa5p8c6LBaQqU9TvM6TLNfSG8xRaNTk2w014WdBATBpwnd2VeGr6-PH-_sGeCAZZwrq",
"refunded": false,
"refunds": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges/ch_NORMALIZED00000000000001/refunds"
},
"review": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_GjSC2H9KhGusuka7dHcnSXXMq",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $80.0 x 6",
"id": "pi_NORMALIZED00000000000002",
"invoice": null,
"last_payment_error": null,
"latest_charge": "ch_NORMALIZED00000000000001",
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"price_per_license": "8000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}

View File

@@ -4,7 +4,7 @@
"amount_details": {
"tip": {}
},
"amount_received": 0,
"amount_received": 48000,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
@@ -12,40 +12,168 @@
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"data": [],
"data": [
{
"amount": 48000,
"amount_captured": 48000,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_NORMALIZED00000000000001",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $80.0 x 6",
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
"id": "ch_NORMALIZED00000000000001",
"invoice": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "8000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "othello@zulip.com",
"user_id": "12"
},
"object": "charge",
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 0,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"payment_method_details": {
"card": {
"amount_authorized": 48000,
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 12,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"incremental_authorization": {
"status": "unavailable"
},
"installments": null,
"last4": "4242",
"mandate": null,
"multicapture": {
"status": "unavailable"
},
"network": "visa",
"network_token": {
"used": false
},
"overcapture": {
"maximum_amount_capturable": 48000,
"status": "unavailable"
},
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"radar_options": {},
"receipt_email": "othello@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKN76qqsGMgZgaui54Nc6LBbpn6dtN1hXcAVR2ChXte8Ecqdu3cWtk6VikFLgoeo7xJBfgY0LR4CIhJhe",
"refunded": false,
"refunds": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges/ch_NORMALIZED00000000000001/refunds"
},
"review": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000001"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_GjSC2H9KhGusuka7dHcnSXXMq",
"client_secret": "pi_NORMALIZED00000000000001_secret_gaaXxocbBjOepNzIQyoHlMcVE",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"description": "Upgrade to Zulip Cloud Standard, $80.0 x 6",
"id": "pi_NORMALIZED00000000000002",
"id": "pi_NORMALIZED00000000000001",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"latest_charge": "ch_NORMALIZED00000000000001",
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "8000",
"realm_id": "1",
"realm_str": "zulip",
"seat_count": "6",
"type": "upgrade",
"user_email": "hamlet@zulip.com",
"user_id": "10"
"user_email": "othello@zulip.com",
"user_id": "12"
},
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
@@ -59,14 +187,14 @@
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"receipt_email": "othello@zulip.com",
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "requires_payment_method",
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}

View File

@@ -2,21 +2,21 @@
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbcFDEQaroqDjsQfMm42ws_secret_OyYjgcW1HC7Fs8iRLfn8iQ35FmvPJmH",
"client_secret": "seti_1OIlXiDEQaroqDjsvI6zH6rR_secret_P6zWBPBOAxlXilaHazyONXX4vFcGI5s",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbcFDEQaroqDjsQfMm42ws",
"id": "seti_1OIlXiDEQaroqDjsvI6zH6rR",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbcFDEQaroqDjsJ6wKO7Zx",
"latest_attempt": "setatt_1OIlXiDEQaroqDjs8vqJZ53H",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbcFDEQaroqDjsNEmc1jWB",
"payment_method": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {

View File

@@ -4,12 +4,12 @@
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbcDDEQaroqDjs6URoWzLY_secret_OyYj6KwwzRa2rP52fvEQaNH3OWcEQfU",
"client_secret": "seti_1OIlXhDEQaroqDjs1fXXuFlu_secret_P6zWkqWwRlOqpbP3JGSRFT1s92F1sUU",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbcDDEQaroqDjs6URoWzLY",
"id": "seti_1OIlXhDEQaroqDjs1fXXuFlu",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,

Some files were not shown because too many files have changed in this diff Show More