Compare commits

..

1 Commits

Author SHA1 Message Date
Tim Abbott
79c247fde1 Release Zulip Server 7.0-beta2. 2023-05-11 16:09:47 -07:00
2944 changed files with 130824 additions and 250570 deletions

View File

@@ -50,10 +50,8 @@
"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-unresolved": ["error", {"ignore": ["^date-fns-tz$|^js-cookie$|^katex$"]}],
"import/no-useless-path-segments": "error",
"import/order": ["error", {"alphabetize": {"order": "asc"}, "newlines-between": "always"}],
"import/unambiguous": "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"
}
},
@@ -274,8 +266,7 @@
}
]
}
],
"unicorn/prefer-string-replace-all": "off"
]
}
}
]

View File

@@ -1,10 +0,0 @@
---
name: Issue discussed in the Zulip development community
about: Bug report, feature or improvement already discussed on chat.zulip.org.
---
<!-- Issue description -->
<!-- Link to a message in the chat.zulip.org discussion. Message links will still work even if the topic is renamed or resolved. Link back to this issue from the chat.zulip.org thread. -->
CZO thread

View File

@@ -1,17 +0,0 @@
---
name: Bug report
about: A concrete bug report with steps to reproduce the behavior. (See also "Possible bug" below.)
labels: ["bug"]
---
<!-- Describe what you were expecting to see, what you saw instead, and steps to take in order to reproduce the buggy behavior. Screenshots can be helpful. -->
<!-- Check the box for the version of Zulip you are using (see https://zulip.com/help/view-zulip-version).-->
**Zulip Server and web app version:**
- [ ] Zulip Cloud (`*.zulipchat.com`)
- [ ] Zulip Server 7.0+
- [ ] Zulip Server 6.0+
- [ ] Zulip Server 5.0 or older
- [ ] Other or not sure

View File

@@ -1,6 +0,0 @@
---
name: Feature or improvement request
about: A specific proposal for a new feature of improvement. (See also "Feature suggestion or feedback" below.)
---
<!-- Describe the proposal, including how it would help you or your organization. -->

View File

@@ -1,14 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Possible bug
url: https://zulip.readthedocs.io/en/latest/contributing/reporting-bugs.html
about: Report unexpected behavior that may be a bug.
- name: Feature suggestion or feedback
url: https://zulip.readthedocs.io/en/latest/contributing/suggesting-features.html
about: Start a discussion about your idea for improving Zulip.
- name: Issue with running or upgrading a Zulip server
url: https://zulip.readthedocs.io/en/latest/production/troubleshooting.html
about: We provide free, interactive support for the vast majority of questions about running a Zulip server.
- name: Other support requests and sales questions
url: https://zulip.com/help/contact-support
about: Contact us — we're happy to help!

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' }}
@@ -150,11 +144,6 @@ jobs:
os: bullseye
extra-args: --test-custom-db
- docker_image: zulip/ci:bookworm
name: Debian 12 production install
os: bookworm
extra-args: ""
name: ${{ matrix.name }}
container:
image: ${{ matrix.docker_image }}
@@ -190,6 +179,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 +283,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

@@ -27,6 +27,8 @@ jobs:
strategy:
fail-fast: false
matrix:
include_documentation_tests: [false]
include_frontend_tests: [false]
include:
# Base images are built using `tools/ci/Dockerfile.prod.template`.
# The comments at the top explain how to build and upload these images.
@@ -34,26 +36,16 @@ jobs:
- docker_image: zulip/ci:focal
name: Ubuntu 20.04 (Python 3.8, backend + frontend)
os: focal
include_documentation_tests: false
include_frontend_tests: true
# Debian 11 ships with Python 3.9.2.
- docker_image: zulip/ci:bullseye
name: Debian 11 (Python 3.9, backend + documentation)
os: bullseye
include_documentation_tests: true
include_frontend_tests: false
# Ubuntu 22.04 ships with Python 3.10.4.
- docker_image: zulip/ci:jammy
name: Ubuntu 22.04 (Python 3.10, backend)
os: jammy
include_documentation_tests: false
include_frontend_tests: false
# Debian 12 ships with Python 3.11.2.
- docker_image: zulip/ci:bookworm
name: Debian 12 (Python 3.11, backend)
os: bookworm
include_documentation_tests: false
include_frontend_tests: false
runs-on: ubuntu-latest
name: ${{ matrix.name }}
@@ -79,7 +71,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 +92,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 +107,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 +119,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 +158,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 +237,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,29 @@
# # 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>
Adarsh Tiwari <xoldyckk@gmail.com>
Adam Benesh <Adam.Benesh@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 +42,6 @@ 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>
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,23 +54,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>
Palash Baderia <palash.baderia@outlook.com> <66828942+palashb01@users.noreply.github.com>
Palash Raghuwanshi <singhpalash0@gmail.com>
@@ -85,10 +75,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>
@@ -106,27 +96,29 @@ Steve Howell <showell@zulip.com> <showell@zulipchat.com>
Steve Howell <showell@zulip.com> <steve@humbughq.com>
Steve Howell <showell@zulip.com> <steve@zulip.com>
strifel <info@strifel.de>
Tim Abbott <tabbott@zulip.com>
Tim Abbott <tabbott@zulip.com> <tabbott@dropbox.com>
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

@@ -55,10 +55,8 @@ needs doing:
**Non-code contributions**: Some of the most valuable ways to contribute
don't require touching the codebase at all. For example, you can:
- Report issues, including both [feature
requests](https://zulip.readthedocs.io/en/latest/contributing/suggesting-features.html)
and [bug
reports](https://zulip.readthedocs.io/en/latest/contributing/reporting-bugs.html).
- [Report issues](#reporting-issues), including both feature requests and
bug reports.
- [Give feedback](#user-feedback) if you are evaluating or using Zulip.
- [Participate
thoughtfully](https://zulip.readthedocs.io/en/latest/contributing/design-discussions.html)
@@ -154,10 +152,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
@@ -214,16 +208,101 @@ stream](https://chat.zulip.org/#narrow/stream/101-design) in the [Zulip
development community](https://zulip.com/development-community/)
For more advice, see [What makes a great Zulip
contributor?](#what-makes-a-great-zulip-contributor) below. It's OK if your
first issue takes you a while; that's normal! You'll be able to work a lot
faster as you build experience.
contributor?](#what-makes-a-great-zulip-contributor)
below.
### Submitting a pull request
See the [pull request review
process](https://zulip.readthedocs.io/en/latest/contributing/review-process.html)
guide for detailed instructions on how to submit a pull request, and information
on the stages of review your PR will go through.
When you believe your code is ready, follow the [guide on how to review
code](https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html#how-to-review-code)
to review your own work. You can often find things you missed by taking a step
back to look over your work before asking others to do so. Catching mistakes
yourself will help your PRs be merged faster, and folks will appreciate the
quality and professionalism of your work.
Then, submit your changes. Carefully reading our [Git guide][git-guide], and in
particular the section on [making a pull request][git-guide-make-pr], will help
avoid many common mistakes. If any part of your contribution is from someone
else (code snippets, images, sounds, or any other copyrightable work, modified
or unmodified), be sure to review the instructions on how to [properly
attribute][licensing] the work.
[licensing]: https://zulip.readthedocs.io/en/latest/contributing/licensing.html#contributing-someone-else-s-work
Once you are satisfied with the quality of your PR, follow the
[guidelines on asking for a code
review](https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html#asking-for-a-code-review)
to request a review. If you are not sure what's best, simply post a
comment on the main GitHub thread for your PR clearly indicating that
it is ready for review, and the project maintainers will take a look
and follow up with next steps.
It's OK if your first issue takes you a while; that's normal! You'll be
able to work a lot faster as you build experience.
If it helps your workflow, you can submit your pull request marked as
a [draft][github-help-draft-pr] while you're still working on it, and
then mark it ready when you think it's time for someone else to review
your work.
[git-guide]: https://zulip.readthedocs.io/en/latest/git/
[git-guide-make-pr]: https://zulip.readthedocs.io/en/latest/git/pull-requests.html
[github-help-draft-pr]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests
### Stages of a pull request
Your pull request will likely go through several stages of review.
1. If your PR makes user-facing changes, the UI and user experience may be
reviewed early on, without reference to the code. You will get feedback on
any user-facing bugs in the implementation. To minimize the number of review
round-trips, make sure to [thoroughly
test](https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html#manual-testing)
your own PR prior to asking for review.
2. There may be choices made in the implementation that the reviewer
will ask you to revisit. This process will go more smoothly if you
specifically call attention to the decisions you made while
drafting the PR and any points about which you are uncertain. The
PR description and comments on your own PR are good ways to do this.
3. Oftentimes, seeing an initial implementation will make it clear that the
product design for a feature needs to be revised, or that additional changes
are needed. The reviewer may therefore ask you to amend or change the
implementation. Some changes may be blockers for getting the PR merged, while
others may be improvements that can happen afterwards. Feel free to ask if
it's unclear which type of feedback you're getting. (Follow-ups can be a
great next issue to work on!)
4. In addition to any UI/user experience review, all PRs will go through one or
more rounds of code review. Your code may initially be [reviewed by other
contributors](https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html).
This helps us make good use of project maintainers' time, and helps you make
progress on the PR by getting more frequent feedback. A project maintainer
may leave a comment asking someone with expertise in the area you're working
on to review your work.
5. Final code review and integration for server and web app PRs is generally done
by `@timabbott`.
#### How to help move the review process forward
The key to keeping your review moving through the review process is to:
- Address _all_ the feedback to the best of your ability.
- Make it clear when the requested changes have been made
and you believe it's time for another look.
- Make it as easy as possible to review the changes you made.
In order to do this, when you believe you have addressed the previous round of
feedback on your PR as best you can, post a comment asking reviewers to take
another look. Your comment should make it easy to understand what has been done
and what remains by:
- Summarizing the changes made since the last review you received.
- Highlighting remaining questions or decisions, with links to any relevant
chat.zulip.org threads.
- Providing updated screenshots and information on manual testing if
appropriate.
The easier it is to review your work, the more likely you are to receive quick
feedback.
### Beyond the first issue
@@ -249,12 +328,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
@@ -276,7 +349,7 @@ labels.
on [Git commit
discipline](https://zulip.readthedocs.io/en/latest/contributing/commit-discipline.html).
2. If all the feedback has been addressed, did you [leave a
comment](https://zulip.readthedocs.io/en/latest/contributing/review-process.html#how-to-help-move-the-review-process-forward)
comment](#how-to-help-move-the-review-process-forward)
explaining that you have done so and **requesting another review**? If not,
it may not be clear to project maintainers or reviewers that your PR is
ready for another look.
@@ -326,6 +399,29 @@ experience, these are the best predictors of success:
[great-questions]: https://zulip.readthedocs.io/en/latest/contributing/asking-great-questions.html
## Reporting issues
If you find an easily reproducible bug and/or are experienced in reporting
bugs, feel free to just open an issue on the relevant project on GitHub.
If you have a feature request or are not yet sure what the underlying bug
is, the best place to post issues is
[#issues](https://chat.zulip.org/#narrow/stream/9-issues) (or
[#mobile](https://chat.zulip.org/#narrow/stream/48-mobile) or
[#desktop](https://chat.zulip.org/#narrow/stream/16-desktop)) on the
[Zulip community server](https://zulip.com/development-community/).
This allows us to interactively figure out what is going on, let you know if
a similar issue has already been opened, and collect any other information
we need. Choose a 2-4 word topic that describes the issue, explain the issue
and how to reproduce it if known, your browser/OS if relevant, and a
[screenshot or screenGIF](https://zulip.readthedocs.io/en/latest/tutorials/screenshot-and-gif-software.html)
if appropriate.
**Reporting security issues**. Please do not report security issues
publicly, including on public streams on chat.zulip.org. You can
email [security@zulip.com](mailto:security@zulip.com). We create a CVE for every
security issue in our released software.
## User feedback
Nearly every feature we develop starts with a user request. If you are part

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
@@ -25,17 +24,7 @@ from zerver.lib.storage import static_path
from zerver.lib.stream_color import STREAM_ASSIGNMENT_COLORS
from zerver.lib.timestamp import floor_to_day
from zerver.lib.upload import upload_message_attachment_from_request
from zerver.models import (
Client,
Realm,
RealmAuditLog,
Recipient,
Stream,
Subscription,
SystemGroups,
UserGroup,
UserProfile,
)
from zerver.models import Client, Realm, Recipient, Stream, Subscription, UserGroup, UserProfile
class Command(BaseCommand):
@@ -69,7 +58,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 +103,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",
@@ -128,20 +116,16 @@ class Command(BaseCommand):
stream.save(update_fields=["recipient"])
# Subscribe shylock to the stream to avoid invariant failures.
Subscription.objects.create(
recipient=recipient,
user_profile=shylock,
is_user_active=shylock.is_active,
color=STREAM_ASSIGNMENT_COLORS[0],
)
RealmAuditLog.objects.create(
realm=realm,
modified_user=shylock,
modified_stream=stream,
event_last_message_id=0,
event_type=RealmAuditLog.SUBSCRIPTION_CREATED,
event_time=installation_time,
)
# TODO: This should use subscribe_users_to_streams from populate_db.
subs = [
Subscription(
recipient=recipient,
user_profile=shylock,
is_user_active=shylock.is_active,
color=STREAM_ASSIGNMENT_COLORS[0],
),
]
Subscription.objects.bulk_create(subs)
# Create an attachment in the database for set_storage_space_used_statistic.
IMAGE_FILE_PATH = static_path("images/test-images/checkbox.png")
@@ -150,7 +134,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 +142,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 +154,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:
@@ -313,12 +249,6 @@ class TestSupportEndpoint(ZulipTestCase):
check_hamlet_user_query_result(result)
check_zulip_realm_query_result(result)
# Search should be case-insensitive:
assert self.example_email("hamlet") != self.example_email("hamlet").upper()
result = get_check_query_result(self.example_email("hamlet").upper(), 1)
check_hamlet_user_query_result(result)
check_zulip_realm_query_result(result)
result = get_check_query_result(lear_user.email, 1)
check_lear_user_query_result(result)
check_lear_realm_query_result(result)
@@ -373,7 +303,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 +362,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 +502,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 +593,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:
@@ -781,26 +702,3 @@ class TestSupportEndpoint(ZulipTestCase):
result = self.client_post("/activity/support", {"realm_id": f"{lear_realm.id}"})
self.assert_json_error(result, "Invalid parameters")
m.assert_not_called()
def test_delete_user(self) -> None:
cordelia = self.example_user("cordelia")
hamlet = self.example_user("hamlet")
hamlet_email = hamlet.delivery_email
realm = get_realm("zulip")
self.login_user(cordelia)
result = self.client_post(
"/activity/support", {"realm_id": f"{realm.id}", "delete_user_by_id": hamlet.id}
)
self.assertEqual(result.status_code, 302)
self.assertEqual(result["Location"], "/login/")
self.login("iago")
with mock.patch("analytics.views.support.do_delete_user_preserving_messages") as m:
result = self.client_post(
"/activity/support",
{"realm_id": f"{realm.id}", "delete_user_by_id": hamlet.id},
)
m.assert_called_once_with(hamlet)
self.assert_in_success_response([f"{hamlet_email} in zulip deleted"], result)

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,13 +3,12 @@ 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
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.db.models import Q
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
@@ -27,7 +26,6 @@ from zerver.actions.realm_settings import (
do_scrub_realm,
do_send_realm_reactivation_email,
)
from zerver.actions.users import do_delete_user_preserving_messages
from zerver.decorator import require_server_admin
from zerver.forms import check_subdomain_available
from zerver.lib.exceptions import JsonableError
@@ -44,29 +42,22 @@ from zerver.models import (
UserProfile,
get_org_type_display_name,
get_realm,
get_user_profile_by_id,
)
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,
@@ -175,7 +166,6 @@ def support(
default=None, str_validator=check_string_in(VALID_MODIFY_PLAN_METHODS)
),
scrub_realm: bool = REQ(default=False, json_validator=check_bool),
delete_user_by_id: Optional[int] = REQ(default=None, converter=to_non_negative_int),
query: Optional[str] = REQ("q", default=None),
org_type: Optional[int] = REQ(default=None, converter=to_non_negative_int),
) -> HttpResponse:
@@ -185,8 +175,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 +187,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 +230,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 +245,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":
@@ -286,20 +276,11 @@ def support(
elif scrub_realm:
do_scrub_realm(realm, acting_user=acting_user)
context["success_message"] = f"{realm.string_id} scrubbed."
elif delete_user_by_id:
user_profile_for_deletion = get_user_profile_by_id(delete_user_by_id)
user_email = user_profile_for_deletion.delivery_email
assert user_profile_for_deletion.realm == realm
do_delete_user_preserving_messages(user_profile_for_deletion)
context["success_message"] = f"{user_email} in {realm.subdomain} deleted."
if query:
key_words = get_invitee_emails_set(query)
case_insensitive_users_q = Q()
for key_word in key_words:
case_insensitive_users_q |= Q(delivery_email__iexact=key_word)
users = set(UserProfile.objects.filter(case_insensitive_users_q))
users = set(UserProfile.objects.filter(delivery_email__in=key_words))
realms = set(Realm.objects.filter(string_id__in=key_words))
for key_word in key_words:
@@ -375,8 +356,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 +393,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,422 +18,19 @@ 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**
No changes; feature level used for Zulip 7.0 release.
**Feature level 184**
* [`PATCH /scheduled_messages/<int:scheduled_message_id>`](/api/update-scheduled-message):
Added new endpoint for editing an existing scheduled message.
* [`POST /scheduled_messages`](/api/create-scheduled-message):
Removed optional `scheduled_message_id` parameter, which had
been a previous way for clients to support editing an existing
scheduled message.
**Feature level 183**
* [`POST /register`](/api/register-queue): Removed the
`realm_community_topic_editing_limit_seconds` property, which was no
longer in use. The time limit for editing topics is controlled by the
realm setting `move_messages_within_stream_limit_seconds`, see feature
level 162.
* [`GET /events`](/api/get-events): Removed the `community_topic_editing_limit_seconds`
property from realm `update_dict` event documentation, because it was
never returned as a changed property in this event and was only ever
returned in the [`POST /register`](/api/register-queue) response.
**Feature level 182**
* `POST /export/realm`: This endpoint now returns the ID of the data
export object created by the request.
**Feature level 181**
* [`GET /scheduled_messages`](/api/get-scheduled-messages), [`GET
/events`](/api/get-events), [`POST /register`](/api/register-queue):
Added `failed` boolean field to scheduled message objects to
indicate if the server tried to send the scheduled message and was
unsuccessful. Clients that support unscheduling and editing
scheduled messages should use this field to indicate to the user
when a scheduled message failed to send at the appointed time.
**Feature level 180**
* `POST /invites`: Added support for invitations specifying the empty
list as the user's initial stream subscriptions. Previously, this
returned an error. This change was also backported to Zulip 6.2, and
is available at feature levels 157-158 as well.
returned an error.
**Feature level 179**
**Feature level 179**:
* [`POST /scheduled_messages`](/api/create-scheduled-message):
* [`POST /scheduled_messages`](/api/create-or-update-scheduled-message):
Added new endpoint to create and edit scheduled messages.
* [`GET /events`](/api/get-events):
* [`GET /events`](/api/get-events)
Added `scheduled_messages` events sent to clients when a user creates,
edits or deletes scheduled messages.
* [`POST /register`](/api/register-queue):
@@ -442,14 +39,11 @@ No changes; feature level used for Zulip 7.0 release.
**Feature level 178**
* `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),
[`GET /events`](/api/get-events):
The server no longer stores which client submitted presence data,
and presence responses from the server will always contain the
`"aggregated"` and `"website"` keys.
* `POST users/me/presence`, [`POST /register`](/api/register-queue),
[`GET /events`](/api/get-events), `GET /realm/presence`, `GET
/users/<user_id_or_email>/presence`: The server no longer stores
which client submitted presence data, and presence responses from
the server will always contain the `aggregated` and `website` keys.
**Feature level 177**
@@ -467,30 +61,25 @@ 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):
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
strings are no longer supported.
* [`POST /realm/filters`](/api/add-linkifier), [`realm/filters/<int:filter_id>`](/api/update-linkifier):
The parameter `url_format_string` is replaced by `url_template`.
The linkifiers now accept only [RFC 6570][rfc6570] compliant URL Templates.
The old URL format strings are no longer supported.
* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue):
The `url_format_string` key in `realm_linkifiers` objects is replaced
by `url_template`. For backwards-compatibility, clients that do not
support the `linkifier_url_template`
The key `url_format_string` is replaced by `url_template` for the `realm_linkifiers`
event type. For backwards-compatibility, clients that do not support the
`linkifier_url_template`
[client capability](/api/register-queue#parameter-client_capabilities)
will receive an empty `realm_linkifiers` array in the `/register`
response and not receive `realm_linkifiers` events. Unconditionally,
the deprecated `realm_filters` event type returns an empty array in
the `/register` response and these events are no longer sent to
clients.
will get an empty list in the response of `/register` and not receive `realm_linkifiers`
events. Unconditionally, the deprecated event type `realm_filters` gives an empty list in the
response of `/register` and is no longer sent the clients otherwise.
**Feature level 175**
* [`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings),
[`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults):
Added new user setting `web_mark_read_on_scroll_policy`. Clients may use this to
determine the user's preference on whether to mark messages as read or not when
scrolling through their message feed.
Added new user setting `web_mark_read_on_scroll_policy` . This determines whether to mark
messages as read or not as the client scrolls through their feed.
**Feature level 174**:
@@ -509,15 +98,12 @@ No changes; feature level used for Zulip 7.0 release.
**Feature level 172**
* [`PATCH /messages/{message_id}`](/api/update-message):
[Topic editing restrictions](/help/restrict-moving-messages) now apply
to stream messages without a topic.
* [`PATCH /messages/{message_id}`](/api/update-message): When users, other
than organization administrators and moderators, use
`"propagate_mode": "change_all"` to move messages that have passed the
organization's time limit for updating a message's topic and/or stream,
this endpoint now returns an error response
(`"code": "MOVE_MESSAGES_TIME_LIMIT_EXCEEDED"`).
* [`PATCH /messages/{message_id}`](/api/update-message): Topic editing
restrictions now apply to messages without a topic as well.
* [`PATCH /messages/{message_id}`](/api/update-message): The endpoint
now returns an error when users, other than organization administrators
and moderators, try to move messages that have passed the time limit
using `change_all` value for `propagate_mode` parameter.
**Feature level 171**:
@@ -528,14 +114,8 @@ No changes; feature level used for Zulip 7.0 release.
**Feature level 170**
* [`POST /user_topics`](/api/update-user-topic): Added a new endpoint to
update a user's personal preferences for a topic, which deprecates the
[`PATCH /users/me/subscriptions/muted_topics`](/api/mute-topic) endpoint.
The deprecated endpoint is maintained for backwards-compatibility but may be
removed in a future release.
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events):
Unmuted added as a visibility policy option to the objects sent in response
to the `user_topic` event.
* [`POST /user_topics`](/api/update-user-topic):
Added a new endpoint to update the personal preferences for a topic.
**Feature level 169**
@@ -546,10 +126,10 @@ No changes; feature level used for Zulip 7.0 release.
**Feature level 168**
* [`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings),
[`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults):
Replaced the boolean user setting `realm_name_in_notifications`
with an integer `realm_name_in_email_notifications_policy`.
* [`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults),
[`POST /register`](/api/register-queue),
[`PATCH /settings`](/api/update-settings): Replaced the `realm_name_in_notifications`
boolean field with an integer field `realm_name_in_email_notifications_policy`.
**Feature level 167**
@@ -577,95 +157,76 @@ No changes; feature level used for Zulip 7.0 release.
**Feature level 164**
* [`POST /register`](/api/register-queue): Added the
`server_presence_ping_interval_seconds` and
`server_presence_offline_threshold_seconds` fields for clients
to use when implementing the [presence](/api/get-presence) system.
`server_presence_ping_interval_seconds` and `server_presence_offline_threshold_seconds`
attributes.
**Feature level 163**
* [`GET /users`](/api/get-users), [`GET /users/{user_id}`](/api/get-user),
[`GET /users/{email}`](/api/get-user-by-email),
[`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
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
`delivery_email` is now sent to all users who have access to it, and
is also sent when a user's `email_address_visibility` setting changes.
* [`GET /events`](/api/get-events), [`POST /register`](/api/register-queue)
[`GET /users`](/api/get-users), [`GET /users/{user_id}`](/api/get-user),
[`GET /users/{email}`](/api/get-user-by-email),
[`GET /users/me`](/api/get-own-user), [`GET /messages`](/api/get-messages),
[`GET /messages/{message_id}`](/api/get-message): Whether the `avatar_url`
field in message and user objects returned by these endpoints can be `null`
now depends on if the current user has access to the other user's real
email address based on the other user's `email_address_visibility` policy.
* [`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings),
[`PATCH /realm/user_settings_defaults`](/api/update-realm-user-settings-defaults):
Added user setting `email_address_visibility`, to replace the
realm setting `email_address_visibility`.
* [`POST /register`](/api/register-queue), `PATCH /realm`: Removed realm
[`GET /users/me`](/api/get-own-user) and [`GET /events`](/api/get-events):
The `delivery_email` field is always present in user objects, including the case
when `email_address_visibility` is set to `EMAIL_ADDRESS_VISIBILITY_EVERYONE`,
with the value being `None` 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 real email.
* [`GET /events`](/api/get-events): Event for updating `delivery_email` is now sent to
all users who have access to it and is also sent when `email_address_visibility` setting
changes.
* [`POST /register`](/api/register-queue), [`PATCH
/settings`](/api/update-settings), [`PATCH
/realm/user_settings_defaults`](/api/update-realm-user-settings-defaults): Added
user setting `email_address_visibility` which will replace the existing realm
setting `email_address_visibility`.
* [`POST /register`](/api/register-queue), `PATCH /realm`: Removed realm-level
`email_address_visibility` setting.
**Feature level 162**
* `PATCH /realm`, [`POST /register`](/api/register-queue),
[`GET /events`](/api/get-events): Added two new realm settings
`move_messages_within_stream_limit_seconds` and
`move_messages_between_streams_limit_seconds` for organizations to
configure time limits for editing topics and moving messages between streams.
* [`PATCH /messages/{message_id}`](/api/update-message): For users other than
administrators and moderators, the time limit for editing topics is now
controlled via the realm setting `move_messages_within_stream_limit_seconds`
and the time limit for moving messages between streams is now controlled by
the realm setting `move_messages_between_streams_limit_seconds`.
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events),
`PATCH /realm`: Added new `move_messages_within_stream_limit_seconds` setting.
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events),
`PATCH /realm`: Added new `move_messages_between_streams_limit_seconds` setting.
* [`PATCH /messages/{message_id}`](/api/update-message): Time limit to edit
topics, for users other than administrators and moderators, can now be
configured using `move_messages_within_stream_limit_seconds` setting.
* [`PATCH /messages/{message_id}`](/api/update-message): Time limit to move
messages between streams, for users other than administrators and moderators,
can now be configured using `move_messages_between_streams_limit_seconds` setting.
**Feature level 161**
* [`POST /users/me/subscriptions`](/api/subscribe),
[`PATCH /streams/{stream_id}`](/api/update-stream): Added
`can_remove_subscribers_group_id` parameter to support setting and
changing the user group whose members can remove other subscribers
from the specified stream.
* [`DELETE /users/me/subscriptions`](/api/unsubscribe): Expanded the
situations where users can use this endpoint to unsubscribe other
users from a stream to include the case where the current user has
access to the stream and is a member of the user group specified by
the `can_remove_subscribers_group_id` for the stream.
* [`PATCH /streams/{stream_id}`](/api/update-stream): Added
`can_remove_subscribers_group_id` parameter to support
changing `can_remove_subscribers_group` setting.
* [`POST /users/me/subscriptions`](/api/subscribe): Added
`can_remove_subscribers_group_id` parameter to set
`can_remove_subscribers_group` setting while creating
streams.
**Feature level 160**
* `POST /api/v1/jwt/fetch_api_key`: Added new endpoint to fetch API
* `POST /api/v1/jwt/fetch_api_key`: New API endpoint to fetch API
keys using JSON Web Token (JWT) authentication.
* `accounts/login/jwt/`: Adjusted format of requests to undocumented,
optional endpoint for JWT authentication log in support.
* `accounts/login/jwt/`: Adjusted format of requests to this
previously undocumented, optional endpoint for
JWT authentication log in support.
**Feature level 159**
* `PATCH /realm`, [`POST /register`](/api/register-queue),
[`GET /events`](/api/get-events):
Nobody added as an option for the realm settings `edit_topic_policy`
and `move_messages_between_streams_policy`.
* [`PATCH /messages/{message_id}`](/api/update-message): Permission
to edit the stream and/or topic of messages no longer depends on the
realm setting `allow_message_editing`.
* [`PATCH /messages/{message_id}`](/api/update-message): The user who
sent the message can no longer edit the message's topic indefinitely.
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events),
`PATCH /realm`: Nobody added as an option for the realm setting
`edit_topic_policy`.
* [`POST /register`](/api/register-queue), [`GET /events`](/api/get-events),
`PATCH /realm`: Nobody added as an option for the realm setting
`move_messages_between_streams_policy`.
* [`PATCH /messages/{message_id}`](/api/update-message): Permission to edit stream
and topic of messages do not depend on `allow_message_editing` setting now.
* [`PATCH /messages/{message_id}`](/api/update-message): Message senders are not
allowed to edit topics indefinitely now.
Feature level 158 is reserved for future use in 6.x maintenance
Feature levels 157-158 are reserved for future use in 6.x maintenance
releases.
## Changes in Zulip 6.2
**Feature level 157**
* `POST /invites`: Added support for invitations specifying the empty
list as the user's initial stream subscriptions. Previously, this
returned an error. This change was backported from the Zulip 7.0
branch, and thus is available at feature levels 157-158 and 180+.
## Changes in Zulip 6.0
**Feature level 156**
@@ -688,29 +249,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 +321,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 +342,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 +413,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 +509,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**
@@ -967,14 +521,13 @@ No changes; feature level used for Zulip 5.0 release.
/events`](/api/get-events): Improved the format of the
`edit_history` object within message objects. Entries for stream
edits now include a both a `prev_stream` and `stream` field to
indicate the previous and current stream IDs. Prior to this feature
level, only the `prev_stream` field was present. Entries for topic
indicate the previous and current stream IDs. Entries for topic
edits now include both a `prev_topic` and `topic` field to indicate
the previous and current topic, replacing the `prev_subject`
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 +603,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 +676,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 +816,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 +989,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 +1069,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 +1103,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 +1140,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 +1164,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 +1177,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 +1195,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 +1247,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 +1258,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 +1280,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 +1297,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 +1307,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
@@ -1837,12 +1383,11 @@ No changes; feature level used for Zulip 3.0 release.
`POST /register` to make them accessible to all the clients;
they were only internally available to Zulip's web app prior to this.
**Feature level 3**
**Feature level 3**:
* [`POST /register`](/api/register-queue): `zulip_version` and
`zulip_feature_level` are always returned in the endpoint response.
Previously, they were only present if `event_types` included
`zulip_version`.
* `zulip_version` and `zulip_feature_level` are always returned
in `POST /register`; previously they were only returned if `event_types`
included `zulip_version`.
* Added new `presence_enabled` user notification setting; previously
[presence](/help/status-and-availability) was always enabled.
@@ -1859,15 +1404,6 @@ No changes; feature level used for Zulip 3.0 release.
**Feature level 1**:
* [`PATCH /messages/{message_id}`](/api/update-message): Added the
`stream_id` parameter to support moving messages between streams.
* [`GET /messages`](/api/get-messages), [`GET /events`](/api/get-events):
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):
`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
`zulip_feature_level`, which can be used by clients to detect which
of the features described in this changelog are supported.
@@ -1914,8 +1450,6 @@ No changes; feature level used for Zulip 3.0 release.
## Changes in Zulip 2.1
* [`POST /register`](/api/register-queue): Added
`realm_default_external_accounts` to endpoint response.
* [`GET /messages`](/api/get-messages): Added support for
[search/narrow options](/api/construct-narrow) that use stream/user
IDs to specify a message's sender, its stream, and/or its recipient(s).
@@ -1969,7 +1503,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

@@ -17,17 +17,35 @@ curl -X POST {{ api_url }}/v1/scheduled_messages \
--data-urlencode type=stream \
--data-urlencode to=9 \
--data-urlencode topic=Hello \
--data-urlencode 'content=Nice to meet everyone!' \
--data-urlencode 'content=Thank you for' \
--data-urlencode scheduled_delivery_timestamp=3165826990
# Update a scheduled stream message
curl -X POST {{ api_url }}/v1/scheduled_messages \
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
--data-urlencode type=stream \
--data-urlencode to=9 \
--data-urlencode 'topic=Welcome aboard' \
--data-urlencode 'content=Thank you for the help!' \
--data-urlencode scheduled_delivery_timestamp=3165856990 \
--data-urlencode scheduled_message_id=1
# Create a scheduled direct message
curl -X POST {{ api_url }}/v1/messages \
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
--data-urlencode type=direct \
--data-urlencode 'to=[9, 10]' \
--data-urlencode 'content=Can we meet on Monday?' \
--data-urlencode 'content=Can we meet tomorrow?' \
--data-urlencode scheduled_delivery_timestamp=3165826990
# Update a scheduled direct message
curl -X POST {{ api_url }}/v1/messages \
-u BOT_EMAIL_ADDRESS:BOT_API_KEY \
--data-urlencode type=direct \
--data-urlencode 'to=[9, 10, 11]' \
--data-urlencode 'content=Can we meet tomorrow?' \
--data-urlencode scheduled_delivery_timestamp=3165856990 \
--data-urlencode scheduled_message_id=2
```
{end_tabs}

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,16 +14,13 @@
* [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
* [Get scheduled messages](/api/get-scheduled-messages)
* [Create a scheduled message](/api/create-scheduled-message)
* [Edit a scheduled message](/api/update-scheduled-message)
* [Create or edit a scheduled message](/api/create-or-update-scheduled-message)
* [Delete a scheduled message](/api/delete-scheduled-message)
#### Drafts
@@ -47,7 +44,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 +91,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,

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