Compare commits

...

1277 Commits

Author SHA1 Message Date
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
Tim Abbott
a9cb0bcba6 Release Zulip Server 8.0-beta1. 2023-11-16 16:59:47 -08:00
Tim Abbott
3cfe4b720c Revert "linkifiers: Match JS implementation to server implementation."
This reverts commit 091e2f177b.

This version of python_to_js_linkifier fails for at least some real
linkifiers. We'll likely re-introduce this after a bit more debugging.
2023-11-16 14:59:48 -08:00
Tim Abbott
43e9bbe31c docs: Update changelog to cover changes staged for 8.0. 2023-11-16 14:58:23 -08:00
Alya Abbott
94ef310106 help: Document that pronouns are now shown in @-mention typeahead. 2023-11-16 14:56:59 -08:00
Alex Vandiver
a782aae78e analytics: Regenerate partial indexes due to Django bug.
Due to a bug[^1] in Django 4.2, fixed in 4.2.6, queries using
`__isnull` added an unnecessary cast.  This cast was _also_ used in
`WHERE` clauses for partial indexes.  This means that partial indexes
created before Zulip was using Django 4.2 (i.e. before Zulip Server
7.0 or 2c20028aa4) will not be used when the server is using Django
4.2.0 through 4.2.5 -- and, conversely, that indexes created while
Zulip had those versions of Django (i.e. Zulip Server 7.0 through 7.4
or 7807bff526) will not be used later.

We re-create the indexes, to ensure that users that installed Zulip
after Zulip Server 7.0 / 2c20028aa4 and before Zulip Server 7.5 /
7807bff526 have indexes which can be used by current Django.  This
is useless work for some installations, but most analytics tables are
not large enough for this to take significant time.

[^1]: https://code.djangoproject.com/ticket/34840
2023-11-16 13:53:04 -08:00
Alex Vandiver
c47ee4a296 zulip_ops: Configure stats to be pushed to status.zulip.com. 2023-11-16 16:21:12 -05:00
Greg Price
528a76a419 push_notifs: Set APNs topic individually on each notification.
This makes it possible to send notifications to more than one app ID
from the same server: for example, the main Zulip mobile app and the
new Flutter-based app, which has a separate app ID for use through its
beta period so that it can be installed alongside the existing app.
2023-11-16 12:03:30 -08:00
Greg Price
40e5243c35 requirements: Support multiple APNs topics in aioapns. 2023-11-16 12:03:30 -08:00
Greg Price
ff32d51700 push_notifs: Support APNs token auth, as well as cert auth.
This will make it possible to send notifications to multiple
distinct app IDs over the same connection.
2023-11-16 12:03:30 -08:00
Alex Vandiver
1af1a6d7e9 rest-endpoints: Add new /api/get-stream-email-address endpoint. 2023-11-16 15:01:43 -05:00
Alex Vandiver
d6e809d185 streams: Fix unsorted import. 2023-11-16 19:02:13 +00:00
Alex Vandiver
9ba5fe2805 Update version and changelog after 7.5 release. 2023-11-16 18:58:45 +00:00
Alex Vandiver
5e4bb69498 subscription_info: Fix typo caught by codespell. 2023-11-16 18:55:31 +00:00
Sahil Batra
dc98136346 streams: Send stream deletion events on unsubscribing users.
This commit adds code to send stream deletion events when
unsubscribing non-admin users from private streams and
when unsubscribing guests from public streams since
non-admins cannot access unsubscribed private streams
and guests cannot access unsubscribed public streams.
2023-11-16 13:25:33 -05:00
Sahil Batra
d4fb244d2d CVE-2023-47642: Invalid metadata access for formerly subscribed streams.
It was discovered by the Zulip development team that active users who
had previously been subscribed to a stream incorrectly continued being
able to use the Zulip API to access metadata for that stream. As a
result, users who had been removed from a stream, but still had an
account in the organization, could still view metadata for that
stream (including the stream name, description, settings, and an email
address used to send emails into the stream via the incoming email
integration). This potentially allowed users to see changes to a
stream’s metadata after they had lost access to the stream.

This bug was present in all Zulip releases prior to today's Zulip
Server 7.5.
2023-11-16 13:25:33 -05:00
Sahil Batra
3c8701ee36 streams: Add API endpoint to get stream email.
This commit adds new API endpoint to get stream email which is
used by the web-app as well to get the email when a user tries
to open the stream email modal.

The stream email is returned only to the users who have access
to it. Specifically for private streams only subscribed users
have access to its email. And for public streams, all non-guest
users and only subscribed guests have access to its email.
All users can access email of web-public streams.
2023-11-16 13:25:33 -05:00
Sahil Batra
432001656e streams: Remove "email_address" field from Subscription objects.
This commit removes "email_address" field from Subscription objects
and we would instead a new endpoint in next commit to get email
address for stream with proper access check.

This change also fixes the bug where we would include email address
for the unsubscribed private stream as well when user did not have
permission to send message to the stream, and having email allowed
the unsubscribed user to send message to the stream.

Note that the unsubscribed user can still send message to the stream
if the user had noted down the email before being unsubscribed
and the stream token is not changed after unsubscribing the user.
2023-11-16 13:25:33 -05:00
Tim Abbott
e6102af351 Revert "lightbox_view: Fix media title update on change in title."
This reverts commit 273081d0a6.

This change broken the arrow key navigation in the lightbox.
2023-11-16 10:19:05 -08:00
David Rosa
21aa5d261a help: Document @ topic mentions.
Documents new wildcard mention for topic participants updating
and making tweaks to all relevant pages.

Fixes #27657.

Co-authored-by: Alya Abbott <alya@zulip.com>
2023-11-16 10:03:41 -08:00
Alya Abbott
d87f47412b help: Define "participate" for automatically following topics.
Fixes #27721.
2023-11-16 10:02:51 -08:00
Prakhar Pratyush
135d7c03cc stripe: Add 'get_billing_page_context' method to 'BillingSession'.
This commit moves the main context creation part of the
'billing_home` view to a new shared
'BillingSession.get_billing_page_context' method.

This refactoring will help in minimizing duplicate code
while supporting both realm and remote_server customers.
2023-11-16 09:48:43 -08:00
Karl Stolley
b04dd62f8a left_sidebar: Preserve DM layout when zoomed into more conversations. 2023-11-16 09:21:48 -08:00
David Rosa
0f157e7e2b docs: Update Spanish translation for "Narrow to".
The word "Filtrar" is ambiguous in this context since it can be
interpreted as "filter out" which is the opposite of what we want
here. "Buscar solo" is a better phrase that we can use unambiguously
and consistently for all instances of "Narrow to".
2023-11-16 09:15:54 -08:00
David Rosa
56a6eee656 help: Document changing home view from left sidebar.
Documents this new feature as a tip for the existing instructions.

Fixes #27655.
2023-11-16 09:15:36 -08:00
Mateusz Mandera
1819b85b85 management: Allow changing is_billing_admin using change_user_role. 2023-11-15 18:36:07 -08:00
Tim Abbott
49eed7540a i18n: Update translation data from Transifex. 2023-11-15 18:01:47 -08:00
Tim Abbott
7a6f288fab narrow: Move maybe_scroll_to_selected call.
This should now happen at the same time it did prior to this change,
without requiring the show_all_message_view wrapper to have any
business logic.

This fixes a potential scroll position bug in the event that
narrow.deactivate in fact calls itself recursively after a timeout.
2023-11-15 17:33:21 -08:00
Tim Abbott
eabab840e9 narrow: Hide inbox/recent views inside deactivate.
We already do a very parallel construction in narrow.activate, so this
moves us towards being able to unify those code paths, while also just
being more readable by avoiding a small-but-important wrapper function
in hashchange.js.

I believe this fixes a bug where we were not saving scroll position in
browser history when navigating to "All messages" from another view.
2023-11-15 17:33:21 -08:00
Tim Abbott
d505a3c8b7 hashchange: Fix duplicate hides of inbox/recent views.
Since at least 6ef0753a51, it's been the
case that narrow.activate already hides the inbox/recent views if
open, and the same is true for all messages.

Fixing the duplicate call is important in show_home_view, because
show_all_message_view relies on having an accurate value for whether the
recent/inbox views were already open in order to correctly update the
left sidebar.
2023-11-15 17:33:21 -08:00
Tim Abbott
a3842584dd hashchange: Fix flicker when navigating to all messages.
Testing experimentally, removing the setTimeout seems to fix a visible
flicker when using Esc to navigate to "All messages" from the Inbox
view. That setTimeout has been moved around without real examination
since 5d79bb6a20 from early 2013; I
don't see any good reason why it would make be necessary only in the
"All messages" code path, and not when narrowing to any other view.
2023-11-15 17:33:21 -08:00
Karl Stolley
62f5806f42 left_sidebar: Ensure All messages highlight on reload.
Co-Authored-By: Tim Abbott <tabbott@zulip.com>
2023-11-15 17:33:21 -08:00
Tim Abbott
0f01eae655 narrow: Simplify actively_scrolling data flow.
There's no good reason to have the caller of deactivate pass this
parameter in.

This effectively reverts a18b1662cb,
which did this as part of trying to avoid an import cycle, with a more
appropriate solution using the existing message_scroll_state module.

Importantly, it also means that we again wait for scrolls longer than
50ms to finish before opening All messages; I think this might fix a
regression.
2023-11-15 17:33:21 -08:00
Tim Abbott
03f3e17a40 narrow: Clarify first parameter to deactivate.
This previous parameter name was inaccurate, since that's not what the
caller is actually asserting for us.
2023-11-15 17:33:21 -08:00
Karl Stolley
f17e4e7198 left_sidebar: Group view-highlight code together. 2023-11-15 17:33:21 -08:00
Anders Kaseorg
c8784634e1 openapi: Remove unnecessary cast.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-15 17:10:48 -08:00
Anders Kaseorg
2fc327b775 compose_ui: Fall back to comma join when Intl.ListFormat is missing.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-15 15:51:33 -08:00
Anders Kaseorg
f4e7a11c35 requirements: Upgrade Python requirements.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-15 15:27:54 -08:00
Anders Kaseorg
a688e753de test_helpers: Fix logging in cursor_executemany mock.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-15 15:27:54 -08:00
Anders Kaseorg
cb9a04d3e3 test_create_video_call: Add missing not None assertions.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-15 15:27:54 -08:00
Anders Kaseorg
7a4ca3135d stripe: Prepare to switch to stripe inline annotations.
https://github.com/stripe/stripe-python/wiki/Inline-type-annotations

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-15 15:27:54 -08:00
Anders Kaseorg
2c9c105bbb db: Check connection type in wrapper_execute.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-15 15:27:54 -08:00
Anders Kaseorg
248ee82db0 migrations: Remove useless null argument for ManyToManyField.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-15 15:27:54 -08:00
Anders Kaseorg
9552014047 models: Include explicit app name in ManyToManyField reference strings.
https://github.com/typeddjango/django-stubs/issues/1802

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-15 15:27:54 -08:00
Karl Stolley
dd938911f7 compose_box: Set auto height on textarea in full screen. 2023-11-15 15:23:48 -08:00
Alya Abbott
681097fd43 docs: Update server roadmap documentation. 2023-11-15 12:46:10 -08:00
Karl Stolley
4493ee346d reactions: Set more explicit reaction-text setting. 2023-11-15 14:02:45 -06:00
Karl Stolley
b374b21171 reactions: Set interactive hover delay on reaction Tippies.
This introduces a new INTERACTIVE_HOVER_DELAY of 425 milliseconds.
It's meant to be short enough that the tooltip's contents are
displayed without too much of a lag, but long enough that a quick
interaction--like +1'ing an existing emoji--happens without the
quick flash of the tooltip itself.
2023-11-15 14:02:45 -06:00
Karl Stolley
6fe49f93ad reactions: Redesign reaction button colors and style.
See CZO discussion:
https://chat.zulip.org/#narrow/stream/9-issues/topic/Reactions.20background.20color/near/1632171

Also:
https://github.com/zulip/zulip/pull/26580#issuecomment-1705734770

Co-Authored-By: Vlad Korobov <terpimost@gmail.com>
2023-11-15 14:02:45 -06:00
Karl Stolley
ff9c931366 reactions: Nuke overwrought inherit declaration.
This is made unnecessary thanks to CSS variables.
2023-11-15 14:02:45 -06:00
Karl Stolley
534683a3ff reactions: Express reaction button colors as CSS vars. 2023-11-15 14:02:45 -06:00
Karl Stolley
474dcf60a2 reactions: Unify stream and DM colors, express as CSS vars. 2023-11-15 14:02:45 -06:00
Karl Stolley
a929220d3c message_edit: Add styles for Save and Cancel buttons. 2023-11-15 10:41:51 -08:00
Karl Stolley
8bc12a4ee2 modal_buttons: Make exit button colors in to CSS variables. 2023-11-15 10:41:51 -08:00
Karl Stolley
f1a79085eb message_edit: Build basic multi-line layout for message editing. 2023-11-15 10:41:51 -08:00
Karl Stolley
6890c9d171 left_sidebar: Place unread count right of All DMs icon.
Also set a CSS variable for header-icon widths in the left sidebar.

Fixes: #27559
2023-11-15 10:07:04 -08:00
Karl Stolley
614abd58be left_sidebar: Place unread count right of stream controls.
Fixes: #27380
2023-11-15 10:07:04 -08:00
Karl Stolley
335790dae8 left_sidebar: Add unread streams count to Streams header. 2023-11-15 10:07:04 -08:00
Tim Abbott
2e2997bd7d typing: Limit typing notifications in large streams. 2023-11-15 09:42:25 -08:00
Prakhar Pratyush
f8a0035215 stripe: Move make_end_of_cycle_updates_if_needed to BillingSession.
Moves the 'make_end_of_cycle_updates_if_needed' function to
the 'BillingSession' abstract class.

This refactoring will help in minimizing duplicate code while
supporting both realm and remote_server customers.

Since the function is called from our main daily billing cron job
as well, we have changed 'RealmBillingSession' to accept 'user=None'
(our convention for automated system jobs).
2023-11-15 09:26:41 -08:00
Karl Stolley
5accf36115 recent_view: Correct icon alignment within rows. 2023-11-15 09:05:47 -08:00
Karl Stolley
17e87c9a20 compose_box: Fix regression on full-screen Preview areas. 2023-11-15 09:04:54 -08:00
Karl Stolley
c5b7432cf2 compose_box: Maintain top alignment in Preview mode. 2023-11-15 09:04:54 -08:00
N-Shar-ma
bdba280c3d typeahead: Include wildcard mentions in typeahead for small streams.
We refactor the code checking if wildcard mentions are allowed by
renaming the pre-existing function `wildcard_mention_allowed` to
`wildcard_mention_allowed_in_large_stream`, adding a new function
`is_recipient_large_stream`, then redefining `wildcard_mention_allowed`
to combine these two functions.

Fixes: #27248.
2023-11-15 09:02:34 -08:00
Aman Agrawal
27ffcc6576 bililng: Fix incorrect price per license on billing page.
There is a discrepancy between price per license shown on billing
and upgrade page for annual billing frequency due to difference
in methods used to calculate the amount.

To fix it, we use the same method on both the pages.

I didn't use the helpers.format_money directly here since that would
create a visual delay in showing the price per license which will
not be nice considering that will be the only thing on this page
being updated that way.

The stripe fixture used is same as
free_trial_upgrade_by_card--Customer.retrieve.3.json
2023-11-15 15:59:26 +05:30
Alex Vandiver
091e2f177b linkifiers: Match JS implementation to server implementation.
Since the server-side implementation no longer uses look-ahead
or (more importantly) look-behind, it is possible to exactly implement
in Javascript.  This removes a common class which would prevent local
echo.

This requires reworking the topic linking algorithm, to march the
server's as well.  The tests and behaviour are adjusted in so doing --
previously, the JS implementation would have linked `#foo` with a
`foo` regex on the linkifier, but the server implementation would not
have.
2023-11-14 20:43:39 -08:00
Alex Vandiver
70b20e9d2b markdown: Use \p{White_Space} equivalent for linkifier boundaries.
We do not use \p{White_Space} itself because re2 does not support it.
2023-11-14 20:43:39 -08:00
Alex Vandiver
549dd8a4c4 markdown: Expand prepare_linkifier_pattern to make it more legible. 2023-11-14 20:43:39 -08:00
Alya Abbott
cc4c672773 docs: Improve Mobile push notification service documentation. 2023-11-14 17:16:31 -08:00
Alya Abbott
ee8e792588 help: Add instructions for testing mobile push notifications. 2023-11-14 17:16:31 -08:00
Tim Abbott
ffaa8b9ced changelog: Simplify mobile push notification service links. 2023-11-14 17:16:31 -08:00
Karl Stolley
b2c2afe374 left_sidebar: Keep all DM container rows to same right margin. 2023-11-14 16:43:28 -08:00
N-Shar-ma
76a8c80b63 compose: Pass in stream_id to compose when editing scheduled message.
Earlier, we only passed in the stream name which isn't considered in
`compose_actions.start` when populating the recipient.

There was no visible bug since we first narrow to the correct stream and
then call `compose_actions.start`, but this was brittle since it would
not work if we open the compose box before narrowing.
2023-11-14 16:36:50 -08:00
Sayam Samal
06600028a5 send_later_popover: Use .navigate-link-on-enter for scheduled msgs.
We need to use the `navigate-link-on-enter` class for the scheduled
messages link in the popover, since it's a popover menu item with a
`href` attribute. The reason for using this class has been documented
in 246c1d7b4e.
2023-11-14 16:33:44 -08:00
Sayam Samal
0a487e4f92 popovers: Focus first option in send later popover when opened with kbd.
While opening the send later popover via the keyboard, we want to focus
the first text option, which is "Schedule message" at present. When the
popover is opened via mouse click, we want to keep the original behavior
of not focusing on any option.

Note: This is similar to the behavior of the message actions popover,
and hence this commit follows a similar pattern to add the focus.

Fixes: #27701.
2023-11-14 16:33:44 -08:00
Karl Stolley
6f8cac8077 compose_box: Better align and maintain alignment of topic marker. 2023-11-14 13:14:40 -08:00
Aman Agrawal
d45a2bf35b stream_color: Increase contrast of stream icon color and background. 2023-11-14 12:23:10 -08:00
Aman Agrawal
bbd49d4634 stream_color: Extract function to get corrected color. 2023-11-14 12:23:10 -08:00
kraktus
3de6ec0a59 sort_emojis: Prioritize perfect matches above all.
Currently we prioritize (even partial) realm emojis above all.
Including over perfect matches if the emoji is **not** a realm emoji.
The commit changes this behavior to prioritize perfect matches above all,
regardless of emoji category.

close https://github.com/zulip/zulip/issues/27545
2023-11-14 12:13:22 -08:00
Karl Stolley
5797381ea2 compose_box: Suppress send-area tooltips when vdots popover is open. 2023-11-14 12:10:38 -08:00
Karl Stolley
fd8711023d compose_box: Move send-control-button Tippy logic to compose file. 2023-11-14 12:10:38 -08:00
Karl Stolley
e82c875bc3 compose_box: Remove unreachable enter_sends cruft. 2023-11-14 12:10:38 -08:00
Prakhar Pratyush
c76b3acce7 typeahead: Fix '@topic' not present when stream wildcard suggested.
Earlier, we suggested only the first matching wildcard mention,
irrespective of how many equivalent wildcard mentions match.

That logic leads to the exclusion of '@topic' suggestion whenever
a stream wildcard is suggested, which is not correct as
stream wildcards and topic wildcard are not equivalent.

This commit updates the logic to make the behavior of suggesting
only the first match valid for stream wildcard mentions only.
2023-11-14 12:09:16 -08:00
Prakhar Pratyush
fb9e258a65 stripe: Add 'do_upgrade' method to the 'BillingSession' class.
This commit moves a major portion of the 'upgrade`
view to a new shared 'BillingSession.do_upgrade' method.

This refactoring will help in minimizing duplicate code
while supporting both realm and remote_server customers.
2023-11-14 12:07:26 -08:00
N-Shar-ma
e18c180414 reload: Fix bug where stream input is unexpectedly cleared on reload.
Earlier, we preserved the stream name across reloads, and to restore the
compose state, passed that to `compose_actions.start`, which since
c3fe96a, considers the stream id, and not its name.

This caused a bug where on server initiated reload, the stream input
would not retain its value and be unexpectedly cleared.

This is fixed by preserving the stream id across reloads, instead of its
name, and passing that to `compose_actions.start` instead.
2023-11-14 12:05:14 -08:00
N-Shar-ma
4e35c78fd5 left-sidebar: Fix "New topic" button in stream popover.
Now we pass in the stream's id to `compose_actions.start()` (not its
name), so that the compose box opens with the correct stream selected.
2023-11-14 11:29:54 -08:00
Karl Stolley
994e29e1fc compose_box: Hide send icon when load spinner is showing. 2023-11-14 09:21:11 -08:00
Karl Stolley
0c95772f4e compose_box: Maximize clickable area on send-later vdots. 2023-11-14 09:21:11 -08:00
Karl Stolley
3201c7dfb5 compose_box: Extend use of send-control-button.
This uses `.send-control-button` to handle Tippies on those
elements (currently vdots and Drafts), and removes a confusing
compose-formatting class.
2023-11-14 09:21:11 -08:00
Karl Stolley
5bedbb14b4 compose_box: Rename message-control-button to send-control-button. 2023-11-14 09:21:11 -08:00
Alya Abbott
acf5ba5432 help: Minor tweaks to Inbox and followed topics documentation. 2023-11-14 09:19:02 -08:00
Karl Stolley
41b61cfb3e compose_box: Don't fill Topic line on restored draft. 2023-11-13 12:45:13 -08:00
Karl Stolley
93d6bcef0e compose_box: Properly present Drafts button in all languages. 2023-11-13 12:45:13 -08:00
Karl Stolley
be34c4c2ef compose_box: Improve Drafts and button alignment in smaller views. 2023-11-13 12:45:13 -08:00
Karl Stolley
71468492b7 compose_box: Restore purple color to Send button. 2023-11-13 12:45:13 -08:00
Karl Stolley
d92a1e7ca8 compose_box: Update vdots tooltip to 'Send options'. 2023-11-13 12:45:13 -08:00
Karl Stolley
4fe9e4e715 compose_box: Handle delay, radio selection on Enter-sends elements. 2023-11-13 12:45:13 -08:00
Karl Stolley
7209476b18 compose_box: Integrate Drafts, Enter-to-send in send-later popover. 2023-11-13 12:45:13 -08:00
Karl Stolley
4fee51fc99 compose_box: Render limit indicator as template with zero-width space. 2023-11-13 12:45:13 -08:00
Karl Stolley
601faf8548 compose_box: Handle responsive layouts on send column.
Fixes #27576.
2023-11-13 12:45:13 -08:00
Karl Stolley
a6b3e74f7d compose_box: Sensibly style disabled Send/vdots buttons. 2023-11-13 12:45:13 -08:00
Karl Stolley
609106e2d0 compose_box: Implement redesigned vdots-button styles. 2023-11-13 12:45:13 -08:00
Karl Stolley
bf0e806fed compose_box: Implement redesigned send-button styles. 2023-11-13 12:45:13 -08:00
Karl Stolley
f8fa47fa02 compose_box: Use columnar flex to position Drafts, limit, Send button. 2023-11-13 12:45:13 -08:00
Karl Stolley
a3c1399924 compose_box: Handle compose and preview expansion. 2023-11-13 12:45:13 -08:00
Karl Stolley
ec38c7b5be compose_box: Establish basic message box grid geography. 2023-11-13 12:45:13 -08:00
Karl Stolley
9f00b513f7 compose_box: Improve structures around compose buttons. 2023-11-13 12:45:13 -08:00
Karl Stolley
e608a389af compose_box: Improve structures around send controls. 2023-11-13 12:45:13 -08:00
Karl Stolley
b4b71880e1 compose_box: Prepare redesign send and draft structures. 2023-11-13 12:45:13 -08:00
Alex Vandiver
5e49804004 puppet_ops: Include Akamai log parser on prometheus server. 2023-11-13 14:35:39 -05:00
Aman Agrawal
637e9c11ee billing: Show success text after plan change on top after reload. 2023-11-13 10:35:39 -08:00
Aman Agrawal
67bddb3d72 billing: Show success message for license change after page reload. 2023-11-13 10:35:39 -08:00
Aman Agrawal
c2e01f06a3 billing: Show message for users without access to page in a white box. 2023-11-13 10:35:39 -08:00
Aman Agrawal
a2fc9fabd8 populate_billing_realms: Increase price_per_license.
This help us show a nice number when divided by 12 for annual
frequency on billing page.
2023-11-13 10:35:39 -08:00
Aman Agrawal
2997c269e1 billing: Show breakdown of renewal amount if not fixed_price. 2023-11-13 10:35:39 -08:00
Aman Agrawal
17573cd1fb billing: Don't show license update buttons until valid change in value. 2023-11-13 10:35:39 -08:00
Aman Agrawal
775a90771e stripe: Modify error message for less than min licenses. 2023-11-13 10:35:39 -08:00
Aman Agrawal
0772f8011c billing: Don't reduce button width when loading. 2023-11-13 10:35:39 -08:00
Aman Agrawal
7dc4983898 billing: Wait 300ms before updating disabled status in buttons. 2023-11-13 10:35:39 -08:00
Aman Agrawal
5429582892 billing: Don't allow negative input. 2023-11-13 10:35:39 -08:00
Aman Agrawal
2a70143050 upgrade: Show user/month plurals based on their count. 2023-11-13 10:35:39 -08:00
Aman Agrawal
b516ae75db upgrade: Remove tests temporarily as they need to be rewritten. 2023-11-13 10:35:39 -08:00
Aman Agrawal
067d820a8a billing_helpers: Delete temporarily.
This needs to be re-written entirely.
2023-11-13 10:35:39 -08:00
Aman Agrawal
874d4a7026 upgrade: Improve styles for free trial upgrade. 2023-11-13 10:35:39 -08:00
Aman Agrawal
2ffae56efa upgrade: Improve styles for onboarding free trial orgs. 2023-11-13 10:35:39 -08:00
Aman Agrawal
a04204e47d billing: Style go to your org better. 2023-11-13 10:35:39 -08:00
Aman Agrawal
6158411ecb upgrade: Move error fields under the purchase button. 2023-11-13 10:35:39 -08:00
Aman Agrawal
7d10b3dcf7 upgrade: Add required fields to upgrade a realm. 2023-11-13 10:35:39 -08:00
Aman Agrawal
5f0969f52b session: Remove unnecessary has_request_variables wrapper. 2023-11-13 10:35:39 -08:00
Aman Agrawal
09db89a0c8 upgrade: Remove extra wrapping elements. 2023-11-13 10:35:39 -08:00
Aman Agrawal
2f9a48cce7 upgrade: Show payment amount based on user input.
Write script to make payment amount vary with payment schedule
and number of licenses.
2023-11-13 10:35:39 -08:00
Aman Agrawal
dfe179bf0d upgrade: Add input for num of licenses for manual management. 2023-11-13 10:35:39 -08:00
Aman Agrawal
e3f20cdb8a billing: Reduce clickable area to visible button area.
Clicking outside the button also used to work for cancel plan and
other buttons in that area which can deceiving for the user.
2023-11-13 10:35:39 -08:00
Aman Agrawal
f5ba6fe03c upgrade: Fix HTML structure and styles. 2023-11-13 10:35:39 -08:00
Aman Agrawal
6cadf333fb upgrade: Remove invoice payment instructions. 2023-11-13 10:35:39 -08:00
Aman Agrawal
f273229b01 upgrade: Add payment button and info style with fake text. 2023-11-13 10:35:39 -08:00
Aman Agrawal
4638f67e4e upgrade: Use style similar to /billing page. 2023-11-13 10:35:39 -08:00
Aman Agrawal
d3363bab96 upgrade: Add a dropdown to select billing cycle. 2023-11-13 10:35:39 -08:00
Aman Agrawal
2570f7ce23 upgrade: Control automatic / manual license management via URL. 2023-11-13 10:35:39 -08:00
Aman Agrawal
682ef235b5 upgrade: Remove tab structure. 2023-11-13 10:35:39 -08:00
Sahil Batra
532293c4cf streams: Unset is_web_public and is_realm_public fields on attachments.
This commit adds code to unset is_web_public and is_realm_public fields
on attachments when deactivating a stream as we do not want to allow
spectators to access them after the stream is deactivated.

