Compare commits

..

56 Commits

Author SHA1 Message Date
Alex Vandiver
4174047f64 Release Zulip Server 7.2. 2023-07-05 19:57:28 +00:00
Alex Vandiver
adbee935f7 upload: Provide a default upload file name, rather than 500.
(cherry picked from commit e2847790b6)
2023-07-04 16:31:05 +00:00
Alex Vandiver
ebed224395 i18n: Update translation data from Transifex. 2023-07-03 20:40:46 +00:00
Alex Vandiver
738429cf77 middleware: Detect reverse proxy misconfigurations.
Combine nginx and Django middlware to stop putting misleading warnings
about `CSRF_TRUSTED_ORIGINS` when the issue is untrusted proxies.
This attempts to, in the error logs, diagnose and suggest next steps
to fix common proxy misconfigurations.

See also #24599 and zulip/docker-zulip#403.

(cherry picked from commit 8a77cca341)
2023-07-03 18:52:30 +00:00
Alex Vandiver
2f91471e98 zproject: Prevent having exactly 17/18 middlewares, for Python 3.11 bug.
Having exactly 17 or 18 middlewares, on Python 3.11.0 and above,
causes python to segfault when running tests with coverage; see
https://github.com/python/cpython/issues/106092

Work around this by adding one or two no-op middlewares if we would
hit those unlucky numbers.  We only add them in testing, since
coverage is a requirement to trigger it, and there is no reason to
burden production with additional wrapping.

(cherry picked from commit cf0b803d50)
2023-07-03 18:52:30 +00:00
Alex Vandiver
8f9807176e puppet: Remove loadbalancer configurations when they are unset.
(cherry picked from commit 671b708c4b)
2023-07-03 18:52:30 +00:00
Karl Stolley
217f5731fe stream_settings: Fix scroll at max-height.
Simplebar seems unaware of the `max-height: 1000px` on
`.subscriptions-container`, and therefore does not properly provide
a scrollbar when it's needed.

This commit adds a `max-height` to the stream Simplebar container,
ensuring that otherwise hidden content that Simplebar believes to
be visible can be scrolled to.

Finally, rather than rely on magic numbers or math done in comments,
this commit establishes CSS variables for all relevant modal-element
heights, doing the math inline using CSS calc().

Fixes #26107.

(cherry picked from commit 0c55fb7e89)
2023-07-03 18:51:58 +00:00
Karl Stolley
ca0fd7f797 bot_icon: Adjust bot-icon color for light and dark modes.
(cherry picked from commit a9bc5e94e7)
2023-07-03 18:51:37 +00:00
Alex Vandiver
da4c4f74f2 slack: Handle the special case of permissions denied on team.info call.
This is a follow-up to 4c8915c8e4, for
the case when the `team:read` permission is missing, which causes the
`team.info` call itself to fail.  The error message supplies
information about the provided and missing permissions -- but it also
still sends the `X-OAuth-Scopes` header which we normall read, so we can
use that as normal.

(cherry picked from commit 21aeb4a040)
2023-07-03 18:51:15 +00:00
Alex Vandiver
b2068222e0 pgroonga: Remove 'GRANT USAGE' statement again.
dc2726c814 removed these statements, but c8ec3dfcf6 accidentally
brought one back.  Remove it.

(cherry picked from commit f5540303ba)
2023-07-03 18:50:51 +00:00
Alex Vandiver
2dfc0463bd pgroonga: Run upgrade SQL when pgroonga package is updated.
Updating the pgroonga package is not sufficient to upgrade the
extension in PostgreSQL -- an `ALTER EXTENSION pgroonga UPDATE` must
explicitly be run[^1].  Failure to do so can lead to unexpected behavior,
including crashes of PostgreSQL.

Expand on the existing `pgroonga_setup.sql.applied` file, to track
which version of the PostgreSQL extension has been configured.  If the
file exists but is empty, we run `ALTER EXTENSION pgroonga UPDATE`
regardless -- if it is a no-op, it still succeeds with a `NOTICE`:

```
zulip=# ALTER EXTENSION pgroonga UPDATE;
NOTICE:  version "3.0.8" of extension "pgroonga" is already installed
ALTER EXTENSION
```

The simple `ALTER EXTENSION` is sufficient for the
backwards-compatible case[^1] -- which, for our usage, is every
upgrade since 0.9 -> 1.0.  Since version 1.0 was released in 2015,
before pgroonga support was added to Zulip in 2016, we can assume for
the moment that all pgroonga upgrades are backwards-compatible, and
not bother regenerating indexes.

Fixes: #25989.

[^1]: https://pgroonga.github.io/upgrade/

(cherry picked from commit c8ec3dfcf6)
2023-07-03 18:50:14 +00:00
Alex Vandiver
92c538c862 pgroonga: Remove now-unnecessary 'GRANT USAGE' statement.
This was only necessary for PGroonga 1.x, and the `pgroonga` schema
will most likely be removed at some point inthe future, which will
make this statement error out.

Drop the unnecessary statement.

(cherry picked from commit dc2726c814)
2023-07-03 18:50:14 +00:00
Lauryn Menard
2e03e1b6ee narrow: Mark as read in by_recipient based on case ("dm" or "stream").
In commit #25837, we added in a check for the user's mark as read
policy in the frontend for `by_topic` and `by_recipient` narrowing.
In that change, the assumption was that for both functions, it was
sufficient to check only for whether the user policy was to never
mark as read.

But because the `by_recipient` function may narrow to an interleaved
stream view, it is possible that message will be marked as read
when the user did not expect it to be (e.g. they marked all the
messages in a topic narrow as unread and then used the `S` key
shortcut to navigate back to the stream view) when they have
conversation views only as their mark as read  policy.

Here we move the check for the user's mark as read policy to be in
the two cases for `by_recipient` so that the mark as read behavior
here matches the user's setting.

(cherry picked from commit c5fbd3f085)
2023-07-03 18:49:31 +00:00
Daniil Fadeev
0ac81a1b77 compose_banner: Remove uploads banners when clearing compose box.
Upload banners were not cleared after closing compose box, which meant
that they would remain present in a paused state after compose was reopened.

https://chat.zulip.org/#narrow/stream/9-issues/topic/Incomplete.20Upload.20banner.20remains.20on.20closing.20compose/near/1582602
(cherry picked from commit daab1d4265)
2023-07-03 18:49:10 +00:00
Alex Vandiver
92e840efd1 puppet: Support IPv6 nameservers.
The syntax in `/etc/resolv.conf` does not include any brackets:
```
nameserver 2001:db8::a3
```

However, the format of the nginx `resolver` directive[^1] requires that
IPv6 addresses be enclosed in brackets.

Adjust the `resolver_ip` puppet function to surround any IPv6
addresses extracted from `/etc/resolv.conf` with square brackets, and
any addresses from `application_server.resolver` to gain brackets if
necessary.

Fixes: #26013.

[^1]: http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver

(cherry picked from commit 7ef05316d5)
2023-07-03 18:48:47 +00:00
Alex Vandiver
f1a8c402d1 convert_slack_data: Document "--token" more correctly.
(cherry picked from commit 38d1b3314a)
2023-07-03 18:48:08 +00:00
Alex Vandiver
8878fee6d6 slack: Provide more information when a Slack token fails to validate.
(cherry picked from commit 4c8915c8e4)
2023-07-03 18:48:08 +00:00
Alex Vandiver
783f3fac3b test_slack_importer: Switch to xoxb tokens, which is what we accept.
(cherry picked from commit 1b2ba4e09d)
2023-07-03 18:48:08 +00:00
Sahil Batra
5f397e5fa8 stream_settings: Fix height of select elements.
Previously, we used to have top and bottom paddings of 4px to
the select elements but it was removed in a208da9c4d to make
sure that text for the selected option is aligned properly.

All other select elements have height set to 30px, but the
select elements in stream settings page had height set to
"fit-content" and so they looked ugly after removing the
padding.

This commit sets the height of select elements in stream
settings to 30px.

(cherry picked from commit b119ff68c3)
2023-07-03 18:47:31 +00:00
Daniil Fadeev
8ff2684d61 compose: Fix keyboard indicator appearance in send shortcut.
(cherry picked from commit b13a85cdbf)
2023-07-03 18:46:41 +00:00
Daniil Fadeev
016f53711d compose: Fix keyboard indicator vertical alignment in send shortcut.
(cherry picked from commit 83b4fef060)
2023-07-03 18:46:41 +00:00
Alex Vandiver
e921c7dafe docs: Clarify that trust of X-Fowarded-Proto is also necessary.
Previously, `X-Forwarded-Proto` did not need to be set, and failure to
set `loadbalancer.ips` would merely result in bad IP-address
rate-limiting and incorrect access logs; after 0935d388f0, however,
failure to do either of those, if Zulip is deployed with `http_only`,
will lead to infinite redirect loops after login.  These are
accompanied by a misleading error, from Tornado, of:

    Forbidden (Origin checking failed - https://zulip.example.com does not match any trusted origins.): /json/events

This is most common with Docker deployments, where deployments use
another docker container, such as nginx or Traefik, to do SSL
termination.  See zulip/docker-zulip#403.

Update the documentation to reinforce that `loadbalancer.ips` also
controls trust of `X-Forwarded-Proto`, and that failure to set it will
cause the application to not function correctly.

(cherry picked from commit d46279c41e)
2023-07-03 18:45:54 +00:00
Alex Vandiver
9b950f9c6a send_email: Delete ScheduledEmail objects with no recipients.
9d97af6ebb addressed the one major source of inconsistent data which
would be solved by simply re-attempting the ScheduledEmail row.  Every
other instance that we have seen since then has been a corrupt or
modified database in some way, which does not self-resolve.  This
results in an endless stream of emails to the administrator, and no
forward progress.

Drop this to a warning, and make it remove the offending row.  This
ensures we make forward progress.

(cherry picked from commit 77c146b8b0)
2023-07-03 18:44:33 +00:00
Alex Vandiver
aab515feb9 version: Update version after 7.1 release. 2023-06-13 18:15:14 +00:00
Alex Vandiver
b178bb7c59 Release Zulip Server 7.1. 2023-06-13 17:59:16 +00:00
Alex Vandiver
8a783c448f i18n: Update translation data from Transifex. 2023-06-13 17:28:04 +00:00
Alya Abbott
f4f8d091a9 github: Configure templates for filing issues.
This PR creates templates for filing issues. The templates are
intentionally quite light-weight. Note that I'm specifically not using
forms for creating issues, as the UI for filling out such a form does
not include GitHub's helpful formatting buttons and preview mode.

Follow-up to #25998, pushed as a separate PR so that the original one can pass CI.

This PR creates templates for filing issues. The templates are
intentionally quite light-weight. Note that I'm specifically not using
forms for creating issues, as the UI for filling out such a form does
not include GitHub's helpful formatting buttons and preview mode.

A major goal is to guide users towards starting a CZO conversation
when that's more appropriate than filing a GitHub issue.

Note that the config makes it possible to create a blank issue, which
should be handy for:

* Issues filed by maintainers
* Issues for tracking follow-ups from merged PRs
* Probably some other situations

Because the blank issue option is easy to miss, it should probably be
documented somewhere, but I'm not sure where. We can perhaps start
with a note on CZO.

Relevant CZO threads:

https://chat.zulip.org/#narrow/stream/137-feedback/topic/issues.20link.20in.20description/near/1561110)
https://chat.zulip.org/#narrow/stream/2-general/topic/bug.20report.20management/near/1589141

Also provide external documentation links for situations where
filing an issue is not the best approach.

(cherry picked from commit 0adcc2a1df)
2023-06-13 11:48:50 -04:00
Alya Abbott
67157ec2b8 docs: Link to new guide on suggesting features and improvements.
(cherry picked from commit 03659004a9)
2023-06-13 11:48:50 -04:00
Alex Vandiver
4bf4c8a040 upgrade-postgresql: Only upgrade to a supported version.
(cherry picked from commit 875502b2e1)
2023-06-13 11:48:50 -04:00
Alex Vandiver
08a844153c docs: Document supported versions of PostgreSQL.
Fixes: #25853.
(cherry picked from commit f4b20337a7)
2023-06-13 11:48:50 -04:00
Alya Abbott
69e04c20f9 docs: Add a guide on suggesting features and improvements.
Similar to the guide on reporting bugs.

(cherry picked from commit 9ee5a5a70e)
2023-06-13 11:48:50 -04:00
Alya Abbott
ec8d341cae docs: Link to new "Reporting bugs" guide.
(cherry picked from commit 9258acce14)
2023-06-13 11:48:50 -04:00
Alya Abbott
3ee59df091 docs: Improve instructions for reporting bugs.
- Create a dedicated "Reporting bugs" page to learly document
where and how bugs should be reported.
- Drop "Reporting issues" section from the Contributing guide.
- Delete "Bug report guidelines" page.

(cherry picked from commit 052a109ba4)
2023-06-13 11:48:50 -04:00
Alex Vandiver
dd940d2eac puppet: Read resolver from /etc/resolv.conf.
04cf68b45e make nginx responsible for downloading (and caching)
files from S3.  As noted in that commit, nginx implements its own
non-blocking DNS resolver, since the base syscall is blocking, so
requires an explicit nameserver configuration.  That commit used
127.0.0.53, which is provided by systemd-resolved, as the resolver.

However, that service may not always be enabled and running, and may
in fact not even be installed (e.g. on Docker).  Switch to parsing
`/etc/resolv.conf` and using the first-provided nameserver.  In many
deployments, this will still be `127.0.0.53`, but for others it will
provide a working DNS server which is external to the host.

In the event that a server is misconfigured and has no resolvers in
`/etc/resolv.conf`, it will error out:
```console
Error: Evaluation Error: Error while evaluating a Function Call, No nameservers found in /etc/resolv.conf!  Configure one by setting application_server.nameserver in /etc/zulip/zulip.conf (file: /home/zulip/deployments/current/puppet/zulip/manifests/app_frontend_base.pp, line: 76, column: 70) on node example.zulipdev.org
```

(cherry picked from commit bd217ad31b)
2023-06-12 21:12:50 +00:00
Tim Abbott
e3f0c28528 docs: Improve troubleshooting overview intro.
(cherry picked from commit 6ca5130cd8)
2023-06-12 20:09:13 +00:00
Alya Abbott
b44ee89245 docs: Clarify instructions for getting help with self-hosting.
(cherry picked from commit 582e88544c)
2023-06-12 20:09:13 +00:00
Alex Vandiver
ee2654c4ee uploads: Allow access to the /download/ variant anonymously.
This was mistakenly left off of b799ec32b0.

(cherry picked from commit fbb831ff3b)
2023-06-12 20:05:46 +00:00
Alex Vandiver
c12f8de80b test_helpers: Switch add/remove_ratelimit to a contextmanager.
Failing to remove all of the rules which were added causes action at a
distance with other tests.  The two methods were also only used by
test code, making their existence in zerver.lib.rate_limiter clearly
misplaced.

This fixes one instance of a mis-balanced add/remove, which caused
tests to start failing if run non-parallel and one more anonymous
request was added within a rate-limit-enabled block.

(cherry picked from commit 0dbe111ab3)
2023-06-12 20:05:46 +00:00
Sahil Batra
94437ab5be user_groups: Prevent cycles when adding subgroups for a user group.
The user group depedency graph should always be a DAG.
This commit adds code to make sure we keep the graph DAG
while adding subgroups to a user group.

Fixes #25913.
2023-06-12 16:04:18 -04:00
Zixuan James Li
a9a30ad5b4 user_groups: Make system groups creation atomic.
We want to make sure that the system groups, once created, will always
have the GroupGroupMemberships fully set up.

Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2023-06-12 16:04:18 -04:00
Alex Vandiver
e9f0ef4c15 docs: Clarify access to port 25 is needed for local email delivery. 2023-06-12 16:04:18 -04:00
Alex Vandiver
2e16e27265 ui_init: Fix typo in error data. 2023-06-12 16:04:18 -04:00
Karl Stolley
f5e2a2a38f popovers: Reorganize Tippy onShow logic for stream popover.
These changes appear to correct the keyboard-navigation repro
from #25907, and it makes it possible for users without the
permission to create streams to exit the streams modal by
hitting Esc.

This reorganizes logic within the Tippy `onShow` method to
ensure that nothing is set or called for those users without
stream-creation privileges.

These changes probably require broader testing to determine
whether the fix addresses only that specific reproducer, or
the broader problems #25907 addresses with malfunctioning
j, k, Esc, and Return keys (when Ctrl + Return to send is
enabled).

Fixes a part of #25907.
2023-06-12 16:04:18 -04:00
Alex Vandiver
5a177bff3a prod_settings_template: Document REALM_HOSTS configuration.
This was in docs/production/multiple-organizations.md, but not the
settings template.
2023-06-12 16:04:18 -04:00
Alex Vandiver
fabb5ffe94 upgrade-zulip: Verify postgresql.version against where data is stored.
This prevents installing a PostgreSQL server which matches
/etc/zulip/zulip.conf but which has no data and is not used by Django.
2023-06-12 16:04:18 -04:00
Alex Vandiver
5b4a673bbd upgrade-zulip: Set postgresql.version from running version, not a guess. 2023-06-12 16:04:18 -04:00
Alex Vandiver
afeb73e12a upgrade-zulip: Simplify PostgreSQL version check.
This is much simpler now that we do not support PostgreSQL 9.x.
2023-06-12 16:04:18 -04:00
Alex Vandiver
d5a39a6564 upgrade-postgresql: Prevent upgrades if /etc/zulip/zulip.conf is wrong.
If the `postgresql.version` in `/etc/zulip/zulip.conf` is out of date
or wrong, upgrading to the actual current version would drop your
production database without prompting.  While we do document taking a
Zulip backup (which includes a database backup) before running
`upgrade-postgresql`[^1], not everyone does so, with possibly
catastrophic consequences.

Do a true end-to-end check of the version in `/etc/zulip/zulip.conf`
by asking Django to query the database for its version, checking that
against the configured value, and aborting if there is any
disagreement.

[^1]: https://zulip.readthedocs.io/en/latest/production/upgrade.html#upgrading-postgresql
2023-06-12 16:04:18 -04:00
Alex Vandiver
a4b5ee41ea upgrade-postgresql: Prevent PostgreSQL downgrades. 2023-06-12 16:04:18 -04:00
Alex Vandiver
884a8d5628 upgrade-postgresql: Check for undefined variables. 2023-06-12 16:04:18 -04:00
Mateusz Mandera
8c9e521f57 migrations: Handle duplicate fk constraint in 0443.
It turns out that for some some deployments, there exists a second,
duplicate, foreign key constraint for user_profile_id. The logic below
would try to rename both to the same name, which would fail on the
second:

```
psycopg2.errors.DuplicateObject: constraint "zerver_userpresenceo_user_profile_id_d75366d6_fk_zerver_us" for relation "zerver_userpresence" already exists
```

Eliminate the duplicate constraint, rather than attempting to rename
it.  Also add a block, in case of future reuse of this pattern, which
caveats that this approach will not work in the presence of
explicitly-named indexes.  UserPresence happens to not have any, so
this technique is safe in this instance.

Co-authored-by: Alex Vandiver <alexmv@zulip.com>
2023-06-12 16:04:18 -04:00
Brijmohan Siyag
2f04875ad3 send_later: Wildcard mention throwing error on send later.
It was throwing error while schudiling a message having wildcard mention,
because the function `open_send_later_menu` was using param instance to track down
interval, but the parametere instance was not passed from when it was
called from warning banner action. This commit removes the instance
param as it is of no use, and uses a variable to track interval.
2023-06-12 16:04:18 -04:00
Anders Kaseorg
9dcf1944ad install: Check CPU and OS architecture.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-06-12 16:04:18 -04:00
Anders Kaseorg
6b895d1622 install: Add system_requirements_failure helper.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-06-12 16:04:18 -04:00
Mateusz Mandera
1a65e8a538 migrations: Fix bug in migration 0439.
This code clearly meant to return host and returning realm.host is a
mistake. realm.host is not accessible in a migration due to being a
@property-decorated method. The code constructs the host var value just
above this line.

(cherry picked from commit a55901aa67)
2023-06-05 16:33:22 +00:00
Tim Abbott
3240a513f1 version: Update version after 7.0 release. 2023-05-31 09:20:26 -07:00
2833 changed files with 161529 additions and 220393 deletions

View File

@@ -50,8 +50,6 @@
"import/extensions": "error",
"import/first": "error",
"import/newline-after-import": "error",
"import/no-cycle": ["error", {"ignoreExternal": true}],
"import/no-duplicates": "error",
"import/no-self-import": "error",
"import/no-unresolved": "off",
"import/no-useless-path-segments": "error",
@@ -66,6 +64,7 @@
"no-catch-shadow": "error",
"no-constant-condition": ["error", {"checkLoops": false}],
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-else-return": "error",
"no-eq-null": "error",
"no-eval": "error",
@@ -94,10 +93,7 @@
"no-undef-init": "error",
"no-unneeded-ternary": ["error", {"defaultAssignment": false}],
"no-unused-expressions": "error",
"no-unused-vars": [
"error",
{"args": "all", "argsIgnorePattern": "^_", "ignoreRestSiblings": true}
],
"no-unused-vars": ["error", {"ignoreRestSiblings": true}],
"no-use-before-define": ["error", {"functions": false}],
"no-useless-concat": "error",
"no-useless-constructor": "error",
@@ -170,12 +166,9 @@
},
"rules": {
// Disable base rule to avoid conflict
"no-duplicate-imports": "off",
"no-use-before-define": "off",
"@typescript-eslint/consistent-type-assertions": [
"error",
{"assertionStyle": "never"}
],
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/explicit-function-return-type": [
@@ -183,7 +176,9 @@
{"allowExpressions": true}
],
"@typescript-eslint/member-ordering": "error",
"@typescript-eslint/no-duplicate-imports": "error",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-parameter-properties": "error",
"@typescript-eslint/no-unnecessary-condition": "off",
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unsafe-argument": "off",
@@ -191,13 +186,10 @@
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{"args": "all", "argsIgnorePattern": "^_", "ignoreRestSiblings": true}
],
"@typescript-eslint/no-unused-vars": ["error", {"ignoreRestSiblings": true}],
"@typescript-eslint/no-use-before-define": ["error", {"functions": false}],
"@typescript-eslint/parameter-properties": "error",
"@typescript-eslint/promise-function-async": "error",
"import/no-cycle": "error",
"no-undef": "error"
}
},

View File

