mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +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,7 +116,6 @@
 | 
			
		||||
        "pygments_data": false,
 | 
			
		||||
        "reactions": false,
 | 
			
		||||
        "realm_icon": false,
 | 
			
		||||
        "realm_logo": false,
 | 
			
		||||
        "recent_senders": false,
 | 
			
		||||
        "reload": false,
 | 
			
		||||
        "reload_state": false,
 | 
			
		||||
@@ -145,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,
 | 
			
		||||
@@ -158,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,
 | 
			
		||||
@@ -168,7 +162,6 @@
 | 
			
		||||
        "stream_muting": false,
 | 
			
		||||
        "stream_popover": false,
 | 
			
		||||
        "stream_sort": false,
 | 
			
		||||
        "StripeCheckout": false,
 | 
			
		||||
        "submessage": false,
 | 
			
		||||
        "subs": false,
 | 
			
		||||
        "tab_bar": false,
 | 
			
		||||
@@ -181,7 +174,6 @@
 | 
			
		||||
        "topic_data": false,
 | 
			
		||||
        "topic_generator": false,
 | 
			
		||||
        "topic_list": false,
 | 
			
		||||
        "topic_zoom": false,
 | 
			
		||||
        "transmit": false,
 | 
			
		||||
        "tutorial": false,
 | 
			
		||||
        "typeahead_helper": false,
 | 
			
		||||
@@ -190,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,
 | 
			
		||||
@@ -231,12 +219,6 @@
 | 
			
		||||
                "functions": "never"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "comma-spacing": [ "error",
 | 
			
		||||
            {
 | 
			
		||||
                "before": false,
 | 
			
		||||
                "after": true
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "complexity": [ 0, 4 ],
 | 
			
		||||
        "curly": 2,
 | 
			
		||||
        "dot-notation": [ "error", { "allowKeywords": true } ],
 | 
			
		||||
@@ -254,12 +236,6 @@
 | 
			
		||||
            "FunctionExpression": {"parameters": "first"},
 | 
			
		||||
            "FunctionDeclaration": {"parameters": "first"}
 | 
			
		||||
        }],
 | 
			
		||||
        "key-spacing": [ "error",
 | 
			
		||||
            {
 | 
			
		||||
                "beforeColon": false,
 | 
			
		||||
                "afterColon": true
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "keyword-spacing": [ "error",
 | 
			
		||||
            {
 | 
			
		||||
                "before": true,
 | 
			
		||||
@@ -386,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("")
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user