This commit also adds a comment explaining why we don't use
do_change_stream_permission to set the privacy fields on deactivating
a stream.

Fixes #27634.
2023-11-13 10:26:14 -08:00
Sahil Batra
30e2c14f4a streams: Unset is_realm_public on attachment when unarchiving streams.
We did not unset is_realm_public field on attachements when unarchiving
streams, but we do unset is_web_public field. This commit adds code to
unset the is_realm_public field as well as we make the stream private
while unarchiving it.
2023-11-13 10:26:14 -08:00
Alex Vandiver
1f3a53db2d cache: Drop the huddle cache.
This cache was only used in one place, which is infrequently
called (only when sending messages, or searching explicitly for a list
of users) and the overhead of maintaining the cache is not worth
trying to avoid the well-indexed lookup of the huddle.
2023-11-13 10:01:29 -08:00
Alex Vandiver
5591d6f65c zulip_ops: Add configuration for Vector Akamai stats.
Akamai writes access logs to S3; we use an SQS events queue, combined
with Vector, to transform those into Prometheus statistics.
2023-11-13 09:53:20 -08:00
Karl Stolley
690b4efca8 left_sidebar: Explicitly handle clicks on collapsible Views targets. 2023-11-13 09:33:21 -08:00
Tim Abbott
8ad2e10cfe typeahead: Fix exception trying to mention @all in DMs.
Fixes a regression in 4d1ade1f88.
2023-11-13 09:07:36 -08:00
Prakhar Pratyush
1f91db1d43 topic_mentions: Add @topic mention typeahead.
This commit adds a @topic mention typeahead that appears
only in stream messages and not in private messages.

Fixes part of #22829.
2023-11-13 09:07:36 -08:00
Prakhar Pratyush
5ec26c6542 typeahead: Fix message_type to correctly test stream mention typeahead.
This commit sets the 'message_type' to "stream" in the beginning
of the test to properly verify wildcard mentions typeahead for
stream messages.

Earlier, 'message_type' was set to "False", and the test was passing
falsely because in the 'broadcast_mentions' function, the 'else' block
gets executed for 'message_type' having a value of either
'stream' or 'False'.

```
if (compose_state.get_message_type() === "private") {
    wildcard_string = $t({defaultMessage: "Notify recipients"});
} else {
    wildcard_string = $t({defaultMessage: "Notify stream"});
    wildcard_mention_array.push("stream");
}
```

We don't need to explicitly change the condition to:
```
else if (compose_state.get_message_type() === "stream")
```
because in practise, the 'message_type' is either set to 'stream'
or 'private' when the user types a message.

Only changing the 'message_type' in the test to simulate the
actual behaviour fulfils our purpose.

This should have been included in 5bddd8f.
2023-11-13 09:07:36 -08:00
Aman Agrawal
1b804cd0dd sponsorship: Don't preserve scroll position on reload. 2023-11-13 08:52:13 -08:00
Aman Agrawal
395cafcf46 sponsorship: Add extra input fields to get more org info.
Fixes #27600
2023-11-13 08:52:13 -08:00
Aman Agrawal
bf53c70dd9 sponsorship: Update discount description on page load.
Fixes part of #27645.
2023-11-13 08:52:13 -08:00
Prakhar Pratyush
64005c03b6 topic_mentions: Highlight the @topic mention for topic participants.
This commit adds support to highlight the '@topic' wildcard
mention text for the users having 'topic_wildcard_mentioned'
flag set.

Fixes #27497.
2023-11-13 08:29:16 -08:00
Tim Abbott
07769aec4b rows: Fix logic for finding drafts rows. 2023-11-13 08:29:16 -08:00
Karl Stolley
554f33d033 left_sidebar: Set shared right margin on DM section. 2023-11-13 08:27:28 -08:00
Prakhar Pratyush
b5a6742adc stripe: Move process_initial_upgrade to BillingSession.
Moves the 'process_initial_upgrade' function to the
'BillingSession' abstract class.

This refactoring will help in minimizing duplicate code while
supporting both realm and remote_server customers.
2023-11-13 08:11:27 -08:00
N-Shar-ma
ed59316ff6 refactor: Rename is_search to is_keyword_search for clarity.
For more clarity, the 3 related functions `is_search` in `filter.js`,
`message_list.js` and `message_list_data.js` are all renamed to
`is_keyword_search`.
2023-11-13 08:09:03 -08:00
Sahil Batra
d1113b5371 invite: Use ICU plural syntax for invite success message.
The message shown on successful invitation included "User(s)"
previously. This commit updates it to include "User" or "Users"
according to the number of users invited.
2023-11-13 08:07:12 -08:00
Sahil Batra
8f79cec51a users: Pass bogus data for inaccessible users.
We now pass bogus data for inaccessible users when sending
the users data in "realm_users" field of "register" response
or when using endpoints like "GET /users" to get data of
all the users in realm.

We would add a client capability field in future commits
such that new clients would receive data only for accessible
users and they can form the bogus data by themselves.
2023-11-13 08:04:45 -08:00
Sahil Batra
aedb9b23b4 models: Pass "realm__can_access_all_users_group" to select_related. 2023-11-13 08:04:45 -08:00
Sahil Batra
c82bb3ec76 models: Add can_access_all_users_group setting.
This commit adds new setting for controlling who can access
all users in the realm which would have "Everyone" and
"Members only" option.

Fixes part of #10970.
2023-11-13 08:04:45 -08:00
Sahil Batra
f20d427a4d users: Remove unnecessary comment.
This comment can be removed as the code was updated in 2ab71fb67d.
2023-11-13 08:04:45 -08:00
Sahil Batra
df01d0725c message_send: Remove select_related call for stream.
We do not access any related field from the stream object,
so there is no need for select_related.
2023-11-13 08:04:45 -08:00
Aman Agrawal
060b94b71f inbox: Fix search icon position on narrow widths.
Due to filters dropdown's variable width, the search icon was
incorrectly positioned, we fix it by positioning it relative to
the filter dropdown.
2023-11-10 18:20:26 -08:00
Lalit
b86022ea52 ts: Convert activity.js module to TypeScript. 2023-11-10 16:37:44 -08:00
Alya Abbott
0ca6d58e23 help: Document worflows for followed topics. 2023-11-10 16:29:55 -08:00
Alya Abbott
3d874367fe help: Fix tab label in starred message documentation. 2023-11-10 16:29:55 -08:00
Alya Abbott
72fbcfb5f6 help: Document topic status dropdown in Inbox view. 2023-11-10 16:29:55 -08:00
BrandonAWong
6edf24b0f7 search: Remove "All messages" typeahead
Previously, the "All messages" typeahead would be shown whenever
the search bar had an empty input. This change removes that typeahead
completely in favor of using other filters such as -is:muted.

Fixes #27358

Co-authored-by: Heran Zhang & Wei-Cheng Sung
2023-11-10 16:27:32 -08:00
Mateusz Mandera
48db4bf854 counts: Add new mobile_pushes RemoteRealmCount stats.
This requires a bit of complexity to avoid a name collision in
COUNT_STATS with the RemoteInstallationCount stats with the same name.
2023-11-10 16:09:11 -08:00
Mateusz Mandera
2512e66c06 counts: Don't allow syncing mobile_pushes_forwarded::day count.
6819ecee92 forgot to add this.
2023-11-10 16:09:11 -08:00
Mateusz Mandera
8a6d5b4997 counts: Add new Add new mobile_pushes_sent::day LoggingCountStat.
This is a CountStat for tracking how many mobile notifications the
server requested.
1. On a self-hosted server, that means requesting from the push bouncer.
2. On a server that's its own push bouncer, that's just the number
   directly sent.

This number has room for inaccuracy due to incrementing by the number of
user devices on a self-hosted server, as it doesn't account for errors
that may occur in the GCM/APNs low-level sending codepaths on the bouncer.

Also tests that a server that's its own push bouncer correctly
increments its mobile_pushes_sent::day CountStat, by basing it on the
values returned from the send_apple/android_push_notification functions
which tell us the actual number of successfully sent notifications.

Since the return values of send_..._push_notification are now
used in those codepaths, we need to tweak our mocks in some unrelated
tests to set up some return value to avoid errors.
2023-11-10 16:09:11 -08:00
Alya Abbott
1e08c5f3c6 help: Add a note about how notifications work in muted streams.
Note matches the string used in the settings UI.
2023-11-10 16:00:26 -08:00
Alya Abbott
fbbf626e8c help: Update "Mute a stream" help page to reflect UI tweak. 2023-11-10 16:00:26 -08:00
N-Shar-ma
c3c72cda6d compose: Completely remove style elements when pasting HTML as markdown.
Turndown would strip `<style>` tags, but retain the content enclosed.
This resulted in unwanted content being pasted, like the comment nodes
inside the `<style>` tag when pasting cell/s copied from Google Sheets.
2023-11-10 13:56:51 -08:00
N-Shar-ma
e15f518ccc compose: Always retain ul tag when pasting content enclosed by it.
This functionally makes no difference, but we add it along with `ol`
for consistency.
2023-11-10 13:56:51 -08:00
N-Shar-ma
db9ac4b674 commit: Disable automatic numbering / bulleting when inside code block. 2023-11-10 13:56:51 -08:00
N-Shar-ma
602d1d7323 compose: Do not paste formatted markdown when inside a code block.
We usually default to pasting formatted markdown (unless the Shift key
is used to paste plainly instead), but when we're inside a code block,
we always want to paste plainly.
2023-11-10 13:56:51 -08:00
N-Shar-ma
f5bcf5d608 compose: Add a function checking whether the cursor is in a code block.
This is a prep commit for the following 2 commits, so we can opt out of
the formatted paste and automatic bulleting / numbering behaviors when
inside a code block.
2023-11-10 13:56:51 -08:00
Prakhar Pratyush
2f469ff830 hotkey: Give feedback when no unreads left in followed topics.
When navigating to the next unread followed topic using
the Shift+N hotkey, we notify the user when there are no more
unread messages in followed topics.

Earlier, the hotkey simply did nothing in such a case.

We use feedback_widget to do so.

Fixes #27604.
2023-11-10 13:33:13 -08:00
Joelute
4d1ade1f88 compose: Automatically change @ mentions to silent mentions in 1:1 DMs.
It is common for users in other chat tools to start off a direct message
with @ mentioning the addressee. However, this results in potentially
unexpected behavior in Zulip, where the message is highlighted and shows
up in @ mentions in addition to DMs. We want to prevent this behaviour by
automatically changing the @ mentions to silent @ mentions for the user.
These changes only apply to 1:1 DMs and not group chats.

Fixes: #17998.
2023-11-10 12:59:44 -08:00
Lauryn Menard
928cb7e09e corporate: Pass plan_tier parameter to process_initial_upgrade.
Updates `process_initial_upgrade` to take a plan_tier parameter,
so that information can be specific to the type of BillingSession.

Note that ideally the plan tier would be passed as metadata to the
stripe.checkout.Session, but in order to do so, we need to be able
to update the generated stripe fixtures for tests. So for now, we
set the plan tier directly in the stripe event handler code.
2023-11-10 11:26:18 -08:00
Lauryn Menard
3e550364c6 corporate: Use plan.name for stripe.Invoice statement descriptor.
Instead of hard coding the string for the stripe.InvoiceItem and
stripe.Invoice statement_descriptor, we use the name of the plan
that was just created.
2023-11-10 11:26:18 -08:00
Lauryn Menard
d29c454e80 corporate: Make BillingSession abstract method for changing plan type.
Creates `do_change_plan_type` as a BillingSession abstract method
as an alias for updating the plan_type for a realm (and eventually
remote server).

This removes the last direct reference to a customer's realm in
`process_initial_upgrade`.
2023-11-10 11:26:18 -08:00
Lauryn Menard
26ad2e29c6 corporate: Make BillingSession alias for latest seat count.
Creates `current_count_for_billed_licenses` as an abstract method
in BillingSession class, to get the latest seat count for the realm
(and eventually remote server) connected to the customer.
2023-11-10 11:26:18 -08:00
Prakhar Pratyush
c597de6a1d topic_mentions: Rename wildcard_mentioned to stream_wildcard_mentioned.
Rename the existing 'wildcard_mentioned' flag to
'stream_wildcard_mentioned'.

The 'wildcard_mentioned' flag is deprecated and exists for
backwards compatibility.

We have two separate flags for stream and topic wildcard mentions,
i.e., 'stream_wildcard_mentioned' and 'topic_wildcard_mentioned',
respectively.

* stream wildcard mentions: `@all`, `@everyone`, and `@stream`
* topic wildcard mentions: `@topic`

The `wildcard_mentioned` flag is included in the events and
API response if either `stream_wildcard_mentioned` or
`topic_wildcard_mentioned` is set.
2023-11-10 11:06:26 -08:00
Prakhar Pratyush
24fa361f40 migration: Clear old data for unused usermessage flags.
In c37871ac3a, we renamed the
two unused and historical bits of the 'flags' bitfield of
the 'UserMessage' table:

* 'summarize_in_home' to 'topic_wildcard_mentioned'
* 'summarize_in_stream' to 'group_mentioned'

This commit clears out the old data for those bits.

Additionally, we are clearing 'force_expand' and 'force_collapse'
unused flags to save future work.
2023-11-10 11:06:26 -08:00
Aman Agrawal
a7f02c89d7 inbox_view: Move filters dropdown to the left of search box. 2023-11-10 10:22:18 -08:00
Aman Agrawal
69d59acc5a inbox: Focus currently selected option in dropdown. 2023-11-10 10:22:18 -08:00
Aman Agrawal
f191efce1b inbox: Add dropdown to filter topics.
Fixes #27522

Replace `Include muted` checkbox with a dropdown to filter displayed
topics.

Also, adds a new filter to display only followed topics.
2023-11-10 10:22:18 -08:00
Aman Agrawal
03dcb9c0e4 dropdown_widget: Add option to hide search box. 2023-11-10 10:22:18 -08:00
Karl Stolley
2557f90c01 left_sidebar: Set extra-long hover delay for Views, DMs tooltips. 2023-11-10 09:22:07 -08:00
Karl Stolley
f3887f1fa7 left_sidebar: Add a Views expand/collapse tooltip. 2023-11-10 09:22:07 -08:00
Karl Stolley
f8ce278e76 left_sidebar: Include DM expand/collapse tooltip on arrow. 2023-11-10 09:22:07 -08:00
Karl Stolley
5f1c9ca107 left_sidebar: Center Streams tooltip over heading text. 2023-11-10 09:22:07 -08:00
Akarsh Jain
d8d70d9e43 compose_banner: Display time for messages with < 5 mins scheduled time.
This commit addresses the issue where scheduling a message for a
time less than 5 minutes in the future resulted in the confirmation
banner not displaying the scheduled time, which should have actually
been 5 minutes in the future, as enforced at the UI level by the
MINIMUM_SCHEDULED_MESSAGE_DELAY_SECONDS variable.

We remove the `is_send_later_timestamp_missing_or_expired` method along
with it's tests since `selected_send_later_timestamp` is already set to
the minimum of 5 minutes in the future through the `minDate` option of
the flatpickr.

Fixes: #26784.

Co-authored-by: Sayam Samal <samal.sayam@gmail.com>
2023-11-10 09:20:15 -08:00
Prakhar Pratyush
31f048d054 stripe: Move setup_upgrade_checkout_session_and... to BillingSession.
Moves the 'setup_upgrade_checkout_session_and_payment_intent'
function to the 'BillingSession' abstract class.

This refactoring will help in minimizing duplicate code while
supporting both realm and remote_server customers.
2023-11-10 09:15:10 -08:00
Karl Stolley
f2e8d2dc07 left_sidebar: Set border radius on rows for all states. 2023-11-09 17:39:26 -08:00
Karl Stolley
34475bf867 left_sidebar: Establish filter-handling grid on Streams, DM headers.
Fixes part of #27559.
2023-11-09 17:39:26 -08:00
Karl Stolley
2235413c15 left_sidebar: Establish grid on Direct Messages header. 2023-11-09 17:39:26 -08:00
Karl Stolley
12bf754a8a left_sidebar: Consolidate left-sidebar-title styles. 2023-11-09 17:39:26 -08:00
Karl Stolley
3f36c52f41 sidebars: Structurally separate left- and right-sidebar headings. 2023-11-09 17:39:26 -08:00
Karl Stolley
ee32654723 sidebars: Express heading colors as CSS variables. 2023-11-09 17:39:26 -08:00
Aman Agrawal
df703f847c billing: Fix free trial billing page end plan button not working. 2023-11-10 05:17:52 +05:30
Aman Agrawal
0f31f18744 populate_billing_realms: Add a realm on free-trial. 2023-11-10 05:17:52 +05:30
Aman Agrawal
b0fb5c33e9 test_stripe: Fix tests related to text changes on billing page. 2023-11-10 05:17:52 +05:30
Aman Agrawal
9a186ac8d5 billing_page: Redirect users on free plan to /plans.
Fixes #27379
2023-11-10 05:17:52 +05:30
Aman Agrawal
56eec2734c billing_page: Add capitalization format of various common cards. 2023-11-10 05:17:52 +05:30
Aman Agrawal
f85bd0234a billing: Minor changes. 2023-11-10 05:17:52 +05:30
Aman Agrawal
bb557d5193 billing: Add different modals for increasing / decreasing licenses. 2023-11-10 05:17:52 +05:30
Aman Agrawal
caa40ad2c6 billing_page: Remove unused context. 2023-11-10 05:17:52 +05:30
Aman Agrawal
a124374202 populate_billing_realms: Add switch-to-annual-EOC customer. 2023-11-10 05:17:52 +05:30
Aman Agrawal
07990ad8f1 populate_billing_realms: Add invoice-payment customer. 2023-11-10 05:17:52 +05:30
Aman Agrawal
3d45e7ca64 billing: Make plan cancel/renew work. 2023-11-10 05:17:52 +05:30
Aman Agrawal
e185e91581 billing: Redirect to stripe card change page on clicking change card. 2023-11-10 05:17:52 +05:30
Aman Agrawal
8c4ef3e9aa billing: Temporarily remove tests.
These seem important to have but will have to be rewritten.
2023-11-10 05:17:52 +05:30
Aman Agrawal
cce5666e5d billing: Make changing current and next cycle licenses work. 2023-11-10 05:17:52 +05:30
Aman Agrawal
9d7e10950f billing: Fix style of cancel button and adjust app margins. 2023-11-10 05:17:52 +05:30
Aman Agrawal
a96cd44926 billing: Fix style of next payment info. 2023-11-10 05:17:52 +05:30
Aman Agrawal
d79b59d3b1 billing: Make payment method look nice. 2023-11-10 05:17:52 +05:30
Aman Agrawal
f9aba9ba33 billing: Make license count management buttons look nicer. 2023-11-10 05:17:52 +05:30
Aman Agrawal
a75afe1462 bililng: Style similar to sponsorship page. 2023-11-10 05:17:52 +05:30
Aman Agrawal
a4425d26d2 billing: Add plan change buttons, modals and notices. 2023-11-10 05:17:52 +05:30
Aman Agrawal
9d3536ca2b billing: Add switch to annual in next cycle notice. 2023-11-10 05:17:52 +05:30
Aman Agrawal
2a6a7d2382 billing: Add billing contact. 2023-11-10 05:17:52 +05:30
Aman Agrawal
3adddaa643 billing: Move next payment info below card details. 2023-11-10 05:17:52 +05:30
Aman Agrawal
68a117d490 billing: Modify next payment info. 2023-11-10 05:17:52 +05:30
Aman Agrawal
e72df84396 billing: Change formatting for showing card info. 2023-11-10 05:17:52 +05:30
Aman Agrawal
ebc7cbaf39 billing: Reformat structure of HTML to update licenses manually.
This inclues a major change to allow users to update number of
licenses in the current billing period.
2023-11-10 05:17:52 +05:30
Aman Agrawal
3ad2cb8c9c billing: Show automatic license count in new style. 2023-11-10 05:17:52 +05:30
Aman Agrawal
2f532e49d1 billing: Add field to show billing frequency. 2023-11-10 05:17:52 +05:30
Aman Agrawal
c41a8317cf billing: Convert plan name to new style. 2023-11-10 05:17:52 +05:30
Aman Agrawal
89003dcb25 billing: Remove tabs. 2023-11-10 05:17:52 +05:30
Aman Agrawal
5b97eb0445 billing: Modify title. 2023-11-10 05:17:52 +05:30
Aman Agrawal
b5b7fc9957 billing: Use navbar used in login/help pages. 2023-11-10 05:17:52 +05:30
Alex Vandiver
7c8eacfa12 queue: Increase paging thresholds for deferred_work queue.
This queue is used to things which definitionally may take longer than
a request, so paging after 60s is rather aggressive.  This is
especially true because this queue has a very long tail of very slow
tasks -- p99 of task time in this queue is 8.5s, while p99.9 is 197s.

Raise the paging threshold to 15 minutes.  While there are
semi-user-facing tasks which use this queue (primarily marking
messages as read), those being delayed for minutes is already a real
possibility if they are stuck behind a large realm export -- and this
is not a situation which should necessarily page, since it is not
solvable by the administrator.
2023-11-09 13:51:00 -08:00
Tim Abbott
6af9a91b9d docs: Improve billing testing docs.
You don't need to run any other test suites for this, so this should
be a lot faster.
2023-11-09 12:04:14 -08:00
Lauryn Menard
6a53005074 corporate: Update generated stripe fixtures for test_stripe.
Generated with both parellel=1 and generate-stripe-fixtures params
on test-backend locally.

Note that the previous series of commits need these test fixtures
to pass CI. If those are changed, then this commit should be updated
or dropped and replaced with a commit that updates the fixtures
based on the new changes.
2023-11-09 12:03:33 -08:00
Lauryn Menard
717d40475e corporate: Use the stripe checkout session metadata for free trials.
Instead of using the data from the stripe.SetupIntent for processing
an initial upgrade for the free trial session types, use the metadata
from the stripe.checkout.Session that was returned.

