mirror of
https://github.com/zulip/zulip.git
synced 2025-10-25 00:53:56 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79c247fde1 |
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/1_discussed_on_czo.md
vendored
10
.github/ISSUE_TEMPLATE/1_discussed_on_czo.md
vendored
@@ -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
|
||||
17
.github/ISSUE_TEMPLATE/2_bug_report.md
vendored
17
.github/ISSUE_TEMPLATE/2_bug_report.md
vendored
@@ -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
|
||||
6
.github/ISSUE_TEMPLATE/3_feature_request.md
vendored
6
.github/ISSUE_TEMPLATE/3_feature_request.md
vendored
@@ -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. -->
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
14
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -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!
|
||||
30
.github/workflows/production-suite.yml
vendored
30
.github/workflows/production-suite.yml
vendored
@@ -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
|
||||
|
||||
|
||||
120
.github/workflows/zulip-ci.yml
vendored
120
.github/workflows/zulip-ci.yml
vendored
@@ -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
5
.gitignore
vendored
@@ -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
|
||||
|
||||
48
.mailmap
48
.mailmap
@@ -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>
|
||||
|
||||
@@ -136,8 +136,7 @@ Here are some guidelines for you how can help:
|
||||
|
||||
I’ve 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 someone’s behavior is poor or disrespectful.
|
||||
and positive tone even when someone‘s 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.
|
||||
|
||||
140
CONTRIBUTING.md
140
CONTRIBUTING.md
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,7 +17,7 @@ Come find us on the [development community chat](https://zulip.com/development-c
|
||||
[](https://github.com/zulip/zulip/actions/workflows/zulip-ci.yml?query=branch%3Amain)
|
||||
[](https://codecov.io/gh/zulip/zulip)
|
||||
[][mypy-coverage]
|
||||
[](https://github.com/astral-sh/ruff)
|
||||
[](https://github.com/charliermarsh/ruff)
|
||||
[](https://github.com/psf/black)
|
||||
[](https://github.com/prettier/prettier)
|
||||
[](https://github.com/zulip/zulip/releases/latest)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -30,5 +30,4 @@ def time_range(
|
||||
while current >= start:
|
||||
times.append(current)
|
||||
current -= step
|
||||
times.reverse()
|
||||
return times
|
||||
return list(reversed(times))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -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}"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
[
|
||||
|
||||
@@ -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}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
89
api_docs/mark-all-as-read.md
Normal file
89
api_docs/mark-all-as-read.md
Normal 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}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -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=""),
|
||||
),
|
||||
]
|
||||
@@ -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="")
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
@@ -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": [],
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user