mirror of
https://github.com/zulip/zulip.git
synced 2025-10-27 10:03:56 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a6eefcc5a | ||
|
|
3011229a2e |
@@ -1,86 +1,7 @@
|
||||
# See https://zulip.readthedocs.io/en/latest/testing/continuous-integration.html for
|
||||
# high-level documentation on our CircleCI setup.
|
||||
# See CircleCI upstream's docs on this config format:
|
||||
# https://circleci.com/docs/2.0/language-python/
|
||||
#
|
||||
version: 2
|
||||
aliases:
|
||||
- &create_cache_directories
|
||||
run:
|
||||
name: create cache directories
|
||||
command: |
|
||||
dirs=(/srv/zulip-{npm,venv}-cache)
|
||||
sudo mkdir -p "${dirs[@]}"
|
||||
sudo chown -R circleci "${dirs[@]}"
|
||||
|
||||
- &restore_cache_package_json
|
||||
restore_cache:
|
||||
keys:
|
||||
- v1-npm-base.{{ .Environment.CIRCLE_JOB }}-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
- &restore_cache_requirements
|
||||
restore_cache:
|
||||
keys:
|
||||
- v1-venv-base.{{ .Environment.CIRCLE_JOB }}-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- &install_dependencies
|
||||
run:
|
||||
name: install dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
# Install moreutils so we can use `ts` and `mispipe` in the following.
|
||||
sudo apt-get install -y moreutils
|
||||
|
||||
# CircleCI sets the following in Git config at clone time:
|
||||
# url.ssh://git@github.com.insteadOf https://github.com
|
||||
# This breaks the Git clones in the NVM `install.sh` we run
|
||||
# in `install-node`.
|
||||
# TODO: figure out why that breaks, and whether we want it.
|
||||
# (Is it an optimization?)
|
||||
rm -f /home/circleci/.gitconfig
|
||||
|
||||
# This is the main setup job for the test suite
|
||||
mispipe "tools/ci/setup-backend" ts
|
||||
|
||||
# Cleaning caches is mostly unnecessary in Circle, because
|
||||
# most builds don't get to write to the cache.
|
||||
# mispipe "scripts/lib/clean-unused-caches --verbose --threshold 0" ts
|
||||
|
||||
- &save_cache_package_json
|
||||
save_cache:
|
||||
paths:
|
||||
- /srv/zulip-npm-cache
|
||||
key: v1-npm-base.{{ .Environment.CIRCLE_JOB }}-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
- &save_cache_requirements
|
||||
save_cache:
|
||||
paths:
|
||||
- /srv/zulip-venv-cache
|
||||
key: v1-venv-base.{{ .Environment.CIRCLE_JOB }}-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
# TODO: in Travis we also cache ~/zulip-emoji-cache, ~/node, ~/misc
|
||||
|
||||
- &run_backend_tests
|
||||
run:
|
||||
name: run backend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/ci/backend ts
|
||||
|
||||
- &run_frontend_tests
|
||||
run:
|
||||
name: run frontend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/ci/frontend ts
|
||||
|
||||
- &upload_coverage_report
|
||||
run:
|
||||
name: upload coverage report
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
pip install codecov && codecov \
|
||||
|| echo "Error in uploading coverage reports to codecov.io."
|
||||
|
||||
jobs:
|
||||
"trusty-python-3.4":
|
||||
docker:
|
||||
@@ -92,15 +13,71 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- *create_cache_directories
|
||||
- *restore_cache_package_json
|
||||
- *restore_cache_requirements
|
||||
- *install_dependencies
|
||||
- *save_cache_package_json
|
||||
- *save_cache_requirements
|
||||
- *run_backend_tests
|
||||
- *run_frontend_tests
|
||||
- *upload_coverage_report
|
||||
- run:
|
||||
name: create cache directories
|
||||
command: |
|
||||
dirs=(/srv/zulip-{npm,venv}-cache)
|
||||
sudo mkdir -p "${dirs[@]}"
|
||||
sudo chown -R circleci "${dirs[@]}"
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-npm-base.trusty-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-venv-base.trusty-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- run:
|
||||
name: install dependencies
|
||||
command: |
|
||||
# Install moreutils so we can use `ts` and `mispipe` in the following.
|
||||
sudo apt-get install -y moreutils
|
||||
|
||||
# CircleCI sets the following in Git config at clone time:
|
||||
# url.ssh://git@github.com.insteadOf https://github.com
|
||||
# This breaks the Git clones in the NVM `install.sh` we run
|
||||
# in `install-node`.
|
||||
# TODO: figure out why that breaks, and whether we want it.
|
||||
# (Is it an optimization?)
|
||||
rm -f /home/circleci/.gitconfig
|
||||
|
||||
# This is the main setup job for the test suite
|
||||
mispipe "tools/travis/setup-backend" ts
|
||||
|
||||
# Cleaning caches is mostly unnecessary in Circle, because
|
||||
# most builds don't get to write to the cache.
|
||||
# mispipe "scripts/lib/clean-unused-caches --verbose --threshold 0" ts
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-npm-cache
|
||||
key: v1-npm-base.trusty-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-venv-cache
|
||||
key: v1-venv-base.trusty-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
# TODO: in Travis we also cache ~/zulip-emoji-cache, ~/node, ~/misc
|
||||
|
||||
# The moment of truth! Run the tests.
|
||||
|
||||
- run:
|
||||
name: run backend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/travis/backend ts
|
||||
|
||||
- run:
|
||||
name: run frontend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/travis/frontend ts
|
||||
|
||||
- run:
|
||||
name: upload coverage report
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
pip install codecov && codecov \
|
||||
|| echo "Error in uploading coverage reports to codecov.io."
|
||||
|
||||
# - store_artifacts: # TODO
|
||||
# path: var/casper/
|
||||
@@ -116,14 +93,50 @@ jobs:
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- *create_cache_directories
|
||||
- *restore_cache_package_json
|
||||
- *restore_cache_requirements
|
||||
- *install_dependencies
|
||||
- *save_cache_package_json
|
||||
- *save_cache_requirements
|
||||
- *run_backend_tests
|
||||
- *upload_coverage_report
|
||||
|
||||
- run:
|
||||
name: create cache directories
|
||||
command: |
|
||||
dirs=(/srv/zulip-{npm,venv}-cache)
|
||||
sudo mkdir -p "${dirs[@]}"
|
||||
sudo chown -R circleci "${dirs[@]}"
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-npm-base.xenial-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-venv-base.xenial-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- run:
|
||||
name: install dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y moreutils
|
||||
rm -f /home/circleci/.gitconfig
|
||||
mispipe "tools/travis/setup-backend" ts
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-npm-cache
|
||||
key: v1-npm-base.xenial-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-venv-cache
|
||||
key: v1-venv-base.xenial-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- run:
|
||||
name: run backend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/travis/backend ts
|
||||
|
||||
- run:
|
||||
name: upload coverage report
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
pip install codecov && codecov \
|
||||
|| echo "Error in uploading coverage reports to codecov.io."
|
||||
|
||||
"bionic-python-3.6":
|
||||
docker:
|
||||
@@ -135,22 +148,53 @@ jobs:
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- *create_cache_directories
|
||||
|
||||
- run:
|
||||
name: do Bionic hack
|
||||
name: create cache directories
|
||||
command: |
|
||||
dirs=(/srv/zulip-{npm,venv}-cache)
|
||||
sudo mkdir -p "${dirs[@]}"
|
||||
sudo chown -R circleci "${dirs[@]}"
|
||||
# Temporary hack till `sudo service redis-server start` gets fixes in Bionic. See
|
||||
# https://chat.zulip.org/#narrow/stream/3-backend/topic/Ubuntu.20bionic.20CircleCI
|
||||
redis-server --daemonize yes
|
||||
|
||||
- *restore_cache_package_json
|
||||
- *restore_cache_requirements
|
||||
- *install_dependencies
|
||||
- *save_cache_package_json
|
||||
- *save_cache_requirements
|
||||
- *run_backend_tests
|
||||
- *upload_coverage_report
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-npm-base.bionic-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-venv-base.bionic-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- run:
|
||||
name: install dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y moreutils
|
||||
rm -f /home/circleci/.gitconfig
|
||||
mispipe "tools/travis/setup-backend" ts
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-npm-cache
|
||||
key: v1-npm-base.bionic-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }}
|
||||
- save_cache:
|
||||
paths:
|
||||
- /srv/zulip-venv-cache
|
||||
key: v1-venv-base.bionic-{{ checksum "requirements/thumbor.txt" }}-{{ checksum "requirements/dev.txt" }}
|
||||
|
||||
- run:
|
||||
name: run backend tests
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
mispipe ./tools/travis/backend ts
|
||||
|
||||
- run:
|
||||
name: upload coverage report
|
||||
command: |
|
||||
. /srv/zulip-py3-venv/bin/activate
|
||||
pip install codecov && codecov \
|
||||
|| echo "Error in uploading coverage reports to codecov.io."
|
||||
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
"_": false,
|
||||
"activity": false,
|
||||
"admin": false,
|
||||
"admin_sections": false,
|
||||
"alert_words": false,
|
||||
"alert_words_ui": false,
|
||||
"attachments_ui": false,
|
||||
"avatar": false,
|
||||
"billing": false,
|
||||
"blueslip": false,
|
||||
"bot_data": false,
|
||||
"bridge": false,
|
||||
@@ -38,7 +38,6 @@
|
||||
"buddy_list": false,
|
||||
"channel": false,
|
||||
"click_handlers": false,
|
||||
"color_data": false,
|
||||
"colorspace": false,
|
||||
"common": false,
|
||||
"components": false,
|
||||
@@ -50,7 +49,6 @@
|
||||
"compose_ui": false,
|
||||
"composebox_typeahead": false,
|
||||
"condense": false,
|
||||
"confirm_dialog": false,
|
||||
"copy_and_paste": false,
|
||||
"csrf_token": false,
|
||||
"current_msg_list": true,
|
||||
@@ -61,14 +59,12 @@
|
||||
"emoji_picker": false,
|
||||
"favicon": false,
|
||||
"feature_flags": false,
|
||||
"feedback_widget": false,
|
||||
"fenced_code": false,
|
||||
"flatpickr": false,
|
||||
"floating_recipient_bar": false,
|
||||
"gear_menu": false,
|
||||
"hash_util": false,
|
||||
"hashchange": false,
|
||||
"helpers": false,
|
||||
"home_msg_list": false,
|
||||
"hotspots": false,
|
||||
"i18n": false,
|
||||
@@ -120,8 +116,6 @@
|
||||
"pygments_data": false,
|
||||
"reactions": false,
|
||||
"realm_icon": false,
|
||||
"realm_logo": false,
|
||||
"realm_night_logo": false,
|
||||
"recent_senders": false,
|
||||
"reload": false,
|
||||
"reload_state": false,
|
||||
@@ -146,7 +140,7 @@
|
||||
"settings_bots": false,
|
||||
"settings_display": false,
|
||||
"settings_emoji": false,
|
||||
"settings_linkifiers": false,
|
||||
"settings_filters": false,
|
||||
"settings_invites": false,
|
||||
"settings_muting": false,
|
||||
"settings_notifications": false,
|
||||
@@ -159,7 +153,6 @@
|
||||
"settings_ui": false,
|
||||
"settings_user_groups": false,
|
||||
"settings_users": false,
|
||||
"starred_messages": false,
|
||||
"stream_color": false,
|
||||
"stream_create": false,
|
||||
"stream_data": false,
|
||||
@@ -169,7 +162,6 @@
|
||||
"stream_muting": false,
|
||||
"stream_popover": false,
|
||||
"stream_sort": false,
|
||||
"StripeCheckout": false,
|
||||
"submessage": false,
|
||||
"subs": false,
|
||||
"tab_bar": false,
|
||||
@@ -182,7 +174,6 @@
|
||||
"topic_data": false,
|
||||
"topic_generator": false,
|
||||
"topic_list": false,
|
||||
"topic_zoom": false,
|
||||
"transmit": false,
|
||||
"tutorial": false,
|
||||
"typeahead_helper": false,
|
||||
@@ -191,23 +182,19 @@
|
||||
"typing_events": false,
|
||||
"typing_status": false,
|
||||
"ui": false,
|
||||
"ui_init": false,
|
||||
"ui_report": false,
|
||||
"ui_util": false,
|
||||
"unread": false,
|
||||
"unread_ops": false,
|
||||
"unread_ui": false,
|
||||
"upgrade": false,
|
||||
"upload": false,
|
||||
"upload_widget": false,
|
||||
"user_events": false,
|
||||
"user_groups": false,
|
||||
"user_pill": false,
|
||||
"user_search": false,
|
||||
"user_status": false,
|
||||
"user_status_ui": false,
|
||||
"util": false,
|
||||
"poll_widget": false,
|
||||
"voting_widget": false,
|
||||
"widgetize": false,
|
||||
"zcommand": false,
|
||||
"zform": false,
|
||||
@@ -232,12 +219,6 @@
|
||||
"functions": "never"
|
||||
}
|
||||
],
|
||||
"comma-spacing": [ "error",
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"complexity": [ 0, 4 ],
|
||||
"curly": 2,
|
||||
"dot-notation": [ "error", { "allowKeywords": true } ],
|
||||
@@ -255,12 +236,6 @@
|
||||
"FunctionExpression": {"parameters": "first"},
|
||||
"FunctionDeclaration": {"parameters": "first"}
|
||||
}],
|
||||
"key-spacing": [ "error",
|
||||
{
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}
|
||||
],
|
||||
"keyword-spacing": [ "error",
|
||||
{
|
||||
"before": true,
|
||||
@@ -387,7 +362,6 @@
|
||||
"quotes": [ 0, "single" ],
|
||||
"radix": 2,
|
||||
"semi": 2,
|
||||
"semi-spacing": [2, {"before": false, "after": true}],
|
||||
"space-before-blocks": 2,
|
||||
"space-before-function-paren": [ "error",
|
||||
{
|
||||
|
||||
@@ -59,9 +59,5 @@
|
||||
|
||||
"comment-whitespace-inside": "always",
|
||||
"indentation": 4,
|
||||
|
||||
# Limit language features
|
||||
"color-no-hex": true,
|
||||
"color-named": "never",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# See https://zulip.readthedocs.io/en/latest/testing/continuous-integration.html for
|
||||
# See https://zulip.readthedocs.io/en/latest/testing/travis.html for
|
||||
# high-level documentation on our Travis CI setup.
|
||||
dist: trusty
|
||||
group: deprecated-2017Q4
|
||||
@@ -15,7 +15,7 @@ install:
|
||||
- mispipe "pip install codecov" ts || mispipe "pip install codecov" ts
|
||||
|
||||
# This is the main setup job for the test suite
|
||||
- mispipe "tools/ci/setup-$TEST_SUITE" ts
|
||||
- mispipe "tools/travis/setup-$TEST_SUITE" ts
|
||||
|
||||
# Clean any caches that are not in use to avoid our cache
|
||||
# becoming huge.
|
||||
@@ -26,7 +26,7 @@ script:
|
||||
# broken running their system puppet with Ruby. See
|
||||
# https://travis-ci.org/zulip/zulip/jobs/240120991 for an example traceback.
|
||||
- unset GEM_PATH
|
||||
- mispipe "./tools/ci/$TEST_SUITE" ts
|
||||
- mispipe "./tools/travis/$TEST_SUITE" ts
|
||||
cache:
|
||||
yarn: true
|
||||
apt: false
|
||||
@@ -38,7 +38,7 @@ cache:
|
||||
- $HOME/misc
|
||||
env:
|
||||
global:
|
||||
- BOTO_CONFIG=/nonexistent
|
||||
- BOTO_CONFIG=/tmp/nowhere
|
||||
language: python
|
||||
# Our test suites generally run on Python 3.4, the version in
|
||||
# Ubuntu 14.04 trusty, which is the oldest OS release we support.
|
||||
|
||||
@@ -31,7 +31,7 @@ needs doing:
|
||||
[desktop app](https://github.com/zulip/zulip-electron).
|
||||
* Building out our
|
||||
[Python API and bots](https://github.com/zulip/python-zulip-api) framework.
|
||||
* [Writing an integration](https://zulipchat.com/api/integrations-overview).
|
||||
* [Writing an integration](https://zulipchat.com/api/integration-guide).
|
||||
* Improving our [user](https://zulipchat.com/help/) or
|
||||
[developer](https://zulip.readthedocs.io/en/latest/) documentation.
|
||||
* [Reviewing code](https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html)
|
||||
@@ -69,11 +69,10 @@ to help.
|
||||
if you run into any troubles.
|
||||
* Read the
|
||||
[Zulip guide to Git](https://zulip.readthedocs.io/en/latest/git/index.html)
|
||||
and do the Git tutorial (coming soon) if you are unfamiliar with
|
||||
Git, getting help in
|
||||
[#git help](https://chat.zulip.org/#narrow/stream/44-git-help) if
|
||||
you run into any troubles. Be sure to check out the
|
||||
[extremely useful Zulip-specific tools page](https://zulip.readthedocs.io/en/latest/git/zulip-tools.html).
|
||||
and do the Git tutorial (coming soon) if you are unfamiliar with Git,
|
||||
getting help in
|
||||
[#git help](https://chat.zulip.org/#narrow/stream/44-git-help) if you run
|
||||
into any troubles.
|
||||
* Sign the
|
||||
[Dropbox Contributor License Agreement](https://opensource.dropbox.com/cla/).
|
||||
|
||||
@@ -120,10 +119,8 @@ Other notes:
|
||||
than a bigger one. Many first contributions have fewer than 10 lines of
|
||||
changes (not counting changes to tests).
|
||||
* The full list of issues looking for a contributor can be found with the
|
||||
[good first issue](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
||||
and
|
||||
[help wanted](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22)
|
||||
labels.
|
||||
label.
|
||||
* For most new contributors, there's a lot to learn while making your first
|
||||
pull request. It's OK if it takes you a while; that's normal! You'll be
|
||||
able to work a lot faster as you build experience.
|
||||
@@ -135,12 +132,6 @@ the issue thread. [Zulipbot](https://github.com/zulip/zulipbot) is a GitHub
|
||||
workflow bot; it will assign you to the issue and label the issue as "in
|
||||
progress". Some additional notes:
|
||||
|
||||
* You can only claim issues with the
|
||||
[good first issue](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)
|
||||
or
|
||||
[help wanted](https://github.com/zulip/zulip/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22)
|
||||
labels. Zulipbot will give you an error if you try to claim an issue
|
||||
without one of those labels.
|
||||
* You're encouraged to ask questions on how to best implement or debug your
|
||||
changes -- the Zulip maintainers are excited to answer questions to help
|
||||
you stay unblocked and working efficiently. You can ask questions on
|
||||
|
||||
@@ -4,14 +4,14 @@ EXPOSE 9991
|
||||
|
||||
RUN apt-get update && apt-get install -y wget
|
||||
|
||||
RUN localedef -i en_US -f UTF-8 en_US.UTF-8
|
||||
RUN locale-gen en_US.UTF-8
|
||||
|
||||
RUN useradd -d /home/zulip -m zulip && echo 'zulip ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||
|
||||
USER zulip
|
||||
|
||||
RUN ln -nsf /srv/zulip ~/zulip
|
||||
|
||||
RUN echo 'export LC_ALL="en_US.UTF-8" LANG="en_US.UTF-8" LANGUAGE="en_US.UTF-8"' >> ~zulip/.bashrc
|
||||
RUN echo 'export LC_ALL="en_US.UTF-8" LANG="en_US.UTF-8" LANGUAGE="en_US.UTF-8"' >> ~zulip/.bash_profile
|
||||
|
||||
WORKDIR /srv/zulip
|
||||
|
||||
47
LICENSE
47
LICENSE
@@ -1,4 +1,24 @@
|
||||
Copyright 2011-2018 Dropbox, Inc., Kandra Labs, Inc., and contributors
|
||||
Copyright 2011-2017 Dropbox, Inc., Kandra Labs, Inc., and contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
The software includes some works released by third parties under other
|
||||
free and open source licenses. Those works are redistributed under the
|
||||
license terms under which the works were received. For more details,
|
||||
see the ``docs/THIRDPARTY`` file included with this distribution.
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
@@ -176,28 +196,3 @@ Copyright 2011-2018 Dropbox, Inc., Kandra Labs, Inc., and contributors
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -8,11 +8,10 @@ allows users to easily process hundreds or thousands of messages a day. With
|
||||
over 300 contributors merging over 500 commits a month, Zulip is also the
|
||||
largest and fastest growing open source group chat project.
|
||||
|
||||
[](https://circleci.com/gh/zulip/zulip)
|
||||
[](https://circleci.com/gh/zulip/zulip)
|
||||
[](https://travis-ci.org/zulip/zulip)
|
||||
[](https://codecov.io/gh/zulip/zulip)
|
||||
[][mypy-coverage]
|
||||
[](https://github.com/zulip/zulip/releases/latest)
|
||||
[](https://zulip.readthedocs.io/en/latest/)
|
||||
[](https://chat.zulip.org)
|
||||
[](https://twitter.com/zulip)
|
||||
@@ -59,7 +58,7 @@ You might be interested in:
|
||||
* **Running a Zulip server**. Setting up a server takes just a couple
|
||||
of minutes. Zulip runs on Ubuntu 18.04 Bionic, Ubuntu 16.04 Xenial,
|
||||
Ubuntu 14.04 Trusty, and Debian 9 Stretch. The installation process is
|
||||
[documented here](https://zulip.readthedocs.io/en/stable/production/install.html).
|
||||
[documented here](https://zulip.readthedocs.io/en/stable/prod.html).
|
||||
Commercial support is available; see <https://zulipchat.com/plans>
|
||||
for details.
|
||||
|
||||
|
||||
2
Vagrantfile
vendored
2
Vagrantfile
vendored
@@ -202,7 +202,7 @@ if [ ! -w /srv/zulip ]; then
|
||||
# sudo is required since our uid is not 1000
|
||||
echo ' vagrant halt -f'
|
||||
echo ' rm -rf /PATH/TO/ZULIP/CLONE/.vagrant'
|
||||
echo ' sudo chown -R 1000:$(id -g) /PATH/TO/ZULIP/CLONE'
|
||||
echo ' sudo chown -R 1000:$(whoami) /PATH/TO/ZULIP/CLONE'
|
||||
echo "Replace /PATH/TO/ZULIP/CLONE with the path to where zulip code is cloned."
|
||||
echo "You can resume setting up your vagrant environment by running:"
|
||||
echo " vagrant up"
|
||||
|
||||
@@ -2,14 +2,14 @@ import time
|
||||
from collections import OrderedDict, defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Callable, Dict, List, \
|
||||
from typing import Any, Callable, Dict, List, \
|
||||
Optional, Tuple, Type, Union
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
from django.db import connection, models
|
||||
from django.db.models import F
|
||||
|
||||
from analytics.models import BaseCount, \
|
||||
from analytics.models import Anomaly, BaseCount, \
|
||||
FillState, InstallationCount, RealmCount, StreamCount, \
|
||||
UserCount, installation_epoch, last_successful_fill
|
||||
from zerver.lib.logging_util import log_to_file
|
||||
@@ -226,6 +226,7 @@ def do_drop_all_analytics_tables() -> None:
|
||||
RealmCount.objects.all().delete()
|
||||
InstallationCount.objects.all().delete()
|
||||
FillState.objects.all().delete()
|
||||
Anomaly.objects.all().delete()
|
||||
|
||||
def do_drop_single_stat(property: str) -> None:
|
||||
UserCount.objects.filter(property=property).delete()
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from argparse import ArgumentParser
|
||||
from datetime import timedelta
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.timezone import now as timezone_now
|
||||
|
||||
from analytics.models import installation_epoch, \
|
||||
from analytics.models import InstallationCount, installation_epoch, \
|
||||
last_successful_fill
|
||||
from analytics.lib.counts import COUNT_STATS, CountStat
|
||||
from zerver.lib.timestamp import floor_to_hour, floor_to_day, verify_UTC, \
|
||||
@@ -11,6 +12,7 @@ from zerver.lib.timestamp import floor_to_hour, floor_to_day, verify_UTC, \
|
||||
from zerver.models import Realm
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, Dict
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Mapping, Optional, Type
|
||||
from typing import Any, Dict, List, Mapping, Optional, Type, Union
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.timezone import now as timezone_now
|
||||
@@ -11,10 +11,9 @@ from analytics.lib.fixtures import generate_time_series_data
|
||||
from analytics.lib.time_utils import time_range
|
||||
from analytics.models import BaseCount, FillState, RealmCount, UserCount, \
|
||||
StreamCount, InstallationCount
|
||||
from zerver.lib.actions import do_change_is_admin, STREAM_ASSIGNMENT_COLORS
|
||||
from zerver.lib.timestamp import floor_to_day
|
||||
from zerver.models import Realm, UserProfile, Stream, Client, \
|
||||
RealmAuditLog, Recipient, Subscription
|
||||
from zerver.models import Realm, UserProfile, Stream, Message, Client, \
|
||||
RealmAuditLog, Recipient
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = """Populates analytics tables with randomly generated data."""
|
||||
@@ -69,19 +68,9 @@ class Command(BaseCommand):
|
||||
realm = Realm.objects.create(
|
||||
string_id='analytics', name='Analytics', date_created=installation_time)
|
||||
shylock = self.create_user('shylock@analytics.ds', 'Shylock', True, installation_time, realm)
|
||||
do_change_is_admin(shylock, True)
|
||||
stream = Stream.objects.create(
|
||||
name='all', realm=realm, date_created=installation_time)
|
||||
recipient = Recipient.objects.create(type_id=stream.id, type=Recipient.STREAM)
|
||||
|
||||
# Subscribe shylock to the stream to avoid invariant failures.
|
||||
# TODO: This should use subscribe_users_to_streams from populate_db.
|
||||
subs = [
|
||||
Subscription(recipient=recipient,
|
||||
user_profile=shylock,
|
||||
color=STREAM_ASSIGNMENT_COLORS[0]),
|
||||
]
|
||||
Subscription.objects.bulk_create(subs)
|
||||
Recipient.objects.create(type_id=stream.id, type=Recipient.STREAM)
|
||||
|
||||
def insert_fixture_data(stat: CountStat,
|
||||
fixture_data: Mapping[Optional[str], List[int]],
|
||||
|
||||
@@ -2,6 +2,7 @@ import datetime
|
||||
from argparse import ArgumentParser
|
||||
from typing import Any, List
|
||||
|
||||
import pytz
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import Count
|
||||
from django.utils.timezone import now as timezone_now
|
||||
|
||||
@@ -25,33 +25,20 @@ class Command(BaseCommand):
|
||||
realms = Realm.objects.all()
|
||||
|
||||
for realm in realms:
|
||||
print(realm.string_id)
|
||||
print("------------")
|
||||
print("%25s %15s %10s" % ("stream", "subscribers", "messages"))
|
||||
streams = Stream.objects.filter(realm=realm).exclude(Q(name__istartswith="tutorial-"))
|
||||
# private stream count
|
||||
private_count = 0
|
||||
# public stream count
|
||||
public_count = 0
|
||||
invite_only_count = 0
|
||||
for stream in streams:
|
||||
if stream.invite_only:
|
||||
private_count += 1
|
||||
else:
|
||||
public_count += 1
|
||||
print("------------")
|
||||
print(realm.string_id, end=' ')
|
||||
print("%10s %d public streams and" % ("(", public_count), end=' ')
|
||||
print("%d private streams )" % (private_count,))
|
||||
print("------------")
|
||||
print("%25s %15s %10s %12s" % ("stream", "subscribers", "messages", "type"))
|
||||
|
||||
for stream in streams:
|
||||
if stream.invite_only:
|
||||
stream_type = 'private'
|
||||
else:
|
||||
stream_type = 'public'
|
||||
invite_only_count += 1
|
||||
continue
|
||||
print("%25s" % (stream.name,), end=' ')
|
||||
recipient = Recipient.objects.filter(type=Recipient.STREAM, type_id=stream.id)
|
||||
print("%10d" % (len(Subscription.objects.filter(recipient=recipient,
|
||||
active=True)),), end=' ')
|
||||
num_messages = len(Message.objects.filter(recipient=recipient))
|
||||
print("%12d" % (num_messages,), end=' ')
|
||||
print("%15s" % (stream_type,))
|
||||
print("%12d" % (num_messages,))
|
||||
print("%d private streams" % (invite_only_count,))
|
||||
print("")
|
||||
|
||||
@@ -11,7 +11,6 @@ from django.utils.timezone import utc as timezone_utc
|
||||
|
||||
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_remote_server
|
||||
from zerver.lib.timestamp import floor_to_hour
|
||||
from zerver.models import Realm
|
||||
|
||||
@@ -85,6 +84,3 @@ class Command(BaseCommand):
|
||||
print("Finished updating analytics counts through %s in %.3fs" %
|
||||
(fill_to_time, time.time() - start))
|
||||
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_remote_server()
|
||||
|
||||
@@ -3,6 +3,8 @@ import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
import zerver.lib.str_utils
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db import migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db import migrations, models
|
||||
|
||||
import zerver.lib.str_utils
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db import migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.4 on 2017-01-16 20:50
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2019-02-02 02:47
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('analytics', '0012_add_on_delete'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='installationcount',
|
||||
name='anomaly',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='realmcount',
|
||||
name='anomaly',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='streamcount',
|
||||
name='anomaly',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='usercount',
|
||||
name='anomaly',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Anomaly',
|
||||
),
|
||||
]
|
||||
@@ -1,10 +1,10 @@
|
||||
import datetime
|
||||
from typing import Optional
|
||||
from typing import Any, Dict, Optional, Tuple, Union
|
||||
|
||||
from django.db import models
|
||||
|
||||
from zerver.lib.timestamp import floor_to_day
|
||||
from zerver.models import Realm, Stream, UserProfile
|
||||
from zerver.models import Realm, Recipient, Stream, UserProfile
|
||||
|
||||
class FillState(models.Model):
|
||||
property = models.CharField(max_length=40, unique=True) # type: str
|
||||
@@ -34,6 +34,13 @@ def last_successful_fill(property: str) -> Optional[datetime.datetime]:
|
||||
return fillstate.end_time
|
||||
return fillstate.end_time - datetime.timedelta(hours=1)
|
||||
|
||||
# would only ever make entries here by hand
|
||||
class Anomaly(models.Model):
|
||||
info = models.CharField(max_length=1000) # type: str
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "<Anomaly: %s... %s>" % (self.info, self.id)
|
||||
|
||||
class BaseCount(models.Model):
|
||||
# Note: When inheriting from BaseCount, you may want to rearrange
|
||||
# the order of the columns in the migration to make sure they
|
||||
@@ -42,6 +49,7 @@ class BaseCount(models.Model):
|
||||
subgroup = models.CharField(max_length=16, null=True) # type: Optional[str]
|
||||
end_time = models.DateTimeField() # type: datetime.datetime
|
||||
value = models.BigIntegerField() # type: int
|
||||
anomaly = models.ForeignKey(Anomaly, on_delete=models.SET_NULL, null=True) # type: Optional[Anomaly]
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
||||
|
||||
import ujson
|
||||
from django.apps import apps
|
||||
@@ -10,20 +10,19 @@ from django.test import TestCase
|
||||
from django.utils.timezone import now as timezone_now
|
||||
from django.utils.timezone import utc as timezone_utc
|
||||
|
||||
from analytics.lib.counts import COUNT_STATS, CountStat, \
|
||||
from analytics.lib.counts import COUNT_STATS, CountStat, DataCollector, \
|
||||
DependentCountStat, LoggingCountStat, do_aggregate_to_summary_table, \
|
||||
do_drop_all_analytics_tables, do_drop_single_stat, \
|
||||
do_fill_count_stat_at_hour, do_increment_logging_stat, \
|
||||
process_count_stat, sql_data_collector
|
||||
from analytics.models import BaseCount, \
|
||||
from analytics.models import Anomaly, BaseCount, \
|
||||
FillState, InstallationCount, RealmCount, StreamCount, \
|
||||
UserCount, installation_epoch
|
||||
UserCount, installation_epoch, last_successful_fill
|
||||
from zerver.lib.actions import do_activate_user, do_create_user, \
|
||||
do_deactivate_user, do_reactivate_user, update_user_activity_interval, \
|
||||
do_invite_users, do_revoke_user_invite, do_resend_user_invite_email, \
|
||||
InvitationError
|
||||
from zerver.lib.timestamp import TimezoneNotUTCException, floor_to_day
|
||||
from zerver.lib.topic import DB_TOPIC_NAME
|
||||
from zerver.models import Client, Huddle, Message, Realm, \
|
||||
RealmAuditLog, Recipient, Stream, UserActivityInterval, \
|
||||
UserProfile, get_client, get_user, PreregistrationUser
|
||||
@@ -57,10 +56,7 @@ class AnalyticsTestCase(TestCase):
|
||||
'api_key': '42'}
|
||||
for key, value in defaults.items():
|
||||
kwargs[key] = kwargs.get(key, value)
|
||||
kwargs['delivery_email'] = kwargs['email']
|
||||
user_profile = UserProfile.objects.create(**kwargs)
|
||||
# TODO: Make this pass user_profile.full_clean()
|
||||
return user_profile
|
||||
return UserProfile.objects.create(**kwargs)
|
||||
|
||||
def create_stream_with_recipient(self, **kwargs: Any) -> Tuple[Stream, Recipient]:
|
||||
self.name_counter += 1
|
||||
@@ -86,7 +82,7 @@ class AnalyticsTestCase(TestCase):
|
||||
defaults = {
|
||||
'sender': sender,
|
||||
'recipient': recipient,
|
||||
DB_TOPIC_NAME: 'subject',
|
||||
'subject': 'subject',
|
||||
'content': 'hi',
|
||||
'pub_date': self.TIME_LAST_HOUR,
|
||||
'sending_client': get_client("website")}
|
||||
@@ -846,6 +842,7 @@ class TestDeleteStats(AnalyticsTestCase):
|
||||
RealmCount.objects.create(realm=user.realm, **count_args)
|
||||
InstallationCount.objects.create(**count_args)
|
||||
FillState.objects.create(property='test', end_time=self.TIME_ZERO, state=FillState.DONE)
|
||||
Anomaly.objects.create(info='test anomaly')
|
||||
|
||||
analytics = apps.get_app_config('analytics')
|
||||
for table in list(analytics.models.values()):
|
||||
@@ -868,6 +865,7 @@ class TestDeleteStats(AnalyticsTestCase):
|
||||
InstallationCount.objects.create(**count_args)
|
||||
FillState.objects.create(property='to_delete', end_time=self.TIME_ZERO, state=FillState.DONE)
|
||||
FillState.objects.create(property='to_save', end_time=self.TIME_ZERO, state=FillState.DONE)
|
||||
Anomaly.objects.create(info='test anomaly')
|
||||
|
||||
analytics = apps.get_app_config('analytics')
|
||||
for table in list(analytics.models.values()):
|
||||
@@ -875,8 +873,11 @@ class TestDeleteStats(AnalyticsTestCase):
|
||||
|
||||
do_drop_single_stat('to_delete')
|
||||
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())
|
||||
if table._meta.db_table == 'analytics_anomaly':
|
||||
self.assertTrue(table.objects.exists())
|
||||
else:
|
||||
self.assertFalse(table.objects.filter(property='to_delete').exists())
|
||||
self.assertTrue(table.objects.filter(property='to_save').exists())
|
||||
|
||||
class TestActiveUsersAudit(AnalyticsTestCase):
|
||||
def setUp(self) -> None:
|
||||
@@ -1024,7 +1025,7 @@ class TestActiveUsersAudit(AnalyticsTestCase):
|
||||
self.assertTrue(UserCount.objects.filter(
|
||||
user=user, property=self.current_property, subgroup='false',
|
||||
end_time=end_time, value=1).exists())
|
||||
self.assertFalse(UserCount.objects.filter(user=user2, end_time=end_time).exists())
|
||||
self.assertFalse(UserCount.objects.filter(user=user2).exists())
|
||||
|
||||
class TestRealmActiveHumans(AnalyticsTestCase):
|
||||
def setUp(self) -> None:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Optional
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import mock
|
||||
from django.utils.timezone import utc
|
||||
@@ -8,8 +8,8 @@ from analytics.lib.counts import COUNT_STATS, CountStat
|
||||
from analytics.lib.time_utils import time_range
|
||||
from analytics.models import FillState, \
|
||||
RealmCount, UserCount, last_successful_fill
|
||||
from analytics.views import rewrite_client_arrays, \
|
||||
sort_by_totals, sort_client_labels
|
||||
from analytics.views import get_chart_data, rewrite_client_arrays, \
|
||||
sort_by_totals, sort_client_labels, stats
|
||||
from zerver.lib.test_classes import ZulipTestCase
|
||||
from zerver.lib.timestamp import ceiling_to_day, \
|
||||
ceiling_to_hour, datetime_to_timestamp
|
||||
@@ -24,15 +24,6 @@ class TestStatsEndpoint(ZulipTestCase):
|
||||
# Check that we get something back
|
||||
self.assert_in_response("Zulip analytics for", result)
|
||||
|
||||
def test_guest_user_cant_access_stats(self) -> None:
|
||||
self.user = self.example_user('polonius')
|
||||
self.login(self.user.email)
|
||||
result = self.client_get('/stats')
|
||||
self.assert_json_error(result, "Not allowed for guest users", 400)
|
||||
|
||||
result = self.client_get('/json/analytics/chart_data')
|
||||
self.assert_json_error(result, "Not allowed for guest users", 400)
|
||||
|
||||
def test_stats_for_realm(self) -> None:
|
||||
user_profile = self.example_user('hamlet')
|
||||
self.login(user_profile.email)
|
||||
|
||||
@@ -16,12 +16,6 @@ i18n_urlpatterns = [
|
||||
name='analytics.views.stats_for_realm'),
|
||||
url(r'^stats/installation$', analytics.views.stats_for_installation,
|
||||
name='analytics.views.stats_for_installation'),
|
||||
url(r'^stats/remote/(?P<remote_server_id>[\S]+)/installation$',
|
||||
analytics.views.stats_for_remote_installation,
|
||||
name='analytics.views.stats_for_remote_installation'),
|
||||
url(r'^stats/remote/(?P<remote_server_id>[\S]+)/realm/(?P<remote_realm_id>[\S]+)/$',
|
||||
analytics.views.stats_for_remote_realm,
|
||||
name='analytics.views.stats_for_remote_realm'),
|
||||
|
||||
# User-visible stats page
|
||||
url(r'^stats$', analytics.views.stats,
|
||||
@@ -44,11 +38,6 @@ v1_api_and_json_patterns = [
|
||||
{'GET': 'analytics.views.get_chart_data_for_realm'}),
|
||||
url(r'^analytics/chart_data/installation$', rest_dispatch,
|
||||
{'GET': 'analytics.views.get_chart_data_for_installation'}),
|
||||
url(r'^analytics/chart_data/remote/(?P<remote_server_id>[\S]+)/installation$', rest_dispatch,
|
||||
{'GET': 'analytics.views.get_chart_data_for_remote_installation'}),
|
||||
url(r'^analytics/chart_data/remote/(?P<remote_server_id>[\S]+)/realm/(?P<remote_realm_id>[\S]+)$',
|
||||
rest_dispatch,
|
||||
{'GET': 'analytics.views.get_chart_data_for_remote_realm'}),
|
||||
]
|
||||
|
||||
i18n_urlpatterns += [
|
||||
|
||||
@@ -1,54 +1,47 @@
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Callable, Dict, List, \
|
||||
Optional, Set, Tuple, Type, Union, cast
|
||||
Optional, Set, Tuple, Type, Union
|
||||
|
||||
import pytz
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.db import connection
|
||||
from django.db.models import Sum
|
||||
from django.db.models.query import QuerySet
|
||||
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
|
||||
from django.shortcuts import render
|
||||
from django.template import loader
|
||||
from django.template import RequestContext, loader
|
||||
from django.utils.timezone import now as timezone_now, utc as timezone_utc
|
||||
from django.utils.translation import ugettext as _
|
||||
from jinja2 import Markup as mark_safe
|
||||
|
||||
from analytics.lib.counts import COUNT_STATS, CountStat
|
||||
from analytics.lib.counts import COUNT_STATS, CountStat, process_count_stat
|
||||
from analytics.lib.time_utils import time_range
|
||||
from analytics.models import BaseCount, InstallationCount, \
|
||||
RealmCount, StreamCount, UserCount, last_successful_fill, installation_epoch
|
||||
from zerver.decorator import require_server_admin, require_server_admin_api, \
|
||||
to_non_negative_int, to_utc_datetime, zulip_login_required, require_non_guest_user
|
||||
to_non_negative_int, to_utc_datetime, zulip_login_required
|
||||
from zerver.lib.exceptions import JsonableError
|
||||
from zerver.lib.json_encoder_for_html import JSONEncoderForHTML
|
||||
from zerver.lib.request import REQ, has_request_variables
|
||||
from zerver.lib.response import json_success
|
||||
from zerver.lib.timestamp import convert_to_UTC, timestamp_to_datetime
|
||||
from zerver.lib.timestamp import ceiling_to_day, \
|
||||
ceiling_to_hour, convert_to_UTC, timestamp_to_datetime
|
||||
from zerver.models import Client, get_realm, Realm, \
|
||||
UserActivity, UserActivityInterval, UserProfile
|
||||
|
||||
if settings.ZILENCER_ENABLED:
|
||||
from zilencer.models import RemoteInstallationCount, RemoteRealmCount, \
|
||||
RemoteZulipServer
|
||||
else:
|
||||
from mock import Mock
|
||||
RemoteInstallationCount = Mock() # type: ignore # https://github.com/JukkaL/mypy/issues/1188
|
||||
RemoteZulipServer = Mock() # type: ignore # https://github.com/JukkaL/mypy/issues/1188
|
||||
RemoteRealmCount = Mock() # type: ignore # https://github.com/JukkaL/mypy/issues/1188
|
||||
|
||||
def render_stats(request: HttpRequest, data_url_suffix: str, target_name: str,
|
||||
for_installation: bool=False, remote: bool=False) -> HttpRequest:
|
||||
for_installation: bool=False) -> HttpRequest:
|
||||
page_params = dict(
|
||||
data_url_suffix=data_url_suffix,
|
||||
for_installation=for_installation,
|
||||
remote=remote,
|
||||
debug_mode=False,
|
||||
)
|
||||
return render(request,
|
||||
@@ -59,10 +52,6 @@ def render_stats(request: HttpRequest, data_url_suffix: str, target_name: str,
|
||||
@zulip_login_required
|
||||
def stats(request: HttpRequest) -> HttpResponse:
|
||||
realm = request.user.realm
|
||||
if request.user.is_guest:
|
||||
# TODO: Make @zulip_login_required pass the UserProfile so we
|
||||
# can use @require_non_guest_human_user
|
||||
raise JsonableError(_("Not allowed for guest users"))
|
||||
return render_stats(request, '', realm.name or realm.string_id)
|
||||
|
||||
@require_server_admin
|
||||
@@ -74,14 +63,6 @@ def stats_for_realm(request: HttpRequest, realm_str: str) -> HttpResponse:
|
||||
|
||||
return render_stats(request, '/realm/%s' % (realm_str,), realm.name or realm.string_id)
|
||||
|
||||
@require_server_admin
|
||||
@has_request_variables
|
||||
def stats_for_remote_realm(request: HttpRequest, remote_server_id: str,
|
||||
remote_realm_id: str) -> HttpResponse:
|
||||
server = RemoteZulipServer.objects.get(id=remote_server_id)
|
||||
return render_stats(request, '/remote/%s/realm/%s' % (server.id, remote_realm_id),
|
||||
"Realm %s on server %s" % (remote_realm_id, server.hostname))
|
||||
|
||||
@require_server_admin_api
|
||||
@has_request_variables
|
||||
def get_chart_data_for_realm(request: HttpRequest, user_profile: UserProfile,
|
||||
@@ -92,65 +73,25 @@ def get_chart_data_for_realm(request: HttpRequest, user_profile: UserProfile,
|
||||
|
||||
return get_chart_data(request=request, user_profile=user_profile, realm=realm, **kwargs)
|
||||
|
||||
@require_server_admin_api
|
||||
@has_request_variables
|
||||
def get_chart_data_for_remote_realm(
|
||||
request: HttpRequest, user_profile: UserProfile, remote_server_id: str,
|
||||
remote_realm_id: str, **kwargs: Any) -> HttpResponse:
|
||||
server = RemoteZulipServer.objects.get(id=remote_server_id)
|
||||
return get_chart_data(request=request, user_profile=user_profile, server=server,
|
||||
remote=True, remote_realm_id=int(remote_realm_id), **kwargs)
|
||||
|
||||
@require_server_admin
|
||||
def stats_for_installation(request: HttpRequest) -> HttpResponse:
|
||||
return render_stats(request, '/installation', 'Installation', True)
|
||||
|
||||
@require_server_admin
|
||||
def stats_for_remote_installation(request: HttpRequest, remote_server_id: str) -> HttpResponse:
|
||||
server = RemoteZulipServer.objects.get(id=remote_server_id)
|
||||
return render_stats(request, '/remote/%s/installation' % (server.id,),
|
||||
'remote Installation %s' % (server.hostname), True, True)
|
||||
|
||||
@require_server_admin_api
|
||||
@has_request_variables
|
||||
def get_chart_data_for_installation(request: HttpRequest, user_profile: UserProfile,
|
||||
chart_name: str=REQ(), **kwargs: Any) -> HttpResponse:
|
||||
return get_chart_data(request=request, user_profile=user_profile, for_installation=True, **kwargs)
|
||||
|
||||
@require_server_admin_api
|
||||
@has_request_variables
|
||||
def get_chart_data_for_remote_installation(
|
||||
request: HttpRequest,
|
||||
user_profile: UserProfile,
|
||||
remote_server_id: str,
|
||||
chart_name: str=REQ(),
|
||||
**kwargs: Any) -> HttpResponse:
|
||||
server = RemoteZulipServer.objects.get(id=remote_server_id)
|
||||
return get_chart_data(request=request, user_profile=user_profile, for_installation=True,
|
||||
remote=True, server=server, **kwargs)
|
||||
|
||||
@require_non_guest_user
|
||||
@has_request_variables
|
||||
def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name: str=REQ(),
|
||||
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),
|
||||
realm: Optional[Realm]=None, for_installation: bool=False,
|
||||
remote: bool=False, remote_realm_id: Optional[int]=None,
|
||||
server: Optional[RemoteZulipServer]=None) -> HttpResponse:
|
||||
realm: Optional[Realm]=None, for_installation: bool=False) -> HttpResponse:
|
||||
aggregate_table = RealmCount
|
||||
if for_installation:
|
||||
if remote:
|
||||
aggregate_table = RemoteInstallationCount
|
||||
assert server is not None
|
||||
else:
|
||||
aggregate_table = InstallationCount
|
||||
else:
|
||||
if remote:
|
||||
aggregate_table = RemoteRealmCount
|
||||
assert server is not None
|
||||
assert remote_realm_id is not None
|
||||
else:
|
||||
aggregate_table = RealmCount
|
||||
aggregate_table = InstallationCount
|
||||
|
||||
if chart_name == 'number_of_humans':
|
||||
stats = [
|
||||
@@ -201,60 +142,29 @@ def get_chart_data(request: HttpRequest, user_profile: UserProfile, chart_name:
|
||||
{'start': start, 'end': end})
|
||||
|
||||
if realm is None:
|
||||
# Note that this value is invalid for Remote tables; be
|
||||
# careful not to access it in those code paths.
|
||||
realm = user_profile.realm
|
||||
|
||||
if remote:
|
||||
# For remote servers, we don't have fillstate data, and thus
|
||||
# should simply use the first and last data points for the
|
||||
# table.
|
||||
assert server is not None
|
||||
if not aggregate_table.objects.filter(server=server).exists():
|
||||
raise JsonableError(_("No analytics data available. Please contact your server administrator."))
|
||||
if start is None:
|
||||
start = aggregate_table.objects.filter(server=server).first().end_time
|
||||
if end is None:
|
||||
end = aggregate_table.objects.filter(server=server).last().end_time
|
||||
else:
|
||||
# Otherwise, we can use tables on the current server to
|
||||
# determine a nice range, and some additional validation.
|
||||
if start is None:
|
||||
if for_installation:
|
||||
start = installation_epoch()
|
||||
else:
|
||||
start = realm.date_created
|
||||
if end is None:
|
||||
end = max(last_successful_fill(stat.property) or
|
||||
datetime.min.replace(tzinfo=timezone_utc) for stat in stats)
|
||||
if end is None or start > end:
|
||||
logging.warning("User from realm %s attempted to access /stats, but the computed "
|
||||
"start time: %s (creation of realm or installation) is later than the computed "
|
||||
"end time: %s (last successful analytics update). Is the "
|
||||
"analytics cron job running?" % (realm.string_id, start, end))
|
||||
raise JsonableError(_("No analytics data available. Please contact your server administrator."))
|
||||
if start is None:
|
||||
if for_installation:
|
||||
start = installation_epoch()
|
||||
else:
|
||||
start = realm.date_created
|
||||
if end is None:
|
||||
end = max(last_successful_fill(stat.property) or
|
||||
datetime.min.replace(tzinfo=timezone_utc) for stat in stats)
|
||||
if end is None or start > end:
|
||||
logging.warning("User from realm %s attempted to access /stats, but the computed "
|
||||
"start time: %s (creation of realm or installation) is later than the computed "
|
||||
"end time: %s (last successful analytics update). Is the "
|
||||
"analytics cron job running?" % (realm.string_id, start, end))
|
||||
raise JsonableError(_("No analytics data available. Please contact your server administrator."))
|
||||
|
||||
assert len(set([stat.frequency for stat in stats])) == 1
|
||||
end_times = time_range(start, end, stats[0].frequency, min_length)
|
||||
data = {'end_times': end_times, 'frequency': stats[0].frequency} # type: Dict[str, Any]
|
||||
|
||||
aggregation_level = {
|
||||
InstallationCount: 'everyone',
|
||||
RealmCount: 'everyone',
|
||||
RemoteInstallationCount: 'everyone',
|
||||
RemoteRealmCount: 'everyone',
|
||||
UserCount: 'user',
|
||||
}
|
||||
aggregation_level = {InstallationCount: 'everyone', RealmCount: 'everyone', UserCount: 'user'}
|
||||
# -1 is a placeholder value, since there is no relevant filtering on InstallationCount
|
||||
id_value = {
|
||||
InstallationCount: -1,
|
||||
RealmCount: realm.id,
|
||||
RemoteInstallationCount: cast(RemoteZulipServer, server).id if server is not None else None,
|
||||
# TODO: RemoteRealmCount logic doesn't correctly handle
|
||||
# filtering by server_id as well.
|
||||
RemoteRealmCount: remote_realm_id,
|
||||
UserCount: user_profile.id,
|
||||
}
|
||||
id_value = {InstallationCount: -1, RealmCount: realm.id, UserCount: user_profile.id}
|
||||
for table in tables:
|
||||
data[aggregation_level[table]] = {}
|
||||
for stat in stats:
|
||||
@@ -298,10 +208,6 @@ def table_filtered_to_id(table: Type[BaseCount], key_id: int) -> QuerySet:
|
||||
return StreamCount.objects.filter(stream_id=key_id)
|
||||
elif table == InstallationCount:
|
||||
return InstallationCount.objects.all()
|
||||
elif table == RemoteInstallationCount:
|
||||
return RemoteInstallationCount.objects.filter(server_id=key_id)
|
||||
elif table == RemoteRealmCount:
|
||||
return RemoteRealmCount.objects.filter(realm_id=key_id)
|
||||
else:
|
||||
raise AssertionError("Unknown table: %s" % (table,))
|
||||
|
||||
@@ -449,7 +355,6 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
|
||||
SELECT
|
||||
realm.string_id,
|
||||
realm.date_created,
|
||||
realm.plan_type,
|
||||
coalesce(user_counts.dau_count, 0) dau_count,
|
||||
coalesce(wau_counts.wau_count, 0) wau_count,
|
||||
(
|
||||
@@ -564,8 +469,6 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
|
||||
|
||||
for row in rows:
|
||||
row['date_created_day'] = row['date_created'].strftime('%Y-%m-%d')
|
||||
row['plan_type_string'] = [
|
||||
'', 'self hosted', 'limited', 'standard', 'open source'][row['plan_type']]
|
||||
row['age_days'] = int((now - row['date_created']).total_seconds()
|
||||
/ 86400)
|
||||
row['is_new'] = row['age_days'] < 12 * 7
|
||||
@@ -579,16 +482,6 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
|
||||
except Exception:
|
||||
row['history'] = ''
|
||||
|
||||
# estimate annual subscription revenue
|
||||
total_amount = 0
|
||||
if settings.BILLING_ENABLED:
|
||||
from corporate.lib.stripe import estimate_annual_recurring_revenue_by_realm
|
||||
estimated_arrs = estimate_annual_recurring_revenue_by_realm()
|
||||
for row in rows:
|
||||
if row['string_id'] in estimated_arrs:
|
||||
row['amount'] = estimated_arrs[row['string_id']]
|
||||
total_amount += sum(estimated_arrs.values())
|
||||
|
||||
# augment data with realm_minutes
|
||||
total_hours = 0.0
|
||||
for row in rows:
|
||||
@@ -624,10 +517,8 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
|
||||
total_bot_count += int(row['bot_count'])
|
||||
total_wau_count += int(row['wau_count'])
|
||||
|
||||
total_row = dict(
|
||||
rows.append(dict(
|
||||
string_id='Total',
|
||||
plan_type_string="",
|
||||
amount=total_amount,
|
||||
stats_link = '',
|
||||
date_created_day='',
|
||||
realm_admin_email='',
|
||||
@@ -636,9 +527,7 @@ def realm_summary_table(realm_minutes: Dict[str, float]) -> str:
|
||||
bot_count=total_bot_count,
|
||||
hours=int(total_hours),
|
||||
wau_count=total_wau_count,
|
||||
)
|
||||
|
||||
rows.insert(0, total_row)
|
||||
))
|
||||
|
||||
content = loader.render_to_string(
|
||||
'analytics/realm_summary_table.html',
|
||||
@@ -664,15 +553,15 @@ def user_activity_intervals() -> Tuple[mark_safe, Dict[str, float]]:
|
||||
).only(
|
||||
'start',
|
||||
'end',
|
||||
'user_profile__delivery_email',
|
||||
'user_profile__email',
|
||||
'user_profile__realm__string_id'
|
||||
).order_by(
|
||||
'user_profile__realm__string_id',
|
||||
'user_profile__delivery_email'
|
||||
'user_profile__email'
|
||||
)
|
||||
|
||||
by_string_id = lambda row: row.user_profile.realm.string_id
|
||||
by_email = lambda row: row.user_profile.delivery_email
|
||||
by_email = lambda row: row.user_profile.email
|
||||
|
||||
realm_minutes = {}
|
||||
|
||||
@@ -766,8 +655,7 @@ def sent_messages_report(realm: str) -> str:
|
||||
return make_table(title, cols, rows)
|
||||
|
||||
def ad_hoc_queries() -> List[Dict[str, str]]:
|
||||
def get_page(query: str, cols: List[str], title: str,
|
||||
totals_columns: List[int]=[]) -> Dict[str, str]:
|
||||
def get_page(query: str, cols: List[str], title: str) -> Dict[str, str]:
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(query)
|
||||
rows = cursor.fetchall()
|
||||
@@ -779,24 +667,11 @@ def ad_hoc_queries() -> List[Dict[str, str]]:
|
||||
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)
|
||||
|
||||
@@ -946,49 +821,6 @@ def ad_hoc_queries() -> List[Dict[str, str]]:
|
||||
|
||||
pages.append(get_page(query, cols, title))
|
||||
|
||||
title = 'Remote Zulip servers'
|
||||
|
||||
query = '''
|
||||
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
|
||||
@@ -1014,7 +846,7 @@ def get_activity(request: HttpRequest) -> HttpResponse:
|
||||
def get_user_activity_records_for_realm(realm: str, is_bot: bool) -> QuerySet:
|
||||
fields = [
|
||||
'user_profile__full_name',
|
||||
'user_profile__delivery_email',
|
||||
'user_profile__email',
|
||||
'query',
|
||||
'client__name',
|
||||
'count',
|
||||
@@ -1026,7 +858,7 @@ def get_user_activity_records_for_realm(realm: str, is_bot: bool) -> QuerySet:
|
||||
user_profile__is_active=True,
|
||||
user_profile__is_bot=is_bot
|
||||
)
|
||||
records = records.order_by("user_profile__delivery_email", "-last_visit")
|
||||
records = records.order_by("user_profile__email", "-last_visit")
|
||||
records = records.select_related('user_profile', 'client').only(*fields)
|
||||
return records
|
||||
|
||||
@@ -1040,7 +872,7 @@ def get_user_activity_records_for_email(email: str) -> List[QuerySet]:
|
||||
]
|
||||
|
||||
records = UserActivity.objects.filter(
|
||||
user_profile__delivery_email=email
|
||||
user_profile__email=email
|
||||
)
|
||||
records = records.order_by("-last_visit")
|
||||
records = records.select_related('user_profile', 'client').only(*fields)
|
||||
@@ -1139,12 +971,6 @@ def realm_stats_link(realm_str: str) -> mark_safe:
|
||||
stats_link = '<a href="{}"><i class="fa fa-pie-chart"></i></a>'.format(url, realm_str)
|
||||
return mark_safe(stats_link)
|
||||
|
||||
def remote_installation_stats_link(server_id: int, hostname: str) -> mark_safe:
|
||||
url_name = 'analytics.views.stats_for_remote_installation'
|
||||
url = reverse(url_name, kwargs=dict(remote_server_id=server_id))
|
||||
stats_link = '<a href="{}"><i class="fa fa-pie-chart"></i>{}</a>'.format(url, hostname)
|
||||
return mark_safe(stats_link)
|
||||
|
||||
def realm_client_table(user_summaries: Dict[str, Dict[str, Dict[str, Any]]]) -> str:
|
||||
exclude_keys = [
|
||||
'internal',
|
||||
@@ -1220,7 +1046,7 @@ def realm_user_summary_table(all_records: List[QuerySet],
|
||||
user_records = {}
|
||||
|
||||
def by_email(record: QuerySet) -> str:
|
||||
return record.user_profile.delivery_email
|
||||
return record.user_profile.email
|
||||
|
||||
for email, records in itertools.groupby(all_records, by_email):
|
||||
user_records[email] = get_user_activity_summary(list(records))
|
||||
@@ -1291,7 +1117,7 @@ def get_realm_activity(request: HttpRequest, realm_str: str) -> HttpResponse:
|
||||
except Realm.DoesNotExist:
|
||||
return HttpResponseNotFound("Realm %s does not exist" % (realm_str,))
|
||||
|
||||
admin_emails = {admin.delivery_email for admin in admins}
|
||||
admin_emails = {admin.email for admin in admins}
|
||||
|
||||
for is_bot, page_title in [(False, 'Humans'), (True, 'Bots')]:
|
||||
all_records = list(get_user_activity_records_for_realm(realm_str, is_bot))
|
||||
|
||||
@@ -16,11 +16,12 @@ from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.utils.timezone import now as timezone_now
|
||||
|
||||
from zerver.lib.utils import generate_random_token
|
||||
from zerver.models import PreregistrationUser, EmailChangeStatus, MultiuseInvite, \
|
||||
UserProfile, Realm
|
||||
from random import SystemRandom
|
||||
import string
|
||||
from typing import Dict, Optional, Union
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
class ConfirmationKeyException(Exception):
|
||||
WRONG_LENGTH = 1
|
||||
@@ -69,11 +70,8 @@ def create_confirmation_link(obj: ContentType, host: str,
|
||||
confirmation_type: int,
|
||||
url_args: Optional[Dict[str, str]]=None) -> str:
|
||||
key = generate_key()
|
||||
realm = None
|
||||
if hasattr(obj, 'realm'):
|
||||
realm = obj.realm
|
||||
Confirmation.objects.create(content_object=obj, date_sent=timezone_now(), confirmation_key=key,
|
||||
realm=realm, type=confirmation_type)
|
||||
realm=obj.realm, type=confirmation_type)
|
||||
return confirmation_url(key, host, confirmation_type, url_args)
|
||||
|
||||
def confirmation_url(confirmation_key: str, host: str,
|
||||
@@ -101,7 +99,6 @@ class Confirmation(models.Model):
|
||||
SERVER_REGISTRATION = 5
|
||||
MULTIUSE_INVITE = 6
|
||||
REALM_CREATION = 7
|
||||
REALM_REACTIVATION = 8
|
||||
type = models.PositiveSmallIntegerField() # type: int
|
||||
|
||||
def __str__(self) -> str:
|
||||
@@ -124,18 +121,8 @@ _properties = {
|
||||
'zerver.views.registration.accounts_home_from_multiuse_invite',
|
||||
validity_in_days=settings.INVITATION_LINK_VALIDITY_DAYS),
|
||||
Confirmation.REALM_CREATION: ConfirmationType('check_prereg_key_and_redirect'),
|
||||
Confirmation.REALM_REACTIVATION: ConfirmationType('zerver.views.realm.realm_reactivation'),
|
||||
}
|
||||
|
||||
def one_click_unsubscribe_link(user_profile: UserProfile, email_type: str) -> str:
|
||||
"""
|
||||
Generate a unique link that a logged-out user can visit to unsubscribe from
|
||||
Zulip e-mails without having to first log in.
|
||||
"""
|
||||
return create_confirmation_link(user_profile, user_profile.realm.host,
|
||||
Confirmation.UNSUBSCRIBE,
|
||||
url_args = {'email_type': email_type})
|
||||
|
||||
# Functions related to links generated by the generate_realm_creation_link.py
|
||||
# management command.
|
||||
# Note that being validated here will just allow the user to access the create_realm
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
# Copyright: (c) 2008, Jarek Zgoda <jarek.zgoda@gmail.com>
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
__revision__ = '$Id: settings.py 12 2008-11-23 19:38:52Z jarek.zgoda $'
|
||||
|
||||
STATUS_ACTIVE = 1
|
||||
|
||||
@@ -1,467 +0,0 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from functools import wraps
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
from typing import Any, Callable, Dict, Optional, TypeVar, Tuple, cast
|
||||
import ujson
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.timezone import now as timezone_now
|
||||
from django.core.signing import Signer
|
||||
import stripe
|
||||
|
||||
from zerver.lib.logging_util import log_to_file
|
||||
from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime
|
||||
from zerver.lib.utils import generate_random_token
|
||||
from zerver.models import Realm, UserProfile, RealmAuditLog
|
||||
from corporate.models import Customer, CustomerPlan, LicenseLedger, \
|
||||
get_active_plan
|
||||
from zproject.settings import get_secret
|
||||
|
||||
STRIPE_PUBLISHABLE_KEY = get_secret('stripe_publishable_key')
|
||||
stripe.api_key = get_secret('stripe_secret_key')
|
||||
|
||||
BILLING_LOG_PATH = os.path.join('/var/log/zulip'
|
||||
if not settings.DEVELOPMENT
|
||||
else settings.DEVELOPMENT_LOG_DIRECTORY,
|
||||
'billing.log')
|
||||
billing_logger = logging.getLogger('corporate.stripe')
|
||||
log_to_file(billing_logger, BILLING_LOG_PATH)
|
||||
log_to_file(logging.getLogger('stripe'), BILLING_LOG_PATH)
|
||||
|
||||
CallableT = TypeVar('CallableT', bound=Callable[..., Any])
|
||||
|
||||
MIN_INVOICED_LICENSES = 30
|
||||
DEFAULT_INVOICE_DAYS_UNTIL_DUE = 30
|
||||
|
||||
def get_seat_count(realm: Realm) -> int:
|
||||
non_guests = UserProfile.objects.filter(
|
||||
realm=realm, is_active=True, is_bot=False, is_guest=False).count()
|
||||
guests = UserProfile.objects.filter(
|
||||
realm=realm, is_active=True, is_bot=False, is_guest=True).count()
|
||||
return max(non_guests, math.ceil(guests / 5))
|
||||
|
||||
def sign_string(string: str) -> Tuple[str, str]:
|
||||
salt = generate_random_token(64)
|
||||
signer = Signer(salt=salt)
|
||||
return signer.sign(string), salt
|
||||
|
||||
def unsign_string(signed_string: str, salt: str) -> str:
|
||||
signer = Signer(salt=salt)
|
||||
return signer.unsign(signed_string)
|
||||
|
||||
# Be extremely careful changing this function. Historical billing periods
|
||||
# are not stored anywhere, and are just computed on the fly using this
|
||||
# function. Any change you make here should return the same value (or be
|
||||
# within a few seconds) for basically any value from when the billing system
|
||||
# went online to within a year from now.
|
||||
def add_months(dt: datetime, months: int) -> datetime:
|
||||
assert(months >= 0)
|
||||
# It's fine that the max day in Feb is 28 for leap years.
|
||||
MAX_DAY_FOR_MONTH = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30,
|
||||
7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31}
|
||||
year = dt.year
|
||||
month = dt.month + months
|
||||
while month > 12:
|
||||
year += 1
|
||||
month -= 12
|
||||
day = min(dt.day, MAX_DAY_FOR_MONTH[month])
|
||||
# datetimes don't support leap seconds, so don't need to worry about those
|
||||
return dt.replace(year=year, month=month, day=day)
|
||||
|
||||
def next_month(billing_cycle_anchor: datetime, dt: datetime) -> datetime:
|
||||
estimated_months = round((dt - billing_cycle_anchor).days * 12. / 365)
|
||||
for months in range(max(estimated_months - 1, 0), estimated_months + 2):
|
||||
proposed_next_month = add_months(billing_cycle_anchor, months)
|
||||
if 20 < (proposed_next_month - dt).days < 40:
|
||||
return proposed_next_month
|
||||
raise AssertionError('Something wrong in next_month calculation with '
|
||||
'billing_cycle_anchor: %s, dt: %s' % (billing_cycle_anchor, dt))
|
||||
|
||||
# TODO take downgrade into account
|
||||
def next_renewal_date(plan: CustomerPlan, event_time: datetime) -> datetime:
|
||||
months_per_period = {
|
||||
CustomerPlan.ANNUAL: 12,
|
||||
CustomerPlan.MONTHLY: 1,
|
||||
}[plan.billing_schedule]
|
||||
periods = 1
|
||||
dt = plan.billing_cycle_anchor
|
||||
while dt <= event_time:
|
||||
dt = add_months(plan.billing_cycle_anchor, months_per_period * periods)
|
||||
periods += 1
|
||||
return dt
|
||||
|
||||
# TODO take downgrade into account
|
||||
def next_invoice_date(plan: CustomerPlan) -> datetime:
|
||||
months_per_period = {
|
||||
CustomerPlan.ANNUAL: 12,
|
||||
CustomerPlan.MONTHLY: 1,
|
||||
}[plan.billing_schedule]
|
||||
if plan.automanage_licenses:
|
||||
months_per_period = 1
|
||||
periods = 1
|
||||
dt = plan.billing_cycle_anchor
|
||||
while dt <= plan.next_invoice_date:
|
||||
dt = add_months(plan.billing_cycle_anchor, months_per_period * periods)
|
||||
periods += 1
|
||||
return dt
|
||||
|
||||
def renewal_amount(plan: CustomerPlan, event_time: datetime) -> Optional[int]: # nocoverage: TODO
|
||||
if plan.fixed_price is not None:
|
||||
return plan.fixed_price
|
||||
last_ledger_entry = add_plan_renewal_to_license_ledger_if_needed(plan, event_time)
|
||||
if last_ledger_entry.licenses_at_next_renewal is None:
|
||||
return None
|
||||
assert(plan.price_per_license is not None) # for mypy
|
||||
return plan.price_per_license * last_ledger_entry.licenses_at_next_renewal
|
||||
|
||||
class BillingError(Exception):
|
||||
# error messages
|
||||
CONTACT_SUPPORT = _("Something went wrong. Please contact %s." % (settings.ZULIP_ADMINISTRATOR,))
|
||||
TRY_RELOADING = _("Something went wrong. Please reload the page.")
|
||||
|
||||
# description is used only for tests
|
||||
def __init__(self, description: str, message: str=CONTACT_SUPPORT) -> None:
|
||||
self.description = description
|
||||
self.message = message
|
||||
|
||||
class StripeCardError(BillingError):
|
||||
pass
|
||||
|
||||
class StripeConnectionError(BillingError):
|
||||
pass
|
||||
|
||||
def catch_stripe_errors(func: CallableT) -> CallableT:
|
||||
@wraps(func)
|
||||
def wrapped(*args: Any, **kwargs: Any) -> Any:
|
||||
if settings.DEVELOPMENT and not settings.TEST_SUITE: # nocoverage
|
||||
if STRIPE_PUBLISHABLE_KEY is None:
|
||||
raise BillingError('missing stripe config', "Missing Stripe config. "
|
||||
"See https://zulip.readthedocs.io/en/latest/subsystems/billing.html.")
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
# See https://stripe.com/docs/api/python#error_handling, though
|
||||
# https://stripe.com/docs/api/ruby#error_handling suggests there are additional fields, and
|
||||
# https://stripe.com/docs/error-codes gives a more detailed set of error codes
|
||||
except stripe.error.StripeError as e:
|
||||
err = e.json_body.get('error', {})
|
||||
billing_logger.error("Stripe error: %s %s %s %s" % (
|
||||
e.http_status, err.get('type'), err.get('code'), err.get('param')))
|
||||
if isinstance(e, stripe.error.CardError):
|
||||
# TODO: Look into i18n for this
|
||||
raise StripeCardError('card error', err.get('message'))
|
||||
if isinstance(e, stripe.error.RateLimitError) or \
|
||||
isinstance(e, stripe.error.APIConnectionError): # nocoverage TODO
|
||||
raise StripeConnectionError(
|
||||
'stripe connection error',
|
||||
_("Something went wrong. Please wait a few seconds and try again."))
|
||||
raise BillingError('other stripe error', BillingError.CONTACT_SUPPORT)
|
||||
return wrapped # type: ignore # https://github.com/python/mypy/issues/1927
|
||||
|
||||
@catch_stripe_errors
|
||||
def stripe_get_customer(stripe_customer_id: str) -> stripe.Customer:
|
||||
return stripe.Customer.retrieve(stripe_customer_id, expand=["default_source"])
|
||||
|
||||
@catch_stripe_errors
|
||||
def do_create_stripe_customer(user: UserProfile, stripe_token: Optional[str]=None) -> Customer:
|
||||
realm = user.realm
|
||||
# We could do a better job of handling race conditions here, but if two
|
||||
# people from a realm try to upgrade at exactly the same time, the main
|
||||
# bad thing that will happen is that we will create an extra stripe
|
||||
# customer that we can delete or ignore.
|
||||
stripe_customer = stripe.Customer.create(
|
||||
description="%s (%s)" % (realm.string_id, realm.name),
|
||||
email=user.email,
|
||||
metadata={'realm_id': realm.id, 'realm_str': realm.string_id},
|
||||
source=stripe_token)
|
||||
event_time = timestamp_to_datetime(stripe_customer.created)
|
||||
with transaction.atomic():
|
||||
RealmAuditLog.objects.create(
|
||||
realm=user.realm, acting_user=user, event_type=RealmAuditLog.STRIPE_CUSTOMER_CREATED,
|
||||
event_time=event_time)
|
||||
if stripe_token is not None:
|
||||
RealmAuditLog.objects.create(
|
||||
realm=user.realm, acting_user=user, event_type=RealmAuditLog.STRIPE_CARD_CHANGED,
|
||||
event_time=event_time)
|
||||
customer, created = Customer.objects.update_or_create(realm=realm, defaults={
|
||||
'stripe_customer_id': stripe_customer.id})
|
||||
user.is_billing_admin = True
|
||||
user.save(update_fields=["is_billing_admin"])
|
||||
return customer
|
||||
|
||||
@catch_stripe_errors
|
||||
def do_replace_payment_source(user: UserProfile, stripe_token: str) -> stripe.Customer:
|
||||
stripe_customer = stripe_get_customer(Customer.objects.get(realm=user.realm).stripe_customer_id)
|
||||
stripe_customer.source = stripe_token
|
||||
# Deletes existing card: https://stripe.com/docs/api#update_customer-source
|
||||
# This can also have other side effects, e.g. it will try to pay certain past-due
|
||||
# invoices: https://stripe.com/docs/api#update_customer
|
||||
updated_stripe_customer = stripe.Customer.save(stripe_customer)
|
||||
RealmAuditLog.objects.create(
|
||||
realm=user.realm, acting_user=user, event_type=RealmAuditLog.STRIPE_CARD_CHANGED,
|
||||
event_time=timezone_now())
|
||||
return updated_stripe_customer
|
||||
|
||||
# event_time should roughly be timezone_now(). Not designed to handle
|
||||
# event_times in the past or future
|
||||
# TODO handle downgrade
|
||||
def add_plan_renewal_to_license_ledger_if_needed(plan: CustomerPlan, event_time: datetime) -> LicenseLedger:
|
||||
last_ledger_entry = LicenseLedger.objects.filter(plan=plan).order_by('-id').first()
|
||||
last_renewal = LicenseLedger.objects.filter(plan=plan, is_renewal=True) \
|
||||
.order_by('-id').first().event_time
|
||||
plan_renewal_date = next_renewal_date(plan, last_renewal)
|
||||
if plan_renewal_date <= event_time:
|
||||
return LicenseLedger.objects.create(
|
||||
plan=plan, is_renewal=True, event_time=plan_renewal_date,
|
||||
licenses=last_ledger_entry.licenses_at_next_renewal,
|
||||
licenses_at_next_renewal=last_ledger_entry.licenses_at_next_renewal)
|
||||
return last_ledger_entry
|
||||
|
||||
# Returns Customer instead of stripe_customer so that we don't make a Stripe
|
||||
# API call if there's nothing to update
|
||||
def update_or_create_stripe_customer(user: UserProfile, stripe_token: Optional[str]=None) -> Customer:
|
||||
realm = user.realm
|
||||
customer = Customer.objects.filter(realm=realm).first()
|
||||
if customer is None or customer.stripe_customer_id is None:
|
||||
return do_create_stripe_customer(user, stripe_token=stripe_token)
|
||||
if stripe_token is not None:
|
||||
do_replace_payment_source(user, stripe_token)
|
||||
return customer
|
||||
|
||||
def compute_plan_parameters(
|
||||
automanage_licenses: bool, billing_schedule: int,
|
||||
discount: Optional[Decimal]) -> Tuple[datetime, datetime, datetime, int]:
|
||||
# Everything in Stripe is stored as timestamps with 1 second resolution,
|
||||
# so standardize on 1 second resolution.
|
||||
# TODO talk about leapseconds?
|
||||
billing_cycle_anchor = timezone_now().replace(microsecond=0)
|
||||
if billing_schedule == CustomerPlan.ANNUAL:
|
||||
# TODO use variables to account for Zulip Plus
|
||||
price_per_license = 8000
|
||||
period_end = add_months(billing_cycle_anchor, 12)
|
||||
elif billing_schedule == CustomerPlan.MONTHLY:
|
||||
price_per_license = 800
|
||||
period_end = add_months(billing_cycle_anchor, 1)
|
||||
else:
|
||||
raise AssertionError('Unknown billing_schedule: {}'.format(billing_schedule))
|
||||
if discount is not None:
|
||||
# There are no fractional cents in Stripe, so round down to nearest integer.
|
||||
price_per_license = int(float(price_per_license * (1 - discount / 100)) + .00001)
|
||||
next_invoice_date = period_end
|
||||
if automanage_licenses:
|
||||
next_invoice_date = add_months(billing_cycle_anchor, 1)
|
||||
return billing_cycle_anchor, next_invoice_date, period_end, price_per_license
|
||||
|
||||
# Only used for cloud signups
|
||||
@catch_stripe_errors
|
||||
def process_initial_upgrade(user: UserProfile, licenses: int, automanage_licenses: bool,
|
||||
billing_schedule: int, stripe_token: Optional[str]) -> None:
|
||||
realm = user.realm
|
||||
customer = update_or_create_stripe_customer(user, stripe_token=stripe_token)
|
||||
if CustomerPlan.objects.filter(customer=customer, status=CustomerPlan.ACTIVE).exists():
|
||||
# Unlikely race condition from two people upgrading (clicking "Make payment")
|
||||
# at exactly the same time. Doesn't fully resolve the race condition, but having
|
||||
# a check here reduces the likelihood.
|
||||
billing_logger.warning(
|
||||
"Customer {} trying to upgrade, but has an active subscription".format(customer))
|
||||
raise BillingError('subscribing with existing subscription', BillingError.TRY_RELOADING)
|
||||
|
||||
billing_cycle_anchor, next_invoice_date, period_end, price_per_license = compute_plan_parameters(
|
||||
automanage_licenses, billing_schedule, customer.default_discount)
|
||||
# The main design constraint in this function is that if you upgrade with a credit card, and the
|
||||
# charge fails, everything should be rolled back as if nothing had happened. This is because we
|
||||
# expect frequent card failures on initial signup.
|
||||
# Hence, if we're going to charge a card, do it at the beginning, even if we later may have to
|
||||
# adjust the number of licenses.
|
||||
charge_automatically = stripe_token is not None
|
||||
if charge_automatically:
|
||||
stripe_charge = stripe.Charge.create(
|
||||
amount=price_per_license * licenses,
|
||||
currency='usd',
|
||||
customer=customer.stripe_customer_id,
|
||||
description="Upgrade to Zulip Standard, ${} x {}".format(price_per_license/100, licenses),
|
||||
receipt_email=user.email,
|
||||
statement_descriptor='Zulip Standard')
|
||||
# Not setting a period start and end, but maybe we should? Unclear what will make things
|
||||
# most similar to the renewal case from an accounting perspective.
|
||||
stripe.InvoiceItem.create(
|
||||
amount=price_per_license * licenses * -1,
|
||||
currency='usd',
|
||||
customer=customer.stripe_customer_id,
|
||||
description="Payment (Card ending in {})".format(cast(stripe.Card, stripe_charge.source).last4),
|
||||
discountable=False)
|
||||
|
||||
# TODO: The correctness of this relies on user creation, deactivation, etc being
|
||||
# in a transaction.atomic() with the relevant RealmAuditLog entries
|
||||
with transaction.atomic():
|
||||
# billed_licenses can greater than licenses if users are added between the start of
|
||||
# this function (process_initial_upgrade) and now
|
||||
billed_licenses = max(get_seat_count(realm), licenses)
|
||||
plan_params = {
|
||||
'automanage_licenses': automanage_licenses,
|
||||
'charge_automatically': charge_automatically,
|
||||
'price_per_license': price_per_license,
|
||||
'discount': customer.default_discount,
|
||||
'billing_cycle_anchor': billing_cycle_anchor,
|
||||
'billing_schedule': billing_schedule,
|
||||
'tier': CustomerPlan.STANDARD}
|
||||
plan = CustomerPlan.objects.create(
|
||||
customer=customer,
|
||||
next_invoice_date=next_invoice_date,
|
||||
**plan_params)
|
||||
ledger_entry = LicenseLedger.objects.create(
|
||||
plan=plan,
|
||||
is_renewal=True,
|
||||
event_time=billing_cycle_anchor,
|
||||
licenses=billed_licenses,
|
||||
licenses_at_next_renewal=billed_licenses)
|
||||
plan.invoiced_through = ledger_entry
|
||||
plan.save(update_fields=['invoiced_through'])
|
||||
RealmAuditLog.objects.create(
|
||||
realm=realm, acting_user=user, event_time=billing_cycle_anchor,
|
||||
event_type=RealmAuditLog.CUSTOMER_PLAN_CREATED,
|
||||
extra_data=ujson.dumps(plan_params))
|
||||
stripe.InvoiceItem.create(
|
||||
currency='usd',
|
||||
customer=customer.stripe_customer_id,
|
||||
description='Zulip Standard',
|
||||
discountable=False,
|
||||
period = {'start': datetime_to_timestamp(billing_cycle_anchor),
|
||||
'end': datetime_to_timestamp(period_end)},
|
||||
quantity=billed_licenses,
|
||||
unit_amount=price_per_license)
|
||||
|
||||
if charge_automatically:
|
||||
billing_method = 'charge_automatically'
|
||||
days_until_due = None
|
||||
else:
|
||||
billing_method = 'send_invoice'
|
||||
days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE
|
||||
stripe_invoice = stripe.Invoice.create(
|
||||
auto_advance=True,
|
||||
billing=billing_method,
|
||||
customer=customer.stripe_customer_id,
|
||||
days_until_due=days_until_due,
|
||||
statement_descriptor='Zulip Standard')
|
||||
stripe.Invoice.finalize_invoice(stripe_invoice)
|
||||
|
||||
from zerver.lib.actions import do_change_plan_type
|
||||
do_change_plan_type(realm, Realm.STANDARD)
|
||||
|
||||
def update_license_ledger_for_automanaged_plan(realm: Realm, plan: CustomerPlan,
|
||||
event_time: datetime) -> None:
|
||||
last_ledger_entry = add_plan_renewal_to_license_ledger_if_needed(plan, event_time)
|
||||
# todo: handle downgrade, where licenses_at_next_renewal should be 0
|
||||
licenses_at_next_renewal = get_seat_count(realm)
|
||||
licenses = max(licenses_at_next_renewal, last_ledger_entry.licenses)
|
||||
LicenseLedger.objects.create(
|
||||
plan=plan, event_time=event_time, licenses=licenses,
|
||||
licenses_at_next_renewal=licenses_at_next_renewal)
|
||||
|
||||
def update_license_ledger_if_needed(realm: Realm, event_time: datetime) -> None:
|
||||
customer = Customer.objects.filter(realm=realm).first()
|
||||
if customer is None:
|
||||
return
|
||||
plan = get_active_plan(customer)
|
||||
if plan is None:
|
||||
return
|
||||
if not plan.automanage_licenses:
|
||||
return
|
||||
update_license_ledger_for_automanaged_plan(realm, plan, event_time)
|
||||
|
||||
def invoice_plan(plan: CustomerPlan, event_time: datetime) -> None:
|
||||
if plan.invoicing_status == CustomerPlan.STARTED:
|
||||
raise NotImplementedError('Plan with invoicing_status==STARTED needs manual resolution.')
|
||||
add_plan_renewal_to_license_ledger_if_needed(plan, event_time)
|
||||
assert(plan.invoiced_through is not None)
|
||||
licenses_base = plan.invoiced_through.licenses
|
||||
invoice_item_created = False
|
||||
for ledger_entry in LicenseLedger.objects.filter(plan=plan, id__gt=plan.invoiced_through.id,
|
||||
event_time__lte=event_time).order_by('id'):
|
||||
price_args = {} # type: Dict[str, int]
|
||||
if ledger_entry.is_renewal:
|
||||
if plan.fixed_price is not None:
|
||||
price_args = {'amount': plan.fixed_price}
|
||||
else:
|
||||
assert(plan.price_per_license is not None) # needed for mypy
|
||||
price_args = {'unit_amount': plan.price_per_license,
|
||||
'quantity': ledger_entry.licenses}
|
||||
description = "Zulip Standard - renewal"
|
||||
elif ledger_entry.licenses != licenses_base:
|
||||
assert(plan.price_per_license)
|
||||
last_renewal = LicenseLedger.objects.filter(
|
||||
plan=plan, is_renewal=True, event_time__lte=ledger_entry.event_time) \
|
||||
.order_by('-id').first().event_time
|
||||
period_end = next_renewal_date(plan, ledger_entry.event_time)
|
||||
proration_fraction = (period_end - ledger_entry.event_time) / (period_end - last_renewal)
|
||||
price_args = {'unit_amount': int(plan.price_per_license * proration_fraction + .5),
|
||||
'quantity': ledger_entry.licenses - licenses_base}
|
||||
description = "Additional license ({} - {})".format(
|
||||
ledger_entry.event_time.strftime('%b %-d, %Y'), period_end.strftime('%b %-d, %Y'))
|
||||
|
||||
if price_args:
|
||||
plan.invoiced_through = ledger_entry
|
||||
plan.invoicing_status = CustomerPlan.STARTED
|
||||
plan.save(update_fields=['invoicing_status', 'invoiced_through'])
|
||||
idempotency_key = 'ledger_entry:{}'.format(ledger_entry.id) # type: Optional[str]
|
||||
if settings.TEST_SUITE:
|
||||
idempotency_key = None
|
||||
stripe.InvoiceItem.create(
|
||||
currency='usd',
|
||||
customer=plan.customer.stripe_customer_id,
|
||||
description=description,
|
||||
discountable=False,
|
||||
period = {'start': datetime_to_timestamp(ledger_entry.event_time),
|
||||
'end': datetime_to_timestamp(next_renewal_date(plan, ledger_entry.event_time))},
|
||||
idempotency_key=idempotency_key,
|
||||
**price_args)
|
||||
invoice_item_created = True
|
||||
plan.invoiced_through = ledger_entry
|
||||
plan.invoicing_status = CustomerPlan.DONE
|
||||
plan.save(update_fields=['invoicing_status', 'invoiced_through'])
|
||||
licenses_base = ledger_entry.licenses
|
||||
|
||||
if invoice_item_created:
|
||||
if plan.charge_automatically:
|
||||
billing_method = 'charge_automatically'
|
||||
days_until_due = None
|
||||
else:
|
||||
billing_method = 'send_invoice'
|
||||
days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE
|
||||
stripe_invoice = stripe.Invoice.create(
|
||||
auto_advance=True,
|
||||
billing=billing_method,
|
||||
customer=plan.customer.stripe_customer_id,
|
||||
days_until_due=days_until_due,
|
||||
statement_descriptor='Zulip Standard')
|
||||
stripe.Invoice.finalize_invoice(stripe_invoice)
|
||||
|
||||
plan.next_invoice_date = next_invoice_date(plan)
|
||||
plan.save(update_fields=['next_invoice_date'])
|
||||
|
||||
def invoice_plans_as_needed(event_time: datetime) -> None:
|
||||
for plan in CustomerPlan.objects.filter(next_invoice_date__lte=event_time):
|
||||
invoice_plan(plan, event_time)
|
||||
|
||||
def attach_discount_to_realm(realm: Realm, discount: Decimal) -> None:
|
||||
Customer.objects.update_or_create(realm=realm, defaults={'default_discount': discount})
|
||||
|
||||
def process_downgrade(user: UserProfile) -> None: # nocoverage
|
||||
pass
|
||||
|
||||
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'):
|
||||
# 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()) or 0
|
||||
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
|
||||
@@ -1,53 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-09-25 12:02
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('zerver', '0189_userprofile_add_some_emojisets'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BillingProcessor',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('state', models.CharField(max_length=20)),
|
||||
('last_modified', models.DateTimeField(auto_now=True)),
|
||||
('log_row', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='zerver.RealmAuditLog')),
|
||||
('realm', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='zerver.Realm')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Coupon',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('percent_off', models.SmallIntegerField(unique=True)),
|
||||
('stripe_coupon_id', models.CharField(max_length=255, unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Customer',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('stripe_customer_id', models.CharField(max_length=255, unique=True)),
|
||||
('has_billing_relationship', models.BooleanField(default=False)),
|
||||
('realm', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='zerver.Realm')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Plan',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('nickname', models.CharField(max_length=40, unique=True)),
|
||||
('stripe_plan_id', models.CharField(max_length=255, unique=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.16 on 2018-12-12 20:19
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='customer',
|
||||
name='default_discount',
|
||||
field=models.DecimalField(decimal_places=4, max_digits=7, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,35 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.16 on 2018-12-22 21:05
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0002_customer_default_discount'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CustomerPlan',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('licenses', models.IntegerField()),
|
||||
('automanage_licenses', models.BooleanField(default=False)),
|
||||
('charge_automatically', models.BooleanField(default=False)),
|
||||
('price_per_license', models.IntegerField(null=True)),
|
||||
('fixed_price', models.IntegerField(null=True)),
|
||||
('discount', models.DecimalField(decimal_places=4, max_digits=6, null=True)),
|
||||
('billing_cycle_anchor', models.DateTimeField()),
|
||||
('billing_schedule', models.SmallIntegerField()),
|
||||
('billed_through', models.DateTimeField()),
|
||||
('next_billing_date', models.DateTimeField(db_index=True)),
|
||||
('tier', models.SmallIntegerField()),
|
||||
('status', models.SmallIntegerField(default=1)),
|
||||
('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='corporate.Customer')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,27 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2019-01-19 05:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0003_customerplan'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LicenseLedger',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_renewal', models.BooleanField(default=False)),
|
||||
('event_time', models.DateTimeField()),
|
||||
('licenses', models.IntegerField()),
|
||||
('licenses_at_next_renewal', models.IntegerField(null=True)),
|
||||
('plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='corporate.CustomerPlan')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,35 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2019-01-28 13:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0004_licenseledger'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='customerplan',
|
||||
old_name='next_billing_date',
|
||||
new_name='next_invoice_date',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='customerplan',
|
||||
name='billed_through',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customerplan',
|
||||
name='invoiced_through',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='corporate.LicenseLedger'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='customerplan',
|
||||
name='invoicing_status',
|
||||
field=models.SmallIntegerField(default=1),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2019-01-29 01:46
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0005_customerplan_invoicing'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customer',
|
||||
name='stripe_customer_id',
|
||||
field=models.CharField(max_length=255, null=True, unique=True),
|
||||
),
|
||||
]
|
||||
@@ -1,40 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2019-01-31 22:16
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('corporate', '0006_nullable_stripe_customer_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='billingprocessor',
|
||||
name='log_row',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='billingprocessor',
|
||||
name='realm',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Coupon',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Plan',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='customer',
|
||||
name='has_billing_relationship',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='customerplan',
|
||||
name='licenses',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='BillingProcessor',
|
||||
),
|
||||
]
|
||||
@@ -1,70 +0,0 @@
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import CASCADE
|
||||
|
||||
from zerver.models import Realm
|
||||
|
||||
class Customer(models.Model):
|
||||
realm = models.OneToOneField(Realm, on_delete=CASCADE) # type: Realm
|
||||
stripe_customer_id = models.CharField(max_length=255, null=True, unique=True) # type: str
|
||||
# A percentage, like 85.
|
||||
default_discount = models.DecimalField(decimal_places=4, max_digits=7, null=True) # type: Optional[Decimal]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "<Customer %s %s>" % (self.realm, self.stripe_customer_id)
|
||||
|
||||
class CustomerPlan(models.Model):
|
||||
customer = models.ForeignKey(Customer, on_delete=CASCADE) # type: Customer
|
||||
automanage_licenses = models.BooleanField(default=False) # type: bool
|
||||
charge_automatically = models.BooleanField(default=False) # type: bool
|
||||
|
||||
# Both of these are in cents. Exactly one of price_per_license or
|
||||
# fixed_price should be set. fixed_price is only for manual deals, and
|
||||
# can't be set via the self-serve billing system.
|
||||
price_per_license = models.IntegerField(null=True) # type: Optional[int]
|
||||
fixed_price = models.IntegerField(null=True) # type: Optional[int]
|
||||
|
||||
# Discount that was applied. For display purposes only.
|
||||
discount = models.DecimalField(decimal_places=4, max_digits=6, null=True) # type: Optional[Decimal]
|
||||
|
||||
billing_cycle_anchor = models.DateTimeField() # type: datetime.datetime
|
||||
ANNUAL = 1
|
||||
MONTHLY = 2
|
||||
billing_schedule = models.SmallIntegerField() # type: int
|
||||
|
||||
next_invoice_date = models.DateTimeField(db_index=True) # type: datetime.datetime
|
||||
invoiced_through = models.ForeignKey(
|
||||
'LicenseLedger', null=True, on_delete=CASCADE, related_name='+') # type: Optional[LicenseLedger]
|
||||
DONE = 1
|
||||
STARTED = 2
|
||||
invoicing_status = models.SmallIntegerField(default=DONE) # type: int
|
||||
|
||||
STANDARD = 1
|
||||
PLUS = 2 # not available through self-serve signup
|
||||
ENTERPRISE = 10
|
||||
tier = models.SmallIntegerField() # type: int
|
||||
|
||||
ACTIVE = 1
|
||||
ENDED = 2
|
||||
NEVER_STARTED = 3
|
||||
# You can only have 1 active subscription at a time
|
||||
status = models.SmallIntegerField(default=ACTIVE) # type: int
|
||||
|
||||
# TODO maybe override setattr to ensure billing_cycle_anchor, etc are immutable
|
||||
|
||||
def get_active_plan(customer: Customer) -> Optional[CustomerPlan]:
|
||||
return CustomerPlan.objects.filter(customer=customer, status=CustomerPlan.ACTIVE).first()
|
||||
|
||||
class LicenseLedger(models.Model):
|
||||
plan = models.ForeignKey(CustomerPlan, on_delete=CASCADE) # type: CustomerPlan
|
||||
# Also True for the initial upgrade.
|
||||
is_renewal = models.BooleanField(default=False) # type: bool
|
||||
event_time = models.DateTimeField() # type: datetime.datetime
|
||||
licenses = models.IntegerField() # type: int
|
||||
# None means the plan does not automatically renew.
|
||||
# 0 means the plan has been explicitly downgraded.
|
||||
# This cannot be None if plan.automanage_licenses.
|
||||
licenses_at_next_renewal = models.IntegerField(null=True) # type: Optional[int]
|
||||
@@ -1,78 +0,0 @@
|
||||
{
|
||||
"amount": 7200,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000001",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $12.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_NORMALIZED00000000000001/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
{
|
||||
"amount": 36000,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000002",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $60.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000002/rcpt_NORMALIZED000000000000000000002",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_NORMALIZED00000000000002/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000002",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount": 7200,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000001",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $12.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
|
||||
"refunded": false,
|
||||
"refunds": {},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/charges"
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount": 36000,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000002",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $60.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000002/rcpt_NORMALIZED000000000000000000002",
|
||||
"refunded": false,
|
||||
"refunds": {},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000002",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
},
|
||||
{
|
||||
"amount": 7200,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000001",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $12.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
|
||||
"refunded": false,
|
||||
"refunds": {},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/charges"
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": null,
|
||||
"default_source": "card_NORMALIZED00000000000001",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"default_source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"default_source": "card_NORMALIZED00000000000002",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000002",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7200,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -7200,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 36000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -36000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7200,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -7200,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 36000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -36000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"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-0002",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7200,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -7200,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/invoices"
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 36000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -36000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"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-0002",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
},
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7200,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -7200,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/invoices"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": -7200,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": -7200
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": 7200,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"unit_amount": 1200
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": -36000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": -36000
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": 36000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"unit_amount": 6000
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"card": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "unchecked",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "unchecked",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"cvc_check": "unchecked",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"client_ip": "0.0.0.0",
|
||||
"created": 1000000000,
|
||||
"id": "tok_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"object": "token",
|
||||
"type": "card",
|
||||
"used": false
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"card": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "unchecked",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "unchecked",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"cvc_check": "unchecked",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000002",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"client_ip": "0.0.0.0",
|
||||
"created": 1000000000,
|
||||
"id": "tok_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"object": "token",
|
||||
"type": "card",
|
||||
"used": false
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
{
|
||||
"amount": 48000,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000001",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $80.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_NORMALIZED00000000000001/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": null,
|
||||
"default_source": "card_NORMALIZED00000000000001",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"default_source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"default_source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": -48000
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"unit_amount": 8000
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"card": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "unchecked",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "unchecked",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"cvc_check": "unchecked",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"client_ip": "0.0.0.0",
|
||||
"created": 1000000000,
|
||||
"id": "tok_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"object": "token",
|
||||
"type": "card",
|
||||
"used": false
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": null,
|
||||
"default_source": null,
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"amount_due": 984000,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 984000,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 984000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 123,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 984000,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 984000,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"amount_due": 100,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 100,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 100,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 100,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 100,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"amount_due": 984000,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 984000,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 984000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 123,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 984000,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 984000,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"amount_due": 100,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 100,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 100,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 100,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 100,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount_due": 100,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 100,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 100,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 100,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 100,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
},
|
||||
{
|
||||
"amount_due": 984000,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 984000,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "send_invoice",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 984000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 123,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 984000,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 984000,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/invoices"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": 984000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 123,
|
||||
"subscription": null,
|
||||
"unit_amount": 8000
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": 100,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": 100
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
{
|
||||
"amount": 48000,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000001",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $80.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_NORMALIZED00000000000001/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": null,
|
||||
"default_source": "card_NORMALIZED00000000000001",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
{
|
||||
"amount_due": 80697,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 80697,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7255,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1360033445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 56000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 7,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 17442,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000005",
|
||||
"invoice_item": "ii_NORMALIZED00000000000005",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1334113445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 3,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 3,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 80697,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 80697,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
{
|
||||
"amount_due": 80697,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 80697,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7255,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1360033445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 56000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 7,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 17442,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000005",
|
||||
"invoice_item": "ii_NORMALIZED00000000000005",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1334113445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 3,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 3,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 80697,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 80697,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"amount_due": 80697,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 80697,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002",
|
||||
"id": "in_NORMALIZED00000000000002",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000002/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 7255,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice_item": "ii_NORMALIZED00000000000003",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1360033445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 56000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice_item": "ii_NORMALIZED00000000000004",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 7,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": 17442,
|
||||
"currency": "usd",
|
||||
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000005",
|
||||
"invoice_item": "ii_NORMALIZED00000000000005",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1334113445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 3,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 3,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000002/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1000000000,
|
||||
"number": "NORMALI-0002",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "open",
|
||||
"subscription": null,
|
||||
"subtotal": 80697,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 80697,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
},
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1000000000,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1000000000,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001",
|
||||
"id": "in_NORMALIZED00000000000001",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_NORMALIZED0000000000000001/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice_item": "ii_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice_item": "ii_NORMALIZED00000000000002",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_NORMALIZED00000000000001/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "NORMALI-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1000000000,
|
||||
"period_start": 1000000000,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1000000000
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"url": "/v1/invoices"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": -48000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000002",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1000000000,
|
||||
"start": 1000000000
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": -48000
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": 48000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 6,
|
||||
"subscription": null,
|
||||
"unit_amount": 8000
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": 17442,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000005",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1334113445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 3,
|
||||
"subscription": null,
|
||||
"unit_amount": 5814
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": 56000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Zulip Standard - renewal",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000004",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1357095845
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 7,
|
||||
"subscription": null,
|
||||
"unit_amount": 8000
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": 7255,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"date": 1000000000,
|
||||
"description": "Additional license (Feb 5, 2013 - Jan 2, 2014)",
|
||||
"discountable": false,
|
||||
"id": "ii_NORMALIZED00000000000003",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1388631845,
|
||||
"start": 1360033445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": 7255
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"card": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "unchecked",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "unchecked",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"cvc_check": "unchecked",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"client_ip": "0.0.0.0",
|
||||
"created": 1000000000,
|
||||
"id": "tok_NORMALIZED00000000000001",
|
||||
"livemode": false,
|
||||
"object": "token",
|
||||
"type": "card",
|
||||
"used": false
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
{
|
||||
"amount": 64000,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_1DxdeSGh0CmXqmnw9I7ziL6D",
|
||||
"captured": true,
|
||||
"created": 1548695524,
|
||||
"currency": "usd",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"description": "Upgrade to Zulip Standard, $80.0 x 8",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_1DxdeSGh0CmXqmnwsw80Rn8n",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 46,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_1BWYgHGh0CmXqmnw/ch_1DxdeSGh0CmXqmnwsw80Rn8n/rcpt_EQUda9O5I5cYpJL0yxiRbxnPbWZ415D",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_1DxdeSGh0CmXqmnwsw80Rn8n/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DxdeRGh0CmXqmnwu3BibWtM",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1548695523,
|
||||
"currency": null,
|
||||
"default_source": "card_1DxdeRGh0CmXqmnwu3BibWtM",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_EQUd48LphR5ahk",
|
||||
"invoice_prefix": "9F53235",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DxdeRGh0CmXqmnwu3BibWtM",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_EQUd48LphR5ahk/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_EQUd48LphR5ahk/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": false,
|
||||
"auto_advance": true,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"date": 1548695526,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": null,
|
||||
"ending_balance": null,
|
||||
"finalized_at": null,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": null,
|
||||
"id": "in_1DxdeUGh0CmXqmnwoowtAcQW",
|
||||
"invoice_pdf": null,
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 64000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
|
||||
"invoice_item": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 8,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -64000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
|
||||
"invoice_item": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1548695525,
|
||||
"start": 1548695525
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_1DxdeUGh0CmXqmnwoowtAcQW/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": 1548699126,
|
||||
"number": "9F53235-0001",
|
||||
"object": "invoice",
|
||||
"paid": false,
|
||||
"period_end": 1548695526,
|
||||
"period_start": 1548695526,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "draft",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": null
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
{
|
||||
"amount_due": 0,
|
||||
"amount_paid": 0,
|
||||
"amount_remaining": 0,
|
||||
"application_fee": null,
|
||||
"attempt_count": 0,
|
||||
"attempted": true,
|
||||
"auto_advance": false,
|
||||
"billing": "charge_automatically",
|
||||
"billing_reason": "manual",
|
||||
"charge": null,
|
||||
"currency": "usd",
|
||||
"custom_fields": null,
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"date": 1548695526,
|
||||
"default_source": null,
|
||||
"description": "",
|
||||
"discount": null,
|
||||
"due_date": 1551287527,
|
||||
"ending_balance": 0,
|
||||
"finalized_at": 1548695527,
|
||||
"footer": null,
|
||||
"hosted_invoice_url": "https://pay.stripe.com/invoice/invst_8N3Zw202aBfkZSHzyjUVcv9vrE",
|
||||
"id": "in_1DxdeUGh0CmXqmnwoowtAcQW",
|
||||
"invoice_pdf": "https://pay.stripe.com/invoice/invst_8N3Zw202aBfkZSHzyjUVcv9vrE/pdf",
|
||||
"lines": {
|
||||
"data": [
|
||||
{
|
||||
"amount": 64000,
|
||||
"currency": "usd",
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
|
||||
"invoice_item": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 8,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
},
|
||||
{
|
||||
"amount": -64000,
|
||||
"currency": "usd",
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
|
||||
"invoice_item": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "line_item",
|
||||
"period": {
|
||||
"end": 1548695525,
|
||||
"start": 1548695525
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"type": "invoiceitem"
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 2,
|
||||
"url": "/v1/invoices/in_1DxdeUGh0CmXqmnwoowtAcQW/lines"
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"next_payment_attempt": null,
|
||||
"number": "9F53235-0001",
|
||||
"object": "invoice",
|
||||
"paid": true,
|
||||
"period_end": 1548695526,
|
||||
"period_start": 1548695526,
|
||||
"receipt_number": null,
|
||||
"starting_balance": 0,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "paid",
|
||||
"subscription": null,
|
||||
"subtotal": 0,
|
||||
"tax": 0,
|
||||
"tax_percent": null,
|
||||
"total": 0,
|
||||
"webhooks_delivered_at": 1548695526
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": -64000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"date": 1548695525,
|
||||
"description": "Payment (Card ending in 4242)",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnwDYYlalDE",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1548695525,
|
||||
"start": 1548695525
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 1,
|
||||
"subscription": null,
|
||||
"unit_amount": -64000
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": 64000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"date": 1548695525,
|
||||
"description": "Zulip Standard",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeTGh0CmXqmnw7MfZoBVx",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1325473445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 8,
|
||||
"subscription": null,
|
||||
"unit_amount": 8000
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"amount": 17442,
|
||||
"currency": "usd",
|
||||
"customer": "cus_EQUd48LphR5ahk",
|
||||
"date": 1548695527,
|
||||
"description": "Additional license (Apr 11, 2012 - Jan 2, 2013)",
|
||||
"discountable": false,
|
||||
"id": "ii_1DxdeVGh0CmXqmnwf6cLlEA7",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "invoiceitem",
|
||||
"period": {
|
||||
"end": 1357095845,
|
||||
"start": 1334113445
|
||||
},
|
||||
"plan": null,
|
||||
"proration": false,
|
||||
"quantity": 3,
|
||||
"subscription": null,
|
||||
"unit_amount": 5814
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"card": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "unchecked",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "unchecked",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"cvc_check": "unchecked",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "6dAXT9VZvwro65EK",
|
||||
"funding": "credit",
|
||||
"id": "card_1DxdeRGh0CmXqmnwu3BibWtM",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"client_ip": "107.202.144.213",
|
||||
"created": 1548695523,
|
||||
"id": "tok_1DxdeRGh0CmXqmnw5X6CeYnC",
|
||||
"livemode": false,
|
||||
"object": "token",
|
||||
"type": "card",
|
||||
"used": false
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
{
|
||||
"amount": 48000,
|
||||
"amount_refunded": 0,
|
||||
"application": null,
|
||||
"application_fee": null,
|
||||
"application_fee_amount": null,
|
||||
"balance_transaction": "txn_NORMALIZED00000000000001",
|
||||
"captured": true,
|
||||
"created": 1000000000,
|
||||
"currency": "usd",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"description": "Upgrade to Zulip Standard, $80.0 x 6",
|
||||
"destination": null,
|
||||
"dispute": null,
|
||||
"failure_code": null,
|
||||
"failure_message": null,
|
||||
"fraud_details": {},
|
||||
"id": "ch_NORMALIZED00000000000001",
|
||||
"invoice": null,
|
||||
"livemode": false,
|
||||
"metadata": {},
|
||||
"object": "charge",
|
||||
"on_behalf_of": null,
|
||||
"order": null,
|
||||
"outcome": {
|
||||
"network_status": "approved_by_network",
|
||||
"reason": null,
|
||||
"risk_level": "normal",
|
||||
"risk_score": 00,
|
||||
"seller_message": "Payment complete.",
|
||||
"type": "authorized"
|
||||
},
|
||||
"paid": true,
|
||||
"payment_intent": null,
|
||||
"receipt_email": "hamlet@zulip.com",
|
||||
"receipt_number": null,
|
||||
"receipt_url": "https://pay.stripe.com/receipts/acct_NORMALIZED000001/ch_NORMALIZED00000000000001/rcpt_NORMALIZED000000000000000000001",
|
||||
"refunded": false,
|
||||
"refunds": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/charges/ch_NORMALIZED00000000000001/refunds"
|
||||
},
|
||||
"review": null,
|
||||
"shipping": null,
|
||||
"source": {
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
},
|
||||
"source_transfer": null,
|
||||
"statement_descriptor": "Zulip Standard",
|
||||
"status": "succeeded",
|
||||
"transfer_data": null,
|
||||
"transfer_group": null
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"account_balance": 0,
|
||||
"created": 1000000000,
|
||||
"currency": null,
|
||||
"default_source": "card_NORMALIZED00000000000001",
|
||||
"delinquent": false,
|
||||
"description": "zulip (Zulip Dev)",
|
||||
"discount": null,
|
||||
"email": "hamlet@zulip.com",
|
||||
"id": "cus_NORMALIZED0001",
|
||||
"invoice_prefix": "NORMA01",
|
||||
"invoice_settings": {
|
||||
"custom_fields": null,
|
||||
"footer": null
|
||||
},
|
||||
"livemode": false,
|
||||
"metadata": {
|
||||
"realm_id": "1",
|
||||
"realm_str": "zulip"
|
||||
},
|
||||
"object": "customer",
|
||||
"shipping": null,
|
||||
"sources": {
|
||||
"data": [
|
||||
{
|
||||
"address_city": "Pacific",
|
||||
"address_country": "United States",
|
||||
"address_line1": "Under the sea,",
|
||||
"address_line1_check": "pass",
|
||||
"address_line2": null,
|
||||
"address_state": null,
|
||||
"address_zip": "33333",
|
||||
"address_zip_check": "pass",
|
||||
"brand": "Visa",
|
||||
"country": "US",
|
||||
"customer": "cus_NORMALIZED0001",
|
||||
"cvc_check": "pass",
|
||||
"dynamic_last4": null,
|
||||
"exp_month": 3,
|
||||
"exp_year": 2033,
|
||||
"fingerprint": "NORMALIZED000001",
|
||||
"funding": "credit",
|
||||
"id": "card_NORMALIZED00000000000001",
|
||||
"last4": "4242",
|
||||
"metadata": {},
|
||||
"name": "Ada Starr",
|
||||
"object": "card",
|
||||
"tokenization_method": null
|
||||
}
|
||||
],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 1,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/sources"
|
||||
},
|
||||
"subscriptions": {
|
||||
"data": [],
|
||||
"has_more": false,
|
||||
"object": "list",
|
||||
"total_count": 0,
|
||||
"url": "/v1/customers/cus_NORMALIZED0001/subscriptions"
|
||||
},
|
||||
"tax_info": null,
|
||||
"tax_info_verification": null
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user