The metadata is the same for both the checkout session and the setup
intent.
2023-11-09 12:03:33 -08:00
Lauryn Menard
c09bfc136e corporate: Update generate stripe fixtures to not use test cc numbers.
Updates generate stripe fixtures to use test credit card strings from
Stripe, instead of creating fake stripe.PaymentMethods for the tests
with the test credit card numbers provided by Stripe.
2023-11-09 12:03:33 -08:00
Lauryn Menard
1f28837bd7 corporate: Update create_stripe_customer to not attach payment method.
According to stripe's documentation for attaching payment methods to
customers (see https://stripe.com/docs/api/payment_methods/attach),
payment methods should be attached to customers through a SetupIntent
or PaymentIntent, which is what we do as we process new customers
and accounts.

Updates create_stripe_customer so that it is clear that the payment
method should not be added when we directly create a new  stripe
customer.
2023-11-09 12:03:33 -08:00
Lauryn Menard
4dd6f105a0 coporate: Change statement descriptor for Zulip Cloud Credit.
There is a 22 char limit on the stripe.Invoice.statement_descriptor
field. The current value for that field in this error case, "Zulip
Cloud Standard Credit" is 27 chars.

Updates the value for this error case to be "Cloud Standard Credit".
2023-11-09 12:03:33 -08:00
Aman Agrawal
757675db80 rows: Use message under / next to sticky header for narrowing / info.
Fixes #27489

For sticky message headers, we should not use the `first_message_in_group`
for narrowing to the topic since can be out of view for the user.
2023-11-09 10:04:58 -08:00
David Rosa
6fb1af00f1 help: Document "Load more" recent conversations.
Adds a new section with a short intro and instructions that explain
how to load more conversations.

Fixes #27253.
2023-11-09 06:36:10 -08:00
Anders Kaseorg
cc1e6c3e4b emoji_names: Rebuild with CLDR 44.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-09 06:35:26 -08:00
Anders Kaseorg
dadf035366 dependencies: Upgrade JavaScript dependencies.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-08 16:11:15 -08:00
Anders Kaseorg
9b83dc1b79 stylelint: Fix declaration-block-no-redundant-longhand-properties.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-08 16:11:15 -08:00
Anders Kaseorg
dcdbb39c31 install-node: Upgrade Node.js from 18.18.0 to 20.9.0.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-08 16:11:15 -08:00
Mateusz Mandera
1312c7ccd7 zilencer: Add mechanism to update RemoteRealm when Realm is changed.
This requires a migration to allow RemoteRealmAuditLog.remote_id to be
NULL, and to add a RemoteRealmAuditLog.remote_realm.
2023-11-08 15:54:22 -08:00
Mateusz Mandera
76e0511481 zilencer: Add new model RemoteRealm and send the data to the bouncer.
Add the new model for recording basic information about Realms on remote
server, to go with the other analytics data. Also adds necessary changes
to the bouncer endpoint and the send_analytics_to_push_bouncer()
function to submit such Realm information.
2023-11-08 15:54:22 -08:00
Lauryn Menard
e72a9fb814 docs: Update API and contributor documentation for new macro.
Updates API and contributor documentation for writing integrations
for the new `generate-integration-url.md` macro. Removes all
refrences to `create-bot-construct-url.md`, which was removed in
the previous commit.
2023-11-08 14:19:05 -08:00
Tim Abbott
0d525cf644 integrations: Recommend new integration URL tool.
Fixes part of #25976.
2023-11-08 14:19:05 -08:00
David Rosa
0c4ef58acd help: Document new help menu.
Updates help center docs to reflect the new dedicated help menu.

- Adds `keyboard_tip` to document the `?` keyboard shortcut.

Fixes #27384.
2023-11-08 14:17:13 -08:00
roanster007
273081d0a6 lightbox_view: Fix media title update on change in title.
Presently in a session when an image or video title is updated,
the same change is not reflected in the lightbox view of
them, until a new session is made.

This is because the metadata of the media is stored in Map
for the session with the preview_src as keys, and hence,
when title is changed the preview_src being same, the title
change is not reflected inside lightbox view.

This is fixed by modifing the asset_map (cache map) keys
from preview_src to an array of preview_src, and the media
title. Hence, when title is modified now, the keys won't map
to the existing value, and hence new metadata would be
shown.
A new function get_asset_map_key_for_media() introduced
to get the corresponing metadata from this asset_map
by passing in the prview_src and media elements.

Fixes #21311
2023-11-08 14:10:51 -08:00
Karl Stolley
e8fe6f873e left_sidebar: Register mark-all-read handlers on all home views. 2023-11-08 14:04:58 -08:00
Alex Vandiver
fb80ecd9c3 upgrade-zulip: Defer cache-filling to just outside the critical period.
Filling caches needs to happen close to when the server is restarted,
as the gap opens us up to race conditions with user modifications.  If
there are migrations, however, it must happen within the critical
period after the migrations are applied.

Move the call to fill the caches to within the `shutdown_server`
function, so that we push it as close to the server shutdown as
possible.
2023-11-08 11:00:00 -08:00
Alex Vandiver
acb4e9e783 cache_helpers: Show item and DB query counts. 2023-11-08 11:00:00 -08:00
Aman Agrawal
f6516cba89 personal_menu: Add opacity hover effect on clear status button. 2023-11-08 10:19:24 -08:00
Aman Agrawal
66276d88ef personal_menu: Add different colors for status text. 2023-11-08 10:19:24 -08:00
Tim Abbott
807de37561 analytics: Fix misplaced zilencer import statement. 2023-11-08 10:00:13 -08:00
N-Shar-ma
eca6738b00 compose: Always retain link formatting when pasting just a link.
We add anchors to the list of enclosing elements we don't strip away.
2023-11-08 09:51:33 -08:00
roanster007
dc492867af user_mention: Fix mentions of deactivated users.
Previously, when a deactivated user was mentioned, he wasn't
rendered as a Pill. This is because the dataset for validating mentions
only included active users, which is fixed by removing that filter.

To allow only silent mentions of them, an extra is_active property
added to FullNameInfo class, which is populated from the query,
which tells if user is deactivated. This is used to convert any
mentions of them to silent mentions in the backend markdown.

Fixes #26857
2023-11-08 09:48:31 -08:00
evykassirer
c3bee2038a message edit: Use a default error message for failed edits.
Bug reported here:
https://chat.zulip.org/#narrow/stream/464-kandra-js-errors/topic/Error.3A.20The.20partial.20.40partial-block.20could.20not.20be.20found/near/1673466

The reported issue was for a message edit request, and it was
happening because `channel.xhr_error_message` can return the
empty string sometimes, and `render_compose_banner` shows the
banner text if it exists and otherwise tries to render a
`@partial-block`. Unfortunately the empty string acts as falsy
in the template, leading to the partial block error.

This commit adds a default error message that was removed
in 96680e95fb.

It also adds a check to not show an error banner at all if
`readyState` is 0, which means the request was cancelled.
2023-11-08 09:47:32 -08:00
Mateusz Mandera
3cafdbdc1e counts: Add function compute_max_monthly_messages for remote servers.
This calculates the largest amount of messages sent within a month for
the last 3 months. The query is targeted for the specific use-case in
this function - for finding the count for a specific server. For
calculating this in bulk for a large number of remote server an
adapted, bulk query will be needed - rather than running this one in a
loop, which would likely be very inefficient.
2023-11-08 09:45:49 -08:00
Sahil Batra
617d2d509c users: Update format_user_row to return a TypedDict.
This commit updates format_user_row to return a TypedDict.

This commit is a prep commit for feature of restricting user
access such that code can be easy to read and understand when
we add that feature.
2023-11-08 09:22:26 -08:00
Sahil Batra
2ab71fb67d users: Update user_profile_to_user_row to return a TypedDict.
This commit updates user_profile_to_user_row to return a TypedDict
and also updates the return type of get_realm_user_dicts to be a
TypedDict.

This commit is a prep commit for feature of restricting user
access such that code can be easy to read and understand when
we add that feature.
2023-11-08 09:22:26 -08:00
Sahil Batra
deb8431a47 users: Rename get_raw_user_data to get_users_for_api.
This is a prep commit for adding feature of restricting
user access to guests such that we can keep the code
easy to read and understand when that feature is added.
2023-11-08 09:22:26 -08:00
David Rosa
a568f0cf16 help: Document deleting multiple drafts via checkboxes.
- Splits section for deleting drafts into deleting a single draft
  and deleting multiple drafts via checkboxes to document new
  feature.

Fixes #26888.
2023-11-08 09:18:12 -08:00
Aman Agrawal
3a7ce432e5 popovers: Fix topic visibility popover in sticky header not working.
The popover didn't open since sticky_header is part of message feed
and we were hiding popovers if `elements_at_reference_position`
had `sticky_header`. Now, we just don't do that check if the
reference is part of sticky_header.
2023-11-08 09:17:25 -08:00
ecxtacy
f2deb8da46 settings-ui: Change <select> style using custom chevron-down icon.
Align the `dropdown-widget` and `<select>` UIs.
Removed default `<select>` tag styles with `appearance: none` css.
Added custom chevron-down icon with `background-image` attribute
to the `<select>` tag.

The svg icon is used in a CSS variable with a data URI.
The CSS variable is used to set different colors for dark and light
theme.

Tweaked by tabbott to scope selectors appropriately and also delete a
10px font-size in one settings dropdown widget.

Fixes #26859.
2023-11-07 16:55:34 -08:00
ecxtacy
0c1f340934 settings-ui: Replace chevron-down fa-icon with svg icon.
Fixes part of #26859.
2023-11-07 16:55:34 -08:00
ecxtacy
952c5e1865 icons: Add chevron-down svg icon for dropdowns. 2023-11-07 16:55:34 -08:00
Greg Price
7a0ebc0d18 push_notifs: Log an error if ios_app_id missing on any APNs token. 2023-11-07 16:19:42 -08:00
Greg Price
f109e3b598 push_notifs: Backfill ios_app_id on bouncer. 2023-11-07 16:19:42 -08:00
Greg Price
052eddbac8 push_notifs: Drop illusory ios_app_id param on unregister at bouncer.
This parameter appeared here on the function definition,
but because it lacked a `REQ` call it didn't actually connect
to any parameter passed in the HTTP request.

It doesn't make any sense on this endpoint anyway -- presumably
it was copy-pasted from its "register" counterpart -- so just cut it.
2023-11-07 16:19:42 -08:00
Greg Price
be2a9a03d0 push_notifs: Check app ID has a plausible shape at bouncer. 2023-11-07 16:19:42 -08:00
Greg Price
1b2178f558 push_notifs: Require ios_app_id on register at bouncer, too.
We'll need this information in order to properly direct APNs
notifications.  Happily, the Zulip server always sends it when
registering an APNs token; and it appears it always has done so
since the commit:

cddee49e7 Add support infrastructure for push notification bouncer service.

back in 2016.  So there's no compatibility issue from requiring it.
2023-11-07 16:19:42 -08:00
Greg Price
9c2d53bcef push_notifs: Stop dropping ios_app_id on floor at bouncer.
This missing `REQ` call has meant we just drop this parameter:
even though the remote Zulip server passes it (for all APNs tokens),
we never notice and never store it.  Fix that.
2023-11-07 16:19:42 -08:00
Greg Price
9072ddda05 push_notifs: Remove remaining references to ZULIP_IOS_APP_ID setting. 2023-11-07 16:19:42 -08:00
Greg Price
a9796ec503 push_notifs: Check app ID has a plausible shape. 2023-11-07 16:19:42 -08:00
Greg Price
fb3af7fbcb push_notifs: Make appid required on add_apns_device_token.
We're going to need to use this information, so we shouldn't just
assume a value; the client should tell us the actual value.

Conveniently, the Zulip mobile app does already pass this parameter
and has since forever.  So we can just start requiring it, with no
compatibility constraint.
2023-11-07 16:19:42 -08:00
Greg Price
5d0d35d3f5 push_notifs: Always supply appid to add_apns_device_token in tests.
We already always pass this parameter from the mobile client,
so this makes the tests more realistic already.  And we'll shortly
be making this parameter required.
2023-11-07 16:19:42 -08:00
Greg Price
3893a70888 push_notifs: Cut spurious token_kind arguments in some test data.
These endpoints distinguish the token kinds by having different
URL paths, and don't take any argument by this name.
2023-11-07 16:19:42 -08:00
Greg Price
d1e4347cb2 push_notifs: Keep APNS_CERT_FILE as None in tests.
In 0b3f7a5a6 we started automatically setting this in dev if we
found a cert file in an appropriate place.  But for running tests,
we don't want such variability.  Set it back to None there.
2023-11-07 16:19:42 -08:00
Pratik
137505e2ad settings_notifications: Enable muted stream notification settings.
This commit allow the users to modify notification settings for muted
streams through the 'notification' section located in the personal
settings.

Fixes #27272.
2023-11-07 13:46:19 -08:00
Aman Agrawal
ebd0dcd5d2 actions_popover: Fix copy link not working on enter keypress. 2023-11-07 13:31:53 -08:00
Rahul
4dfb04d501 compose_ui: Use Intl.ListFormat rather than .join(", ").
This commit changes .join(",") to Int1.ListFormat.
This produces more accurate translations.

Refactor compose_ui.test to update the testcase for Alice and Bob.
The previous test case output of "Alice,Bob"  modify to "Alice and Bob"
to align with the behavior of Int1.ListFormat,
which joins lists using ',' & 'and'.
2023-11-07 13:18:57 -08:00
Joelute
76024e479d alerts: Fix improper copied tooltip location in About Zulip Modal.
Previously when a user tries to click on the clipboard to copy the merge
base version info, the copied tooltip would appear in the wrong location
where the Zulip version is instead. This is caused by both clipboard
icon using the same Clipboard instance where the success event would only
display the tooltip at the Zulip version. These changes fixes this and
allows the clipboards to be independent of each other.
2023-11-07 13:17:55 -08:00
David Rosa
a9cca4a9f6 integrations: Update Jitsi Meet documentation.
- Tweaks documentation and clarifies that the "Jitsi server URL"
  setting is not just a server-side setting.
- Updates instructions to a series of steps to be followed directly,
  without having to go to the help center.

Fixes #26907.
2023-11-07 13:09:52 -08:00
David Rosa
b5351285b6 help: Document "Jitsi server URL" setting.
- Updates "Start a call", documents the "Jitsi server URL" setting,
  and links to it in a (?) from the relevant admin UI.
- Fixes misplaced include block for opening the compose box.

Fixes #26907.
2023-11-07 13:09:52 -08:00
Alya Abbott
8fd631bf06 integrations-docs: Update intro content for Slack incoming webhooks. 2023-11-07 12:59:24 -08:00
Lauryn Menard
1618060a3e integrations-docs: Update both Slack integration articles.
Updates the Slack integration page to not describe adding a stream
or topic parameter to the URL query since that's not supported by
the current integration implementation.

Updates the Slack-compatible webhook integration page to have the
extra notes about the integration at the top of the page. Also,
removes the reference to a screenshot of the webhook since there
isn't one.
2023-11-07 12:59:24 -08:00
Lauryn Menard
2da1b3307a corporate: Rename billable_user variables to billing_session in tests. 2023-11-07 12:57:20 -08:00
Lauryn Menard
b6fa00d4fd corporate: Set realm strings for test of downgrade small realms.
Instead of randomly generating an integer for the realm string_id
and realm name use a counter to increment the string ids by one.
This way if a particular realm fails the test, it will be easier
to identify which one failed.

Also, removes unused Customer objects that were being returned on
realm creation in the test, as these were not being checked/used
in the test.
2023-11-07 12:57:20 -08:00
Prakhar Pratyush
94679d590f email_notification: Include prior message context only when mentioned.
Earlier, email message notifications included prior messages sent
to the same topic for context. This is more confusing than helpful
for messages that the user is likely to have received notifications
for all the prior messages in the conversation already (or read them
in the Zulip UI).

Now, we include prior context only when the user is mentioned via
personal, group, stream or topic wildcard mention.

Fixes #27479.
2023-11-07 11:44:38 -08:00
Prakhar Pratyush
c0f445294c test_message_notif_email: Verify email has multiple stream messages.
This commit improves the test to explicitly verify that multiple
messages that were sent in quick succession to a topic are included
in the email body when we have email notifications enabled for a
given stream.

Earlier, the test was only verifying the email subject and the fact
that only one email was sent.

It is important to verify the fact that all the messages sent to a
topic in quick succession should be included in the email body.
2023-11-07 11:44:38 -08:00
N-Shar-ma
ff5e389e9b compose: Allow pasting plainly, when Shift is held down, for Firefox.
We introduce a state variable `shift_pressed` for the purpose of
detecting whether the Shift key is pressed when pasting content. Only if
it is not, do we proceed with the default formatted paste behavior.

In Chrome, plain paste works out of the box for `Ctrl+Shift+V`, but in
Firefox, we need to handle it ourselves.
2023-11-07 11:39:06 -08:00
N-Shar-ma
dac56b4d08 compose: Paste content into the compose box without adding any space.
We replace the call to `insert_syntax_and_focus` with a direct call to
`insert_and_scroll_into_view` when pasting content into the compose box,
which fixes the bug where leading and trailing spaces were added to any
pasted content.
2023-11-07 11:39:06 -08:00
N-Shar-ma
24e8119350 compose: Always retain ol tag when pasting content enclosed by it. 2023-11-07 11:39:06 -08:00
Aman Agrawal
25877114cf popover_menus: Move popoover to top if focused on text field.
Fixes #11448

Tested on Chrome, Firefox (iOS, Android) and Safari (iOS).
2023-11-07 11:31:10 -08:00
Aman Agrawal
75960bba16 emoji_picker: Don't autofocus search input on mobile.
Since focus on input elements on mobile opens keyboard which
changes window height, emoji popover can hide or scroll out of
view. To fix it, we don't focus on search input unless user
wants to when emoji popover is open.
2023-11-07 11:31:10 -08:00
Lauryn Menard
fff2c9e47b integrations-docs: Update GitHub doc for new URL instructions. 2023-11-07 11:21:05 -08:00
Lauryn Menard
b45998a18e api-docs: Document incoming webhook URL specification and generator.
Updates the shared integrations documentation on generating a URL for
an incoming webhook for the new modal in the web-app, with links to the
help center. Also, links to a new section in the incoming webhooks
overview in the API documentation about the URL specification.

Also, adds a link to the help center in the shared webhooks integration
instructions for creating a stream for an incoming webhook. And updates
the example webhook URL to use a stream ID instead of a stream name.
2023-11-07 11:21:05 -08:00
Prakhar Pratyush
e6e156709a typing_notifications: Don't notify long_term_idle subscribers.
The event for stream typing notifications is no longer sent
to the long_term_idle subscribers of the stream.

This helps to reduce the tornado's work of parsing super-long
JSON-encoded lists of user IDs in large streams. Now the lists
are shorter.
2023-11-07 09:30:27 -08:00
Aman Agrawal
6e9da8ab2a popover_menus: Only take special care of references in message feed.
If the popover reference is in an overlay, the below check:
```js
elements_at_reference_position.some(
    (element) =>
        element.id === "navbar-fixed-container" ||
        element.id === "compose-content" ||
        element.classList.contains("sticky_header"),
)
```
would make the popover not appear if the reference was above them.

To make things simpler for us to check, we only handle popover
references inside message list which we know for sure will
always be below the elements checked above in stacking context.

Hiding popovers for other view is done good enough by
`data-reference-hidden` check that we don't need to manually
handle them.
2023-11-07 09:23:48 -08:00
Aman Agrawal
a4b4b0ad38 popover_menus: Fix popovers not shown if reference is beside compose.
Since `#compose` occupies full width of the window, it is a
part of `elements_at_reference_position` if the reference is
under it.

Since `#compose-content` only occupies the part of window where
compose box is actually visible, we use it decide if the
popover should be hidden.
2023-11-07 09:23:48 -08:00
Lauryn Menard
c3fd7d837b corporate: Add billing admin check to approve sponsorship test.
In commit a3093fad9, we made Hamlet a billing admin for testing.

Because the approve sponsorship flow sends messages from the
notification bot to both organization owners and billing admins,
we need to update that test to cover both of those user types.
2023-11-07 09:13:50 -08:00
evykassirer
8cf436370c stream search: Always close search on Escape.
Fixes #27491.
2023-11-06 15:10:23 -08:00
evykassirer
b51da3bb9a hotkey: Close text fields before closing sidebar.
Before this change, we'd try to close the sidebar before
closing text fields. This means if the user sidebar was
open with a text search in progress, pressing Escape
would close the full sidebar instead of closing search.

This change deprioritizes closing the sidebar, in favor
of closing input fields like search.

This also helps with #26717, because the sidebar seems to
be marked as open even when it's not an overlay, which
means pressing Escape will first "close" it (no visual
changes), before a second Escape would trigger closing
the search field. Ideally we differentiate between
situations where we are showing the sidebar as an
overlay and when we aren't, but that might be a more
involved change.
2023-11-06 15:10:23 -08:00
evykassirer
ac0e03fe89 user search: Always close widget on escape instead of clearing first.
Fixes part of #27491
2023-11-06 15:10:23 -08:00
evykassirer
a764a3e66f more topics: Escape clears input when input is focused.
Fixes part of #27491

We don't immediatley remove the input box, because there's
no existing UI for reopening the input box. (The input box
is always visible when the more topics view is visible.)

This commit makes the change of blurring and clearing the
input, instead of only blurring.
2023-11-06 15:10:23 -08:00
Aman Agrawal
1c03d5e5ce gear_menu: Don't show sponsorship link for orgs on paid plan. 2023-11-06 14:55:14 -08:00
Aman Agrawal
29f4150757 sponsorship: Change title. 2023-11-06 14:55:14 -08:00
Aman Agrawal
48c907e1a2 gear_menu: Show Zulip Cloud Plus for orgs on that plan.
Fixes #27525
2023-11-06 14:55:14 -08:00
Aman Agrawal
ce20325947 corporate: Redirect Request sponsorship to new /sponsorship/ URL. 2023-11-06 14:55:14 -08:00
Aman Agrawal
e8c70a71d9 gear_menu: Show options based on sponsorship status.
Includes some additional tweaks for when to show various
billing related options.

Fixes #27524
2023-11-06 14:55:14 -08:00
Aman Agrawal
be6f467f42 home: Add sponsorship_pending page_param.
This will be used in gear menu to inform admin of their
sponsorship application status.

This includes some additional tweaks for when to show
billing and plans to users.
2023-11-06 14:55:14 -08:00
Aman Agrawal
0012a52e88 billing: Don't redirect to sponsorship page for orgs on paid plan. 2023-11-06 14:55:14 -08:00
Aman Agrawal
0ffa207118 sponsorship: Use this organization instead of your organization. 2023-11-06 14:55:14 -08:00
Aman Agrawal
8da8a625a9 sponsorship: Remove all translation tags. 2023-11-06 14:55:14 -08:00
Aman Agrawal
4fef63de52 sponsorship: Show error message for the field under the field. 2023-11-06 14:55:14 -08:00
Aman Agrawal
073d9bf3c9 custom_check: Exclude billing pages from translation check. 2023-11-06 14:55:14 -08:00
Aman Agrawal
b5fd5f2f5e sponsorship: Minor changes.
Wrap sponsored / sponsorship pending message in a white box.
Remove green heart.
2023-11-06 14:55:14 -08:00
Aman Agrawal
a3093fad97 billing: Show sponsorship status on /sponsorship page.
We redirect users from billing and upgrade page to sponsorship page if the
org has requested for sponsorship or is already sponsored.
2023-11-06 14:55:14 -08:00
Aman Agrawal
ff5e1c3aee sponsorsihp: Make submitting the form work.
Includes various changes to make submitting the sponsorship form work.
2023-11-06 14:55:14 -08:00
Aman Agrawal
a75c8ee6f0 sponsorship: Replace old loading indicator with one on support page. 2023-11-06 14:55:14 -08:00
Aman Agrawal
2466101fe1 sponsorship: Set width of elements like on support page. 2023-11-06 14:55:14 -08:00
Aman Agrawal
349f6f8ab8 sponsorship: Use new style for existing input fields. 2023-11-06 14:55:14 -08:00
Aman Agrawal
91d591046f sponsorship: Add org name field. 2023-11-06 14:55:14 -08:00
Aman Agrawal
e6bb291b8a sponsorship: Add wrapper elements above #sponsorship like support page. 2023-11-06 14:55:14 -08:00
Aman Agrawal
00df9c6c18 urls: Show sponsorship page on a new URL.
Fixes #27377

Replaced #sponsorship links with sponsorship/ links and in other
places liking gear menu.
2023-11-06 14:55:14 -08:00
Aman Agrawal
3ba0fe4349 sponsorship: Remove from billing and upgrade pages. 2023-11-06 14:55:14 -08:00
Viktor
74b2d884c8 translating: Add guidelines for gender-inclusive language in German. 2023-11-06 14:54:14 -08:00
Viktor
d7989417d5 translating: Update Private message → Direct message in German guide. 2023-11-06 14:54:14 -08:00
Viktor
1cefe9117b translating: Fix contradictory or obsolete German notes. 2023-11-06 14:54:14 -08:00
Lauryn Menard
6f7b75c9a3 corporate: Add create_stripe_payment_intent to BillingSession class.
Adds `create_stripe_payment_intent` to the BillingSession abstract
class for the initial upgrade process, which is used only in
`setup_upgrade_checkout_session_and_payment_intent`.

Adds a helper dataclass for the data used to create the stripe
payment intent, StripePaymentIntentData, for the implementation of
more than one child class of BillingSession.

Also adds two abstract helper functions for getting the above stripe
payment intent data as well as updating that data for a stripe
checkout session that is associated with a payment intent:
update_data_for_checkout_session_and_payment_intent,
get_data_for_stripe_payment_intent.
2023-11-06 14:39:36 -08:00
Lauryn Menard
600fea23bb corporate: Add create_stripe_checkout_session to BillingSession class.
Adds `create_stripe_checkout_session` to BillingSession abstract
class, which is then used in all places where a stripe.checkout.Session
and Session were created: start_retry_payment_intent_session,
start_card_update_stripe_session, and
setup_upgrade_checkout_session_and_payment_intent.

Adds a `billing_session_url`  abstract property to the BillingSession
abstract class and for the RealmBillingSession child class sets that
value to `self.realm.uri`.
2023-11-06 14:39:36 -08:00
Lauryn Menard
227645ab65 corporate: Update setup upgrade checkout to only get price per license.
setup_upgrade_checkout_session_and_payment_intent was not using the
datetime values that were being returned by compute_plan_parameters,
so just get the price per license directly.
2023-11-06 14:39:36 -08:00
Aman Agrawal
48d0bedda9 left_sidebar_popovers: Show sidebar icon when visible.
Fixes #27508

Show sidebar icon when sidebar popover is visible so that
it tells users for which row popover is visible and also helps
tippy track when the reference of the popover so that it
can apply [data-reference-hidden] property appropriately.
2023-11-06 14:20:48 -08:00
Aman Agrawal
1a1c5863e4 ui_util: Import entire ui_util module. 2023-11-06 14:20:48 -08:00
Aman Agrawal
6dc17fab57 stream_popover: Extract functions to show sidebar menu icon. 2023-11-06 14:20:48 -08:00
Aman Agrawal
eaf69dc9d5 left_sidebar: Employ existing left_sidebar_menu_icon_visible.
Seems like `left_sidebar_menu_icon_visible` had a regression where
its CSS was removed.

We use it show stream row icon when stream popover is visible.
2023-11-06 14:20:48 -08:00
Tim Abbott
b59e90d100 puppet: Fix buggy media-src Content-Security-Policy.
The colon is invalid syntax. Verified the updated policy using an
online CSP checker.
2023-11-06 14:45:05 -05:00
Prakhar Pratyush
5697084832 css: Improve the css of popover used to change visibility policy.
This commit improves the CSS of popovers in the recipient bar,
recent conversations, and Inbox, which are used to change the
visibility policy.
2023-11-06 10:16:53 -08:00
N-Shar-ma
3a8aac8c67 compose: Remove formatting enclosing entire selection when pasting.
If the selection copied, is entirely within a single element, like text
within a heading (like `h1`) we discard this outermost element and only
convert the inner HTML to markdown.

However, we retain `pre` so a code block within it stays as is and does
not change into inline code, losing all its linebreaks.
2023-11-06 09:07:50 -08:00
roanster007
cf4b46d6f7 compose: Fix re-upload of files when upload fails.
Previously, when uploading of a file fails, the same file
couldn't be re-uploaded again without re opening the
compose box. This is because, the default behaviour of
uppy is that it won't allow re-upload of same files.

This could be fixed by removing the uploaded file when
the upload-error event is invoked for it.

Fixes #27495
2023-11-06 08:53:56 -08:00
Lauryn Menard
ec2b00e8c4 corporate: Move update_billing_method... to BillingSession class.
Moves `update_billing_method_of_current_plan` to the BillingSession
abstract class.

Adds a helper function for support views for the realm case:
`update_realm_billing_method`.
2023-11-04 17:20:49 -07:00
Lauryn Menard
5d07666362 corporate: Move update_sponsorship_status to BillingSession class.
Moves `update_sponsorship_status` to BillingSession abstract class
as `update_customer_sponsorship_status`.

Updates the support views to have a helper for updating this on a
realm: `update_realm_sponsorship_status`.
2023-11-04 17:20:49 -07:00
Lauryn Menard
d06062c179 corporate: Move approve_sponsorship to BillingSession class.
Makes `approve_sponshorship` an abstract method in BillingSession
abstract base class and moves the implementation for realms to the
RealmBillingSession child class.

Adds `approve_realm_sponsorship` helper function that's used in
the support view and initiates the billing session.
2023-11-04 17:20:49 -07:00
Lauryn Menard
c8021925c8 corporate: Create AuditLogEventType enum for BillingSession audit logs.
Creates an enum class, AuditLogEventType, and an abstract method in
BillingSession, get_audit_log_event, so that we have an abstraction
for getting the audit log event type since it might be different for
Customer objects with a realm vs a remote_server.
2023-11-04 17:20:49 -07:00
Lauryn Menard
ee19a9c274 corporate: Move attach_realm_discount to BillingSession class.
This moves the logic for `attach_realm_discount`, which is used in
the support view, to be in the BillingSession class.

Updates the function name to be `attach_discount_to_customer` so
that the context is generalized vs realm specific.

Updates RealmBillingSession implementation to account for actions
that are initiated by a support admin user.

Also moves the helper function `get_discount_for_realm` that is
only used in support views to `corporate/lib/support.py`.
2023-11-04 17:20:49 -07:00
Lauryn Menard
63abf063b7 corporate: Use StripeCustomerData dataclass to create stripe Customer.
So that all child classes of BillingSession generate the same data
structure for customers that are created in Stripe, revise
`get_data_for_stripe_customer` to return a specific dataclass:
StripeCustomerData.
2023-11-04 17:20:49 -07:00
Lauryn Menard
ce6f1f8c18 stripe: Create BillingSession class for different customer types.
So that `update_or_create_stripe_customer` can work for Customer
objects with either a realm or remote_server, we create an abstract
base class, BillingSession, and implement a child class for the
current implementation of Customer objects with a realm.

Refactoring `update_or_create_stripe_customer` also moves
`create_stripe_customer` and `replace_payment_method` to the
BillingSession class.
2023-11-04 17:20:49 -07:00
Karl Stolley
a2ae715226 left_sidebar: Align no-alpha variable for light mode unreads. 2023-11-04 12:50:46 -07:00
David Rosa
56f725e39b help: Document ability to drag-and-drop anywhere to upload a file.
- Replaces the "Via Markdown" tab with "Via drag-and-drop", and
modifies the instructions to explain that you can drag and drop
anywhere in the app, whether or not the compose box is open.
- Adds "Via paste" tab for the copy-pasting instructions.

Fixes #26894.
2023-11-04 12:49:54 -07:00
Aman Agrawal
ddab0e6cd2 test_user_groups: Fix flaky tests.
There order of group ids doesn't matter here and thus the
compared values can have the ids in different order and test
should still pass. So, using `set` for comparing unordered
lists seems like the right fix here.
2023-11-04 12:46:06 -07:00
Aman Agrawal
c9a55941b0 populate_billing_realms: Add Zulip Cloud Free realms. 2023-11-04 12:46:06 -07:00
Aman Agrawal
ac8ad111de recent_view: Handle topics_widget being undefined.
update_load_more_banner can still be called from message_fetch
before topics_widget is defined.
2023-11-04 12:23:48 -07:00
Aman Agrawal
8ea49aa5b5 recent_view: Remove unnecessary call to update_load_more_banner.
Since we are already updating load more banner after each
render in ListWidget, we don't need to call it separately.
2023-11-04 12:23:48 -07:00
Lauryn Menard
379c8d92ec populate_billing_realms: Add next_invoice_date to CustomerPlans.
Having a next_invoice_date is expected for the support view of the
organization.
2023-11-04 12:16:30 -07:00
N-Shar-ma
5c73744aca compose: Fix bug where pasted content would only go to the compose box.
This commits ensures that we pass in the correct context (compose box
or a message being edited) when pasting content.
2023-11-03 14:49:58 -07:00
Alya Abbott
9a9202e093 help: Add instructions to check whether push notifications are configured. 2023-11-03 12:53:41 -07:00
roanster007
ac084ddb0e mention_dataset: Add query to mention cross realm bots as pill.
Previously, cross realm bots were not displayed as mention Pills.

This is because, the data set for validating mentions considers
only the realm id which is None in case of cross realm bots.

Hence, adding an or Q object to it, to also check if
the email is a part of the cross realm bots email, in case the
realm id returns None.

Fixes #26913
2023-11-03 12:29:20 -07:00
Aman Agrawal
826f25744d scroll_bar: Hook to window resize event instead of ResizeObserver.
ResizeObserver isn't supported for Safari iOS versions we support.

We support iOS Safari ≥ 12.2 but ResizeObserver support requires
iOS Safari >=13.4. So if they're on iOS Safari >= 12.2 and < 13.4,
it crashes.
2023-11-03 10:24:36 -07:00
Karl Stolley
322b06b364 left_sidebar: Ensure mark as read vdots follows home view.
Regardless of which home view a user selects, it will have a
hoverable vdots menu and the option to mark all as read.
2023-11-03 10:08:37 -07:00
Karl Stolley
2e4bd4639f left_sidebar: Update order and unreads when switching home views. 2023-11-03 10:08:37 -07:00
Karl Stolley
9226e8bdca left_sidebar: Use grid or flexbox to order selected home view. 2023-11-03 10:08:37 -07:00
Karl Stolley
5d2d8f9215 left_sidebar: Add logic and styles to show unreads on selected home view. 2023-11-03 10:08:37 -07:00
Karl Stolley
4759fc9f69 left_sidebar: Prepare home views with selection logic, unread counts. 2023-11-03 10:08:37 -07:00
Aman Agrawal
00d00036fe recent_view: Don't show load more banner until all rows are rendered.
Fixes #27365
2023-11-03 10:02:17 -07:00
Aman Agrawal
556b61d778 list_widget: Add method to check if all items are rendered. 2023-11-03 10:02:17 -07:00
Aman Agrawal
339f85c308 compose_tooltips: Don't show tooltip on focus.
Fixes #27383
2023-11-03 10:01:21 -07:00
Karl Stolley
14ef3a5464 left_sidebar: Display dark-mode condensed unreads without alpha. 2023-11-03 09:33:06 -07:00
Aman Agrawal
666ac2c445 user_card_popover: Fix reactivate user button not working.
Fixes #27459
2023-11-03 08:47:04 -07:00
Aman Agrawal
c2d45e4cf7 emoji_picker: Stop propagating enter keypress to message.
Similar to emoji_picker.process_keypress, process_enter_while_filtering
should have a stopPropagation.
2023-11-03 08:46:15 -07:00
Aman Agrawal
9ebb3f06b3 populate_billing_realms: Add script to populate different realms.
These realms are based on different cases of realms which can
exist while billing them and hence are useful for testing.
2023-11-03 08:44:23 -07:00
Greg Price
0b3f7a5a6b push_notifs: In dev, automatically use dev APNs cert if provided
This lets us simplify the instructions for testing push notifications
with a development client on iOS.
2023-11-02 17:25:41 -07:00
Aman Agrawal
8b5110d218 popover_menus: Hide popover for which the reference element is hidden. 2023-11-02 16:27:18 -07:00
Aman Agrawal
c3eb17a411 popover_menus: Do early return if possible. 2023-11-02 16:27:18 -07:00
Aman Agrawal
434d156cbc personal_menu_popover: Don't make clear status tooltip interactive.
There is no need for that tooltip to be interactive.
2023-11-02 16:27:18 -07:00
Aman Agrawal
bee8ce4e56 popover_menus: Don't hide on scroll / resize.
Tippyjs is equipped to handle reference element moving from
its initial position so that the popover moves / changes
along with the reference / window size.
2023-11-02 16:27:18 -07:00
Nehal Sharma
d08d287431 compose: Disallow hotkeys when dropdowns (like stream pickers) are open.
When the focus is inside a dropdown, we do not consider any key strokes
to be hotkeys.  This prevents the user from accidentally triggering an
action while trying to type into the dropdown search / filter.

This aims to fix the bug where on pressing a character key that is also
a hotkey, the hotkey action would be triggered, even if the focus was
inside the stream picker dropdown.

Fixes #27492.
2023-11-02 16:08:36 -07:00
Sayam Samal
0c04246723 stream_settings: Remove muted notice string for muted streams.
The muted notice string "Muted streams don't show up in "All messages"
or generate notifications unless you are mentioned." doesn't properly
explain the behavior of muted streams.

A user can still get notifications for muted streams if they follow a
topic in the stream. This is because the followed topics global
settings will override the stream notifications settings.

Fixes #27274.
2023-11-02 14:07:44 -07:00
Sayam Samal
67fd391e3d stream_settings: Add a ? link to "/help/mute-a-stream".
Fixes part of #27274.
2023-11-02 14:07:44 -07:00
Sayam Samal
9f70c531a8 stream_settings: Move "Mute stream" to the top of Notification settings.
In this commit, we move the "Mute stream" option to the top of the
notification settings, since the action of muting a stream works
in conjunction with the other notification settings.

We also rename `is_notification_setting` to
`has_global_notification_setting` which better defines the purpose of
the function and now use `is_notification_setting` only to check which
options we need to show under the "Notification settings" section.

This allows us to accommodate the "Mute stream" option under the
"Notification settings" section without affecting the functionality of
the other notification options.

Fixes part of #27274.
2023-11-02 14:07:44 -07:00
Anders Kaseorg
2e3a8afe2a tests: Add missing $ prefix to jQuery object parameter.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-02 14:06:04 -07:00
Anders Kaseorg
b3fa7b15fc compose_state: Convert module to TypeScript.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-02 14:06:04 -07:00
Anders Kaseorg
d6be353299 compose_state: Make get_or_set accept a full selector.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-02 14:06:04 -07:00
Pratik
daa34685c3 stream_edit: Enable notification settings for muted streams.
This commit allows the user to change notification settings even though
the stream is muted and adds a line of text in stream settings and
notification settings.

Fixes #27272.
2023-11-02 13:57:56 -07:00
N-Shar-ma
a01d670fb2 turndown: Improve pasting experience, focused on pasting Zulip messages.
Turned off and tested escaping with `/` (for now).

Added support + tests for:
- headings.
- strikethrough.
- nested lists.
- code blocks.

Improved handling of:
- links (custom and raw, ignored when wrapping a single image).
- images (now pasted in Zulip's link like syntax).
- custom emojis.
- LaTeX (no garbage symbols, unformatted plain text is pasted)

Added tests for emojis.

Known concerns:
- External images aren't handled anymore by upload.js -- is this a bug?
- Tables lose their formatting on paste.
2023-11-02 13:52:30 -07:00
N-Shar-ma
7c10775e96 markdown: Add zulip-code-block class to code blocks.
We add this class to code blocks so that we can easily detect them in
`copy_and_paste.js` in the next commit.
2023-11-02 13:52:30 -07:00
David Rosa
3eb569f7b1 help: Document "Start new conversation" button.
- Documents the new contextually-aware button since the instructions
  now differ depending on whether you're currently in a direct message
  view or not.
- Simplifies the new topic instructions by combining the first two
  tabs into a single web/desktop tab, with an optional step 2 to
  change the destination stream.

Fixes #27159.
2023-11-02 12:46:10 -07:00
David Rosa
b40fcdff36 help: Document font configuration for unsupported languages.
- Documents how to figure out whether the Zulip font supports a
  language and what to do about unsupported languages, providing
  links to official font / browser documentation rather than
  step-by-step instructions in case the instructions change.

Fixes #26986.
2023-11-02 12:45:10 -07:00
David Rosa
c486ae37d3 help: Document option to paste URL to create a named link.
- Adds a "Via paste" tab to document the new functionality.

Fixes #26892.
2023-11-02 12:44:33 -07:00
evykassirer
bbfe1c7e30 sidebar_ui: Move initializing functions to module file. 2023-11-02 12:23:25 -07:00
Tim Abbott
ac1f1e224b test-backend: Fix testing via shortcuts outside zerver/. 2023-11-02 10:59:45 -07:00
Karl Stolley
c56bd24715 left_sidebar: Attach navigation Tippies to <li> elements.
This corrects a likely long-standing bug where nav tippies appear
over the navigation box. With the gridded DM rows having corrected
the placement of DM Tippies, this commit brings the nav Tippies in
league with those.
2023-11-02 09:56:29 -07:00
Karl Stolley
6784dee775 left_sidebar: Align 'more conversations' and 'back to streams'. 2023-11-02 09:56:29 -07:00
Karl Stolley
65ed4d0ac2 left_sidebar: Provide inline-grid layout for DM row status emoji. 2023-11-02 09:56:29 -07:00
Karl Stolley
b50a09f99e left_sidebar: Set vertical alignment on 22px-tall DM row. 2023-11-02 09:56:29 -07:00
Karl Stolley
4e03209c76 left_sidebar: Establsh CSS Grid on DM rows. 2023-11-02 09:56:29 -07:00
Prakhar Pratyush
b0ef76bf27 topic_mentions: Set 'topic_wildcard_mentioned' flag on @topic mention.
Earlier, the 'wildcard_mentioned' flag was set for both the
stream and topic wildcard mentions.

Now, the 'topic_wildcard_mentioned' flag is set for topic
wildcard mentions, and the 'wildcard_mentioned' flag is set for
stream wildcard mentions.

We will rename the 'wildcard_mentioned' flag to
'stream_wildcard_mentioned' in a later commit.
2023-11-02 09:25:51 -07:00
Prakhar Pratyush
c37871ac3a user_message: Rename unused flags and create an index.
This commit renames the two unused and historical bits of the
'fields' bitfield of the 'UserMessage' and 'ArchivedUserMessage'
tables.

* 'summarize_in_home' to 'topic_wildcard_mentioned'
* 'summarize_in_stream' to 'group_mentioned'

The 'group_mentioned' flag doesn't affect the feature,
but completing the work here helps to save future migration
and indexing efforts on the UserMessage table, as we plan to
use this flag in the future for group mentions.

The unused bits may have old data; we'll clear that in
a separate commit.

It creates the 'zerver_usermessage_any_mentioned_message_id'
index concurrently.
2023-11-02 09:25:51 -07:00
Alex Vandiver
5a44a6624b gitlab: Description can be none. 2023-11-02 09:24:31 -07:00
Anders Kaseorg
a0656a6c56 inbox_util: Convert module to TypeScript.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-02 08:36:44 -07:00
Anders Kaseorg
9ed74cbfcd inbox_util: Remove unused get_dm_key function.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-02 08:36:44 -07:00
Anders Kaseorg
519a2af2a4 stream_color: Convert module to TypeScript.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-02 08:36:44 -07:00
Anders Kaseorg
8208776b47 stream_color: Remove unused container argument to spectrum-colorpicker.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-02 08:36:44 -07:00
Anders Kaseorg
341ba92f23 stream_settings_api: Convert module to TypeScript.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-02 08:36:44 -07:00
Anders Kaseorg
1c5321e57f stream_settings_api: Adjust set_stream_property usage.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-02 08:36:44 -07:00
Sahil Batra
bb15b2d708 users: Send "update" events when deactivating or reactivating users.
We now send "realm_user/update" (and "realm_bot/update" for bots)
events with "is_active" field when deactivating and reactivating
users, including bots.

We would want to use "remove" event for a user losing access
to another user for #10970, so it is better to use "update"
event for deactivation as we only update "is_active" field
in the user objects and the clients still have the data for
deactivated users.

Previously, we used to send "add" event for reactivation along
with complete user objects, but clients should have the data
for deactivated users as well, so an "update" event is enough
like we do when deactivating users.
2023-11-02 08:31:12 -07:00
Anders Kaseorg
5dc9b060d2 compose_pm_pill: Convert module to TypeScript.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-01 21:39:05 -07:00
Anders Kaseorg
b8acfe08a4 user_pill: Convert module to TypeScript.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-11-01 21:39:05 -07:00
Prakhar Pratyush
8e2264b585 hotkey: Add 'narrow to next unread followed topic' hotkey.
This commit adds a 'Shift + N' keyboard shortcut, which is
used to narrow to the next unread followed topic.

Fixes part of #27323.
2023-11-01 17:37:41 -07:00
Mateusz Mandera
6819ecee92 zilencer: Add new LoggingCountStat mobile_pushes_forwarded.
This one counts actual successful deliveries.
2023-11-01 17:26:10 -07:00
Mateusz Mandera
b7117d51b2 zilencer: Don't allow syncing mobile_pushes_received::day count. 2023-11-01 17:26:10 -07:00
Mateusz Mandera
183c775603 zilencer: Add new mobile_pushes_received::day LoggingCountStat. 2023-11-01 17:26:10 -07:00
Mateusz Mandera
2ecd7abc0d zilencer: Make BaseRemoteCount.remote_id field nullable. 2023-11-01 17:26:10 -07:00
Mateusz Mandera
c4fbb6319b do_increment_logging_stat: Rename zerver_object argument.
We are about to add support for having RemoteZulipServer here, which is
a zilencer, not zerver, object. So let's rename this argument to
something more appropriately general.
2023-11-01 17:26:10 -07:00
Mateusz Mandera
21c94953c8 do_increment_logging_stat: Assert that .frequency is either DAY or HOUR.
An assert is appropriate here to ensure that some future additions of
other frequencies don't make this if/else logic wrong without explicitly
failing.
2023-11-01 17:26:10 -07:00
Mateusz Mandera
7eb24ff237 send_test_push_notification_api: Fixes to the API endpoint doc.
Fixes misspellings and bad wording in the original zulip.yaml doc for
this endpoint.
2023-11-01 16:57:04 -07:00
Alex Vandiver
779268c3a4 sentry: Circuit-break on 429s and 500s from Sentry. 2023-11-01 16:16:37 -07:00
Lauryn Menard
784c599c7b integrations: Use stream ID for incoming webhook URL generated in modal.
Instead of using the stream name for the incoming webhook URL that is
generated in the web-app, we used the stream ID instead.

Also, limits the streams listed in the dropdown widget to streams that
the bot owner has permission to post in.
2023-11-01 16:15:23 -07:00
Lauryn Menard
ae7e105ce9 analytics: Use realm_str to generate per realm URL.
Instead of making a database query for each realm, use realm_str
to generate the URL with Realm.host_for_subdomain static method.
2023-11-01 13:08:49 -07:00
Alex Vandiver
631a1def13 portico: Skip footer and adjust header on invalid realm. 2023-11-01 11:24:12 -07:00
Alex Vandiver
54605e54b6 tests: Switch test_i18n fetch helper to use client_foo methods.
38f2a2f475 updated the comment but not the code.  Using
`self.client.post` instead of `self.client_post` means that we do not
set the host headers correctly.
2023-11-01 11:24:12 -07:00
Aryan Bhokare
646128db9e ts: Migrate ‘user_sort.js’ to TypeScript.
Also fixes a bug in the people.ts User; `delivery_email` is never undefined.
2023-11-01 11:19:30 -07:00
Sahil Batra
c756259094 settings: Use server_supported_permission_settings in webapp.
Since we now get the configuration for group permission settings
from server, we can directly use that in webapp instead of
defining it again.
2023-11-01 10:42:56 -07:00
Sahil Batra
47c8e369cf register: Pass the configuration objects for group settings in response.
This commit adds code to pass configuration objects for group
permission settings in register response to clients such that
we do need to duplicate that data in clients and can avoid
future bugs due to inconsistency.

The "server_supported_permission_settings" field is included
in the response if "realm" is present in "fetch_event_types",
as this is what we do for other server-related fields.
2023-11-01 10:42:56 -07:00
Sahil Batra
e458b73a01 user_groups: Move constants for system group names to a new class.
This commit moves constants for system group names to a new
"SystemGroups" class so that we can use these group names
in multiple classes in models.py without worrying about the
order of defining them.
2023-11-01 10:42:56 -07:00
Sahil Batra
2c43c2a4db user_groups: Pass config object to access_user_group_for_setting.
We now pass the complete configuration object for a setting to
access_user_group_for_setting instead of passing the configuration
object's fields as different variables.
2023-11-01 10:42:56 -07:00
Sahil Batra
08d41f9eb3 settings: Rename permissions_configuration variable.
This commit renames permissions_configuration variable to
permission_configuration since the object contains config for
a single permission setting and thus permission_configuration
seems like a better name.
2023-11-01 10:42:56 -07:00
Lauryn Menard
1112cbc822 analytics: Use activity_details_template.html for installation page. 2023-11-01 10:31:04 -07:00
Alex Vandiver
9f0008372c message_fetch: opts.msg_list can be undefined.
See also 69e10b4dae.

Even though opts.msg_list can be undefined, we still want to retry
loading messages for it since our code needs that data regardless
of opts.msg_list being undefined.
2023-10-31 23:33:37 -07:00
Aman Agrawal
cd48ee2152 recent_view: Fix error when user has no messages.
If the user has no messages, `message_list_data` can be empty,
so, in that case, we just use the current oldest_message_timestamp.

The bug can also be reproduced if the last 400 messages were
in a muted topic for the user and we process recent_view_message_list_data
before all messages data.

We still process the remaining logic in `set_oldest_message_date` to
update has_found_newest/oldest.
2023-10-31 22:34:11 -07:00
evykassirer
1f8db236f5 buddy list: Rename user_presences to buddy-list-users-matching-view.
This switches to our preferred dash-separated classnames
instead of underscore-separated, and also updates the
classname to be differentiable from users that aren't
part of the current narrow.

Until the next commit that splits the buddy list, the
name is a bit inaccurate since it still contains all
the users.
2023-10-31 16:06:07 -07:00
evykassirer
beead8e614 lazy set: Increase code coverage.
This was pvreviously covered by tests for the buddy list.

We shouldn't rely on other code to complete code coverage
for this module. In the future it would be good to add
more tests to the lazy set tests to directly cover lazy
set functionality.
2023-10-31 16:06:07 -07:00
roanster007
e2a6d0f4b2 input_pill: Fix backspace behavior on selecting text from start.
This commit fixes the behavior of trying to remove the selected
text when selection is made from start in stream membership
input and compose box dm using backspace button.

Previously the selected text was not removed if no typeahead
was shown for the text and it instead removed the last pill.

Now we remove the selected text and not any input pills on
pressing the backspace button irrespective whether tyepeahead
is shown or not.

This commit updated the code to remove pill only when there is no
other text or when the pointer is at start of input but its selection
 length is 0, in the input and let it follow the default behavior in
cases when there is any text (other than user pills) in the input
to fix the bug.
2023-10-31 15:13:27 -07:00
Lauryn Menard
6671a4a3f5 api-docs: Update description of update_message_flags op: add event.
Updates the description of update_message_flags op: add event for
details about actions that send/trigger the event and other details
that are useful for client implementations.

Also, links to the above updates in the op: remove variant of the
update_message_flags event.
2023-10-31 14:28:30 -07:00
Lauryn Menard
afa5119303 api-docs: Update delete_message event for moved messages case. 2023-10-31 14:28:30 -07:00
N-Shar-ma
ad26247247 compose: Add a placeholder "is …" for the /me slash command.
Fixes: #27399.
2023-10-31 12:32:03 -07:00
Sahil Batra
9a6cf82adc streams: Fix sending stream-related events to guests.
Previous behavior-
- Guest did not receive stream creation events for new
web-public streams.
- Guest did not receive peer_add and peer_remove events
for web-public and subscribed public streams.

This commit fixes the behavior to be -
- Guests now receive stream creation events for new
web-public streams.
- Guest now receive peer_add and peer_remove events for
web-public and subscribed public streams.
2023-10-31 10:54:21 -07:00
Sahil Batra
71b8f49614 streams: Return early if there is no change in subscriptions.
This commit updates code in bulk_remove_subscriptions and
bulk_add_subscriptions to return early if there are no
subscribers to remove or add to the streams.

This change helps us in avoiding unnecessary queries like the
one used to get subscribers list of streams, which is then used
to send events but we would not send any events if no subscribers
are added or removed and some more similar queries.
2023-10-31 10:54:21 -07:00
Sahil Batra
cdd15b4a69 test_events: Fix typo in comments. 2023-10-31 10:54:21 -07:00
David Rosa
4da7fca491 help: Document reactivating a user via user profile.
- Adds instructions tab for reactivating a user via the user profile.

Fixes #26906.
2023-10-31 10:32:46 -07:00
David Rosa
9af7fb9b05 help: Simplify instructions for deactivating a user.
- Drops mostly redundant instuctions tab.
- Adds include block with `tip` for accessing the Manage user tab.
2023-10-31 10:32:46 -07:00
evykassirer
2f6d3605d9 recent view: Rename topic to conversation. 2023-10-31 10:28:55 -07:00
evykassirer
83e0f8bd33 recent view data: Rename get to get_topics. 2023-10-31 10:28:55 -07:00
evykassirer
f786b10403 recent view: Import module instead of separate functions. 2023-10-31 10:28:55 -07:00
Alex Vandiver
803b7b4b93 puppet: Fix SHA256sum of sentry-cli binary. 2023-10-31 10:24:49 -07:00
Alex Vandiver
37b261ef0f puppet: Update dependencies. 2023-10-30 16:10:25 -07:00
Lauryn Menard
d1e2e2d857 corporate: Check for no CustomerPlan on Customer instead of Realm.
In ensure_customer_does_not_have_active_plan, we were already going
through the Customer table to get/check for an active CustomerPlan.

Now we directly get/check for an active CustomerPlan with via the
Customer, which allows for reusing this function for Customer
objects without a Realm set.
2023-10-30 16:09:52 -07:00
Lauryn Menard
85ab5b70e9 corporate: Update string for Customer model.
Update the string method on the Customer model to account for the
realm possibly being None.
2023-10-30 16:09:52 -07:00
Tim Abbott
206c6862f9 i18n: Update translation data from Transifex. 2023-10-30 14:17:54 -07:00
Anders Kaseorg
05d8a65f75 user_status: Remove useless number coercion for object keys.
There’s no such thing as a numeric object key in JavaScript.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-10-30 13:30:47 -07:00
Anders Kaseorg
9989365fe8 user_status: Use unknown, not object, for unvalidated type.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-10-30 13:30:31 -07:00
Karl Stolley
3f5764e5ba navbar: Correct for alpha background on unread dot's border.
This is a case where the use of alpha channels in HSL color values
shows its limits.

The use of an alpha channel is necessary for the background
elements for the top navbar elements, because they sit over the
top of the navbar's bottom border (actually an inset shaddow).

However, it's impossible to use the alpha-channel based color on
elements like the unread dot, where the border actually sits on
top of the element itself--meaning that the effect would be a
larger dot with an imperceptibly darker ring around it.

What this commit does is use a technique suggested by Anders
Kaseorg for using CSS's `color-mix()` functional notation to
calculate an opaque version of the alpha color for use on
elements that do not or cannot directly take the color with
the alpha channel.

See CZO discussion:
https://chat.zulip.org/#narrow/stream/6-frontend/topic/alphas.20in.20color.20definitions/near/1670102
2023-10-30 11:25:12 -07:00
Lalit Kumar Singh
dcf45da09c ts: Migrate user_status module to TypeScript.
Also removed an defensive if check from `emoji.ts` since it is not
needed now that we have `emoji.ts` converted to TypeScript.
2023-10-30 11:10:47 -07:00
Chandan Yadav
951e31a154 user_status: Remove status field from UserInfoDict type. 2023-10-30 11:10:47 -07:00
Aman Agrawal
e1960670ca hello: Make logos more visible in light theme. 2023-10-30 11:05:02 -07:00
Karl Stolley
825a7106c6 left_sidebar: Use DM identifiers around direct-messages list. 2023-10-30 10:14:28 -07:00
Karl Stolley
dcd891a176 left_sidebar: Use dm-list class. 2023-10-30 10:14:28 -07:00
Karl Stolley
01ca3e119b left_sidebar: Use DM identifiers on DM containers. 2023-10-30 10:14:28 -07:00
Karl Stolley
f496b40cd6 left_sidebar: Use DM identifiers on DM list items. 2023-10-30 10:14:28 -07:00
Karl Stolley
9062c75481 left_sidebar: Use DM identifiers on DM row box. 2023-10-30 10:14:28 -07:00
Karl Stolley
a175c3829c left_sidebar: Clean up DM list structures and selectors.
This adds some temporary styles to preserve the DM row layout
as-is, but that will be removed as part of the grid implementation.
2023-10-30 10:14:28 -07:00
Lauryn Menard
37b70a8e24 corporate: Exclude non-realm Customer objects in downgrade small realms.
Updates query in downgrade_small_realms_behind_on_payments_as_needed
to exclude Customer objects with realm=None.
2023-10-30 10:09:17 -07:00
Lauryn Menard
c996bb3c19 corporate: Move functions for realm installation data to new file.
Moves two functions in corporate/lib/stripe.py that are used to
get data for the main installation activity analytics page to a
separate file: corporate/lib/analytics.py.

Also, updates these functions for the possibility of realm being
None for a Customer object.
2023-10-30 10:08:45 -07:00
xoldyckk
8b3f8d77b0 zform validation: Migrate validation logic to zod.
Migrated zform validation logic to make use of zod schema.
2023-10-27 16:14:51 -07:00
Aman Agrawal
2d033c94d9 recent_view: Fix user unable to focus change_visibility_policy icon.
`recent_view_focusable` class should be set on element whose
children can receive focus as per

`$topic_row.find(".recent_view_focusable").eq(col).children().trigger("focus")`
2023-10-27 13:26:36 -07:00
Lalit
151d51acfa input_pill: Remove over defensive if checks for create function.
We should remove this overly defensive code and remove the `undefined`
type from the return type of this function to avoid handling unexpected
`undefined` values in the downstream code.
2023-10-27 13:11:37 -07:00
Lauryn Menard
b19a7a8b8a analytics: Remove Durations tab and hourly columns. 2023-10-27 12:19:13 -07:00
Karl Stolley
01392a57dd left_sidebar: Resize scroll container when toggling condensed views. 2023-10-27 12:07:49 -07:00
evykassirer
0a33598f65 compose fade: Add test for want_normal_display. 2023-10-26 18:33:53 -07:00
evykassirer
6563e621fd buddy list: Simplify argument to get_data_from_keys. 2023-10-26 18:33:53 -07:00
1986 changed files with 144242 additions and 87309 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:

3
.gitignore vendored
View File

@@ -17,7 +17,10 @@
# See `git help ignore` for details on the format.
## Config files for the dev environment
/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>
@@ -99,6 +104,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>

View File

@@ -19,14 +19,20 @@ 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
## Logging setup ##
if settings.ZILENCER_ENABLED:
from zilencer.models import (
RemoteInstallationCount,
RemoteRealm,
RemoteRealmCount,
RemoteZulipServer,
)
logger = logging.getLogger("zulip.analytics")
logger = logging.getLogger("zulip.management")
log_to_file(logger, settings.ANALYTICS_LOG_PATH)
# You can't subtract timedelta.max from a datetime, so use this instead
TIMEDELTA_MAX = timedelta(days=365 * 1000)
@@ -103,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 ##
@@ -191,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()
@@ -212,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
@@ -293,7 +302,7 @@ def do_aggregate_to_summary_table(
# called from zerver.actions; should not throw any errors
def do_increment_logging_stat(
zerver_object: Union[Realm, UserProfile, Stream],
model_object_for_bucket: Union[Realm, UserProfile, Stream, "RemoteRealm", "RemoteZulipServer"],
stat: CountStat,
subgroup: Optional[Union[str, int, bool]],
event_time: datetime,
@@ -304,19 +313,35 @@ def do_increment_logging_stat(
table = stat.data_collector.output_table
if table == RealmCount:
assert isinstance(zerver_object, Realm)
id_args: Dict[str, Union[Realm, UserProfile, Stream]] = {"realm": zerver_object}
assert isinstance(model_object_for_bucket, Realm)
id_args: Dict[
str, Optional[Union[Realm, UserProfile, Stream, "RemoteRealm", "RemoteZulipServer"]]
] = {"realm": model_object_for_bucket}
elif table == UserCount:
assert isinstance(zerver_object, UserProfile)
id_args = {"realm": zerver_object.realm, "user": zerver_object}
else: # StreamCount
assert isinstance(zerver_object, Stream)
id_args = {"realm": zerver_object.realm, "stream": zerver_object}
assert isinstance(model_object_for_bucket, UserProfile)
id_args = {"realm": model_object_for_bucket.realm, "user": model_object_for_bucket}
elif table == StreamCount:
assert isinstance(model_object_for_bucket, Stream)
id_args = {"realm": model_object_for_bucket.realm, "stream": model_object_for_bucket}
elif table == RemoteInstallationCount:
assert isinstance(model_object_for_bucket, RemoteZulipServer)
id_args = {"server": model_object_for_bucket, "remote_id": None}
elif table == RemoteRealmCount:
assert isinstance(model_object_for_bucket, RemoteRealm)
id_args = {
"server": model_object_for_bucket.server,
"remote_realm": model_object_for_bucket,
"remote_id": None,
}
else:
raise AssertionError("Unsupported CountStat output_table")
if stat.frequency == CountStat.DAY:
end_time = ceiling_to_day(event_time)
else: # CountStat.HOUR:
elif stat.frequency == CountStat.HOUR:
end_time = ceiling_to_hour(event_time)
else:
raise AssertionError("Unsupported CountStat frequency")
row, created = table._default_manager.get_or_create(
property=stat.property,
@@ -817,6 +842,12 @@ def get_count_stats(realm: Optional[Realm] = None) -> Dict[str, CountStat]:
CountStat(
"minutes_active::day", DataCollector(UserCount, do_pull_minutes_active), CountStat.DAY
),
# Tracks the number of push notifications requested by the server.
LoggingCountStat(
"mobile_pushes_sent::day",
RealmCount,
CountStat.DAY,
),
# Rate limiting stats
# Used to limit the number of invitation emails sent by a realm
LoggingCountStat("invites_sent::day", RealmCount, CountStat.DAY),
@@ -831,8 +862,65 @@ def get_count_stats(realm: Optional[Realm] = None) -> Dict[str, CountStat]:
),
]
if settings.ZILENCER_ENABLED:
# See also the remote_installation versions of these in REMOTE_INSTALLATION_COUNT_STATS.
count_stats_.append(
LoggingCountStat(
"mobile_pushes_received::day",
RemoteRealmCount,
CountStat.DAY,
)
)
count_stats_.append(
LoggingCountStat(
"mobile_pushes_forwarded::day",
RemoteRealmCount,
CountStat.DAY,
)
)
return OrderedDict((stat.property, stat) for stat in count_stats_)
# These properties are tracked by the bouncer itself and therefore syncing them
# from a remote server should not be allowed - or the server would be able to interfere
# with our data.
BOUNCER_ONLY_REMOTE_COUNT_STAT_PROPERTIES = [
"mobile_pushes_received::day",
"mobile_pushes_forwarded::day",
]
# To avoid refactoring for now COUNT_STATS can be used as before
COUNT_STATS = get_count_stats()
REMOTE_INSTALLATION_COUNT_STATS = OrderedDict()
if settings.ZILENCER_ENABLED:
# REMOTE_INSTALLATION_COUNT_STATS contains duplicates of the
# RemoteRealmCount stats declared above; it is necessary because
# pre-8.0 servers do not send the fields required to identify a
# RemoteRealm.
# Tracks the number of push notifications requested to be sent
# by a remote server.
REMOTE_INSTALLATION_COUNT_STATS["mobile_pushes_received::day"] = LoggingCountStat(
"mobile_pushes_received::day",
RemoteInstallationCount,
CountStat.DAY,
)
# Tracks the number of push notifications successfully sent to
# mobile devices, as requested by the remote server. Therefore
# this should be less than or equal to mobile_pushes_received -
# with potential tiny offsets resulting from a request being
# *received* by the bouncer right before midnight, but *sent* to
# the mobile device right after midnight. This would cause the
# increments to happen to CountStat records for different days.
REMOTE_INSTALLATION_COUNT_STATS["mobile_pushes_forwarded::day"] = LoggingCountStat(
"mobile_pushes_forwarded::day",
RemoteInstallationCount,
CountStat.DAY,
)
ALL_COUNT_STATS = OrderedDict(
list(COUNT_STATS.items()) + list(REMOTE_INSTALLATION_COUNT_STATS.items())
)

View File

@@ -7,7 +7,7 @@ from django.core.management.base import BaseCommand
from django.utils.timezone import now as timezone_now
from typing_extensions import override
from analytics.lib.counts import COUNT_STATS, CountStat
from analytics.lib.counts import ALL_COUNT_STATS, CountStat
from analytics.models import installation_epoch
from zerver.lib.timestamp import TimeZoneNotUTCError, floor_to_day, floor_to_hour, verify_UTC
from zerver.models import Realm
@@ -44,7 +44,7 @@ class Command(BaseCommand):
warning_unfilled_properties = []
critical_unfilled_properties = []
for property, stat in COUNT_STATS.items():
for property, stat in ALL_COUNT_STATS.items():
last_fill = stat.last_successful_fill()
if last_fill is None:
last_fill = installation_epoch()

View File

@@ -4,7 +4,7 @@ from typing import Any
from django.core.management.base import BaseCommand, CommandError
from typing_extensions import override
from analytics.lib.counts import COUNT_STATS, do_drop_single_stat
from analytics.lib.counts import ALL_COUNT_STATS, do_drop_single_stat
class Command(BaseCommand):
@@ -18,7 +18,7 @@ class Command(BaseCommand):
@override
def handle(self, *args: Any, **options: Any) -> None:
property = options["property"]
if property not in COUNT_STATS:
if property not in ALL_COUNT_STATS:
raise CommandError(f"Invalid property: {property}")
if not options["force"]:
raise CommandError("No action taken. Use --force.")

View File

@@ -32,6 +32,7 @@ from zerver.models import (
Recipient,
Stream,
Subscription,
SystemGroups,
UserGroup,
UserProfile,
)
@@ -114,7 +115,7 @@ class Command(BaseCommand):
)
administrators_user_group = UserGroup.objects.get(
name=UserGroup.ADMINISTRATORS_GROUP_NAME, realm=realm, is_system_group=True
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
stream = Stream.objects.create(
name="all",

View File

@@ -1,3 +1,4 @@
import hashlib
import os
import time
from argparse import ArgumentParser
@@ -10,9 +11,9 @@ from django.utils.dateparse import parse_datetime
from django.utils.timezone import now as timezone_now
from typing_extensions import override
from analytics.lib.counts import COUNT_STATS, logger, process_count_stat
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
@@ -74,9 +75,9 @@ class Command(BaseCommand):
fill_to_time = floor_to_hour(fill_to_time.astimezone(timezone.utc))
if options["stat"] is not None:
stats = [COUNT_STATS[options["stat"]]]
stats = [ALL_COUNT_STATS[options["stat"]]]
else:
stats = list(COUNT_STATS.values())
stats = list(ALL_COUNT_STATS.values())
logger.info("Starting updating analytics counts through %s", fill_to_time)
if options["verbose"]:
@@ -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

@@ -0,0 +1,114 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("analytics", "0016_unique_constraint_when_subgroup_null"),
]
# If the server was installed between 7.0 and 7.4 (or main between
# 2c20028aa451 and 7807bff52635), it contains indexes which (when
# running 7.5 or 7807bff52635 or higher) are never used, because
# they contain an improper cast
# (https://code.djangoproject.com/ticket/34840).
#
# We regenerate the indexes here, by dropping and re-creating
# them, so that we know that they are properly formed.
operations = [
migrations.RemoveConstraint(
model_name="installationcount",
name="unique_installation_count",
),
migrations.AddConstraint(
model_name="installationcount",
constraint=models.UniqueConstraint(
condition=models.Q(subgroup__isnull=False),
fields=("property", "subgroup", "end_time"),
name="unique_installation_count",
),
),
migrations.RemoveConstraint(
model_name="installationcount",
name="unique_installation_count_null_subgroup",
),
migrations.AddConstraint(
model_name="installationcount",
constraint=models.UniqueConstraint(
condition=models.Q(subgroup__isnull=True),
fields=("property", "end_time"),
name="unique_installation_count_null_subgroup",
),
),
migrations.RemoveConstraint(
model_name="realmcount",
name="unique_realm_count",
),
migrations.AddConstraint(
model_name="realmcount",
constraint=models.UniqueConstraint(
condition=models.Q(subgroup__isnull=False),
fields=("realm", "property", "subgroup", "end_time"),
name="unique_realm_count",
),
),
migrations.RemoveConstraint(
model_name="realmcount",
name="unique_realm_count_null_subgroup",
),
migrations.AddConstraint(
model_name="realmcount",
constraint=models.UniqueConstraint(
condition=models.Q(subgroup__isnull=True),
fields=("realm", "property", "end_time"),
name="unique_realm_count_null_subgroup",
),
),
migrations.RemoveConstraint(
model_name="streamcount",
name="unique_stream_count",
),
migrations.AddConstraint(
model_name="streamcount",
constraint=models.UniqueConstraint(
condition=models.Q(subgroup__isnull=False),
fields=("stream", "property", "subgroup", "end_time"),
name="unique_stream_count",
),
),
migrations.RemoveConstraint(
model_name="streamcount",
name="unique_stream_count_null_subgroup",
),
migrations.AddConstraint(
model_name="streamcount",
constraint=models.UniqueConstraint(
condition=models.Q(subgroup__isnull=True),
fields=("stream", "property", "end_time"),
name="unique_stream_count_null_subgroup",
),
),
migrations.RemoveConstraint(
model_name="usercount",
name="unique_user_count",
),
migrations.AddConstraint(
model_name="usercount",
constraint=models.UniqueConstraint(
condition=models.Q(subgroup__isnull=False),
fields=("user", "property", "subgroup", "end_time"),
name="unique_user_count",
),
),
migrations.RemoveConstraint(
model_name="usercount",
name="unique_user_count_null_subgroup",
),
migrations.AddConstraint(
model_name="usercount",
constraint=models.UniqueConstraint(
condition=models.Q(subgroup__isnull=True),
fields=("user", "property", "end_time"),
name="unique_user_count_null_subgroup",
),
),
]

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,73 @@
from datetime import timedelta
from unittest import mock
from django.utils.timezone import now as timezone_now
from zerver.lib.test_classes import ZulipTestCase
from zerver.models import Client, UserActivity, UserProfile
from zilencer.models import RemoteRealmAuditLog, 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):
@@ -31,11 +95,12 @@ class ActivityTest(ZulipTestCase):
user_profile.is_staff = True
user_profile.save(update_fields=["is_staff"])
with self.assert_database_query_count(12):
with self.assert_database_query_count(11):
result = self.client_get("/activity")
self.assertEqual(result.status_code, 200)
with self.assert_database_query_count(4):
RemoteRealmAuditLog.objects.bulk_create([RemoteRealmAuditLog(**data) for data in data_list])
with self.assert_database_query_count(6):
result = self.client_get("/activity/remote")
self.assertEqual(result.status_code, 200)
@@ -51,3 +116,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

@@ -3,9 +3,11 @@ from typing import Any, Dict, List, Optional, Tuple, Type
from unittest import mock
import orjson
import time_machine
from django.apps import apps
from django.db import models
from django.db.models import Sum
from django.test import override_settings
from django.utils.timezone import now as timezone_now
from psycopg2.sql import SQL, Literal
from typing_extensions import override
@@ -53,19 +55,26 @@ from zerver.actions.user_activity import update_user_activity_interval
from zerver.actions.users import do_deactivate_user
from zerver.lib.create_user import create_user
from zerver.lib.exceptions import InvitationError
from zerver.lib.push_notifications import (
get_message_payload_apns,
get_message_payload_gcm,
hex_to_b64,
)
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.timestamp import TimeZoneNotUTCError, floor_to_day
from zerver.lib.timestamp import TimeZoneNotUTCError, ceiling_to_day, floor_to_day
from zerver.lib.topic import DB_TOPIC_NAME
from zerver.lib.utils import assert_is_not_none
from zerver.models import (
Client,
Huddle,
Message,
NotificationTriggers,
PreregistrationUser,
Realm,
RealmAuditLog,
Recipient,
Stream,
SystemGroups,
UserActivityInterval,
UserGroup,
UserProfile,
@@ -73,6 +82,14 @@ from zerver.models import (
get_user,
is_cross_realm_bot_email,
)
from zilencer.models import (
RemoteInstallationCount,
RemotePushDeviceToken,
RemoteRealm,
RemoteRealmCount,
RemoteZulipServer,
)
from zilencer.views import get_last_id_from_server
class AnalyticsTestCase(ZulipTestCase):
@@ -89,7 +106,9 @@ class AnalyticsTestCase(ZulipTestCase):
string_id="realmtest", name="Realm Test", date_created=self.TIME_ZERO - 2 * self.DAY
)
self.administrators_user_group = UserGroup.objects.get(
name=UserGroup.ADMINISTRATORS_GROUP_NAME, realm=self.default_realm, is_system_group=True
name=SystemGroups.ADMINISTRATORS,
realm=self.default_realm,
is_system_group=True,
)
# used to generate unique names in self.create_*
@@ -97,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
@@ -111,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
@@ -231,7 +254,10 @@ class AnalyticsTestCase(ZulipTestCase):
kwargs[arg_keys[i]] = values[i]
for key, value in defaults.items():
kwargs[key] = kwargs.get(key, value)
if table is not InstallationCount and "realm" not in kwargs:
if (
table not in [InstallationCount, RemoteInstallationCount, RemoteRealmCount]
and "realm" not in kwargs
):
if "user" in kwargs:
kwargs["realm"] = kwargs["user"].realm
elif "stream" in kwargs:
@@ -1372,6 +1398,249 @@ class TestLoggingCountStats(AnalyticsTestCase):
],
)
@override_settings(PUSH_NOTIFICATION_BOUNCER_URL="https://push.zulip.org.example.com")
def test_mobile_pushes_received_count(self) -> None:
self.server_uuid = "6cde5f7a-1f7e-4978-9716-49f69ebfc9fe"
self.server = RemoteZulipServer.objects.create(
uuid=self.server_uuid,
api_key="magic_secret_api_key",
hostname="demo.example.com",
last_updated=timezone_now(),
)
hamlet = self.example_user("hamlet")
token = "aaaa"
RemotePushDeviceToken.objects.create(
kind=RemotePushDeviceToken.GCM,
token=hex_to_b64(token),
user_uuid=(hamlet.uuid),
server=self.server,
)
RemotePushDeviceToken.objects.create(
kind=RemotePushDeviceToken.GCM,
token=hex_to_b64(token + "aa"),
user_uuid=(hamlet.uuid),
server=self.server,
)
RemotePushDeviceToken.objects.create(
kind=RemotePushDeviceToken.APNS,
token=hex_to_b64(token),
user_uuid=str(hamlet.uuid),
server=self.server,
)
message = Message(
sender=hamlet,
recipient=self.example_user("othello").recipient,
realm_id=hamlet.realm_id,
content="This is test content",
rendered_content="This is test content",
date_sent=timezone_now(),
sending_client=get_client("test"),
)
message.set_topic_name("Test topic")
message.save()
gcm_payload, gcm_options = get_message_payload_gcm(hamlet, message)
apns_payload = get_message_payload_apns(
hamlet, message, NotificationTriggers.DIRECT_MESSAGE
)
# First we'll make a request without providing realm_uuid. That means
# the bouncer can't increment the RemoteRealmCount stat, and only
# RemoteInstallationCount will be incremented.
payload = {
"user_id": hamlet.id,
"user_uuid": str(hamlet.uuid),
"gcm_payload": gcm_payload,
"apns_payload": apns_payload,
"gcm_options": gcm_options,
}
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
), self.assertLogs(
"zilencer.views", level="INFO"
):
result = self.uuid_post(
self.server_uuid,
"/api/v1/remotes/push/notify",
payload,
content_type="application/json",
subdomain="",
)
self.assert_json_success(result)
# There are 3 devices we created for the user:
# 1. The mobile_pushes_received increment should match that number.
# 2. mobile_pushes_forwarded only counts successful deliveries, and we've set up
# the mocks above to simulate 1 successful android and 1 successful apple delivery.
# Thus the increment should be just 2.
self.assertTableState(
RemoteInstallationCount,
["property", "value", "subgroup", "server", "remote_id", "end_time"],
[
[
"mobile_pushes_received::day",
3,
None,
self.server,
None,
ceiling_to_day(now),
],
[
"mobile_pushes_forwarded::day",
2,
None,
self.server,
None,
ceiling_to_day(now),
],
],
)
self.assertFalse(
RemoteRealmCount.objects.filter(property="mobile_pushes_received::day").exists()
)
self.assertFalse(
RemoteRealmCount.objects.filter(property="mobile_pushes_forwarded::day").exists()
)
# Now provide the realm_uuid. However, the RemoteRealm record doesn't exist yet, so it'll
# still be ignored.
payload = {
"user_id": hamlet.id,
"user_uuid": str(hamlet.uuid),
"realm_uuid": str(hamlet.realm.uuid),
"gcm_payload": gcm_payload,
"apns_payload": apns_payload,
"gcm_options": gcm_options,
}
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
), self.assertLogs(
"zilencer.views", level="INFO"
):
result = self.uuid_post(
self.server_uuid,
"/api/v1/remotes/push/notify",
payload,
content_type="application/json",
subdomain="",
)
self.assert_json_success(result)
# The RemoteInstallationCount records get incremented again, but the RemoteRealmCount
# remains ignored due to missing RemoteRealm record.
self.assertTableState(
RemoteInstallationCount,
["property", "value", "subgroup", "server", "remote_id", "end_time"],
[
[
"mobile_pushes_received::day",
6,
None,
self.server,
None,
ceiling_to_day(now),
],
[
"mobile_pushes_forwarded::day",
4,
None,
self.server,
None,
ceiling_to_day(now),
],
],
)
self.assertFalse(
RemoteRealmCount.objects.filter(property="mobile_pushes_received::day").exists()
)
self.assertFalse(
RemoteRealmCount.objects.filter(property="mobile_pushes_forwarded::day").exists()
)
# Create the RemoteRealm registration and repeat the above. This time RemoteRealmCount
# stats should be collected.
realm = hamlet.realm
remote_realm = RemoteRealm.objects.create(
server=self.server,
uuid=realm.uuid,
uuid_owner_secret=realm.uuid_owner_secret,
host=realm.host,
realm_deactivated=realm.deactivated,
realm_date_created=realm.date_created,
)
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
), self.assertLogs(
"zilencer.views", level="INFO"
):
result = self.uuid_post(
self.server_uuid,
"/api/v1/remotes/push/notify",
payload,
content_type="application/json",
subdomain="",
)
self.assert_json_success(result)
# The RemoteInstallationCount records get incremented again, and the RemoteRealmCount
# gets collected.
self.assertTableState(
RemoteInstallationCount,
["property", "value", "subgroup", "server", "remote_id", "end_time"],
[
[
"mobile_pushes_received::day",
9,
None,
self.server,
None,
ceiling_to_day(now),
],
[
"mobile_pushes_forwarded::day",
6,
None,
self.server,
None,
ceiling_to_day(now),
],
],
)
self.assertTableState(
RemoteRealmCount,
["property", "value", "subgroup", "server", "remote_realm", "remote_id", "end_time"],
[
[
"mobile_pushes_received::day",
3,
None,
self.server,
remote_realm,
None,
ceiling_to_day(now),
],
[
"mobile_pushes_forwarded::day",
2,
None,
self.server,
remote_realm,
None,
ceiling_to_day(now),
],
],
)
def test_invites_sent(self) -> None:
property = "invites_sent::day"
@@ -1849,3 +2118,26 @@ class TestRealmActiveHumans(AnalyticsTestCase):
1,
)
self.assertEqual(RealmCount.objects.filter(property="realm_active_humans::day").count(), 1)
class GetLastIdFromServerTest(ZulipTestCase):
def test_get_last_id_from_server_ignores_null(self) -> None:
"""
Verifies that get_last_id_from_server ignores null remote_ids, since this goes
against the default Postgres ordering behavior, which treats nulls as the largest value.
"""
self.server_uuid = "6cde5f7a-1f7e-4978-9716-49f69ebfc9fe"
self.server = RemoteZulipServer.objects.create(
uuid=self.server_uuid,
api_key="magic_secret_api_key",
hostname="demo.example.com",
last_updated=timezone_now(),
)
first = RemoteInstallationCount.objects.create(
end_time=timezone_now(), server=self.server, property="test", value=1, remote_id=1
)
RemoteInstallationCount.objects.create(
end_time=timezone_now(), server=self.server, property="test2", value=1, remote_id=None
)
result = get_last_id_from_server(self.server, RemoteInstallationCount)
self.assertEqual(result, first.remote_id)