@@ -75,7 +75,7 @@ jobs:
- name: Restore pnpm store
uses: actions/cache@v3
with:
path: /__w/.pnpm-store
path: ~/.local/share/pnpm/store
key: v1-pnpm-store-focal-${{ hashFiles('pnpm-lock.yaml') }}
- name: Restore python cache
@@ -102,12 +102,6 @@ jobs:
path: /tmp/production-build
retention-days: 1
- name: Verify pnpm store path
run: |
set -x
path="$(pnpm store path)"
[[ "$path" == /__w/.pnpm-store/* ]]
- name: Generate failure report string
id: failure_report_string
if: ${{ failure() && github.repository == 'zulip/zulip' && github.event_name == 'push' }}
@@ -190,6 +184,12 @@ jobs:
sudo mkdir -p "${dirs[@]}"
sudo chown -R github "${dirs[@]}"
- name: Restore pnpm store
uses: actions/cache@v3
with:
path: ~/.local/share/pnpm/store
key: v1-pnpm-store-${{ matrix.os }}-${{ hashFiles('/tmp/pnpm-lock.yaml') }}
- name: Install production
run: sudo /tmp/production-install ${{ matrix.extra-args }}
@@ -288,17 +288,6 @@ jobs:
sudo mkdir -p "${dirs[@]}"
sudo chown -R github "${dirs[@]}"
- name: Temporarily bootstrap PostgreSQL upgrades
# https://chat.zulip.org/#narrow/stream/43-automated-testing/topic/postgres.20client.20upgrade.20failures/near/1640444
# On Debian, there is an ordering issue with post-install maintainer
# scripts when postgresql-client-common is upgraded at the same time as
# postgresql-client and postgresql-client-15. Upgrade just
# postgresql-client-common first, so the main upgrade process can
# succeed. This is a _temporary_ work-around to improve CI signal, as
# the failure does represent a real failure that production systems may
# encounter.
run: sudo apt-get update && sudo apt-get install -y --only-upgrade postgresql-client-common
- name: Upgrade production
run: sudo /tmp/production-upgrade

View File

@@ -79,7 +79,7 @@ jobs:
- name: Restore pnpm store
uses: actions/cache@v3
with:
path: /__w/.pnpm-store
path: ~/.local/share/pnpm/store
key: v1-pnpm-store-${{ matrix.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: Restore python cache
@@ -100,7 +100,10 @@ jobs:
run: |
# This is the main setup job for the test suite
./tools/ci/setup-backend --skip-dev-db-build
scripts/lib/clean_unused_caches.py --verbose --threshold=0
# Cleaning caches is mostly unnecessary in GitHub Actions, because
# most builds don't get to write to the cache.
# scripts/lib/clean_unused_caches.py --verbose --threshold 0
- name: Run tools test
run: |
@@ -112,26 +115,11 @@ jobs:
source tools/ci/activate-venv
./tools/run-codespell
# We run the tests that are only run in a specific job early, so
# that we get feedback to the developer about likely failures as
# quickly as possible. Backend/mypy failures that aren't
# identical across different versions are much more rare than
# frontend linter or node test failures.
- name: Run documentation and api tests
if: ${{ matrix.include_documentation_tests }}
- name: Run backend lint
run: |
source tools/ci/activate-venv
# In CI, we only test links we control in test-documentation to avoid flakes
./tools/test-documentation --skip-external-links
./tools/test-help-documentation --skip-external-links
./tools/test-api
- name: Run node tests
if: ${{ matrix.include_frontend_tests }}
run: |
source tools/ci/activate-venv
# Run the node tests first, since they're fast and deterministic
./tools/test-js-with-node --coverage --parallel=1
echo "Test suite is running under $(python --version)."
./tools/lint --groups=backend --skip=gitlint,mypy # gitlint disabled because flaky
- name: Run frontend lint
if: ${{ matrix.include_frontend_tests }}
@@ -139,41 +127,10 @@ jobs:
source tools/ci/activate-venv
./tools/lint --groups=frontend --skip=gitlint # gitlint disabled because flaky
- name: Check schemas
if: ${{ matrix.include_frontend_tests }}
run: |
source tools/ci/activate-venv
# Check that various schemas are consistent. (is fast)
./tools/check-schemas
- name: Check capitalization of strings
if: ${{ matrix.include_frontend_tests }}
run: |
source tools/ci/activate-venv
./manage.py makemessages --locale en
PYTHONWARNINGS=ignore ./tools/check-capitalization --no-generate
PYTHONWARNINGS=ignore ./tools/check-frontend-i18n --no-generate
- name: Run puppeteer tests
if: ${{ matrix.include_frontend_tests }}
run: |
source tools/ci/activate-venv
./tools/test-js-with-puppeteer
- name: Check pnpm dedupe
if: ${{ matrix.include_frontend_tests }}
run: pnpm dedupe --check
- name: Run backend lint
run: |
source tools/ci/activate-venv
echo "Test suite is running under $(python --version)."
./tools/lint --groups=backend --skip=gitlint,mypy # gitlint disabled because flaky
- name: Run backend tests
run: |
source tools/ci/activate-venv
./tools/test-backend --coverage --xml-report --no-html-report --include-webhooks --include-transaction-tests --no-cov-cleanup --ban-console-output
./tools/test-backend --coverage --xml-report --no-html-report --include-webhooks --no-cov-cleanup --ban-console-output
- name: Run mypy
run: |
@@ -209,6 +166,47 @@ jobs:
./scripts/lib/check-database-compatibility
chmod 755 static/generated web/generated
- name: Run documentation and api tests
if: ${{ matrix.include_documentation_tests }}
run: |
source tools/ci/activate-venv
# In CI, we only test links we control in test-documentation to avoid flakes
./tools/test-documentation --skip-external-links
./tools/test-help-documentation --skip-external-links
./tools/test-api
- name: Run node tests
if: ${{ matrix.include_frontend_tests }}
run: |
source tools/ci/activate-venv
# Run the node tests first, since they're fast and deterministic
./tools/test-js-with-node --coverage --parallel=1
- name: Check schemas
if: ${{ matrix.include_frontend_tests }}
run: |
source tools/ci/activate-venv
# Check that various schemas are consistent. (is fast)
./tools/check-schemas
- name: Check capitalization of strings
if: ${{ matrix.include_frontend_tests }}
run: |
source tools/ci/activate-venv
./manage.py makemessages --locale en
PYTHONWARNINGS=ignore ./tools/check-capitalization --no-generate
PYTHONWARNINGS=ignore ./tools/check-frontend-i18n --no-generate
- name: Run puppeteer tests
if: ${{ matrix.include_frontend_tests }}
run: |
source tools/ci/activate-venv
./tools/test-js-with-puppeteer
- name: Check pnpm dedupe
if: ${{ matrix.include_frontend_tests }}
run: pnpm dedupe --check
- name: Check for untracked files
run: |
source tools/ci/activate-venv
@@ -247,12 +245,6 @@ jobs:
- name: Check development database build
run: ./tools/ci/setup-backend
- name: Verify pnpm store path
run: |
set -x
path="$(pnpm store path)"
[[ "$path" == /__w/.pnpm-store/* ]]
- name: Generate failure report string
id: failure_report_string
if: ${{ failure() && github.repository == 'zulip/zulip' && github.event_name == 'push' }}

5
.gitignore vendored
View File

@@ -17,8 +17,6 @@
# 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
/tools/conf.ini
/tools/custom_provision
@@ -85,9 +83,6 @@ zulip.kdev4
# Core dump files
core
# Static generated files for landing page.
/static/images/landing-page/hello/generated
## Miscellaneous
# (Ideally this section is empty.)
.transifexrc

View File

@@ -12,32 +12,31 @@
# # shows raw names/emails, filtered by mapped name:
# $ git log --format='%an %ae' --author=$NAME | uniq -c
acrefoot <acrefoot@zulip.com> <acrefoot@alum.mit.edu>
acrefoot <acrefoot@zulip.com> <acrefoot@dropbox.com>
acrefoot <acrefoot@zulip.com> <acrefoot@humbughq.com>
Adam Benesh <Adam.Benesh@gmail.com>
acrefoot <acrefoot@zulip.com> <acrefoot@dropbox.com>
acrefoot <acrefoot@zulip.com> <acrefoot@alum.mit.edu>
Adam Benesh <Adam.Benesh@gmail.com> <Adam-Daniel.Benesh@t-systems.com>
Adam Benesh <Adam.Benesh@gmail.com>
Adarsh Tiwari <xoldyckk@gmail.com>
Alex Vandiver <alexmv@zulip.com> <alex@chmrr.net>
Alex Vandiver <alexmv@zulip.com> <github@chmrr.net>
Allen Rabinovich <allenrabinovich@yahoo.com> <allenr@humbughq.com>
Allen Rabinovich <allenrabinovich@yahoo.com> <allenr@zulip.com>
Alya Abbott <alya@zulip.com> <2090066+alya@users.noreply.github.com>
Alya Abbott <alya@zulip.com> <alyaabbott@elance-odesk.com>
Aman Agrawal <amanagr@zulip.com>
Aman Agrawal <amanagr@zulip.com> <f2016561@pilani.bits-pilani.ac.in>
Aman Agrawal <amanagr@zulip.com>
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 Shridhar <aryanshridhar7@gmail.com>
Aryan Shridhar <aryanshridhar7@gmail.com> <53977614+aryanshridhar@users.noreply.github.com>
Aryan Shridhar <aryanshridhar7@gmail.com>
aparna-bhatt <aparnabhatt2001@gmail.com> <86338542+aparna-bhatt@users.noreply.github.com>
Ashwat Kumar Singh <ashwat.kumarsingh.met20@itbhu.ac.in>
Austin Riba <austin@zulip.com> <austin@m51.io>
BIKI DAS <bikid475@gmail.com>
Brijmohan Siyag <brijsiyag@gmail.com>
Brock Whittaker <brock@zulipchat.com> <bjwhitta@asu.edu>
Brock Whittaker <brock@zulipchat.com> <brock@zulipchat.org>
Brock Whittaker <brock@zulipchat.com> <brockwhittaker@Brocks-MacBook.local>
Brock Whittaker <brock@zulipchat.com> <brock@zulipchat.org>
Chris Bobbe <cbobbe@zulip.com> <cbobbe@zulipchat.com>
Chris Bobbe <cbobbe@zulip.com> <csbobbe@gmail.com>
Danny Su <contact@dannysu.com> <opensource@emailengine.org>
@@ -45,9 +44,8 @@ Dinesh <chdinesh1089@gmail.com>
Dinesh <chdinesh1089@gmail.com> <chdinesh1089>
Eeshan Garg <eeshan@zulip.com> <jerryguitarist@gmail.com>
Eric Smith <erwsmith@gmail.com> <99841919+erwsmith@users.noreply.github.com>
Evy Kassirer <evy@zulip.com>
Evy Kassirer <evy@zulip.com> <evy.kassirer@gmail.com>
Evy Kassirer <evy@zulip.com> <evykassirer@users.noreply.github.com>
Evy Kassirer <evy.kassirer@gmail.com>
Evy Kassirer <evy.kassirer@gmail.com> <evykassirer@users.noreply.github.com>
Ganesh Pawar <pawarg256@gmail.com> <58626718+ganpa3@users.noreply.github.com>
Greg Price <greg@zulip.com> <gnprice@gmail.com>
Greg Price <greg@zulip.com> <greg@zulipchat.com>
@@ -60,21 +58,19 @@ Jeff Arnold <jbarnold@gmail.com> <jbarnold@humbughq.com>
Jeff Arnold <jbarnold@gmail.com> <jbarnold@zulip.com>
Jessica McKellar <jesstess@mit.edu> <jesstess@humbughq.com>
Jessica McKellar <jesstess@mit.edu> <jesstess@zulip.com>
Joseph Ho <josephho678@gmail.com>
Joseph Ho <josephho678@gmail.com> <62449508+Joelute@users.noreply.github.com>
Julia Bichler <julia.bichler@tum.de> <74348920+juliaBichler01@users.noreply.github.com>
Karl Stolley <karl@zulip.com> <karl@stolley.dev>
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>
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>
m-e-l-u-h-a-n <purushottam.tiwari.cd.cse19@itbhu.ac.in> <pururshottam.tiwari.cd.cse19@itbhu.ac.in>
Lauryn Menard <lauryn@zulip.com> <63245456+laurynmm@users.noreply.github.com>
Mateusz Mandera <mateusz.mandera@zulip.com> <mateusz.mandera@protonmail.com>
Matt Keller <matt@zulip.com>
Matt Keller <matt@zulip.com> <m@cognusion.com>
m-e-l-u-h-a-n <purushottam.tiwari.cd.cse19@itbhu.ac.in>
m-e-l-u-h-a-n <purushottam.tiwari.cd.cse19@itbhu.ac.in> <pururshottam.tiwari.cd.cse19@itbhu.ac.in>
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>
@@ -85,10 +81,10 @@ Priyam Seth <sethpriyam1@gmail.com> <b19188@students.iitmandi.ac.in>
Ray Kraesig <rkraesig@zulip.com> <rkraesig@zulipchat.com>
Reid Barton <rwbarton@gmail.com> <rwbarton@humbughq.com>
Rein Zustand (rht) <rhtbot@protonmail.com>
Rishabh Maheshwari <b20063@students.iitmandi.ac.in>
Rishi Gupta <rishig@zulipchat.com> <rishig+git@mit.edu>
Rishi Gupta <rishig@zulipchat.com> <rishig@kandralabs.com>
Rishi Gupta <rishig@zulipchat.com> <rishig@users.noreply.github.com>
Rishabh Maheshwari <b20063@students.iitmandi.ac.in>
Rixant Rokaha <rixantrokaha@gmail.com>
Rixant Rokaha <rixantrokaha@gmail.com> <rishantrokaha@gmail.com>
Rixant Rokaha <rixantrokaha@gmail.com> <rrokaha@caldwell.edu>
@@ -112,21 +108,24 @@ Tim Abbott <tabbott@zulip.com> <tabbott@humbughq.com>
Tim Abbott <tabbott@zulip.com> <tabbott@mit.edu>
Tim Abbott <tabbott@zulip.com> <tabbott@zulipchat.com>
Ujjawal Modi <umodi2003@gmail.com> <99073049+Ujjawal3@users.noreply.github.com>
umkay <ukhan@zulipchat.com> <umaimah.k@gmail.com>
umkay <ukhan@zulipchat.com> <umkay@users.noreply.github.com>
Vishnu KS <vishnu@zulip.com> <hackerkid@vishnuks.com>
Vishnu KS <vishnu@zulip.com> <yo@vishnuks.com>
Waseem Daher <wdaher@zulip.com> <wdaher@dropbox.com>
Alya Abbott <alya@zulip.com> <alyaabbott@elance-odesk.com>
umkay <ukhan@zulipchat.com> <umaimah.k@gmail.com>
umkay <ukhan@zulipchat.com> <umkay@users.noreply.github.com>
Waseem Daher <wdaher@zulip.com> <wdaher@humbughq.com>
Yash RE <33805964+YashRE42@users.noreply.github.com>
Waseem Daher <wdaher@zulip.com> <wdaher@dropbox.com>
Yash RE <33805964+YashRE42@users.noreply.github.com> <YashRE42@github.com>
Yash RE <33805964+YashRE42@users.noreply.github.com>
Yogesh Sirsat <yogeshsirsat56@gmail.com>
Yogesh Sirsat <yogeshsirsat56@gmail.com> <41695888+yogesh-sirsat@users.noreply.github.com>
Zeeshan Equbal <equbalzeeshan@gmail.com>
Zeeshan Equbal <equbalzeeshan@gmail.com> <54993043+zee-bit@users.noreply.github.com>
Zeeshan Equbal <equbalzeeshan@gmail.com>
Zev Benjamin <zev@zulip.com> <zev@dropbox.com>
Zev Benjamin <zev@zulip.com> <zev@humbughq.com>
Zev Benjamin <zev@zulip.com> <zev@mit.edu>
Zixuan James Li <p359101898@gmail.com>
Zixuan James Li <p359101898@gmail.com> <359101898@qq.com>
Zixuan James Li <p359101898@gmail.com> <39874143+PIG208@users.noreply.github.com>
Zixuan James Li <p359101898@gmail.com> <359101898@qq.com>
Joseph Ho <josephho678@gmail.com>
Joseph Ho <josephho678@gmail.com> <62449508+Joelute@users.noreply.github.com>

View File

@@ -136,8 +136,7 @@ Here are some guidelines for you how can help:
Ive gone ahead and moved the other copy of this message to this thread.
- If asked a question in a direct message that is better discussed in a public
stream:
- If asked a question in a PM that is better discussed in a public stream:
> Hi @user! Please start by reviewing
> https://zulip.com/development-community/#community-norms to learn how to
> get help in this community.
@@ -167,7 +166,7 @@ Here are some guidelines for you how can help:
- Try to assume the best intentions from others (given the range of
possibilities presented by their visible behavior), and stick with a friendly
and positive tone even when someones behavior is poor or disrespectful.
and positive tone even when someones behavior is poor or disrespectful.
Everyone has bad days and stressful situations that can result in them
behaving not their best, and while we should be firm about our community
rules, we should also enforce them with kindness.

View File

@@ -154,10 +154,6 @@ repository](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3
- We especially recommend browsing recently opened issues, as there are more
likely to be easy ones for you to find.
- Take a look at issues with the ["good first issue"
label](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22),
as they are especially accessible to new contributors. However, you will
likely find issues without this label that are accessible as well.
- All issues are partitioned into areas like
admin, compose, emoji, hotkeys, i18n, onboarding, search, etc. Look
through our [list of labels](https://github.com/zulip/zulip/labels), and
@@ -249,12 +245,6 @@ labels.
use the existing pull request (PR) as a starting point for your contribution. If
you think a different approach is needed, you can post a new PR, with a comment that clearly
explains _why_ you decided to start from scratch.
- **What if I ask if someone is still working on an issue, and they don't
respond?** If you don't get a reply within 2-3 days, go ahead and post a comment
that you are working on the issue, and submit a pull request. If the original
assignee ends up submitting a pull request first, no worries! You can help by
providing feedback on their work, or submit your own PR if you think a
different approach is needed (as described above).
- **Can I come up with my own feature idea and work on it?** We welcome
suggestions of features or other improvements that you feel would be valuable. If you
have a new feature you'd like to add, you can start a conversation [in our

View File

@@ -8,7 +8,7 @@
# -f ./Dockerfile-postgresql -t zulip/zulip-postgresql:14 --push .
# Currently the PostgreSQL images do not support automatic upgrading of
# the on-disk data in volumes. So the base image cannot currently be upgraded
# the on-disk data in volumes. So the base image can not currently be upgraded
# without users needing a manual pgdump and restore.
# https://hub.docker.com/r/groonga/pgroonga/tags

View File

@@ -17,7 +17,7 @@ Come find us on the [development community chat](https://zulip.com/development-c
[![GitHub Actions build status](https://github.com/zulip/zulip/actions/workflows/zulip-ci.yml/badge.svg)](https://github.com/zulip/zulip/actions/workflows/zulip-ci.yml?query=branch%3Amain)
[![coverage status](https://img.shields.io/codecov/c/github/zulip/zulip/main.svg)](https://codecov.io/gh/zulip/zulip)
[![Mypy coverage](https://img.shields.io/badge/mypy-100%25-green.svg)][mypy-coverage]
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json)](https://github.com/charliermarsh/ruff)
[![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
[![GitHub release](https://img.shields.io/github/release/zulip/zulip.svg)](https://github.com/zulip/zulip/releases/latest)

View File

@@ -33,5 +33,5 @@ See also our documentation on the [Zulip release
lifecycle][release-lifecycle].
[security-model]: https://zulip.readthedocs.io/en/latest/production/security-model.html
[upgrades]: https://zulip.readthedocs.io/en/stable/production/upgrade.html#upgrading-to-a-release
[upgrades]: https://zulip.readthedocs.io/en/latest/production/upgrade-or-modify.html#upgrading-to-a-release
[release-lifecycle]: https://zulip.readthedocs.io/en/latest/overview/release-lifecycle.html

View File

@@ -8,7 +8,6 @@ from django.conf import settings
from django.db import connection, models
from django.db.models import F
from psycopg2.sql import SQL, Composable, Identifier, Literal
from typing_extensions import TypeAlias, override
from analytics.models import (
BaseCount,
@@ -23,14 +22,6 @@ 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
if settings.ZILENCER_ENABLED:
from zilencer.models import (
RemoteInstallationCount,
RemoteRealm,
RemoteRealmCount,
RemoteZulipServer,
)
## Logging setup ##
logger = logging.getLogger("zulip.management")
@@ -71,7 +62,6 @@ class CountStat:
else:
self.interval = self.time_increment
@override
def __repr__(self) -> str:
return f"<CountStat: {self.property}>"
@@ -301,7 +291,7 @@ def do_aggregate_to_summary_table(
# called from zerver.actions; should not throw any errors
def do_increment_logging_stat(
model_object_for_bucket: Union[Realm, UserProfile, Stream, "RemoteRealm", "RemoteZulipServer"],
zerver_object: Union[Realm, UserProfile, Stream],
stat: CountStat,
subgroup: Optional[Union[str, int, bool]],
event_time: datetime,
@@ -312,37 +302,21 @@ def do_increment_logging_stat(
table = stat.data_collector.output_table
if table == RealmCount:
assert isinstance(model_object_for_bucket, Realm)
id_args: Dict[
str, Optional[Union[Realm, UserProfile, Stream, "RemoteRealm", "RemoteZulipServer"]]
] = {"realm": model_object_for_bucket}
assert isinstance(zerver_object, Realm)
id_args: Dict[str, Union[Realm, UserProfile, Stream]] = {"realm": zerver_object}
elif table == UserCount:
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")
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}
if stat.frequency == CountStat.DAY:
end_time = ceiling_to_day(event_time)
elif stat.frequency == CountStat.HOUR:
else: # CountStat.HOUR:
end_time = ceiling_to_hour(event_time)
else:
raise AssertionError("Unsupported CountStat frequency")
row, created = table._default_manager.get_or_create(
row, created = table.objects.get_or_create(
property=stat.property,
subgroup=subgroup,
end_time=end_time,
@@ -372,7 +346,7 @@ def do_drop_single_stat(property: str) -> None:
## DataCollector-level operations ##
QueryFn: TypeAlias = Callable[[Dict[str, Composable]], Composable]
QueryFn = Callable[[Dict[str, Composable]], Composable]
def do_pull_by_sql_query(
@@ -472,13 +446,7 @@ def count_message_by_user_query(realm: Optional[Realm]) -> QueryFn:
if realm is None:
realm_clause: Composable = SQL("")
else:
# We limit both userprofile and message so that we only see
# users from this realm, but also get the performance speedup
# of limiting messages by realm.
realm_clause = SQL(
"zerver_userprofile.realm_id = {} AND zerver_message.realm_id = {} AND"
).format(Literal(realm.id), Literal(realm.id))
# Uses index: zerver_message_realm_date_sent (or the only-date index)
realm_clause = SQL("zerver_userprofile.realm_id = {} AND").format(Literal(realm.id))
return lambda kwargs: SQL(
"""
INSERT INTO analytics_usercount
@@ -505,13 +473,7 @@ def count_message_type_by_user_query(realm: Optional[Realm]) -> QueryFn:
if realm is None:
realm_clause: Composable = SQL("")
else:
# We limit both userprofile and message so that we only see
# users from this realm, but also get the performance speedup
# of limiting messages by realm.
realm_clause = SQL(
"zerver_userprofile.realm_id = {} AND zerver_message.realm_id = {} AND"
).format(Literal(realm.id), Literal(realm.id))
# Uses index: zerver_message_realm_date_sent (or the only-date index)
realm_clause = SQL("zerver_userprofile.realm_id = {} AND").format(Literal(realm.id))
return lambda kwargs: SQL(
"""
INSERT INTO analytics_usercount
@@ -560,10 +522,7 @@ def count_message_by_stream_query(realm: Optional[Realm]) -> QueryFn:
if realm is None:
realm_clause: Composable = SQL("")
else:
realm_clause = SQL(
"zerver_stream.realm_id = {} AND zerver_message.realm_id = {} AND"
).format(Literal(realm.id), Literal(realm.id))
# Uses index: zerver_message_realm_date_sent (or the only-date index)
realm_clause = SQL("zerver_stream.realm_id = {} AND").format(Literal(realm.id))
return lambda kwargs: SQL(
"""
INSERT INTO analytics_streamcount
@@ -841,12 +800,6 @@ 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),
@@ -861,65 +814,8 @@ 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

@@ -1,5 +1,5 @@
from math import sqrt
from random import Random
from random import gauss, random, seed
from typing import List
from analytics.lib.counts import CountStat
@@ -36,8 +36,6 @@ def generate_time_series_data(
partial_sum -- If True, return partial sum of the series.
random_seed -- Seed for random number generator.
"""
rng = Random(random_seed)
if frequency == CountStat.HOUR:
length = days * 24
seasonality = [non_business_hours_base] * 24 * 7
@@ -46,13 +44,13 @@ def generate_time_series_data(
seasonality[24 * day + hour] = business_hours_base
holidays = []
for i in range(days):
holidays.extend([rng.random() < holiday_rate] * 24)
holidays.extend([random() < holiday_rate] * 24)
elif frequency == CountStat.DAY:
length = days
seasonality = [8 * business_hours_base + 16 * non_business_hours_base] * 5 + [
24 * non_business_hours_base
] * 2
holidays = [rng.random() < holiday_rate for i in range(days)]
holidays = [random() < holiday_rate for i in range(days)]
else:
raise AssertionError(f"Unknown frequency: {frequency}")
if length < 2:
@@ -64,10 +62,11 @@ def generate_time_series_data(
seasonality[i % len(seasonality)] * (growth_base**i) for i in range(length)
]
noise_scalars = [rng.gauss(0, 1)]
seed(random_seed)
noise_scalars = [gauss(0, 1)]
for i in range(1, length):
noise_scalars.append(
noise_scalars[-1] * autocorrelation + rng.gauss(0, 1) * (1 - autocorrelation)
noise_scalars[-1] * autocorrelation + gauss(0, 1) * (1 - autocorrelation)
)
values = [

View File

@@ -30,5 +30,4 @@ def time_range(
while current >= start:
times.append(current)
current -= step
times.reverse()
return times
return list(reversed(times))

View File

@@ -5,9 +5,8 @@ from typing import Any, Dict
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 ALL_COUNT_STATS, CountStat
from analytics.lib.counts import 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
@@ -25,7 +24,6 @@ class Command(BaseCommand):
Run as a cron job that runs every hour."""
@override
def handle(self, *args: Any, **options: Any) -> None:
fill_state = self.get_fill_state()
status = fill_state["status"]
@@ -44,7 +42,7 @@ class Command(BaseCommand):
warning_unfilled_properties = []
critical_unfilled_properties = []
for property, stat in ALL_COUNT_STATS.items():
for property, stat in COUNT_STATS.items():
last_fill = stat.last_successful_fill()
if last_fill is None:
last_fill = installation_epoch()

View File

@@ -2,7 +2,6 @@ from argparse import ArgumentParser
from typing import Any
from django.core.management.base import BaseCommand, CommandError
from typing_extensions import override
from analytics.lib.counts import do_drop_all_analytics_tables
@@ -10,11 +9,9 @@ from analytics.lib.counts import do_drop_all_analytics_tables
class Command(BaseCommand):
help = """Clear analytics tables."""
@override
def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument("--force", action="store_true", help="Clear analytics tables.")
@override
def handle(self, *args: Any, **options: Any) -> None:
if options["force"]:
do_drop_all_analytics_tables()

View File

@@ -2,23 +2,20 @@ from argparse import ArgumentParser
from typing import Any
from django.core.management.base import BaseCommand, CommandError
from typing_extensions import override
from analytics.lib.counts import ALL_COUNT_STATS, do_drop_single_stat
from analytics.lib.counts import COUNT_STATS, do_drop_single_stat
class Command(BaseCommand):
help = """Clear analytics tables."""
@override
def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument("--force", action="store_true", help="Actually do it.")
parser.add_argument("--property", help="The property of the stat to be cleared.")
@override
def handle(self, *args: Any, **options: Any) -> None:
property = options["property"]
if property not in ALL_COUNT_STATS:
if property not in COUNT_STATS:
raise CommandError(f"Invalid property: {property}")
if not options["force"]:
raise CommandError("No action taken. Use --force.")

View File

@@ -5,7 +5,6 @@ from typing import Any, Dict, List, Mapping, Type, Union
from django.core.files.uploadedfile import UploadedFile
from django.core.management.base import BaseCommand
from django.utils.timezone import now as timezone_now
from typing_extensions import TypeAlias, override
from analytics.lib.counts import COUNT_STATS, CountStat, do_drop_all_analytics_tables
from analytics.lib.fixtures import generate_time_series_data
@@ -32,7 +31,6 @@ from zerver.models import (
Recipient,
Stream,
Subscription,
SystemGroups,
UserGroup,
UserProfile,
)
@@ -69,7 +67,6 @@ class Command(BaseCommand):
random_seed=self.random_seed,
)
@override
def handle(self, *args: Any, **options: Any) -> None:
# TODO: This should arguably only delete the objects
# associated with the "analytics" realm.
@@ -115,7 +112,7 @@ class Command(BaseCommand):
)
administrators_user_group = UserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
name=UserGroup.ADMINISTRATORS_GROUP_NAME, realm=realm, is_system_group=True
)
stream = Stream.objects.create(
name="all",
@@ -150,7 +147,7 @@ class Command(BaseCommand):
with open(IMAGE_FILE_PATH, "rb") as fp:
upload_message_attachment_from_request(UploadedFile(fp), shylock, file_size)
FixtureData: TypeAlias = Mapping[Union[str, int, None], List[int]]
FixtureData = Mapping[Union[str, int, None], List[int]]
def insert_fixture_data(
stat: CountStat,
@@ -158,7 +155,7 @@ class Command(BaseCommand):
table: Type[BaseCount],
) -> None:
end_times = time_range(
last_end_time, last_end_time, stat.frequency, len(next(iter(fixture_data.values())))
last_end_time, last_end_time, stat.frequency, len(list(fixture_data.values())[0])
)
if table == InstallationCount:
id_args: Dict[str, Any] = {}
@@ -170,7 +167,7 @@ class Command(BaseCommand):
id_args = {"stream": stream, "realm": realm}
for subgroup, values in fixture_data.items():
table._default_manager.bulk_create(
table.objects.bulk_create(
table(
property=stat.property,
subgroup=subgroup,

View File

@@ -8,11 +8,10 @@ from django.conf import settings
from django.core.management.base import BaseCommand
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 ALL_COUNT_STATS, logger, process_count_stat
from analytics.lib.counts import 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_analytics_to_remote_server
from zerver.lib.timestamp import floor_to_hour
from zerver.models import Realm
@@ -22,7 +21,6 @@ class Command(BaseCommand):
Run as a cron job that runs every hour."""
@override
def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument(
"--time",
@@ -39,7 +37,6 @@ class Command(BaseCommand):
"--verbose", action="store_true", help="Print timing information to stdout."
)
@override
def handle(self, *args: Any, **options: Any) -> None:
try:
os.mkdir(settings.ANALYTICS_LOCK_DIR)
@@ -74,9 +71,9 @@ class Command(BaseCommand):
fill_to_time = floor_to_hour(fill_to_time.astimezone(timezone.utc))
if options["stat"] is not None:
stats = [ALL_COUNT_STATS[options["stat"]]]
stats = [COUNT_STATS[options["stat"]]]
else:
stats = list(ALL_COUNT_STATS.values())
stats = list(COUNT_STATS.values())
logger.info("Starting updating analytics counts through %s", fill_to_time)
if options["verbose"]:
@@ -96,4 +93,4 @@ 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()
send_analytics_to_remote_server()

View File

@@ -1,5 +1,5 @@
# Generated by Django 1.10.5 on 2017-02-01 22:28
from django.db import migrations, models
from django.db import migrations
class Migration(migrations.Migration):
@@ -9,25 +9,16 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AddIndex(
model_name="realmcount",
index=models.Index(
fields=["property", "end_time"],
name="analytics_realmcount_property_end_time_3b60396b_idx",
),
migrations.AlterIndexTogether(
name="realmcount",
index_together={("property", "end_time")},
),
migrations.AddIndex(
model_name="streamcount",
index=models.Index(
fields=["property", "realm", "end_time"],
name="analytics_streamcount_property_realm_id_end_time_155ae930_idx",
),
migrations.AlterIndexTogether(
name="streamcount",
index_together={("property", "realm", "end_time")},
),
migrations.AddIndex(
model_name="usercount",
index=models.Index(
fields=["property", "realm", "end_time"],
name="analytics_usercount_property_realm_id_end_time_591dbec1_idx",
),
migrations.AlterIndexTogether(
name="usercount",
index_together={("property", "realm", "end_time")},
),
]

View File

@@ -1,114 +0,0 @@
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,11 +1,7 @@
# https://github.com/typeddjango/django-stubs/issues/1698
# mypy: disable-error-code="explicit-override"
import datetime
from django.db import models
from django.db.models import Q, UniqueConstraint
from typing_extensions import override
from zerver.lib.timestamp import floor_to_day
from zerver.models import Realm, Stream, UserProfile
@@ -20,7 +16,6 @@ class FillState(models.Model):
STARTED = 2
state = models.PositiveSmallIntegerField()
@override
def __str__(self) -> str:
return f"{self.property} {self.end_time} {self.state}"
@@ -63,7 +58,6 @@ class InstallationCount(BaseCount):
),
]
@override
def __str__(self) -> str:
return f"{self.property} {self.subgroup} {self.value}"
@@ -85,14 +79,8 @@ class RealmCount(BaseCount):
name="unique_realm_count_null_subgroup",
),
]
indexes = [
models.Index(
fields=["property", "end_time"],
name="analytics_realmcount_property_end_time_3b60396b_idx",
)
]
index_together = ["property", "end_time"]
@override
def __str__(self) -> str:
return f"{self.realm!r} {self.property} {self.subgroup} {self.value}"
@@ -117,14 +105,8 @@ class UserCount(BaseCount):
]
# This index dramatically improves the performance of
# aggregating from users to realms
indexes = [
models.Index(
fields=["property", "realm", "end_time"],
name="analytics_usercount_property_realm_id_end_time_591dbec1_idx",
)
]
index_together = ["property", "realm", "end_time"]
@override
def __str__(self) -> str:
return f"{self.user!r} {self.property} {self.subgroup} {self.value}"
@@ -149,13 +131,7 @@ class StreamCount(BaseCount):
]
# This index dramatically improves the performance of
# aggregating from streams to realms
indexes = [
models.Index(
fields=["property", "realm", "end_time"],
name="analytics_streamcount_property_realm_id_end_time_155ae930_idx",
)
]
index_together = ["property", "realm", "end_time"]
@override
def __str__(self) -> str:
return f"{self.stream!r} {self.property} {self.subgroup} {self.value} {self.id}"

View File

@@ -3,7 +3,7 @@ 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 zerver.models import Client, UserActivity, UserProfile, flush_per_request_caches
class ActivityTest(ZulipTestCase):
@@ -31,23 +31,18 @@ class ActivityTest(ZulipTestCase):
user_profile.is_staff = True
user_profile.save(update_fields=["is_staff"])
with self.assert_database_query_count(11):
flush_per_request_caches()
with self.assert_database_query_count(18):
result = self.client_get("/activity")
self.assertEqual(result.status_code, 200)
with self.assert_database_query_count(4):
result = self.client_get("/activity/remote")
self.assertEqual(result.status_code, 200)
with self.assert_database_query_count(4):
result = self.client_get("/activity/integrations")
self.assertEqual(result.status_code, 200)
flush_per_request_caches()
with self.assert_database_query_count(8):
result = self.client_get("/realm_activity/zulip/")
self.assertEqual(result.status_code, 200)
iago = self.example_user("iago")
flush_per_request_caches()
with self.assert_database_query_count(5):
result = self.client_get(f"/user_activity/{iago.id}/")
self.assertEqual(result.status_code, 200)

View File

@@ -3,14 +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
from analytics.lib.counts import (
COUNT_STATS,
@@ -55,26 +52,19 @@ 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, ceiling_to_day, floor_to_day
from zerver.lib.timestamp import TimeZoneNotUTCError, 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,
@@ -82,14 +72,6 @@ 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):
@@ -99,16 +81,13 @@ class AnalyticsTestCase(ZulipTestCase):
TIME_ZERO = datetime(1988, 3, 14, tzinfo=timezone.utc)
TIME_LAST_HOUR = TIME_ZERO - HOUR
@override
def setUp(self) -> None:
super().setUp()
self.default_realm = do_create_realm(
string_id="realmtest", name="Realm Test", date_created=self.TIME_ZERO - 2 * self.DAY
)
self.administrators_user_group = UserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS,
realm=self.default_realm,
is_system_group=True,
name=UserGroup.ADMINISTRATORS_GROUP_NAME, realm=self.default_realm, is_system_group=True
)
# used to generate unique names in self.create_*
@@ -203,9 +182,7 @@ class AnalyticsTestCase(ZulipTestCase):
) -> None:
if property is None:
property = self.current_property
queryset = table._default_manager.filter(property=property, end_time=end_time).filter(
**kwargs
)
queryset = table.objects.filter(property=property, end_time=end_time).filter(**kwargs)
if table is not InstallationCount:
if realm is None:
realm = self.default_realm
@@ -250,18 +227,15 @@ class AnalyticsTestCase(ZulipTestCase):
kwargs[arg_keys[i]] = values[i]
for key, value in defaults.items():
kwargs[key] = kwargs.get(key, value)
if (
table not in [InstallationCount, RemoteInstallationCount, RemoteRealmCount]
and "realm" not in kwargs
):
if table is not InstallationCount and "realm" not in kwargs:
if "user" in kwargs:
kwargs["realm"] = kwargs["user"].realm
elif "stream" in kwargs:
kwargs["realm"] = kwargs["stream"].realm
else:
kwargs["realm"] = self.default_realm
self.assertEqual(table._default_manager.filter(**kwargs).count(), 1)
self.assert_length(arg_values, table._default_manager.count())
self.assertEqual(table.objects.filter(**kwargs).count(), 1)
self.assert_length(arg_values, table.objects.count())
class TestProcessCountStat(AnalyticsTestCase):
@@ -479,7 +453,6 @@ class TestProcessCountStat(AnalyticsTestCase):
class TestCountStats(AnalyticsTestCase):
@override
def setUp(self) -> None:
super().setUp()
# This tests two things for each of the queries/CountStats: Handling
@@ -684,7 +657,7 @@ class TestCountStats(AnalyticsTestCase):
self.create_message(user1, recipient_huddle1)
self.create_message(user2, recipient_huddle2)
# direct messages
# private messages
recipient_user1 = Recipient.objects.get(type_id=user1.id, type=Recipient.PERSONAL)
recipient_user2 = Recipient.objects.get(type_id=user2.id, type=Recipient.PERSONAL)
recipient_user3 = Recipient.objects.get(type_id=user3.id, type=Recipient.PERSONAL)
@@ -1394,249 +1367,6 @@ 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"
@@ -1777,12 +1507,12 @@ class TestDeleteStats(AnalyticsTestCase):
FillState.objects.create(property="test", end_time=self.TIME_ZERO, state=FillState.DONE)
analytics = apps.get_app_config("analytics")
for table in analytics.models.values():
self.assertTrue(table._default_manager.exists())
for table in list(analytics.models.values()):
self.assertTrue(table.objects.exists())
do_drop_all_analytics_tables()
for table in analytics.models.values():
self.assertFalse(table._default_manager.exists())
for table in list(analytics.models.values()):
self.assertFalse(table.objects.exists())
def test_do_drop_single_stat(self) -> None:
user = self.create_user()
@@ -1801,17 +1531,16 @@ class TestDeleteStats(AnalyticsTestCase):
FillState.objects.create(property="to_save", end_time=self.TIME_ZERO, state=FillState.DONE)
analytics = apps.get_app_config("analytics")
for table in analytics.models.values():
self.assertTrue(table._default_manager.exists())
for table in list(analytics.models.values()):
self.assertTrue(table.objects.exists())
do_drop_single_stat("to_delete")
for table in analytics.models.values():
self.assertFalse(table._default_manager.filter(property="to_delete").exists())
self.assertTrue(table._default_manager.filter(property="to_save").exists())
for table in list(analytics.models.values()):
self.assertFalse(table.objects.filter(property="to_delete").exists())
self.assertTrue(table.objects.filter(property="to_save").exists())
class TestActiveUsersAudit(AnalyticsTestCase):
@override
def setUp(self) -> None:
super().setUp()
self.user = self.create_user()
@@ -1994,7 +1723,6 @@ class TestActiveUsersAudit(AnalyticsTestCase):
class TestRealmActiveHumans(AnalyticsTestCase):
@override
def setUp(self) -> None:
super().setUp()
self.stat = COUNT_STATS["realm_active_humans::day"]
@@ -2114,26 +1842,3 @@ 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

@@ -2,11 +2,10 @@ from datetime import datetime, timedelta, timezone
from typing import List, Optional
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.time_utils import time_range
from analytics.models import FillState, RealmCount, StreamCount, UserCount
from analytics.models import FillState, RealmCount, UserCount
from analytics.views.stats import rewrite_client_arrays, sort_by_totals, sort_client_labels
from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.timestamp import ceiling_to_day, ceiling_to_hour, datetime_to_timestamp
@@ -69,12 +68,10 @@ class TestStatsEndpoint(ZulipTestCase):
class TestGetChartData(ZulipTestCase):
@override
def setUp(self) -> None:
super().setUp()
self.realm = get_realm("zulip")
self.user = self.example_user("hamlet")
self.stream_id = self.get_stream_id(self.get_streams(self.user)[0])
self.login_user(self.user)
self.end_times_hour = [
ceiling_to_hour(self.realm.date_created) + timedelta(hours=i) for i in range(4)
@@ -117,17 +114,6 @@ class TestGetChartData(ZulipTestCase):
)
for i, subgroup in enumerate(user_subgroups)
)
StreamCount.objects.bulk_create(
StreamCount(
property=stat.property,
subgroup=subgroup,
end_time=insert_time,
value=100 + i,
stream_id=self.stream_id,
realm=self.realm,
)
for i, subgroup in enumerate(realm_subgroups)
)
FillState.objects.create(property=stat.property, end_time=fill_time, state=FillState.DONE)
def test_number_of_humans(self) -> None:
@@ -264,49 +250,6 @@ class TestGetChartData(ZulipTestCase):
},
)
def test_messages_sent_by_stream(self) -> None:
stat = COUNT_STATS["messages_in_stream:is_bot:day"]
self.insert_data(stat, ["true", "false"], [])
result = self.client_get(
f"/json/analytics/chart_data/stream/{self.stream_id}",
{
"chart_name": "messages_sent_by_stream",
},
)
data = self.assert_json_success(result)
self.assertEqual(
data,
{
"msg": "",
"end_times": [datetime_to_timestamp(dt) for dt in self.end_times_day],
"frequency": CountStat.DAY,
"everyone": {"bot": self.data(100), "human": self.data(101)},
"display_order": None,
"result": "success",
},
)
result = self.api_get(
self.example_user("polonius"),
f"/api/v1/analytics/chart_data/stream/{self.stream_id}",
{
"chart_name": "messages_sent_by_stream",
},
)
self.assert_json_error(result, "Not allowed for guest users")
# Verify we correctly forbid access to stats of streams in other realms.
result = self.api_get(
self.mit_user("sipbtest"),
f"/api/v1/analytics/chart_data/stream/{self.stream_id}",
{
"chart_name": "messages_sent_by_stream",
},
subdomain="zephyr",
)
self.assert_json_error(result, "Invalid stream ID")
def test_include_empty_subgroups(self) -> None:
FillState.objects.create(
property="realm_active_humans::day",

View File

@@ -4,10 +4,8 @@ from unittest import mock
import orjson
from django.utils.timezone import now as timezone_now
from typing_extensions import override
from corporate.lib.stripe import add_months
from corporate.lib.support import update_realm_sponsorship_status
from corporate.lib.stripe import add_months, update_sponsorship_status
from corporate.models import Customer, CustomerPlan, LicenseLedger, 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
@@ -23,72 +21,10 @@ 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
import uuid
from zilencer.models import RemoteZulipServer
class TestRemoteServerSupportEndpoint(ZulipTestCase):
@override
def setUp(self) -> None:
super().setUp()
# Set up some initial example data.
for i in range(20):
hostname = f"zulip-{i}.example.com"
RemoteZulipServer.objects.create(
hostname=hostname, contact_email=f"admin@{hostname}", plan_type=1, uuid=uuid.uuid4()
)
def test_search(self) -> None:
self.login("cordelia")
result = self.client_get("/activity/remote/support")
self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], "/login/")
# Iago is the user with the appropriate permissions to access this page.
self.login("iago")
assert self.example_user("iago").is_staff
result = self.client_get("/activity/remote/support")
self.assert_in_success_response(
[
'input type="text" name="q" class="input-xxlarge search-query" placeholder="hostname or contact email"'
],
result,
)
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"})
for i in range(20):
self.assert_in_success_response([f"<h3>zulip-{i}.example.com</h3>"], result)
result = self.client_get("/activity/remote/support", {"q": "admin@zulip-2.example.com"})
self.assert_in_success_response(["<h3>zulip-2.example.com</h3>"], result)
self.assert_in_success_response(["<b>Contact email</b>: admin@zulip-2.example.com"], result)
self.assert_not_in_success_response(["<h3>zulip-1.example.com</h3>"], result)
class TestSupportEndpoint(ZulipTestCase):
def test_search(self) -> None:
@@ -373,7 +309,7 @@ class TestSupportEndpoint(ZulipTestCase):
email = self.nonreg_email("alice")
self.submit_realm_creation_form(
email, realm_subdomain="custom-test", realm_name="Zulip test"
email, realm_subdomain="zuliptest", realm_name="Zulip test"
)
result = get_check_query_result(email, 1)
check_realm_creation_query_result(result, email)
@@ -432,7 +368,7 @@ class TestSupportEndpoint(ZulipTestCase):
result,
)
@mock.patch("analytics.views.support.update_realm_billing_method")
@mock.patch("analytics.views.support.update_billing_method_of_current_plan")
def test_change_billing_method(self, m: mock.Mock) -> None:
cordelia = self.example_user("cordelia")
self.login_user(cordelia)
@@ -572,9 +508,8 @@ 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_realm_sponsorship_status(lear_realm, True, acting_user=support_admin)
update_sponsorship_status(lear_realm, True, acting_user=None)
king_user = self.lear_user("king")
king_user.role = UserProfile.ROLE_REALM_OWNER
king_user.save()
@@ -664,29 +599,21 @@ class TestSupportEndpoint(ZulipTestCase):
"/activity/support", {"realm_id": f"{lear_realm.id}", "new_subdomain": "new-name"}
)
self.assert_in_success_response(
["Subdomain already in use. Please choose a different one."], result
["Subdomain unavailable. Please choose a different one."], result
)
result = self.client_post(
"/activity/support", {"realm_id": f"{lear_realm.id}", "new_subdomain": "zulip"}
)
self.assert_in_success_response(
["Subdomain already in use. Please choose a different one."], result
["Subdomain unavailable. Please choose a different one."], result
)
result = self.client_post(
"/activity/support", {"realm_id": f"{lear_realm.id}", "new_subdomain": "lear"}
)
self.assert_in_success_response(
["Subdomain already in use. Please choose a different one."], result
)
# Test renaming to a "reserved" subdomain
result = self.client_post(
"/activity/support", {"realm_id": f"{lear_realm.id}", "new_subdomain": "your-org"}
)
self.assert_in_success_response(
["Subdomain reserved. Please choose a different one."], result
["Subdomain unavailable. Please choose a different one."], result
)
def test_downgrade_realm(self) -> None:

View File

@@ -4,36 +4,28 @@ from django.conf.urls import include
from django.urls import path
from django.urls.resolvers import URLPattern, URLResolver
from analytics.views.installation_activity import (
get_installation_activity,
get_integrations_activity,
)
from analytics.views.installation_activity import get_installation_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),
@@ -57,7 +49,6 @@ i18n_urlpatterns: List[Union[URLPattern, URLResolver]] = [
v1_api_and_json_patterns = [
# get data for the graphs at /stats
rest_path("analytics/chart_data", GET=get_chart_data),
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(

View File

@@ -1,20 +1,17 @@
import re
import sys
from datetime import datetime
from typing import Any, Callable, Collection, Dict, List, Optional, Sequence, Union
from typing import Any, Collection, Dict, List, Optional, Sequence
from urllib.parse import urlencode
from django.conf import settings
from django.db import connection
from django.db.backends.utils import CursorWrapper
from django.template import loader
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 Realm, UserActivity
from zerver.models import UserActivity, get_realm
if sys.version_info < (3, 9): # nocoverage
from backports import zoneinfo
@@ -48,48 +45,6 @@ def make_table(
return content
def get_page(
query: Composable, cols: Sequence[str], title: str, totals_columns: Sequence[int] = []
) -> Dict[str, str]:
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,
)
def dictfetchall(cursor: CursorWrapper) -> List[Dict[str, Any]]:
"""Returns all rows from a cursor as a dict"""
desc = cursor.description
@@ -132,8 +87,7 @@ def realm_support_link(realm_str: str) -> Markup:
def realm_url_link(realm_str: str) -> Markup:
host = Realm.host_for_subdomain(realm_str)
url = settings.EXTERNAL_URI_SCHEME + mark_sanitized(host)
url = get_realm(realm_str).uri
return Markup('<a href="{url}"><i class="fa fa-home"></i></a>').format(url=url)
@@ -141,7 +95,7 @@ def remote_installation_stats_link(server_id: int, hostname: str) -> 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(
return Markup('<a href="{url}"><i class="fa fa-pie-chart"></i>{hostname}</a>').format(
url=url, hostname=hostname
)

View File

@@ -1,5 +1,9 @@
import itertools
import time
from collections import defaultdict
from typing import Dict, Optional
from contextlib import suppress
from datetime import datetime, timedelta
from typing import Callable, Dict, List, Optional, Sequence, Tuple, Union
from django.conf import settings
from django.db import connection
@@ -8,50 +12,55 @@ from django.shortcuts import render
from django.template import loader
from django.utils.timezone import now as timezone_now
from markupsafe import Markup
from psycopg2.sql import SQL
from psycopg2.sql import SQL, Composable, Literal
from analytics.lib.counts import COUNT_STATS
from analytics.views.activity_common import (
dictfetchall,
get_page,
format_date_for_activity_reports,
make_table,
realm_activity_link,
realm_stats_link,
realm_support_link,
realm_url_link,
remote_installation_stats_link,
)
from analytics.views.support import get_plan_name
from zerver.decorator import require_server_admin
from zerver.lib.request import has_request_variables
from zerver.models import Realm, get_org_type_display_name
from zerver.lib.timestamp import timestamp_to_datetime
from zerver.models import Realm, UserActivityInterval, get_org_type_display_name
if settings.BILLING_ENABLED:
from corporate.lib.analytics import (
from corporate.lib.stripe import (
estimate_annual_recurring_revenue_by_realm,
get_realms_with_default_discount_dict,
get_realms_to_default_discount_dict,
)
def get_realm_day_counts() -> Dict[str, Dict[str, Markup]]:
# To align with UTC days, we subtract an hour from end_time to
# get the start_time, since the hour that starts at midnight was
# on the previous day.
query = SQL(
"""
select
r.string_id,
(now()::date - (end_time - interval '1 hour')::date) age,
coalesce(sum(value), 0) cnt
from zerver_realm r
join analytics_realmcount rc on r.id = rc.realm_id
(now()::date - date_sent::date) age,
count(*) cnt
from zerver_message m
join zerver_userprofile up on up.id = m.sender_id
join zerver_realm r on r.id = up.realm_id
join zerver_client c on c.id = m.sending_client_id
where
property = 'messages_sent:is_bot:hour'
(not up.is_bot)
and
subgroup = 'false'
date_sent > now()::date - interval '8 day'
and
end_time > now()::date - interval '8 day' - interval '1 hour'
c.name not in ('zephyr_mirror', 'ZulipMonitoring')
group by
r.string_id,
age
order by
r.string_id,
age
"""
)
cursor = connection.cursor()
@@ -63,31 +72,33 @@ def get_realm_day_counts() -> Dict[str, Dict[str, Markup]]:
for row in rows:
counts[row["string_id"]][row["age"]] = row["cnt"]
def format_count(cnt: int, style: Optional[str] = None) -> Markup:
if style is not None:
good_bad = style
elif cnt == min_cnt:
good_bad = "bad"
elif cnt == max_cnt:
good_bad = "good"
else:
good_bad = "neutral"
return Markup('<td class="number {good_bad}">{cnt}</td>').format(good_bad=good_bad, cnt=cnt)
result = {}
for string_id in counts:
raw_cnts = [counts[string_id].get(age, 0) for age in range(8)]
min_cnt = min(raw_cnts[1:])
max_cnt = max(raw_cnts[1:])
def format_count(cnt: int, style: Optional[str] = None) -> Markup:
if style is not None:
good_bad = style
elif cnt == min_cnt:
good_bad = "bad"
elif cnt == max_cnt:
good_bad = "good"
else:
good_bad = "neutral"
return Markup('<td class="number {good_bad}">{cnt}</td>').format(
good_bad=good_bad, cnt=cnt
)
cnts = format_count(raw_cnts[0], "neutral") + Markup().join(map(format_count, raw_cnts[1:]))
result[string_id] = dict(cnts=cnts)
return result
def realm_summary_table() -> str:
def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
now = timezone_now()
query = SQL(
@@ -198,7 +209,7 @@ def realm_summary_table() -> str:
total_arr = 0
if settings.BILLING_ENABLED:
estimated_arrs = estimate_annual_recurring_revenue_by_realm()
realms_with_default_discount = get_realms_with_default_discount_dict()
realms_to_default_discount = get_realms_to_default_discount_dict()
for row in rows:
row["plan_type_string"] = get_plan_name(row["plan_type"])
@@ -209,14 +220,14 @@ def realm_summary_table() -> 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_with_default_discount.get(string_id, 0))
row["effective_rate"] = 100 - int(realms_to_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_with_default_discount
and string_id in realms_to_default_discount
):
row["effective_rate"] = 100 - int(realms_with_default_discount[string_id])
row["effective_rate"] = 100 - int(realms_to_default_discount[string_id])
else:
row["effective_rate"] = ""
@@ -225,6 +236,17 @@ def realm_summary_table() -> 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"])
@@ -233,7 +255,10 @@ def realm_summary_table() -> str:
row["string_id"] = realm_activity_link(row["string_id"])
# Count active sites
num_active_sites = sum(row["dau_count"] >= 5 for row in rows)
def meets_goal(row: Dict[str, int]) -> bool:
return row["dau_count"] >= 5
num_active_sites = len(list(filter(meets_goal, rows)))
# create totals
total_dau_count = 0
@@ -259,6 +284,7 @@ def realm_summary_table() -> 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,
)
@@ -276,21 +302,218 @@ def realm_summary_table() -> str:
return content
@require_server_admin
@has_request_variables
def get_installation_activity(request: HttpRequest) -> HttpResponse:
content: str = realm_summary_table()
title = "Installation activity"
def user_activity_intervals() -> Tuple[Markup, Dict[str, float]]:
day_end = timestamp_to_datetime(time.time())
day_start = day_end - timedelta(hours=24)
return render(
request,
"analytics/activity_details_template.html",
context=dict(data=content, title=title, is_home=True),
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
def ad_hoc_queries() -> List[Dict[str, str]]:
def get_page(
query: Composable, cols: Sequence[str], title: str, totals_columns: Sequence[int] = []
) -> Dict[str, str]:
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,
)
pages = []
###
for mobile_type in ["Android", "ZulipiOS"]:
title = f"{mobile_type} usage"
query: Composable = SQL(
"""
select
realm.string_id,
up.id user_id,
client.name,
sum(count) as hits,
max(last_visit) as last_time
from zerver_useractivity ua
join zerver_client client on client.id = ua.client_id
join zerver_userprofile up on up.id = ua.user_profile_id
join zerver_realm realm on realm.id = up.realm_id
where
client.name like {mobile_type}
group by string_id, up.id, client.name
having max(last_visit) > now() - interval '2 week'
order by string_id, up.id, client.name
"""
).format(
mobile_type=Literal(mobile_type),
)
cols = [
"Realm",
"User id",
"Name",
"Hits",
"Last time",
]
pages.append(get_page(query, cols, title))
###
title = "Desktop users"
query = SQL(
"""
select
realm.string_id,
client.name,
sum(count) as hits,
max(last_visit) as last_time
from zerver_useractivity ua
join zerver_client client on client.id = ua.client_id
join zerver_userprofile up on up.id = ua.user_profile_id
join zerver_realm realm on realm.id = up.realm_id
where
client.name like 'desktop%%'
group by string_id, client.name
having max(last_visit) > now() - interval '2 week'
order by string_id, client.name
"""
)
cols = [
"Realm",
"Client",
"Hits",
"Last time",
]
pages.append(get_page(query, cols, title))
###
title = "Integrations by realm"
query = SQL(
"""
select
realm.string_id,
case
when query like '%%external%%' then split_part(query, '/', 5)
else client.name
end client_name,
sum(count) as hits,
max(last_visit) as last_time
from zerver_useractivity ua
join zerver_client client on client.id = ua.client_id
join zerver_userprofile up on up.id = ua.user_profile_id
join zerver_realm realm on realm.id = up.realm_id
where
(query in ('send_message_backend', '/api/v1/send_message')
and client.name not in ('Android', 'ZulipiOS')
and client.name not like 'test: Zulip%%'
)
or
query like '%%external%%'
group by string_id, client_name
having max(last_visit) > now() - interval '2 week'
order by string_id, client_name
"""
)
cols = [
"Realm",
"Client",
"Hits",
"Last time",
]
pages.append(get_page(query, cols, title))
###
@require_server_admin
def get_integrations_activity(request: HttpRequest) -> HttpResponse:
title = "Integrations by client"
query = SQL(
@@ -327,14 +550,71 @@ def get_integrations_activity(request: HttpRequest) -> HttpResponse:
"Last time",
]
integrations_activity = get_page(query, cols, title)
pages.append(get_page(query, cols, title))
title = "Remote Zulip servers"
query = SQL(
"""
with icount as (
select
server_id,
max(value) as max_value,
max(end_time) as max_end_time
from zilencer_remoteinstallationcount
where
property='active_users:is_bot:day'
and subgroup='false'
group by server_id
),
remote_push_devices as (
select server_id, count(distinct(user_id)) as push_user_count from zilencer_remotepushdevicetoken
group by server_id
)
select
rserver.id,
rserver.hostname,
rserver.contact_email,
max_value,
push_user_count,
max_end_time
from zilencer_remotezulipserver rserver
left join icount on icount.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
"""
)
cols = [
"ID",
"Hostname",
"Contact email",
"Analytics users",
"Mobile users",
"Last update time",
]
pages.append(get_page(query, cols, title, totals_columns=[3, 4]))
return pages
@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),
]
for page in ad_hoc_queries():
data.append((page["title"], page["content"]))
title = "Activity"
return render(
request,
"analytics/activity_details_template.html",
context=dict(
data=integrations_activity["content"],
title=integrations_activity["title"],
is_home=False,
),
"analytics/activity.html",
context=dict(data=data, title=title, is_home=True),
)

View File

@@ -163,13 +163,12 @@ def sent_messages_report(realm: str) -> str:
"Bots",
]
# Uses index: zerver_message_realm_date_sent
query = SQL(
"""
select
series.day::date,
user_messages.humans,
user_messages.bots
humans.cnt,
bots.cnt
from (
select generate_series(
(now()::date - interval '2 week'),
@@ -180,27 +179,45 @@ def sent_messages_report(realm: str) -> str:
left join (
select
date_sent::date date_sent,
count(*) filter (where not up.is_bot) as humans,
count(*) filter (where up.is_bot) as bots
count(*) cnt
from zerver_message m
join zerver_userprofile up on up.id = m.sender_id
join zerver_realm r on r.id = up.realm_id
where
r.string_id = %s
and
date_sent > now() - interval '2 week'
(not up.is_bot)
and
m.realm_id = r.id
date_sent > now() - interval '2 week'
group by
date_sent::date
order by
date_sent::date
) user_messages on
series.day = user_messages.date_sent
) humans on
series.day = humans.date_sent
left join (
select
date_sent::date date_sent,
count(*) cnt
from zerver_message m
join zerver_userprofile up on up.id = m.sender_id
join zerver_realm r on r.id = up.realm_id
where
r.string_id = %s
and
up.is_bot
and
date_sent > now() - interval '2 week'
group by
date_sent::date
order by
date_sent::date
) bots on
series.day = bots.date_sent
"""
)
cursor = connection.cursor()
cursor.execute(query, [realm])
cursor.execute(query, [realm, realm])
rows = cursor.fetchall()
cursor.close()

View File

@@ -1,59 +0,0 @@
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 zerver.decorator import require_server_admin
@require_server_admin
def get_remote_server_activity(request: HttpRequest) -> HttpResponse:
title = "Remote servers"
query = SQL(
"""
with icount as (
select
server_id,
max(value) as max_value,
max(end_time) as max_end_time
from zilencer_remoteinstallationcount
where
property='active_users:is_bot:day'
and subgroup='false'
group by server_id
),
remote_push_devices as (
select server_id, count(distinct(user_id)) as push_user_count from zilencer_remotepushdevicetoken
group by server_id
)
select
rserver.id,
rserver.hostname,
rserver.contact_email,
max_value,
push_user_count,
max_end_time
from zilencer_remotezulipserver rserver
left join icount on icount.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
"""
)
cols = [
"ID",
"Hostname",
"Contact email",
"Analytics users",
"Mobile users",
"Last update time",
]
remote_servers = get_page(query, cols, title, totals_columns=[3, 4])
return render(
request,
"analytics/activity_details_template.html",
context=dict(data=remote_servers["content"], title=remote_servers["title"], is_home=False),
)

View File

@@ -10,7 +10,6 @@ from django.shortcuts import render
from django.utils import translation
from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _
from typing_extensions import TypeAlias
from analytics.lib.counts import COUNT_STATS, CountStat
from analytics.lib.time_utils import time_range
@@ -33,10 +32,9 @@ from zerver.lib.exceptions import JsonableError
from zerver.lib.i18n import get_and_set_request_language, get_language_translation_data
from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_success
from zerver.lib.streams import access_stream_by_id
from zerver.lib.timestamp import convert_to_UTC
from zerver.lib.validator import to_non_negative_int
from zerver.models import Client, Realm, Stream, UserProfile, get_realm
from zerver.models import Client, Realm, UserProfile, get_realm
if settings.ZILENCER_ENABLED:
from zilencer.models import RemoteInstallationCount, RemoteRealmCount, RemoteZulipServer
@@ -157,21 +155,6 @@ def get_chart_data_for_realm(
return get_chart_data(request, user_profile, realm=realm, **kwargs)
@require_non_guest_user
@has_request_variables
def get_chart_data_for_stream(
request: HttpRequest, /, user_profile: UserProfile, stream_id: int
) -> HttpResponse:
stream, ignored_sub = access_stream_by_id(
user_profile,
stream_id,
require_active=True,
allow_realm_admin=True,
)
return get_chart_data(request, user_profile, stream=stream)
@require_server_admin_api
@has_request_variables
def get_chart_data_for_remote_realm(
@@ -253,17 +236,13 @@ def get_chart_data(
min_length: Optional[int] = REQ(converter=to_non_negative_int, default=None),
start: Optional[datetime] = REQ(converter=to_utc_datetime, default=None),
end: Optional[datetime] = REQ(converter=to_utc_datetime, default=None),
# These last several parameters are only used by functions
# wrapping get_chart_data; the callers are responsible for
# parsing/validation/authorization for them.
realm: Optional[Realm] = None,
for_installation: bool = False,
remote: bool = False,
remote_realm_id: Optional[int] = None,
server: Optional["RemoteZulipServer"] = None,
stream: Optional[Stream] = None,
) -> HttpResponse:
TableType: TypeAlias = Union[
TableType = Union[
Type["RemoteInstallationCount"],
Type[InstallationCount],
Type["RemoteRealmCount"],
@@ -285,9 +264,7 @@ def get_chart_data(
else:
aggregate_table = RealmCount
tables: Union[
Tuple[TableType], Tuple[TableType, Type[UserCount]], Tuple[TableType, Type[StreamCount]]
]
tables: Union[Tuple[TableType], Tuple[TableType, Type[UserCount]]]
if chart_name == "number_of_humans":
stats = [
@@ -337,18 +314,8 @@ def get_chart_data(
subgroup_to_label = {stats[0]: {None: "read"}}
labels_sort_function = None
include_empty_subgroups = True
elif chart_name == "messages_sent_by_stream":
if stream is None:
raise JsonableError(
_("Missing stream for chart: {chart_name}").format(chart_name=chart_name)
)
stats = [COUNT_STATS["messages_in_stream:is_bot:day"]]
tables = (aggregate_table, StreamCount)
subgroup_to_label = {stats[0]: {"false": "human", "true": "bot"}}
labels_sort_function = None
include_empty_subgroups = True
else:
raise JsonableError(_("Unknown chart name: {chart_name}").format(chart_name=chart_name))
raise JsonableError(_("Unknown chart name: {}").format(chart_name))
# Most likely someone using our API endpoint. The /stats page does not
# pass a start or end in its requests.
@@ -429,7 +396,6 @@ def get_chart_data(
InstallationCount: "everyone",
RealmCount: "everyone",
UserCount: "user",
StreamCount: "everyone",
}
if settings.ZILENCER_ENABLED:
aggregation_level[RemoteInstallationCount] = "everyone"
@@ -441,9 +407,6 @@ def get_chart_data(
RealmCount: realm.id,
UserCount: user_profile.id,
}
if stream is not None:
id_value[StreamCount] = stream.id
if settings.ZILENCER_ENABLED:
if server is not None:
id_value[RemoteInstallationCount] = server.id
@@ -474,7 +437,8 @@ def get_chart_data(
def sort_by_totals(value_arrays: Dict[str, List[int]]) -> List[str]:
totals = sorted(((sum(values), label) for label, values in value_arrays.items()), reverse=True)
totals = [(sum(values), label) for label, values in value_arrays.items()]
totals.sort(reverse=True)
return [label for total, label in totals]
@@ -500,17 +464,17 @@ CountT = TypeVar("CountT", bound=BaseCount)
def table_filtered_to_id(table: Type[CountT], key_id: int) -> QuerySet[CountT]:
if table == RealmCount:
return table._default_manager.filter(realm_id=key_id)
return table.objects.filter(realm_id=key_id)
elif table == UserCount:
return table._default_manager.filter(user_id=key_id)
return table.objects.filter(user_id=key_id)
elif table == StreamCount:
return table._default_manager.filter(stream_id=key_id)
return table.objects.filter(stream_id=key_id)
elif table == InstallationCount:
return table._default_manager.all()
return table.objects.all()
elif settings.ZILENCER_ENABLED and table == RemoteInstallationCount:
return table._default_manager.filter(server_id=key_id)
return table.objects.filter(server_id=key_id)
elif settings.ZILENCER_ENABLED and table == RemoteRealmCount:
return table._default_manager.filter(realm_id=key_id)
return table.objects.filter(realm_id=key_id)
else:
raise AssertionError(f"Unknown table: {table}")
@@ -542,10 +506,10 @@ def rewrite_client_arrays(value_arrays: Dict[str, List[int]]) -> Dict[str, List[
for label, array in value_arrays.items():
mapped_label = client_label_map(label)
if mapped_label in mapped_arrays:
for i in range(len(array)):
for i in range(0, len(array)):
mapped_arrays[mapped_label][i] += value_arrays[label][i]
else:
mapped_arrays[mapped_label] = [value_arrays[label][i] for i in range(len(array))]
mapped_arrays[mapped_label] = [value_arrays[label][i] for i in range(0, len(array))]
return mapped_arrays

View File

@@ -3,7 +3,7 @@ from contextlib import suppress
from dataclasses import dataclass
from datetime import timedelta
from decimal import Decimal
from typing import Any, Dict, Iterable, List, Optional, Union
from typing import Any, Dict, Iterable, List, Optional
from urllib.parse import urlencode
from django.conf import settings
@@ -48,25 +48,19 @@ from zerver.models import (
)
from zerver.views.invite import get_invitee_emails_set
if settings.ZILENCER_ENABLED:
from zilencer.lib.remote_counts import MissingDataError, compute_max_monthly_messages
from zilencer.models import RemoteZulipServer
if settings.BILLING_ENABLED:
from corporate.lib.stripe import approve_sponsorship as do_approve_sponsorship
from corporate.lib.stripe import (
RealmBillingSession,
attach_discount_to_realm,
downgrade_at_the_end_of_billing_cycle,
downgrade_now_without_creating_additional_invoices,
get_latest_seat_count,
switch_realm_from_standard_to_plus_plan,
void_all_open_invoices,
)
from corporate.lib.support import (
approve_realm_sponsorship,
attach_discount_to_realm,
get_discount_for_realm,
update_realm_billing_method,
update_realm_sponsorship_status,
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,
)
from corporate.models import (
Customer,
@@ -185,8 +179,6 @@ 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.
@@ -199,6 +191,8 @@ 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:
current_plan_type = realm.plan_type
do_change_realm_plan_type(realm, plan_type, acting_user=acting_user)
@@ -240,14 +234,14 @@ def support(
context["success_message"] = f"{realm.string_id} deactivated."
elif billing_method is not None:
if billing_method == "send_invoice":
update_realm_billing_method(
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_realm_billing_method(
update_billing_method_of_current_plan(
realm, charge_automatically=True, acting_user=acting_user
)
context[
@@ -255,13 +249,13 @@ def support(
] = f"Billing method of {realm.string_id} updated to charge automatically."
elif sponsorship_pending is not None:
if sponsorship_pending:
update_realm_sponsorship_status(realm, True, acting_user=acting_user)
update_sponsorship_status(realm, True, acting_user=acting_user)
context["success_message"] = f"{realm.string_id} marked as pending sponsorship."
else:
update_realm_sponsorship_status(realm, False, acting_user=acting_user)
update_sponsorship_status(realm, False, acting_user=acting_user)
context["success_message"] = f"{realm.string_id} is no longer pending sponsorship."
elif approve_sponsorship:
approve_realm_sponsorship(realm, acting_user=acting_user)
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":
@@ -375,8 +369,7 @@ def support(
current_plan=current_plan,
)
if current_plan is not None:
billing_session = RealmBillingSession(user=None, realm=realm)
new_plan, last_ledger_entry = billing_session.make_end_of_cycle_updates_if_needed(
new_plan, last_ledger_entry = make_end_of_cycle_updates_if_needed(
current_plan, timezone_now()
)
if last_ledger_entry is not None:
@@ -413,53 +406,3 @@ def support(
)
return render(request, "analytics/support.html", context=context)
def get_remote_servers_for_support(
email_to_search: Optional[str], hostname_to_search: Optional[str]
) -> List["RemoteZulipServer"]:
if not email_to_search and not hostname_to_search:
return []
remote_servers_query = RemoteZulipServer.objects.order_by("id")
if email_to_search:
remote_servers_query = remote_servers_query.filter(contact_email__iexact=email_to_search)
elif hostname_to_search:
remote_servers_query = remote_servers_query.filter(hostname__icontains=hostname_to_search)
return list(remote_servers_query)
@require_server_admin
@has_request_variables
def remote_servers_support(
request: HttpRequest, query: Optional[str] = REQ("q", default=None)
) -> HttpResponse:
email_to_search = None
hostname_to_search = None
if query:
if "@" in query:
email_to_search = query
else:
hostname_to_search = query
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()
for remote_server in remote_servers:
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"
return render(
request,
"analytics/remote_server_support.html",
context=dict(
remote_servers=remote_servers,
remote_server_to_max_monthly_messages=remote_server_to_max_monthly_messages,
),
)

View File

@@ -60,7 +60,7 @@ def raw_user_activity_table(records: QuerySet[UserActivity]) -> str:
def user_activity_summary_table(user_summary: Dict[str, Dict[str, Any]]) -> str:
rows = []
for k, v in user_summary.items():
if k in ("name", "user_profile_id"):
if k == "name" or k == "user_profile_id":
continue
client = k
count = v["count"]

View File

@@ -18,368 +18,6 @@ clients should check the `zulip_feature_level` field, present in the
/register`](/api/register-queue) responses, to determine the API
format used by the Zulip server that they are interacting with.
## Changes in Zulip 8.0
**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)
[`POST /register`](/api/register-queue), [`GET /events`](/api/get-events),
[`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.
**Feature level 218**
* [`POST /messages`](/api/send-message): Added an optional
`automatic_new_visibility_policy` enum field in the success response
to indicate the new visibility policy value due to the [visibility policy settings](/help/mute-a-topic)
during the send message action.
**Feature level 217**
* [`POST /mobile_push/test_notification`](/api/test-notify): Added new endpoint
to send a test push notification to a mobile device or devices.
**Feature level 216**:
* `PATCH /realm`, [`POST register`](/api/register-queue),
[`GET /events`](/api/get-events): Added `enable_guest_user_indicator`
setting to control whether "(guest)" is added to user names in UI.
**Feature level 215**
* [`GET /events`](/api/get-events): Replaced the value `private`
with `direct` in the `message_type` field for the `typing` events
sent when a user starts or stops typing a message.
* [`POST /typing`](/api/set-typing-status): Stopped supporting `private`
as a valid value for the `type` parameter.
* [`POST /typing`](/api/set-typing-status): Stopped using the `to` parameter
for the `"stream"` type. Previously, in the case of the `"stream"` type, it
accepted a single-element list containing the ID of the stream. Added an
optional parameter, `stream_id`. Now, `to` is used only for `"direct"` type.
In the case of `"stream"` type, `stream_id` and `topic` are used.
* Note that stream typing notifications were not enabled in any Zulip client
prior to feature level 215.
**Feature level 214**
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),
[`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings):
Added two new user settings, `automatically_follow_topics_policy` and
`automatically_unmute_topics_in_muted_streams_policy`. The settings control the
user's preference on which topics the user will automatically 'follow' and
'unmute in muted streams' respectively.
**Feature level 213**
* [`POST /register`](/api/register-queue): Fixed incorrect handling of
unmuted and followed topics in calculating the `mentions` and
`count` fields of the `unread_msgs` object.
**Feature level 212**
* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue),
`PATCH /realm`: Added the `jitsi_server_url` field to the `realm` object,
allowing organizations to set a custom Jitsi Meet server. Previously, this
was only available as a server-level configuration.
* [`POST /register`](/api/register-queue): Added `server_jitsi_server_url`
fields to the `realm` object. The existing `jitsi_server_url` will now be
calculated as `realm_jitsi_server_url || server_jitsi_server_url`.
**Feature level 211**
* [`POST /streams/{stream_id}/delete_topic`](/api/delete-topic),
[`POST /mark_all_as_read`](/api/mark-all-as-read):
Added a `complete` boolean field in the success response to indicate
whether all or only some of the targeted messages were processed.
This replaces the use of `"result": "partially_completed"` (introduced
in feature levels 154 and 153), so that these endpoints now send a
`result` string of either `"success"` or `"error"`, like the rest of
the Zulip API.
**Feature level 210**
* [`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings),
[`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults):
Added new `web_stream_unreads_count_display_policy` display setting, which controls in
which streams (all/unmuted/none) unread messages count shows up
in left sidebar.
**Feature level 209**
* `PATCH /realm`, [`POST /register`](/api/register-queue),
[`GET /events`](/api/get-events): Added `create_multiuse_invite_group`
realm setting, which is the ID of the user group whose members can
create [reusable invitation links](/help/invite-new-users#create-a-reusable-invitation-link)
to an organization. Previously, only admin users could create these
links.
* `POST /invites/multiuse`: Non-admin users can now use this endpoint
to create reusable invitation links. Previously, this endpoint was
restricted to admin users only.
* `GET /invites`: Endpoint response for non-admin users now includes both
email invitations and reusable invitation links that they have created.
Previously, non-admin users could only create email invitations, and
therefore the response did not include reusable invitation links for these users.
* `DELETE /invites/multiuse/{invite_id}`: Non-admin users can now revoke
reusable invitation links they have created. Previously, only admin users could
create and revoke reusable invitation links.
* [`GET /events`](/api/get-events): When the set of invitations in an
organization changes, an `invites_changed` event is now sent to the
creator of the changed invitation, as well as all admin users.
Previously, this event was only sent to admin users.
**Feature level 208**
* [`POST /users/me/subscriptions`](/api/subscribe),
[`DELETE /users/me/subscriptions`](/api/unsubscribe): These endpoints
now return an HTTP status code of 400 with `code: "BAD_REQUEST"` in
the error response when a user specified in the `principals` parameter
is deactivated or does not exist. Previously, these endpoints returned
an HTTP status code of 403 with `code: "UNAUTHORIZED_PRINCIPAL"` in the
error response for these cases.
**Feature level 207**
* [`POST /register`](/api/register-queue): Added `display_name` and
`all_event_types` fields to the `realm_incoming_webhook_bots` object.
**Feature level 206**
* `POST /calls/zoom/create`: Added `is_video_call` parameter
controlling whether to request a Zoom meeting that defaults to
having video enabled.
**Feature level 205**
* [`POST /register`](/api/register-queue): `streams` field in the response
now includes [web-public streams](/help/public-access-option) as well.
* [`GET /events`](/api/get-events): Events for stream creation and deletion
are now sent to clients when a user gains or loses access to any streams
due to a change in their [role](/help/roles-and-permissions).
* [`GET /events`](/api/get-events): The `subscription` events for `op:
"peer_add"` are now sent to clients when a user gains access to a stream
due to a change in their role.
**Feature level 204**
* [`POST /register`](/api/register-queue): Added
`server_typing_started_wait_period_milliseconds`,
`server_typing_stopped_wait_period_milliseconds`, and
`server_typing_started_expiry_period_milliseconds` fields
for clients to use when implementing [typing
notifications](/api/set-typing-status) protocol.
**Feature level 203**
* [`POST /register`](/api/register-queue): Add
`realm_date_created` field to realm data.
**Feature level 202**
* [`PATCH /realm/linkifiers`](/api/reorder-linkifiers): Added new endpoint
to support changing the order in which linkifiers will be processed.
**Feature level 201**
* `POST /zulip-outgoing-webhook`: Renamed the notification trigger
`private_message` to `direct_message`.
**Feature level 200**
* [`PATCH /streams/{stream_id}`](/api/update-stream): Added
`is_default_stream` parameter to change whether the stream is a
default stream for new users in the organization.
* [`POST /users/me/subscriptions`](/api/subscribe): Added
`is_default_stream` parameter which determines whether any streams
created by this request will be default streams for new users.
**Feature level 199**
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events),
[`GET /streams`](/api/get-streams),
[`GET /streams/{stream_id}`](/api/get-stream-by-id): Stream objects now
include a `stream_weekly_traffic` field indicating the stream's level of
traffic.
**Feature level 198**
* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue),
[`GET /user_groups`](/api/get-user-groups),
[`POST /user_groups/create`](/api/create-user-group),
[`PATCH /user_groups/{user_group_id}`](/api/update-user-group):Renamed
group setting `can_mention_group_id` to `can_mention_group`.
**Feature level 197**
* [`POST /users/me/subscriptions`](/api/subscribe),
[`PATCH /streams/{stream_id}`](/api/update-stream),
[`GET /users/me/subscriptions`](/api/get-subscriptions),
[`GET /streams`](/api/get-streams),
[`POST /register`](/api/register-queue),
[`GET /events`](/api/get-events): Renamed
stream setting `can_remove_subscribers_group_id`
to `can_remove_subscribers_group`.
**Feature level 196**
* [`POST /realm/playgrounds`](/api/add-code-playground): `url_prefix` is
replaced by `url_template`, which only accepts [RFC 6570][rfc6570] compliant
URL templates. The old prefix format is no longer supported.
* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue):
`url_prefix` is replaced by `url_template` in `realm_playgrounds` events.
**Feature level 195**
* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue):
The `default_code_block_language` realm setting is now consistently an
empty string when no default pygments language code is set. Previously,
the server had a bug that meant it might represent no default for this
realm setting as either `null` or an empty string. Clients supporting
older server versions should treat either value (`null` or `""`) as no
default being set.
**Feature level 194**
* [`GET /messages`](/api/get-messages),
[`GET /messages/matches_narrow`](/api/check-messages-match-narrow),
[`POST /message/flags/narrow`](/api/update-message-flags-for-narrow),
[`POST /register`](/api/register-queue):
For [search/narrow filters](/api/construct-narrow) with the `id`
operator, added support for encoding the message ID operand as either
a string or an integer. Previously, only string encoding was supported.
**Feature level 193**
* [`POST /messages/{message_id}/reactions`](/api/add-reaction),
[`DELETE /messages/{message_id}/reactions`](/api/remove-reaction):
Endpoints return specific error responses if an emoji reaction
already exists when adding a reaction (`"code": "REACTION_ALREADY_EXISTS"`)
or if an emoji reaction does not exist when deleting a reaction
(`"code": "REACTION_DOES_NOT_EXIST"`). Previously, these errors
returned the `"BAD_REQUEST"` code.
**Feature level 192**
* [`GET /events`](/api/get-events): Stream creation events are now
sent when guest users gain access to a public stream by being
subscribed. Guest users previously only received these events when
subscribed to private streams.
**Feature level 191**
* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue),
[`GET /user_groups`](/api/get-user-groups): Add `can_mention_group_id` to
user group objects.
* [`POST /user_groups/create`](/api/create-user-group): Added `can_mention_group_id`
parameter to support setting the user group whose members can mention the new user
group.
* [`PATCH /user_groups/{user_group_id}`](/api/update-user-group): Added
`can_mention_group_id` parameter to support changing the user group whose
members can mention the specified user group.
**Feature level 190**
* [`DELETE /realm/emoji/{emoji_name}`](/api/deactivate-custom-emoji): This endpoint
now returns an HTTP status code of 404 when an emoji does not exist, instead of 400.
**Feature level 189**
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),
[`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings):
Added new boolean user settings `enable_followed_topic_email_notifications`,
`enable_followed_topic_push_notifications`,
`enable_followed_topic_wildcard_mentions_notify`,
`enable_followed_topic_desktop_notifications`
and `enable_followed_topic_audible_notifications` to control whether a user
receives email, push, wildcard mention, visual desktop and audible desktop
notifications, respectively, for messages sent to followed topics.
**Feature level 188**
* [`POST /users/me/muted_users/{muted_user_id}`](/api/mute-user),
[`DELETE /users/me/muted_users/{muted_user_id}`](/api/unmute-user):
Added support to mute/unmute bot users.
Feature levels 186-187 are reserved for future use in 7.x maintenance
releases.
## Changes in Zulip 7.0
**Feature level 185**
@@ -442,7 +80,7 @@ No changes; feature level used for Zulip 7.0 release.
**Feature level 178**
* `POST /users/me/presence`,
* `POST users/me/presence`,
[`GET /users/<user_id_or_email>/presence`](/api/get-user-presence),
[`GET /realm/presence`](/api/get-presence),
[`POST /register`](/api/register-queue),
@@ -468,7 +106,7 @@ No changes; feature level used for Zulip 7.0 release.
**Feature level 176**
* [`POST /realm/filters`](/api/add-linkifier),
[`PATCH /realm/filters/<int:filter_id>`](/api/update-linkifier):
[`PATCH realm/filters/<int:filter_id>`](/api/update-linkifier):
The `url_format_string` parameter is replaced by `url_template`.
[Linkifiers](/help/add-a-custom-linkifier) now only accept
[RFC 6570][rfc6570] compliant URL templates. The old URL format
@@ -588,7 +226,7 @@ No changes; feature level used for Zulip 7.0 release.
[`GET /users/me`](/api/get-own-user), [`GET /events`](/api/get-events):
The `delivery_email` field is always present in user objects, including
the case when a user's `email_address_visibility` is set to everyone.
The value will be `null` if the requester does not have access to the
The value will be `null` if the requestor does not have access to the
user's real email. For bot users, the `delivery_email` field is always
set to the bot user's real email.
* [`GET /events`](/api/get-events): Event for updating a user's
@@ -688,29 +326,26 @@ No changes; feature level used for Zulip 6.0 release.
**Feature level 154**
* [`POST /streams/{stream_id}/delete_topic`](/api/delete-topic):
When the process of deleting messages times out, but successfully
deletes some messages in the topic (see feature level 147 for when
this endpoint started deleting messages in batches), a success
response with `"result": "partially_completed"` will now be returned
by the server, analogically to the `POST /mark_all_as_read` endpoint
(see feature level 153 entry below).
When the process of deleting messages times out, a success response
with "partially_completed" result will now be returned by the server,
analogically to the `/mark_all_as_read` endpoint.
**Feature level 153**
* [`POST /mark_all_as_read`](/api/mark-all-as-read): Messages are now
marked as read in batches, so that progress will be made even if the
request times out because of an extremely large number of unread
messages to process. Upon timeout, a success response with
`"result": "partially_completed"` will be returned by the server.
messages to process. Upon timeout, a success response with a
"partially_completed" result will be returned by the server.
**Feature level 152**
* [`PATCH /messages/{message_id}`](/api/update-message):
The default value for `send_notification_to_old_thread` was changed from
`true` to `false`.
When moving a topic within a stream, the `send_notification_to_old_thread`
and `send_notification_to_new_thread` parameters are now respected, and by
default a notification is sent to the new thread.
* [`PATCH /messages/{message_id}`](/api/update-message): The
`send_notification_to_old_thread` and
`send_notification_to_new_thread` parameters are now respected when
moving a topic within a stream. The default value for
`send_notification_to_old_thread` was changed from `true` to
`false`.
**Feature level 151**
@@ -763,7 +398,7 @@ user's profile.
**Feature level 145**
* [`DELETE /users/me/subscriptions`](/api/unsubscribe): Normal users can
* [`DELETE users/me/subscriptions`](/api/unsubscribe): Normal users can
now remove bots that they own from streams.
**Feature level 144**
@@ -784,7 +419,7 @@ user's profile.
**Feature level 142**
* [`GET /users/me/subscriptions`](/api/get-subscriptions), [`GET
* [`GET users/me/subscriptions`](/api/get-subscriptions), [`GET
/streams`](/api/get-streams), [`POST /register`](/api/register-queue),
[`GET /events`](/api/get-events): Added `can_remove_subscribers_group_id`
field to Stream and Subscription objects.
@@ -855,9 +490,6 @@ user's profile.
to the response. This generalizes and replaces the previous
`muted_topics` array, which will no longer be sent if `user_topic`
is included in `fetch_event_types`.
* [`GET /events`](/api/get-events): When private streams are made
public, `stream` events for `op: "create"` and `subscription` events
for `op: "peer_add"` are now sent to clients.
**Feature level 133**
@@ -954,12 +586,11 @@ No changes; feature level used for Zulip 5.0 release.
**Feature level 119**
* [`POST /register`](/api/register-queue): Added `other_user_id` field
to the `pms` objects in the `unread_msgs` data set, deprecating the
less clearly named `sender_id` field. This change was motivated by
the possibility that a one-on-one direct message sent by the current
user to another user could be marked as unread. The `sender_id` field
is still present for backwards compatibility with older server versions.
* [`POST /register`](/api/register-queue): The `unread_msgs` section
of the response now prefers `other_user_id` over the poorly named
`sender_id` field in the `pms` dictionaries. This change is
motivated by the possibility that a message you yourself sent to
another user could be marked as unread.
**Feature level 118**
@@ -974,7 +605,7 @@ No changes; feature level used for Zulip 5.0 release.
field. These changes substantially simplify client complexity for
processing historical message edits.
* [`GET /messages/{message_id}/history`](/api/get-message-history):
* [`GET messages/{message_id}/history`](/api/get-message-history):
Added `stream` field to message history `snapshot` indicating
the updated stream ID of messages moved to a new stream.
@@ -1050,7 +681,7 @@ No changes; feature level used for Zulip 5.0 release.
* [`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings),
[`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults):
Added user setting `escape_navigates_to_default_view` to allow users to
[disable the keyboard shortcut](/help/configure-home-view) for the `Esc` key that
[disable the keyboard shortcut](/help/configure-default-view) for the `Esc` key that
navigates the app to the default view.
**Feature level 106**
@@ -1123,7 +754,7 @@ No changes; feature level used for Zulip 5.0 release.
**Feature level 98**
* [`POST /users/me/subscriptions`](/api/subscribe): Added `is_web_public` parameter
* [`POST /subscribe`](/api/subscribe): Added `is_web_public` parameter
for requesting the creation of a web-public stream.
* [`PATCH /streams/{stream_id}`](/api/update-stream): Added
`is_web_public` parameter for converting a stream into a web-public stream.
@@ -1263,7 +894,7 @@ No changes; feature level used for Zulip 5.0 release.
**Feature level 83**
* [`POST /register`](/api/register-queue): The `cross_realm_bots`
* * [`POST /register`](/api/register-queue): The `cross_realm_bots`
section of the response now uses the `is_system_bot` flag to
indicate whether the bot is a system bot.
@@ -1436,7 +1067,7 @@ No changes; feature level used for Zulip 4.0 release.
events to support typing notifications in stream messages. These new
events are only sent to clients with `client_capabilities`
showing support for `stream_typing_notifications`.
* [`POST /typing`](/api/set-typing-status): Added support
* [`POST /set-typing-status`](/api/set-typing-status): Added support
for sending typing notifications for stream messages.
**Feature level 57**
@@ -1516,8 +1147,6 @@ field with an integer field `invite_to_realm_policy`.
* [`GET /events`](/api/get-events): Added new event type `muted_users`
which will be sent to a user when the set of users muted by them has
changed.
* [`POST /register`](/api/register-queue): Added a new `muted_users` field,
which identifies the set of other users the current user has muted.
**Feature level 47**
@@ -1552,7 +1181,7 @@ field with an integer field `invite_to_realm_policy`.
**Feature level 42**
* `PATCH /settings/display`: Added a new `default_view` setting allowing
the user to [set the default view](/help/configure-home-view).
the user to [set the default view](/help/configure-default-view).
**Feature level 41**
@@ -1589,10 +1218,8 @@ field with an integer field `invite_to_realm_policy`.
**Feature level 35**
* [`GET /events`](/api/get-events): The `subscription` events for
`peer_add` and `peer_remove` now include `user_ids` and `stream_ids`
arrays. Previously, these events included singular `user_id` and
`stream_id` integers.
* The peer_add and peer_remove subscription events now have plural
versions of `user_ids` and `stream_ids`.
**Feature level 34**
@@ -1615,7 +1242,7 @@ field with an integer field `invite_to_realm_policy`.
**Feature level 31**
* [`GET /users/me/subscriptions`](/api/get-subscriptions): Added a
* [`GET users/me/subscriptions`](/api/get-subscriptions): Added a
`role` field to Subscription objects representing whether the user
is a stream administrator.
@@ -1628,7 +1255,7 @@ user to be a stream administrator at this feature level.
**Feature level 30**
* [`GET /users/me/subscriptions`](/api/get-subscriptions), [`GET
* [`GET users/me/subscriptions`](/api/get-subscriptions), [`GET
/streams`](/api/get-streams): Added `date_created` to Stream
objects.
* [`POST /users`](/api/create-user), `POST /bots`: The ID of the newly
@@ -1646,9 +1273,8 @@ releases.
**Feature level 26**
* [`GET /messages`](/api/get-messages), [`GET /events`](/api/get-events):
The `sender_short_name` field is no longer included in message objects
returned by these endpoints.
* [`GET /messages`](/api/get-messages): `sender_short_name` field is no
longer included in return values for this endpoint.
* [`GET /messages`](/api/get-messages) : Removed `short_name` field from
`display_recipient` array objects.
@@ -1699,9 +1325,9 @@ No changes; feature level used for Zulip 3.0 release.
**Feature level 19**
* [`GET /events`](/api/get-events): The `subscription` events for
`peer_add` and `peer_remove` now identify the modified
stream by the `stream_id` field, replacing the old `name` field.
* [`GET /events`](/api/get-events): `subscriptions` event with
`op="peer_add"` and `op="peer_remove"` now identify the modified
stream by a `stream_id` field, replacing the old `name` field.
**Feature level 18**
@@ -1710,11 +1336,11 @@ No changes; feature level used for Zulip 3.0 release.
**Feature level 17**
* [`GET /users/me/subscriptions`](/api/get-subscriptions),
* [`GET users/me/subscriptions`](/api/get-subscriptions),
[`GET /streams`](/api/get-streams): Added
`message_retention_days` to Stream objects.
* [`POST /users/me/subscriptions`](/api/subscribe), [`PATCH
/streams/{stream_id}`](/api/update-stream): Added `message_retention_days`
* [`POST users/me/subscriptions`](/api/subscribe), [`PATCH
streams/{stream_id}`](/api/update-stream): Added `message_retention_days`
parameter.
**Feature level 16**
@@ -1732,7 +1358,7 @@ No changes; feature level used for Zulip 3.0 release.
**Feature level 14**
* [`GET /users/me/subscriptions`](/api/get-subscriptions): Removed
* [`GET users/me/subscriptions`](/api/get-subscriptions): Removed
the `is_old_stream` field from Stream objects. This field was
always equivalent to `stream_weekly_traffic != null` on the same object.
@@ -1749,7 +1375,7 @@ No changes; feature level used for Zulip 3.0 release.
**Feature level 12**
* [`GET /users/{user_id}/subscriptions/{stream_id}`](/api/get-subscription-status):
* [`GET users/{user_id}/subscriptions/{stream_id}`](/api/get-subscription-status):
New endpoint added for checking if another user is subscribed to a stream.
**Feature level 11**
@@ -1759,24 +1385,22 @@ No changes; feature level used for Zulip 3.0 release.
time limit before community topic editing is forbidden. A `null`
value means no limit. This was previously hard-coded in the server
as 86400 seconds (1 day).
* [`POST /register`](/api/register-queue): The response now contains
an `is_owner` boolean field, which is similar to the existing
`is_admin` and `is_guest` fields.
* [`POST /typing`](/api/set-typing-status): Removed legacy
support for sending email addresses in the `to` parameter, rather
than user IDs, to encode direct message recipients.
* [`POST /register`](/api/register-queue): The response now contains a
`is_owner`, similar to the existing `is_admin` and `is_guest` fields.
* [`POST /set-typing-status`](/api/set-typing-status): Removed legacy support for sending email
addresses, rather than user IDs, to encode private message recipients.
**Feature level 10**
* [`GET /users/me`](/api/get-own-user): Added `avatar_version`, `is_guest`,
* [`GET users/me`](/api/get-own-user): Added `avatar_version`, `is_guest`,
`is_active`, `timezone`, and `date_joined` fields to the User objects.
* [`GET /users/me`](/api/get-own-user): Removed `client_id` and `short_name`
* [`GET users/me`](/api/get-own-user): Removed `client_id` and `short_name`
from the response to this endpoint. These fields had no purpose and
were inconsistent with other API responses describing users.
**Feature level 9**
* [`POST /users/me/subscriptions`](/api/subscribe), [`DELETE
* [`POST users/me/subscriptions`](/api/subscribe), [`DELETE
/users/me/subscriptions`](/api/unsubscribe): Other users to
subscribe/unsubscribe, declared in the `principals` parameter, can
now be referenced by user_id, rather than Zulip display email
@@ -1865,7 +1489,7 @@ No changes; feature level used for Zulip 3.0 release.
Added `prev_stream` as a potential property of the `edit_history` object
within message objects to indicate when a message was moved to another
stream.
* [`GET /messages/{message_id}/history`](/api/get-message-history):
* [`GET messages/{message_id}/history`](/api/get-message-history):
`prev_stream` is present in `snapshot` objects within `message_history`
object when a message was moved to another stream.
* [`GET /server_settings`](/api/get-server-settings): Added
@@ -1969,7 +1593,7 @@ No changes; feature level used for Zulip 3.0 release.
encoding topics using the `topic` parameter name. The previous
`subject` parameter name was deprecated but is still supported for
backwards-compatibility.
* [`POST /typing`](/api/set-typing-status): Added support for specifying the
* [`POST /set-typing-status`](/api/set-typing-status): Added support for specifying the
recipients with user IDs, deprecating the original API of specifying
them using email addresses.

View File

@@ -65,33 +65,12 @@ filters did.
## Narrows that use IDs
### Message IDs
The `near` and `id` operators, documented in the help center, use message
IDs for their operands.
* `near:12345`: Search messages around the message with ID `12345`.
* `id:12345`: Search for only message with ID `12345`.
The message ID operand for the `id` operator may be encoded as either a
number or a string. The message ID operand for the `near` operator must
be encoded as a string.
**Changes**: Prior to Zulip 8.0 (feature level 194), the message ID
operand for the `id` operator needed to be encoded as a string.
```json
[
{
"operator": "id",
"operand": 12345
}
]
```
### Stream and user IDs
There are a few additional narrow/search options (new in Zulip 2.1)
that use either stream IDs or user IDs that are not documented in the
help center because they are primarily useful to API clients:
@@ -107,8 +86,8 @@ help center because they are primarily useful to API clients:
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,
user 1234, and user 5678, the correct JSON-encoded query is:
messages sent by a user 1234 to a PM thread with yourself, user 1234,
and user 5678, the correct JSON-encoded query is:
```json
[

View File

@@ -36,7 +36,7 @@ Zulip Botserver starts a web server that listens to incoming messages
from your main Zulip server. The sequence of events in a successful
Botserver interaction are:
1. Your bot user is mentioned or receives a direct message:
1. Your bot user is mentioned or receives a private message:
```
@**My Bot User** hello world

View File

@@ -14,9 +14,7 @@
* [Get a message's edit history](/api/get-message-history)
* [Update personal message flags](/api/update-message-flags)
* [Update personal message flags for narrow](/api/update-message-flags-for-narrow)
* [Mark all messages as read](/api/mark-all-as-read)
* [Mark messages in a stream as read](/api/mark-stream-as-read)
* [Mark messages in a topic as read](/api/mark-topic-as-read)
* [Mark messages as read in bulk](/api/mark-all-as-read)
* [Get a message's read receipts](/api/get-read-receipts)
#### Scheduled messages
@@ -47,7 +45,6 @@
* [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)
@@ -95,12 +92,10 @@
* [Add a linkifier](/api/add-linkifier)
* [Update a linkifier](/api/update-linkifier)
* [Remove a linkifier](/api/remove-linkifier)
* [Reorder linkifiers](/api/reorder-linkifiers)
* [Add a code playground](/api/add-code-playground)
* [Remove a code playground](/api/remove-code-playground)
* [Get all custom emoji](/api/get-custom-emoji)
* [Upload custom emoji](/api/upload-custom-emoji)
* [Deactivate custom emoji](/api/deactivate-custom-emoji)
* [Get all custom profile fields](/api/get-custom-profile-fields)
* [Reorder custom profile fields](/api/reorder-custom-profile-fields)
* [Create a custom profile field](/api/create-custom-profile-field)

View File

@@ -155,39 +155,3 @@ 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.
* `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.
[add-bot]: /help/add-a-bot-or-integration
[url-encoder]: https://www.urlencoder.org/

View File

@@ -171,7 +171,7 @@ validate the message and do the following:
* Send a public (stream) message if the `stream` query parameter is
specified in the webhook URL.
* If the `stream` query parameter isn't specified, it will send a direct
* If the `stream` query parameter isn't specified, it will send a private
message to the owner of the webhook bot.
Finally, we return a 200 http status with a JSON format success message via
@@ -429,9 +429,8 @@ 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-an-incoming-webhook.md!}
1. {!create-bot-construct-url.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
@@ -456,7 +455,7 @@ Learn how Zulip integrations work with this simple Hello World example!
```
`{!create-an-incoming-webhook.md!}` and `{!congrats.md!}` are examples of
`{!create-bot-construct-url.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.
@@ -628,8 +627,8 @@ payloads, the absence of such a header usually indicates a configuration
issue, where one either entered the URL for a different integration, or happens to
be running an older version of the integration that doesn't set that header.
If the requisite header is missing, this function sends a direct message to the
owner of the webhook bot, notifying them of the missing header.
If the requisite header is missing, this function sends a PM to the owner of the
webhook bot, notifying them of the missing header.
### Handling unexpected webhook event types

View File

@@ -0,0 +1,89 @@
{generate_api_header(/mark_all_as_read:post)}
## Usage examples
{start_tabs}
{generate_code_example(python)|/mark_all_as_read:post|example}
{generate_code_example(javascript)|/mark_all_as_read:post|example}
{tab|curl}
{generate_code_example(curl)|/mark_all_as_read:post|example}
{end_tabs}
## Parameters
{generate_api_arguments_table|zulip.yaml|/mark_all_as_read:post}
{generate_parameter_description(/mark_all_as_read:post)}
## Response
{generate_response_description(/mark_all_as_read:post)}
#### Example response(s)
{generate_code_example|/mark_all_as_read:post|fixture}
{generate_api_header(/mark_stream_as_read:post)}
## Usage examples
{start_tabs}
{generate_code_example(python)|/mark_stream_as_read:post|example}
{generate_code_example(javascript)|/mark_all_as_read:post|example}
{tab|curl}
{generate_code_example(curl)|/mark_stream_as_read:post|example}
{end_tabs}
## Parameters
{generate_api_arguments_table|zulip.yaml|/mark_stream_as_read:post}
{generate_parameter_description(/mark_all_as_read:post)}
## Response
{generate_response_description(/mark_all_as_read:post)}
#### Example response(s)
{generate_code_example|/mark_stream_as_read:post|fixture}
{generate_api_header(/mark_topic_as_read:post)}
## Usage examples
{start_tabs}
{generate_code_example(python)|/mark_topic_as_read:post|example}
{generate_code_example(javascript)|/mark_all_as_read:post|example}
{tab|curl}
{generate_code_example(curl)|/mark_topic_as_read:post|example}
{end_tabs}
## Parameters
{generate_api_arguments_table|zulip.yaml|/mark_topic_as_read:post}
{generate_parameter_description(/mark_all_as_read:post)}
## Response
{generate_response_description(/mark_all_as_read:post)}
#### Example response(s)
{generate_code_example|/mark_topic_as_read:post|fixture}

View File

@@ -31,7 +31,7 @@ There are currently two ways to trigger an outgoing webhook:
1. **@-mention** the bot user in a stream. If the bot replies, its
reply will be sent to that stream and topic.
2. **Send a direct message** with the bot as one of the recipients.
2. **Send a private message** with the bot as one of the recipients.
If the bot replies, its reply will be sent to that thread.
## Timeouts

View File

@@ -148,7 +148,7 @@ With this API, you *cannot*
* modify an intercepted message (you have to send a new message).
* send messages on behalf of or impersonate other users.
* intercept direct messages (except for direct messages with the bot as an
* intercept private messages (except for PMs with the bot as an
explicit recipient).
### usage

View File

@@ -16,7 +16,6 @@ from django.http import HttpRequest, HttpResponse
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.timezone import now as timezone_now
from typing_extensions import TypeAlias, override
from confirmation import settings as confirmation_settings
from zerver.lib.types import UnspecifiedValue
@@ -56,7 +55,7 @@ def generate_key() -> str:
return b32encode(secrets.token_bytes(15)).decode().lower()
ConfirmationObjT: TypeAlias = Union[
ConfirmationObjT = Union[
MultiuseInvite,
PreregistrationRealm,
PreregistrationUser,
@@ -190,7 +189,6 @@ class Confirmation(models.Model):
class Meta:
unique_together = ("type", "confirmation_key")
@override
def __str__(self) -> str:
return f"{self.content_object!r}"

View File

@@ -1,39 +0,0 @@
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.MONTHLY:
renewal_cents *= 12
# TODO: Decimal stuff
annual_revenue[plan.customer.realm.string_id] = int(renewal_cents / 100)
return annual_revenue

File diff suppressed because it is too large Load Diff

View File

@@ -7,11 +7,12 @@ from django.conf import settings
from corporate.lib.stripe import (
BillingError,
RealmBillingSession,
UpgradeWithExistingPlanError,
ensure_customer_does_not_have_active_plan,
ensure_realm_does_not_have_active_plan,
process_initial_upgrade,
update_or_create_stripe_customer,
)
from corporate.models import CustomerPlan, Event, PaymentIntent, Session
from corporate.models import Event, PaymentIntent, Session
from zerver.models import get_active_user_profile_by_id_in_realm
billing_logger = logging.getLogger("corporate.stripe")
@@ -69,23 +70,19 @@ def handle_checkout_session_completed_event(
session.status = Session.COMPLETED
session.save()
assert isinstance(stripe_session.setup_intent, str)
stripe_setup_intent = stripe.SetupIntent.retrieve(stripe_session.setup_intent)
assert session.customer.realm is not None
assert stripe_session.metadata is not None
user_id = stripe_session.metadata.get("user_id")
assert user_id is not None
user = get_active_user_profile_by_id_in_realm(int(user_id), session.customer.realm)
billing_session = RealmBillingSession(user)
user = get_active_user_profile_by_id_in_realm(user_id, session.customer.realm)
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,
]:
ensure_customer_does_not_have_active_plan(session.customer)
billing_session.update_or_create_stripe_customer(payment_method)
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 = ()
@@ -100,18 +97,18 @@ def handle_checkout_session_completed_event(
Session.FREE_TRIAL_UPGRADE_FROM_BILLING_PAGE,
Session.FREE_TRIAL_UPGRADE_FROM_ONBOARDING_PAGE,
]:
ensure_customer_does_not_have_active_plan(session.customer)
billing_session.update_or_create_stripe_customer(payment_method)
billing_session.process_initial_upgrade(
CustomerPlan.STANDARD,
int(stripe_session.metadata["licenses"]),
stripe_session.metadata["license_management"] == "automatic",
int(stripe_session.metadata["billing_schedule"]),
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]:
billing_session.update_or_create_stripe_customer(payment_method)
update_or_create_stripe_customer(user, payment_method)
@error_handler
@@ -127,10 +124,7 @@ def handle_payment_intent_succeeded_event(
user = get_active_user_profile_by_id_in_realm(user_id, payment_intent.customer.realm)
description = ""
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
for charge in stripe_payment_intent.charges:
description = f"Payment (Card ending in {charge.payment_method_details.card.last4})"
break
@@ -142,21 +136,20 @@ def handle_payment_intent_succeeded_event(
discountable=False,
)
try:
ensure_customer_does_not_have_active_plan(payment_intent.customer)
ensure_realm_does_not_have_active_plan(user.realm)
except UpgradeWithExistingPlanError as e:
stripe_invoice = stripe.Invoice.create(
auto_advance=True,
collection_method="charge_automatically",
customer=stripe_payment_intent.customer,
days_until_due=None,
statement_descriptor="Cloud Standard Credit",
statement_descriptor="Zulip Cloud Standard Credit",
)
stripe.Invoice.finalize_invoice(stripe_invoice)
raise e
billing_session = RealmBillingSession(user)
billing_session.process_initial_upgrade(
CustomerPlan.STANDARD,
process_initial_upgrade(
user,
int(metadata["licenses"]),
metadata["license_management"] == "automatic",
int(metadata["billing_schedule"]),
@@ -169,7 +162,6 @@ def handle_payment_intent_succeeded_event(
def handle_payment_intent_payment_failed_event(
stripe_payment_intent: stripe.PaymentIntent, payment_intent: PaymentIntent
) -> None:
assert stripe_payment_intent.last_payment_error is not None
payment_intent.status = PaymentIntent.get_status_integer_from_status_text(
stripe_payment_intent.status
)

View File

@@ -1,13 +1,9 @@
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 corporate.lib.stripe import RealmBillingSession
from corporate.models import get_customer_by_realm
from zerver.models import Realm, UserProfile, get_realm
from zerver.models import Realm, get_realm
def get_support_url(realm: Realm) -> str:
@@ -17,34 +13,3 @@ def get_support_url(realm: Realm) -> str:
urlunsplit(("", "", reverse("support"), urlencode({"q": realm.string_id}), "")),
)
return support_url
def get_discount_for_realm(realm: Realm) -> Optional[Decimal]:
customer = get_customer_by_realm(realm)
if customer is not None:
return customer.default_discount
return None
def attach_discount_to_realm(realm: Realm, discount: Decimal, *, acting_user: UserProfile) -> None:
billing_session = RealmBillingSession(acting_user, realm)
billing_session.attach_discount_to_customer(discount)
def approve_realm_sponsorship(realm: Realm, *, acting_user: UserProfile) -> None:
billing_session = RealmBillingSession(acting_user, realm)
billing_session.approve_sponsorship()
def update_realm_sponsorship_status(
realm: Realm, sponsorship_pending: bool, *, acting_user: UserProfile
) -> None:
billing_session = RealmBillingSession(acting_user, realm)
billing_session.update_customer_sponsorship_status(sponsorship_pending)
def update_realm_billing_method(
realm: Realm, charge_automatically: bool, *, acting_user: UserProfile
) -> None:
billing_session = RealmBillingSession(acting_user, realm)
billing_session.update_billing_method_of_current_plan(charge_automatically)

View File

@@ -1,24 +0,0 @@
# Generated by Django 4.2.1 on 2023-05-30 03:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"corporate",
"0017_rename_exempt_from_from_license_number_check_customer_exempt_from_license_number_check",
),
]
operations = [
migrations.AddConstraint(
model_name="customer",
constraint=models.CheckConstraint(
check=models.Q(
("realm__isnull", False), ("remote_server__isnull", False), _connector="XOR"
),
name="cloud_xor_self_hosted",
),
),
]

View File

@@ -1,27 +0,0 @@
# 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

@@ -3,8 +3,7 @@ from typing import Any, Dict, Optional, Union
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import CASCADE, Q
from typing_extensions import override
from django.db.models import CASCADE
from zerver.models import Realm, UserProfile
from zilencer.models import RemoteZulipServer
@@ -29,20 +28,22 @@ class Customer(models.Model):
# they purchased.
exempt_from_license_number_check = models.BooleanField(default=False)
class Meta:
constraints = [
models.CheckConstraint(
check=Q(realm__isnull=False) ^ Q(remote_server__isnull=False),
name="cloud_xor_self_hosted",
)
]
@override
def __str__(self) -> str:
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})"
return f"{self.realm!r} {self.stripe_customer_id}"
@property
def is_self_hosted(self) -> bool:
is_self_hosted = self.remote_server is not None
if is_self_hosted:
assert self.realm is None
return is_self_hosted
@property
def is_cloud(self) -> bool:
is_cloud = self.realm is not None
if is_cloud:
assert self.remote_server is None
return is_cloud
def get_customer_by_realm(realm: Realm) -> Optional[Customer]:
@@ -216,10 +217,6 @@ class CustomerPlan(models.Model):
ANNUAL = 1
MONTHLY = 2
BILLING_SCHEDULES = {
ANNUAL: "Annual",
MONTHLY: "Monthly",
}
billing_schedule = models.SmallIntegerField()
# The next date the billing system should go through ledger
@@ -360,6 +357,3 @@ 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="")

View File

@@ -21,7 +21,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"calculated_statement_descriptor": "ZULIP STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -30,7 +30,6 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -63,10 +62,9 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -74,30 +72,13 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"exp_month": 3,
"exp_year": 2033,
"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
},
@@ -105,7 +86,7 @@
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKLmktKoGMgYrcqQJ_XQ6LBZLSpoFwx3AcKTCrmps2vSpTYEuUPpKK37EcqScvxgnj4SR9xDWiSPE9-Ni",
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
"refunded": false,
"refunds": {
"data": [],

View File

@@ -21,7 +21,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"calculated_statement_descriptor": "ZULIP STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -30,7 +30,6 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -63,10 +62,9 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_details": {
"card": {
"amount_authorized": 36000,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -74,30 +72,13 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"exp_month": 3,
"exp_year": 2033,
"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
},
@@ -105,7 +86,7 @@
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKMyktKoGMgY7jXPALx46LBZ93Aicbpv_SAI_VTk6csq3v1o-6Isr2CHYie17adWgts9dL_uZ8bLzxthw",
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000002/rcpt_NORMALIZED000000000000000000002",
"refunded": false,
"refunds": {
"data": [],
@@ -145,7 +126,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"calculated_statement_descriptor": "ZULIP STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -154,7 +135,6 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -187,10 +167,9 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -198,30 +177,13 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"exp_month": 3,
"exp_year": 2033,
"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
},
@@ -229,7 +191,7 @@
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKMyktKoGMgYMbX-GnOM6LBagBjW6OMx9HAU5uMfOZ8snKne1PHYDc42XAxy7IdyNE7BH38b7vW-rIqLk",
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
"refunded": false,
"refunds": {
"data": [],

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
{
"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

@@ -5,83 +5,189 @@
"created": 1000000000,
"data": {
"object": {
"amount": 6400,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 0,
"application": null,
"account_country": "US",
"account_name": "NORMALIZED-1",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": 1000000000,
"cancellation_reason": "void_invoice",
"capture_method": "automatic",
"charges": {
"data": [],
"has_more": false,
"object": "list",
"total_count": 0,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000003"
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"automatic_tax": {
"enabled": false,
"status": null
},
"client_secret": "pi_NORMALIZED00000000000003_secret_fYThlYzmA6ZihKmrZLnmWcTvW",
"confirmation_method": "automatic",
"billing_reason": "manual",
"charge": null,
"collection_method": "charge_automatically",
"created": 1000000000,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0002",
"description": "Payment for Invoice",
"id": "pi_NORMALIZED00000000000003",
"invoice": "in_NORMALIZED00000000000001",
"last_payment_error": null,
"latest_charge": null,
"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_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"
},
"livemode": false,
"metadata": {},
"next_action": null,
"object": "payment_intent",
"next_payment_attempt": null,
"number": "NORMALI-0001",
"object": "invoice",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
},
"cashapp": {},
"wechat_pay": {
"app_id": null,
"client": null
}
"paid": true,
"payment_intent": null,
"payment_settings": {
"payment_method_options": null,
"payment_method_types": null
},
"payment_method_types": [
"ach_credit_transfer",
"card",
"cashapp",
"wechat_pay"
],
"processing": null,
"receipt_email": "king@lear.org",
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": 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",
"statement_descriptor_suffix": null,
"status": "canceled",
"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,
"transfer_group": null
"webhooks_delivered_at": null
}
},
"id": "evt_3OAbimDEQaroqDjs0BaEBsNX",
"id": "evt_1K2OXlHSaWXyvFpKIjChqmtl",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0001",
"idempotency_key": "25effa1f-8fed-45ea-a660-fd0b33abc23c"
"idempotency_key": "cae8e48c-5622-4bdf-85a2-cb06a7ed12d4"
},
"type": "payment_intent.canceled"
"type": "invoice.payment_succeeded"
}
],
"has_more": true,

View File

@@ -7,9 +7,6 @@
"object": {
"amount": 7200,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 7200,
"application": null,
"application_fee_amount": null,
@@ -40,7 +37,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"calculated_statement_descriptor": "ZULIP STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -49,7 +46,6 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -82,10 +78,9 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -93,30 +88,13 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"exp_month": 3,
"exp_year": 2033,
"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
},
@@ -124,7 +102,7 @@
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKLKktKoGMgZfETNIDN86LBY50xSk957U6AlRoP5-YOxSRkjaDyWZxvhpX5Lq7EfiLZlknEWX_xe2Db-Q",
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
"refunded": false,
"refunds": {
"data": [],
@@ -149,7 +127,7 @@
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000001"
},
"client_secret": "pi_NORMALIZED00000000000001_secret_U9fLWaQ5foMLX6li0E7nIl4wX",
"client_secret": "pi_NORMALIZED00000000000001_secret_fD7F9AdDLLYQt94Ii4rEflkYg",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -158,7 +136,6 @@
"id": "pi_NORMALIZED00000000000001",
"invoice": null,
"last_payment_error": null,
"latest_charge": "ch_NORMALIZED00000000000001",
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
@@ -176,12 +153,10 @@
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -189,7 +164,6 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
@@ -202,13 +176,13 @@
"transfer_group": null
}
},
"id": "evt_3OAbiwDEQaroqDjs1KG0PpkK",
"id": "evt_3K2OXoHSaWXyvFpK1IgAmGCx",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0002",
"idempotency_key": "a6fbc05f-71a7-41dd-89d9-d83491088de5"
"idempotency_key": "bbd6840b-375d-408a-a4b2-0353118fef83"
},
"type": "payment_intent.succeeded"
},
@@ -237,7 +211,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"calculated_statement_descriptor": "ZULIP STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -246,7 +220,6 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -279,10 +252,9 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -290,30 +262,13 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"exp_month": 3,
"exp_year": 2033,
"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
},
@@ -321,7 +276,7 @@
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKLKktKoGMgYmLI2wbBc6LBayDyiusyUGbOxDSVFnS4jnIBbaP5HhJCOa-jwmTSISDTAtkNQlQs2ntZKo",
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
"refunded": false,
"refunds": {
"data": [],
@@ -341,13 +296,13 @@
"transfer_group": null
}
},
"id": "evt_3OAbiwDEQaroqDjs1MgkdMZv",
"id": "evt_3K2OXoHSaWXyvFpK1BCXNPsv",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0002",
"idempotency_key": "a6fbc05f-71a7-41dd-89d9-d83491088de5"
"idempotency_key": "bbd6840b-375d-408a-a4b2-0353118fef83"
},
"type": "charge.succeeded"
},
@@ -360,7 +315,6 @@
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
@@ -370,9 +324,8 @@
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"footer": null,
"rendering_options": null
"default_payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"footer": null
},
"livemode": false,
"metadata": {
@@ -385,8 +338,7 @@
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
"tax_exempt": "none"
},
"previous_attributes": {
"invoice_settings": {
@@ -394,13 +346,13 @@
}
}
},
"id": "evt_1OAbj2DEQaroqDjsdHXrdECt",
"id": "evt_1K2OXrHSaWXyvFpKTft9z8iM",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0003",
"idempotency_key": "ba127619-d57d-424c-bc0d-e1b5d6e75b4f"
"idempotency_key": "41855f8d-7eaf-45a3-8f4b-ebf23c527d2a"
},
"type": "customer.updated"
},
@@ -410,28 +362,35 @@
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbiyDEQaroqDjsEnpR1Onp_secret_OyYqFrkey3OlMfxWtyreL2AbO9RHVB4",
"client_secret": "seti_1K2OXpHSaWXyvFpKOq6F3F9K_secret_KhoAzpsEjV8G4oAeYDSFmGYMKv5BRkc",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbiyDEQaroqDjsEnpR1Onp",
"id": "seti_1K2OXpHSaWXyvFpKOq6F3F9K",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbiyDEQaroqDjsnLExjW56",
"latest_attempt": "setatt_1K2OXpHSaWXyvFpKcauo7Bx8",
"livemode": false,
"mandate": null,
"metadata": {},
"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_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
@@ -443,13 +402,13 @@
"usage": "off_session"
}
},
"id": "evt_1OAbizDEQaroqDjsMSKWCeS4",
"id": "evt_1K2OXqHSaWXyvFpKyccpbqqf",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "b367784c-9178-4e50-ad4b-33271f44771f"
"idempotency_key": "8c0e4f10-2995-45d3-9b35-aa76865e3557"
},
"type": "setup_intent.succeeded"
},
@@ -479,8 +438,8 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"exp_month": 3,
"exp_year": 2033,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
@@ -498,20 +457,20 @@
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"id": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
}
},
"id": "evt_1OAbizDEQaroqDjs7C1ESMag",
"id": "evt_1K2OXqHSaWXyvFpKUpLnWMHc",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "b367784c-9178-4e50-ad4b-33271f44771f"
"idempotency_key": "8c0e4f10-2995-45d3-9b35-aa76865e3557"
},
"type": "payment_method.attached"
},
@@ -521,28 +480,35 @@
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbiyDEQaroqDjsEnpR1Onp_secret_OyYqFrkey3OlMfxWtyreL2AbO9RHVB4",
"client_secret": "seti_1K2OXpHSaWXyvFpKOq6F3F9K_secret_KhoAzpsEjV8G4oAeYDSFmGYMKv5BRkc",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbiyDEQaroqDjsEnpR1Onp",
"id": "seti_1K2OXpHSaWXyvFpKOq6F3F9K",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbiyDEQaroqDjsnLExjW56",
"latest_attempt": "setatt_1K2OXpHSaWXyvFpKcauo7Bx8",
"livemode": false,
"mandate": null,
"metadata": {},
"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_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
@@ -554,13 +520,13 @@
"usage": "off_session"
}
},
"id": "evt_1OAbizDEQaroqDjs12Wkj59b",
"id": "evt_1K2OXqHSaWXyvFpKZ0zBpGzN",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0004",
"idempotency_key": "b367784c-9178-4e50-ad4b-33271f44771f"
"idempotency_key": "8c0e4f10-2995-45d3-9b35-aa76865e3557"
},
"type": "setup_intent.created"
},
@@ -570,28 +536,35 @@
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbixDEQaroqDjsZ13bFTzk_secret_OyYqTKT3uNo6hXb6nycNN82HUV4tLVa",
"client_secret": "seti_1K2OXoHSaWXyvFpKLyy5ns16_secret_KhoANgFdO2YICL1Urfnax58nGUY0MeV",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbixDEQaroqDjsZ13bFTzk",
"id": "seti_1K2OXoHSaWXyvFpKLyy5ns16",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,
"mandate": null,
"metadata": {},
"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_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
@@ -603,13 +576,13 @@
"usage": "off_session"
}
},
"id": "evt_1OAbixDEQaroqDjswQf5925l",
"id": "evt_1K2OXoHSaWXyvFpKm8uayD4o",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0005",
"idempotency_key": "2f531396-87a0-4a7a-85f2-4cbeae89359b"
"idempotency_key": "1eca5c75-5177-4abb-94c1-4e6c39916bef"
},
"type": "setup_intent.created"
},
@@ -620,9 +593,6 @@
"object": {
"amount": 7200,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 0,
"application": null,
"application_fee_amount": null,
@@ -637,7 +607,7 @@
"total_count": 0,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000001"
},
"client_secret": "pi_NORMALIZED00000000000001_secret_U9fLWaQ5foMLX6li0E7nIl4wX",
"client_secret": "pi_NORMALIZED00000000000001_secret_fD7F9AdDLLYQt94Ii4rEflkYg",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -646,7 +616,6 @@
"id": "pi_NORMALIZED00000000000001",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
@@ -665,11 +634,9 @@
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -677,7 +644,6 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
@@ -690,13 +656,13 @@
"transfer_group": null
}
},
"id": "evt_3OAbiwDEQaroqDjs19zAFXo5",
"id": "evt_3K2OXoHSaWXyvFpK1tATyd2u",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0006",
"idempotency_key": "dcb3c560-eb12-48b6-a8b1-c3781dc8dc47"
"idempotency_key": "d6ea8ee3-361c-451e-a3d7-e841957c3d25"
},
"type": "payment_intent.created"
},
@@ -709,7 +675,6 @@
"balance": 0,
"created": 1000000000,
"currency": null,
"default_currency": null,
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
@@ -720,8 +685,7 @@
"invoice_settings": {
"custom_fields": null,
"default_payment_method": null,
"footer": null,
"rendering_options": null
"footer": null
},
"livemode": false,
"metadata": {
@@ -734,17 +698,16 @@
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
"tax_exempt": "none"
}
},
"id": "evt_1OAbivDEQaroqDjsBYwBWKGq",
"id": "evt_1K2OXnHSaWXyvFpKsZOF5R1j",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0007",
"idempotency_key": "d7e3039e-deb3-4b01-b0d4-cb3365326a0e"
"idempotency_key": "d4faf9c7-d357-4870-b964-b1d993c5c058"
},
"type": "customer.created"
}

View File

@@ -6,13 +6,11 @@
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,
@@ -42,27 +40,23 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF?s=ap",
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF/pdf?s=ap",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"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",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -76,14 +70,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -93,26 +86,21 @@
"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"
"type": "invoiceitem"
},
{
"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",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -126,14 +114,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -143,15 +130,11 @@
"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"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -162,14 +145,12 @@
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0001",
"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
},
@@ -179,15 +160,6 @@
"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",
@@ -198,258 +170,23 @@
"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
}
},
"id": "evt_1OAbjADEQaroqDjswTbvBGJ7",
"livemode": false,
"object": "event",
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "da0bb2e0-e63b-4ef2-8190-e4a898a67895"
},
"type": "invoice.finalized"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_tax_ids": null,
"amount_due": 0,
"amount_paid": 0,
"amount_remaining": 0,
"amount_shipping": 0,
"application": null,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": true,
"auto_advance": false,
"automatic_tax": {
"enabled": false,
"status": null
},
"billing_reason": "manual",
"charge": null,
"collection_method": "charge_automatically",
"created": 1000000000,
"currency": "usd",
"custom_fields": null,
"customer": "cus_NORMALIZED0001",
"customer_address": null,
"customer_email": "hamlet@zulip.com",
"customer_name": null,
"customer_phone": null,
"customer_shipping": null,
"customer_tax_exempt": "none",
"customer_tax_ids": [],
"default_payment_method": null,
"default_source": null,
"default_tax_rates": [],
"description": null,
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUx0200otAxmBG3?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUx0200otAxmBG3/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 7200,
"amount_excluding_tax": 7200,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 1200,
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "1200"
},
{
"amount": -7200,
"amount_excluding_tax": -7200,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-7200"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0001",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
"period_end": 1000000000,
"period_start": 1000000000,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "letter"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "paid",
"status_transitions": {
"finalized_at": 1000000000,
"marked_uncollectible_at": null,
"paid_at": 1000000000,
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
},
"previous_attributes": {
"attempted": false,
"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,
@@ -457,13 +194,13 @@
}
}
},
"id": "evt_1OAbjADEQaroqDjsSI8lFXJv",
"id": "evt_1K2OXvHSaWXyvFpKAxZLQePJ",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "da0bb2e0-e63b-4ef2-8190-e4a898a67895"
"idempotency_key": "f3d64bf5-ddf0-4933-86ee-96e9f6aa7149"
},
"type": "invoice.updated"
},
@@ -473,13 +210,11 @@
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,
@@ -509,27 +244,23 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": null,
"ending_balance": null,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000002",
"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_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -543,14 +274,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -560,26 +290,21 @@
"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"
"type": "invoiceitem"
},
{
"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",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -593,14 +318,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -610,15 +334,11 @@
"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"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -633,10 +353,8 @@
"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
},
@@ -646,15 +364,6 @@
"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",
@@ -665,31 +374,88 @@
"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_1OAbj8DEQaroqDjskFTdkB1y",
"id": "evt_1K2OXvHSaWXyvFpKykfdKZvo",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0009",
"idempotency_key": "54d61ab3-cd30-4b7a-ad40-0dce334dd232"
"idempotency_key": "6f6f6557-9e62-418d-ac7f-8a0a1f602c00"
},
"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,
@@ -702,7 +468,70 @@
"description": "Zulip Cloud Standard",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000001",
"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",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -717,14 +546,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -737,18 +565,17 @@
"quantity": 6,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": 1200,
"unit_amount_decimal": "1200"
}
},
"id": "evt_1OAbj7DEQaroqDjs4SWJFkYg",
"id": "evt_1K2OXuHSaWXyvFpK7gSvsu8e",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0010",
"idempotency_key": "2ae230b6-12fb-4c3a-b8b7-6ecd041a313c"
"idempotency_key": "b60066b7-cf3e-4569-aa2e-8050562cedb4"
},
"type": "invoiceitem.created"
},
@@ -764,7 +591,7 @@
"description": "Payment (Card ending in 4242)",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000002",
"id": "ii_NORMALIZED00000000000004",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -779,14 +606,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -799,18 +625,17 @@
"quantity": 1,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
}
},
"id": "evt_1OAbj5DEQaroqDjsNe3rtI3x",
"id": "evt_1K2OXuHSaWXyvFpK4K7m30wK",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0011",
"idempotency_key": "22eeb342-33df-4bcb-8750-7c8649ef72e3"
"idempotency_key": "cd6054f5-d831-4874-9ed9-d7b0e3b24336"
},
"type": "invoiceitem.created"
},
@@ -823,7 +648,6 @@
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
@@ -833,9 +657,8 @@
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"footer": null,
"rendering_options": null
"default_payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"footer": null
},
"livemode": false,
"metadata": {
@@ -848,21 +671,19 @@
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
"tax_exempt": "none"
},
"previous_attributes": {
"currency": null,
"default_currency": null
"currency": null
}
},
"id": "evt_1OAbj5DEQaroqDjsGpvAlLYX",
"id": "evt_1K2OXtHSaWXyvFpKaZ6mOKiF",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0011",
"idempotency_key": "22eeb342-33df-4bcb-8750-7c8649ef72e3"
"idempotency_key": "cd6054f5-d831-4874-9ed9-d7b0e3b24336"
},
"type": "customer.updated"
}

View File

@@ -6,13 +6,11 @@
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,
@@ -42,27 +40,23 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF?s=ap",
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF/pdf?s=ap",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"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",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -76,14 +70,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -93,26 +86,21 @@
"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"
"type": "invoiceitem"
},
{
"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",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -126,14 +114,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -143,15 +130,11 @@
"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"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -162,14 +145,12 @@
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"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
},
@@ -179,15 +160,6 @@
"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",
@@ -198,28 +170,22 @@
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbjADEQaroqDjsRFLyV68r",
"id": "evt_1K2OXwHSaWXyvFpKr5uez7KF",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "da0bb2e0-e63b-4ef2-8190-e4a898a67895"
"idempotency_key": "f3d64bf5-ddf0-4933-86ee-96e9f6aa7149"
},
"type": "invoice.payment_succeeded"
},
@@ -229,13 +195,11 @@
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,
@@ -265,27 +229,23 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF?s=ap",
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF/pdf?s=ap",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"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",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -299,14 +259,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -316,26 +275,21 @@
"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"
"type": "invoiceitem"
},
{
"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",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -349,14 +303,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -366,15 +319,11 @@
"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"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -385,14 +334,12 @@
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"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
},
@@ -402,15 +349,6 @@
"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",
@@ -421,30 +359,213 @@
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbjADEQaroqDjsHfm4lKFu",
"id": "evt_1K2OXvHSaWXyvFpKbgniTOEt",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "da0bb2e0-e63b-4ef2-8190-e4a898a67895"
"idempotency_key": "f3d64bf5-ddf0-4933-86ee-96e9f6aa7149"
},
"type": "invoice.paid"
},
{
"api_version": "2020-08-27",
"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",
"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,
"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"
},
"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
},
"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
}
},
"id": "evt_1K2OXvHSaWXyvFpKPFo4dPYw",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "f3d64bf5-ddf0-4933-86ee-96e9f6aa7149"
},
"type": "invoice.finalized"
}
],
"has_more": false,

View File

@@ -6,13 +6,11 @@
"data": {
"object": {
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,
@@ -42,27 +40,23 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF?s=ap",
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUy0200wEanKwxF/pdf?s=ap",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"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",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -76,14 +70,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -93,26 +86,21 @@
"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"
"type": "invoiceitem"
},
{
"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",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -126,14 +114,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -143,15 +130,11 @@
"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"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -162,14 +145,12 @@
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"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
},
@@ -179,15 +160,6 @@
"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",
@@ -198,28 +170,22 @@
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}
},
"id": "evt_1OAbjADEQaroqDjsRFLyV68r",
"id": "evt_1K2OXwHSaWXyvFpKr5uez7KF",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0008",
"idempotency_key": "da0bb2e0-e63b-4ef2-8190-e4a898a67895"
"idempotency_key": "f3d64bf5-ddf0-4933-86ee-96e9f6aa7149"
},
"type": "invoice.payment_succeeded"
}

View File

@@ -7,9 +7,6 @@
"object": {
"amount": 36000,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 36000,
"application": null,
"application_fee_amount": null,
@@ -40,7 +37,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"calculated_statement_descriptor": "ZULIP STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -49,7 +46,6 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -82,10 +78,9 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_details": {
"card": {
"amount_authorized": 36000,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -93,30 +88,13 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"exp_month": 3,
"exp_year": 2033,
"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
},
@@ -124,7 +102,7 @@
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKMWktKoGMgbBejqty7k6LBay1D-S4hg39Q9UDIOGUp79dAR3ZWzowgod2EPK3yoyXY0CYXWsROxc7cuw",
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000002/rcpt_NORMALIZED000000000000000000002",
"refunded": false,
"refunds": {
"data": [],
@@ -149,7 +127,7 @@
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_IM9QK0XPBRsrtvMFpF1HVQGr3",
"client_secret": "pi_NORMALIZED00000000000002_secret_KPTN4Jk0sR9DrnVymxUmd0o0F",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -158,7 +136,6 @@
"id": "pi_NORMALIZED00000000000002",
"invoice": null,
"last_payment_error": null,
"latest_charge": "ch_NORMALIZED00000000000002",
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
@@ -176,12 +153,10 @@
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -189,7 +164,6 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
@@ -202,13 +176,13 @@
"transfer_group": null
}
},
"id": "evt_3OAbjEDEQaroqDjs0Fdo8Gmm",
"id": "evt_3K2OXxHSaWXyvFpK152vEHml",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"pending_webhooks": 2,
"request": {
"id": "req_NORMALIZED0012",
"idempotency_key": "ead047f4-390c-4143-bca4-b560cb2c76af"
"idempotency_key": "5169e60e-793c-4161-9873-1ba5c523737f"
},
"type": "payment_intent.succeeded"
},
@@ -217,42 +191,67 @@
"created": 1000000000,
"data": {
"object": {
"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,
"address": null,
"balance": 0,
"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,
"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": {
"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",
@@ -266,172 +265,12 @@
"user_email": "hamlet@zulip.com",
"user_id": "10"
},
"object": "charge",
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 0,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method_details": {
"card": {
"amount_authorized": 36000,
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"incremental_authorization": {
"status": "unavailable"
},
"installments": null,
"last4": "4242",
"mandate": null,
"multicapture": {
"status": "unavailable"
},
"network": "visa",
"network_token": {
"used": false
},
"overcapture": {
"maximum_amount_capturable": 36000,
"status": "unavailable"
},
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKMWktKoGMgYpVepO4vY6LBbu8Gdq5swMUKpN609lyeB_YsbAnOp7XAL-DHUMj7-nEPl0juPLx6Hv11vd",
"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
}
},
"id": "evt_3OAbjEDEQaroqDjs0Jr4G9pD",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0012",
"idempotency_key": "ead047f4-390c-4143-bca4-b560cb2c76af"
},
"type": "charge.succeeded"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"address": null,
"balance": 0,
"created": 1000000000,
"currency": "usd",
"default_currency": "usd",
"default_source": null,
"delinquent": false,
"description": "zulip (Zulip Dev)",
"discount": null,
"email": "hamlet@zulip.com",
"id": "cus_NORMALIZED0001",
"invoice_prefix": "NORMA01",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"footer": null,
"rendering_options": null
},
"livemode": false,
"metadata": {
"realm_id": "1",
"realm_str": "zulip"
},
"name": null,
"next_invoice_sequence": 2,
"object": "customer",
"phone": null,
"preferred_locales": [],
"shipping": null,
"tax_exempt": "none",
"test_clock": null
},
"previous_attributes": {
"invoice_settings": {
"default_payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ"
}
}
},
"id": "evt_1OAbjJDEQaroqDjsiVYBYR7z",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0013",
"idempotency_key": "4ebda7fb-5c3a-4ce9-8abc-9cade78757d3"
},
"type": "customer.updated"
},
{
"api_version": "2020-08-27",
"created": 1000000000,
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjHDEQaroqDjsiyGUfkGg_secret_OyYq1ljcbx3AZiCSK143sZdt7S0n6pN",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjHDEQaroqDjsiyGUfkGg",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbjHDEQaroqDjswrjSFGn9",
"livemode": false,
"mandate": null,
"metadata": {},
"next_action": null,
"object": "setup_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
@@ -443,13 +282,13 @@
"usage": "off_session"
}
},
"id": "evt_1OAbjIDEQaroqDjsAq5yUHSA",
"id": "evt_1K2OY0HSaWXyvFpKOGS3PDsC",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0014",
"idempotency_key": "703bb85b-e5cd-4cde-9b55-b712437a5113"
"idempotency_key": "415ae25a-0c46-48f9-8299-d873f9fc51f1"
},
"type": "setup_intent.succeeded"
},
@@ -479,8 +318,8 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"exp_month": 3,
"exp_year": 2033,
"fingerprint": "NORMALIZED000001",
"funding": "credit",
"generated_from": null,
@@ -498,20 +337,20 @@
},
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"id": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"id": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"livemode": false,
"metadata": {},
"object": "payment_method",
"type": "card"
}
},
"id": "evt_1OAbjHDEQaroqDjs5X4eBOmU",
"id": "evt_1K2OY0HSaWXyvFpKHdeiTg2r",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0014",
"idempotency_key": "703bb85b-e5cd-4cde-9b55-b712437a5113"
"idempotency_key": "415ae25a-0c46-48f9-8299-d873f9fc51f1"
},
"type": "payment_method.attached"
},
@@ -521,28 +360,35 @@
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjHDEQaroqDjsiyGUfkGg_secret_OyYq1ljcbx3AZiCSK143sZdt7S0n6pN",
"client_secret": "seti_1K2OXzHSaWXyvFpKzkPjM4k7_secret_KhoALa8Xt6eK1ATCOPGurJLxmo2y6xh",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjHDEQaroqDjsiyGUfkGg",
"id": "seti_1K2OXzHSaWXyvFpKzkPjM4k7",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbjHDEQaroqDjswrjSFGn9",
"latest_attempt": "setatt_1K2OXzHSaWXyvFpKG0BonJhW",
"livemode": false,
"mandate": null,
"metadata": {},
"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_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
@@ -554,13 +400,13 @@
"usage": "off_session"
}
},
"id": "evt_1OAbjHDEQaroqDjsj1Jcj4L7",
"id": "evt_1K2OY0HSaWXyvFpKuYHMv2vM",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0014",
"idempotency_key": "703bb85b-e5cd-4cde-9b55-b712437a5113"
"idempotency_key": "415ae25a-0c46-48f9-8299-d873f9fc51f1"
},
"type": "setup_intent.created"
},
@@ -570,28 +416,35 @@
"data": {
"object": {
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjFDEQaroqDjsOtWXOh3z_secret_OyYqDwZSEotDZcPqC8EEXDlXcWpwH0i",
"client_secret": "seti_1K2OXyHSaWXyvFpKvIhWI0JW_secret_KhoAlunPkSHQPGu7zyaTpT81wBUPhqc",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjFDEQaroqDjsOtWXOh3z",
"id": "seti_1K2OXyHSaWXyvFpKvIhWI0JW",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,
"mandate": null,
"metadata": {},
"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_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
@@ -603,13 +456,13 @@
"usage": "off_session"
}
},
"id": "evt_1OAbjFDEQaroqDjslF0bckh4",
"id": "evt_1K2OXyHSaWXyvFpK5sw7382A",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0015",
"idempotency_key": "ab40363f-ed16-49a3-abcb-e0050fe97641"
"idempotency_key": "de8b28ae-b3f1-416b-bae0-43f9af477f33"
},
"type": "setup_intent.created"
},
@@ -620,9 +473,6 @@
"object": {
"amount": 36000,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 0,
"application": null,
"application_fee_amount": null,
@@ -637,7 +487,7 @@
"total_count": 0,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_IM9QK0XPBRsrtvMFpF1HVQGr3",
"client_secret": "pi_NORMALIZED00000000000002_secret_KPTN4Jk0sR9DrnVymxUmd0o0F",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -646,7 +496,6 @@
"id": "pi_NORMALIZED00000000000002",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
@@ -665,11 +514,9 @@
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -677,7 +524,6 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,
@@ -690,13 +536,13 @@
"transfer_group": null
}
},
"id": "evt_3OAbjEDEQaroqDjs04u0RIvp",
"id": "evt_3K2OXxHSaWXyvFpK1GA2kN6r",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0016",
"idempotency_key": "5e88c770-f3aa-4e2f-83fd-fa3bfb251535"
"idempotency_key": "d8d89509-f2c5-433c-b8ca-1ae78f192ee5"
},
"type": "payment_intent.created"
}

View File

@@ -1,5 +1,384 @@
{
"data": [],
"data": [
{
"api_version": "2020-08-27",
"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",
"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_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"last_finalization_error": null,
"lines": {
"data": [
{
"amount": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"livemode": false,
"metadata": {},
"object": "line_item",
"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_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
},
{
"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": null
}
},
"id": "evt_1K2OY5HSaWXyvFpK4UZiVK2B",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0017",
"idempotency_key": "c3e3b8e3-19f0-4bab-865b-d2d621dd254b"
},
"type": "invoice.payment_succeeded"
},
{
"api_version": "2020-08-27",
"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",
"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_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8",
"id": "in_NORMALIZED00000000000003",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"last_finalization_error": null,
"lines": {
"data": [
{
"amount": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"livemode": false,
"metadata": {},
"object": "line_item",
"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_amounts": [],
"tax_rates": [],
"type": "invoiceitem"
},
{
"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": null
}
},
"id": "evt_1K2OY5HSaWXyvFpKy72Pl7GJ",
"livemode": false,
"object": "event",
"pending_webhooks": 0,
"request": {
"id": "req_NORMALIZED0017",
"idempotency_key": "c3e3b8e3-19f0-4bab-865b-d2d621dd254b"
},
"type": "invoice.paid"
}
],
"has_more": false,
"object": "list",
"url": "/v1/events"

View File

@@ -1,12 +1,10 @@
{
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,
@@ -36,27 +34,23 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": null,
"ending_balance": null,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000002",
"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_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -70,14 +64,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -87,26 +80,21 @@
"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"
"type": "invoiceitem"
},
{
"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",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -120,14 +108,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -137,15 +124,11 @@
"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"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -160,10 +143,8 @@
"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
},
@@ -173,15 +154,6 @@
"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",
@@ -192,16 +164,10 @@
"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,12 +1,10 @@
{
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,
@@ -36,27 +34,23 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": null,
"ending_balance": null,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000003",
"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_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -70,14 +64,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"id": "price_NORMALIZED00000000000005",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0005",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -87,26 +80,21 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "6000"
"type": "invoiceitem"
},
{
"amount": -36000,
"amount_excluding_tax": -36000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000006",
"invoice_item": "ii_NORMALIZED00000000000006",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -120,14 +108,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000006",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0006",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -137,15 +124,11 @@
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -160,10 +143,8 @@
"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
},
@@ -173,15 +154,6 @@
"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",
@@ -192,16 +164,10 @@
"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,12 +1,10 @@
{
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,27 +34,23 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": null,
"ending_balance": null,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": null,
"id": "in_NORMALIZED00000000000004",
"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_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000007",
"invoice_item": "ii_NORMALIZED00000000000007",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -70,14 +64,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000007",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0007",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -87,15 +80,11 @@
"unit_amount_decimal": "4000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "4000"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -110,10 +99,8 @@
"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
},
@@ -123,15 +110,6 @@
"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",
@@ -142,16 +120,10 @@
"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,12 +1,10 @@
{
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,27 +34,23 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUx0200otAxmBG3?s=ap",
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTUx0200otAxmBG3/pdf?s=ap",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"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",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -70,14 +64,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -87,26 +80,21 @@
"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"
"type": "invoiceitem"
},
{
"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",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -120,14 +108,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -137,15 +124,11 @@
"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"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -156,14 +139,12 @@
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"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
},
@@ -173,15 +154,6 @@
"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",
@@ -192,17 +164,11 @@
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 1000000000
"webhooks_delivered_at": null
}

View File

@@ -1,12 +1,10 @@
{
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,27 +34,23 @@
"discount": null,
"discounts": [],
"due_date": null,
"effective_at": 1000000000,
"ending_balance": 0,
"footer": null,
"from_invoice": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6?s=ap",
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcw0200rWnYu5C6/pdf?s=ap",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 36000,
"amount_excluding_tax": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -70,14 +64,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"id": "price_NORMALIZED00000000000005",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0005",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -87,26 +80,21 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "6000"
"type": "invoiceitem"
},
{
"amount": -36000,
"amount_excluding_tax": -36000,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"id": "il_NORMALIZED00000000000006",
"invoice_item": "ii_NORMALIZED00000000000006",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -120,14 +108,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000006",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0006",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -137,15 +124,11 @@
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -156,14 +139,12 @@
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0003",
"number": "NORMALI-0004",
"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
},
@@ -173,15 +154,6 @@
"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",
@@ -192,16 +164,10 @@
"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,12 +1,10 @@
{
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,27 +34,23 @@
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyM1ZjWEROSGJMNWtrMHFKNFY5ZW1DekRtM2cxLDkwMDkwNTc3020054DiyClm?s=ap",
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyM1ZjWEROSGJMNWtrMHFKNFY5ZW1DekRtM2cxLDkwMDkwNTc3020054DiyClm/pdf?s=ap",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BZ1A2VnVzRGRzR1BIS3V5YWJtejhEYVd1Z0xO0100PL5hiifd/pdf",
"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_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000007",
"invoice_item": "ii_NORMALIZED00000000000007",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -70,14 +64,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000007",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0007",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -87,15 +80,11 @@
"unit_amount_decimal": "4000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "4000"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -106,14 +95,12 @@
"livemode": false,
"metadata": {},
"next_payment_attempt": 1000000000,
"number": "NORMALI-0004",
"number": "NORMALI-0005",
"object": "invoice",
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": "pi_NORMALIZED00000000000004",
"payment_intent": "pi_NORMALIZED00000000000003",
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -123,15 +110,6 @@
"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",
@@ -142,17 +120,11 @@
"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
}

View File

@@ -2,13 +2,11 @@
"data": [
{
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,
@@ -38,27 +36,23 @@
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTU00200ozOzXCq5?s=ap",
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTU00200ozOzXCq5/pdf?s=ap",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BNDRqUzlSeE5ZbkM2YXNhYmhHRDNCN0pTZ3E001000SGRn6zX/pdf",
"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",
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -72,14 +66,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -89,26 +82,21 @@
"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"
"type": "invoiceitem"
},
{
"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",
"id": "il_NORMALIZED00000000000004",
"invoice_item": "ii_NORMALIZED00000000000004",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -122,14 +110,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -139,15 +126,11 @@
"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"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -158,14 +141,12 @@
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"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
},
@@ -175,15 +156,6 @@
"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",
@@ -194,16 +166,10 @@
"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,13 +2,11 @@
"data": [
{
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,
@@ -38,27 +36,23 @@
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcz0200NuJEg74w?s=ap",
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTcz0200NuJEg74w/pdf?s=ap",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 36000,
"amount_excluding_tax": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -72,14 +66,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"id": "price_NORMALIZED00000000000005",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0005",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -89,19 +82,188 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "6000"
"type": "invoiceitem"
},
{
"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",
"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,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
@@ -122,222 +284,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0003",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
"period_end": 1000000000,
"period_start": 1000000000,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "letter"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "paid",
"status_transitions": {
"finalized_at": 1000000000,
"marked_uncollectible_at": null,
"paid_at": 1000000000,
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTcz0200z2u9pNME?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTcz0200z2u9pNME/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 7200,
"amount_excluding_tax": 7200,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 1200,
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "1200"
},
{
"amount": -7200,
"amount_excluding_tax": -7200,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -347,15 +300,11 @@
"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"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -366,14 +315,12 @@
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"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
},
@@ -383,15 +330,6 @@
"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",
@@ -402,16 +340,10 @@
"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,13 +2,11 @@
"data": [
{
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,
@@ -38,27 +36,23 @@
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyM1ZjWEROSGJMNWtrMHFKNFY5ZW1DekRtM2cxLDkwMDkwNTc40200Sj4IFpmz?s=ap",
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyM1ZjWEROSGJMNWtrMHFKNFY5ZW1DekRtM2cxLDkwMDkwNTc40200Sj4IFpmz/pdf?s=ap",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9BZ1A2VnVzRGRzR1BIS3V5YWJtejhEYVd1Z0xO0100PL5hiifd/pdf",
"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_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"id": "il_NORMALIZED00000000000007",
"invoice_item": "ii_NORMALIZED00000000000007",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -72,14 +66,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000007",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0007",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -89,15 +82,11 @@
"unit_amount_decimal": "4000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "4000"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -108,14 +97,12 @@
"livemode": false,
"metadata": {},
"next_payment_attempt": 1000000000,
"number": "NORMALI-0004",
"number": "NORMALI-0005",
"object": "invoice",
"on_behalf_of": null,
"paid": false,
"paid_out_of_band": false,
"payment_intent": "pi_NORMALIZED00000000000004",
"payment_intent": "pi_NORMALIZED00000000000003",
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
@@ -125,15 +112,6 @@
"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",
@@ -144,29 +122,21 @@
"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
},
{
"account_country": "US",
"account_name": "Kandra Labs, Inc.",
"account_name": "NORMALIZED-1",
"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,
@@ -196,27 +166,23 @@
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTc40200bk3mcbnW?s=ap",
"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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlyRHlaT1pCN2U0dG8xY2hwejVidFN6a3ZSMHRuLDkwMDkwNTc40200bk3mcbnW/pdf?s=ap",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01cDVIU2FXWHl2RnBLLF9LaG9Ba2NPMVJlTGJDNXlwMEFBVU1sY01VTDNHTHNk0100ARPvAEt8/pdf",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 36000,
"amount_excluding_tax": 36000,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000003",
"invoice_item": "ii_NORMALIZED00000000000003",
"id": "il_NORMALIZED00000000000005",
"invoice_item": "ii_NORMALIZED00000000000005",
"livemode": false,
"metadata": {},
"object": "line_item",
@@ -230,14 +196,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"id": "price_NORMALIZED00000000000005",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0005",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -247,19 +212,188 @@
"unit_amount_decimal": "6000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "6000"
"type": "invoiceitem"
},
{
"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",
"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,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
@@ -280,222 +414,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": -36000,
"unit_amount_decimal": "-36000"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 1,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "-36000"
}
],
"has_more": false,
"object": "list",
"total_count": 2,
"url": "/v1/invoices/in_NORMALIZED00000000000003/lines"
},
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0003",
"object": "invoice",
"on_behalf_of": null,
"paid": true,
"paid_out_of_band": false,
"payment_intent": null,
"payment_settings": {
"default_mandate": null,
"payment_method_options": null,
"payment_method_types": null
},
"period_end": 1000000000,
"period_start": 1000000000,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"quote": null,
"receipt_number": null,
"rendering": {
"amount_tax_display": null,
"pdf": {
"page_size": "letter"
}
},
"rendering_options": null,
"shipping_cost": null,
"shipping_details": null,
"starting_balance": 0,
"statement_descriptor": "Zulip Cloud Standard",
"status": "paid",
"status_transitions": {
"finalized_at": 1000000000,
"marked_uncollectible_at": null,
"paid_at": 1000000000,
"voided_at": null
},
"subscription": null,
"subscription_details": {
"metadata": null
},
"subtotal": 0,
"subtotal_excluding_tax": 0,
"tax": null,
"test_clock": null,
"total": 0,
"total_discount_amounts": [],
"total_excluding_tax": 0,
"total_tax_amounts": [],
"transfer_data": null,
"webhooks_delivered_at": 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_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTc40200iIpWcfWk?s=ap",
"id": "in_NORMALIZED00000000000002",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_NORMALIZED000001/test_NORMALIZED01a3dERVFhcm9xRGpzLF9PeVlxMmxZbXZoUnVicXBZajdsRFU4azJSWVppaDNSLDkwMDkwNTc40200iIpWcfWk/pdf?s=ap",
"last_finalization_error": null,
"latest_revision": null,
"lines": {
"data": [
{
"amount": 7200,
"amount_excluding_tax": 7200,
"currency": "usd",
"description": "Zulip Cloud Standard",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000001",
"invoice_item": "ii_NORMALIZED00000000000001",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 1200,
"unit_amount_decimal": "1200"
},
"proration": false,
"proration_details": {
"credited_items": null
},
"quantity": 6,
"subscription": null,
"tax_amounts": [],
"tax_rates": [],
"type": "invoiceitem",
"unit_amount_excluding_tax": "1200"
},
{
"amount": -7200,
"amount_excluding_tax": -7200,
"currency": "usd",
"description": "Payment (Card ending in 4242)",
"discount_amounts": [],
"discountable": false,
"discounts": [],
"id": "il_NORMALIZED00000000000002",
"invoice_item": "ii_NORMALIZED00000000000002",
"livemode": false,
"metadata": {},
"object": "line_item",
"period": {
"end": 1000000000,
"start": 1000000000
},
"plan": null,
"price": {
"active": false,
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -505,15 +430,11 @@
"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"
"type": "invoiceitem"
}
],
"has_more": false,
@@ -524,14 +445,12 @@
"livemode": false,
"metadata": {},
"next_payment_attempt": null,
"number": "NORMALI-0002",
"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
},
@@ -541,15 +460,6 @@
"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",
@@ -560,16 +470,10 @@
"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_NORMALIZED00000000000002",
"id": "ii_NORMALIZED00000000000004",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -21,14 +21,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000002",
"id": "price_NORMALIZED00000000000004",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0004",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -41,7 +40,6 @@
"quantity": 1,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": -7200,
"unit_amount_decimal": "-7200"
}

View File

@@ -6,7 +6,7 @@
"description": "Zulip Cloud Standard",
"discountable": false,
"discounts": [],
"id": "ii_NORMALIZED00000000000001",
"id": "ii_NORMALIZED00000000000003",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -21,14 +21,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000001",
"id": "price_NORMALIZED00000000000003",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0003",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -41,7 +40,6 @@
"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_NORMALIZED00000000000004",
"id": "ii_NORMALIZED00000000000006",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -21,14 +21,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000004",
"id": "price_NORMALIZED00000000000006",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0002",
"product": "prod_NORMALIZED0006",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -41,7 +40,6 @@
"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_NORMALIZED00000000000003",
"id": "ii_NORMALIZED00000000000005",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -21,14 +21,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000003",
"id": "price_NORMALIZED00000000000005",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0001",
"product": "prod_NORMALIZED0005",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -41,7 +40,6 @@
"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_NORMALIZED00000000000005",
"id": "ii_NORMALIZED00000000000007",
"invoice": null,
"livemode": false,
"metadata": {},
@@ -21,14 +21,13 @@
"billing_scheme": "per_unit",
"created": 1000000000,
"currency": "usd",
"custom_unit_amount": null,
"id": "price_NORMALIZED00000000000005",
"id": "price_NORMALIZED00000000000007",
"livemode": false,
"lookup_key": null,
"metadata": {},
"nickname": null,
"object": "price",
"product": "prod_NORMALIZED0003",
"product": "prod_NORMALIZED0007",
"recurring": null,
"tax_behavior": "unspecified",
"tiers_mode": null,
@@ -41,7 +40,6 @@
"quantity": 6,
"subscription": null,
"tax_rates": [],
"test_clock": null,
"unit_amount": 4000,
"unit_amount_decimal": "4000"
}

View File

@@ -1,9 +1,6 @@
{
"amount": 7200,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 7200,
"application": null,
"application_fee_amount": null,
@@ -34,7 +31,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"calculated_statement_descriptor": "ZULIP STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -43,7 +40,6 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -76,10 +72,9 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000001",
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_details": {
"card": {
"amount_authorized": 7200,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -87,30 +82,13 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"exp_month": 3,
"exp_year": 2033,
"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
},
@@ -118,7 +96,7 @@
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKLKktKoGMgYimrXpHqA6LBbX51UEvwndRXqUkshrzze-vtY1kaBfu3kQ_cy4eOwD7ZOJ0MTItvKdslAN",
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
"refunded": false,
"refunds": {
"data": [],
@@ -143,7 +121,7 @@
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000001"
},
"client_secret": "pi_NORMALIZED00000000000001_secret_U9fLWaQ5foMLX6li0E7nIl4wX",
"client_secret": "pi_NORMALIZED00000000000001_secret_fD7F9AdDLLYQt94Ii4rEflkYg",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -152,7 +130,6 @@
"id": "pi_NORMALIZED00000000000001",
"invoice": null,
"last_payment_error": null,
"latest_charge": "ch_NORMALIZED00000000000001",
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
@@ -170,12 +147,10 @@
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -183,7 +158,6 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,

View File

@@ -1,9 +1,6 @@
{
"amount": 36000,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 36000,
"application": null,
"application_fee_amount": null,
@@ -34,7 +31,7 @@
"name": null,
"phone": null
},
"calculated_statement_descriptor": "ZULIP CLOUD STANDARD",
"calculated_statement_descriptor": "ZULIP STANDARD",
"captured": true,
"created": 1000000000,
"currency": "usd",
@@ -43,7 +40,6 @@
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {},
@@ -76,10 +72,9 @@
},
"paid": true,
"payment_intent": "pi_NORMALIZED00000000000002",
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_details": {
"card": {
"amount_authorized": 36000,
"brand": "visa",
"checks": {
"address_line1_check": null,
@@ -87,30 +82,13 @@
"cvc_check": "pass"
},
"country": "US",
"exp_month": 11,
"exp_year": 2024,
"extended_authorization": {
"status": "disabled"
},
"exp_month": 3,
"exp_year": 2033,
"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
},
@@ -118,7 +96,7 @@
},
"receipt_email": "hamlet@zulip.com",
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/payment/CAcaFwoVYWNjdF8xN3ZUa3dERVFhcm9xRGpzKMWktKoGMgb3nDVqmxw6LBbhC0PW9T-U53VSB9Q3DZCtbnQ72IGDALfDXWC-jcTkpT6EUGI9HGa9d-fo",
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000002/rcpt_NORMALIZED000000000000000000002",
"refunded": false,
"refunds": {
"data": [],
@@ -143,7 +121,7 @@
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_IM9QK0XPBRsrtvMFpF1HVQGr3",
"client_secret": "pi_NORMALIZED00000000000002_secret_KPTN4Jk0sR9DrnVymxUmd0o0F",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -152,7 +130,6 @@
"id": "pi_NORMALIZED00000000000002",
"invoice": null,
"last_payment_error": null,
"latest_charge": "ch_NORMALIZED00000000000002",
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
@@ -170,12 +147,10 @@
"next_action": null,
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": "pm_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -183,7 +158,6 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,

View File

@@ -1,9 +1,6 @@
{
"amount": 7200,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 0,
"application": null,
"application_fee_amount": null,
@@ -18,7 +15,7 @@
"total_count": 0,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000001"
},
"client_secret": "pi_NORMALIZED00000000000001_secret_U9fLWaQ5foMLX6li0E7nIl4wX",
"client_secret": "pi_NORMALIZED00000000000001_secret_fD7F9AdDLLYQt94Ii4rEflkYg",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -27,7 +24,6 @@
"id": "pi_NORMALIZED00000000000001",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
@@ -46,11 +42,9 @@
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -58,7 +52,6 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,

View File

@@ -1,9 +1,6 @@
{
"amount": 36000,
"amount_capturable": 0,
"amount_details": {
"tip": {}
},
"amount_received": 0,
"application": null,
"application_fee_amount": null,
@@ -18,7 +15,7 @@
"total_count": 0,
"url": "/v1/charges?payment_intent=pi_NORMALIZED00000000000002"
},
"client_secret": "pi_NORMALIZED00000000000002_secret_IM9QK0XPBRsrtvMFpF1HVQGr3",
"client_secret": "pi_NORMALIZED00000000000002_secret_KPTN4Jk0sR9DrnVymxUmd0o0F",
"confirmation_method": "automatic",
"created": 1000000000,
"currency": "usd",
@@ -27,7 +24,6 @@
"id": "pi_NORMALIZED00000000000002",
"invoice": null,
"last_payment_error": null,
"latest_charge": null,
"livemode": false,
"metadata": {
"billing_modality": "charge_automatically",
@@ -46,11 +42,9 @@
"object": "payment_intent",
"on_behalf_of": null,
"payment_method": null,
"payment_method_configuration_details": null,
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
@@ -58,7 +52,6 @@
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": "hamlet@zulip.com",
"review": null,
"setup_future_usage": null,

View File

@@ -0,0 +1,47 @@
{
"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

@@ -0,0 +1,47 @@
{
"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,27 +1,34 @@
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbiyDEQaroqDjsEnpR1Onp_secret_OyYqFrkey3OlMfxWtyreL2AbO9RHVB4",
"client_secret": "seti_1K2OXpHSaWXyvFpKOq6F3F9K_secret_KhoAzpsEjV8G4oAeYDSFmGYMKv5BRkc",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbiyDEQaroqDjsEnpR1Onp",
"id": "seti_1K2OXpHSaWXyvFpKOq6F3F9K",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbiyDEQaroqDjsnLExjW56",
"latest_attempt": "setatt_1K2OXpHSaWXyvFpKcauo7Bx8",
"livemode": false,
"mandate": null,
"metadata": {},
"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_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},

View File

@@ -1,27 +1,34 @@
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjHDEQaroqDjsiyGUfkGg_secret_OyYq1ljcbx3AZiCSK143sZdt7S0n6pN",
"client_secret": "seti_1K2OXzHSaWXyvFpKzkPjM4k7_secret_KhoALa8Xt6eK1ATCOPGurJLxmo2y6xh",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjHDEQaroqDjsiyGUfkGg",
"id": "seti_1K2OXzHSaWXyvFpKzkPjM4k7",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbjHDEQaroqDjswrjSFGn9",
"latest_attempt": "setatt_1K2OXzHSaWXyvFpKG0BonJhW",
"livemode": false,
"mandate": null,
"metadata": {},
"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_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},

View File

@@ -2,28 +2,35 @@
"data": [
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbixDEQaroqDjsZ13bFTzk_secret_OyYqTKT3uNo6hXb6nycNN82HUV4tLVa",
"client_secret": "seti_1K2OXoHSaWXyvFpKLyy5ns16_secret_KhoANgFdO2YICL1Urfnax58nGUY0MeV",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbixDEQaroqDjsZ13bFTzk",
"id": "seti_1K2OXoHSaWXyvFpKLyy5ns16",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,
"mandate": null,
"metadata": {},
"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_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},

View File

@@ -2,28 +2,35 @@
"data": [
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjFDEQaroqDjsOtWXOh3z_secret_OyYqDwZSEotDZcPqC8EEXDlXcWpwH0i",
"client_secret": "seti_1K2OXyHSaWXyvFpKvIhWI0JW_secret_KhoAlunPkSHQPGu7zyaTpT81wBUPhqc",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjFDEQaroqDjsOtWXOh3z",
"id": "seti_1K2OXyHSaWXyvFpKvIhWI0JW",
"last_setup_error": null,
"latest_attempt": null,
"livemode": false,
"mandate": null,
"metadata": {},
"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_configuration_details": null,
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},

View File

@@ -1,27 +1,34 @@
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbiyDEQaroqDjsEnpR1Onp_secret_OyYqFrkey3OlMfxWtyreL2AbO9RHVB4",
"client_secret": "seti_1K2OXpHSaWXyvFpKOq6F3F9K_secret_KhoAzpsEjV8G4oAeYDSFmGYMKv5BRkc",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbiyDEQaroqDjsEnpR1Onp",
"id": "seti_1K2OXpHSaWXyvFpKOq6F3F9K",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbiyDEQaroqDjsnLExjW56",
"latest_attempt": "setatt_1K2OXpHSaWXyvFpKcauo7Bx8",
"livemode": false,
"mandate": null,
"metadata": {},
"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_1OAbiyDEQaroqDjs3ViUEwAQ",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXoHSaWXyvFpKhrceNhbI",
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},

View File

@@ -1,27 +1,34 @@
{
"application": null,
"automatic_payment_methods": null,
"cancellation_reason": null,
"client_secret": "seti_1OAbjHDEQaroqDjsiyGUfkGg_secret_OyYq1ljcbx3AZiCSK143sZdt7S0n6pN",
"client_secret": "seti_1K2OXzHSaWXyvFpKzkPjM4k7_secret_KhoALa8Xt6eK1ATCOPGurJLxmo2y6xh",
"created": 1000000000,
"customer": "cus_NORMALIZED0001",
"description": null,
"flow_directions": null,
"id": "seti_1OAbjHDEQaroqDjsiyGUfkGg",
"id": "seti_1K2OXzHSaWXyvFpKzkPjM4k7",
"last_setup_error": null,
"latest_attempt": "setatt_1OAbjHDEQaroqDjswrjSFGn9",
"latest_attempt": "setatt_1K2OXzHSaWXyvFpKG0BonJhW",
"livemode": false,
"mandate": null,
"metadata": {},
"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_1OAbjHDEQaroqDjsFfj8byC8",
"payment_method_configuration_details": null,
"payment_method": "pm_1K2OXyHSaWXyvFpKNE66Ex7T",
"payment_method_options": {
"card": {
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},

View File

@@ -8,35 +8,16 @@
"status": null
},
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/billing/",
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
"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_creation": null,
"customer_details": {
"address": null,
"email": "hamlet@zulip.com",
"name": null,
"phone": null,
"tax_exempt": null,
"tax_ids": null
},
"customer_details": null,
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED02Z5ZWoaj9BNmgBmGSOWkzCIOee3905mO0A4AaeSETi2qfDk",
"invoice": null,
"invoice_creation": null,
"id": "cs_test_NORMALIZED02JXoyt8JsWg6jjuoI58GyCaB4aDXDWB7D0FqdiqlF6JDHjM",
"livemode": false,
"locale": null,
"metadata": {
@@ -55,9 +36,6 @@
"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"
@@ -67,7 +45,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1OAbixDEQaroqDjsZ13bFTzk",
"setup_intent": "seti_1K2OXoHSaWXyvFpKLyy5ns16",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -77,6 +55,5 @@
"subscription": null,
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED02Z5ZWoaj9BNmgBmGSOWkzCIOee3905mO0A4AaeSETi2qfDk#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"url": "https://checkout.stripe.com/pay/cs_test_NORMALIZED02JXoyt8JsWg6jjuoI58GyCaB4aDXDWB7D0FqdiqlF6JDHjM#fidkdWxOYHwnPyd1blpxYHZxWnZkQk5DYTNqTGNxQl9JQjJIf2B1Y05BdScpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}

View File

@@ -8,35 +8,16 @@
"status": null
},
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/billing/",
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
"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_creation": null,
"customer_details": {
"address": null,
"email": "hamlet@zulip.com",
"name": null,
"phone": null,
"tax_exempt": null,
"tax_ids": null
},
"customer_details": null,
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED03LS5wbvnBYGdnCEW5t1ot0jYJyM0nR7L9zyMjaPa4nlbim5",
"invoice": null,
"invoice_creation": null,
"id": "cs_test_NORMALIZED03qjdrziUfZOYhaTIKoiuAENHHbecsfYggKZ4kWegRShmXDo",
"livemode": false,
"locale": null,
"metadata": {
@@ -55,9 +36,6 @@
"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"
@@ -67,7 +45,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1OAbjFDEQaroqDjsOtWXOh3z",
"setup_intent": "seti_1K2OXyHSaWXyvFpKvIhWI0JW",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -77,6 +55,5 @@
"subscription": null,
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED03LS5wbvnBYGdnCEW5t1ot0jYJyM0nR7L9zyMjaPa4nlbim5#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"url": "https://checkout.stripe.com/pay/cs_test_NORMALIZED03qjdrziUfZOYhaTIKoiuAENHHbecsfYggKZ4kWegRShmXDo#fidkdWxOYHwnPyd1blpxYHZxWnZkQk5DYTNqTGNxQl9JQjJIf2B1Y05BdScpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}

View File

@@ -10,35 +10,16 @@
"status": null
},
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/billing/",
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
"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_creation": null,
"customer_details": {
"address": null,
"email": "hamlet@zulip.com",
"name": null,
"phone": null,
"tax_exempt": null,
"tax_ids": null
},
"customer_details": null,
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED02Z5ZWoaj9BNmgBmGSOWkzCIOee3905mO0A4AaeSETi2qfDk",
"invoice": null,
"invoice_creation": null,
"id": "cs_test_NORMALIZED02JXoyt8JsWg6jjuoI58GyCaB4aDXDWB7D0FqdiqlF6JDHjM",
"livemode": false,
"locale": null,
"metadata": {
@@ -57,9 +38,6 @@
"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"
@@ -69,7 +47,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1OAbixDEQaroqDjsZ13bFTzk",
"setup_intent": "seti_1K2OXoHSaWXyvFpKLyy5ns16",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -79,8 +57,7 @@
"subscription": null,
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED02Z5ZWoaj9BNmgBmGSOWkzCIOee3905mO0A4AaeSETi2qfDk#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"url": "https://checkout.stripe.com/pay/cs_test_NORMALIZED02JXoyt8JsWg6jjuoI58GyCaB4aDXDWB7D0FqdiqlF6JDHjM#fidkdWxOYHwnPyd1blpxYHZxWnZkQk5DYTNqTGNxQl9JQjJIf2B1Y05BdScpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}
],
"has_more": true,

View File

@@ -10,35 +10,16 @@
"status": null
},
"billing_address_collection": null,
"cancel_url": "http://zulip.testserver/billing/",
"cancel_url": "http://zulip.testserver/upgrade/",
"client_reference_id": null,
"client_secret": null,
"consent": null,
"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_creation": null,
"customer_details": {
"address": null,
"email": "hamlet@zulip.com",
"name": null,
"phone": null,
"tax_exempt": null,
"tax_ids": null
},
"customer_details": null,
"customer_email": null,
"expires_at": 1000000000,
"id": "cs_test_NORMALIZED03LS5wbvnBYGdnCEW5t1ot0jYJyM0nR7L9zyMjaPa4nlbim5",
"invoice": null,
"invoice_creation": null,
"id": "cs_test_NORMALIZED03qjdrziUfZOYhaTIKoiuAENHHbecsfYggKZ4kWegRShmXDo",
"livemode": false,
"locale": null,
"metadata": {
@@ -57,9 +38,6 @@
"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"
@@ -69,7 +47,7 @@
"enabled": false
},
"recovered_from": null,
"setup_intent": "seti_1OAbjFDEQaroqDjsOtWXOh3z",
"setup_intent": "seti_1K2OXyHSaWXyvFpKvIhWI0JW",
"shipping": null,
"shipping_address_collection": null,
"shipping_options": [],
@@ -79,8 +57,7 @@
"subscription": null,
"success_url": "http://zulip.testserver/billing/event_status?stripe_session_id={CHECKOUT_SESSION_ID}",
"total_details": null,
"ui_mode": "hosted",
"url": "https://checkout.stripe.com/c/pay/cs_test_NORMALIZED03LS5wbvnBYGdnCEW5t1ot0jYJyM0nR7L9zyMjaPa4nlbim5#fidkdWxOYHwnPyd1blpxYHZxWl1UZjFOczZJXUE2PUpzUWNPV2ZpdlFzUCcpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
"url": "https://checkout.stripe.com/pay/cs_test_NORMALIZED03qjdrziUfZOYhaTIKoiuAENHHbecsfYggKZ4kWegRShmXDo#fidkdWxOYHwnPyd1blpxYHZxWnZkQk5DYTNqTGNxQl9JQjJIf2B1Y05BdScpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBaZmppcGhrJyknYGtkZ2lgVWlkZmBtamlhYHd2Jz9xd3BgeCUl"
}
],
"has_more": true,

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
{
"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_1K2OXeHSaWXyvFpKfQsNmKNC",
"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"
}

View File

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

View File

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

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