View File

@@ -1,13 +1,21 @@
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, update_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,
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
@@ -22,6 +30,7 @@ from zerver.models import (
get_org_type_display_name,
get_realm,
)
from zilencer.lib.remote_counts import MissingDataError
if TYPE_CHECKING:
from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse
@@ -62,8 +71,20 @@ class TestRemoteServerSupportEndpoint(ZulipTestCase):
result,
)
result = self.client_get("/activity/remote/support", {"q": "zulip-1.example.com"})
with mock.patch("analytics.views.support.compute_max_monthly_messages", return_value=1000):
result = self.client_get("/activity/remote/support", {"q": "zulip-1.example.com"})
self.assert_in_success_response(["<h3>zulip-1.example.com</h3>"], result)
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)
with mock.patch(
"analytics.views.support.compute_max_monthly_messages", side_effect=MissingDataError
):
result = self.client_get("/activity/remote/support", {"q": "zulip-1.example.com"})
self.assert_in_success_response(["<h3>zulip-1.example.com</h3>"], result)
self.assert_in_success_response(
["<b>Max monthly messages</b>: Recent data missing"], result
)
self.assert_not_in_success_response(["<h3>zulip-2.example.com</h3>"], result)
result = self.client_get("/activity/remote/support", {"q": "example.com"})
@@ -177,7 +198,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)",
@@ -277,8 +298,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),
)
@@ -342,55 +363,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")
@@ -418,38 +440,50 @@ class TestSupportEndpoint(ZulipTestCase):
result,
)
@mock.patch("analytics.views.support.update_billing_method_of_current_plan")
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")
@@ -470,7 +504,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:
@@ -479,7 +513,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:
@@ -516,14 +550,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")
@@ -558,8 +593,12 @@ class TestSupportEndpoint(ZulipTestCase):
self.assertFalse(customer.sponsorship_pending)
def test_approve_sponsorship(self) -> None:
support_admin = self.example_user("iago")
lear_realm = get_realm("lear")
update_sponsorship_status(lear_realm, True, acting_user=None)
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()
@@ -674,73 +713,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

@@ -12,8 +12,9 @@ from django.urls import reverse
from markupsafe import Markup
from psycopg2.sql import Composable
from zerver.lib.pysa import mark_sanitized
from zerver.lib.url_encoding import append_url_query_string
from zerver.models import UserActivity, get_realm
from zerver.models import Realm, UserActivity
if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo
@@ -47,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]]:
@@ -127,21 +104,27 @@ 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:
url = get_realm(realm_str).uri
host = Realm.host_for_subdomain(realm_str)
url = settings.EXTERNAL_URI_SCHEME + mark_sanitized(host)
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

@@ -1,9 +1,5 @@
import itertools
import time
from collections import defaultdict
from contextlib import suppress
from datetime import timedelta
from typing import Dict, Optional, Tuple
from typing import Dict, Optional
from django.conf import settings
from django.db import connection
@@ -17,22 +13,24 @@ 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.lib.timestamp import timestamp_to_datetime
from zerver.models import Realm, UserActivityInterval, get_org_type_display_name
from zerver.models import Realm, get_org_type_display_name
if settings.BILLING_ENABLED:
from corporate.lib.stripe import (
from corporate.lib.analytics import (
estimate_annual_recurring_revenue_by_realm,
get_realms_to_default_discount_dict,
get_realms_with_default_discount_dict,
)
@@ -92,7 +90,7 @@ def get_realm_day_counts() -> Dict[str, Dict[str, Markup]]:
return result
def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
def realm_summary_table() -> str:
now = timezone_now()
query = SQL(
@@ -203,10 +201,10 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
total_arr = 0
if settings.BILLING_ENABLED:
estimated_arrs = estimate_annual_recurring_revenue_by_realm()
realms_to_default_discount = get_realms_to_default_discount_dict()
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"]
@@ -214,14 +212,14 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
row["arr"] = estimated_arrs[string_id]
if row["plan_type"] in [Realm.PLAN_TYPE_STANDARD, Realm.PLAN_TYPE_PLUS]:
row["effective_rate"] = 100 - int(realms_to_default_discount.get(string_id, 0))
row["effective_rate"] = 100 - int(realms_with_default_discount.get(string_id, 0))
elif row["plan_type"] == Realm.PLAN_TYPE_STANDARD_FREE:
row["effective_rate"] = 0
elif (
row["plan_type"] == Realm.PLAN_TYPE_LIMITED
and string_id in realms_to_default_discount
and string_id in realms_with_default_discount
):
row["effective_rate"] = 100 - int(realms_to_default_discount[string_id])
row["effective_rate"] = 100 - int(realms_with_default_discount[string_id])
else:
row["effective_rate"] = ""
@@ -230,17 +228,6 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
for row in rows:
row["org_type_string"] = get_org_type_display_name(row["org_type"])
# augment data with realm_minutes
total_hours = 0.0
for row in rows:
string_id = row["string_id"]
minutes = realm_minutes.get(string_id, 0.0)
hours = minutes / 60.0
total_hours += hours
row["hours"] = str(int(hours))
with suppress(Exception):
row["hours_per_user"] = "{:.1f}".format(hours / row["dau_count"])
# formatting
for row in rows:
row["realm_url"] = realm_url_link(row["string_id"])
@@ -275,7 +262,6 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
dau_count=total_dau_count,
user_profile_count=total_user_profile_count,
bot_count=total_bot_count,
hours=int(total_hours),
wau_count=total_wau_count,
)
@@ -293,79 +279,16 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
return content
def user_activity_intervals() -> Tuple[Markup, Dict[str, float]]:
day_end = timestamp_to_datetime(time.time())
day_start = day_end - timedelta(hours=24)
output = Markup()
output += "Per-user online duration for the last 24 hours:\n"
total_duration = timedelta(0)
all_intervals = (
UserActivityInterval.objects.filter(
end__gte=day_start,
start__lte=day_end,
)
.select_related(
"user_profile",
"user_profile__realm",
)
.only(
"start",
"end",
"user_profile__delivery_email",
"user_profile__realm__string_id",
)
.order_by(
"user_profile__realm__string_id",
"user_profile__delivery_email",
)
)
by_string_id = lambda row: row.user_profile.realm.string_id
by_email = lambda row: row.user_profile.delivery_email
realm_minutes = {}
for string_id, realm_intervals in itertools.groupby(all_intervals, by_string_id):
realm_duration = timedelta(0)
output += Markup("<hr>") + f"{string_id}\n"
for email, intervals in itertools.groupby(realm_intervals, by_email):
duration = timedelta(0)
for interval in intervals:
start = max(day_start, interval.start)
end = min(day_end, interval.end)
duration += end - start
total_duration += duration
realm_duration += duration
output += f" {email:<37}{duration}\n"
realm_minutes[string_id] = realm_duration.total_seconds() / 60
output += f"\nTotal duration: {total_duration}\n"
output += f"\nTotal duration in minutes: {total_duration.total_seconds() / 60.}\n"
output += f"Total duration amortized to a month: {total_duration.total_seconds() * 30. / 60.}"
content = Markup("<pre>{}</pre>").format(output)
return content, realm_minutes
@require_server_admin
@has_request_variables
def get_installation_activity(request: HttpRequest) -> HttpResponse:
duration_content, realm_minutes = user_activity_intervals()
counts_content: str = realm_summary_table(realm_minutes)
data = [
("Counts", counts_content),
("Durations", duration_content),
]
title = "Activity"
content: str = realm_summary_table()
title = "Installation activity"
return render(
request,
"analytics/activity.html",
context=dict(data=data, title=title, is_home=True),
"analytics/activity_details_template.html",
context=dict(data=content, title=title, is_home=True),
)
@@ -407,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,16 @@ 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 zerver.decorator import require_server_admin
from zilencer.models import get_remote_server_guest_and_non_guest_count
@require_server_admin
@@ -12,32 +20,56 @@ 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
order by latest_value DESC NULLS LAST, push_user_count DESC NULLS LAST
"""
)
@@ -45,15 +77,41 @@ def get_remote_server_activity(request: HttpRequest) -> HttpResponse:
"ID",
"Hostname",
"Contact email",
"Zulip version",
"Analytics users",
"Mobile users",
"Last update time",
"Mobile pushes forwarded",
"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]
for row in rows:
stats = remote_installation_stats_link(row[0])
support = remote_installation_support_link(row[1])
links = stats + " " + support
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)
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
from urllib.parse import urlencode
from typing import Any, Dict, Iterable, List, Optional, Union
from urllib.parse import urlencode, urlsplit
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -49,37 +47,36 @@ from zerver.models import (
from zerver.views.invite import get_invitee_emails_set
if settings.ZILENCER_ENABLED:
from zilencer.models import RemoteZulipServer
from zilencer.lib.remote_counts import MissingDataError, compute_max_monthly_messages
from zilencer.models import RemoteRealm, RemoteZulipServer
if settings.BILLING_ENABLED:
from corporate.lib.stripe import approve_sponsorship as do_approve_sponsorship
from corporate.lib.stripe import (
attach_discount_to_realm,
downgrade_at_the_end_of_billing_cycle,
downgrade_now_without_creating_additional_invoices,
get_discount_for_realm,
get_latest_seat_count,
make_end_of_cycle_updates_if_needed,
switch_realm_from_standard_to_plus_plan,
update_billing_method_of_current_plan,
update_sponsorship_status,
void_all_open_invoices,
RealmBillingSession,
RemoteRealmBillingSession,
RemoteServerBillingSession,
SupportType,
SupportViewRequest,
)
from corporate.models import (
Customer,
CustomerPlan,
get_current_plan_by_realm,
get_customer_by_realm,
from corporate.lib.support import (
PlanData,
get_current_plan_data_for_support_view,
get_customer_discount_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_HOSTED: "Self-hosted",
RemoteZulipServer.PLAN_TYPE_COMMUNITY: "Community",
RemoteZulipServer.PLAN_TYPE_BUSINESS: "Business",
RemoteZulipServer.PLAN_TYPE_ENTERPRISE: "Enterprise",
}[plan_type]
@@ -132,7 +129,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 = [
@@ -140,20 +137,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(
@@ -163,8 +152,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),
@@ -182,6 +171,8 @@ def support(
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: The
# realm_id and a field to change.
@@ -194,24 +185,42 @@ def support(
assert realm_id is not None
realm = Realm.objects.get(id=realm_id)
acting_user = request.user
assert isinstance(acting_user, UserProfile)
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:
@@ -235,51 +244,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_billing_method_of_current_plan(
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_billing_method_of_current_plan(
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_sponsorship_status(realm, True, acting_user=acting_user)
context["success_message"] = f"{realm.string_id} marked as pending sponsorship."
else:
update_sponsorship_status(realm, False, acting_user=acting_user)
context["success_message"] = f"{realm.string_id} is no longer pending sponsorship."
elif approve_sponsorship:
do_approve_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."
@@ -290,6 +254,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)
@@ -302,7 +273,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:
@@ -366,22 +337,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:
new_plan, last_ledger_entry = 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:
@@ -400,7 +358,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
@@ -417,7 +375,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:
@@ -429,8 +389,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:
@@ -442,10 +475,45 @@ def remote_servers_support(
remote_servers = get_remote_servers_for_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_plan_data: Dict[int, PlanData] = {}
realm_plan_data: Dict[int, PlanData] = {}
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_plan_data = get_current_plan_data_for_support_view(realm_billing_session)
realm_plan_data[remote_realm.id] = remote_realm_plan_data
# Get plan data for remote server
server_billing_session = RemoteServerBillingSession(remote_server=remote_server)
remote_server_plan_data = get_current_plan_data_for_support_view(server_billing_session)
server_plan_data[remote_server.id] = remote_server_plan_data
# Get max monthly messages
try:
remote_server_to_max_monthly_messages[remote_server.id] = compute_max_monthly_messages(
remote_server
)
except MissingDataError:
remote_server_to_max_monthly_messages[remote_server.id] = "Recent data missing"
context["remote_servers"] = remote_servers
context["remote_servers_plan_data"] = server_plan_data
context["remote_server_to_max_monthly_messages"] = remote_server_to_max_monthly_messages
context["remote_realms"] = remote_realms
context["remote_realms_plan_data"] = realm_plan_data
context["get_discount"] = get_customer_discount_for_support_view
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,
),
context=context,
)

View File

@@ -20,6 +20,186 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 8.0
**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 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),
[`GET /users/me/subscriptions`](/api/get-subscriptions): Removed
`email_address` field from subscription objects.
* [`GET /streams/{stream_id}/email_address`](/api/get-stream-email-address):
Added new endpoint to get email address of a stream.
**Feature level 225**
* `PATCH /realm`, [`POST /register`](/api/register-queue),
[`GET /events`](/api/get-events): Added `can_access_all_users_group_id`
realm setting, which is the ID of the user group whose members can
access all the users in the oragnization.
* [`POST /register`](/api/register-queue): Added `allowed_system_groups`
field to configuration data object of permission settings passed in
`server_supported_permission_settings`.
**Feature level 224**
* [`GET /events`](/api/get-events), [`GET /messages`](/api/get-messages),
[`GET /messages/{message_id}`](/api/get-message): The `wildcard_mentioned`
flag was deprecated, replaced with `stream_wildcard_mentioned` and
`topic_wildcard_mentioned`, but it is still available for backwards compatibility.
**Feature level 223**
* `POST /users/me/apns_device_token`:
The `appid` parameter is now required.
Previously it defaulted to the server setting `ZULIP_IOS_APP_ID`,
defaulting to "org.zulip.Zulip".
* `POST /remotes/server/register`: The `ios_app_id` parameter is now
required when `kind` is 1, i.e. when registering an APNs token.
Previously it was ignored, and the push bouncer effectively
assumed its value was the server setting `APNS_TOPIC`,
defaulting to "org.zulip.Zulip".
**Feature level 222**
* [`GET /events`](/api/get-events): When a user is deactivated or
reactivated, the server uses `realm_user` events with `op: "update"`
updating the `is_active` field, instead of `realm_user` events with
`op: "remove"` and `op: "add"`, respectively.
* [`GET /events`](/api/get-events): When a bot is deactivated or
reactivated, the server sends `realm_bot` events with `op: "update"`
updating the `is_active` field, instead of `realm_bot` events with
`op: "remove"` and `op: "add"`, respectively.
**Feature level 221**
* [`POST /register`](/api/register-queue): Added `server_supported_permission_settings`
field in the response which contains configuration data for various permission
settings.
**Feature level 220**
* [`GET /events`](/api/get-events): Stream creation events for web-public
streams are now sent to all guest users in the organization as well.
* [`GET /events`](/api/get-events): The `subscription` events for `op:
"peer_add"` and `op: "peer_remove"` are now sent to subscribed guest
users for public streams and to all the guest users for web-public
streams; previously, they incorrectly only received these for
private streams.
**Feature level 219**
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults)
@@ -27,6 +207,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

@@ -47,6 +47,7 @@
* [Create a stream](/api/create-stream)
* [Update a stream](/api/update-stream)
* [Archive a stream](/api/archive-stream)
* [Get stream's email address](/api/get-stream-email-address)
* [Get topics in a stream](/api/get-stream-topics)
* [Topic muting](/api/mute-topic)
* [Update personal preferences for a topic](/api/update-user-topic)

View File

@@ -155,3 +155,45 @@ below are for a webhook named `MyWebHook`.
testing with live data from the service you're integrating and can help you
spot why something isn't working or if the service is using custom HTTP
headers.
## URLs
The base URL for an incoming webhook integration bot is
`{{ api_url }}/v1/external/INTEGRATION_NAME?api_key=API_KEY` where
`INTEGRATION_NAME` is the name of the specific webhook integration and
`API_KEY` is the API key of the bot created by the user for the
integration.
The list of existing webhook integrations can be found in
`zerver/lib/integrations.py` (at `WEBHOOK_INTEGRATIONS`) or by browsing
the [Integrations documentation](/integrations).
Parameters accepted in the URL include:
* `api_key`: **Required**. The API key of the bot created by the user
for the integration. To get a bot's API key, see the [API
keys](/api/api-keys) documentation.
* `stream`: The stream for the integration to send notifications to.
Can be either the stream ID or the [URL-encoded][url-encoder] stream
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
topic configured for stream messages.
* `only_events`, `exclude_events`: Some incoming webhook integrations
support these parameters to filter which events will trigger a
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

@@ -429,8 +429,9 @@ Learn how Zulip integrations work with this simple Hello World example!
by default in the Zulip development environment. If you are running
Zulip in production, you should make sure that this stream exists.
1. {!create-bot-construct-url.md!}
1. {!create-an-incoming-webhook.md!}
1. {!generate-integration-url.md!}
1. To trigger a notification using this example webhook, you can use
`send_webhook_fixture_message` from a [Zulip development
@@ -455,7 +456,7 @@ Learn how Zulip integrations work with this simple Hello World example!
```
`{!create-bot-construct-url.md!}` and `{!congrats.md!}` are examples of
`{!create-an-incoming-webhook.md!}` and `{!congrats.md!}` are examples of
a Markdown macro. Zulip has a macro-based Markdown/Jinja2 framework that
includes macros for common instructions in Zulip's webhooks/integrations
documentation.

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

@@ -0,0 +1,39 @@
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.models import Customer, CustomerPlan
from zerver.lib.utils import assert_is_not_none
def get_realms_with_default_discount_dict() -> Dict[str, Decimal]:
realms_with_default_discount: Dict[str, Any] = {}
customers = (
Customer.objects.exclude(default_discount=None)
.exclude(default_discount=0)
.exclude(realm=None)
)
for customer in customers:
assert customer.realm is not None
realms_with_default_discount[customer.realm.string_id] = assert_is_not_none(
customer.default_discount
)
return realms_with_default_discount
def estimate_annual_recurring_revenue_by_realm() -> Dict[str, int]: # nocoverage
annual_revenue = {}
for plan in CustomerPlan.objects.filter(status=CustomerPlan.ACTIVE).select_related(
"customer__realm"
):
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.BILLING_SCHEDULE_MONTHLY:
renewal_cents *= 12
# TODO: Decimal stuff
annual_revenue[plan.customer.realm.string_id] = int(renewal_cents / 100)
return annual_revenue

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 settings.DEVELOPMENT and 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,18 +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_realm_does_not_have_active_plan,
process_initial_upgrade,
update_or_create_stripe_customer,
)
from corporate.models import 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")
@@ -63,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,45 +84,20 @@ def handle_checkout_session_completed_event(
session.status = Session.COMPLETED
session.save()
assert isinstance(stripe_session.setup_intent, str)
assert stripe_session.metadata is not None
stripe_setup_intent = stripe.SetupIntent.retrieve(stripe_session.setup_intent)
assert session.customer.realm 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(user_id, session.customer.realm)
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_realm_does_not_have_active_plan(user.realm)
update_or_create_stripe_customer(user, 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_realm_does_not_have_active_plan(user.realm)
update_or_create_stripe_customer(user, payment_method)
process_initial_upgrade(
user,
int(stripe_setup_intent.metadata["licenses"]),
stripe_setup_intent.metadata["license_management"] == "automatic",
int(stripe_setup_intent.metadata["billing_schedule"]),
charge_automatically=True,
free_trial=True,
)
elif session.type in [Session.CARD_UPDATE_FROM_BILLING_PAGE]:
update_or_create_stripe_customer(user, payment_method)
billing_session.update_or_create_stripe_customer(payment_method)
@error_handler
@@ -118,13 +107,12 @@ 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 = ""
for charge in stripe_payment_intent.charges:
charge: stripe.Charge
for charge in stripe_payment_intent.charges: # type: ignore[attr-defined] # https://stripe.com/docs/upgrades#2022-11-15
assert charge.payment_method_details is not None
assert charge.payment_method_details.card is not None
description = f"Payment (Card ending in {charge.payment_method_details.card.last4})"
break
@@ -135,48 +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_realm_does_not_have_active_plan(user.realm)
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="Zulip Cloud Standard Credit",
statement_descriptor=CustomerPlan.name_from_tier(plan_tier).replace("Zulip ", "")
+ " Credit",
)
stripe.Invoice.finalize_invoice(stripe_invoice)
raise e
process_initial_upgrade(
user,
billing_session.process_initial_upgrade(
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:
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,15 +1,71 @@
from dataclasses import dataclass
from decimal import Decimal
from typing import Optional
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 BillingSession
from corporate.models import Customer, CustomerPlan, get_current_plan_by_customer
from zerver.models import Realm, get_realm
from zilencer.lib.remote_counts import MissingDataError
def get_support_url(realm: Realm) -> str:
@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
def get_realm_support_url(realm: Realm) -> str:
support_realm_uri = get_realm(settings.STAFF_SUBDOMAIN).uri
support_url = urljoin(
support_realm_uri,
urlunsplit(("", "", reverse("support"), urlencode({"q": realm.string_id}), "")),
)
return support_url
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_current_plan_data_for_support_view(billing_session: BillingSession) -> PlanData:
customer = billing_session.get_customer()
plan = None
if customer is not 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

View File

@@ -0,0 +1,27 @@
# Generated by Django 4.2.6 on 2023-11-11 14:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("corporate", "0018_customer_cloud_xor_self_hosted"),
]
operations = [
migrations.AddField(
model_name="zulipsponsorshiprequest",
name="expected_total_users",
field=models.TextField(default=""),
),
migrations.AddField(
model_name="zulipsponsorshiprequest",
name="paid_users_count",
field=models.TextField(default=""),
),
migrations.AddField(
model_name="zulipsponsorshiprequest",
name="paid_users_description",
field=models.TextField(default=""),
),
]

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,22 +35,36 @@ 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",
)
]
@override
def __str__(self) -> str:
return f"{self.realm!r} {self.stripe_customer_id}"
if self.realm is not None:
return f"{self.realm!r} (with stripe_customer_id: {self.stripe_customer_id})"
else:
return f"{self.remote_server!r} (with stripe_customer_id: {self.stripe_customer_id})"
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)
@@ -88,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]:
@@ -118,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()
@@ -164,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
@@ -211,8 +224,12 @@ 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 = {
BILLING_SCHEDULE_ANNUAL: "Annual",
BILLING_SCHEDULE_MONTHLY: "Monthly",
}
billing_schedule = models.SmallIntegerField()
# The next date the billing system should go through ledger
@@ -232,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
@@ -260,19 +289,33 @@ class CustomerPlan(models.Model):
# TODO maybe override setattr to ensure billing_cycle_anchor, etc
# are immutable.
@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",
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]
@@ -283,7 +326,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
@@ -340,9 +386,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"],
@@ -353,3 +406,12 @@ class ZulipSponsorshipRequest(models.Model):
org_website = models.URLField(max_length=MAX_ORG_URL_LENGTH, blank=True, null=True)
org_description = models.TextField(default="")
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

@@ -21,7 +21,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP STANDARD",
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -30,6 +30,7 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -41,6 +42,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -62,9 +64,10 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -72,21 +75,39 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"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/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKIr9qqsGMgaM-GKNpmc6LBZoWb7DJvCIVhczLJKOYXKROrzmBYTFtupIgmreHcTL1xLwXB0pQP-R7gBA",
"refunded": false,
"refunds": {
"data": [],

View File

@@ -21,7 +21,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP STANDARD",
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -30,6 +30,7 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -41,6 +42,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
@@ -62,9 +64,10 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_details": {
"card": {
"amount_authorized": 36000,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -72,21 +75,39 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"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/acct_NORMALIZED000001/ch_NORMALIZED00000000000002/rcpt_NORMALIZED000000000000000000002",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKJT9qqsGMgbmGcE2CPg6LBYt6tPCI7CnKaxaLVq3zjSaM7atoyWnbp6xDOd4JEAbIIuFCeKskEzYWUyb",
"refunded": false,
"refunds": {
"data": [],
@@ -126,7 +147,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP STANDARD",
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -135,6 +156,7 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -146,6 +168,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -167,9 +190,10 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -177,21 +201,39 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"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/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKJT9qqsGMgZmMGKxY746LBarTbqnnWLLGRzmrQAWL3_ig-D1uUENWBVpjxcDGfuFmMRWLxqSgEkXJuqq",
"refunded": false,
"refunds": {
"data": [],

View File

@@ -3,6 +3,7 @@
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
@@ -13,7 +14,8 @@
"invoice_settings": {
"custom_fields": null,
"default_payment_method": null,
"footer": null
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
@@ -26,5 +28,6 @@
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -3,6 +3,7 @@
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
@@ -12,8 +13,9 @@
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": null,
"footer": null
"default_payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
@@ -26,5 +28,6 @@
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -2,7 +2,8 @@
"address": null,
"balance": 0,
"created": 1000000000,
"currency": null,
"currency": "usd",
"default_currency": "usd",
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
@@ -12,8 +13,9 @@
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"footer": null
"default_payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
@@ -21,10 +23,11 @@
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"next_invoice_sequence": 2,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -1,30 +0,0 @@
{
"address": null,
"balance": 0,
"created": 1000000000,
"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_1K2OXyHSaWXyvFpKNE66Ex7T",
"footer": 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"
}

View File

@@ -3,6 +3,7 @@
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
@@ -34,12 +35,12 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "0341",
"last4": "4242",
"networks": {
"available": [
"visa"
@@ -53,13 +54,14 @@
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1K2ObQHSaWXyvFpK01Q0QnWB",
"id": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
@@ -72,5 +74,6 @@
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -3,6 +3,7 @@
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
@@ -34,12 +35,12 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
"last4": "0341",
"last4": "4242",
"networks": {
"available": [
"visa"
@@ -53,13 +54,14 @@
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1K2Ob7HSaWXyvFpKia7kC5ud",
"id": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
@@ -72,5 +74,6 @@
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
"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,189 +5,53 @@
"created": 1000000000,
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"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_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,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BRzhHOXVXS0dTMk5hbEl2TEhOdnM1ZUF0dloz0100yY43uPHV",
"id": "in_NORMALIZED00000000000001",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BRzhHOXVXS0dTMk5hbEl2TEhOdnM1ZUF0dloz0100yY43uPHV/pdf",
"last_finalization_error": null,
"lines": {
"data": [
{
"amount": 48000,
"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",
"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": 8000,
"unit_amount_decimal": "8000"
},
"proration": false,
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
},
{
"amount": -48000,
"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",
"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": -48000,
"unit_amount_decimal": "-48000"
},
"proration": false,
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
"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
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0001",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"payment_intent": null,
"payment_settings": {
"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,
"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,
"subtotal": 0,
"tax": null,
"total": 0,
"total_discount_amounts": [],
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": null
"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_1K2OXlHSaWXyvFpKIjChqmtl",
"id": "evt_1OIlcWDEQaroqDjsDYC5kGA8",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0001",
"idempotency_key": "cae8e48c-5622-4bdf-85a2-cb06a7ed12d4"
"idempotency_key": "ec0d8ceb-bc0b-4141-9ecf-9f91d42f5a7e"
},
"type": "invoice.payment_succeeded"
"type": "customer.updated"
}
],
"has_more": true,

View File

@@ -7,6 +7,9 @@
"object": {
"amount": 7200,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 7200,
"application": null,
"application_fee_amount": null,
@@ -37,7 +40,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP STANDARD",
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -46,6 +49,7 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -57,6 +61,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -78,9 +83,10 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -88,21 +94,39 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"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/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKIf9qqsGMgZb20Gi7do6LBb8Nk4YXcMYuHylkNS09JAb63jzjtrknnoa60TiX-hHWdshgcroMLKDWN5c",
"refunded": false,
"refunds": {
"data": [],
@@ -127,7 +151,7 @@
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000001"
},
"client_secret": "pi_NORMALIZED00000000000001_secret_fD7F9AdDLLYQt94Ii4rEflkYg",
"client_secret": "pi_NORMALIZED00000000000001_secret_TT71wq3meHmzTEd7mpZbqrDnP",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -136,12 +160,14 @@
"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",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -153,10 +179,12 @@
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -164,6 +192,7 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
@@ -176,13 +205,13 @@
"transfer_group": null
}
},
"id": "evt_3K2OXoHSaWXyvFpK1IgAmGCx",
"id": "evt_3OIlcYDEQaroqDjs16WfiUYV",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0002",
"idempotency_key": "bbd6840b-375d-408a-a4b2-0353118fef83"
"idempotency_key": "89f4d29d-4c41-4c1d-baf7-b33a1c7d25ff"
},
"type": "payment_intent.succeeded"
},
@@ -211,7 +240,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP STANDARD",
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -220,6 +249,7 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -231,6 +261,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -252,9 +283,10 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -262,21 +294,39 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"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/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKIf9qqsGMgaRG54HB6k6LBYbdkPPJRe4ZuuV5tR6SrHKPTGj9DFYvf8ZlOiAmw8lVKk8NDaGGFlsjRM8",
"refunded": false,
"refunds": {
"data": [],
@@ -296,420 +346,15 @@
"transfer_group": null
}
},
"id": "evt_3K2OXoHSaWXyvFpK1BCXNPsv",
"id": "evt_3OIlcYDEQaroqDjs1OA8t8hC",
"livemode": false,
"object": "event",
"pending_webhooks": 2,
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0002",
"idempotency_key": "bbd6840b-375d-408a-a4b2-0353118fef83"
"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_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_1K2OXoHSaWXyvFpKhrceNhbI",
"footer": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
},
"previous_attributes": {
"invoice_settings": {
"default_payment_method": null
}
}
},
"id": "evt_1K2OXrHSaWXyvFpKTft9z8iM",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0003",
"idempotency_key": "41855f8d-7eaf-45a3-8f4b-ebf23c527d2a"
},
"type": "customer.updated"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXpHSaWXyvFpKOq6F3F9K_secret_KhoAzpsEjV8G4oAeYDSFmGYMKv5BRkc",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXpHSaWXyvFpKOq6F3F9K",
"last_setup_error": null,
"latest_attempt": "setatt_1K2OXpHSaWXyvFpKcauo7Bx8",
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_options": {
"card": {
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "succeeded",
"usage": "off_session"
}
},
"id": "evt_1K2OXqHSaWXyvFpKyccpbqqf",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "8c0e4f10-2995-45d3-9b35-aa76865e3557"
},
"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": 3,
"exp_year": 2033,
"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_1K2OXoHSaWXyvFpKhrceNhbI",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
}
},
"id": "evt_1K2OXqHSaWXyvFpKUpLnWMHc",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "8c0e4f10-2995-45d3-9b35-aa76865e3557"
},
"type": "payment_method.attached"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXpHSaWXyvFpKOq6F3F9K_secret_KhoAzpsEjV8G4oAeYDSFmGYMKv5BRkc",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXpHSaWXyvFpKOq6F3F9K",
"last_setup_error": null,
"latest_attempt": "setatt_1K2OXpHSaWXyvFpKcauo7Bx8",
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_options": {
"card": {
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "succeeded",
"usage": "off_session"
}
},
"id": "evt_1K2OXqHSaWXyvFpKZ0zBpGzN",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "8c0e4f10-2995-45d3-9b35-aa76865e3557"
},
"type": "setup_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXoHSaWXyvFpKLyy5ns16_secret_KhoANgFdO2YICL1Urfnax58nGUY0MeV",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXoHSaWXyvFpKLyy5ns16",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_options": {
"card": {
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "requires_payment_method",
"usage": "off_session"
}
},
"id": "evt_1K2OXoHSaWXyvFpKm8uayD4o",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0005",
"idempotency_key": "1eca5c75-5177-4abb-94c1-4e6c39916bef"
},
"type": "setup_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"amount": 7200,
"amount_capturable": 0,
"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_fD7F9AdDLLYQt94Ii4rEflkYg",
"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,
"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_options": {
"card": {
"installments": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"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_3K2OXoHSaWXyvFpK1tATyd2u",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0006",
"idempotency_key": "d6ea8ee3-361c-451e-a3d7-e841957c3d25"
},
"type": "payment_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"address": null,
"balance": 0,
"created": 1000000000,
"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
},
"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"
}
},
"id": "evt_1K2OXnHSaWXyvFpKsZOF5R1j",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0007",
"idempotency_key": "d4faf9c7-d357-4870-b964-b1d993c5c058"
},
"type": "customer.created"
}
],
"has_more": false,

View File

@@ -6,11 +6,13 @@
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -40,29 +42,33 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"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_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"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": {
@@ -70,13 +76,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000003",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -86,21 +93,26 @@
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -114,13 +126,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000004",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -130,27 +143,33 @@
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"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,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -160,6 +179,15 @@
"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",
@@ -170,10 +198,16 @@
"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
@@ -181,12 +215,18 @@
"previous_attributes": {
"attempted": false,
"auto_advance": true,
"effective_at": null,
"ending_balance": null,
"hosted_invoice_url": null,
"invoice_pdf": null,
"next_payment_attempt": 1000000000,
"number": null,
"paid": false,
"rendering": {
"pdf": {
"page_size": "auto"
}
},
"status": "draft",
"status_transitions": {
"finalized_at": null,
@@ -194,13 +234,13 @@
}
}
},
"id": "evt_1K2OXvHSaWXyvFpKAxZLQePJ",
"id": "evt_1OIlcbDEQaroqDjs0RgNo7tS",
"livemode": false,
"object": "event",
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "f3d64bf5-ddf0-4933-86ee-96e9f6aa7149"
"id": "req_NORMALIZED0003",
"idempotency_key": "d8040535-2a29-417e-a8cd-313f534cb633"
},
"type": "invoice.updated"
},
@@ -210,11 +250,13 @@
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"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": false,
@@ -244,29 +286,33 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": null,
"ending_balance": null,
"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,
"lines": {
"data": [
{
"amount": 7200,
"amount_excluding_tax": 7200,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"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": {
@@ -274,13 +320,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000003",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -290,21 +337,26 @@
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -318,13 +370,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000004",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -334,17 +387,21 @@
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
@@ -353,8 +410,10 @@
"object": "invoice",
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -364,6 +423,15 @@
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "auto"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "draft",
@@ -374,88 +442,31 @@
"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_1K2OXvHSaWXyvFpKykfdKZvo",
"id": "evt_1OIlcaDEQaroqDjsgW8xhHxB",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0009",
"idempotency_key": "6f6f6557-9e62-418d-ac7f-8a0a1f602c00"
"id": "req_NORMALIZED0004",
"idempotency_key": "e1bdf299-f588-4704-ae2c-82d0debf6534"
},
"type": "invoice.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"amount": -7200,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"description": "Payment (Card ending in 4242)",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000004",
"invoice": "in_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
},
"proration": false,
"quantity": 1,
"subscription": null,
"tax_rates": [],
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
},
"previous_attributes": {
"invoice": null
}
},
"id": "evt_1K2OXvHSaWXyvFpKRcyaG8m2",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0009",
"idempotency_key": "6f6f6557-9e62-418d-ac7f-8a0a1f602c00"
},
"type": "invoiceitem.updated"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
@@ -468,77 +479,14 @@
"description": "Zulip Cloud Standard",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000003",
"invoice": "in_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"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": 1200,
"unit_amount_decimal": "1200"
},
"proration": false,
"quantity": 6,
"subscription": null,
"tax_rates": [],
"unit_amount": 1200,
"unit_amount_decimal": "1200"
},
"previous_attributes": {
"invoice": null
}
},
"id": "evt_1K2OXvHSaWXyvFpKGZKUgbvc",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0009",
"idempotency_key": "6f6f6557-9e62-418d-ac7f-8a0a1f602c00"
},
"type": "invoiceitem.updated"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"amount": 7200,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"description": "Zulip Cloud Standard",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000003",
"id": "ii_NORMALIZED00000000000001",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -546,13 +494,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000003",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -565,17 +514,18 @@
"quantity": 6,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": 1200,
"unit_amount_decimal": "1200"
}
},
"id": "evt_1K2OXuHSaWXyvFpK7gSvsu8e",
"id": "evt_1OIlcaDEQaroqDjsCma6I5rr",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0010",
"idempotency_key": "b60066b7-cf3e-4569-aa2e-8050562cedb4"
"id": "req_NORMALIZED0005",
"idempotency_key": "cfb4b441-f25a-4d95-a2f0-f0f950bd40fe"
},
"type": "invoiceitem.created"
},
@@ -591,7 +541,7 @@
"description": "Payment (Card ending in 4242)",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000004",
"id": "ii_NORMALIZED00000000000002",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -606,13 +556,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000004",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -625,17 +576,18 @@
"quantity": 1,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
}
},
"id": "evt_1K2OXuHSaWXyvFpK4K7m30wK",
"id": "evt_1OIlcZDEQaroqDjs6GyIO3FO",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0011",
"idempotency_key": "cd6054f5-d831-4874-9ed9-d7b0e3b24336"
"id": "req_NORMALIZED0006",
"idempotency_key": "13255827-b4d3-42fe-9f18-fbcb67305a04"
},
"type": "invoiceitem.created"
},
@@ -648,6 +600,7 @@
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
@@ -657,8 +610,9 @@
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"footer": null
"default_payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
@@ -671,19 +625,21 @@
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
"tax_exempt": "none",
"test_clock": null
},
"previous_attributes": {
"currency": null
"currency": null,
"default_currency": null
}
},
"id": "evt_1K2OXtHSaWXyvFpKaZ6mOKiF",
"id": "evt_1OIlcZDEQaroqDjs2IZnZELZ",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0011",
"idempotency_key": "cd6054f5-d831-4874-9ed9-d7b0e3b24336"
"id": "req_NORMALIZED0006",
"idempotency_key": "13255827-b4d3-42fe-9f18-fbcb67305a04"
},
"type": "customer.updated"
}

View File

@@ -6,11 +6,13 @@
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -40,29 +42,33 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"from_invoice": null,
"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": {
"data": [
{
"amount": 7200,
"amount_excluding_tax": 7200,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"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": {
@@ -70,13 +76,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000003",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -86,21 +93,26 @@
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -114,13 +126,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000004",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -130,27 +143,33 @@
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"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,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -160,6 +179,15 @@
"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",
@@ -170,22 +198,28 @@
"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_1K2OXwHSaWXyvFpKr5uez7KF",
"id": "evt_1OIlccDEQaroqDjsArfDmoTy",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "f3d64bf5-ddf0-4933-86ee-96e9f6aa7149"
"id": "req_NORMALIZED0003",
"idempotency_key": "d8040535-2a29-417e-a8cd-313f534cb633"
},
"type": "invoice.payment_succeeded"
},
@@ -195,11 +229,13 @@
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -229,29 +265,33 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"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_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"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": {
@@ -259,13 +299,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000003",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -275,21 +316,26 @@
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -303,13 +349,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000004",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -319,27 +366,33 @@
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"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,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -349,6 +402,15 @@
"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",
@@ -359,22 +421,28 @@
"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_1K2OXvHSaWXyvFpKbgniTOEt",
"id": "evt_1OIlcbDEQaroqDjsn0Gh2ZXB",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 1,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "f3d64bf5-ddf0-4933-86ee-96e9f6aa7149"
"id": "req_NORMALIZED0003",
"idempotency_key": "d8040535-2a29-417e-a8cd-313f534cb633"
},
"type": "invoice.paid"
},
@@ -384,11 +452,13 @@
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -418,29 +488,33 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"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_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"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": {
@@ -448,13 +522,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000003",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -464,21 +539,26 @@
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -492,13 +572,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000004",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -508,27 +589,33 @@
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"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,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -538,6 +625,15 @@
"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",
@@ -548,22 +644,28 @@
"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_1K2OXvHSaWXyvFpKPFo4dPYw",
"id": "evt_1OIlcbDEQaroqDjshdrWm9Tg",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "f3d64bf5-ddf0-4933-86ee-96e9f6aa7149"
"id": "req_NORMALIZED0003",
"idempotency_key": "d8040535-2a29-417e-a8cd-313f534cb633"
},
"type": "invoice.finalized"
}

View File

@@ -5,189 +5,53 @@
"created": 1000000000,
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"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,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"last_finalization_error": null,
"lines": {
"data": [
{
"amount": 7200,
"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": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"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": 1200,
"unit_amount_decimal": "1200"
},
"proration": false,
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
},
{
"amount": -7200,
"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",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
},
"proration": false,
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
}
],
"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-0003",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"payment_intent": null,
"payment_settings": {
"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,
"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,
"subtotal": 0,
"tax": null,
"total": 0,
"total_discount_amounts": [],
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": null
"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_1K2OXwHSaWXyvFpKr5uez7KF",
"id": "evt_1OIlcgDEQaroqDjsPE8wVcBq",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "f3d64bf5-ddf0-4933-86ee-96e9f6aa7149"
"id": "req_NORMALIZED0007",
"idempotency_key": "6e41d51b-0e25-4d1f-9da2-4472878eb45f"
},
"type": "invoice.payment_succeeded"
"type": "customer.updated"
}
],
"has_more": true,

View File

@@ -7,6 +7,9 @@
"object": {
"amount": 36000,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 36000,
"application": null,
"application_fee_amount": null,
@@ -37,7 +40,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP STANDARD",
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -46,6 +49,7 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -57,6 +61,7 @@
"billing_schedule": "1",
"license_management": "automatic",
"licenses": "6",
"plan_tier": "1",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
@@ -78,9 +83,10 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_details": {
"card": {
"amount_authorized": 36000,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -88,21 +94,39 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"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/acct_NORMALIZED000001/ch_NORMALIZED00000000000002/rcpt_NORMALIZED000000000000000000002",
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKJH9qqsGMgbQjtDZqQM6LBZPz4stEfKrXRPEFZYCyclDAo2Dez80I8lUHYTVeT16LQpU7Ou6wGCFugiR",
"refunded": false,
"refunds": {
"data": [],
@@ -127,7 +151,7 @@
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_KPTN4Jk0sR9DrnVymxUmd0o0F",
"client_secret": "pi_NORMALIZED00000000000002_secret_6P4eLudA3uvQYCOoNiDTkNQEI",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -136,12 +160,14 @@
"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",
"plan_tier": "1",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
@@ -153,10 +179,12 @@
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -164,6 +192,7 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
@@ -176,13 +205,13 @@
"transfer_group": null
}
},
"id": "evt_3K2OXxHSaWXyvFpK152vEHml",
"id": "evt_3OIlciDEQaroqDjs14Vw2Reo",
"livemode": false,
"object": "event",
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0012",
"idempotency_key": "5169e60e-793c-4161-9873-1ba5c523737f"
"id": "req_NORMALIZED0008",
"idempotency_key": "f7ce8212-6677-40df-912b-4ac6c3765065"
},
"type": "payment_intent.succeeded"
},
@@ -191,112 +220,13 @@
"created": 1000000000,
"data": {
"object": {
"address": null,
"balance": 0,
"created": 1000000000,
"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_1K2OXyHSaWXyvFpKNE66Ex7T",
"footer": 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"
},
"previous_attributes": {
"invoice_settings": {
"default_payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI"
}
}
},
"id": "evt_1K2OY1HSaWXyvFpKNFkX6Ye6",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0013",
"idempotency_key": "196311c6-d3ab-40d0-aefc-f770c1411cf5"
},
"type": "customer.updated"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"amount": 36000,
"amount_captured": 36000,
"amount_refunded": 0,
"application": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXzHSaWXyvFpKzkPjM4k7_secret_KhoALa8Xt6eK1ATCOPGurJLxmo2y6xh",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXzHSaWXyvFpKzkPjM4k7",
"last_setup_error": null,
"latest_attempt": "setatt_1K2OXzHSaWXyvFpKG0BonJhW",
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_options": {
"card": {
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "succeeded",
"usage": "off_session"
}
},
"id": "evt_1K2OY0HSaWXyvFpKOGS3PDsC",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0014",
"idempotency_key": "415ae25a-0c46-48f9-8299-d873f9fc51f1"
},
"type": "setup_intent.succeeded"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_NORMALIZED00000000000002",
"billing_details": {
"address": {
"city": null,
@@ -310,198 +240,28 @@
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"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_1K2OXyHSaWXyvFpKNE66Ex7T",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
}
},
"id": "evt_1K2OY0HSaWXyvFpKHdeiTg2r",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0014",
"idempotency_key": "415ae25a-0c46-48f9-8299-d873f9fc51f1"
},
"type": "payment_method.attached"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXzHSaWXyvFpKzkPjM4k7_secret_KhoALa8Xt6eK1ATCOPGurJLxmo2y6xh",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXzHSaWXyvFpKzkPjM4k7",
"last_setup_error": null,
"latest_attempt": "setatt_1K2OXzHSaWXyvFpKG0BonJhW",
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_options": {
"card": {
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "succeeded",
"usage": "off_session"
}
},
"id": "evt_1K2OY0HSaWXyvFpKuYHMv2vM",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0014",
"idempotency_key": "415ae25a-0c46-48f9-8299-d873f9fc51f1"
},
"type": "setup_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXyHSaWXyvFpKvIhWI0JW_secret_KhoAlunPkSHQPGu7zyaTpT81wBUPhqc",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXyHSaWXyvFpKvIhWI0JW",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_options": {
"card": {
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"single_use_mandate": null,
"status": "requires_payment_method",
"usage": "off_session"
}
},
"id": "evt_1K2OXyHSaWXyvFpK5sw7382A",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0015",
"idempotency_key": "de8b28ae-b3f1-416b-bae0-43f9af477f33"
},
"type": "setup_intent.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"amount": 36000,
"amount_capturable": 0,
"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_KPTN4Jk0sR9DrnVymxUmd0o0F",
"confirmation_method": "automatic",
"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",
"id": "pi_NORMALIZED00000000000002",
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
"id": "ch_NORMALIZED00000000000002",
"invoice": null,
"last_payment_error": 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",
@@ -510,41 +270,91 @@
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"next_action": null,
"object": "payment_intent",
"object": "charge",
"on_behalf_of": null,
"payment_method": null,
"payment_method_options": {
"card": {
"installments": null,
"network": null,
"request_three_d_secure": "automatic"
}
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 0,
"seller_message": "Payment complete.",
"type": "authorized"
},
"payment_method_types": [
"card"
],
"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/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKJH9qqsGMgYqjd-cSvE6LBaZocBeNc1j6HmSljaGjK9coB5-xRAyLYhQxqOON0JTW8MYWAJtnxrRxAiL",
"refunded": false,
"refunds": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges/ch_NORMALIZED00000000000002/refunds"
},
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": "Zulip Cloud Standard",
"statement_descriptor_suffix": null,
"status": "requires_payment_method",
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
},
"id": "evt_3K2OXxHSaWXyvFpK1GA2kN6r",
"id": "evt_3OIlciDEQaroqDjs1zZA7DlT",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0016",
"idempotency_key": "d8d89509-f2c5-433c-b8ca-1ae78f192ee5"
"id": "req_NORMALIZED0008",
"idempotency_key": "f7ce8212-6677-40df-912b-4ac6c3765065"
},
"type": "payment_intent.created"
"type": "charge.succeeded"
}
],
"has_more": false,

View File

@@ -6,11 +6,13 @@
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -40,23 +42,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"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_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -70,13 +76,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -86,21 +93,26 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000006",
"invoice_item": "ii_NORMALIZED00000000000006",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -114,13 +126,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -130,27 +143,33 @@
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0004",
"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
},
@@ -160,6 +179,15 @@
"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",
@@ -170,22 +198,28 @@
"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_1K2OY5HSaWXyvFpK1LOtVUsj",
"id": "evt_1OIlclDEQaroqDjslxJqiQFY",
"livemode": false,
"object": "event",
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0017",
"idempotency_key": "c3e3b8e3-19f0-4bab-865b-d2d621dd254b"
"id": "req_NORMALIZED0009",
"idempotency_key": "5fb5bcd9-f037-402f-b67a-6f8eb89f3c11"
},
"type": "invoice.finalized"
},
@@ -195,11 +229,13 @@
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -229,23 +265,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"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_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -259,13 +299,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -275,21 +316,26 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000006",
"invoice_item": "ii_NORMALIZED00000000000006",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -303,13 +349,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -319,27 +366,33 @@
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0004",
"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
},
@@ -349,6 +402,15 @@
"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",
@@ -359,10 +421,16 @@
"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
@@ -370,12 +438,18 @@
"previous_attributes": {
"attempted": false,
"auto_advance": true,
"effective_at": null,
"ending_balance": null,
"hosted_invoice_url": null,
"invoice_pdf": null,
"next_payment_attempt": 1000000000,
"number": null,
"paid": false,
"rendering": {
"pdf": {
"page_size": "auto"
}
},
"status": "draft",
"status_transitions": {
"finalized_at": null,
@@ -383,13 +457,13 @@
}
}
},
"id": "evt_1K2OY5HSaWXyvFpKAdaAGCmF",
"id": "evt_1OIlclDEQaroqDjsbyHu8WF8",
"livemode": false,
"object": "event",
"pending_webhooks": 1,
"request": {
"id": "req_NORMALIZED0017",
"idempotency_key": "c3e3b8e3-19f0-4bab-865b-d2d621dd254b"
"id": "req_NORMALIZED0009",
"idempotency_key": "5fb5bcd9-f037-402f-b67a-6f8eb89f3c11"
},
"type": "invoice.updated"
},
@@ -399,11 +473,13 @@
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"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": false,
@@ -433,23 +509,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": null,
"ending_balance": null,
"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,
"lines": {
"data": [
{
"amount": 36000,
"amount_excluding_tax": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -463,13 +543,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -479,21 +560,26 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000006",
"invoice_item": "ii_NORMALIZED00000000000006",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -507,13 +593,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -523,17 +610,21 @@
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
@@ -542,8 +633,10 @@
"object": "invoice",
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -553,6 +646,15 @@
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "auto"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "draft",
@@ -563,88 +665,31 @@
"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_1K2OY4HSaWXyvFpK5WkKkAff",
"id": "evt_1OIlckDEQaroqDjsUsuf3ZOp",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0018",
"idempotency_key": "f117a125-002e-4795-b38a-dd9babcf6f10"
"id": "req_NORMALIZED0010",
"idempotency_key": "a4d2ad76-c98a-446a-8236-ec43f0faf5fa"
},
"type": "invoice.created"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"amount": -36000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"description": "Payment (Card ending in 4242)",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000006",
"invoice": "in_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"proration": false,
"quantity": 1,
"subscription": null,
"tax_rates": [],
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"previous_attributes": {
"invoice": null
}
},
"id": "evt_1K2OY4HSaWXyvFpKtk38Nrov",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0018",
"idempotency_key": "f117a125-002e-4795-b38a-dd9babcf6f10"
},
"type": "invoiceitem.updated"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
@@ -657,70 +702,7 @@
"description": "Zulip Cloud Standard",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000005",
"invoice": "in_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 6000,
"unit_amount_decimal": "6000"
},
"proration": false,
"quantity": 6,
"subscription": null,
"tax_rates": [],
"unit_amount": 6000,
"unit_amount_decimal": "6000"
},
"previous_attributes": {
"invoice": null
}
},
"id": "evt_1K2OY4HSaWXyvFpK7ju3WXQp",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0018",
"idempotency_key": "f117a125-002e-4795-b38a-dd9babcf6f10"
},
"type": "invoiceitem.updated"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"amount": 36000,
"currency": "usd",
"customer": "cus_NORMALIZED0001",
"date": 1000000000,
"description": "Zulip Cloud Standard",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000005",
"id": "ii_NORMALIZED00000000000003",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -735,13 +717,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -754,17 +737,18 @@
"quantity": 6,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": 6000,
"unit_amount_decimal": "6000"
}
},
"id": "evt_1K2OY4HSaWXyvFpKxihqsHVn",
"id": "evt_1OIlckDEQaroqDjsYzRhmX8V",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0019",
"idempotency_key": "613148af-3c2d-4206-968d-e4ed39a3d02e"
"id": "req_NORMALIZED0011",
"idempotency_key": "0c11698d-33dd-4270-81bd-05e97ff35e0e"
},
"type": "invoiceitem.created"
},
@@ -780,7 +764,7 @@
"description": "Payment (Card ending in 4242)",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000006",
"id": "ii_NORMALIZED00000000000004",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -795,13 +779,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -814,17 +799,18 @@
"quantity": 1,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
}
},
"id": "evt_1K2OY3HSaWXyvFpKc0mBnhSo",
"id": "evt_1OIlcjDEQaroqDjsWfKiiWQ1",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0020",
"idempotency_key": "14c29b95-ba26-4fee-afc1-97ad8a540ebb"
"id": "req_NORMALIZED0012",
"idempotency_key": "e0683c69-8080-4a6a-b9e7-7750eba12988"
},
"type": "invoiceitem.created"
}

View File

@@ -6,11 +6,13 @@
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -40,23 +42,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"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_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -70,13 +76,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -86,21 +93,26 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000006",
"invoice_item": "ii_NORMALIZED00000000000006",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -114,13 +126,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -130,27 +143,33 @@
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0004",
"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
},
@@ -160,6 +179,15 @@
"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",
@@ -170,22 +198,28 @@
"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_1K2OY5HSaWXyvFpK4UZiVK2B",
"id": "evt_1OIlclDEQaroqDjsYQluU75w",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0017",
"idempotency_key": "c3e3b8e3-19f0-4bab-865b-d2d621dd254b"
"id": "req_NORMALIZED0009",
"idempotency_key": "5fb5bcd9-f037-402f-b67a-6f8eb89f3c11"
},
"type": "invoice.payment_succeeded"
},
@@ -195,11 +229,13 @@
"data": {
"object": {
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -229,23 +265,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"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_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -259,13 +299,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -275,21 +316,26 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000006",
"invoice_item": "ii_NORMALIZED00000000000006",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -303,13 +349,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -319,27 +366,33 @@
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0004",
"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
},
@@ -349,6 +402,15 @@
"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",
@@ -359,22 +421,28 @@
"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_1K2OY5HSaWXyvFpKy72Pl7GJ",
"id": "evt_1OIlclDEQaroqDjsWkTK3kjn",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0017",
"idempotency_key": "c3e3b8e3-19f0-4bab-865b-d2d621dd254b"
"id": "req_NORMALIZED0009",
"idempotency_key": "5fb5bcd9-f037-402f-b67a-6f8eb89f3c11"
},
"type": "invoice.paid"
}

View File

@@ -1,10 +1,12 @@
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"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": false,
@@ -34,29 +36,33 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": null,
"ending_balance": null,
"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,
"lines": {
"data": [
{
"amount": 7200,
"amount_excluding_tax": 7200,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"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": {
@@ -64,13 +70,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000003",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -80,21 +87,26 @@
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -108,13 +120,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000004",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -124,17 +137,21 @@
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
},
"livemode": false,
"metadata": {},
@@ -143,8 +160,10 @@
"object": "invoice",
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -154,6 +173,15 @@
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "auto"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "draft",
@@ -164,10 +192,16 @@
"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

View File

@@ -1,10 +1,12 @@
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"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": false,
@@ -34,23 +36,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": null,
"ending_balance": null,
"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,
"lines": {
"data": [
{
"amount": 36000,
"amount_excluding_tax": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -64,13 +70,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -80,21 +87,26 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000006",
"invoice_item": "ii_NORMALIZED00000000000006",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -108,13 +120,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -124,17 +137,21 @@
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
@@ -143,8 +160,10 @@
"object": "invoice",
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -154,6 +173,15 @@
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "auto"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "draft",
@@ -164,10 +192,16 @@
"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

View File

@@ -1,10 +1,12 @@
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 24000,
"amount_paid": 0,
"amount_remaining": 24000,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": false,
@@ -34,23 +36,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": null,
"ending_balance": null,
"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,
"lines": {
"data": [
{
"amount": 24000,
"amount_excluding_tax": 24000,
"currency": "usd",
"description": "Zulip Cloud Standard - renewal",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000007",
"invoice_item": "ii_NORMALIZED00000000000007",
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -64,13 +70,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000007",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0007",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -80,17 +87,21 @@
"unit_amount_decimal": "4000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "4000"
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000004/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},
@@ -99,8 +110,10 @@
"object": "invoice",
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -110,6 +123,15 @@
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "auto"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "draft",
@@ -120,10 +142,16 @@
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 24000,
"subtotal_excluding_tax": 24000,
"tax": null,
"test_clock": null,
"total": 24000,
"total_discount_amounts": [],
"total_excluding_tax": 24000,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": null

View File

@@ -1,10 +1,12 @@
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -34,29 +36,33 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"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_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"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": {
@@ -64,13 +70,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000003",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -80,21 +87,26 @@
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -108,13 +120,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000004",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -124,27 +137,33 @@
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"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,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -154,6 +173,15 @@
"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",
@@ -164,10 +192,16 @@
"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

View File

@@ -1,10 +1,12 @@
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -34,23 +36,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"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_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -64,13 +70,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -80,21 +87,26 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000006",
"invoice_item": "ii_NORMALIZED00000000000006",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -108,13 +120,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -124,27 +137,33 @@
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0004",
"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
},
@@ -154,6 +173,15 @@
"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",
@@ -164,10 +192,16 @@
"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

View File

@@ -1,10 +1,12 @@
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 24000,
"amount_paid": 0,
"amount_remaining": 24000,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": false,
@@ -34,23 +36,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BZ1A2VnVzRGRzR1BIS3V5YWJtejhEYVd1Z0xO0100PL5hiifd",
"id": "in_NORMALIZED00000000000004",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BZ1A2VnVzRGRzR1BIS3V5YWJtejhEYVd1Z0xO0100PL5hiifd/pdf",
"from_invoice": null,
"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": {
"data": [
{
"amount": 24000,
"amount_excluding_tax": 24000,
"currency": "usd",
"description": "Zulip Cloud Standard - renewal",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000007",
"invoice_item": "ii_NORMALIZED00000000000007",
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -64,13 +70,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000007",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0007",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -80,27 +87,33 @@
"unit_amount_decimal": "4000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "4000"
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000004/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": 1000000000,
"number": "NORMALI-0005",
"number": "NORMALI-0004",
"object": "invoice",
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": "pi_NORMALIZED00000000000003",
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -110,6 +123,15 @@
"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",
@@ -120,10 +142,16 @@
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 24000,
"subtotal_excluding_tax": 24000,
"tax": null,
"test_clock": null,
"total": 24000,
"total_discount_amounts": [],
"total_excluding_tax": 24000,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": null

View File

@@ -2,11 +2,13 @@
"data": [
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -36,29 +38,33 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"from_invoice": null,
"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": {
"data": [
{
"amount": 7200,
"amount_excluding_tax": 7200,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"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": {
@@ -66,13 +72,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000003",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -82,21 +89,26 @@
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"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_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -110,13 +122,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000004",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -126,27 +139,33 @@
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"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,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -156,6 +175,15 @@
"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",
@@ -166,10 +194,16 @@
"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

View File

@@ -2,11 +2,13 @@
"data": [
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -36,23 +38,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"from_invoice": null,
"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": {
"data": [
{
"amount": 36000,
"amount_excluding_tax": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -66,13 +72,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -82,188 +89,19 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "6000"
},
{
"amount": -36000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000006",
"invoice_item": "ii_NORMALIZED00000000000006",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"proration": false,
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0004",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"payment_intent": null,
"payment_settings": {
"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,
"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,
"subtotal": 0,
"tax": null,
"total": 0,
"total_discount_amounts": [],
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
},
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"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,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"last_finalization_error": null,
"lines": {
"data": [
{
"amount": 7200,
"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": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"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": 1200,
"unit_amount_decimal": "1200"
},
"proration": false,
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
},
{
"amount": -7200,
"amount_excluding_tax": -36000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
@@ -284,27 +122,32 @@
"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_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
@@ -319,8 +162,10 @@
"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
},
@@ -330,6 +175,15 @@
"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",
@@ -340,10 +194,224 @@
"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
},
{
"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_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": {
"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": 1000000000

View File

@@ -2,11 +2,13 @@
"data": [
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 24000,
"amount_paid": 0,
"amount_remaining": 24000,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": false,
@@ -36,23 +38,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BZ1A2VnVzRGRzR1BIS3V5YWJtejhEYVd1Z0xO0100PL5hiifd",
"id": "in_NORMALIZED00000000000004",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BZ1A2VnVzRGRzR1BIS3V5YWJtejhEYVd1Z0xO0100PL5hiifd/pdf",
"from_invoice": null,
"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": {
"data": [
{
"amount": 24000,
"amount_excluding_tax": 24000,
"currency": "usd",
"description": "Zulip Cloud Standard - renewal",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000007",
"invoice_item": "ii_NORMALIZED00000000000007",
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -66,13 +72,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000007",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0007",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -82,27 +89,33 @@
"unit_amount_decimal": "4000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "4000"
}
],
"has_more": false,
"object": "list",
"total_count": 1,
"url": "/v1/invoices/in_NORMALIZED00000000000004/lines"
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": 1000000000,
"number": "NORMALI-0005",
"number": "NORMALI-0004",
"object": "invoice",
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": "pi_NORMALIZED00000000000003",
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -112,6 +125,15 @@
"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",
@@ -122,21 +144,29 @@
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 24000,
"subtotal_excluding_tax": 24000,
"tax": null,
"test_clock": null,
"total": 24000,
"total_discount_amounts": [],
"total_excluding_tax": 24000,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
},
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"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,
@@ -166,23 +196,27 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"from_invoice": null,
"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": {
"data": [
{
"amount": 36000,
"amount_excluding_tax": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -196,13 +230,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -212,188 +247,19 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "6000"
},
{
"amount": -36000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000006",
"invoice_item": "ii_NORMALIZED00000000000006",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"proration": false,
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0004",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"payment_intent": null,
"payment_settings": {
"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,
"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,
"subtotal": 0,
"tax": null,
"total": 0,
"total_discount_amounts": [],
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
},
{
"account_country": "US",
"account_name": "NORMALIZED-1",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"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,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"last_finalization_error": null,
"lines": {
"data": [
{
"amount": 7200,
"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": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"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": 1200,
"unit_amount_decimal": "1200"
},
"proration": false,
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
},
{
"amount": -7200,
"amount_excluding_tax": -36000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
@@ -414,27 +280,32 @@
"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_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
@@ -449,8 +320,10 @@
"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
},
@@ -460,6 +333,15 @@
"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",
@@ -470,10 +352,224 @@
"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
},
{
"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_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": {
"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": 1000000000

View File

@@ -6,7 +6,7 @@
"description": "Payment (Card ending in 4242)",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000004",
"id": "ii_NORMALIZED00000000000002",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -21,13 +21,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000004",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0004",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -40,6 +41,7 @@
"quantity": 1,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
}

View File

@@ -6,14 +6,14 @@
"description": "Zulip Cloud Standard",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000003",
"id": "ii_NORMALIZED00000000000001",
"invoice": null,
"livemode": false,
"metadata": {},
"object": "invoiceitem",
"period": {
"end": 1000000000,
"start": 1000000000
"end": 1357095845,
"start": 1325473445
},
"plan": null,
"price": {
@@ -21,13 +21,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000003",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -40,6 +41,7 @@
"quantity": 6,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": 1200,
"unit_amount_decimal": "1200"
}

View File

@@ -6,7 +6,7 @@
"description": "Payment (Card ending in 4242)",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000006",
"id": "ii_NORMALIZED00000000000004",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -21,13 +21,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000006",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0006",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -40,6 +41,7 @@
"quantity": 1,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
}

View File

@@ -6,7 +6,7 @@
"description": "Zulip Cloud Standard",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000005",
"id": "ii_NORMALIZED00000000000003",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -21,13 +21,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000005",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0005",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -40,6 +41,7 @@
"quantity": 6,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": 6000,
"unit_amount_decimal": "6000"
}

View File

@@ -6,7 +6,7 @@
"description": "Zulip Cloud Standard - renewal",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000007",
"id": "ii_NORMALIZED00000000000005",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -21,13 +21,14 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"id": "price_NORMALIZED00000000000007",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0007",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -40,6 +41,7 @@
"quantity": 6,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": 4000,
"unit_amount_decimal": "4000"
}

View File

@@ -1,171 +0,0 @@
{
"amount": 7200,
"amount_capturable": 0,
"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 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_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_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_details": {
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"installments": null,
"last4": "4242",
"network": "visa",
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
"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_fD7F9AdDLLYQt94Ii4rEflkYg",
"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,
"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_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_options": {
"card": {
"installments": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"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,171 +0,0 @@
{
"amount": 36000,
"amount_capturable": 0,
"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 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_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_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_details": {
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"installments": null,
"last4": "4242",
"network": "visa",
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000002/rcpt_NORMALIZED000000000000000000002",
"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_KPTN4Jk0sR9DrnVymxUmd0o0F",
"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,
"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_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_options": {
"card": {
"installments": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"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,7 +1,10 @@
{
"amount": 7200,
"amount_capturable": 0,
"amount_received": 0,
"amount_details": {
"tip": {}
},
"amount_received": 7200,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
@@ -9,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_fD7F9AdDLLYQt94Ii4rEflkYg",
"client_secret": "pi_NORMALIZED00000000000001_secret_TT71wq3meHmzTEd7mpZbqrDnP",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -24,12 +154,14 @@
"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",
"plan_tier": "1",
"price_per_license": "1200",
"realm_id": "1",
"realm_str": "zulip",
@@ -41,10 +173,12 @@
"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": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -52,6 +186,7 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
@@ -59,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

@@ -1,7 +1,10 @@
{
"amount": 36000,
"amount_capturable": 0,
"amount_received": 0,
"amount_details": {
"tip": {}
},
"amount_received": 36000,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
@@ -9,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_KPTN4Jk0sR9DrnVymxUmd0o0F",
"client_secret": "pi_NORMALIZED00000000000002_secret_6P4eLudA3uvQYCOoNiDTkNQEI",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -24,12 +154,14 @@
"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",
"plan_tier": "1",
"price_per_license": "6000",
"realm_id": "1",
"realm_str": "zulip",
@@ -41,10 +173,12 @@
"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": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -52,6 +186,7 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
@@ -59,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

@@ -1,47 +0,0 @@
{
"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": "unchecked"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"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": null,
"id": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
}

View File

@@ -1,47 +0,0 @@
{
"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": "unchecked"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"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": null,
"id": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
}

View File

@@ -1,34 +1,27 @@
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXpHSaWXyvFpKOq6F3F9K_secret_KhoAzpsEjV8G4oAeYDSFmGYMKv5BRkc",
"client_secret": "seti_1OIlcUDEQaroqDjsvk7SRRcH_secret_P6zbAgeO1YNtM2cmjYChSjYCWBsUOAB",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXpHSaWXyvFpKOq6F3F9K",
"flow_directions": null,
"id": "seti_1OIlcUDEQaroqDjsvk7SRRcH",
"last_setup_error": null,
"latest_attempt": "setatt_1K2OXpHSaWXyvFpKcauo7Bx8",
"latest_attempt": "setatt_1OIlcUDEQaroqDjsLAiOPmHx",
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},

View File

@@ -1,34 +1,27 @@
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXzHSaWXyvFpKzkPjM4k7_secret_KhoALa8Xt6eK1ATCOPGurJLxmo2y6xh",
"client_secret": "seti_1OIlceDEQaroqDjsF69R2WG0_secret_P6zbftiDh6C2B0Le8GK6ALVcHby9Gyu",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXzHSaWXyvFpKzkPjM4k7",
"flow_directions": null,
"id": "seti_1OIlceDEQaroqDjsF69R2WG0",
"last_setup_error": null,
"latest_attempt": "setatt_1K2OXzHSaWXyvFpKG0BonJhW",
"latest_attempt": "setatt_1OIlceDEQaroqDjs1hVqql0s",
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},

View File

@@ -2,35 +2,28 @@
"data": [
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXoHSaWXyvFpKLyy5ns16_secret_KhoANgFdO2YICL1Urfnax58nGUY0MeV",
"client_secret": "seti_1OIlcTDEQaroqDjs7K1pjsyq_secret_P6zb4X2b8va5VZDxRMR3CgIYMwreYzC",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXoHSaWXyvFpKLyy5ns16",
"flow_directions": null,
"id": "seti_1OIlcTDEQaroqDjs7K1pjsyq",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"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"
}
},

View File

@@ -2,35 +2,28 @@
"data": [
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXyHSaWXyvFpKvIhWI0JW_secret_KhoAlunPkSHQPGu7zyaTpT81wBUPhqc",
"client_secret": "seti_1OIlcdDEQaroqDjsB4ugOdVp_secret_P6zbipSmx7qqSUGMRGqVxyYPXoN42bP",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXyHSaWXyvFpKvIhWI0JW",
"flow_directions": null,
"id": "seti_1OIlcdDEQaroqDjsB4ugOdVp",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"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"
}
},

View File

@@ -1,34 +1,27 @@
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXpHSaWXyvFpKOq6F3F9K_secret_KhoAzpsEjV8G4oAeYDSFmGYMKv5BRkc",
"client_secret": "seti_1OIlcUDEQaroqDjsvk7SRRcH_secret_P6zbAgeO1YNtM2cmjYChSjYCWBsUOAB",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXpHSaWXyvFpKOq6F3F9K",
"flow_directions": null,
"id": "seti_1OIlcUDEQaroqDjsvk7SRRcH",
"last_setup_error": null,
"latest_attempt": "setatt_1K2OXpHSaWXyvFpKcauo7Bx8",
"latest_attempt": "setatt_1OIlcUDEQaroqDjsLAiOPmHx",
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method": "pm_1OIlcUDEQaroqDjsqDT70KqK",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},

View File

@@ -1,34 +1,27 @@
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1K2OXzHSaWXyvFpKzkPjM4k7_secret_KhoALa8Xt6eK1ATCOPGurJLxmo2y6xh",
"client_secret": "seti_1OIlceDEQaroqDjsF69R2WG0_secret_P6zbftiDh6C2B0Le8GK6ALVcHby9Gyu",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"id": "seti_1K2OXzHSaWXyvFpKzkPjM4k7",
"flow_directions": null,
"id": "seti_1OIlceDEQaroqDjsF69R2WG0",
"last_setup_error": null,
"latest_attempt": "setatt_1K2OXzHSaWXyvFpKG0BonJhW",
"latest_attempt": "setatt_1OIlceDEQaroqDjs1hVqql0s",
"livemode": false,
"mandate": 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",
"user_id": "10"
},
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method": "pm_1OIlceDEQaroqDjshc2Y2l9b",
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},

View File

@@ -10,32 +10,45 @@
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
"consent_collection": null,
"created": 1000000000,
"currency": null,
"currency_conversion": null,
"custom_fields": [],
"custom_text": {
"shipping_address": null,
"submit": null,
"terms_of_service_acceptance": null
},
"customer": "cus_NORMALIZED0001",
"customer_details": null,
"customer_creation": null,
"customer_details": {
"address": null,
"email": "hamlet@zulip.com",
"name": null,
"phone": null,
"tax_exempt": null,
"tax_ids": null
},
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED02JXoyt8JsWg6jjuoI58GyCaB4aDXDWB7D0FqdiqlF6JDHjM",
"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",
"object": "checkout.session",
"payment_intent": null,
"payment_link": null,
"payment_method_collection": "always",
"payment_method_configuration_details": null,
"payment_method_options": {},
"payment_method_types": [
"card"
@@ -45,7 +58,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1K2OXoHSaWXyvFpKLyy5ns16",
"setup_intent": "seti_1OIlcTDEQaroqDjs7K1pjsyq",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -55,5 +68,6 @@
"subscription": null,
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"url": "https://checkout.stripe.com/pay/cs_test_NORMALIZED02JXoyt8JsWg6jjuoI58GyCaB4aDXDWB7D0FqdiqlF6JDHjM#fidkdWxOYHwnPyd1blpxYHZxWnZkQk5DYTNqTGNxQl9JQjJIf2B1Y05BdScpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED02sb4yHaq28VHOZscOtqKWTLoGQJALldTi43GUE0P56ay9sU#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}

View File

@@ -10,32 +10,45 @@
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
"consent_collection": null,
"created": 1000000000,
"currency": null,
"currency_conversion": null,
"custom_fields": [],
"custom_text": {
"shipping_address": null,
"submit": null,
"terms_of_service_acceptance": null
},
"customer": "cus_NORMALIZED0001",
"customer_details": null,
"customer_creation": null,
"customer_details": {
"address": null,
"email": "hamlet@zulip.com",
"name": null,
"phone": null,
"tax_exempt": null,
"tax_ids": null
},
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED03qjdrziUfZOYhaTIKoiuAENHHbecsfYggKZ4kWegRShmXDo",
"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",
"object": "checkout.session",
"payment_intent": null,
"payment_link": null,
"payment_method_collection": "always",
"payment_method_configuration_details": null,
"payment_method_options": {},
"payment_method_types": [
"card"
@@ -45,7 +58,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1K2OXyHSaWXyvFpKvIhWI0JW",
"setup_intent": "seti_1OIlcdDEQaroqDjsB4ugOdVp",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -55,5 +68,6 @@
"subscription": null,
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"url": "https://checkout.stripe.com/pay/cs_test_NORMALIZED03qjdrziUfZOYhaTIKoiuAENHHbecsfYggKZ4kWegRShmXDo#fidkdWxOYHwnPyd1blpxYHZxWnZkQk5DYTNqTGNxQl9JQjJIf2B1Y05BdScpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED03vZmJUBP0ybiRb1yrtpVKGaX0cm1V0nhnpM6e5pcI1y8CkG#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}

View File

@@ -12,32 +12,45 @@
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
"consent_collection": null,
"created": 1000000000,
"currency": null,
"currency_conversion": null,
"custom_fields": [],
"custom_text": {
"shipping_address": null,
"submit": null,
"terms_of_service_acceptance": null
},
"customer": "cus_NORMALIZED0001",
"customer_details": null,
"customer_creation": null,
"customer_details": {
"address": null,
"email": "hamlet@zulip.com",
"name": null,
"phone": null,
"tax_exempt": null,
"tax_ids": null
},
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED02JXoyt8JsWg6jjuoI58GyCaB4aDXDWB7D0FqdiqlF6JDHjM",
"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",
"object": "checkout.session",
"payment_intent": null,
"payment_link": null,
"payment_method_collection": "always",
"payment_method_configuration_details": null,
"payment_method_options": {},
"payment_method_types": [
"card"
@@ -47,7 +60,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1K2OXoHSaWXyvFpKLyy5ns16",
"setup_intent": "seti_1OIlcTDEQaroqDjs7K1pjsyq",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -57,7 +70,8 @@
"subscription": null,
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"url": "https://checkout.stripe.com/pay/cs_test_NORMALIZED02JXoyt8JsWg6jjuoI58GyCaB4aDXDWB7D0FqdiqlF6JDHjM#fidkdWxOYHwnPyd1blpxYHZxWnZkQk5DYTNqTGNxQl9JQjJIf2B1Y05BdScpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED02sb4yHaq28VHOZscOtqKWTLoGQJALldTi43GUE0P56ay9sU#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}
],
"has_more": true,

View File

@@ -12,32 +12,45 @@
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
"consent_collection": null,
"created": 1000000000,
"currency": null,
"currency_conversion": null,
"custom_fields": [],
"custom_text": {
"shipping_address": null,
"submit": null,
"terms_of_service_acceptance": null
},
"customer": "cus_NORMALIZED0001",
"customer_details": null,
"customer_creation": null,
"customer_details": {
"address": null,
"email": "hamlet@zulip.com",
"name": null,
"phone": null,
"tax_exempt": null,
"tax_ids": null
},
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED03qjdrziUfZOYhaTIKoiuAENHHbecsfYggKZ4kWegRShmXDo",
"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",
"object": "checkout.session",
"payment_intent": null,
"payment_link": null,
"payment_method_collection": "always",
"payment_method_configuration_details": null,
"payment_method_options": {},
"payment_method_types": [
"card"
@@ -47,7 +60,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1K2OXyHSaWXyvFpKvIhWI0JW",
"setup_intent": "seti_1OIlcdDEQaroqDjsB4ugOdVp",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -57,7 +70,8 @@
"subscription": null,
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"url": "https://checkout.stripe.com/pay/cs_test_NORMALIZED03qjdrziUfZOYhaTIKoiuAENHHbecsfYggKZ4kWegRShmXDo#fidkdWxOYHwnPyd1blpxYHZxWnZkQk5DYTNqTGNxQl9JQjJIf2B1Y05BdScpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED03vZmJUBP0ybiRb1yrtpVKGaX0cm1V0nhnpM6e5pcI1y8CkG#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}
],
"has_more": true,

View File

@@ -3,17 +3,19 @@
"balance": 0,
"created": 1000000000,
"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": {
"custom_fields": null,
"default_payment_method": null,
"footer": null
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
@@ -26,5 +28,6 @@
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -3,17 +3,19 @@
"balance": 0,
"created": 1000000000,
"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": {
"custom_fields": null,
"default_payment_method": null,
"footer": null
"default_payment_method": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
@@ -26,5 +28,6 @@
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -2,12 +2,13 @@
"address": null,
"balance": 0,
"created": 1000000000,
"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": {
@@ -34,8 +35,8 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
@@ -53,13 +54,14 @@
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1K2OXeHSaWXyvFpKfQsNmKNC",
"id": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
@@ -67,10 +69,11 @@
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
"tax_exempt": "none",
"test_clock": null
}

View File

@@ -2,12 +2,13 @@
"address": null,
"balance": 0,
"created": 1000000000,
"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": {
@@ -34,8 +35,8 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2033,
"exp_month": 12,
"exp_year": 2024,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
@@ -53,13 +54,14 @@
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1K2OXeHSaWXyvFpKfQsNmKNC",
"id": "pm_1OIlXhDEQaroqDjsm8crnb7t",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
},
"footer": null
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
@@ -67,10 +69,11 @@
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"next_invoice_sequence": 1,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none"
"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": 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
}

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