[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig: http://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\nquote_type = double\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nmax_line_length = 88\n\n[*.json]\ninsert_final_newline = false\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: dj-stripe\nopen_collective: dj-stripe\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/api-change-notice.md",
    "content": "---\nname: API Change Notice\nabout: Has something changed in the Stripe API that we haven't gotten to? Let us know\ntitle: API change for [Stripe Object Names] in version [New Stripe Version]\nlabels: api change\nassignees: ''\n\n---\n\n**Changelog Link**\n<!-- Supply a direct link to the [changelog](https://stripe.com/docs/upgrades#api-changelog) entry -->\n\n**Describe the Change**\n<!-- Provide a brief, clear, and concise description of what was added/updated/removed. A bulleted-list of affected models and fields would be incredibly helpful (bonus points for links to dj-stripe code). -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Any other type of bug or crash\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\n<!-- Please describe how to reproduce the issue below. Make sure not to include private keys or live customer information. -->\n\n\n**Software versions**\n- Dj-Stripe version:\n- Python version:\n- Django version:\n- Stripe API version:\n- Database type and version:\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/documentation-issue-request.md",
    "content": "---\nname: Documentation Issue/Request\nabout: Is something missing from the docs? Is a section of the docs outdated or incorrect?\n  Let us know.\ntitle: ''\nlabels: documentation\nassignees: ''\n\n---\n\n**What did you run into?**\n\n**Why would it be helpful to document or fix this?**\n\n**To which section of the docs does this belong?**\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-or-enhancement-proposal.md",
    "content": "---\nname: Feature or Enhancement Proposal\nabout: Suggest an idea for this project\ntitle: ''\nlabels: discussion\nassignees: ''\n\n---\n\n**Is your request related to a problem? Please describe.**\n<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->\n\n**Describe the solution you'd like**\n<!-- A clear and concise description of what you would like to see. -->\n\n**Additional context**\n<!-- Add any other context about the request here. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/general-bug.md",
    "content": "---\nname: General Bug\nabout: Any type of bug or crash not related to webhooks or syncing\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the bug**\n<!-- A clear and concise description of what the bug is. Make sure not to include private keys or live customer information. -->\n\n**Software versions**\n- dj-stripe version:\n- Python version:\n- Django version:\n- Stripe API version:\n- Database type and version:\n\n**Steps To Reproduce**\n<!-- Please tell us how to reproduce the issue. Use numbered steps. Make sure not to include private keys or live customer information. -->\n\n**Can you reproduce the issue with the latest version of master?**\n<!-- Yes or No -->\n\n**Expected Behavior**\n<!-- Please clearly and concisely describe what should happen after following the reproduction steps -->\n\n**Actual Behavior**\n<!-- Please clearly and concisely describe what actually happens after following the reproduction steps. Include a traceback if you have it, but ENSURE YOU REDACT IT  -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/how-to-usage-question.md",
    "content": "---\nname: How To/Usage Question\nabout: Have a question on how to use a dj-stripe feature?\ntitle: How do I [short description]?\nlabels: question\nassignees: ''\n\n---\n\n<!-- If this is an implementation-related question, you're probably better off asking it on StackOverflow and tagging it ``dj-stripe`` -->\n\n**What are you trying to accomplish?**\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/issue-with-webhooks-or-sync.md",
    "content": "---\nname: Issue with webhooks or sync\nabout: For issues happening specifically when data is being synced from Stripe to\n  Dj-Stripe\ntitle: ''\nlabels: webhook / sync issues\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n\ne.g.:\n\n1. Enable stripe webhook to dj-stripe\n2. In stripe dashboard create a billing product with feature X\n3. See attached error on webhook\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\nIf relevant it's very helpful to include webhook tracebacks and content (note that these are logged in the database and are visible in django admin - eg http://127.0.0.1:8000/admin/djstripe/webhookeventtrigger/ )\n\n**Environment**\n- dj-stripe version: [e.g. master at <hash>, 2.0.0 etc]\n- Your Stripe account's default API version: [e.g. 2019-02-19 - shown as \"default\" on https://dashboard.stripe.com/developers]\n- Database: [e.g. MySQL 5.7.25]\n- Python version: [e.g. 3.8.2]\n- Django version: [e.g. 2.1.7]\n\n**Can you reproduce the issue with the latest version of master?**\n\n[Yes / No]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/migration-upgrade-issue.md",
    "content": "---\nname: Migration/Upgrade Issue\nabout: Having trouble upgrading versions of dj-stripe? Let us know\ntitle: Failure when attempting to upgrade from dj-strive [old version] to [new version]\nlabels: migrations\nassignees: ''\n\n---\n\n**Software versions**\n- Upgrading from dj-stripe version:\n- Upgrading to dj-stripe version:\n- Python version:\n- Django version:\n- Stripe API version:\n- Database type and version:\n- Failing migration id:\n\n**Can you reproduce the issue with the latest version of master?**\n<!-- Yes or No -->\n\n**Describe the issue**\n<!-- Please clearly and concisely describe what happens when attempting to upgrade Include a traceback if you have it, but ENSURE YOU REDACT IT.  -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/other-issue.md",
    "content": "---\nname: Other Issue\nabout: For anything else\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\n\nThank you for your pull request!\n\nPlease adhere to the following guidelines:\n\n- Clear, single-purpose, atomic commits with a short summary and a descriptive body.\n- Make sure your code is tested, especially if it fixes bugs or introduces complexity.\n- Document important changes in the changelog (Most recent file in docs/history/ folder)\n\nMuch appreciated!\n\n-->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # Update Github actions in workflows\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/install_poetry_action/action.yaml",
    "content": "name: Setup and install poetry\ndescription: Install and Setup Poetry\n\ninputs:\n  POETRY_VERSION:\n    required: true\n    type: string\n  python_version:\n    required: true\n    type: string\n\n\nruns:\n  using: \"composite\"\n  steps:\n      - name: Set up Python ${{ inputs.python_version }}\n        uses: actions/setup-python@v4\n        with:\n          python-version: ${{ inputs.python_version }}\n\n      - name: Install poetry (${{ inputs.POETRY_VERSION }}) binary on runner\n        run: |\n          curl -sL https://install.python-poetry.org | python - --version ${{ inputs.POETRY_VERSION }}\n        shell: bash\n\n      - name: Set up cache\n        uses: actions/cache@v3\n        id: cache\n        with:\n          path: .venv\n          key: venv-${{ inputs.python_version }}\n\n      - name: Ensure cache is healthy\n        if: steps.cache.outputs.cache-hit == 'true'\n        run: timeout 10s poetry run pip --version || rm -rf .venv\n        shell: bash\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI tests\n\non:\n  push:\n    paths-ignore:\n      - \"docs/**\"\n      - \"pyproject.toml\"\n      - \"mkdocs.yml\"\n      - \".readthedocs.yml\"\n      - \".github/workflows/docs.yml\"\n\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [master]\n\nenv:\n  POETRY_VERSION: \"1.2.2\"\n  POETRY_VIRTUALENVS_IN_PROJECT: \"1\"\n\njobs:\n  tests:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: true\n      matrix:\n        python-version: [\"3.8\", \"3.9\", \"3.10\", \"3.11\"]\n\n    services:\n      postgres:\n        image: postgres:12\n        env:\n          POSTGRES_PASSWORD: djstripe\n          POSTGRES_DB: djstripe\n        ports:\n          - 5432:5432\n        options: >-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n      mysql:\n        image: mysql:5.7\n        env:\n          MYSQL_ROOT_PASSWORD: djstripe\n          MYSQL_DATABASE: djstripe\n        ports:\n          - 3306:3306\n        options: >-\n          --health-cmd=\"mysqladmin ping\"\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - uses: ./.github/install_poetry_action\n        with:\n            POETRY_VERSION: ${{ env.POETRY_VERSION }}\n            python_version: ${{ matrix.python-version }}\n\n      - name: Install dependencies\n        run: poetry install --with ci\n\n      - name: Test with tox for ${{ matrix.python-version }}\n        run: poetry run tox\n\n      - name: Convert coverage\n        run: poetry run coverage xml\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [master]\n  schedule:\n    - cron: \"0 11 * * 2\"\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        # Override automatic language detection by changing the below list\n        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']\n        language: [\"python\"]\n        # Learn more...\n        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v2\n        with:\n          languages: ${{ matrix.language }}\n          # If you wish to specify custom queries, you can do so here or in a config file.\n          # By default, queries listed here will override any specified in a config file.\n          # Prefix the list here with \"+\" to use these queries and those in the config file.\n          # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n      # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n      # If this step fails, then you should remove it and run the build manually (see below)\n      - name: Autobuild\n        uses: github/codeql-action/autobuild@v2\n\n      # ℹ️ Command-line programs to run using the OS shell.\n      # 📚 https://git.io/JvXDl\n\n      # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n      #    and modify them (or add more) to build your code if your project\n      #    uses a compiled language\n\n      #- run: |\n      #   make bootstrap\n      #   make release\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v2\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Build and deploy docs\n\non:\n  push:\n    branches:\n      - \"master\"\n      # Push events to branches matching \"stable/*\"\n      - \"stable/.+\"\n\n  workflow_dispatch: # to trigger manually\n\nenv:\n  POETRY_VERSION: \"1.2.2\"\n  POETRY_VIRTUALENVS_IN_PROJECT: \"1\"\n  LATEST_STABLE_BRANCH: \"stable/2.7\"\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v3\n\n      - uses: ./.github/install_poetry_action\n        with:\n            POETRY_VERSION: ${{ env.POETRY_VERSION }}\n            python_version: \"3.11\"\n\n      - name: Install dependencies\n        run: poetry install --with docs\n\n      - name: Configure git user to make commit\n        run: |\n          git config user.name \"dj-stripe commit bot\"\n          git config user.email \"admin@djstripe.dev\"\n\n      - name: Fetch gh-pages remote changes (if any)\n        run: git fetch origin gh-pages --depth=1\n\n      - name: Deploy (and Update) docs for the branch, ${GITHUB_REF##*/}\n        run: poetry run mike deploy --push --rebase \"${GITHUB_REF##*/}\"\n\n      - name: Set default docs to ${LATEST_STABLE_BRANCH##*/}\n        run: poetry run mike set-default --push --rebase \"${LATEST_STABLE_BRANCH##*/}\"\n"
  },
  {
    "path": ".github/workflows/linting.yml",
    "content": "name: Linting\n\non:\n  push:\n\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [master]\n\nenv:\n  POETRY_VERSION: \"1.2.2\"\n  POETRY_VIRTUALENVS_IN_PROJECT: \"1\"\n\n\njobs:\n  linting:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n\n      - uses: ./.github/install_poetry_action\n        with:\n            POETRY_VERSION: ${{ env.POETRY_VERSION }}\n            python_version: \"3.11\"\n\n      - name: Install pre-commit\n        run: poetry install --with dev\n\n      - name: Run pre-commit\n        run: poetry run pre-commit run --all-files --show-diff-on-failure\n"
  },
  {
    "path": ".gitignore",
    "content": "*.py[cod]\n__pycache__\n.tox\ndj_stripe.egg-info\n*.mo\n\n# Environments\n.venv\n\n# mkdocs\nsite/\n\n# SQLite\n*.sqlite3\n\n# Coverage\n/cover\n.coverage\n\n# Editor files\n.vscode\n.idea\n\n# Do not commit Poetry lockfiles for libraries\npoetry.lock\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "exclude: \".git|.tox|.pytest_cache\"\ndefault_stages: [commit]\nfail_fast: true\n\nrepos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: \"v4.4.0\"\n    hooks:\n      - id: check-builtin-literals\n      - id: check-case-conflict\n      - id: check-toml\n      - id: check-yaml\n      - id: end-of-file-fixer\n      - id: trailing-whitespace\n\n  - repo: https://github.com/psf/black\n    rev: \"23.1.0\"\n    hooks:\n      - id: black\n\n  - repo: https://github.com/timothycrosley/isort\n    rev: \"5.12.0\"\n    hooks:\n      - id: isort\n\n  - repo: https://github.com/adamchainz/django-upgrade\n    rev: \"1.12.0\"\n    hooks:\n      - id: django-upgrade\n        args: [--target-version, \"3.2\"]\n"
  },
  {
    "path": ".readthedocs.yml",
    "content": "# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\nversion: 2\nmkdocs:\n  configuration: mkdocs.yml\n\npython:\n  version: \"3.11\"\n  install:\n    - method: pip\n      path: .\n      extra_requirements:\n        - docs\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 The @dj-stripe Organization\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "djstripe/__init__.py",
    "content": ""
  },
  {
    "path": "djstripe/admin/__init__.py",
    "content": "from .admin import StripeModelAdmin\n\n# Do not remove: This ensures loading of djstripe.admin.admin\n__all__ = [\"StripeModelAdmin\"]\n"
  },
  {
    "path": "djstripe/admin/actions.py",
    "content": "\"\"\"\nDjango Administration Custom Actions Module\n\"\"\"\nfrom django.contrib import admin\nfrom django.contrib.admin import helpers\nfrom django.contrib.admin.utils import quote\nfrom django.shortcuts import render\nfrom django.urls import path, reverse\nfrom django.utils.html import format_html\nfrom django.utils.text import capfirst\n\nfrom . import views\nfrom .forms import CustomActionForm\n\n\nclass CustomActionMixin:\n    # So that actions get shown even if there are 0 instances\n    # https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.show_full_result_count\n    show_full_result_count = False\n\n    def get_urls(self):\n        custom_urls = [\n            path(\n                \"action/<str:action_name>/<str:model_name>/\",\n                self.admin_site.admin_view(views.ConfirmCustomAction.as_view()),\n                name=\"djstripe_custom_action\",\n            ),\n        ]\n        return custom_urls + super().get_urls()\n\n    def get_admin_action_context(self, queryset, action_name, form_class):\n        context = {\n            \"action_name\": action_name,\n            \"model_name\": self.model._meta.model_name,\n            \"info\": [],\n            \"queryset\": queryset,\n            \"changelist_url\": reverse(\n                f\"admin:{self.model._meta.app_label}_{self.model._meta.model_name}_changelist\"\n            ),\n            \"ACTION_CHECKBOX_NAME\": helpers.ACTION_CHECKBOX_NAME,\n            \"form\": form_class(\n                initial={\n                    helpers.ACTION_CHECKBOX_NAME: queryset.values_list(\"pk\", flat=True)\n                },\n                model_name=self.model._meta.model_name,\n                action_name=action_name,\n            ),\n        }\n\n        if action_name == \"_sync_all_instances\":\n            context[\"form\"] = form_class(\n                initial={helpers.ACTION_CHECKBOX_NAME: [action_name]},\n                model_name=self.model._meta.model_name,\n                action_name=action_name,\n            )\n\n        else:\n            for obj in queryset:\n                admin_url = reverse(\n                    f\"admin:{obj._meta.app_label}_{obj._meta.model_name}_change\",\n                    None,\n                    (quote(obj.pk),),\n                )\n                context[\"info\"].append(\n                    format_html(\n                        '{}: <a href=\"{}\">{}</a>',\n                        capfirst(obj._meta.verbose_name),\n                        admin_url,\n                        obj,\n                    )\n                )\n        return context\n\n    def get_actions(self, request):\n        \"\"\"\n        Returns _resync_instances only for\n        models with a defined model.stripe_class.retrieve\n        \"\"\"\n        actions = super().get_actions(request)\n\n        # ensure we return \"_resync_instances\" ONLY for\n        # models that have a GET method\n        if not getattr(self.model.stripe_class, \"retrieve\", None):\n            actions.pop(\"_resync_instances\", None)\n\n        return actions\n\n    @admin.action(description=\"Re-Sync Selected Instances\")\n    def _resync_instances(self, request, queryset):\n        \"\"\"Admin Action to resync selected instances\"\"\"\n        context = self.get_admin_action_context(\n            queryset, \"_resync_instances\", CustomActionForm\n        )\n        return render(request, \"djstripe/admin/confirm_action.html\", context)\n\n    @admin.action(description=\"Sync All Instances for all API Keys\")\n    def _sync_all_instances(self, request, queryset):\n        \"\"\"Admin Action to Sync All Instances\"\"\"\n        context = self.get_admin_action_context(\n            queryset, \"_sync_all_instances\", CustomActionForm\n        )\n        return render(request, \"djstripe/admin/confirm_action.html\", context)\n\n    def changelist_view(self, request, extra_context=None):\n        # we fool it into thinking we have selected some query\n        # since we need to sync all instances\n        post = request.POST.copy()\n        if (\n            helpers.ACTION_CHECKBOX_NAME not in post\n            and post.get(\"action\") == \"_sync_all_instances\"\n        ):\n            post[helpers.ACTION_CHECKBOX_NAME] = None\n            request._set_post(post)\n        return super().changelist_view(request, extra_context)\n"
  },
  {
    "path": "djstripe/admin/admin.py",
    "content": "\"\"\"\nDjango Administration interface definitions\n\"\"\"\nfrom typing import Any, Dict\n\nfrom django.contrib import admin\nfrom django.db import IntegrityError, transaction\nfrom django.shortcuts import render\nfrom stripe.error import InvalidRequestError\n\nfrom djstripe import models\n\nfrom .actions import CustomActionMixin\nfrom .admin_inline import (\n    InvoiceItemInline,\n    LineItemInline,\n    SubscriptionInline,\n    SubscriptionItemInline,\n    SubscriptionScheduleInline,\n    TaxIdInline,\n)\nfrom .filters import CustomerHasSourceListFilter, CustomerSubscriptionStatusListFilter\nfrom .forms import (\n    APIKeyAdminCreateForm,\n    CustomActionForm,\n    WebhookEndpointAdminCreateForm,\n    WebhookEndpointAdminEditForm,\n)\nfrom .utils import ReadOnlyMixin, get_forward_relation_fields_for_model\n\n\n@admin.register(models.IdempotencyKey)\nclass IdempotencyKeyAdmin(ReadOnlyMixin, admin.ModelAdmin):\n    list_display = (\"uuid\", \"action\", \"created\", \"is_expired\", \"livemode\")\n    list_filter = (\"livemode\",)\n    search_fields = (\"uuid\", \"action\")\n\n\n@admin.register(models.WebhookEventTrigger)\nclass WebhookEventTriggerAdmin(ReadOnlyMixin, admin.ModelAdmin):\n    list_display = (\n        \"created\",\n        \"event\",\n        \"stripe_trigger_account\",\n        \"webhook_endpoint\",\n        \"remote_ip\",\n        \"processed\",\n        \"valid\",\n        \"exception\",\n        \"djstripe_version\",\n    )\n    list_filter = (\"created\", \"valid\", \"processed\")\n    list_select_related = (\"event\",)\n    raw_id_fields = get_forward_relation_fields_for_model(models.WebhookEventTrigger)\n\n    def reprocess(self, request, queryset):\n        for trigger in queryset:\n            if not trigger.valid:\n                self.message_user(request, \"Skipped invalid trigger {trigger!r}\")\n                continue\n\n            trigger.process()\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\"stripe_trigger_account\", \"event\", \"webhook_endpoint\")\n        )\n\n\nclass StripeModelAdmin(CustomActionMixin, admin.ModelAdmin):\n    \"\"\"Base class for all StripeModel-based model admins\"\"\"\n\n    change_form_template = \"djstripe/admin/change_form.html\"\n    add_form_template = \"djstripe/admin/add_form.html\"\n    actions = (\"_resync_instances\", \"_sync_all_instances\")\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self.raw_id_fields = get_forward_relation_fields_for_model(self.model)\n\n    def get_list_display(self, request):\n        return (\n            (\"__str__\", \"id\", \"djstripe_owner_account\")\n            + self.list_display\n            + (\"created\", \"livemode\")\n        )\n\n    def get_list_filter(self, request):\n        return self.list_filter + (\"created\", \"livemode\")\n\n    def get_readonly_fields(self, request, obj=None):\n        return self.readonly_fields + (\"id\", \"djstripe_owner_account\", \"created\")\n\n    def get_search_fields(self, request):\n        return self.search_fields + (\"id\",)\n\n    def get_fieldsets(self, request, obj=None):\n        common_fields = (\"livemode\", \"id\", \"djstripe_owner_account\", \"created\")\n        # Have to remove the fields from the common set,\n        # otherwise they'll show up twice.\n        fields = [f for f in self.get_fields(request, obj) if f not in common_fields]\n        return (\n            (None, {\"fields\": common_fields}),\n            (self.model.__name__, {\"fields\": fields}),\n        )\n\n    def get_queryset(self, request):\n        return super().get_queryset(request).select_related(\"djstripe_owner_account\")\n\n\n@admin.register(models.Account)\nclass AccountAdmin(StripeModelAdmin):\n    list_display = (\"business_url\", \"country\", \"default_currency\")\n    list_filter = (\"details_submitted\",)\n    search_fields = (\"settings\", \"business_profile\")\n\n\n@admin.register(models.APIKey)\nclass APIKeyAdmin(admin.ModelAdmin):\n    add_form_template = \"djstripe/admin/add_form.html\"\n    change_form_template = \"djstripe/admin/change_form.html\"\n\n    list_display = (\"__str__\", \"type\", \"djstripe_owner_account\", \"livemode\")\n    readonly_fields = (\"djstripe_owner_account\", \"livemode\", \"type\", \"secret\")\n    search_fields = (\"name\",)\n\n    def get_readonly_fields(self, request, obj=None):\n        if obj is None:\n            return [\"djstripe_owner_account\", \"livemode\", \"type\"]\n        return super().get_readonly_fields(request, obj=obj)\n\n    def get_fields(self, request, obj=None):\n        if obj is None:\n            return APIKeyAdminCreateForm.Meta.fields\n        return [\"type\", \"djstripe_owner_account\", \"livemode\", \"name\", \"secret\"]\n\n    def get_form(self, request, obj=None, **kwargs):\n        if obj is None:\n            return APIKeyAdminCreateForm\n        return super().get_form(request, obj, **kwargs)\n\n    def get_queryset(self, request):\n        return super().get_queryset(request).select_related(\"djstripe_owner_account\")\n\n    def save_model(self, request: Any, obj, form: Any, change: Any) -> None:\n        try:\n            # for non-existent Platform Accounts, because of Account._find_owner_account()\n            # it will try to retrieve by api_key, Account.get_or_retrieve_for_api_key().\n            # Account.get_or_retrieve_for_api_key will create this APIKey! This would cause\n            # an IntegrityError as the APIKey gets created before this form gets saved\n            with transaction.atomic():\n                obj.save()\n        except IntegrityError:\n            pass\n\n\n@admin.register(models.BalanceTransaction)\nclass BalanceTransactionAdmin(ReadOnlyMixin, StripeModelAdmin):\n    list_display = (\n        \"type\",\n        \"net\",\n        \"amount\",\n        \"fee\",\n        \"currency\",\n        \"available_on\",\n        \"status\",\n    )\n    list_filter = (\"status\", \"type\")\n\n\n@admin.register(models.Charge)\nclass ChargeAdmin(StripeModelAdmin):\n    list_display = (\n        \"customer\",\n        \"amount\",\n        \"invoice\",\n        \"payment_method\",\n        \"description\",\n        \"paid\",\n        \"disputed\",\n        \"refunded\",\n        \"fee\",\n    )\n\n    search_fields = (\"customer__id\", \"invoice__id\")\n    list_filter = (\"status\", \"paid\", \"refunded\", \"captured\")\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\n                \"balance_transaction\",\n                \"customer\",\n                \"invoice\",\n                \"payment_method\",\n                \"payment_method__customer\",\n            )\n        )\n\n\n@admin.register(models.Coupon)\nclass CouponAdmin(StripeModelAdmin):\n    list_display = (\n        \"amount_off\",\n        \"percent_off\",\n        \"duration\",\n        \"duration_in_months\",\n        \"redeem_by\",\n        \"max_redemptions\",\n        \"times_redeemed\",\n    )\n    list_filter = (\"duration\", \"redeem_by\")\n    radio_fields = {\"duration\": admin.HORIZONTAL}\n\n\n@admin.register(models.Customer)\nclass CustomerAdmin(StripeModelAdmin):\n    list_display = (\n        \"deleted\",\n        \"subscriber\",\n        \"email\",\n        \"currency\",\n        \"default_source\",\n        \"default_payment_method\",\n        \"coupon\",\n        \"balance\",\n    )\n\n    list_filter = (\n        CustomerHasSourceListFilter,\n        CustomerSubscriptionStatusListFilter,\n        \"deleted\",\n    )\n    search_fields = (\"email\", \"description\", \"deleted\")\n    inlines = (SubscriptionInline, SubscriptionScheduleInline, TaxIdInline)\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\n                \"subscriber\", \"default_source\", \"default_payment_method\", \"coupon\"\n            )\n        )\n\n\n@admin.register(models.Discount)\nclass DiscountAdmin(ReadOnlyMixin, StripeModelAdmin):\n    list_display = (\n        \"customer\",\n        \"coupon\",\n        \"invoice_item\",\n        \"promotion_code\",\n        \"subscription\",\n    )\n    list_filter = (\"customer\", \"start\", \"end\", \"promotion_code\", \"coupon\")\n\n    def get_actions(self, request):\n        \"\"\"\n        Returns _resync_instances only for\n        models with a defined model.stripe_class.retrieve\n        \"\"\"\n        actions = super().get_actions(request)\n\n        # remove \"_sync_all_instances\" as Discounts cannot be listed\n        actions.pop(\"_sync_all_instances\", None)\n\n        return actions\n\n\n@admin.register(models.Dispute)\nclass DisputeAdmin(ReadOnlyMixin, StripeModelAdmin):\n    list_display = (\"reason\", \"status\", \"amount\", \"currency\", \"is_charge_refundable\")\n    list_filter = (\"is_charge_refundable\", \"reason\", \"status\")\n\n\n@admin.register(models.Event)\nclass EventAdmin(ReadOnlyMixin, StripeModelAdmin):\n    list_display = (\"type\", \"request_id\")\n    list_filter = (\"type\", \"created\")\n    search_fields = (\"request_id\",)\n\n\n@admin.register(models.File)\nclass FileAdmin(StripeModelAdmin):\n    list_display = (\"purpose\", \"size\", \"type\")\n    list_filter = (\"purpose\", \"type\")\n    search_fields = (\"filename\",)\n\n\n@admin.register(models.FileLink)\nclass FileLinkAdmin(StripeModelAdmin):\n    list_display = (\"url\",)\n    list_filter = (\"expires_at\",)\n\n    def get_queryset(self, request):\n        return super().get_queryset(request).select_related(\"file\")\n\n\n@admin.register(models.Order)\nclass OrderAdmin(StripeModelAdmin):\n    list_display = (\n        \"amount_total\",\n        \"customer\",\n        \"status\",\n    )\n    list_filter = (\n        \"customer\",\n        \"status\",\n    )\n    list_select_related = (\"customer\", \"payment_intent\")\n\n\n@admin.register(models.PaymentIntent)\nclass PaymentIntentAdmin(StripeModelAdmin):\n    list_display = (\n        \"on_behalf_of\",\n        \"customer\",\n        \"amount\",\n        \"payment_method\",\n        \"currency\",\n        \"description\",\n        \"amount_capturable\",\n        \"amount_received\",\n        \"receipt_email\",\n    )\n    search_fields = (\"customer__id\", \"invoice__id\")\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\n                \"customer\", \"payment_method\", \"payment_method__customer\", \"on_behalf_of\"\n            )\n        )\n\n\n@admin.register(models.Payout)\nclass PayoutAdmin(StripeModelAdmin):\n    list_display = (\n        \"destination\",\n        \"amount\",\n        \"arrival_date\",\n        \"method\",\n        \"status\",\n        \"type\",\n    )\n    list_filter = (\"destination__id\",)\n    search_fields = (\"destination__id\", \"balance_transaction__id\")\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\"balance_transaction\", \"destination\")\n        )\n\n\n@admin.register(models.SetupIntent)\nclass SetupIntentAdmin(StripeModelAdmin):\n    list_display = (\n        \"created\",\n        \"customer\",\n        \"description\",\n        \"on_behalf_of\",\n        \"payment_method\",\n        \"payment_method_types\",\n        \"status\",\n    )\n    list_filter = (\"status\",)\n    search_fields = (\"customer__id\", \"status\")\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\n                \"on_behalf_of\",\n                \"customer\",\n                \"customer__subscriber\",\n                \"payment_method\",\n                \"payment_method__customer\",\n            )\n        )\n\n\n@admin.register(models.Session)\nclass SessionAdmin(StripeModelAdmin):\n    list_display = (\"customer\", \"customer_email\", \"subscription\")\n    list_filter = (\"customer\", \"mode\")\n    search_fields = (\"customer__id\", \"customer_email\")\n\n    def get_queryset(self, request):\n        return super().get_queryset(request).select_related(\"customer\", \"subscription\")\n\n\n@admin.register(models.Invoice)\nclass InvoiceAdmin(StripeModelAdmin):\n    list_display = (\n        \"total\",\n        \"get_default_tax_rates\",\n        \"paid\",\n        \"currency\",\n        \"number\",\n        \"customer\",\n        \"due_date\",\n    )\n    list_filter = (\n        \"status\",\n        \"paid\",\n        \"attempted\",\n        \"created\",\n        \"due_date\",\n        \"period_start\",\n        \"period_end\",\n    )\n    search_fields = (\"customer__id\", \"number\", \"receipt_number\")\n    inlines = (InvoiceItemInline,)\n\n    @admin.display(description=\"Default Tax Rates\")\n    def get_default_tax_rates(self, obj):\n        result = [str(tax_rate) for tax_rate in obj.default_tax_rates.all()]\n        if result:\n            return \", \".join(result)\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\"customer\")\n            .prefetch_related(\"default_tax_rates\")\n        )\n\n\n@admin.register(models.LineItem)\nclass LineItemAdmin(StripeModelAdmin):\n    list_display = (\n        \"amount\",\n        \"invoice_item\",\n        \"subscription\",\n        \"subscription_item\",\n        \"type\",\n    )\n    list_filter = (\"type\", \"invoice_item\", \"subscription\", \"subscription_item\")\n    list_select_related = (\"invoice_item\", \"subscription\", \"subscription_item\")\n\n\n@admin.register(models.Mandate)\nclass MandateAdmin(StripeModelAdmin):\n    list_display = (\"status\", \"type\", \"payment_method\")\n    list_filter = (\"multi_use\", \"single_use\")\n    search_fields = (\"payment_method__id\",)\n\n    def get_queryset(self, request):\n        return super().get_queryset(request).select_related(\"payment_method\")\n\n    def get_actions(self, request):\n        \"\"\"\n        Returns _resync_instances only for\n        models with a defined model.stripe_class.retrieve\n        \"\"\"\n        actions = super().get_actions(request)\n\n        # remove \"_sync_all_instances\" as Mandates cannot be listed\n        actions.pop(\"_sync_all_instances\", None)\n\n        return actions\n\n\n@admin.register(models.Plan)\nclass PlanAdmin(StripeModelAdmin):\n    radio_fields = {\"interval\": admin.HORIZONTAL}\n\n    def get_readonly_fields(self, request, obj=None):\n        \"\"\"Return extra readonly_fields.\"\"\"\n        readonly_fields = super().get_readonly_fields(request, obj)\n\n        if obj:\n            readonly_fields += (\n                \"amount\",\n                \"currency\",\n                \"interval\",\n                \"interval_count\",\n                \"trial_period_days\",\n            )\n\n        return readonly_fields\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\"product\")\n            .prefetch_related(\"subscriptions\")\n        )\n\n\n@admin.register(models.Price)\nclass PriceAdmin(StripeModelAdmin):\n    list_display = (\"product\", \"currency\", \"active\")\n    list_filter = (\"active\", \"type\", \"billing_scheme\", \"tiers_mode\")\n    raw_id_fields = (\"product\",)\n    search_fields = (\"nickname\",)\n    radio_fields = {\"type\": admin.HORIZONTAL}\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\"product\")\n            .prefetch_related(\"product__prices\")\n        )\n\n\n@admin.register(models.Product)\nclass ProductAdmin(StripeModelAdmin):\n    list_display = (\n        \"name\",\n        \"default_price\",\n        \"type\",\n        \"active\",\n        \"url\",\n        \"statement_descriptor\",\n    )\n    list_filter = (\"type\", \"active\", \"shippable\")\n    search_fields = (\"name\", \"statement_descriptor\")\n\n    def get_queryset(self, request):\n        return super().get_queryset(request).prefetch_related(\"prices\")\n\n\n@admin.register(models.Refund)\nclass RefundAdmin(StripeModelAdmin):\n    list_display = (\n        \"amount\",\n        \"currency\",\n        \"charge\",\n        \"reason\",\n        \"status\",\n        \"failure_reason\",\n    )\n    list_filter = (\"reason\", \"status\")\n    search_fields = (\"receipt_number\",)\n\n    def get_queryset(self, request):\n        return super().get_queryset(request).select_related(\"charge\")\n\n\n@admin.register(models.Source)\nclass SourceAdmin(StripeModelAdmin):\n    list_display = (\"customer\", \"type\", \"status\", \"amount\", \"currency\", \"usage\", \"flow\")\n    list_filter = (\"type\", \"status\", \"usage\", \"flow\")\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\"customer\", \"customer__subscriber\")\n        )\n\n\n@admin.register(models.PaymentMethod)\nclass PaymentMethodAdmin(StripeModelAdmin):\n    list_display = (\"customer\", \"type\", \"billing_details\")\n    list_filter = (\"type\",)\n    search_fields = (\"customer__id\",)\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\"customer\", \"customer__subscriber\")\n        )\n\n\n@admin.register(models.Card)\nclass CardAdmin(StripeModelAdmin):\n    list_display = (\"customer\", \"account\")\n    search_fields = (\"customer__id\", \"account__id\")\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\n                \"customer\",\n                \"customer__default_source\",\n                \"customer__default_payment_method\",\n                \"account\",\n            )\n        )\n\n\n@admin.register(models.BankAccount)\nclass BankAccountAdmin(StripeModelAdmin):\n    list_display = (\"customer\", \"account\")\n    search_fields = (\"customer__id\", \"account__id\")\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\n                \"customer\",\n                \"customer__default_source\",\n                \"customer__default_payment_method\",\n                \"account\",\n            )\n        )\n\n\n@admin.register(models.ShippingRate)\nclass ShippingRateAdmin(StripeModelAdmin):\n    list_display = (\"display_name\", \"active\", \"tax_behavior\", \"tax_code\")\n    list_filter = (\"active\", \"tax_behavior\")\n    list_select_related = (\"tax_code\",)\n\n\n@admin.register(models.Subscription)\nclass SubscriptionAdmin(StripeModelAdmin):\n    list_display = (\"customer\", \"status\", \"get_product_name\", \"get_default_tax_rates\")\n    list_filter = (\"status\", \"cancel_at_period_end\")\n\n    inlines = (SubscriptionItemInline, SubscriptionScheduleInline, LineItemInline)\n\n    def get_actions(self, request):\n        # get all actions\n        actions = super().get_actions(request)\n        actions[\"_cancel\"] = self.get_action(\"_cancel\")\n        return actions\n\n    @admin.action(description=\"Cancel selected subscriptions\")\n    def _cancel(self, request, queryset):\n        \"\"\"Cancel a subscription.\"\"\"\n        context = self.get_admin_action_context(queryset, \"_cancel\", CustomActionForm)\n        return render(request, \"djstripe/admin/confirm_action.html\", context)\n\n    def get_queryset(self, request):\n        return (\n            super()\n            .get_queryset(request)\n            .select_related(\n                \"customer\",\n                \"plan\",\n                \"plan__product\",\n            )\n            .prefetch_related(\n                \"customer__subscriptions\",\n                \"customer__subscriptions__plan\",\n                \"customer__subscriptions__plan__product\",\n                \"default_tax_rates\",\n            )\n        )\n\n    @admin.display(description=\"Default Tax Rates\")\n    def get_default_tax_rates(self, obj):\n        result = [str(tax_rate) for tax_rate in obj.default_tax_rates.all()]\n        if result:\n            return \", \".join(result)\n\n    @admin.display(description=\"Product Name\")\n    def get_product_name(self, obj):\n        if obj.plan and obj.plan.product:\n            return obj.plan.product.name\n\n\n@admin.register(models.SubscriptionSchedule)\nclass SubscriptionScheduleAdmin(StripeModelAdmin):\n    list_display = (\"status\", \"subscription\", \"current_phase\", \"customer\")\n    list_filter = (\"status\", \"subscription\", \"customer\")\n    list_select_related = (\"customer\", \"customer__subscriber\", \"subscription\")\n\n    @admin.display(description=\"Release Selected Subscription Schedules\")\n    def _release_subscription_schedule(self, request, queryset):\n        \"\"\"Release a SubscriptionSchedule.\"\"\"\n        context = self.get_admin_action_context(\n            queryset, \"_release_subscription_schedule\", CustomActionForm\n        )\n        return render(request, \"djstripe/admin/confirm_action.html\", context)\n\n    @admin.display(description=\"Cancel Selected Subscription Schedules\")\n    def _cancel_subscription_schedule(self, request, queryset):\n        \"\"\"Cancel a SubscriptionSchedule.\"\"\"\n        context = self.get_admin_action_context(\n            queryset, \"_cancel_subscription_schedule\", CustomActionForm\n        )\n        return render(request, \"djstripe/admin/confirm_action.html\", context)\n\n    def get_actions(self, request):\n        # get all actions\n        actions = super().get_actions(request)\n        actions[\"_release_subscription_schedule\"] = self.get_action(\n            \"_release_subscription_schedule\"\n        )\n        actions[\"_cancel_subscription_schedule\"] = self.get_action(\n            \"_cancel_subscription_schedule\"\n        )\n        return actions\n\n\n@admin.register(models.TaxCode)\nclass TaxCodeAdmin(StripeModelAdmin):\n    list_display = (\"name\", \"description\")\n    list_filter = (\"name\",)\n\n\n@admin.register(models.TaxRate)\nclass TaxRateAdmin(StripeModelAdmin):\n    list_display = (\"active\", \"display_name\", \"inclusive\", \"jurisdiction\", \"percentage\")\n    list_filter = (\"active\", \"inclusive\", \"jurisdiction\")\n\n\n@admin.register(models.Transfer)\nclass TransferAdmin(StripeModelAdmin):\n    list_display = (\"amount\", \"description\")\n\n\n@admin.register(models.TransferReversal)\nclass TransferReversalAdmin(StripeModelAdmin):\n    list_display = (\"amount\", \"transfer\")\n\n\n@admin.register(models.ApplicationFee)\nclass ApplicationFeeAdmin(StripeModelAdmin):\n    list_display = (\"amount\", \"account\")\n\n\n@admin.register(models.ApplicationFeeRefund)\nclass ApplicationFeeReversalAdmin(StripeModelAdmin):\n    list_display = (\"amount\", \"fee\")\n\n\n@admin.register(models.UsageRecord)\nclass UsageRecordAdmin(StripeModelAdmin):\n    list_display = (\"quantity\", \"subscription_item\", \"timestamp\")\n\n    def get_queryset(self, request):\n        return super().get_queryset(request).select_related(\"subscription_item\")\n\n    def get_actions(self, request):\n        \"\"\"\n        Returns _resync_instances only for\n        models with a defined model.stripe_class.retrieve\n        \"\"\"\n        actions = super().get_actions(request)\n\n        # remove \"_sync_all_instances\" as UsageRecords cannot be listed\n        actions.pop(\"_sync_all_instances\", None)\n\n        return actions\n\n\n@admin.register(models.UsageRecordSummary)\nclass UsageRecordSummaryAdmin(StripeModelAdmin):\n    list_display = (\"invoice\", \"subscription_item\", \"total_usage\")\n\n    def get_queryset(self, request):\n        return (\n            super().get_queryset(request).select_related(\"invoice\", \"subscription_item\")\n        )\n\n\n@admin.register(models.WebhookEndpoint)\nclass WebhookEndpointAdmin(CustomActionMixin, admin.ModelAdmin):\n    change_form_template = \"djstripe/admin/webhook_endpoint/change_form.html\"\n    delete_confirmation_template = (\n        \"djstripe/admin/webhook_endpoint/delete_confirmation.html\"\n    )\n    add_form_template = \"djstripe/admin/webhook_endpoint/add_form.html\"\n    readonly_fields = (\"url\",)\n    list_display = (\n        \"__str__\",\n        \"djstripe_owner_account\",\n        \"livemode\",\n        \"created\",\n        \"api_version\",\n    )\n    actions = (\"_resync_instances\", \"_sync_all_instances\")\n\n    def get_actions(self, request):\n        actions = super().get_actions(request)\n        # Disable the mass-delete action for webhook endpoints.\n        # We don't want to enable deleting multiple endpoints on Stripe at once.\n        if \"delete_selected\" in actions:\n            del actions[\"delete_selected\"]\n        return actions\n\n    def get_form(self, request, obj=None, **kwargs):\n        if obj:\n            return WebhookEndpointAdminEditForm\n        return WebhookEndpointAdminCreateForm\n\n    def get_readonly_fields(self, request, obj=None):\n        if obj:\n            return (\n                \"id\",\n                \"livemode\",\n                \"api_version\",\n                \"url\",\n                \"created\",\n                \"djstripe_owner_account\",\n                \"djstripe_uuid\",\n            )\n        return super().get_readonly_fields(request, obj=obj)\n\n    def get_fieldsets(self, request, obj=None):\n        if obj:\n            # if djstripe_uuid is null, this is not a dj-stripe webhook\n            header_fields = [\"id\", \"livemode\", \"djstripe_owner_account\", \"url\"]\n            advanced_fields = [\n                \"enabled_events\",\n                \"metadata\",\n                \"api_version\",\n                \"djstripe_uuid\",\n            ]\n            if obj.djstripe_uuid:\n                core_fields = [\"enabled\", \"base_url\", \"description\"]\n            else:\n                core_fields = [\"enabled\", \"description\"]\n        else:\n            header_fields = [\"djstripe_owner_account\", \"livemode\"]\n            core_fields = [\"description\", \"base_url\", \"connect\"]\n            advanced_fields = [\"metadata\", \"api_version\", \"enabled_events\"]\n\n        return [\n            (None, {\"fields\": header_fields}),\n            (\"Endpoint configuration\", {\"fields\": core_fields}),\n            (\n                \"Advanced\",\n                {\"fields\": advanced_fields, \"classes\": [\"collapse\"]},\n            ),\n        ]\n\n    def get_changeform_initial_data(self, request) -> Dict[str, str]:\n        ret = super().get_changeform_initial_data(request)\n        base_url = f\"{request.scheme}://{request.get_host()}\"\n        ret.setdefault(\"base_url\", base_url)\n        return ret\n\n    def delete_model(self, request, obj: models.WebhookEndpoint):\n        try:\n            obj._api_delete()\n        except InvalidRequestError as e:\n            if e.user_message.startswith(\"No such webhook endpoint: \"):\n                # Webhook was already deleted in Stripe\n                pass\n            else:\n                raise\n\n        return super().delete_model(request, obj)\n\n    def get_queryset(self, request):\n        return super().get_queryset(request).select_related(\"djstripe_owner_account\")\n"
  },
  {
    "path": "djstripe/admin/admin_inline.py",
    "content": "\"\"\"\nDjango Administration Inline interface definitions\n\"\"\"\nfrom django.contrib import admin\n\nfrom djstripe import models\n\nfrom .utils import get_forward_relation_fields_for_model\n\n\nclass SubscriptionInline(admin.StackedInline):\n    \"\"\"A TabularInline for use models.Subscription.\"\"\"\n\n    model = models.Subscription\n    extra = 0\n    readonly_fields = (\"id\", \"created\", \"djstripe_owner_account\")\n    raw_id_fields = get_forward_relation_fields_for_model(model)\n    show_change_link = True\n\n\nclass SubscriptionScheduleInline(admin.StackedInline):\n    \"\"\"A TabularInline for use models.SubscriptionSchedule.\"\"\"\n\n    model = models.SubscriptionSchedule\n    extra = 0\n    readonly_fields = (\"id\", \"created\", \"djstripe_owner_account\")\n    raw_id_fields = get_forward_relation_fields_for_model(model)\n    show_change_link = True\n\n    def __init__(self, parent_model, admin_site):\n        super().__init__(parent_model, admin_site)\n\n        # dynamically set fk_name as SubscriptionScheduleInline is used\n        # in CustomerAdmin as well as SubscriptionAdmin\n        if parent_model is models.Subscription:\n            self.fk_name = \"subscription\"\n\n\nclass TaxIdInline(admin.TabularInline):\n    \"\"\"A TabularInline for use models.Subscription.\"\"\"\n\n    model = models.TaxId\n    extra = 0\n    max_num = 5\n    readonly_fields = (\n        \"id\",\n        \"created\",\n        \"verification\",\n        \"livemode\",\n        \"country\",\n        \"djstripe_owner_account\",\n    )\n    show_change_link = True\n\n\nclass SubscriptionItemInline(admin.StackedInline):\n    \"\"\"A TabularInline for use models.Subscription.\"\"\"\n\n    model = models.SubscriptionItem\n    extra = 0\n    readonly_fields = (\"id\", \"created\", \"djstripe_owner_account\")\n    raw_id_fields = get_forward_relation_fields_for_model(model)\n    show_change_link = True\n\n\nclass InvoiceItemInline(admin.StackedInline):\n    \"\"\"A TabularInline for use InvoiceItem.\"\"\"\n\n    model = models.InvoiceItem\n    extra = 0\n    readonly_fields = (\"id\", \"created\", \"djstripe_owner_account\")\n    raw_id_fields = get_forward_relation_fields_for_model(model)\n    show_change_link = True\n\n\nclass LineItemInline(admin.StackedInline):\n    \"\"\"A TabularInline for LineItem.\"\"\"\n\n    model = models.LineItem\n    extra = 0\n    readonly_fields = (\"id\", \"created\", \"djstripe_owner_account\")\n    raw_id_fields = get_forward_relation_fields_for_model(model)\n    show_change_link = True\n"
  },
  {
    "path": "djstripe/admin/filters.py",
    "content": "\"\"\"\nDjango Administration Custom Filters Module\n\"\"\"\nfrom django.contrib import admin\n\nfrom djstripe import models\n\n\nclass BaseHasSourceListFilter(admin.SimpleListFilter):\n    title = \"source presence\"\n    parameter_name = \"has_source\"\n\n    def lookups(self, request, model_admin):\n        \"\"\"\n        Return a list of tuples.\n\n        The first element in each tuple is the coded value for the option that will\n        appear in the URL query. The second element is the\n        human-readable name for the option that will appear\n        in the right sidebar.\n        source:\n        https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter\n        \"\"\"\n        return ((\"yes\", \"Has a source\"), (\"no\", \"Has no source\"))\n\n    def queryset(self, request, queryset):\n        \"\"\"\n        Return the filtered queryset based on the value provided in the query string.\n\n        source:\n        https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter\n        \"\"\"\n        filter_args = {self._filter_arg_key: None}\n\n        if self.value() == \"yes\":\n            return queryset.exclude(**filter_args)\n        if self.value() == \"no\":\n            return queryset.filter(**filter_args)\n\n\nclass CustomerHasSourceListFilter(BaseHasSourceListFilter):\n    _filter_arg_key = \"default_source\"\n\n\nclass InvoiceCustomerHasSourceListFilter(BaseHasSourceListFilter):\n    _filter_arg_key = \"customer__default_source\"\n\n\nclass CustomerSubscriptionStatusListFilter(admin.SimpleListFilter):\n    \"\"\"A SimpleListFilter used with Customer admin.\"\"\"\n\n    title = \"subscription status\"\n    parameter_name = \"sub_status\"\n\n    def lookups(self, request, model_admin):\n        \"\"\"\n        Return a list of tuples.\n\n        The first element in each tuple is the coded value for the option that will\n        appear in the URL query. The second element is the\n        human-readable name for the option that will appear\n        in the right sidebar.\n        source:\n        https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter\n        \"\"\"\n        statuses = [\n            [x, x.replace(\"_\", \" \").title()]\n            for x in models.Subscription.objects.values_list(\n                \"status\", flat=True\n            ).distinct()\n        ]\n        statuses.append([\"none\", \"No Subscription\"])\n        return statuses\n\n    def queryset(self, request, queryset):\n        \"\"\"\n        Return the filtered queryset based on the value provided in the query string.\n\n        source:\n        https://docs.djangoproject.com/en/1.10/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter\n        \"\"\"\n        if self.value() is None:\n            return queryset.all()\n        else:\n            return queryset.filter(subscriptions__status=self.value()).distinct()\n"
  },
  {
    "path": "djstripe/admin/forms.py",
    "content": "\"\"\"\nModule for all dj-stripe Admin app forms\n\"\"\"\nfrom typing import Optional\nfrom urllib.parse import urljoin\n\nfrom django import forms\nfrom django.contrib.admin import helpers\nfrom django.urls import reverse\nfrom stripe.error import AuthenticationError, InvalidRequestError\n\nfrom djstripe import enums, models, utils\nfrom djstripe.signals import ENABLED_EVENTS\n\n\nclass CustomActionForm(forms.Form):\n    \"\"\"Form for Custom Django Admin Actions\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        # remove model_name kwarg\n        model_name = kwargs.pop(\"model_name\")\n\n        # remove action_name kwarg\n        action_name = kwargs.pop(\"action_name\")\n\n        super().__init__(*args, **kwargs)\n\n        model = utils.get_model(model_name)\n        # set choices attribute\n        # form field to keep track of all selected instances\n        # for the Custom Django Admin Action\n\n        if action_name == \"_sync_all_instances\":\n            self.fields[helpers.ACTION_CHECKBOX_NAME] = forms.MultipleChoiceField(\n                widget=forms.MultipleHiddenInput,\n                choices=[(action_name, action_name)],\n            )\n        else:\n            self.fields[helpers.ACTION_CHECKBOX_NAME] = forms.MultipleChoiceField(\n                widget=forms.MultipleHiddenInput,\n                choices=zip(\n                    model.objects.values_list(\"pk\", flat=True),\n                    model.objects.values_list(\"pk\", flat=True),\n                ),\n            )\n\n\nclass APIKeyAdminCreateForm(forms.ModelForm):\n    class Meta:\n        model = models.APIKey\n        fields = [\"name\", \"secret\"]\n\n    def _post_clean(self):\n        super()._post_clean()\n\n        if not self.errors:\n            if (\n                self.instance.type == enums.APIKeyType.secret\n                and self.instance.djstripe_owner_account is None\n            ):\n                try:\n                    self.instance.refresh_account()\n                except AuthenticationError as e:\n                    self.add_error(\"secret\", str(e))\n\n\nclass WebhookEndpointAdminBaseForm(forms.ModelForm):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.fields[\"description\"].help_text = \"\"\n        self.fields[\"description\"].widget.attrs[\"rows\"] = 3\n\n    def _get_field_name(self, stripe_field: Optional[str]) -> Optional[str]:\n        if stripe_field is None:\n            return None\n        if stripe_field == \"url\":\n            return \"base_url\"\n        else:\n            return stripe_field.partition(\"[\")[0]\n\n    def save(self, commit: bool = False):\n        # If we do the following in _post_clean(), the data doesn't save properly.\n        if not self._stripe_data:\n            raise ValueError(\"_stripe_data is not present. \")\n\n        # Update scenario\n        # Add back secret if endpoint already exists\n        if self.instance.pk and not self._stripe_data.get(\"secret\"):\n            self._stripe_data[\"secret\"] = self.instance.secret\n\n        # Retrieve the api key that was used to create the endpoint\n        api_key = getattr(self, \"_stripe_api_key\", None)\n        if api_key:\n            self.instance = models.WebhookEndpoint.sync_from_stripe_data(\n                self._stripe_data, api_key=api_key\n            )\n        else:\n            self.instance = models.WebhookEndpoint.sync_from_stripe_data(\n                self._stripe_data\n            )\n        return super().save(commit=commit)\n\n\nclass WebhookEndpointAdminCreateForm(WebhookEndpointAdminBaseForm):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.fields[\"djstripe_owner_account\"].label = \"Stripe account\"\n        self.fields[\"djstripe_owner_account\"].help_text = \"\"\n\n    enabled_events = forms.MultipleChoiceField(\n        label=\"Enabled Events\",\n        required=True,\n        help_text=(\n            \"The list of events to enable for this endpoint. \"\n            \"['*'] indicates that all events are enabled, except those that require explicit selection.\"\n        ),\n        choices=zip(ENABLED_EVENTS, ENABLED_EVENTS),\n        initial=[\"*\"],\n    )\n    livemode = forms.BooleanField(\n        label=\"Live mode\",\n        required=False,\n        help_text=\"Whether to create this endpoint in live mode or test mode\",\n    )\n    base_url = forms.URLField(\n        required=True,\n        help_text=(\n            \"Sets the base URL (scheme and host) for the endpoint. \"\n            \"The final full URL will be auto-generated by dj-stripe.\"\n        ),\n    )\n    connect = forms.BooleanField(\n        label=\"Listen to events on Connected accounts\",\n        initial=False,\n        required=False,\n        help_text=(\n            \"Clients can make requests as connected accounts using the special \"\n            \"header `Stripe-Account` which should contain a Stripe account ID \"\n            \"(usually starting with the prefix `acct_`).\"\n        ),\n    )\n\n    class Meta:\n        model = models.WebhookEndpoint\n        fields = (\n            \"enabled_events\",\n            \"livemode\",\n            \"djstripe_owner_account\",\n            \"description\",\n            \"base_url\",\n            \"connect\",\n            \"api_version\",\n            \"metadata\",\n        )\n\n    # Hook into _post_clean() instead of save().\n    # This is used by Django for ModelForm logic. It's internal, but exactly\n    # what we need to add errors after the data has been validated locally.\n    def _post_clean(self):\n        base_url = self.cleaned_data[\"base_url\"]\n        url_path = reverse(\n            \"djstripe:djstripe_webhook_by_uuid\",\n            kwargs={\"uuid\": self.instance.djstripe_uuid},\n        )\n        url = urljoin(base_url, url_path, allow_fragments=False)\n\n        metadata = self.instance.metadata or {}\n        metadata[\"djstripe_uuid\"] = str(self.instance.djstripe_uuid)\n\n        _api_key = {}\n        account = self.cleaned_data[\"djstripe_owner_account\"]\n        livemode = self.cleaned_data[\"livemode\"]\n        if account:\n            self._stripe_api_key = _api_key[\"api_key\"] = account.get_default_api_key(\n                livemode=livemode\n            )\n\n        try:\n            self._stripe_data = models.WebhookEndpoint._api_create(\n                url=url,\n                api_version=self.cleaned_data[\"api_version\"] or None,\n                description=self.cleaned_data[\"description\"],\n                enabled_events=self.cleaned_data.get(\"enabled_events\"),\n                metadata=metadata,\n                connect=self.cleaned_data[\"connect\"],\n                **_api_key,\n            )\n        except InvalidRequestError as e:\n            field_name = self._get_field_name(e.param)\n            self.add_error(field_name, e.user_message)\n\n        return super()._post_clean()\n\n\nclass WebhookEndpointAdminEditForm(WebhookEndpointAdminBaseForm):\n    enabled_events = forms.MultipleChoiceField(\n        label=\"Enabled Events\",\n        required=True,\n        help_text=(\n            \"The list of events to enable for this endpoint. \"\n            \"['*'] indicates that all events are enabled, except those that require explicit selection.\"\n        ),\n        choices=zip(ENABLED_EVENTS, ENABLED_EVENTS),\n    )\n    base_url = forms.URLField(\n        required=False,\n        help_text=(\n            \"Updating this changes the base URL of the endpoint. \"\n            \"MUST be publicly-accessible.\"\n        ),\n    )\n    enabled = forms.BooleanField(\n        initial=True,\n        required=False,\n        help_text=\"When disabled, the endpoint will not receive events.\",\n    )\n\n    class Meta:\n        model = models.WebhookEndpoint\n        fields = (\"description\", \"base_url\", \"enabled_events\", \"metadata\")\n\n    def get_initial_for_field(self, field, field_name):\n        if field_name == \"base_url\":\n            metadata = self.instance.metadata or {}\n            djstripe_uuid = metadata.get(\"djstripe_uuid\")\n            if djstripe_uuid:\n                # if a djstripe_uuid is set (for dj-stripe endpoints), set the base_url\n                endpoint_path = reverse(\n                    \"djstripe:djstripe_webhook_by_uuid\", kwargs={\"uuid\": djstripe_uuid}\n                )\n                return self.instance.url.replace(endpoint_path, \"\")\n        return super().get_initial_for_field(field, field_name)\n\n    def _post_clean(self):\n        base_url = self.cleaned_data.get(\"base_url\", \"\")\n        if base_url and self.instance.djstripe_uuid:\n            url_path = reverse(\n                \"djstripe:djstripe_webhook_by_uuid\",\n                kwargs={\"uuid\": self.instance.djstripe_uuid},\n            )\n            url = urljoin(base_url, url_path, allow_fragments=False)\n        else:\n            url = self.instance.url\n\n        try:\n            self._stripe_data = self.instance._api_update(\n                url=url,\n                description=self.cleaned_data.get(\"description\"),\n                enabled_events=self.cleaned_data.get(\"enabled_events\"),\n                metadata=self.cleaned_data.get(\"metadata\"),\n                disabled=(not self.cleaned_data.get(\"enabled\")),\n            )\n        except InvalidRequestError as e:\n            field_name = self._get_field_name(e.param)\n            self.add_error(field_name, e.user_message)\n\n        return super()._post_clean()\n"
  },
  {
    "path": "djstripe/admin/utils.py",
    "content": "\"\"\"\nDjango Administration Utils Module\n\"\"\"\n\n\nclass ReadOnlyMixin:\n    def has_add_permission(self, request):\n        return False\n\n    def has_change_permission(self, request, obj=None):\n        return False\n\n\ndef get_forward_relation_fields_for_model(model):\n    \"\"\"Return an iterable of the field names that are forward relations,\n    I.E ManyToManyField, OneToOneField, and ForeignKey.\n\n    Useful for perhaps ensuring the admin is always using raw ID fields for\n    newly added forward relation fields.\n    \"\"\"\n    return [\n        field.name\n        for field in model._meta.get_fields()\n        # Get only relation fields\n        if field.is_relation\n        # Exclude auto relation fields, like reverse one to one.\n        and not field.auto_created\n        # We only want forward relations.\n        and any((field.many_to_many, field.one_to_one, field.many_to_one))\n    ]\n"
  },
  {
    "path": "djstripe/admin/views.py",
    "content": "\"\"\"\ndj-stripe - Views related to the djstripe app.\n\"\"\"\nimport logging\n\nimport stripe\nfrom django.contrib import messages\nfrom django.contrib.admin import helpers, site\nfrom django.core.management import call_command\nfrom django.http import HttpResponseRedirect\nfrom django.urls import reverse\nfrom django.views.generic import FormView\n\nfrom djstripe import utils\n\nfrom .forms import CustomActionForm\n\nlogger = logging.getLogger(__name__)\n\n\nclass ConfirmCustomAction(FormView):\n    template_name = \"djstripe/admin/confirm_action.html\"\n    form_class = CustomActionForm\n\n    def form_valid(self, form):\n        model_name = self.kwargs.get(\"model_name\")\n        action_name = self.kwargs.get(\"action_name\")\n        model = utils.get_model(model_name)\n\n        pks = form.cleaned_data.get(helpers.ACTION_CHECKBOX_NAME)\n\n        # get the handler\n        handler = getattr(self, action_name)\n\n        if action_name == \"_sync_all_instances\":\n            # Create Empty Queryset to be able to extract the model name\n            # as sync all would sync all instances anyway and there is no guarantee\n            # that the local db already has all the instances.\n            qs = model.objects.none()\n        else:\n            qs = utils.get_queryset(pks, model_name)\n\n        # Process Request\n        handler(self.request, qs)\n\n        return HttpResponseRedirect(\n            reverse(\n                f\"admin:{model._meta.app_label}_{model._meta.model_name}_changelist\"\n            )\n        )\n\n    def form_invalid(self, form):\n        model_name = self.kwargs.get(\"model_name\")\n        action_name = self.kwargs.get(\"action_name\")\n\n        model = utils.get_model(model_name)\n        pks = form.data.getlist(helpers.ACTION_CHECKBOX_NAME)\n        pks = list(map(int, pks))\n\n        queryset = utils.get_queryset(pks, model_name)\n\n        model_admin = site._registry.get(model)\n        for msg in form.errors.values():\n            messages.add_message(self.request, messages.ERROR, msg.as_text())\n\n        return model_admin.get_action(action_name)[0](\n            model_admin, self.request, queryset\n        )\n\n    def get_form_kwargs(self):\n        form_kwargs = super().get_form_kwargs()\n        form_kwargs[\"model_name\"] = self.kwargs.get(\"model_name\")\n        form_kwargs[\"action_name\"] = self.kwargs.get(\"action_name\")\n        return form_kwargs\n\n    def _resync_instances(self, request, queryset):\n        for instance in queryset:\n            api_key = instance.default_api_key\n            try:\n                if instance.djstripe_owner_account:\n                    stripe_data = instance.api_retrieve(\n                        stripe_account=instance.djstripe_owner_account.id,\n                        api_key=api_key,\n                    )\n                else:\n                    stripe_data = instance.api_retrieve()\n                instance.__class__.sync_from_stripe_data(stripe_data, api_key=api_key)\n                messages.success(request, f\"Successfully Synced: {instance}\")\n            except stripe.error.PermissionError as error:\n                messages.warning(request, error)\n            except stripe.error.InvalidRequestError:\n                raise\n\n    def _sync_all_instances(self, request, queryset):\n        \"\"\"Admin Action to Sync All Instances\"\"\"\n        call_command(\"djstripe_sync_models\", queryset.model.__name__)\n        messages.success(request, \"Successfully Synced All Instances\")\n\n    def _cancel(self, request, queryset):\n        \"\"\"Cancel a subscription.\"\"\"\n        for subscription in queryset:\n            try:\n                instance = subscription.cancel()\n                messages.success(request, f\"Successfully Canceled: {instance}\")\n            except stripe.error.InvalidRequestError as error:\n                messages.warning(request, error)\n\n    def _release_subscription_schedule(self, request, queryset):\n        \"\"\"Release a SubscriptionSchedule.\"\"\"\n        for subscription_schedule in queryset:\n            try:\n                instance = subscription_schedule.release()\n                messages.success(request, f\"Successfully Released: {instance}\")\n            except stripe.error.InvalidRequestError as error:\n                messages.warning(request, error)\n\n    def _cancel_subscription_schedule(self, request, queryset):\n        \"\"\"Cancel a SubscriptionSchedule.\"\"\"\n        for subscription_schedule in queryset:\n            try:\n                instance = subscription_schedule.cancel()\n                messages.success(request, f\"Successfully Canceled: {instance}\")\n            except stripe.error.InvalidRequestError as error:\n                messages.warning(request, error)\n"
  },
  {
    "path": "djstripe/apps.py",
    "content": "\"\"\"\ndj-stripe - Django + Stripe Made Easy\n\"\"\"\nimport pkg_resources\nfrom django.apps import AppConfig\n\n__version__ = pkg_resources.get_distribution(\"dj-stripe\").version\n\n\nclass DjstripeAppConfig(AppConfig):\n    \"\"\"\n    An AppConfig for dj-stripe which loads system checks\n    and event handlers once Django is ready.\n    \"\"\"\n\n    name = \"djstripe\"\n    default_auto_field = \"django.db.models.AutoField\"\n\n    def ready(self):\n        import stripe\n\n        from . import checks, event_handlers  # noqa (register event handlers)\n\n        # Set app info\n        # https://stripe.com/docs/building-plugins#setappinfo\n        stripe.set_app_info(\n            \"dj-stripe\",\n            version=__version__,\n            url=\"https://github.com/dj-stripe/dj-stripe\",\n        )\n"
  },
  {
    "path": "djstripe/checks.py",
    "content": "\"\"\"\ndj-stripe System Checks\n\"\"\"\nimport re\n\nfrom django.core import checks\nfrom django.db.utils import DatabaseError\n\nSTRIPE_API_VERSION_PATTERN = re.compile(\n    r\"(?P<year>\\d{4})-(?P<month>\\d{1,2})-(?P<day>\\d{1,2})(; [\\w=]*)?$\"\n)\n\n\n# 4 possibilities:\n# Keys in admin and in settings\n# Keys in admin and not in settings\n# Keys not in admin but in settings\n# Keys not in admin and not in settings\n@checks.register(\"djstripe\")\ndef check_stripe_api_key(app_configs=None, **kwargs):\n    \"\"\"Check the user has configured API live/test keys correctly.\"\"\"\n\n    def _check_stripe_api_in_settings(messages):\n        if djstripe_settings.STRIPE_LIVE_MODE:\n            if not djstripe_settings.LIVE_API_KEY.startswith((\"sk_live_\", \"rk_live_\")):\n                msg = \"Bad Stripe live API key.\"\n                hint = 'STRIPE_LIVE_SECRET_KEY should start with \"sk_live_\"'\n                messages.append(checks.Info(msg, hint=hint, id=\"djstripe.I003\"))\n        elif not djstripe_settings.TEST_API_KEY.startswith((\"sk_test_\", \"rk_test_\")):\n            msg = \"Bad Stripe test API key.\"\n            hint = 'STRIPE_TEST_SECRET_KEY should start with \"sk_test_\"'\n            messages.append(checks.Info(msg, hint=hint, id=\"djstripe.I004\"))\n\n    from djstripe.models import APIKey\n\n    from .settings import djstripe_settings\n\n    messages = []\n\n    try:\n        # get all APIKey objects in the db\n        api_qs = APIKey.objects.all()\n\n        if not api_qs.exists():\n            msg = \"You don't have any API Keys in the database. Did you forget to add them?\"\n            hint = \"Add STRIPE_TEST_SECRET_KEY and STRIPE_LIVE_SECRET_KEY directly from the Django Admin.\"\n            messages.append(checks.Info(msg, hint=hint, id=\"djstripe.I001\"))\n\n            # Keys not in admin but in settings\n            if djstripe_settings.STRIPE_SECRET_KEY:\n                msg = \"Your keys are defined in the settings files. You can now add and manage them directly from the django admin.\"\n                hint = \"Add STRIPE_TEST_SECRET_KEY and STRIPE_LIVE_SECRET_KEY directly from the Django Admin.\"\n                messages.append(checks.Info(msg, hint=hint, id=\"djstripe.I002\"))\n\n                # Ensure keys defined in settings files are valid\n                _check_stripe_api_in_settings(messages)\n\n        # Keys in admin and in settings\n        elif djstripe_settings.STRIPE_SECRET_KEY:\n            msg = \"Your keys are defined in the settings files and are also in the admin. You can now add and manage them directly from the django admin.\"\n            hint = \"We suggest adding STRIPE_TEST_SECRET_KEY and STRIPE_LIVE_SECRET_KEY directly from the Django Admin. And removing them from the settings files.\"\n            messages.append(checks.Info(msg, hint=hint, id=\"djstripe.I002\"))\n\n            # Ensure keys defined in settings files are valid\n            _check_stripe_api_in_settings(messages)\n\n    except DatabaseError:\n        # Skip the check - Database most likely not migrated yet\n        return []\n\n    return messages\n\n\ndef validate_stripe_api_version(version):\n    \"\"\"\n    Check the API version is formatted correctly for Stripe.\n\n    The expected format is `YYYY-MM-DD` (an iso8601 date) or\n    for access to alpha or beta releases the expected format is: `YYYY-MM-DD; modelname_version=version_number`.\n    Ex \"2020-08-27; orders_beta=v3\"\n\n    :param version: The version to set for the Stripe API.\n    :type version: ``str``\n    :returns bool: Whether the version is formatted correctly.\n    \"\"\"\n    return re.match(STRIPE_API_VERSION_PATTERN, version)\n\n\n@checks.register(\"djstripe\")\ndef check_stripe_api_version(app_configs=None, **kwargs):\n    \"\"\"Check the user has configured API version correctly.\"\"\"\n    from .settings import djstripe_settings\n\n    messages = []\n    default_version = djstripe_settings.DEFAULT_STRIPE_API_VERSION\n    version = djstripe_settings.STRIPE_API_VERSION\n\n    if not validate_stripe_api_version(version):\n        msg = f\"Invalid Stripe API version: {version!r}\"\n        hint = \"STRIPE_API_VERSION should be formatted as: YYYY-MM-DD\"\n        messages.append(checks.Critical(msg, hint=hint, id=\"djstripe.C004\"))\n\n    if version != default_version:\n        msg = (\n            f\"The Stripe API version has a non-default value of '{version!r}'. \"\n            \"Non-default versions are not explicitly supported, and may \"\n            \"cause compatibility issues.\"\n        )\n        hint = f\"Use the dj-stripe default for Stripe API version: {default_version}\"\n        messages.append(checks.Warning(msg, hint=hint, id=\"djstripe.W001\"))\n\n    return messages\n\n\n@checks.register(\"djstripe\")\ndef check_stripe_api_host(app_configs=None, **kwargs):\n    \"\"\"\n    Check that STRIPE_API_HOST is not being used in production.\n    \"\"\"\n    from django.conf import settings\n\n    messages = []\n\n    if not settings.DEBUG and hasattr(settings, \"STRIPE_API_HOST\"):\n        messages.append(\n            checks.Warning(\n                \"STRIPE_API_HOST should not be set in production! \"\n                \"This is most likely unintended.\",\n                hint=\"Remove STRIPE_API_HOST from your Django settings.\",\n                id=\"djstripe.W002\",\n            )\n        )\n\n    return messages\n\n\n@checks.register(\"djstripe\")\ndef check_webhook_secret(app_configs=None, **kwargs):\n    \"\"\"\n    Check that DJSTRIPE_WEBHOOK_SECRET looks correct\n    \"\"\"\n\n    def check_webhook_endpoint_secret(secret, messages, endpoint=None):\n        if secret and not secret.startswith(\"whsec_\"):\n            if endpoint:\n                extra_msg = (\n                    f\"The secret for Webhook Endpoint: {endpoint} does not look valid\"\n                )\n            else:\n                extra_msg = \"DJSTRIPE_WEBHOOK_SECRET does not look valid\"\n\n            messages.append(\n                checks.Warning(\n                    extra_msg,\n                    hint=\"It should start with whsec_...\",\n                    id=\"djstripe.W003\",\n                )\n            )\n        return messages\n\n    from .models import WebhookEndpoint\n    from .settings import djstripe_settings\n\n    messages = []\n    try:\n        webhooks = list(WebhookEndpoint.objects.all())\n    except DatabaseError:\n        # skip the db-based check (db most likely not migrated yet)\n        webhooks = []\n\n    if webhooks:\n        for endpoint in webhooks:\n            secret = endpoint.secret\n            # check secret\n            check_webhook_endpoint_secret(secret, messages, endpoint=endpoint)\n    else:\n        secret = djstripe_settings.WEBHOOK_SECRET\n        # check secret\n        check_webhook_endpoint_secret(secret, messages)\n\n    return messages\n\n\ndef _check_webhook_endpoint_validation(secret, messages, endpoint=None):\n    if not secret:\n        if endpoint:\n            extra_msg = f\"but Webhook Endpoint: {endpoint} has no secret set\"\n            secret_attr = \"secret\"\n        else:\n            extra_msg = \"but DJSTRIPE_WEBHOOK_SECRET is not set\"\n            secret_attr = \"DJSTRIPE_WEBHOOK_SECRET\"\n\n        messages.append(\n            checks.Info(\n                f\"DJSTRIPE_WEBHOOK_VALIDATION is set to 'verify_signature' {extra_msg}\",\n                hint=f\"Set {secret_attr} from Django shell or set DJSTRIPE_WEBHOOK_VALIDATION='retrieve_event'\",\n                id=\"djstripe.I006\",\n            )\n        )\n    return messages\n\n\n@checks.register(\"djstripe\")\ndef check_webhook_validation(app_configs=None, **kwargs):\n    \"\"\"\n    Check that DJSTRIPE_WEBHOOK_VALIDATION is valid\n    \"\"\"\n    from .models import WebhookEndpoint\n    from .settings import djstripe_settings\n\n    setting_name = \"DJSTRIPE_WEBHOOK_VALIDATION\"\n\n    messages = []\n\n    validation_options = (\"verify_signature\", \"retrieve_event\")\n\n    if djstripe_settings.WEBHOOK_VALIDATION is None:\n        messages.append(\n            checks.Warning(\n                \"Webhook validation is disabled, this is a security risk if the \"\n                \"webhook view is enabled\",\n                hint=f\"Set {setting_name} to one of: {validation_options}\",\n                id=\"djstripe.W004\",\n            )\n        )\n    elif djstripe_settings.WEBHOOK_VALIDATION == \"verify_signature\":\n        try:\n            webhooks = list(WebhookEndpoint.objects.all())\n        except DatabaseError:\n            # Skip the db-based check (database most likely not migrated yet)\n            webhooks = []\n\n        if webhooks:\n            for endpoint in webhooks:\n                secret = endpoint.secret\n                # check secret\n                _check_webhook_endpoint_validation(secret, messages, endpoint=endpoint)\n        else:\n            secret = djstripe_settings.WEBHOOK_SECRET\n            # check secret\n            _check_webhook_endpoint_validation(secret, messages)\n\n    elif djstripe_settings.WEBHOOK_VALIDATION not in validation_options:\n        messages.append(\n            checks.Critical(\n                f\"{setting_name} is invalid\",\n                hint=f\"Set {setting_name} to one of: {validation_options} or None\",\n                id=\"djstripe.C007\",\n            )\n        )\n\n    return messages\n\n\n@checks.register(\"djstripe\")\ndef check_webhook_endpoint_has_secret(app_configs=None, **kwargs):\n    \"\"\"Checks if all Webhook Endpoints have not empty secrets.\"\"\"\n    from djstripe.models import WebhookEndpoint\n\n    messages = []\n\n    try:\n        qs = list(WebhookEndpoint.objects.filter(secret=\"\").all())\n    except DatabaseError:\n        # Skip the check - Database most likely not migrated yet\n        return []\n\n    for webhook in qs:\n        webhook_url = webhook.get_stripe_dashboard_url()\n        messages.append(\n            checks.Warning(\n                (\n                    f\"The secret of Webhook Endpoint: {webhook} is not populated \"\n                    \"in the db. Events sent to it will not work properly.\"\n                ),\n                hint=(\n                    \"This can happen if it was deleted and resynced as Stripe \"\n                    \"sends the webhook secret ONLY on the creation call. \"\n                    \"Please use the django shell and update the secret with \"\n                    f\"the value from {webhook_url}\"\n                ),\n                id=\"djstripe.W005\",\n            )\n        )\n\n    return messages\n\n\n@checks.register(\"djstripe\")\ndef check_subscriber_key_length(app_configs=None, **kwargs):\n    \"\"\"\n    Check that DJSTRIPE_SUBSCRIBER_CUSTOMER_KEY fits in metadata.\n\n    Docs: https://stripe.com/docs/api#metadata\n    \"\"\"\n    from .settings import djstripe_settings\n\n    messages = []\n\n    key = djstripe_settings.SUBSCRIBER_CUSTOMER_KEY\n    key_max_length = 40\n    if key and len(key) > key_max_length:\n        messages.append(\n            checks.Error(\n                \"DJSTRIPE_SUBSCRIBER_CUSTOMER_KEY must be no more than \"\n                f\"{key_max_length} characters long\",\n                hint=f\"Current value: {key!r}\",\n                id=\"djstripe.E001\",\n            )\n        )\n\n    return messages\n\n\n@checks.register(\"djstripe\")\ndef check_djstripe_settings_foreign_key_to_field(app_configs=None, **kwargs):\n    \"\"\"\n    Check that DJSTRIPE_FOREIGN_KEY_TO_FIELD is set to a valid value.\n    \"\"\"\n    from django.conf import settings\n\n    setting_name = \"DJSTRIPE_FOREIGN_KEY_TO_FIELD\"\n    hint = (\n        f'Set {setting_name} to \"id\" if this is a new installation, '\n        f'otherwise set it to \"djstripe_id\".'\n    )\n    messages = []\n\n    if not hasattr(settings, setting_name):\n        messages.append(\n            checks.Error(\n                f\"{setting_name} is not set.\",\n                hint=hint,\n                id=\"djstripe.E002\",\n            )\n        )\n    elif getattr(settings, setting_name) not in (\"id\", \"djstripe_id\"):\n        setting_value = getattr(settings, setting_name)\n        messages.append(\n            checks.Error(\n                f\"{setting_value} is not a valid value for {setting_name}.\",\n                hint=hint,\n                id=\"djstripe.E003\",\n            )\n        )\n\n    return messages\n\n\n@checks.register(\"djstripe\")\ndef check_webhook_event_callback_accepts_api_key(app_configs=None, **kwargs):\n    \"\"\"\n    Checks if the custom callback accepts atleast 2 mandatory positional arguments\n    \"\"\"\n    from inspect import signature\n\n    from .settings import djstripe_settings\n\n    messages = []\n\n    # callable can have exactly 2 arguments or\n    # if more than two, the rest need to be optional.\n    callable = djstripe_settings.WEBHOOK_EVENT_CALLBACK\n\n    if callable:\n        # Deprecated in 2.8.0. Raise a warning.\n        messages.append(\n            checks.Warning(\n                \"DJSTRIPE_WEBHOOK_EVENT_CALLBACK is deprecated. See release notes for details.\",\n                hint=(\n                    \"If you need to trigger a function during webhook processing, \"\n                    \"you can use djstripe.signals instead.\\n\"\n                    \"Available signals:\\n\"\n                    \"- djstripe.signals.webhook_pre_validate\\n\"\n                    \"- djstripe.signals.webhook_post_validate\\n\"\n                    \"- djstripe.signals.webhook_pre_process\\n\"\n                    \"- djstripe.signals.webhook_post_process\\n\"\n                    \"- djstripe.signals.webhook_processing_error\"\n                ),\n            )\n        )\n\n        sig = signature(callable)\n        signature_sz = len(sig.parameters.keys())\n\n        if signature_sz < 2:\n            messages.append(\n                checks.Error(\n                    f\"{callable} accepts {signature_sz} arguments.\",\n                    hint=\"You may have forgotten to add api_key parameter to your custom callback.\",\n                    id=\"djstripe.E004\",\n                )\n            )\n\n    return messages\n"
  },
  {
    "path": "djstripe/enums.py",
    "content": "import operator\nfrom collections import OrderedDict\n\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass EnumMetaClass(type):\n    def __init__(cls, name, bases, classdict):\n        def _human_enum_values(enum):\n            return cls.__choices__[enum]\n\n        # add a class attribute\n        cls.humanize = _human_enum_values\n\n    @classmethod\n    def __prepare__(cls, name, bases):\n        return OrderedDict()\n\n    def __new__(cls, name, bases, classdict):\n        members = []\n        keys = {}\n        choices = OrderedDict()\n        for key, value in classdict.items():\n            if key.startswith(\"__\"):\n                continue\n            members.append(key)\n            if isinstance(value, tuple):\n                value, alias = value\n                keys[alias] = key\n            else:\n                alias = None\n            keys[alias or key] = key\n            choices[alias or key] = value\n\n        for k, v in keys.items():\n            classdict[v] = k\n\n        classdict[\"__choices__\"] = choices\n        classdict[\"__members__\"] = members\n\n        # Note: Differences between Python 2.x and Python 3.x force us to\n        # explicitly use unicode here, and to explicitly sort the list. In\n        # Python 2.x, class members are unordered and so the ordering will\n        # vary on different systems based on internal hashing. Without this\n        # Django will continually require new no-op migrations.\n        classdict[\"choices\"] = tuple(\n            (str(k), str(v))\n            for k, v in sorted(choices.items(), key=operator.itemgetter(0))\n        )\n\n        return type.__new__(cls, name, bases, classdict)\n\n\nclass Enum(metaclass=EnumMetaClass):\n    pass\n\n\nclass APIKeyType(Enum):\n    \"\"\"\n    API Key Types (internal model only)\n    \"\"\"\n\n    publishable = _(\"Publishable key\")\n    secret = _(\"Secret key\")\n    restricted = _(\"Restricted key\")\n\n\nclass ApiErrorCode(Enum):\n    \"\"\"\n    Charge failure error codes.\n\n    https://stripe.com/docs/error-codes\n    \"\"\"\n\n    account_already_exists = _(\"Account already exists\")\n    account_country_invalid_address = _(\"Account country invalid address\")\n    account_invalid = _(\"Account invalid\")\n    account_number_invalid = _(\"Account number invalid\")\n    alipay_upgrade_required = _(\"Alipay upgrade required\")\n    amount_too_large = _(\"Amount too large\")\n    amount_too_small = _(\"Amount too small\")\n    api_key_expired = _(\"Api key expired\")\n    balance_insufficient = _(\"Balance insufficient\")\n    bank_account_exists = _(\"Bank account exists\")\n    bank_account_unusable = _(\"Bank account unusable\")\n    bank_account_unverified = _(\"Bank account unverified\")\n    bitcoin_upgrade_required = _(\"Bitcoin upgrade required\")\n    card_declined = _(\"Card was declined\")\n    charge_already_captured = _(\"Charge already captured\")\n    charge_already_refunded = _(\"Charge already refunded\")\n    charge_disputed = _(\"Charge disputed\")\n    charge_exceeds_source_limit = _(\"Charge exceeds source limit\")\n    charge_expired_for_capture = _(\"Charge expired for capture\")\n    country_unsupported = _(\"Country unsupported\")\n    coupon_expired = _(\"Coupon expired\")\n    customer_max_subscriptions = _(\"Customer max subscriptions\")\n    email_invalid = _(\"Email invalid\")\n    expired_card = _(\"Expired card\")\n    idempotency_key_in_use = _(\"Idempotency key in use\")\n    incorrect_address = _(\"Incorrect address\")\n    incorrect_cvc = _(\"Incorrect security code\")\n    incorrect_number = _(\"Incorrect number\")\n    incorrect_zip = _(\"ZIP code failed validation\")\n    instant_payouts_unsupported = _(\"Instant payouts unsupported\")\n    invalid_card_type = _(\"Invalid card type\")\n    invalid_charge_amount = _(\"Invalid charge amount\")\n    invalid_cvc = _(\"Invalid security code\")\n    invalid_expiry_month = _(\"Invalid expiration month\")\n    invalid_expiry_year = _(\"Invalid expiration year\")\n    invalid_number = _(\"Invalid number\")\n    invalid_source_usage = _(\"Invalid source usage\")\n    invoice_no_customer_line_items = _(\"Invoice no customer line items\")\n    invoice_no_subscription_line_items = _(\"Invoice no subscription line items\")\n    invoice_not_editable = _(\"Invoice not editable\")\n    invoice_upcoming_none = _(\"Invoice upcoming none\")\n    livemode_mismatch = _(\"Livemode mismatch\")\n    missing = _(\"No card being charged\")\n    not_allowed_on_standard_account = _(\"Not allowed on standard account\")\n    order_creation_failed = _(\"Order creation failed\")\n    order_required_settings = _(\"Order required settings\")\n    order_status_invalid = _(\"Order status invalid\")\n    order_upstream_timeout = _(\"Order upstream timeout\")\n    out_of_inventory = _(\"Out of inventory\")\n    parameter_invalid_empty = _(\"Parameter invalid empty\")\n    parameter_invalid_integer = _(\"Parameter invalid integer\")\n    parameter_invalid_string_blank = _(\"Parameter invalid string blank\")\n    parameter_invalid_string_empty = _(\"Parameter invalid string empty\")\n    parameter_missing = _(\"Parameter missing\")\n    parameter_unknown = _(\"Parameter unknown\")\n    parameters_exclusive = _(\"Parameters exclusive\")\n    payment_intent_authentication_failure = _(\"Payment intent authentication failure\")\n    payment_intent_incompatible_payment_method = _(\n        \"Payment intent incompatible payment method\"\n    )\n    payment_intent_invalid_parameter = _(\"Payment intent invalid parameter\")\n    payment_intent_payment_attempt_failed = _(\"Payment intent payment attempt failed\")\n    payment_intent_unexpected_state = _(\"Payment intent unexpected state\")\n    payment_method_unactivated = _(\"Payment method unactivated\")\n    payment_method_unexpected_state = _(\"Payment method unexpected state\")\n    payouts_not_allowed = _(\"Payouts not allowed\")\n    platform_api_key_expired = _(\"Platform api key expired\")\n    postal_code_invalid = _(\"Postal code invalid\")\n    processing_error = _(\"Processing error\")\n    product_inactive = _(\"Product inactive\")\n    rate_limit = _(\"Rate limit\")\n    resource_already_exists = _(\"Resource already exists\")\n    resource_missing = _(\"Resource missing\")\n    routing_number_invalid = _(\"Routing number invalid\")\n    secret_key_required = _(\"Secret key required\")\n    sepa_unsupported_account = _(\"SEPA unsupported account\")\n    shipping_calculation_failed = _(\"Shipping calculation failed\")\n    sku_inactive = _(\"SKU inactive\")\n    state_unsupported = _(\"State unsupported\")\n    tax_id_invalid = _(\"Tax id invalid\")\n    taxes_calculation_failed = _(\"Taxes calculation failed\")\n    testmode_charges_only = _(\"Testmode charges only\")\n    tls_version_unsupported = _(\"TLS version unsupported\")\n    token_already_used = _(\"Token already used\")\n    token_in_use = _(\"Token in use\")\n    transfers_not_allowed = _(\"Transfers not allowed\")\n    upstream_order_creation_failed = _(\"Upstream order creation failed\")\n    url_invalid = _(\"URL invalid\")\n\n    # deprecated\n    invalid_swipe_data = _(\"Invalid swipe data\")\n\n\nclass AccountType(Enum):\n    standard = _(\"Standard\")\n    express = _(\"Express\")\n    custom = _(\"Custom\")\n\n\nclass BalanceTransactionReportingCategory(Enum):\n    \"\"\"\n    https://stripe.com/docs/reports/reporting-categories\n    \"\"\"\n\n    advance = _(\"Advance\")\n    advance_funding = _(\"Advance funding\")\n    anticipation_repayment = _(\"Anticipation loan repayment (BR)\")\n    charge = _(\"Charge\")\n    charge_failure = _(\"Charge failure\")\n    connect_collection_transfer = _(\"Stripe Connect collection transfer\")\n    connect_reserved_funds = _(\"Stripe Connect reserved funds\")\n    dispute = _(\"Dispute\")\n    dispute_reversal = _(\"Dispute reversal\")\n    fee = _(\"Stripe fee\")\n    issuing_authorization_hold = _(\"Issuing authorization hold\")\n    issuing_authorization_release = _(\"Issuing authorization release\")\n    issuing_dispute = _(\"Issuing dispute\")\n    issuing_transaction = _(\"Issuing transaction\")\n    other_adjustment = _(\"Other adjustment\")\n    partial_capture_reversal = _(\"Partial capture reversal\")\n    payout = _(\"Payout\")\n    payout_reversal = _(\"Payout reversal\")\n    platform_earning = _(\"Stripe Connect platform earning\")\n    platform_earning_refund = _(\"Stripe Connect platform earning refund\")\n    refund = _(\"Refund\")\n    refund_failure = _(\"Refund failure\")\n    risk_reserved_funds = _(\"Risk-reserved funds\")\n    tax = _(\"Tax\")\n    topup = _(\"Top-up\")\n    topup_reversal = _(\"Top-up reversal\")\n    transfer = _(\"Stripe Connect transfer\")\n    transfer_reversal = _(\"Stripe Connect transfer reversal\")\n\n\nclass BalanceTransactionStatus(Enum):\n    available = _(\"Available\")\n    pending = _(\"Pending\")\n\n\nclass BalanceTransactionType(Enum):\n    # https://stripe.com/docs/reports/balance-transaction-types\n    adjustment = _(\"Adjustment\")\n    advance = _(\"Advance\")\n    advance_funding = _(\"Advance funding\")\n    anticipation_repayment = _(\"Anticipation loan repayment\")\n    application_fee = _(\"Application fee\")\n    application_fee_refund = _(\"Application fee refund\")\n    balance_transfer_inbound = _(\"Balance transfer (inbound)\")\n    balance_transfer_outbound = _(\"Balance transfer (outbound)\")\n    charge = _(\"Charge\")\n    connect_collection_transfer = _(\"Connect collection transfer\")\n    contribution = _(\"Charitable contribution\")\n    issuing_authorization_hold = _(\"Issuing authorization hold\")\n    issuing_authorization_release = _(\"Issuing authorization release\")\n    issuing_dispute = _(\"Issuing dispute\")\n    issuing_transaction = _(\"Issuing transaction\")\n    network_cost = _(\"Network cost\")\n    payment = _(\"Payment\")\n    payment_failure_refund = _(\"Payment failure refund\")\n    payment_refund = _(\"Payment refund\")\n    payout = _(\"Payout\")\n    payout_cancel = _(\"Payout cancellation\")\n    payout_failure = _(\"Payout failure\")\n    refund = _(\"Refund\")\n    refund_failure = _(\"Refund failure\")\n    reserve_transaction = _(\"Reserve transaction\")\n    reserved_funds = _(\"Reserved funds\")\n    stripe_fee = _(\"Stripe fee\")\n    stripe_fx_fee = _(\"Stripe currency conversion fee\")\n    tax_fee = _(\"Tax fee\")\n    topup = _(\"Topup\")\n    topup_reversal = _(\"Topup reversal\")\n    transfer = _(\"Transfer\")\n    transfer_cancel = _(\"Transfer cancel\")\n    transfer_failure = _(\"Transfer failure\")\n    transfer_refund = _(\"Transfer refund\")\n    validation = _(\"Validation\")\n\n\nclass BankAccountHolderType(Enum):\n    individual = _(\"Individual\")\n    company = _(\"Company\")\n\n\nclass BankAccountStatus(Enum):\n    new = _(\"New\")\n    validated = _(\"Validated\")\n    verified = _(\"Verified\")\n    verification_failed = _(\"Verification failed\")\n    errored = _(\"Errored\")\n\n\nclass BillingScheme(Enum):\n    per_unit = _(\"Per-unit\")\n    tiered = _(\"Tiered\")\n\n\nclass BusinessType(Enum):\n    individual = _(\"Individual\")\n    company = _(\"Company\")\n    non_profit = _(\"Non Profit\")\n    government_entity = _(\"Government Entity\")\n\n\nclass CaptureMethod(Enum):\n    automatic = _(\"Automatic\")\n    manual = _(\"Manual\")\n\n\nclass CardCheckResult(Enum):\n    pass_ = (_(\"Pass\"), \"pass\")\n    fail = _(\"Fail\")\n    unavailable = _(\"Unavailable\")\n    unchecked = _(\"Unchecked\")\n\n\nclass CardBrand(Enum):\n    AmericanExpress = (_(\"American Express\"), \"American Express\")\n    DinersClub = (_(\"Diners Club\"), \"Diners Club\")\n    Discover = _(\"Discover\")\n    JCB = _(\"JCB\")\n    MasterCard = _(\"MasterCard\")\n    UnionPay = _(\"UnionPay\")\n    Visa = _(\"Visa\")\n    Unknown = _(\"Unknown\")\n\n\nclass CardFundingType(Enum):\n    credit = _(\"Credit\")\n    debit = _(\"Debit\")\n    prepaid = _(\"Prepaid\")\n    unknown = _(\"Unknown\")\n\n\nclass CardTokenizationMethod(Enum):\n    apple_pay = _(\"Apple Pay\")\n    android_pay = _(\"Android Pay\")\n\n\nclass ChargeStatus(Enum):\n    succeeded = _(\"Succeeded\")\n    pending = _(\"Pending\")\n    failed = _(\"Failed\")\n\n\nclass ConfirmationMethod(Enum):\n    automatic = _(\"Automatic\")\n    manual = _(\"Manual\")\n\n\nclass CouponDuration(Enum):\n    once = _(\"Once\")\n    repeating = _(\"Multi-month\")\n    forever = _(\"Forever\")\n\n\nclass CustomerTaxExempt(Enum):\n    none = _(\"None\")\n    exempt = _(\"Exempt\")\n    reverse = _(\"Reverse\")\n\n\nclass DisputeReason(Enum):\n    duplicate = _(\"Duplicate\")\n    fraudulent = _(\"Fraudulent\")\n    subscription_canceled = _(\"Subscription canceled\")\n    product_unacceptable = _(\"Product unacceptable\")\n    product_not_received = _(\"Product not received\")\n    unrecognized = _(\"Unrecognized\")\n    credit_not_processed = _(\"Credit not processed\")\n    general = _(\"General\")\n    incorrect_account_details = _(\"Incorrect account details\")\n    insufficient_funds = _(\"Insufficient funds\")\n    bank_cannot_process = _(\"Bank cannot process\")\n    debit_not_authorized = _(\"Debit not authorized\")\n    customer_initiated = _(\"Customer-initiated\")\n\n\nclass DisputeStatus(Enum):\n    warning_needs_response = _(\"Warning needs response\")\n    warning_under_review = _(\"Warning under review\")\n    warning_closed = _(\"Warning closed\")\n    needs_response = _(\"Needs response\")\n    under_review = _(\"Under review\")\n    charge_refunded = _(\"Charge refunded\")\n    won = _(\"Won\")\n    lost = _(\"Lost\")\n\n\nclass FilePurpose(Enum):\n    account_requirement = _(\"Account requirement\")\n    additional_verification = _(\"Additional verification\")\n    business_icon = _(\"Business icon\")\n    business_logo = _(\"Business logo\")\n    customer_signature = _(\"Customer signature\")\n    credit_note = _(\"Credit Note\")\n    dispute_evidence = _(\"Dispute evidence\")\n    document_provider_identity_document = _(\"Document provider identity document\")\n    finance_report_run = _(\"Finance report run\")\n    identity_document = _(\"Identity document\")\n    identity_document_downloadable = _(\"Identity document (downloadable)\")\n    invoice_statement = _(\"Invoice statement\")\n    pci_document = _(\"PCI document\")\n    selfie = _(\"Selfie (Stripe Identity)\")\n    sigma_scheduled_query = _(\"Sigma scheduled query\")\n    tax_document_user_upload = _(\"Tax document user upload\")\n\n\nclass FileType(Enum):\n    pdf = _(\"PDF\")\n    jpg = _(\"JPG\")\n    png = _(\"PNG\")\n    csv = _(\"CSV\")\n    xls = _(\"XLS\")\n    xlsx = _(\"XLSX\")\n    docx = _(\"DOCX\")\n\n\nclass InvoiceBillingReason(Enum):\n    subscription_cycle = _(\"Subscription cycle\")\n    subscription_create = _(\"Subscription create\")\n    subscription_update = _(\"Subscription update\")\n    subscription = _(\"Subscription\")\n    manual = _(\"Manual\")\n    upcoming = _(\"Upcoming\")\n    subscription_threshold = _(\"Subscription threshold\")\n    automatic_pending_invoice_item_invoice = _(\"Automatic pending invoice item invoice\")\n\n\nclass InvoiceCollectionMethod(Enum):\n    charge_automatically = _(\"Charge automatically\")\n    send_invoice = _(\"Send invoice\")\n\n\nclass InvoiceStatus(Enum):\n    draft = _(\"Draft\")\n    open = _(\"Open\")\n    paid = _(\"Paid\")\n    uncollectible = _(\"Uncollectible\")\n    void = _(\"Void\")\n\n\nclass InvoiceorLineItemType(Enum):\n    invoice_item = _(\"Invoice Item\")\n    line_item = _(\"Line Item\")\n    unsupported = _(\"Unsupported\")\n\n\nclass IntentUsage(Enum):\n    on_session = _(\"On session\")\n    off_session = _(\"Off session\")\n\n\nclass IntentStatus(Enum):\n    \"\"\"\n    Status of Intents which apply both to PaymentIntents\n    and SetupIntents.\n    \"\"\"\n\n    requires_payment_method = _(\n        \"Intent created and requires a Payment Method to be attached.\"\n    )\n    requires_confirmation = _(\"Intent is ready to be confirmed.\")\n    requires_action = _(\"Payment Method require additional action, such as 3D secure.\")\n    processing = _(\"Required actions have been handled.\")\n    canceled = _(\n        \"Cancellation invalidates the intent for future confirmation and \"\n        \"cannot be undone.\"\n    )\n\n\nclass LineItem(Enum):\n    invoiceitem = _(\"Invoice Item\")\n    subscription = _(\"Subscription\")\n\n\nclass MandateStatus(Enum):\n    active = _(\"Active\")\n    inactive = _(\"Inactive\")\n    pending = _(\"Pending\")\n\n\nclass MandateType(Enum):\n    multi_use = _(\"Multi-use\")\n    single_use = _(\"Single-use\")\n\n\nclass OrderStatus(Enum):\n    open = _(\"Open\")\n    submitted = _(\"Submitted\")\n    processing = _(\"Processing\")\n    complete = _(\"Complete\")\n    canceled = _(\"Canceled\")\n\n\n# TODO - maybe refactor Enum so that inheritance works,\n#  then PaymentIntentStatus/SetupIntentStatus can inherit from IntentStatus\nclass PaymentIntentStatus(Enum):\n    requires_payment_method = _(\n        \"Intent created and requires a Payment Method to be attached.\"\n    )\n    requires_confirmation = _(\"Intent is ready to be confirmed.\")\n    requires_action = _(\"Payment Method require additional action, such as 3D secure.\")\n    processing = _(\"Required actions have been handled.\")\n    requires_capture = _(\"Capture the funds on the cards which have been put on holds.\")\n    canceled = _(\n        \"Cancellation invalidates the intent for future confirmation and \"\n        \"cannot be undone.\"\n    )\n    succeeded = _(\"The funds are in your account.\")\n\n\nclass SetupIntentStatus(Enum):\n    requires_payment_method = _(\n        \"Intent created and requires a Payment Method to be attached.\"\n    )\n    requires_confirmation = _(\"Intent is ready to be confirmed.\")\n    requires_action = _(\"Payment Method require additional action, such as 3D secure.\")\n    processing = _(\"Required actions have been handled.\")\n    canceled = _(\n        \"Cancellation invalidates the intent for future confirmation and \"\n        \"cannot be undone.\"\n    )\n    succeeded = _(\n        \"Setup was successful and the payment method is optimized for future payments.\"\n    )\n\n\nclass PaymentMethodType(Enum):\n    acss_debit = _(\"Acss Dbit\")\n    affirm = _(\"Affirm\")\n    afterpay_clearpay = _(\"Afterpay Clearpay\")\n    alipay = _(\"Alipay\")\n    au_becs_debit = _(\"BECS Debit (Australia)\")\n    bacs_debit = _(\"Bacs Direct Debit\")\n    bancontact = _(\"Bancontact\")\n    blik = _(\"BLIK\")\n    boleto = _(\"Boleto\")\n    card = _(\"Card\")\n    card_present = _(\"Card present\")\n    customer_balance = _(\"Customer Balance\")\n    eps = _(\"EPS\")\n    fpx = _(\"FPX\")\n    giropay = _(\"Giropay\")\n    grabpay = _(\"Grabpay\")\n    ideal = _(\"iDEAL\")\n    interac_present = _(\"Interac (card present)\")\n    klarna = _(\"Klarna\")\n    konbini = _(\"Konbini\")\n    link = _(\"Link\")\n    oxxo = _(\"OXXO\")\n    p24 = _(\"Przelewy24\")\n    paynow = _(\"PayNow\")\n    pix = _(\"Pix\")\n    promptpay = _(\"PromptPay\")\n    sepa_debit = _(\"SEPA Direct Debit\")\n    sofort = _(\"SOFORT\")\n    us_bank_account = _(\"ACH Direct Debit\")\n    wechat_pay = _(\"Wechat Pay\")\n\n\nclass PayoutFailureCode(Enum):\n    \"\"\"\n    Payout failure error codes.\n\n    https://stripe.com/docs/api#payout_failures\n    \"\"\"\n\n    account_closed = _(\"Bank account has been closed.\")\n    account_frozen = _(\"Bank account has been frozen.\")\n    bank_account_restricted = _(\"Bank account has restrictions on payouts allowed.\")\n    bank_ownership_changed = _(\"Destination bank account has changed ownership.\")\n    could_not_process = _(\"Bank could not process payout.\")\n    debit_not_authorized = _(\"Debit transactions not approved on the bank account.\")\n    declined = _(\n        \"The bank has declined this transfer. Please contact the bank before retrying.\"\n    )\n    insufficient_funds = _(\"Stripe account has insufficient funds.\")\n    invalid_account_number = _(\"Invalid account number\")\n    incorrect_account_holder_name = _(\n        \"Your bank notified us that the bank account holder name on file is incorrect.\"\n    )\n    incorrect_account_holder_address = _(\n        \"Your bank notified us that the bank account holder address on file is incorrect.\"\n    )\n    incorrect_account_holder_tax_id = _(\n        \"Your bank notified us that the bank account holder tax ID on file is incorrect.\"\n    )\n    invalid_currency = _(\"Bank account does not support currency.\")\n    no_account = _(\"Bank account could not be located.\")\n    unsupported_card = _(\"Card no longer supported.\")\n\n\nclass PayoutMethod(Enum):\n    standard = _(\"Standard\")\n    instant = _(\"Instant\")\n\n\nclass PayoutSourceType(Enum):\n    bank_account = _(\"Bank account\")\n    fpx = _(\"Financial Process Exchange (FPX)\")\n    card = _(\"Card\")\n\n\nclass PayoutStatus(Enum):\n    paid = _(\"Paid\")\n    pending = _(\"Pending\")\n    in_transit = _(\"In transit\")\n    canceled = _(\"Canceled\")\n    failed = _(\"Failed\")\n\n\nclass PayoutType(Enum):\n    bank_account = _(\"Bank account\")\n    card = _(\"Card\")\n\n\nclass PaymentIntentCancellationReason(Enum):\n    # see also SetupIntentCancellationReason\n    # User provided reasons:\n    duplicate = _(\"Duplicate\")\n    fraudulent = _(\"Fraudulent\")\n    abandoned = _(\"Abandoned\")\n    requested_by_customer = _(\"Requested by Customer\")\n    # Reasons generated by Stripe internally\n    failed_invoice = _(\"Failed invoice\")\n    void_invoice = _(\"Void invoice\")\n    automatic = _(\"Automatic\")\n\n\nclass PlanAggregateUsage(Enum):\n    last_during_period = _(\"Last during period\")\n    last_ever = _(\"Last ever\")\n    max = _(\"Max\")\n    sum = _(\"Sum\")\n\n\nclass PlanInterval(Enum):\n    day = _(\"Day\")\n    week = _(\"Week\")\n    month = _(\"Month\")\n    year = _(\"Year\")\n\n\nclass PriceTiersMode(Enum):\n    graduated = _(\"Graduated\")\n    volume = _(\"Volume-based\")\n\n\nclass PriceType(Enum):\n    one_time = _(\"One-time\")\n    recurring = _(\"Recurring\")\n\n\nclass PriceUsageType(Enum):\n    metered = _(\"Metered\")\n    licensed = _(\"Licensed\")\n\n\n# Legacy\nPlanTiersMode = PriceTiersMode\nPlanUsageType = PriceUsageType\n\n\nclass ProductType(Enum):\n    good = _(\"Good\")\n    service = _(\"Service\")\n\n\nclass SetupIntentCancellationReason(Enum):\n    # see also PaymentIntentCancellationReason\n    abandoned = _(\"Abandoned\")\n    requested_by_customer = _(\"Requested by Customer\")\n    duplicate = _(\"Duplicate\")\n\n\nclass ScheduledQueryRunStatus(Enum):\n    canceled = _(\"Canceled\")\n    failed = _(\"Failed\")\n    timed_out = _(\"Timed out\")\n\n\nclass SourceFlow(Enum):\n    redirect = _(\"Redirect\")\n    receiver = _(\"Receiver\")\n    code_verification = _(\"Code verification\")\n    none = _(\"None\")\n\n\nclass SourceStatus(Enum):\n    canceled = _(\"Canceled\")\n    chargeable = _(\"Chargeable\")\n    consumed = _(\"Consumed\")\n    failed = _(\"Failed\")\n    pending = _(\"Pending\")\n\n\nclass SourceType(Enum):\n    ach_credit_transfer = _(\"ACH Credit Transfer\")\n    ach_debit = _(\"ACH Debit\")\n    acss_debit = _(\"ACSS Debit\")\n    alipay = _(\"Alipay\")\n    au_becs_debit = _(\"BECS Debit (AU)\")\n    bancontact = _(\"Bancontact\")\n    bitcoin = _(\"Bitcoin (Legacy)\")\n    card = _(\"Card\")\n    card_present = _(\"Card present\")\n    eps = _(\"EPS\")\n    giropay = _(\"Giropay\")\n    ideal = _(\"iDEAL\")\n    klarna = _(\"Klarna\")\n    multibanco = _(\"Multibanco\")\n    p24 = _(\"P24\")\n    paper_check = _(\"Paper check\")\n    sepa_credit_transfer = _(\"SEPA credit transfer\")\n    sepa_debit = _(\"SEPA Direct Debit\")\n    sofort = _(\"SOFORT\")\n    three_d_secure = _(\"3D Secure\")\n    wechat = _(\"WeChat\")\n\n\nclass LegacySourceType(Enum):\n    card = _(\"Card\")\n    bank_account = _(\"Bank account\")\n    bitcoin_receiver = _(\"Bitcoin receiver\")\n    alipay_account = _(\"Alipay account\")\n\n\nclass RefundFailureReason(Enum):\n    lost_or_stolen_card = _(\"Lost or stolen card\")\n    expired_or_canceled_card = _(\"Expired or canceled card\")\n    unknown = _(\"Unknown\")\n\n\nclass RefundReason(Enum):\n    duplicate = _(\"Duplicate charge\")\n    fraudulent = _(\"Fraudulent\")\n    requested_by_customer = _(\"Requested by customer\")\n    expired_uncaptured_charge = _(\"Expired uncaptured charge\")\n\n\nclass RefundStatus(Enum):\n    pending = _(\"Pending\")\n    succeeded = _(\"Succeeded\")\n    failed = _(\"Failed\")\n    canceled = _(\"Canceled\")\n\n\nclass SessionBillingAddressCollection(Enum):\n    auto = _(\"Auto\")\n    required = _(\"Required\")\n\n\nclass SessionMode(Enum):\n    payment = _(\"Payment\")\n    setup = _(\"Setup\")\n    subscription = _(\"Subscription\")\n\n\nclass SourceUsage(Enum):\n    reusable = _(\"Reusable\")\n    single_use = _(\"Single-use\")\n\n\nclass SourceCodeVerificationStatus(Enum):\n    pending = _(\"Pending\")\n    succeeded = _(\"Succeeded\")\n    failed = _(\"Failed\")\n\n\nclass SourceRedirectFailureReason(Enum):\n    user_abort = _(\"User-aborted\")\n    declined = _(\"Declined\")\n    processing_error = _(\"Processing error\")\n\n\nclass SourceRedirectStatus(Enum):\n    pending = _(\"Pending\")\n    succeeded = _(\"Succeeded\")\n    not_required = _(\"Not required\")\n    failed = _(\"Failed\")\n\n\nclass SubmitTypeStatus(Enum):\n    auto = _(\"Auto\")\n    book = _(\"Book\")\n    donate = _(\"donate\")\n    pay = _(\"pay\")\n\n\nclass SubscriptionScheduleEndBehavior(Enum):\n    release = _(\"Release\")\n    cancel = _(\"Cancel\")\n\n\nclass SubscriptionScheduleStatus(Enum):\n    not_started = _(\"Not started\")\n    active = _(\"Active\")\n    completed = _(\"Completed\")\n    released = _(\"Released\")\n    canceled = _(\"Canceled\")\n\n\nclass SubscriptionStatus(Enum):\n    incomplete = _(\"Incomplete\")\n    incomplete_expired = _(\"Incomplete Expired\")\n    trialing = _(\"Trialing\")\n    active = _(\"Active\")\n    past_due = _(\"Past due\")\n    canceled = _(\"Canceled\")\n    unpaid = _(\"Unpaid\")\n\n\nclass SubscriptionProrationBehavior(Enum):\n    create_prorations = _(\"Create prorations\")\n    always_invoice = _(\"Always invoice\")\n    none = _(\"None\")\n\n\nclass ShippingRateType(Enum):\n    fixed_amount = _(\"Fixed Amount\")\n\n\nclass ShippingRateTaxBehavior(Enum):\n    inclusive = _(\"Inclusive\")\n    exclusive = _(\"Exclusive\")\n    unspecified = _(\"Unspecified\")\n\n\nclass TaxIdType(Enum):\n    ae_trn = _(\"AE TRN\")\n    au_abn = _(\"AU ABN\")\n    br_cnp = _(\"BR CNP\")\n    br_cpf = _(\"BR CPF\")\n    ca_bn = _(\"CA BN\")\n    ca_qst = _(\"CA QST\")\n    ch_vat = _(\"CH VAT\")\n    cl_tin = _(\"CL TIN\")\n    es_cif = _(\"ES CIF\")\n    eu_vat = _(\"EU VAT\")\n    hk_br = _(\"HK BR\")\n    id_npw = _(\"ID NPW\")\n    in_gst = _(\"IN GST\")\n    jp_cn = _(\"JP CN\")\n    jp_rn = _(\"JP RN\")\n    kr_brn = _(\"KR BRN\")\n    li_uid = _(\"LI UID\")\n    mx_rfc = _(\"MX RFC\")\n    my_frp = _(\"MY FRP\")\n    my_itn = _(\"MY ITN\")\n    my_sst = _(\"MY SST\")\n    no_vat = _(\"NO VAT\")\n    nz_gst = _(\"NZ GST\")\n    ru_inn = _(\"RU INN\")\n    ru_kpp = _(\"RU KPP\")\n    sa_vat = _(\"SA VAT\")\n    sg_gst = _(\"SG GST\")\n    sg_uen = _(\"SG UEN\")\n    th_vat = _(\"TH VAT\")\n    tw_vat = _(\"TW VAT\")\n    us_ein = _(\"US EIN\")\n    za_vat = _(\"ZA VAT\")\n    unknown = _(\"Unknown\")\n\n\nclass UsageAction(Enum):\n    increment = _(\"increment\")\n    set = _(\"set\")\n\n\nclass WebhookEndpointStatus(Enum):\n    enabled = _(\"enabled\")\n    disabled = _(\"disabled\")\n\n\nclass DjstripePaymentMethodType(Enum):\n    \"\"\"\n    A djstripe-specific enum for the DjStripePaymentMethod model.\n    \"\"\"\n\n    alipay_account = _(\"Alipay account\")\n    card = _(\"Card\")\n    bank_account = _(\"Bank account\")\n    source = _(\"Source\")\n"
  },
  {
    "path": "djstripe/event_handlers.py",
    "content": "\"\"\"\nWebhook event handlers for the various models\n\nStripe docs for Events: https://stripe.com/docs/api/events\nStripe docs for Webhooks: https://stripe.com/docs/webhooks\n\nTODO: Implement webhook event handlers for all the models that need to\n      respond to webhook events.\n\nNOTE:\n    Event data is not guaranteed to be in the correct API version format.\n    See #116. When writing a webhook handler, make sure to first\n    re-retrieve the object you wish to process.\n\n\"\"\"\nimport logging\nfrom enum import Enum\n\nfrom django.core.exceptions import ObjectDoesNotExist\n\nfrom djstripe.settings import djstripe_settings\n\nfrom . import models, webhooks\nfrom .enums import PayoutType, SourceType\nfrom .utils import convert_tstamp\n\nlogger = logging.getLogger(__name__)\n\n\ndef update_customer_helper(metadata, customer_id, subscriber_key):\n    \"\"\"\n    A helper function that updates customer's subscriber and metadata fields\n    \"\"\"\n\n    # only update customer.subscriber if both the customer and subscriber already exist\n    subscriber_id = metadata.get(subscriber_key, \"\")\n    if subscriber_key not in (\"\", None) and subscriber_id and customer_id:\n        subscriber_model = djstripe_settings.get_subscriber_model()\n\n        try:\n            subscriber = subscriber_model.objects.get(id=subscriber_id)\n            customer = models.Customer.objects.get(id=customer_id)\n        except ObjectDoesNotExist:\n            pass\n        else:\n            customer.subscriber = subscriber\n            customer.metadata = metadata\n            customer.save()\n\n\n@webhooks.handler(\"customer\")\ndef customer_webhook_handler(event):\n    \"\"\"Handle updates to customer objects.\n\n    First determines the crud_type and then handles the event if a customer\n    exists locally.\n    As customers are tied to local users, djstripe will not create customers that\n    do not already exist locally.\n\n    And updates to the subscriber model and metadata fields of customer if present\n    in checkout.sessions metadata key.\n\n    Docs and an example customer webhook response:\n    https://stripe.com/docs/api#customer_object\n    \"\"\"\n    # will recieve all events of the type customer.X.Y so\n    # need to ensure the data object is related to Customer Object\n    target_object_type = event.data.get(\"object\", {}).get(\"object\", {})\n\n    if event.customer and target_object_type == \"customer\":\n        metadata = event.data.get(\"object\", {}).get(\"metadata\", {})\n        customer_id = event.data.get(\"object\", {}).get(\"id\", \"\")\n        subscriber_key = djstripe_settings.SUBSCRIBER_CUSTOMER_KEY\n\n        # only update customer.subscriber if both the customer and subscriber already exist\n        update_customer_helper(metadata, customer_id, subscriber_key)\n\n        _handle_crud_like_event(target_cls=models.Customer, event=event)\n\n\n@webhooks.handler(\"customer.discount\")\ndef customer_discount_webhook_handler(event):\n    \"\"\"Handle updates to customer discount objects.\n\n    Docs: https://stripe.com/docs/api#discounts\n\n    Because there is no concept of a \"Discount\" model in dj-stripe (due to the\n    lack of a stripe id on them), this is a little different to the other\n    handlers.\n    \"\"\"\n\n    crud_type = CrudType.determine(event=event)\n    discount_data = event.data.get(\"object\", {})\n    coupon_data = discount_data.get(\"coupon\", {})\n    customer = event.customer\n\n    if crud_type is CrudType.DELETED:\n        coupon = None\n        coupon_start = None\n        coupon_end = None\n    else:\n        coupon = _handle_crud_like_event(\n            target_cls=models.Coupon,\n            event=event,\n            data=coupon_data,\n            id=coupon_data.get(\"id\"),\n        )\n        coupon_start = discount_data.get(\"start\")\n        coupon_end = discount_data.get(\"end\")\n\n    customer.coupon = coupon\n    customer.coupon_start = convert_tstamp(coupon_start)\n    customer.coupon_end = convert_tstamp(coupon_end)\n    customer.save()\n\n\n@webhooks.handler(\"customer.source\")\ndef customer_source_webhook_handler(event):\n    \"\"\"Handle updates to customer payment-source objects.\n\n    Docs: https://stripe.com/docs/api/sources\n    \"\"\"\n    customer_data = event.data.get(\"object\", {})\n    source_type = customer_data.get(\"object\", {})\n\n    # TODO: handle other types of sources\n    #  (https://stripe.com/docs/api/sources)\n    if source_type == SourceType.card:\n        if event.verb.endswith(\"deleted\") and customer_data:\n            # On customer.source.deleted, we do not delete the object,\n            # we merely unlink it.\n            # customer = Customer.objects.get(id=customer_data[\"id\"])\n            # NOTE: for now, customer.sources still points to Card\n            # Also, https://github.com/dj-stripe/dj-stripe/issues/576\n            models.Card.objects.filter(id=customer_data.get(\"id\", \"\")).delete()\n            models.DjstripePaymentMethod.objects.filter(\n                id=customer_data.get(\"id\", \"\")\n            ).delete()\n        else:\n            _handle_crud_like_event(target_cls=models.Card, event=event)\n\n\n@webhooks.handler(\"customer.subscription\")\ndef customer_subscription_webhook_handler(event):\n    \"\"\"Handle updates to customer subscription objects.\n\n    Docs an example subscription webhook response:\n    https://stripe.com/docs/api#subscription_object\n    \"\"\"\n\n    # customer.subscription.deleted doesn't actually delete the subscription\n    # on the stripe side, it updates it to canceled status, so override\n    # crud_type to update to match.\n    crud_type = CrudType.determine(event=event)\n    if crud_type is CrudType.DELETED:\n        crud_type = CrudType.UPDATED\n    _handle_crud_like_event(\n        target_cls=models.Subscription, event=event, crud_type=crud_type\n    )\n\n\n@webhooks.handler(\"customer.tax_id\")\ndef customer_tax_id_webhook_handler(event):\n    \"\"\"\n    Handle updates to customer tax ID objects.\n    \"\"\"\n    _handle_crud_like_event(\n        target_cls=models.TaxId, event=event, crud_type=CrudType.determine(event=event)\n    )\n\n\n@webhooks.handler(\"payment_method\")\ndef payment_method_handler(event):\n    \"\"\"\n    Handle updates to payment_method objects\n    :param event:\n    :return:\n\n    Docs for:\n    - payment_method: https://stripe.com/docs/api/payment_methods\n    \"\"\"\n    # will recieve all events of the type payment_method.X.Y so\n    # need to ensure the data object is related to PaymentMethod Object\n    target_object_type = event.data.get(\"object\", {}).get(\"object\", {})\n\n    if target_object_type == \"payment_method\":\n        id_ = event.data.get(\"object\", {}).get(\"id\", None)\n\n        if (\n            event.parts == [\"payment_method\", \"detached\"]\n            and id_\n            and id_.startswith(\"card_\")\n        ):\n            # Special case to handle a quirk in stripe's wrapping of legacy \"card\" objects\n            # with payment_methods - card objects are deleted on detach, so treat this as\n            # a delete event\n            _handle_crud_like_event(\n                target_cls=models.PaymentMethod,\n                event=event,\n                crud_type=CrudType.DELETED,\n            )\n        else:\n            _handle_crud_like_event(target_cls=models.PaymentMethod, event=event)\n\n\n@webhooks.handler(\"account.external_account\")\ndef account_application_webhook_handler(event):\n    \"\"\"\n    Handles updates to Connected Accounts External Accounts\n    \"\"\"\n    source_type = event.data.get(\"object\", {}).get(\"object\")\n    if source_type == PayoutType.card:\n        _handle_crud_like_event(target_cls=models.Card, event=event)\n\n    if source_type == PayoutType.bank_account:\n        _handle_crud_like_event(target_cls=models.BankAccount, event=event)\n\n\n@webhooks.handler(\"account.updated\")\ndef account_updated_webhook_handler(event):\n    \"\"\"\n    Handles updates to Connected Accounts\n        - account: https://stripe.com/docs/api/accounts\n    \"\"\"\n    _handle_crud_like_event(\n        target_cls=models.Account,\n        event=event,\n        crud_type=CrudType.UPDATED,\n    )\n\n\n@webhooks.handler(\"charge\")\ndef charge_webhook_handler(event):\n    \"\"\"Handle updates to Charge objects\n    - charge: https://stripe.com/docs/api/charges\n    \"\"\"\n    # will recieve all events of the type charge.X.Y so\n    # need to ensure the data object is related to Charge Object\n    target_object_type = event.data.get(\"object\", {}).get(\"object\", {})\n\n    if target_object_type == \"charge\":\n        _handle_crud_like_event(target_cls=models.Charge, event=event)\n\n\n@webhooks.handler(\"charge.dispute\")\ndef dispute_webhook_handler(event):\n    \"\"\"Handle updates to Dispute objects\n    - dispute: https://stripe.com/docs/api/disputes\n    \"\"\"\n    # will recieve all events of the type charge.dispute.Y so\n    # need to ensure the data object is related to Dispute Object\n    target_object_type = event.data.get(\"object\", {}).get(\"object\", {})\n\n    if target_object_type == \"dispute\":\n        _handle_crud_like_event(target_cls=models.Dispute, event=event)\n\n\n@webhooks.handler(\n    \"checkout\",\n    \"coupon\",\n    \"file\",\n    \"invoice\",\n    \"invoiceitem\",\n    \"order\",\n    \"payment_intent\",\n    \"payout\",\n    \"plan\",\n    \"price\",\n    \"product\",\n    \"setup_intent\",\n    \"subscription_schedule\",\n    \"source\",\n    \"tax_rate\",\n    \"transfer\",\n)\ndef other_object_webhook_handler(event):\n    \"\"\"\n    Handle updates to checkout, coupon, file, invoice, invoiceitem, payment_intent,\n    plan, product, setup_intent, subscription_schedule, source, tax_rate\n    and transfer objects.\n\n    Docs for:\n    - checkout: https://stripe.com/docs/api/checkout/sessions\n    - coupon: https://stripe.com/docs/api/coupons\n    - file: https://stripe.com/docs/api/files\n    - invoice: https://stripe.com/docs/api/invoices\n    - invoiceitem: https://stripe.com/docs/api/invoiceitems\n    - order: https://stripe.com/docs/api/orders_v2\n    - payment_intent: https://stripe.com/docs/api/payment_intents\n    - payout: https://stripe.com/docs/api/payouts\n    - plan: https://stripe.com/docs/api/plans\n    - price: https://stripe.com/docs/api/prices\n    - product: https://stripe.com/docs/api/products\n    - setup_intent: https://stripe.com/docs/api/setup_intents\n    - subscription_schedule: https://stripe.com/docs/api/subscription_schedules\n    - source: https://stripe.com/docs/api/sources\n    - tax_rate: https://stripe.com/docs/api/tax_rates/\n    - transfer: https://stripe.com/docs/api/transfers\n    \"\"\"\n\n    target_cls = {\n        \"checkout\": models.Session,\n        \"coupon\": models.Coupon,\n        \"file\": models.File,\n        \"invoice\": models.Invoice,\n        \"invoiceitem\": models.InvoiceItem,\n        \"order\": models.Order,\n        \"payment_intent\": models.PaymentIntent,\n        \"payout\": models.Payout,\n        \"plan\": models.Plan,\n        \"price\": models.Price,\n        \"product\": models.Product,\n        \"transfer\": models.Transfer,\n        \"setup_intent\": models.SetupIntent,\n        \"subscription_schedule\": models.SubscriptionSchedule,\n        \"source\": models.Source,\n        \"tax_rate\": models.TaxRate,\n    }.get(event.category)\n\n    _handle_crud_like_event(target_cls=target_cls, event=event)\n\n\n#\n# Helpers\n#\n\n\nclass CrudType(Enum):\n    \"\"\"Helper object to determine CRUD-like event state.\"\"\"\n\n    UPDATED = \"updated\"\n    DELETED = \"deleted\"\n\n    @classmethod\n    def determine(cls, event, verb=None):\n        \"\"\"\n        Determine if the event verb is a crud_type (without the 'R') event.\n\n        :param event:\n        :type event: models.Event\n        :param verb: The event verb to examine.\n        :type verb: str\n        :returns: The CrudType state object.\n        :rtype: CrudType\n        \"\"\"\n        verb = verb or event.verb\n\n        for enum in CrudType:\n            if verb.endswith(enum.value):\n                return enum\n\n        # in case nothing matches\n        return\n\n\ndef _handle_crud_like_event(\n    target_cls, event: \"models.Event\", data=None, id: str = None, crud_type=None\n):\n    \"\"\"\n    Helper to process crud_type-like events for objects.\n\n    Non-deletes (creates, updates and \"anything else\" events) are treated as\n    update_or_create events - The object will be retrieved locally, then it is\n    synchronised with the Stripe API for parity.\n\n    Deletes only occur for delete events and cause the object to be deleted\n    from the local database, if it existed.  If it doesn't exist then it is\n    ignored (but the event processing still succeeds).\n\n    :param target_cls: The djstripe model being handled.\n    :type target_cls: Type[models.StripeModel]\n    :param event: The event object\n    :param data: The event object data (defaults to ``event.data``).\n    :param id: The object Stripe ID (defaults to ``object.id``).\n    :param crud_type: The CrudType object (determined by default).\n    :returns: The object (if any) and the event CrudType.\n    :rtype: Tuple[models.StripeModel, CrudType]\n    \"\"\"\n    data = data or event.data\n    id = id or data.get(\"object\", {}).get(\"id\", None)\n    stripe_account = getattr(event.djstripe_owner_account, \"id\", None)\n\n    if not id:\n        # We require an object when applying CRUD-like events, so if there's\n        # no ID the event is ignored/dropped. This happens in events such as\n        # invoice.upcoming, which refer to a future (non-existant) invoice.\n        logger.debug(\"Ignoring Stripe event %r without object ID\", event.id)\n        return\n\n    crud_type = crud_type or CrudType.determine(event=event, verb=event.verb)\n\n    if crud_type is CrudType.DELETED:\n        qs = target_cls.objects.filter(id=id)\n        if target_cls is models.Customer and qs.exists():\n            qs.get().purge()\n            obj = None\n        else:\n            obj = target_cls.objects.filter(id=id).delete()\n    else:\n        # Any other event type (creates, updates, etc.) - This can apply to\n        # verbs that aren't strictly CRUD but Stripe do intend an update.  Such\n        # as invoice.payment_failed.\n        kwargs = {\"id\": id}\n        if hasattr(target_cls, \"customer\"):\n            kwargs[\"customer\"] = event.customer\n\n        # For account.external_account.* events\n        if event.parts[:2] == [\"account\", \"external_account\"] and stripe_account:\n            kwargs[\"account\"] = models.Account._get_or_retrieve(id=stripe_account)\n\n        # Stripe doesn't allow retrieval of Discount Objects\n        if target_cls not in (models.Discount,):\n            data = target_cls(**kwargs).api_retrieve(\n                stripe_account=stripe_account, api_key=event.default_api_key\n            )\n        else:\n            data = data.get(\"object\")\n\n        # create or update the object from the retrieved Stripe Data\n        obj = target_cls.sync_from_stripe_data(data, api_key=event.default_api_key)\n\n    return obj\n"
  },
  {
    "path": "djstripe/exceptions.py",
    "content": "\"\"\"\ndj-stripe Exceptions.\n\"\"\"\n\n\nclass MultipleSubscriptionException(Exception):\n    \"\"\"Raised when a Customer has multiple Subscriptions and only one is expected.\"\"\"\n\n    pass\n\n\nclass StripeObjectManipulationException(Exception):\n    \"\"\"\n    Raised when an attempt to manipulate a non-standalone stripe object is made\n     not through its parent object.\n    \"\"\"\n\n    pass\n\n\nclass InvalidStripeAPIKey(ValueError):\n    \"\"\"\n    Raised when a clearly-invalid Stripe API key is used.\n    \"\"\"\n\n    pass\n\n\nclass ImpossibleAPIRequest(Exception):\n    \"\"\"\n    Raised when dj-stripe attempts to make an impossible API request\n    \"\"\"\n\n    pass\n"
  },
  {
    "path": "djstripe/fields.py",
    "content": "\"\"\"\ndj-stripe Custom Field Definitions\n\"\"\"\nimport decimal\n\nfrom django.conf import SettingsReference, settings\nfrom django.core.validators import MaxValueValidator, MinValueValidator\nfrom django.db import models\nfrom django.db.models import JSONField as BaseJSONField\n\nfrom .utils import convert_tstamp\n\n\nclass FieldDeconstructMixin:\n    IGNORED_ATTRS = [\n        \"verbose_name\",\n        \"help_text\",\n        \"choices\",\n        \"get_latest_by\",\n        \"ordering\",\n    ]\n\n    def deconstruct(self):\n        \"\"\"Remove field attributes that have nothing to\n        do with the database. Otherwise unencessary migrations are generated.\"\"\"\n        name, path, args, kwargs = super().deconstruct()\n        for attr in self.IGNORED_ATTRS:\n            kwargs.pop(attr, None)\n        return name, path, args, kwargs\n\n\nclass StripeForeignKey(models.ForeignKey):\n    setting_name = \"DJSTRIPE_FOREIGN_KEY_TO_FIELD\"\n\n    def __init__(self, *args, **kwargs):\n        # The default value will only come into play if the check for\n        # that setting has been disabled.\n        kwargs[\"to_field\"] = getattr(settings, self.setting_name, \"id\")\n        super().__init__(*args, **kwargs)\n\n    def deconstruct(self):\n        name, path, args, kwargs = super().deconstruct()\n        kwargs[\"to_field\"] = SettingsReference(\n            getattr(settings, self.setting_name, \"id\"), self.setting_name\n        )\n        return name, path, args, kwargs\n\n    def get_default(self):\n        # Override to bypass a weird bug in Django\n        # https://stackoverflow.com/a/14390402/227443\n        if isinstance(self.remote_field.model, str):\n            return self._get_default()\n        return super().get_default()\n\n\nclass PaymentMethodForeignKey(FieldDeconstructMixin, models.ForeignKey):\n    def __init__(self, **kwargs):\n        kwargs.setdefault(\"to\", \"DjstripePaymentMethod\")\n        super().__init__(**kwargs)\n\n\nclass InvoiceOrLineItemForeignKey(models.ForeignKey):\n    def __init__(self, **kwargs):\n        kwargs.setdefault(\"to\", \"InvoiceOrLineItem\")\n        super().__init__(**kwargs)\n\n\nclass StripePercentField(FieldDeconstructMixin, models.DecimalField):\n    \"\"\"A field used to define a percent according to djstripe logic.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"Assign default args to this field.\"\"\"\n        defaults = {\n            \"decimal_places\": 2,\n            \"max_digits\": 5,\n            \"validators\": [MinValueValidator(1), MaxValueValidator(100)],\n        }\n        defaults.update(kwargs)\n        super().__init__(*args, **defaults)\n\n\nclass StripeCurrencyCodeField(FieldDeconstructMixin, models.CharField):\n    \"\"\"\n    A field used to store a three-letter currency code (eg. usd, eur, ...)\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        defaults = {\"max_length\": 3, \"help_text\": \"Three-letter ISO currency code\"}\n        defaults.update(kwargs)\n        super().__init__(*args, **defaults)\n\n\nclass StripeQuantumCurrencyAmountField(FieldDeconstructMixin, models.BigIntegerField):\n    \"\"\"\n    A field used to store currency amounts in cents (etc) as per stripe.\n    By contacting stripe support, some accounts will have their limit raised to 11\n    digits, hence the use of BigIntegerField instead of IntegerField\n    \"\"\"\n\n    pass\n\n\nclass StripeDecimalCurrencyAmountField(FieldDeconstructMixin, models.DecimalField):\n    \"\"\"\n    A legacy field to store currency amounts in dollars (etc).\n\n    Stripe is always in cents. Historically djstripe stored everything in dollars.\n\n    Note: Don't use this for new fields, use StripeQuantumCurrencyAmountField instead.\n    We're planning on migrating existing fields in dj-stripe 3.0,\n    see https://github.com/dj-stripe/dj-stripe/issues/955\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        Assign default args to this field. By contacting stripe support, some accounts\n        will have their limit raised to 11 digits\n        \"\"\"\n        defaults = {\"decimal_places\": 2, \"max_digits\": 11}\n        defaults.update(kwargs)\n        super().__init__(*args, **defaults)\n\n    def stripe_to_db(self, data):\n        \"\"\"Convert the raw value to decimal representation.\"\"\"\n        val = data.get(self.name)\n\n        # If already a string, it's decimal in the API (eg. Prices).\n        if isinstance(val, str):\n            return decimal.Decimal(val)\n\n        # Note: 0 is a possible return value, which is 'falseish'\n        if val is not None:\n            return val / decimal.Decimal(\"100\")\n\n\nclass StripeEnumField(FieldDeconstructMixin, models.CharField):\n    def __init__(self, enum, *args, **kwargs):\n        self.enum = enum\n        choices = enum.choices\n        defaults = {\"choices\": choices, \"max_length\": max(len(k) for k, v in choices)}\n        defaults.update(kwargs)\n        super().__init__(*args, **defaults)\n\n    def deconstruct(self):\n        name, path, args, kwargs = super().deconstruct()\n        kwargs[\"enum\"] = self.enum\n        return name, path, args, kwargs\n\n\nclass StripeIdField(FieldDeconstructMixin, models.CharField):\n    \"\"\"A field with enough space to hold any stripe ID.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        Assign default args to this field.\n\n        As per: https://stripe.com/docs/upgrades\n        You can safely assume object IDs we generate will never exceed 255\n        characters, but you should be able to handle IDs of up to that\n        length.\n        \"\"\"\n        defaults = {\"max_length\": 255, \"blank\": False, \"null\": False}\n        defaults.update(kwargs)\n        super().__init__(*args, **defaults)\n\n\nclass StripeDateTimeField(FieldDeconstructMixin, models.DateTimeField):\n    \"\"\"A field used to define a DateTimeField value according to djstripe logic.\"\"\"\n\n    def stripe_to_db(self, data):\n        \"\"\"Convert the raw timestamp value to a DateTime representation.\"\"\"\n        val = data.get(self.name)\n\n        # Note: 0 is a possible return value, which is 'falseish'\n        if val is not None:\n            return convert_tstamp(val)\n\n\nclass JSONField(FieldDeconstructMixin, BaseJSONField):\n    \"\"\"A field used to define a JSONField value according to djstripe logic.\"\"\"\n\n    pass\n"
  },
  {
    "path": "djstripe/locale/fr/LC_MESSAGES/django.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: 1.3.0\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2019-01-28 12:02+1300\\n\"\n\"Last-Translator: Jerome Leclanche <jerome@leclan.ch>\\n\"\n\"Language-Team: French <jerome@leclan.ch>\\n\"\n\"Language: fr\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n > 1);\\n\"\n\n#: enums.py:57\nmsgid \"Account already exists\"\nmsgstr \"\"\n\n#: enums.py:58\nmsgid \"Account country invalid address\"\nmsgstr \"\"\n\n#: enums.py:59\nmsgid \"Account invalid\"\nmsgstr \"\"\n\n#: enums.py:60\nmsgid \"Account number invalid\"\nmsgstr \"\"\n\n#: enums.py:61\nmsgid \"Alipay upgrade required\"\nmsgstr \"\"\n\n#: enums.py:62\nmsgid \"Amount too large\"\nmsgstr \"\"\n\n#: enums.py:63\nmsgid \"Amount too small\"\nmsgstr \"\"\n\n#: enums.py:64\nmsgid \"Api key expired\"\nmsgstr \"\"\n\n#: enums.py:65\nmsgid \"Balance insufficient\"\nmsgstr \"\"\n\n#: enums.py:66\n#, fuzzy\n#| msgid \"Bank account\"\nmsgid \"Bank account exists\"\nmsgstr \"Compte bancaire\"\n\n#: enums.py:67\n#, fuzzy\n#| msgid \"Bank account\"\nmsgid \"Bank account unusable\"\nmsgstr \"Compte bancaire\"\n\n#: enums.py:68\n#, fuzzy\n#| msgid \"Bank account\"\nmsgid \"Bank account unverified\"\nmsgstr \"Compte bancaire\"\n\n#: enums.py:69\n#, fuzzy\n#| msgid \"Bitcoin receiver\"\nmsgid \"Bitcoin upgrade required\"\nmsgstr \"Récipient Bitcoin\"\n\n#: enums.py:70\nmsgid \"Card was declined\"\nmsgstr \"Carte rejetée\"\n\n#: enums.py:71\n#, fuzzy\n#| msgid \"Charge refunded\"\nmsgid \"Charge already captured\"\nmsgstr \"Charge remboursée\"\n\n#: enums.py:72\n#, fuzzy\n#| msgid \"Charge refunded\"\nmsgid \"Charge already refunded\"\nmsgstr \"Charge remboursée\"\n\n#: enums.py:73\n#, fuzzy\n#| msgid \"Charge refunded\"\nmsgid \"Charge disputed\"\nmsgstr \"Charge remboursée\"\n\n#: enums.py:74\nmsgid \"Charge exceeds source limit\"\nmsgstr \"\"\n\n#: enums.py:75\nmsgid \"Charge expired for capture\"\nmsgstr \"\"\n\n#: enums.py:76\n#, fuzzy\n#| msgid \"Card no longer supported.\"\nmsgid \"Country unsupported\"\nmsgstr \"La carte n'est plus supportée.\"\n\n#: enums.py:77\nmsgid \"Coupon expired\"\nmsgstr \"\"\n\n#: enums.py:78\nmsgid \"Customer max subscriptions\"\nmsgstr \"\"\n\n#: enums.py:79\nmsgid \"Email invalid\"\nmsgstr \"\"\n\n#: enums.py:80\nmsgid \"Expired card\"\nmsgstr \"Carte expirée\"\n\n#: enums.py:81\nmsgid \"Idempotency key in use\"\nmsgstr \"\"\n\n#: enums.py:82\n#, fuzzy\n#| msgid \"Incorrect account details\"\nmsgid \"Incorrect address\"\nmsgstr \"Détails de compte incorrects\"\n\n#: enums.py:83\nmsgid \"Incorrect security code\"\nmsgstr \"Code de sécurité incorrect\"\n\n#: enums.py:84\nmsgid \"Incorrect number\"\nmsgstr \"Numéro incorrect\"\n\n#: enums.py:85\nmsgid \"ZIP code failed validation\"\nmsgstr \"Validation du code postal échouée\"\n\n#: enums.py:86\nmsgid \"Instant payouts unsupported\"\nmsgstr \"\"\n\n#: enums.py:87\n#, fuzzy\n#| msgid \"Invalid security code\"\nmsgid \"Invalid card type\"\nmsgstr \"Code de sécurité invalide\"\n\n#: enums.py:88\n#, fuzzy\n#| msgid \"Invalid expiration month\"\nmsgid \"Invalid charge amount\"\nmsgstr \"Mois d'expiration invalide\"\n\n#: enums.py:89\nmsgid \"Invalid security code\"\nmsgstr \"Code de sécurité invalide\"\n\n#: enums.py:90\nmsgid \"Invalid expiration month\"\nmsgstr \"Mois d'expiration invalide\"\n\n#: enums.py:91\nmsgid \"Invalid expiration year\"\nmsgstr \"Année d'expiration invalide\"\n\n#: enums.py:92\nmsgid \"Invalid number\"\nmsgstr \"Numéro invalide\"\n\n#: enums.py:93\n#, fuzzy\n#| msgid \"Invalid security code\"\nmsgid \"Invalid source usage\"\nmsgstr \"Code de sécurité invalide\"\n\n#: enums.py:94\nmsgid \"Invoice no customer line items\"\nmsgstr \"\"\n\n#: enums.py:95\nmsgid \"Invoice no subscription line items\"\nmsgstr \"\"\n\n#: enums.py:96\nmsgid \"Invoice not editable\"\nmsgstr \"\"\n\n#: enums.py:97\nmsgid \"Invoice upcoming none\"\nmsgstr \"\"\n\n#: enums.py:98\nmsgid \"Livemode mismatch\"\nmsgstr \"\"\n\n#: enums.py:99\nmsgid \"No card being charged\"\nmsgstr \"Aucune carte chargée\"\n\n#: enums.py:100\nmsgid \"Not allowed on standard account\"\nmsgstr \"\"\n\n#: enums.py:101\n#, fuzzy\n#| msgid \"Verification failed\"\nmsgid \"Order creation failed\"\nmsgstr \"Vérification échouée\"\n\n#: enums.py:102\nmsgid \"Order required settings\"\nmsgstr \"\"\n\n#: enums.py:103\nmsgid \"Order status invalid\"\nmsgstr \"\"\n\n#: enums.py:104\nmsgid \"Order upstream timeout\"\nmsgstr \"\"\n\n#: enums.py:105\nmsgid \"Out of inventory\"\nmsgstr \"\"\n\n#: enums.py:106\nmsgid \"Parameter invalid empty\"\nmsgstr \"\"\n\n#: enums.py:107\nmsgid \"Parameter invalid integer\"\nmsgstr \"\"\n\n#: enums.py:108\nmsgid \"Parameter invalid string blank\"\nmsgstr \"\"\n\n#: enums.py:109\nmsgid \"Parameter invalid string empty\"\nmsgstr \"\"\n\n#: enums.py:110\nmsgid \"Parameter missing\"\nmsgstr \"\"\n\n#: enums.py:111\nmsgid \"Parameter unknown\"\nmsgstr \"\"\n\n#: enums.py:112\nmsgid \"Parameters exclusive\"\nmsgstr \"\"\n\n#: enums.py:113\nmsgid \"Payment intent authentication failure\"\nmsgstr \"\"\n\n#: enums.py:114\nmsgid \"Payment intent incompatible payment method\"\nmsgstr \"\"\n\n#: enums.py:115\nmsgid \"Payment intent invalid parameter\"\nmsgstr \"\"\n\n#: enums.py:116\nmsgid \"Payment intent payment attempt failed\"\nmsgstr \"\"\n\n#: enums.py:117\nmsgid \"Payment intent unexpected state\"\nmsgstr \"\"\n\n#: enums.py:118\n#, fuzzy\n#| msgid \"Payment refund\"\nmsgid \"Payment method unactivated\"\nmsgstr \"Remboursement de paiment\"\n\n#: enums.py:119\nmsgid \"Payment method unexpected state\"\nmsgstr \"\"\n\n#: enums.py:120\n#, fuzzy\n#| msgid \"Payout failure\"\nmsgid \"Payouts not allowed\"\nmsgstr \"Échec de virement\"\n\n#: enums.py:121\nmsgid \"Platform api key expired\"\nmsgstr \"\"\n\n#: enums.py:122\nmsgid \"Postal code invalid\"\nmsgstr \"\"\n\n#: enums.py:123 enums.py:433\nmsgid \"Processing error\"\nmsgstr \"Erreur de traitement\"\n\n#: enums.py:124\n#, fuzzy\n#| msgid \"Product unacceptable\"\nmsgid \"Product inactive\"\nmsgstr \"Produit inacceptable\"\n\n#: enums.py:125\nmsgid \"Rate limit\"\nmsgstr \"\"\n\n#: enums.py:126\nmsgid \"Resource already exists\"\nmsgstr \"\"\n\n#: enums.py:127\nmsgid \"Resource missing\"\nmsgstr \"\"\n\n#: enums.py:128\nmsgid \"Routing number invalid\"\nmsgstr \"\"\n\n#: enums.py:129\n#, fuzzy\n#| msgid \"Not required\"\nmsgid \"Secret key required\"\nmsgstr \"Non requis\"\n\n#: enums.py:130\nmsgid \"SEPA unsupported account\"\nmsgstr \"\"\n\n#: enums.py:131\n#, fuzzy\n#| msgid \"Verification failed\"\nmsgid \"Shipping calculation failed\"\nmsgstr \"Vérification échouée\"\n\n#: enums.py:132\nmsgid \"SKU inactive\"\nmsgstr \"\"\n\n#: enums.py:133\nmsgid \"State unsupported\"\nmsgstr \"\"\n\n#: enums.py:134\nmsgid \"Tax id invalid\"\nmsgstr \"\"\n\n#: enums.py:135\n#, fuzzy\n#| msgid \"Verification failed\"\nmsgid \"Taxes calculation failed\"\nmsgstr \"Vérification échouée\"\n\n#: enums.py:136\nmsgid \"Testmode charges only\"\nmsgstr \"\"\n\n#: enums.py:137\nmsgid \"TLS version unsupported\"\nmsgstr \"\"\n\n#: enums.py:138\nmsgid \"Token already used\"\nmsgstr \"\"\n\n#: enums.py:139\nmsgid \"Token in use\"\nmsgstr \"\"\n\n#: enums.py:140\n#, fuzzy\n#| msgid \"Transfer refund\"\nmsgid \"Transfers not allowed\"\nmsgstr \"Remboursement de transfert\"\n\n#: enums.py:141\n#, fuzzy\n#| msgid \"Verification failed\"\nmsgid \"Upstream order creation failed\"\nmsgstr \"Vérification échouée\"\n\n#: enums.py:142\nmsgid \"URL invalid\"\nmsgstr \"\"\n\n#: enums.py:145\nmsgid \"Invalid swipe data\"\nmsgstr \"Données swipe invalide\"\n\n#: enums.py:149 enums.py:302\nmsgid \"Standard\"\nmsgstr \"Standard\"\n\n#: enums.py:150\nmsgid \"Express\"\nmsgstr \"Express\"\n\n#: enums.py:151\nmsgid \"Custom\"\nmsgstr \"Custom\"\n\n#: enums.py:155\nmsgid \"Available\"\nmsgstr \"Disponible\"\n\n#: enums.py:156 enums.py:223 enums.py:308 enums.py:371 enums.py:413\n#: enums.py:425 enums.py:437\nmsgid \"Pending\"\nmsgstr \"En attente\"\n\n#: enums.py:160\nmsgid \"Adjustment\"\nmsgstr \"Ajustement\"\n\n#: enums.py:161\nmsgid \"Application fee\"\nmsgstr \"Frais d'application\"\n\n#: enums.py:162\nmsgid \"Application fee refund\"\nmsgstr \"Remboursement de frais d'application\"\n\n#: enums.py:163\nmsgid \"Charge\"\nmsgstr \"Charge\"\n\n#: enums.py:164\nmsgid \"Network cost\"\nmsgstr \"Coût de réseau\"\n\n#: enums.py:165\nmsgid \"Payment\"\nmsgstr \"Paiment\"\n\n#: enums.py:166\nmsgid \"Payment failure refund\"\nmsgstr \"Remboursement d'échec de paiment\"\n\n#: enums.py:167\nmsgid \"Payment refund\"\nmsgstr \"Remboursement de paiment\"\n\n#: enums.py:168\nmsgid \"Payout\"\nmsgstr \"Virement\"\n\n#: enums.py:169\nmsgid \"Payout cancellation\"\nmsgstr \"Annulation de virement\"\n\n#: enums.py:170\nmsgid \"Payout failure\"\nmsgstr \"Échec de virement\"\n\n#: enums.py:171\nmsgid \"Refund\"\nmsgstr \"Remboursement\"\n\n#: enums.py:172\nmsgid \"Stripe fee\"\nmsgstr \"Frais Stripe\"\n\n#: enums.py:173\nmsgid \"Transfer\"\nmsgstr \"Transfert\"\n\n#: enums.py:174\nmsgid \"Transfer refund\"\nmsgstr \"Remboursement de transfert\"\n\n#: enums.py:175\nmsgid \"Validation\"\nmsgstr \"Validation\"\n\n#: enums.py:179\nmsgid \"Individual\"\nmsgstr \"Particulier\"\n\n#: enums.py:180\nmsgid \"Company\"\nmsgstr \"Companie\"\n\n#: enums.py:184\nmsgid \"New\"\nmsgstr \"Nouveau\"\n\n#: enums.py:185\nmsgid \"Validated\"\nmsgstr \"Validé\"\n\n#: enums.py:186\nmsgid \"Verified\"\nmsgstr \"Vérifié\"\n\n#: enums.py:187\nmsgid \"Verification failed\"\nmsgstr \"Vérification échouée\"\n\n#: enums.py:188\nmsgid \"Errored\"\nmsgstr \"Erreur\"\n\n#: enums.py:192\nmsgid \"Pass\"\nmsgstr \"OK\"\n\n#: enums.py:193\nmsgid \"Fail\"\nmsgstr \"Échoué\"\n\n#: enums.py:194\nmsgid \"Unavailable\"\nmsgstr \"Indisponible\"\n\n#: enums.py:195\nmsgid \"Unchecked\"\nmsgstr \"Non vérifié\"\n\n#: enums.py:199\nmsgid \"American Express\"\nmsgstr \"American Express\"\n\n#: enums.py:200\nmsgid \"Diners Club\"\nmsgstr \"Diners Club\"\n\n#: enums.py:201\nmsgid \"Discover\"\nmsgstr \"Discover\"\n\n#: enums.py:202\nmsgid \"JCB\"\nmsgstr \"JCB\"\n\n#: enums.py:203\nmsgid \"MasterCard\"\nmsgstr \"masterCard\"\n\n#: enums.py:204\nmsgid \"UnionPay\"\nmsgstr \"\"\n\n#: enums.py:205\nmsgid \"Visa\"\nmsgstr \"Visa\"\n\n#: enums.py:206 enums.py:213 enums.py:403\nmsgid \"Unknown\"\nmsgstr \"Inconnu\"\n\n#: enums.py:210\nmsgid \"Credit\"\nmsgstr \"Crédit\"\n\n#: enums.py:211\nmsgid \"Debit\"\nmsgstr \"Débit\"\n\n#: enums.py:212\nmsgid \"Prepaid\"\nmsgstr \"Prépayée\"\n\n#: enums.py:217\nmsgid \"Apple Pay\"\nmsgstr \"Apple Pay\"\n\n#: enums.py:218\nmsgid \"Android Pay\"\nmsgstr \"Android Pay\"\n\n#: enums.py:222 enums.py:414 enums.py:426 enums.py:438\nmsgid \"Succeeded\"\nmsgstr \"Réussi\"\n\n#: enums.py:224 enums.py:311 enums.py:355 enums.py:370 enums.py:415\n#: enums.py:427 enums.py:440\nmsgid \"Failed\"\nmsgstr \"Échoué\"\n\n#: enums.py:228\nmsgid \"Once\"\nmsgstr \"Simple\"\n\n#: enums.py:229\nmsgid \"Multi-month\"\nmsgstr \"Multi-mois\"\n\n#: enums.py:230\nmsgid \"Forever\"\nmsgstr \"Illimité\"\n\n#: enums.py:234\nmsgid \"Duplicate\"\nmsgstr \"Doublon\"\n\n#: enums.py:235 enums.py:408\nmsgid \"Fraudulent\"\nmsgstr \"Frauduleux\"\n\n#: enums.py:236\nmsgid \"Subscription canceled\"\nmsgstr \"Abonnement annulé\"\n\n#: enums.py:237\nmsgid \"Product unacceptable\"\nmsgstr \"Produit inacceptable\"\n\n#: enums.py:238\nmsgid \"Product not received\"\nmsgstr \"Produit non reçu\"\n\n#: enums.py:239\nmsgid \"Unrecognized\"\nmsgstr \"Non reconnu\"\n\n#: enums.py:240\nmsgid \"Credit not processed\"\nmsgstr \"Crédit non traité\"\n\n#: enums.py:241\nmsgid \"General\"\nmsgstr \"Général\"\n\n#: enums.py:242\nmsgid \"Incorrect account details\"\nmsgstr \"Détails de compte incorrects\"\n\n#: enums.py:243\nmsgid \"Insufficient funds\"\nmsgstr \"Fonds insuffisants\"\n\n#: enums.py:244\nmsgid \"Bank cannot process\"\nmsgstr \"Banque ne peut traiter\"\n\n#: enums.py:245\nmsgid \"Debit not authorized\"\nmsgstr \"Débit interdit\"\n\n#: enums.py:246\nmsgid \"Customer-initiated\"\nmsgstr \"Initié par le client\"\n\n#: enums.py:250\nmsgid \"Warning needs response\"\nmsgstr \"Avertissement en attente de réponse\"\n\n#: enums.py:251\nmsgid \"Warning under review\"\nmsgstr \"Avertissement en revue\"\n\n#: enums.py:252\nmsgid \"Warning closed\"\nmsgstr \"Avertissement \"\n\n#: enums.py:253\nmsgid \"Needs response\"\nmsgstr \"En attente de réponse\"\n\n#: enums.py:254\nmsgid \"Under review\"\nmsgstr \"En revue\"\n\n#: enums.py:255\nmsgid \"Charge refunded\"\nmsgstr \"Charge remboursée\"\n\n#: enums.py:256\nmsgid \"Won\"\nmsgstr \"Gagnée\"\n\n#: enums.py:257\nmsgid \"Lost\"\nmsgstr \"Perdue\"\n\n#: enums.py:261\nmsgid \"Dispute evidence\"\nmsgstr \"Preuve de dispute\"\n\n#: enums.py:262\nmsgid \"Identity document\"\nmsgstr \"Document d'identité\"\n\n#: enums.py:263\nmsgid \"Tax document user upload\"\nmsgstr \"Document de taxes de l'utilisateur\"\n\n#: enums.py:267\nmsgid \"PDF\"\nmsgstr \"PDF\"\n\n#: enums.py:268\nmsgid \"JPG\"\nmsgstr \"JPG\"\n\n#: enums.py:269\nmsgid \"PNG\"\nmsgstr \"PNG\"\n\n#: enums.py:270\nmsgid \"CSV\"\nmsgstr \"CSV\"\n\n#: enums.py:271\nmsgid \"XLS\"\nmsgstr \"XLS\"\n\n#: enums.py:272\nmsgid \"XLSX\"\nmsgstr \"XLSX\"\n\n#: enums.py:273\nmsgid \"DOCX\"\nmsgstr \"DOCX\"\n\n#: enums.py:277\nmsgid \"Charge automatically\"\nmsgstr \"Débit automatique\"\n\n#: enums.py:278\nmsgid \"Send invoice\"\nmsgstr \"Envoi de facture\"\n\n#: enums.py:288\nmsgid \"Bank account has been closed.\"\nmsgstr \"Compte bancaire fermé\"\n\n#: enums.py:289\nmsgid \"Bank account has been frozen.\"\nmsgstr \"Compte bancaire gelé\"\n\n#: enums.py:290\nmsgid \"Bank account has restrictions on payouts allowed.\"\nmsgstr \"Le compte bancaire a des restrictions sur les virements autorisés\"\n\n#: enums.py:291\nmsgid \"Destination bank account has changed ownership.\"\nmsgstr \"Le compte bancaire destinataire a changé de propriétaire.\"\n\n#: enums.py:292\nmsgid \"Bank could not process payout.\"\nmsgstr \"Le compte bancaire n'a pas pu traiter le virement.\"\n\n#: enums.py:293\nmsgid \"Debit transactions not approved on the bank account.\"\nmsgstr \"\"\n\"Les transactions de débit ne sont pas autorisées sur le compte bancaire.\"\n\n#: enums.py:294\nmsgid \"Stripe account has insufficient funds.\"\nmsgstr \"Stripe n'a pas suffisamment de fonds.\"\n\n#: enums.py:295\nmsgid \"Invalid account number\"\nmsgstr \"Numéro de compte invalide\"\n\n#: enums.py:296\nmsgid \"Bank account does not support currency.\"\nmsgstr \"Le compte bancaire ne supporte pas la monnaie.\"\n\n#: enums.py:297\nmsgid \"Bank account could not be located.\"\nmsgstr \"Le compte bancaire n'a pas été trouvé.\"\n\n#: enums.py:298\nmsgid \"Card no longer supported.\"\nmsgstr \"La carte n'est plus supportée.\"\n\n#: enums.py:303\nmsgid \"Instant\"\nmsgstr \"Instant\"\n\n#: enums.py:307\nmsgid \"Paid\"\nmsgstr \"Payé\"\n\n#: enums.py:309\nmsgid \"In transit\"\nmsgstr \"En cours\"\n\n#: enums.py:310 enums.py:354 enums.py:367 enums.py:416 enums.py:447\nmsgid \"Canceled\"\nmsgstr \"Annulé\"\n\n#: enums.py:315 enums.py:395 enums.py:457\nmsgid \"Bank account\"\nmsgstr \"Compte bancaire\"\n\n#: enums.py:316 enums.py:380 enums.py:394 enums.py:456\nmsgid \"Card\"\nmsgstr \"Carte\"\n\n#: enums.py:320\nmsgid \"Last during period\"\nmsgstr \"Dernier cette période\"\n\n#: enums.py:321\nmsgid \"Last ever\"\nmsgstr \"Dernier\"\n\n#: enums.py:322\nmsgid \"Max\"\nmsgstr \"Max\"\n\n#: enums.py:323\nmsgid \"Sum\"\nmsgstr \"Somme\"\n\n#: enums.py:327\nmsgid \"Per unit\"\nmsgstr \"Par unité\"\n\n#: enums.py:328\nmsgid \"Tiered\"\nmsgstr \"En tiers\"\n\n#: enums.py:332\nmsgid \"Day\"\nmsgstr \"Jour\"\n\n#: enums.py:333\nmsgid \"Week\"\nmsgstr \"Semaine\"\n\n#: enums.py:334\nmsgid \"Month\"\nmsgstr \"Mois\"\n\n#: enums.py:335\nmsgid \"Year\"\nmsgstr \"Année\"\n\n#: enums.py:339\nmsgid \"Metered\"\nmsgstr \"Compté\"\n\n#: enums.py:340\nmsgid \"Licensed\"\nmsgstr \"Sous license\"\n\n#: enums.py:344\nmsgid \"Graduated\"\nmsgstr \"Gradué\"\n\n#: enums.py:345\nmsgid \"Volume-based\"\nmsgstr \"Par volume\"\n\n#: enums.py:349\nmsgid \"Good\"\nmsgstr \"Bien\"\n\n#: enums.py:350\nmsgid \"Service\"\nmsgstr \"Service\"\n\n#: enums.py:356\nmsgid \"Timed out\"\nmsgstr \"\"\n\n#: enums.py:360\nmsgid \"Redirect\"\nmsgstr \"Redirection\"\n\n#: enums.py:361\nmsgid \"Receiver\"\nmsgstr \"Récipient\"\n\n#: enums.py:362\nmsgid \"Code verification\"\nmsgstr \"Vérification de code\"\n\n#: enums.py:363\nmsgid \"None\"\nmsgstr \"Aucune\"\n\n#: enums.py:368\nmsgid \"Chargeable\"\nmsgstr \"Prête a débiter\"\n\n#: enums.py:369\nmsgid \"Consumed\"\nmsgstr \"Consommée\"\n\n#: enums.py:375\nmsgid \"ACH Credit Transfer\"\nmsgstr \"Transfert crédit ACH\"\n\n#: enums.py:376\nmsgid \"ACH Debit\"\nmsgstr \"Débit ACH\"\n\n#: enums.py:377\nmsgid \"Alipay\"\nmsgstr \"Alipay\"\n\n#: enums.py:378\nmsgid \"Bancontact\"\nmsgstr \"Bancontact\"\n\n#: enums.py:379\nmsgid \"Bitcoin\"\nmsgstr \"Bitcoin\"\n\n#: enums.py:381\nmsgid \"Card present\"\nmsgstr \"Carte présentée\"\n\n#: enums.py:382\nmsgid \"EPS\"\nmsgstr \"EPS\"\n\n#: enums.py:383\nmsgid \"Giropay\"\nmsgstr \"Giropay\"\n\n#: enums.py:384\nmsgid \"iDEAL\"\nmsgstr \"iDEAL\"\n\n#: enums.py:385\nmsgid \"P24\"\nmsgstr \"P24\"\n\n#: enums.py:386\nmsgid \"Paper check\"\nmsgstr \"Cheque papier\"\n\n#: enums.py:387\nmsgid \"SEPA Direct Debit\"\nmsgstr \"Débit direct SEPA\"\n\n#: enums.py:388\nmsgid \"SEPA credit transfer\"\nmsgstr \"Transfert crédit SEPA\"\n\n#: enums.py:389\nmsgid \"SOFORT\"\nmsgstr \"SOFORT\"\n\n#: enums.py:390\nmsgid \"3D Secure\"\nmsgstr \"3D Secure\"\n\n#: enums.py:396\nmsgid \"Bitcoin receiver\"\nmsgstr \"Récipient Bitcoin\"\n\n#: enums.py:397\nmsgid \"Alipay account\"\nmsgstr \"Compte Alipay\"\n\n#: enums.py:401\nmsgid \"Lost or stolen card\"\nmsgstr \"Carte perdue ou volée\"\n\n#: enums.py:402\nmsgid \"Expired or canceled card\"\nmsgstr \"Carte expirée ou annulée\"\n\n#: enums.py:407\nmsgid \"Duplicate charge\"\nmsgstr \"Charge double\"\n\n#: enums.py:409\nmsgid \"Requested by customer\"\nmsgstr \"Demandé par le client\"\n\n#: enums.py:420\nmsgid \"Reusable\"\nmsgstr \"Réutilisable\"\n\n#: enums.py:421\nmsgid \"Single-use\"\nmsgstr \"Usage unique\"\n\n#: enums.py:431\nmsgid \"User-aborted\"\nmsgstr \"Annulé par l'utilisateur\"\n\n#: enums.py:432\nmsgid \"Declined\"\nmsgstr \"Rejeté\"\n\n#: enums.py:439\nmsgid \"Not required\"\nmsgstr \"Non requis\"\n\n#: enums.py:444\nmsgid \"Trialing\"\nmsgstr \"En période d'essai\"\n\n#: enums.py:445\nmsgid \"Active\"\nmsgstr \"Active\"\n\n#: enums.py:446\nmsgid \"Past due\"\nmsgstr \"Arriéré dû\"\n\n#: enums.py:448\nmsgid \"Unpaid\"\nmsgstr \"Non payé\"\n\n#: enums.py:458\nmsgid \"Source\"\nmsgstr \"Source\"\n\n#: models/billing.py:863\nmsgid \"day\"\nmsgstr \"jour\"\n\n#: models/billing.py:864\nmsgid \"week\"\nmsgstr \"semaine\"\n\n#: models/billing.py:865\nmsgid \"month\"\nmsgstr \"mois\"\n\n#: models/billing.py:866\nmsgid \"year\"\nmsgstr \"an\"\n\n#: models/billing.py:868\n#, python-brace-format\nmsgid \"{amount}/{interval}\"\nmsgstr \"{amount}/{interval}\"\n\n#: models/billing.py:871\nmsgid \"days\"\nmsgstr \"jours\"\n\n#: models/billing.py:872\nmsgid \"weeks\"\nmsgstr \"semaines\"\n\n#: models/billing.py:873\nmsgid \"months\"\nmsgstr \"mois\"\n\n#: models/billing.py:874\nmsgid \"years\"\nmsgstr \"ans\"\n\n#: models/billing.py:876\n#, python-brace-format\nmsgid \"{amount} every {interval_count} {interval}\"\nmsgstr \"{amount} tous les {interval_count} {interval}\"\n\n#: templates/djstripe/admin/change_form.html:8\nmsgid \"View on Stripe Dashboard\"\nmsgstr \"Voir sur l'administration Stripe\"\n"
  },
  {
    "path": "djstripe/locale/ru/LC_MESSAGES/django.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: 1.3.0\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2019-01-28 12:02+1300\\n\"\n\"Last-Translator: Kirill Shilimanov <mawile@hackerdom.ru>\\n\"\n\"Language-Team: Russian <mawile@hackerdom.ru>\\n\"\n\"Language: ru\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Plural-Forms: nplurals=2; plural=(n > 1);\\n\"\n\n#: enums.py:57\nmsgid \"Account already exists\"\nmsgstr \"\"\n\n#: enums.py:58\nmsgid \"Account country invalid address\"\nmsgstr \"\"\n\n#: enums.py:59\nmsgid \"Account invalid\"\nmsgstr \"\"\n\n#: enums.py:60\nmsgid \"Account number invalid\"\nmsgstr \"\"\n\n#: enums.py:61\nmsgid \"Alipay upgrade required\"\nmsgstr \"\"\n\n#: enums.py:62\nmsgid \"Amount too large\"\nmsgstr \"\"\n\n#: enums.py:63\nmsgid \"Amount too small\"\nmsgstr \"\"\n\n#: enums.py:64\nmsgid \"Api key expired\"\nmsgstr \"\"\n\n#: enums.py:65\nmsgid \"Balance insufficient\"\nmsgstr \"\"\n\n#: enums.py:66\n#, fuzzy\n#| msgid \"Bank account\"\nmsgid \"Bank account exists\"\nmsgstr \"Банковский счет\"\n\n#: enums.py:67\n#, fuzzy\n#| msgid \"Bank account\"\nmsgid \"Bank account unusable\"\nmsgstr \"Банковский счет\"\n\n#: enums.py:68\n#, fuzzy\n#| msgid \"Bank account\"\nmsgid \"Bank account unverified\"\nmsgstr \"Банковский счет\"\n\n#: enums.py:69\n#, fuzzy\n#| msgid \"Bitcoin receiver\"\nmsgid \"Bitcoin upgrade required\"\nmsgstr \"Получатель Bitcoin\"\n\n#: enums.py:70\nmsgid \"Card was declined\"\nmsgstr \"Карта отклонена\"\n\n#: enums.py:71\n#, fuzzy\n#| msgid \"Charge refunded\"\nmsgid \"Charge already captured\"\nmsgstr \"Платеж возвращен\"\n\n#: enums.py:72\n#, fuzzy\n#| msgid \"Charge refunded\"\nmsgid \"Charge already refunded\"\nmsgstr \"Платеж возвращен\"\n\n#: enums.py:73\n#, fuzzy\n#| msgid \"Charge refunded\"\nmsgid \"Charge disputed\"\nmsgstr \"Платеж возвращен\"\n\n#: enums.py:74\nmsgid \"Charge exceeds source limit\"\nmsgstr \"\"\n\n#: enums.py:75\nmsgid \"Charge expired for capture\"\nmsgstr \"\"\n\n#: enums.py:76\n#, fuzzy\n#| msgid \"Card no longer supported.\"\nmsgid \"Country unsupported\"\nmsgstr \"Карта не поддерживается.\"\n\n#: enums.py:77\nmsgid \"Coupon expired\"\nmsgstr \"\"\n\n#: enums.py:78\nmsgid \"Customer max subscriptions\"\nmsgstr \"\"\n\n#: enums.py:79\nmsgid \"Email invalid\"\nmsgstr \"\"\n\n#: enums.py:80\nmsgid \"Expired card\"\nmsgstr \"Срок действия карты истек\"\n\n#: enums.py:81\nmsgid \"Idempotency key in use\"\nmsgstr \"\"\n\n#: enums.py:82\n#, fuzzy\n#| msgid \"Incorrect account details\"\nmsgid \"Incorrect address\"\nmsgstr \"Неверные данные счета\"\n\n#: enums.py:83\nmsgid \"Incorrect security code\"\nmsgstr \"Неверный код безопасности\"\n\n#: enums.py:84\nmsgid \"Incorrect number\"\nmsgstr \"Неверный номер\"\n\n#: enums.py:85\nmsgid \"ZIP code failed validation\"\nmsgstr \"Ошибка проверки почтового индекса\"\n\n#: enums.py:86\nmsgid \"Instant payouts unsupported\"\nmsgstr \"\"\n\n#: enums.py:87\n#, fuzzy\n#| msgid \"Invalid security code\"\nmsgid \"Invalid card type\"\nmsgstr \"Недействительный код безопасности\"\n\n#: enums.py:88\n#, fuzzy\n#| msgid \"Invalid expiration month\"\nmsgid \"Invalid charge amount\"\nmsgstr \"Неверный месяц истечения\"\n\n#: enums.py:89\nmsgid \"Invalid security code\"\nmsgstr \"Недействительный код безопасности\"\n\n#: enums.py:90\nmsgid \"Invalid expiration month\"\nmsgstr \"Неверный месяц истечения\"\n\n#: enums.py:91\nmsgid \"Invalid expiration year\"\nmsgstr \"Неверный год истечения\"\n\n#: enums.py:92\nmsgid \"Invalid number\"\nmsgstr \"Неверный номер\"\n\n#: enums.py:93\n#, fuzzy\n#| msgid \"Invalid security code\"\nmsgid \"Invalid source usage\"\nmsgstr \"Недействительный код безопасности\"\n\n#: enums.py:94\nmsgid \"Invoice no customer line items\"\nmsgstr \"\"\n\n#: enums.py:95\nmsgid \"Invoice no subscription line items\"\nmsgstr \"\"\n\n#: enums.py:96\nmsgid \"Invoice not editable\"\nmsgstr \"\"\n\n#: enums.py:97\nmsgid \"Invoice upcoming none\"\nmsgstr \"\"\n\n#: enums.py:98\nmsgid \"Livemode mismatch\"\nmsgstr \"\"\n\n#: enums.py:99\nmsgid \"No card being charged\"\nmsgstr \"Деньги с карты не сняты\"\n\n#: enums.py:100\nmsgid \"Not allowed on standard account\"\nmsgstr \"\"\n\n#: enums.py:101\n#, fuzzy\n#| msgid \"Verification failed\"\nmsgid \"Order creation failed\"\nmsgstr \"Проверка не удалась\"\n\n#: enums.py:102\nmsgid \"Order required settings\"\nmsgstr \"\"\n\n#: enums.py:103\nmsgid \"Order status invalid\"\nmsgstr \"\"\n\n#: enums.py:104\nmsgid \"Order upstream timeout\"\nmsgstr \"\"\n\n#: enums.py:105\nmsgid \"Out of inventory\"\nmsgstr \"\"\n\n#: enums.py:106\nmsgid \"Parameter invalid empty\"\nmsgstr \"\"\n\n#: enums.py:107\nmsgid \"Parameter invalid integer\"\nmsgstr \"\"\n\n#: enums.py:108\nmsgid \"Parameter invalid string blank\"\nmsgstr \"\"\n\n#: enums.py:109\nmsgid \"Parameter invalid string empty\"\nmsgstr \"\"\n\n#: enums.py:110\nmsgid \"Parameter missing\"\nmsgstr \"\"\n\n#: enums.py:111\nmsgid \"Parameter unknown\"\nmsgstr \"\"\n\n#: enums.py:112\nmsgid \"Parameters exclusive\"\nmsgstr \"\"\n\n#: enums.py:113\nmsgid \"Payment intent authentication failure\"\nmsgstr \"\"\n\n#: enums.py:114\nmsgid \"Payment intent incompatible payment method\"\nmsgstr \"\"\n\n#: enums.py:115\nmsgid \"Payment intent invalid parameter\"\nmsgstr \"\"\n\n#: enums.py:116\nmsgid \"Payment intent payment attempt failed\"\nmsgstr \"\"\n\n#: enums.py:117\nmsgid \"Payment intent unexpected state\"\nmsgstr \"\"\n\n#: enums.py:118\n#, fuzzy\n#| msgid \"Payment refund\"\nmsgid \"Payment method unactivated\"\nmsgstr \"Возврат платежа\"\n\n#: enums.py:119\nmsgid \"Payment method unexpected state\"\nmsgstr \"\"\n\n#: enums.py:120\n#, fuzzy\n#| msgid \"Payout failure\"\nmsgid \"Payouts not allowed\"\nmsgstr \"Сбой выплаты\"\n\n#: enums.py:121\nmsgid \"Platform api key expired\"\nmsgstr \"\"\n\n#: enums.py:122\nmsgid \"Postal code invalid\"\nmsgstr \"\"\n\n#: enums.py:123 enums.py:433\nmsgid \"Processing error\"\nmsgstr \"Ошибка обработки\"\n\n#: enums.py:124\n#, fuzzy\n#| msgid \"Product unacceptable\"\nmsgid \"Product inactive\"\nmsgstr \"Продукт неприемлем\"\n\n#: enums.py:125\nmsgid \"Rate limit\"\nmsgstr \"\"\n\n#: enums.py:126\nmsgid \"Resource already exists\"\nmsgstr \"\"\n\n#: enums.py:127\nmsgid \"Resource missing\"\nmsgstr \"\"\n\n#: enums.py:128\nmsgid \"Routing number invalid\"\nmsgstr \"\"\n\n#: enums.py:129\n#, fuzzy\n#| msgid \"Not required\"\nmsgid \"Secret key required\"\nmsgstr \"Не требуется\"\n\n#: enums.py:130\nmsgid \"SEPA unsupported account\"\nmsgstr \"\"\n\n#: enums.py:131\n#, fuzzy\n#| msgid \"Verification failed\"\nmsgid \"Shipping calculation failed\"\nmsgstr \"Проверка не удалась\"\n\n#: enums.py:132\nmsgid \"SKU inactive\"\nmsgstr \"\"\n\n#: enums.py:133\nmsgid \"State unsupported\"\nmsgstr \"\"\n\n#: enums.py:134\nmsgid \"Tax id invalid\"\nmsgstr \"\"\n\n#: enums.py:135\n#, fuzzy\n#| msgid \"Verification failed\"\nmsgid \"Taxes calculation failed\"\nmsgstr \"Проверка не удалась\"\n\n#: enums.py:136\nmsgid \"Testmode charges only\"\nmsgstr \"\"\n\n#: enums.py:137\nmsgid \"TLS version unsupported\"\nmsgstr \"\"\n\n#: enums.py:138\nmsgid \"Token already used\"\nmsgstr \"\"\n\n#: enums.py:139\nmsgid \"Token in use\"\nmsgstr \"\"\n\n#: enums.py:140\n#, fuzzy\n#| msgid \"Transfer refund\"\nmsgid \"Transfers not allowed\"\nmsgstr \"Возврат перевода\"\n\n#: enums.py:141\n#, fuzzy\n#| msgid \"Verification failed\"\nmsgid \"Upstream order creation failed\"\nmsgstr \"Проверка не удалась\"\n\n#: enums.py:142\nmsgid \"URL invalid\"\nmsgstr \"\"\n\n#: enums.py:145\nmsgid \"Invalid swipe data\"\nmsgstr \"Ошибка чтения магнитной ленты\"\n\n#: enums.py:149 enums.py:302\nmsgid \"Standard\"\nmsgstr \"Standard\"\n\n#: enums.py:150\nmsgid \"Express\"\nmsgstr \"Express\"\n\n#: enums.py:151\nmsgid \"Custom\"\nmsgstr \"На заказ\"\n\n#: enums.py:155\nmsgid \"Available\"\nmsgstr \"Доступно\"\n\n#: enums.py:156 enums.py:223 enums.py:308 enums.py:371 enums.py:413\n#: enums.py:425 enums.py:437\nmsgid \"Pending\"\nmsgstr \"в ожидании\"\n\n#: enums.py:160\nmsgid \"Adjustment\"\nmsgstr \"Корректировка\"\n\n#: enums.py:161\nmsgid \"Application fee\"\nmsgstr \"Абонентская плата\"\n\n#: enums.py:162\nmsgid \"Application fee refund\"\nmsgstr \"Возврат абонентской платы\"\n\n#: enums.py:163\nmsgid \"Charge\"\nmsgstr \"Расход\"\n\n#: enums.py:164\nmsgid \"Network cost\"\nmsgstr \"Сетевая стоимость\"\n\n#: enums.py:165\nmsgid \"Payment\"\nmsgstr \"Платеж\"\n\n#: enums.py:166\nmsgid \"Payment failure refund\"\nmsgstr \"Возврат неудачного платежа\"\n\n#: enums.py:167\nmsgid \"Payment refund\"\nmsgstr \"Возврат платежа\"\n\n#: enums.py:168\nmsgid \"Payout\"\nmsgstr \"Выплата\"\n\n#: enums.py:169\nmsgid \"Payout cancellation\"\nmsgstr \"Отмена выплаты\"\n\n#: enums.py:170\nmsgid \"Payout failure\"\nmsgstr \"Сбой выплаты\"\n\n#: enums.py:171\nmsgid \"Refund\"\nmsgstr \"Возврат\"\n\n#: enums.py:172\nmsgid \"Stripe fee\"\nmsgstr \"Взнос в Stripe\"\n\n#: enums.py:173\nmsgid \"Transfer\"\nmsgstr \"Перевод\"\n\n#: enums.py:174\nmsgid \"Transfer refund\"\nmsgstr \"Возврат перевода\"\n\n#: enums.py:175\nmsgid \"Validation\"\nmsgstr \"Проверка\"\n\n#: enums.py:179\nmsgid \"Individual\"\nmsgstr \"Частное лицо\"\n\n#: enums.py:180\nmsgid \"Company\"\nmsgstr \"Компания\"\n\n#: enums.py:184\nmsgid \"New\"\nmsgstr \"Новый\"\n\n#: enums.py:185\nmsgid \"Validated\"\nmsgstr \"Подтвержденный\"\n\n#: enums.py:186\nmsgid \"Verified\"\nmsgstr \"Проверенный\"\n\n#: enums.py:187\nmsgid \"Verification failed\"\nmsgstr \"Проверка не удалась\"\n\n#: enums.py:188\nmsgid \"Errored\"\nmsgstr \"Ошибочный\"\n\n#: enums.py:192\nmsgid \"Pass\"\nmsgstr \"Успех\"\n\n#: enums.py:193\nmsgid \"Fail\"\nmsgstr \"Отказ\"\n\n#: enums.py:194\nmsgid \"Unavailable\"\nmsgstr \"Недоступен\"\n\n#: enums.py:195\nmsgid \"Unchecked\"\nmsgstr \"Не проверен\"\n\n#: enums.py:199\nmsgid \"American Express\"\nmsgstr \"American Express\"\n\n#: enums.py:200\nmsgid \"Diners Club\"\nmsgstr \"Diners Club\"\n\n#: enums.py:201\nmsgid \"Discover\"\nmsgstr \"Discover\"\n\n#: enums.py:202\nmsgid \"JCB\"\nmsgstr \"JCB\"\n\n#: enums.py:203\nmsgid \"MasterCard\"\nmsgstr \"MasterCard\"\n\n#: enums.py:204\nmsgid \"UnionPay\"\nmsgstr \"\"\n\n#: enums.py:205\nmsgid \"Visa\"\nmsgstr \"Visa\"\n\n#: enums.py:206 enums.py:213 enums.py:403\nmsgid \"Unknown\"\nmsgstr \"Неизвестная\"\n\n#: enums.py:210\nmsgid \"Credit\"\nmsgstr \"Кредитная\"\n\n#: enums.py:211\nmsgid \"Debit\"\nmsgstr \"Дебитовая\"\n\n#: enums.py:212\nmsgid \"Prepaid\"\nmsgstr \"Предоплаченная\"\n\n#: enums.py:217\nmsgid \"Apple Pay\"\nmsgstr \"Apple Pay\"\n\n#: enums.py:218\nmsgid \"Android Pay\"\nmsgstr \"Android Pay\"\n\n#: enums.py:222 enums.py:414 enums.py:426 enums.py:438\nmsgid \"Succeeded\"\nmsgstr \"Успешно\"\n\n#: enums.py:224 enums.py:311 enums.py:355 enums.py:370 enums.py:415\n#: enums.py:427 enums.py:440\nmsgid \"Failed\"\nmsgstr \"Не удалось\"\n\n#: enums.py:228\nmsgid \"Once\"\nmsgstr \"Единоразовый\"\n\n#: enums.py:229\nmsgid \"Multi-month\"\nmsgstr \"Многомесячный\"\n\n#: enums.py:230\nmsgid \"Forever\"\nmsgstr \"Постоянный\"\n\n#: enums.py:234\nmsgid \"Duplicate\"\nmsgstr \"Повторный\"\n\n#: enums.py:235 enums.py:408\nmsgid \"Fraudulent\"\nmsgstr \"Мошеннический\"\n\n#: enums.py:236\nmsgid \"Subscription canceled\"\nmsgstr \"Подписка отменена\"\n\n#: enums.py:237\nmsgid \"Product unacceptable\"\nmsgstr \"Продукт неприемлем\"\n\n#: enums.py:238\nmsgid \"Product not received\"\nmsgstr \"Продукт не получен\"\n\n#: enums.py:239\nmsgid \"Unrecognized\"\nmsgstr \"Не распознано\"\n\n#: enums.py:240\nmsgid \"Credit not processed\"\nmsgstr \"Кредит не обработан\"\n\n#: enums.py:241\nmsgid \"General\"\nmsgstr \"Основной\"\n\n#: enums.py:242\nmsgid \"Incorrect account details\"\nmsgstr \"Неверные данные счета\"\n\n#: enums.py:243\nmsgid \"Insufficient funds\"\nmsgstr \"Недостаточно средств\"\n\n#: enums.py:244\nmsgid \"Bank cannot process\"\nmsgstr \"Банк не может обработать\"\n\n#: enums.py:245\nmsgid \"Debit not authorized\"\nmsgstr \"Дебит не разрешен\"\n\n#: enums.py:246\nmsgid \"Customer-initiated\"\nmsgstr \"По инициативе заказчика\"\n\n#: enums.py:250\nmsgid \"Warning needs response\"\nmsgstr \"Предупреждение требует ответа\"\n\n#: enums.py:251\nmsgid \"Warning under review\"\nmsgstr \"Предупреждение в рассмотрении\"\n\n#: enums.py:252\nmsgid \"Warning closed\"\nmsgstr \"Предупреждение закрыто\"\n\n#: enums.py:253\nmsgid \"Needs response\"\nmsgstr \"Требуется ответ\"\n\n#: enums.py:254\nmsgid \"Under review\"\nmsgstr \"В рассмотрении\"\n\n#: enums.py:255\nmsgid \"Charge refunded\"\nmsgstr \"Платеж возвращен\"\n\n#: enums.py:256\nmsgid \"Won\"\nmsgstr \"Выиграл\"\n\n#: enums.py:257\nmsgid \"Lost\"\nmsgstr \"Проиграл\"\n\n#: enums.py:261\nmsgid \"Dispute evidence\"\nmsgstr \"Доказательства по спору\"\n\n#: enums.py:262\nmsgid \"Identity document\"\nmsgstr \"Удостоверение личности\"\n\n#: enums.py:263\nmsgid \"Tax document user upload\"\nmsgstr \"\"\n\n#: enums.py:267\nmsgid \"PDF\"\nmsgstr \"PDF\"\n\n#: enums.py:268\nmsgid \"JPG\"\nmsgstr \"JPG\"\n\n#: enums.py:269\nmsgid \"PNG\"\nmsgstr \"PNG\"\n\n#: enums.py:270\nmsgid \"CSV\"\nmsgstr \"CSV\"\n\n#: enums.py:271\nmsgid \"XLS\"\nmsgstr \"XLS\"\n\n#: enums.py:272\nmsgid \"XLSX\"\nmsgstr \"XLSX\"\n\n#: enums.py:273\nmsgid \"DOCX\"\nmsgstr \"DOCX\"\n\n#: enums.py:277\nmsgid \"Charge automatically\"\nmsgstr \"Снимать автоматически\"\n\n#: enums.py:278\nmsgid \"Send invoice\"\nmsgstr \"Отправить счет-фактуру\"\n\n#: enums.py:288\nmsgid \"Bank account has been closed.\"\nmsgstr \"Банковский счет закрыт.\"\n\n#: enums.py:289\nmsgid \"Bank account has been frozen.\"\nmsgstr \"Банковский счет заморожен.\"\n\n#: enums.py:290\nmsgid \"Bank account has restrictions on payouts allowed.\"\nmsgstr \"Банковский счет имеет ограничения по платежам.\"\n\n#: enums.py:291\nmsgid \"Destination bank account has changed ownership.\"\nmsgstr \"Банковский счет назначения сменил владельца.\"\n\n#: enums.py:292\nmsgid \"Bank could not process payout.\"\nmsgstr \"Банк не может обработать платеж.\"\n\n#: enums.py:293\nmsgid \"Debit transactions not approved on the bank account.\"\nmsgstr \"Дебетовые транзакции не утверждены на банковском счете.\"\n\n#: enums.py:294\nmsgid \"Stripe account has insufficient funds.\"\nmsgstr \"На счете Stripe недостаточно средств.\"\n\n#: enums.py:295\nmsgid \"Invalid account number\"\nmsgstr \"Неверный номер счета\"\n\n#: enums.py:296\nmsgid \"Bank account does not support currency.\"\nmsgstr \"Валюта не поддерживается банковским счетом.\"\n\n#: enums.py:297\nmsgid \"Bank account could not be located.\"\nmsgstr \"Банковский счет не найден.\"\n\n#: enums.py:298\nmsgid \"Card no longer supported.\"\nmsgstr \"Карта не поддерживается.\"\n\n#: enums.py:303\nmsgid \"Instant\"\nmsgstr \"Мгновенный\"\n\n#: enums.py:307\nmsgid \"Paid\"\nmsgstr \"Оплачено\"\n\n#: enums.py:309\nmsgid \"In transit\"\nmsgstr \"В пути\"\n\n#: enums.py:310 enums.py:354 enums.py:367 enums.py:416 enums.py:447\nmsgid \"Canceled\"\nmsgstr \"Отменен\"\n\n#: enums.py:315 enums.py:395 enums.py:457\nmsgid \"Bank account\"\nmsgstr \"Банковский счет\"\n\n#: enums.py:316 enums.py:380 enums.py:394 enums.py:456\nmsgid \"Card\"\nmsgstr \"Карта\"\n\n#: enums.py:320\nmsgid \"Last during period\"\nmsgstr \"Последний за период\"\n\n#: enums.py:321\nmsgid \"Last ever\"\nmsgstr \"Последний\"\n\n#: enums.py:322\nmsgid \"Max\"\nmsgstr \"Максимум\"\n\n#: enums.py:323\nmsgid \"Sum\"\nmsgstr \"Сумма\"\n\n#: enums.py:327\nmsgid \"Per unit\"\nmsgstr \"За единицу\"\n\n#: enums.py:328\nmsgid \"Tiered\"\nmsgstr \"Многоуровневый\"\n\n#: enums.py:332\nmsgid \"Day\"\nmsgstr \"День\"\n\n#: enums.py:333\nmsgid \"Week\"\nmsgstr \"Неделя\"\n\n#: enums.py:334\nmsgid \"Month\"\nmsgstr \"Месяц\"\n\n#: enums.py:335\nmsgid \"Year\"\nmsgstr \"Год\"\n\n#: enums.py:339\nmsgid \"Metered\"\nmsgstr \"Измеряемый\"\n\n#: enums.py:340\nmsgid \"Licensed\"\nmsgstr \"Лицензированный\"\n\n#: enums.py:344\nmsgid \"Graduated\"\nmsgstr \"Гарантированный\"\n\n#: enums.py:345\nmsgid \"Volume-based\"\nmsgstr \"По объему\"\n\n#: enums.py:349\nmsgid \"Good\"\nmsgstr \"Хороший\"\n\n#: enums.py:350\nmsgid \"Service\"\nmsgstr \"Сервис\"\n\n#: enums.py:356\nmsgid \"Timed out\"\nmsgstr \"\"\n\n#: enums.py:360\nmsgid \"Redirect\"\nmsgstr \"Перенаправить\"\n\n#: enums.py:361\nmsgid \"Receiver\"\nmsgstr \"Получатель\"\n\n#: enums.py:362\nmsgid \"Code verification\"\nmsgstr \"Проверка кода\"\n\n#: enums.py:363\nmsgid \"None\"\nmsgstr \"Отсутствует\"\n\n#: enums.py:368\nmsgid \"Chargeable\"\nmsgstr \"Подлежащий оплате\"\n\n#: enums.py:369\nmsgid \"Consumed\"\nmsgstr \"Потребленный\"\n\n#: enums.py:375\nmsgid \"ACH Credit Transfer\"\nmsgstr \"Кредитный перевод ACH\"\n\n#: enums.py:376\nmsgid \"ACH Debit\"\nmsgstr \"Дебит ACH\"\n\n#: enums.py:377\nmsgid \"Alipay\"\nmsgstr \"Alipay\"\n\n#: enums.py:378\nmsgid \"Bancontact\"\nmsgstr \"Bancontact\"\n\n#: enums.py:379\nmsgid \"Bitcoin\"\nmsgstr \"Bitcoin\"\n\n#: enums.py:381\nmsgid \"Card present\"\nmsgstr \"Подарочная карта\"\n\n#: enums.py:382\nmsgid \"EPS\"\nmsgstr \"EPS\"\n\n#: enums.py:383\nmsgid \"Giropay\"\nmsgstr \"Giropay\"\n\n#: enums.py:384\nmsgid \"iDEAL\"\nmsgstr \"iDEAL\"\n\n#: enums.py:385\nmsgid \"P24\"\nmsgstr \"P24\"\n\n#: enums.py:386\nmsgid \"Paper check\"\nmsgstr \"Бумажный чек\"\n\n#: enums.py:387\nmsgid \"SEPA Direct Debit\"\nmsgstr \"Прямой дебет SEPA\"\n\n#: enums.py:388\nmsgid \"SEPA credit transfer\"\nmsgstr \"Кредитный перевод SEPA\"\n\n#: enums.py:389\nmsgid \"SOFORT\"\nmsgstr \"SOFORT\"\n\n#: enums.py:390\nmsgid \"3D Secure\"\nmsgstr \"3D Secure\"\n\n#: enums.py:396\nmsgid \"Bitcoin receiver\"\nmsgstr \"Получатель Bitcoin\"\n\n#: enums.py:397\nmsgid \"Alipay account\"\nmsgstr \"Счет Alipay\"\n\n#: enums.py:401\nmsgid \"Lost or stolen card\"\nmsgstr \"Утерянная/украденная карта\"\n\n#: enums.py:402\nmsgid \"Expired or canceled card\"\nmsgstr \"Истекшая/отозванная карта\"\n\n#: enums.py:407\nmsgid \"Duplicate charge\"\nmsgstr \"Дублированный платеж\"\n\n#: enums.py:409\nmsgid \"Requested by customer\"\nmsgstr \"По запросу покупателя\"\n\n#: enums.py:420\nmsgid \"Reusable\"\nmsgstr \"Многоразовый\"\n\n#: enums.py:421\nmsgid \"Single-use\"\nmsgstr \"Одноразовый\"\n\n#: enums.py:431\nmsgid \"User-aborted\"\nmsgstr \"Отменен пользователем\"\n\n#: enums.py:432\nmsgid \"Declined\"\nmsgstr \"Отказано\"\n\n#: enums.py:439\nmsgid \"Not required\"\nmsgstr \"Не требуется\"\n\n#: enums.py:444\nmsgid \"Trialing\"\nmsgstr \"Пробный период\"\n\n#: enums.py:445\nmsgid \"Active\"\nmsgstr \"Активен\"\n\n#: enums.py:446\nmsgid \"Past due\"\nmsgstr \"Просроченный\"\n\n#: enums.py:448\nmsgid \"Unpaid\"\nmsgstr \"Не оплачен\"\n\n#: enums.py:458\nmsgid \"Source\"\nmsgstr \"Источник\"\n\n#: models/billing.py:863\nmsgid \"day\"\nmsgstr \"день\"\n\n#: models/billing.py:864\nmsgid \"week\"\nmsgstr \"неделя\"\n\n#: models/billing.py:865\nmsgid \"month\"\nmsgstr \"месяц\"\n\n#: models/billing.py:866\nmsgid \"year\"\nmsgstr \"год\"\n\n#: models/billing.py:868\n#, python-brace-format\nmsgid \"{amount}/{interval}\"\nmsgstr \"{amount}/{interval}\"\n\n#: models/billing.py:871\nmsgid \"days\"\nmsgstr \"дней\"\n\n#: models/billing.py:872\nmsgid \"weeks\"\nmsgstr \"недель\"\n\n#: models/billing.py:873\nmsgid \"months\"\nmsgstr \"месяцев\"\n\n#: models/billing.py:874\nmsgid \"years\"\nmsgstr \"лет\"\n\n#: models/billing.py:876\n#, python-brace-format\nmsgid \"{amount} every {interval_count} {interval}\"\nmsgstr \"{amount} за {interval_count} {interval}\"\n\n#: templates/djstripe/admin/change_form.html:8\nmsgid \"View on Stripe Dashboard\"\nmsgstr \"Посмотреть в панели управления Stripe\"\n\n#~ msgid \"Загрузка налогового документа пользователя\"\n#~ msgstr \"Document de taxes de l'utilisateur\"\n"
  },
  {
    "path": "djstripe/management/__init__.py",
    "content": ""
  },
  {
    "path": "djstripe/management/commands/__init__.py",
    "content": ""
  },
  {
    "path": "djstripe/management/commands/djstripe_clear_expired_idempotency_keys.py",
    "content": "from django.core.management.base import BaseCommand\n\nfrom ...utils import clear_expired_idempotency_keys\n\n\nclass Command(BaseCommand):\n    help = \"Deleted expired Stripe idempotency keys.\"\n\n    def handle(self, *args, **options):\n        clear_expired_idempotency_keys()\n"
  },
  {
    "path": "djstripe/management/commands/djstripe_init_customers.py",
    "content": "\"\"\"\ninit_customers command.\n\"\"\"\nfrom django.core.management.base import BaseCommand\n\nfrom ...models import Customer\nfrom ...settings import djstripe_settings\n\n\nclass Command(BaseCommand):\n    \"\"\"Create customer objects for existing subscribers that don't have one.\"\"\"\n\n    help = \"Create customer objects for existing subscribers that don't have one\"\n\n    def handle(self, *args, **options):\n        \"\"\"\n        Create Customer objects for Subscribers without Customer objects associated.\n        \"\"\"\n        subscriber_qs = djstripe_settings.get_subscriber_model().objects.filter(\n            djstripe_customers=None\n        )\n        if subscriber_qs:\n            for subscriber in subscriber_qs:\n                # use get_or_create in case of race conditions on large subscriber bases\n                Customer.get_or_create(subscriber=subscriber)\n                self.stdout.write(f\"Created subscriber for {subscriber.email}\")\n        else:\n            self.stdout.write(\"All Customers already have subscribers\")\n"
  },
  {
    "path": "djstripe/management/commands/djstripe_process_events.py",
    "content": "from django.core.management.base import BaseCommand\n\nfrom ... import models\nfrom ...mixins import VerbosityAwareOutputMixin\nfrom ...settings import djstripe_settings\n\n\nclass Command(VerbosityAwareOutputMixin, BaseCommand):\n    \"\"\"Command to process all Events.\n\n    Optional arguments are provided to limit the number of Events processed.\n\n    Note: this is only guaranteed go back at most 30 days based on the\n    current limitation of stripe's events API. See: https://stripe.com/docs/api/events\n    \"\"\"\n\n    help = (\n        \"Process all Events. Use optional arguments to limit the Events to process. \"\n        \"Note: this is only guaranteed go back at most 30 days based on the current \"\n        \"limitation of stripe's events API. See:  https://stripe.com/docs/api/events\"\n    )\n\n    def add_arguments(self, parser):\n        \"\"\"Add optional arguments to filter Events by.\"\"\"\n        # Use a mutually exclusive group to prevent multiple arguments being\n        # specified together.\n        group = parser.add_mutually_exclusive_group()\n        group.add_argument(\n            \"--ids\",\n            nargs=\"*\",\n            help=\"An optional space separated list of specific Event IDs to sync.\",\n        )\n        group.add_argument(\n            \"--failed\",\n            action=\"store_true\",\n            help=\"Syncs and processes only the events that have failed webhooks.\",\n        )\n        group.add_argument(\n            \"--type\",\n            help=(\n                \"A string containing a specific event name,\"\n                \" or group of events using * as a wildcard.\"\n                \" The list will be filtered to include only\"\n                \" events with a matching event property.\"\n            ),\n        )\n\n    def handle(self, *args, **options):\n        \"\"\"Try to process Events listed from the API.\"\"\"\n        # Set the verbosity to determine how much we output, if at all.\n        self.set_verbosity(options)\n\n        event_ids = options[\"ids\"]\n        failed = options[\"failed\"]\n        type_filter = options[\"type\"]\n\n        # Args are mutually exclusive,\n        # so output what we are doing based on that assumption.\n        if failed:\n            self.output(\"Processing all failed events\")\n        elif type_filter:\n            self.output(f\"Processing all events that match {type_filter}\")\n        elif event_ids:\n            self.output(f\"Processing specific events {event_ids}\")\n        else:\n            self.output(\"Processing all available events\")\n\n        # Either use the specific event IDs to retrieve data, or use the api_list\n        # if no specific event IDs are specified.\n        if event_ids:\n            listed_events = (\n                models.Event.stripe_class.retrieve(\n                    id=event_id,\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    stripe_version=djstripe_settings.STRIPE_API_VERSION,\n                )\n                for event_id in event_ids\n            )\n        else:\n            list_kwargs = {}\n            if failed:\n                list_kwargs[\"delivery_success\"] = False\n\n            if type_filter:\n                list_kwargs[\"type\"] = type_filter\n\n            listed_events = models.Event.api_list(**list_kwargs)\n\n        self.process_events(listed_events)\n\n    def process_events(self, listed_events):\n        # Process each listed event. Capture failures and continue,\n        # outputting debug information as verbosity dictates.\n        count = 0\n        total = 0\n        for event_data in listed_events:\n            try:\n                total += 1\n                event = models.Event.process(data=event_data)\n                count += 1\n                self.verbose_output(f\"\\tSynced Event {event.id}\")\n            except Exception as exception:\n                self.verbose_output(f\"\\tFailed processing Event {event_data['id']}\")\n                self.output(f\"\\t{exception}\")\n                self.verbose_traceback()\n\n        if total == 0:\n            self.output(\"\\t(no results)\")\n        else:\n            self.output(f\"\\tProcessed {count} out of {total} Events\")\n"
  },
  {
    "path": "djstripe/management/commands/djstripe_sync_customers.py",
    "content": "\"\"\"\nsync_customer command.\n\"\"\"\nfrom django.core.management.base import BaseCommand\n\nfrom ...settings import djstripe_settings\nfrom ...sync import sync_subscriber\n\n\nclass Command(BaseCommand):\n    \"\"\"Sync subscriber data with stripe.\"\"\"\n\n    help = \"Sync subscriber data with stripe.\"\n\n    def handle(self, *args, **options):\n        \"\"\"Call sync_subscriber on Subscribers without customers associated to them.\"\"\"\n        qs = djstripe_settings.get_subscriber_model().objects.filter(\n            djstripe_customers__isnull=True\n        )\n        count = 0\n        total = qs.count()\n        for subscriber in qs:\n            count += 1\n            pc = int(round(100 * (float(count) / float(total))))\n            print(\n                f\"[{count}/{total} {pc}%] Syncing {subscriber.email} [{subscriber.pk}]\"\n            )\n            sync_subscriber(subscriber)\n"
  },
  {
    "path": "djstripe/management/commands/djstripe_sync_models.py",
    "content": "\"\"\"Module for the djstripe_sync_model management command to sync\nall Stripe objects to the local db.\n\nInvoke like so:\n    1) To sync all Objects for all API keys:\n        python manage.py djstripe_sync_models\n\n    2) To sync all Objects only for sk_test_XXX API key:\n        python manage.py djstripe_sync_models --api-keys sk_test_XXX\n\n    3) To sync all Objects only for sk_test_XXX and sk_test_YYY API keys:\n        python manage.py djstripe_sync_models --api-keys sk_test_XXX sk_test_XXX\n                                    OR\n        python manage.py djstripe_sync_models --api-keys sk_test_XXX --api-keys sk_test_XXX\n\n    4) To only sync Stripe Accounts for all API keys:\n        python manage.py djstripe_sync_models Account\n\n    5) To only sync Stripe Accounts for sk_test_XXX API key:\n        python manage.py djstripe_sync_models Account --api-keys sk_test_XXX\n\n    6) To only sync Stripe Accounts for sk_test_XXX and sk_test_YYY API keys:\n        python manage.py djstripe_sync_models Account --api-keys sk_test_XXX sk_test_YYY\n\n    7) To only sync Stripe Accounts and Charges for sk_test_XXX and sk_test_YYY API keys:\n        python manage.py djstripe_sync_models Account Charge --api-keys sk_test_XXX sk_test_YYY\n\"\"\"\nimport typing\n\nfrom django.apps import apps\nfrom django.core.exceptions import FieldDoesNotExist\nfrom django.core.management.base import BaseCommand, CommandError\nfrom django.db import models as django_models\n\nfrom ... import enums, models\nfrom ...models.base import StripeBaseModel\nfrom ...settings import djstripe_settings\n\n# TODO Improve performance using multiprocessing\n\n\nclass Command(BaseCommand):\n    \"\"\"Sync models from stripe.\"\"\"\n\n    help = \"Sync models from stripe.\"\n\n    def add_arguments(self, parser):\n        parser.add_argument(\n            \"args\",\n            metavar=\"ModelName\",\n            nargs=\"*\",\n            help=\"restricts sync to these model names (default is to sync all \"\n            \"supported models)\",\n        )\n        # Named (optional) arguments\n        parser.add_argument(\n            \"--api-keys\",\n            metavar=\"ApiKeys\",\n            nargs=\"*\",\n            type=str,\n            action=\"extend\",\n            help=\"Specify the api_keys you would like to perform this sync for.\",\n        )\n\n    def handle(self, *args, api_keys: typing.List[str], **options):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        model_list: typing.List[models.StripeModel] = []\n\n        if args:\n            for model_label in args:\n                try:\n                    model = app_config.get_model(model_label)\n                except LookupError:\n                    raise CommandError(f\"Unknown model: {app_label}.{model_label}\")\n\n                model_list.append(model)\n        else:\n            model_list = app_config.get_models()\n\n        if api_keys is not None:\n            for api_key in api_keys:\n                try:\n                    # check to ensure the given key is in the DB\n                    models.APIKey.objects.get(secret=api_key)\n                except models.APIKey.DoesNotExist:\n                    raise CommandError(f\"APIKey: {api_key} is not in the database.\")\n\n            api_qs = models.APIKey.objects.filter(secret__in=api_keys)\n        else:\n            # get all APIKey objects in the db\n            api_qs = models.APIKey.objects.all()\n\n            if not api_qs.exists():\n                self.stderr.write(\n                    \"You don't have any API Keys in the database. Did you forget to add them?\"\n                )\n                return\n\n        for model in model_list:\n            for api_key in api_qs:\n                self.sync_model(model, api_key=api_key)\n\n    def _should_sync_model(self, model):\n        if not issubclass(model, StripeBaseModel):\n            return False, \"not a StripeModel\"\n\n        if model.stripe_class is None:\n            return False, \"no stripe_class\"\n\n        if not hasattr(model.stripe_class, \"list\"):\n            if model in (\n                models.ApplicationFeeRefund,\n                models.LineItem,\n                models.Source,\n                models.TransferReversal,\n                models.TaxId,\n                models.UsageRecordSummary,\n            ):\n                return True, \"\"\n            return False, \"no stripe_class.list\"\n\n        if model is models.UpcomingInvoice:\n            return False, \"Upcoming Invoices are virtual only\"\n\n        if not djstripe_settings.STRIPE_LIVE_MODE:\n            if model is models.ScheduledQueryRun:\n                return False, \"only available in live mode\"\n\n        return True, \"\"\n\n    def sync_model(self, model, api_key: str):\n        model_name = model.__name__\n\n        should_sync, reason = self._should_sync_model(model)\n        if not should_sync:\n            self.stderr.write(f\"Skipping {model}: {reason}\")\n            return\n\n        self.stdout.write(f\"Syncing {model_name} for key {api_key}:\")\n\n        count = 0\n        try:\n            # todo convert get_list_kwargs into a generator to make the code memory effecient.\n            for list_kwargs in self.get_list_kwargs(model, api_key=api_key.secret):\n                stripe_account = list_kwargs.get(\"stripe_account\", \"\")\n\n                if (\n                    model is models.Account\n                    and stripe_account\n                    == models.Account.get_default_account(api_key=api_key.secret).id\n                ):\n                    # special case, since own account isn't returned by Account.api_list\n                    stripe_obj = models.Account.stripe_class.retrieve(\n                        api_key=api_key.secret,\n                        stripe_version=djstripe_settings.STRIPE_API_VERSION,\n                    )\n\n                    djstripe_obj = model.sync_from_stripe_data(\n                        stripe_obj, api_key=api_key.secret\n                    )\n                    self.stdout.write(\n                        f\"  id={djstripe_obj.id}, pk={djstripe_obj.pk} ({djstripe_obj} on {stripe_account} for {api_key})\"\n                    )\n\n                    # syncing BankAccount and Card objects of Stripe Connected Express and Custom Accounts\n                    self.sync_bank_accounts_and_cards(\n                        djstripe_obj,\n                        stripe_account=stripe_account,\n                        api_key=api_key.secret,\n                    )\n                    count += 1\n\n                try:\n                    for stripe_obj in model.api_list(**list_kwargs):\n                        # Skip model instances that throw an error\n                        try:\n                            djstripe_obj = model.sync_from_stripe_data(\n                                stripe_obj, api_key=api_key.secret\n                            )\n                            self.stdout.write(\n                                f\"  id={djstripe_obj.id}, pk={djstripe_obj.pk} ({djstripe_obj} on {stripe_account} for {api_key})\"\n                            )\n                            # syncing BankAccount and Card objects of Stripe Connected Express and Custom Accounts\n                            self.sync_bank_accounts_and_cards(\n                                djstripe_obj,\n                                stripe_account=stripe_account,\n                                api_key=api_key.secret,\n                            )\n                            count += 1\n                        except Exception as e:\n                            self.stderr.write(f\"Skipping {stripe_obj.get('id')}: {e}\")\n\n                            continue\n                except Exception as e:\n                    self.stderr.write(f\"Skipping: {e}\")\n\n            if count == 0:\n                self.stdout.write(\"  (no results)\")\n            else:\n                self.stdout.write(f\"  Synced {count} {model_name} for {api_key}\")\n\n        except Exception as e:\n            self.stderr.write(str(e))\n\n    @classmethod\n    def get_stripe_account(cls, api_key: str, *args, **kwargs):\n        \"\"\"Get set of all stripe account ids including the Platform Acccount\"\"\"\n        accs_set = set()\n\n        # special case, since own account isn't returned by Account.api_list\n        stripe_platform_obj = models.Account.stripe_class.retrieve(\n            api_key=api_key,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n        accs_set.add(stripe_platform_obj.id)\n\n        for stripe_connected_obj in models.Account.api_list(api_key=api_key, **kwargs):\n            accs_set.add(stripe_connected_obj.id)\n\n        return accs_set\n\n    # todo simplfy this code by spliting into 1-2 functions\n    @staticmethod\n    def get_default_list_kwargs(model, accounts_set, api_key: str):\n        \"\"\"Returns default sequence of kwargs to sync\n        all Stripe Accounts\"\"\"\n        expand = []\n\n        try:\n            # get all forward and reverse relations for given cls\n            for field in model.expand_fields:\n                # add expand_field on the current model\n                expand.append(f\"data.{field}\")\n\n                try:\n                    field_inst = model._meta.get_field(field)\n\n                    # get expand_fields on Forward FK and OneToOneField relations on the current model\n                    if isinstance(\n                        field_inst,\n                        (django_models.ForeignKey, django_models.OneToOneField),\n                    ):\n                        try:\n                            for (\n                                related_model_expand_field\n                            ) in field_inst.related_model.expand_fields:\n                                # add expand_field on the current model\n                                expand.append(\n                                    f\"data.{field}.{related_model_expand_field}\"\n                                )\n\n                                related_model_expand_field_inst = (\n                                    field_inst.related_model._meta.get_field(\n                                        related_model_expand_field\n                                    )\n                                )\n\n                                # get expand_fields on Forward FK and OneToOneField relations on the current model\n                                if isinstance(\n                                    related_model_expand_field_inst,\n                                    (\n                                        django_models.ForeignKey,\n                                        django_models.OneToOneField,\n                                    ),\n                                ):\n                                    try:\n                                        # need to prepend \"field_name.\" to each of the entry in the expand_fields list\n                                        related_model_expand_fields = map(\n                                            lambda i: f\"data.{field_inst.name}.{related_model_expand_field}.{i}\",\n                                            related_model_expand_field_inst.related_model.expand_fields,\n                                        )\n\n                                        expand = [\n                                            *expand,\n                                            *related_model_expand_fields,\n                                        ]\n                                    except AttributeError:\n                                        continue\n                        except AttributeError:\n                            continue\n                except FieldDoesNotExist:\n                    pass\n        except AttributeError:\n            pass\n\n        if expand:\n            default_list_kwargs = [\n                {\n                    \"expand\": expand,\n                    \"stripe_account\": account,\n                    \"api_key\": api_key,\n                }\n                for account in accounts_set\n            ]\n\n        else:\n            default_list_kwargs = [\n                {\n                    \"stripe_account\": account,\n                    \"api_key\": api_key,\n                }\n                for account in accounts_set\n            ]\n\n        return default_list_kwargs\n\n    @staticmethod\n    def get_list_kwargs_il(default_list_kwargs):\n        \"\"\"Returns sequence of kwargs to sync Line Items for\n        all Stripe Accounts\"\"\"\n\n        all_list_kwargs = []\n\n        for def_kwarg in default_list_kwargs:\n            stripe_account = def_kwarg.get(\"stripe_account\")\n            api_key = def_kwarg.get(\"api_key\")\n            for stripe_invoice in models.Invoice.api_list(\n                stripe_account=stripe_account, api_key=api_key\n            ):\n                all_list_kwargs.append({\"id\": stripe_invoice.id, **def_kwarg})\n\n        return all_list_kwargs\n\n    @staticmethod\n    def get_list_kwargs_pm(default_list_kwargs):\n        \"\"\"Returns sequence of kwargs to sync Payment Methods for\n        all Stripe Accounts\"\"\"\n\n        all_list_kwargs = []\n        payment_method_types = enums.PaymentMethodType.__members__\n\n        for def_kwarg in default_list_kwargs:\n            stripe_account = def_kwarg.get(\"stripe_account\")\n            api_key = def_kwarg.get(\"api_key\")\n            for stripe_customer in models.Customer.api_list(\n                stripe_account=stripe_account, api_key=api_key\n            ):\n                for type in payment_method_types:\n                    all_list_kwargs.append(\n                        {\"customer\": stripe_customer.id, \"type\": type, **def_kwarg}\n                    )\n\n        return all_list_kwargs\n\n    @staticmethod\n    def get_list_kwargs_src(default_list_kwargs):\n        \"\"\"Returns sequence of kwargs to sync Sources for\n        all Stripe Accounts\"\"\"\n\n        all_list_kwargs = []\n        for def_kwarg in default_list_kwargs:\n            stripe_account = def_kwarg.get(\"stripe_account\")\n            api_key = def_kwarg.get(\"api_key\")\n            for stripe_customer in models.Customer.api_list(\n                stripe_account=stripe_account, api_key=api_key\n            ):\n                all_list_kwargs.append({\"id\": stripe_customer.id, **def_kwarg})\n\n        return all_list_kwargs\n\n    @staticmethod\n    def get_list_kwargs_si(default_list_kwargs):\n        \"\"\"Returns sequence of kwargs to sync Subscription Items for\n        all Stripe Accounts\"\"\"\n\n        all_list_kwargs = []\n        for def_kwarg in default_list_kwargs:\n            stripe_account = def_kwarg.get(\"stripe_account\")\n            api_key = def_kwarg.get(\"api_key\")\n            for subscription in models.Subscription.api_list(\n                stripe_account=stripe_account, api_key=api_key\n            ):\n                all_list_kwargs.append({\"subscription\": subscription.id, **def_kwarg})\n        return all_list_kwargs\n\n    @staticmethod\n    def get_list_kwargs_country_spec(default_list_kwargs):\n        \"\"\"Returns sequence of kwargs to sync Country Specs for\n        all Stripe Accounts\"\"\"\n\n        all_list_kwargs = []\n        for def_kwarg in default_list_kwargs:\n            all_list_kwargs.append({\"limit\": 50, **def_kwarg})\n\n        return all_list_kwargs\n\n    @staticmethod\n    def get_list_kwargs_txcd(default_list_kwargs):\n        \"\"\"Returns sequence of kwargs to sync Tax Codes for\n        all Stripe Accounts\"\"\"\n\n        # tax codes are the same for all Stripe Accounts\n        return [{}]\n\n    @staticmethod\n    def get_list_kwargs_trr(default_list_kwargs):\n        \"\"\"Returns sequence of kwargs to sync Transfer Reversals for\n        all Stripe Accounts\"\"\"\n        all_list_kwargs = []\n        for def_kwarg in default_list_kwargs:\n            stripe_account = def_kwarg.get(\"stripe_account\")\n            api_key = def_kwarg.get(\"api_key\")\n            for transfer in models.Transfer.api_list(\n                stripe_account=stripe_account, api_key=api_key\n            ):\n                all_list_kwargs.append({\"id\": transfer.id, **def_kwarg})\n\n        return all_list_kwargs\n\n    @staticmethod\n    def get_list_kwargs_fee_refund(default_list_kwargs):\n        \"\"\"Returns sequence of kwargs to sync Application Fee Refunds for\n        all Stripe Accounts\"\"\"\n        all_list_kwargs = []\n        for def_kwarg in default_list_kwargs:\n            stripe_account = def_kwarg.get(\"stripe_account\")\n            api_key = def_kwarg.get(\"api_key\")\n            for fee in models.ApplicationFee.api_list(\n                stripe_account=stripe_account, api_key=api_key\n            ):\n                all_list_kwargs.append({\"id\": fee.id, **def_kwarg})\n\n        return all_list_kwargs\n\n    @staticmethod\n    def get_list_kwargs_tax_id(default_list_kwargs):\n        \"\"\"Returns sequence of kwargs to sync Tax Ids for\n        all Stripe Accounts\"\"\"\n        all_list_kwargs = []\n        for def_kwarg in default_list_kwargs:\n            stripe_account = def_kwarg.get(\"stripe_account\")\n            api_key = def_kwarg.get(\"api_key\")\n            for customer in models.Customer.api_list(\n                stripe_account=stripe_account, api_key=api_key\n            ):\n                all_list_kwargs.append({\"id\": customer.id, **def_kwarg})\n\n        return all_list_kwargs\n\n    @staticmethod\n    def get_list_kwargs_sis(default_list_kwargs):\n        \"\"\"Returns sequence of kwargs to sync Usage Record Summarys for\n        all Stripe Accounts\"\"\"\n        all_list_kwargs = []\n        for def_kwarg in default_list_kwargs:\n            stripe_account = def_kwarg.get(\"stripe_account\")\n            api_key = def_kwarg.get(\"api_key\")\n            for subscription in models.Subscription.api_list(\n                stripe_account=stripe_account, api_key=api_key\n            ):\n                for subscription_item in models.SubscriptionItem.api_list(\n                    subscription=subscription.id,\n                    stripe_account=stripe_account,\n                    api_key=api_key,\n                ):\n                    all_list_kwargs.append({\"id\": subscription_item.id, **def_kwarg})\n\n        return all_list_kwargs\n\n    # todo handle supoorting double + nested fields like data.invoice.subscriptions.customer etc?\n    def get_list_kwargs(self, model, api_key: str):\n        \"\"\"\n        Returns a sequence of kwargs dicts to pass to model.api_list\n\n        This allows us to sync models that require parameters to api_list\n\n        :param model:\n        :return: Sequence[dict]\n        \"\"\"\n\n        list_kwarg_handlers_dict = {\n            \"LineItem\": self.get_list_kwargs_il,\n            \"PaymentMethod\": self.get_list_kwargs_pm,\n            \"Source\": self.get_list_kwargs_src,\n            \"SubscriptionItem\": self.get_list_kwargs_si,\n            \"CountrySpec\": self.get_list_kwargs_country_spec,\n            \"TransferReversal\": self.get_list_kwargs_trr,\n            \"ApplicationFeeRefund\": self.get_list_kwargs_fee_refund,\n            \"TaxCode\": self.get_list_kwargs_txcd,\n            \"TaxId\": self.get_list_kwargs_tax_id,\n            \"UsageRecordSummary\": self.get_list_kwargs_sis,\n        }\n\n        # get all Stripe Accounts for the given platform account.\n        # note that we need to fetch from Stripe as we have no way of knowing that the ones in the local db are up to date\n        # as this can also be the first time the user runs sync.\n        accs_set = self.get_stripe_account(api_key=api_key)\n\n        default_list_kwargs = self.get_default_list_kwargs(\n            model, accs_set, api_key=api_key\n        )\n\n        handler = list_kwarg_handlers_dict.get(\n            model.__name__, lambda _: default_list_kwargs\n        )\n\n        return handler(default_list_kwargs)\n\n    def sync_bank_accounts_and_cards(self, instance, *, stripe_account, api_key):\n        \"\"\"\n        Syncs Bank Accounts and Cards for both customers and all external accounts\n        \"\"\"\n        type = getattr(instance, \"type\", None)\n        kwargs = {\n            \"id\": instance.id,\n            \"api_key\": api_key,\n            \"stripe_account\": stripe_account,\n            \"stripe_version\": djstripe_settings.STRIPE_API_VERSION,\n        }\n\n        if type in (enums.AccountType.custom, enums.AccountType.express) and isinstance(\n            instance, models.Account\n        ):\n            # fetch all Card and BankAccount objects associated with the instance\n            items = models.Account.stripe_class.list_external_accounts(\n                **kwargs\n            ).auto_paging_iter()\n\n            self.start_sync(items, instance, api_key=api_key)\n        elif isinstance(instance, models.Customer):\n            for object in (\"card\", \"bank_account\"):\n                kwargs[\"object\"] = object\n\n                # fetch all Card and BankAccount objects associated with the instance\n                items = models.Customer.stripe_class.list_sources(\n                    **kwargs\n                ).auto_paging_iter()\n\n                self.start_sync(items, instance, api_key=api_key)\n\n    def start_sync(self, items, instance, api_key: str):\n        bank_count = 0\n        card_count = 0\n        for item in items:\n            if item.object == \"bank_account\":\n                model = models.BankAccount\n                bank_count += 1\n            elif item.object == \"card\":\n                model = models.Card\n                card_count += 1\n\n            item_obj = model.sync_from_stripe_data(item, api_key=api_key)\n\n            self.stdout.write(\n                f\"\\tSyncing {model._meta.verbose_name} ({instance}): id={item_obj.id}, pk={item_obj.pk}\"\n            )\n\n        if bank_count + card_count > 0:\n            self.stdout.write(\n                f\"\\tSynced {bank_count} BankAccounts and {card_count} Cards\"\n            )\n"
  },
  {
    "path": "djstripe/management/commands/djstripe_update_invoiceitem_ids.py",
    "content": "from django.core.management.base import BaseCommand\nfrom django.db import transaction\n\nfrom ...models.billing import InvoiceItem\n\nno_results_msg = (\n    \"There are no more potential InvoiceItems to migrate. \"\n    \"You do not need to run this command anymore.\"\n)\n\n\nclass Command(BaseCommand):\n    help = \"Update old InvoiceItem IDs to the new, 2019-12-03 format.\"\n\n    def add_arguments(self, parser):\n        \"\"\"Add optional arguments to filter Events by.\"\"\"\n        # Use a mutually exclusive group to prevent multiple arguments being\n        # specified together.\n        group = parser.add_mutually_exclusive_group()\n        group.add_argument(\n            \"--i-understand\",\n            action=\"store_true\",\n            help=\"Run the command, once you've read the warning and understand it.\",\n        )\n\n    def handle(self, *args, **options):\n        invoice_items = InvoiceItem.objects.filter(id__contains=\"-il_\")\n        count = invoice_items.count()\n\n        if not options[\"i_understand\"]:\n            self.stderr.write(\n                \"In Stripe API 2019-12-03, the format of invoice line items changed. \"\n                \"This means that existing InvoiceItem objects with the old ID format \"\n                \"may still be in the database and need to be migrated.\\n\"\n                \"This is a destructive migration, but this command will attempt to \"\n                \"perform it as safely as possible.\\n\"\n                \"More information: https://stripe.com/docs/upgrades#2019-12-03\\n\\n\"\n            )\n            if count:\n                first_few_ids = invoice_items[:10].values_list(\"id\", flat=True)\n                self.stdout.write(f\"I have found {count} InvoiceItems to migrate:\")\n                self.stdout.write(\n                    \"    \" + \", \".join(first_few_ids) + f\", … (and {count-10} more)\"\n                    if count > 10\n                    else \"\"\n                )\n                self.stderr.write(\n                    \"To perform this migration, run this again with `--i-understand`.\"\n                )\n            else:\n                self.stdout.write(no_results_msg)\n            return\n\n        if not count:\n            self.stdout.write(no_results_msg)\n            return\n\n        for ii in invoice_items:\n            old_id = ii.id\n            new_id = old_id.partition(\"-\")[2]\n            if \"-\" in new_id or not new_id.startswith(\"il_\"):\n                self.stderr.write(\n                    f\"Don't know how to migrate {old_id!r}. This is a bug. \"\n                    \"Could you report it?\\n https://github.com/dj-stripe/dj-stripe\"\n                )\n                continue\n\n            self.stdout.write(f\"Migrating {old_id} => {new_id}\")\n            with transaction.atomic():\n                ii.id = new_id\n                stripe_data = ii.api_retrieve()\n                ii.save()\n                InvoiceItem.sync_from_stripe_data(stripe_data)\n"
  },
  {
    "path": "djstripe/managers.py",
    "content": "\"\"\"\ndj-stripe model managers\n\"\"\"\nimport decimal\n\nfrom django.db import models\n\n\nclass StripeModelManager(models.Manager):\n    \"\"\"Manager used in StripeModel.\"\"\"\n\n    pass\n\n\nclass SubscriptionManager(models.Manager):\n    \"\"\"Manager used in models.Subscription.\"\"\"\n\n    def started_during(self, year, month):\n        \"\"\"Return Subscriptions not in trial status between a certain time range.\"\"\"\n        return self.exclude(status=\"trialing\").filter(\n            start_date__year=year, start_date__month=month\n        )\n\n    def active(self):\n        \"\"\"Return active Subscriptions.\"\"\"\n        return self.filter(status=\"active\")\n\n    def canceled(self):\n        \"\"\"Return canceled Subscriptions.\"\"\"\n        return self.filter(status=\"canceled\")\n\n    def canceled_during(self, year, month):\n        \"\"\"Return Subscriptions canceled during a certain time range.\"\"\"\n        return self.canceled().filter(canceled_at__year=year, canceled_at__month=month)\n\n    def started_plan_summary_for(self, year, month):\n        \"\"\"Return started_during Subscriptions with plan counts annotated.\"\"\"\n        return (\n            self.started_during(year, month)\n            .values(\"plan\")\n            .order_by()\n            .annotate(count=models.Count(\"plan\"))\n        )\n\n    def active_plan_summary(self):\n        \"\"\"Return active Subscriptions with plan counts annotated.\"\"\"\n        return (\n            self.active().values(\"plan\").order_by().annotate(count=models.Count(\"plan\"))\n        )\n\n    def canceled_plan_summary_for(self, year, month):\n        \"\"\"\n        Return Subscriptions canceled within a time range with plan counts annotated.\n        \"\"\"\n        return (\n            self.canceled_during(year, month)\n            .values(\"plan\")\n            .order_by()\n            .annotate(count=models.Count(\"plan\"))\n        )\n\n    def churn(self):\n        \"\"\"Return number of canceled Subscriptions divided by active Subscriptions.\"\"\"\n        canceled = self.canceled().count()\n        active = self.active().count()\n        return decimal.Decimal(str(canceled)) / decimal.Decimal(str(active))\n\n\nclass TransferManager(models.Manager):\n    \"\"\"Manager used by models.Transfer.\"\"\"\n\n    def during(self, year, month):\n        \"\"\"Return Transfers between a certain time range.\"\"\"\n        return self.filter(created__year=year, created__month=month)\n\n    def paid_totals_for(self, year, month):\n        \"\"\"\n        Return paid Transfers during a certain year, month with total amounts annotated.\n        \"\"\"\n        return self.during(year, month).aggregate(total_amount=models.Sum(\"amount\"))\n\n\nclass ChargeManager(models.Manager):\n    \"\"\"Manager used by models.Charge.\"\"\"\n\n    def during(self, year, month):\n        \"\"\"Return Charges between a certain time range based on `created`.\"\"\"\n        return self.filter(created__year=year, created__month=month)\n\n    def paid_totals_for(self, year, month):\n        \"\"\"\n        Return paid Charges during a certain year, month with total amount,\n        fee and refunded annotated.\n        \"\"\"\n        return (\n            self.during(year, month)\n            .filter(paid=True)\n            .aggregate(\n                total_amount=models.Sum(\"amount\"),\n                total_refunded=models.Sum(\"amount_refunded\"),\n            )\n        )\n"
  },
  {
    "path": "djstripe/migrations/0001_initial.py",
    "content": "# Generated by Django 3.2.11 on 2022-01-17 03:13\n\nimport uuid\n\nimport django.core.validators\nimport django.db.models.deletion\nfrom django.conf import settings\nfrom django.db import migrations, models\n\nimport djstripe.enums\nimport djstripe.fields\nimport djstripe.models.api\nimport djstripe.models.webhooks\n\nDJSTRIPE_SUBSCRIBER_MODEL: str = getattr(\n    settings, \"DJSTRIPE_SUBSCRIBER_MODEL\", settings.AUTH_USER_MODEL\n)  # type: ignore\n\n# Needed here for external apps that have added the DJSTRIPE_SUBSCRIBER_MODEL\n# *not* in the '__first__' migration of the app, which results in:\n# ValueError: Related model 'DJSTRIPE_SUBSCRIBER_MODEL' cannot be resolved\n# Context: https://github.com/dj-stripe/dj-stripe/issues/707\nDJSTRIPE_SUBSCRIBER_MODEL_MIGRATION_DEPENDENCY = getattr(\n    settings, \"DJSTRIPE_SUBSCRIBER_MODEL_MIGRATION_DEPENDENCY\", \"__first__\"\n)\n\nDJSTRIPE_SUBSCRIBER_MODEL_DEPENDENCY = migrations.swappable_dependency(\n    DJSTRIPE_SUBSCRIBER_MODEL\n)\n\nif DJSTRIPE_SUBSCRIBER_MODEL != settings.AUTH_USER_MODEL:\n    DJSTRIPE_SUBSCRIBER_MODEL_DEPENDENCY = migrations.migration.SwappableTuple(\n        (\n            DJSTRIPE_SUBSCRIBER_MODEL.split(\".\", 1)[0],\n            DJSTRIPE_SUBSCRIBER_MODEL_MIGRATION_DEPENDENCY,\n        ),\n        DJSTRIPE_SUBSCRIBER_MODEL,\n    )\n\n\nclass Migration(migrations.Migration):\n    initial = True\n\n    dependencies = [DJSTRIPE_SUBSCRIBER_MODEL_DEPENDENCY]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"Account\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\"business_profile\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"business_type\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.BusinessType,\n                        max_length=10,\n                    ),\n                ),\n                (\n                    \"charges_enabled\",\n                    models.BooleanField(\n                        help_text=\"Whether the account can create live charges\"\n                    ),\n                ),\n                (\n                    \"country\",\n                    models.CharField(\n                        help_text=\"The country of the account\", max_length=2\n                    ),\n                ),\n                (\"company\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"default_currency\",\n                    djstripe.fields.StripeCurrencyCodeField(max_length=3),\n                ),\n                (\n                    \"details_submitted\",\n                    models.BooleanField(\n                        help_text=\"Whether account details have been submitted. Standard accounts cannot receive payouts before this is true.\"\n                    ),\n                ),\n                (\n                    \"email\",\n                    models.CharField(\n                        help_text=\"The primary user's email address.\", max_length=255\n                    ),\n                ),\n                (\"individual\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"payouts_enabled\",\n                    models.BooleanField(\n                        help_text=\"Whether Stripe can send payouts to this account\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"product_description\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Internal-only description of the product sold or service provided by the business. It's used by Stripe for risk and underwriting purposes.\",\n                        max_length=255,\n                    ),\n                ),\n                (\"requirements\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"settings\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.AccountType, max_length=8\n                    ),\n                ),\n                (\"tos_acceptance\", djstripe.fields.JSONField(blank=True, null=True)),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"Charge\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"amount\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11\n                    ),\n                ),\n                (\n                    \"amount_refunded\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11\n                    ),\n                ),\n                (\n                    \"captured\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"If the charge was created without capturing, this boolean represents whether or not it is still uncaptured or has since been captured.\",\n                    ),\n                ),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"failure_code\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.ApiErrorCode,\n                        max_length=42,\n                    ),\n                ),\n                (\n                    \"failure_message\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Message to user further explaining reason for charge failure if available.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\"fraud_details\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"outcome\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"paid\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"True if the charge succeeded, or was successfully authorized for later capture, False otherwise.\",\n                    ),\n                ),\n                (\n                    \"payment_method_details\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\n                    \"receipt_email\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The email address that the receipt for this charge was sent to.\",\n                        max_length=800,\n                    ),\n                ),\n                (\n                    \"receipt_number\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The transaction number that appears on email receipts sent for this charge.\",\n                        max_length=14,\n                    ),\n                ),\n                (\n                    \"receipt_url\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"This is the URL to view the receipt for this charge. The receipt is kept up-to-date to the latest state of the charge, including any refunds. If the charge is for an Invoice, the receipt will be stylized as an Invoice receipt.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"refunded\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"Whether or not the charge has been fully refunded. If the charge is only partially refunded, this attribute will still be false.\",\n                    ),\n                ),\n                (\"shipping\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"statement_descriptor\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"For card charges, use statement_descriptor_suffix instead. Otherwise, you can use this value as the complete description of a charge on your customers' statements. Must contain at least one letter, maximum 22 characters.\",\n                        max_length=22,\n                        null=True,\n                    ),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.ChargeStatus, max_length=9\n                    ),\n                ),\n                (\n                    \"transfer_group\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"A string that identifies this transaction as part of a group.\",\n                        max_length=255,\n                        null=True,\n                    ),\n                ),\n                (\n                    \"on_behalf_of\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"charges\",\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The account (if any) the charge was made on behalf of without triggering an automatic transfer.\",\n                    ),\n                ),\n                (\n                    \"amount_captured\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"application\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"ID of the Connect application that created the charge.\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"application_fee_amount\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"billing_details\",\n                    djstripe.fields.JSONField(null=True),\n                ),\n                (\n                    \"calculated_statement_descriptor\",\n                    models.CharField(\n                        default=\"\",\n                        help_text=\"The full statement descriptor that is passed to card networks, and that is displayed on your customers' credit card and bank statements. Allows you to see what the statement descriptor looks like after the static and dynamic portions are combined.\",\n                        max_length=22,\n                    ),\n                ),\n                (\n                    \"disputed\",\n                    models.BooleanField(\n                        default=False, help_text=\"Whether the charge has been disputed.\"\n                    ),\n                ),\n                (\n                    \"statement_descriptor_suffix\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"Provides information about the charge that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that's set on the account to form the complete statement descriptor. Maximum 22 characters for the concatenated descriptor.\",\n                        max_length=22,\n                        null=True,\n                    ),\n                ),\n                (\n                    \"transfer_data\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"Coupon\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\"id\", djstripe.fields.StripeIdField(max_length=500)),\n                (\n                    \"amount_off\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"currency\",\n                    djstripe.fields.StripeCurrencyCodeField(\n                        blank=True, max_length=3, null=True\n                    ),\n                ),\n                (\n                    \"duration\",\n                    djstripe.fields.StripeEnumField(\n                        default=\"once\", enum=djstripe.enums.CouponDuration, max_length=9\n                    ),\n                ),\n                (\n                    \"duration_in_months\",\n                    models.PositiveIntegerField(\n                        blank=True,\n                        help_text=\"If `duration` is `repeating`, the number of months the coupon applies.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"max_redemptions\",\n                    models.PositiveIntegerField(\n                        blank=True,\n                        help_text=\"Maximum number of times this coupon can be redeemed, in total, before it is no longer valid.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"percent_off\",\n                    djstripe.fields.StripePercentField(\n                        blank=True,\n                        decimal_places=2,\n                        max_digits=5,\n                        null=True,\n                        validators=[\n                            django.core.validators.MinValueValidator(1),\n                            django.core.validators.MaxValueValidator(100),\n                        ],\n                    ),\n                ),\n                (\n                    \"redeem_by\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"times_redeemed\",\n                    models.PositiveIntegerField(\n                        default=0,\n                        editable=False,\n                        help_text=\"Number of times this coupon has been applied to a customer.\",\n                    ),\n                ),\n                (\n                    \"name\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Name of the coupon displayed to customers on for instance invoices or receipts.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\n                \"get_latest_by\": \"created\",\n                \"unique_together\": {(\"id\", \"livemode\")},\n            },\n        ),\n        migrations.CreateModel(\n            name=\"PaymentMethod\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\"billing_details\", djstripe.fields.JSONField()),\n                (\"card\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"card_present\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.PaymentMethodType, max_length=15\n                    ),\n                ),\n                (\"alipay\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"au_becs_debit\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"bacs_debit\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"bancontact\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"eps\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"fpx\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"giropay\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"ideal\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"interac_present\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"oxxo\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"p24\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"sepa_debit\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"sofort\", djstripe.fields.JSONField(blank=True, null=True)),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"Customer\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"balance\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(default=0),\n                ),\n                (\n                    \"currency\",\n                    djstripe.fields.StripeCurrencyCodeField(\n                        blank=True, default=\"\", max_length=3\n                    ),\n                ),\n                (\n                    \"delinquent\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"Whether or not the latest charge for the customer's latest invoice has failed.\",\n                    ),\n                ),\n                (\n                    \"coupon_start\",\n                    djstripe.fields.StripeDateTimeField(\n                        blank=True, editable=False, null=True\n                    ),\n                ),\n                (\n                    \"coupon_end\",\n                    djstripe.fields.StripeDateTimeField(\n                        blank=True, editable=False, null=True\n                    ),\n                ),\n                (\"email\", models.TextField(blank=True, default=\"\", max_length=5000)),\n                (\"shipping\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"date_purged\", models.DateTimeField(editable=False, null=True)),\n                (\n                    \"coupon\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.coupon\",\n                    ),\n                ),\n                (\n                    \"default_source\",\n                    djstripe.fields.PaymentMethodForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"customers\",\n                        to=\"djstripe.paymentmethod\",\n                    ),\n                ),\n                (\n                    \"subscriber\",\n                    models.ForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"djstripe_customers\",\n                        to=DJSTRIPE_SUBSCRIBER_MODEL,\n                    ),\n                ),\n                (\"address\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"invoice_prefix\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The prefix for the customer used to generate unique invoice numbers.\",\n                        max_length=255,\n                    ),\n                ),\n                (\"invoice_settings\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"name\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The customer's full name or business name.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"phone\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The customer's phone number.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\"preferred_locales\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"tax_exempt\",\n                    djstripe.fields.StripeEnumField(\n                        default=\"\", enum=djstripe.enums.CustomerTaxExempt, max_length=7\n                    ),\n                ),\n                (\n                    \"default_payment_method\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"+\",\n                        to=\"djstripe.paymentmethod\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"default payment method used for subscriptions and invoices for the customer.\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\n                \"get_latest_by\": \"created\",\n                \"unique_together\": {\n                    (\"subscriber\", \"livemode\", \"djstripe_owner_account\")\n                },\n            },\n        ),\n        migrations.CreateModel(\n            name=\"Dispute\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\"amount\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\"evidence\", djstripe.fields.JSONField()),\n                (\"evidence_details\", djstripe.fields.JSONField()),\n                (\n                    \"is_charge_refundable\",\n                    models.BooleanField(\n                        help_text=\"If true, it is still possible to refund the disputed payment. Once the payment has been fully refunded, no further funds will be withdrawn from your Stripe account as a result of this dispute.\"\n                    ),\n                ),\n                (\n                    \"reason\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.DisputeReason, max_length=25\n                    ),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.DisputeStatus, max_length=22\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"Event\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"api_version\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"the API version at which the event data was rendered. Blank for old entries only, all new entries will have this value\",\n                        max_length=15,\n                    ),\n                ),\n                (\"data\", djstripe.fields.JSONField()),\n                (\n                    \"request_id\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Information about the request that triggered this event, for traceability purposes. If empty string then this is an old entry without that data. If Null then this is not an old entry, but a Stripe 'automated' event with no associated request.\",\n                        max_length=50,\n                    ),\n                ),\n                (\"idempotency_key\", models.TextField(blank=True, default=\"\")),\n                (\n                    \"type\",\n                    models.CharField(\n                        help_text=\"Stripe's event description code\", max_length=250\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"FileUpload\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"filename\",\n                    models.CharField(\n                        help_text=\"A filename for the file, suitable for saving to a filesystem.\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"purpose\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.FilePurpose, max_length=35\n                    ),\n                ),\n                (\n                    \"size\",\n                    models.IntegerField(\n                        help_text=\"The size in bytes of the file upload object.\"\n                    ),\n                ),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.FileType, max_length=4\n                    ),\n                ),\n                (\n                    \"url\",\n                    models.CharField(\n                        help_text=\"A read-only URL where the uploaded file can be accessed.\",\n                        max_length=200,\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"DjstripePaymentMethod\",\n            fields=[\n                (\n                    \"id\",\n                    models.CharField(max_length=255, primary_key=True, serialize=False),\n                ),\n                (\"type\", models.CharField(db_index=True, max_length=50)),\n            ],\n        ),\n        migrations.CreateModel(\n            name=\"Plan\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"active\",\n                    models.BooleanField(\n                        help_text=\"Whether the plan can be used for new purchases.\"\n                    ),\n                ),\n                (\n                    \"aggregate_usage\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.PlanAggregateUsage,\n                        max_length=18,\n                    ),\n                ),\n                (\n                    \"amount\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"amount_decimal\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=12, max_digits=19, null=True\n                    ),\n                ),\n                (\n                    \"billing_scheme\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.BillingScheme,\n                        max_length=8,\n                    ),\n                ),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"interval\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.PlanInterval, max_length=5\n                    ),\n                ),\n                (\n                    \"interval_count\",\n                    models.PositiveIntegerField(\n                        blank=True,\n                        help_text=\"The number of intervals (specified in the interval property) between each subscription billing.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"nickname\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"A brief description of the plan, hidden from customers.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\"tiers\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"tiers_mode\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        enum=djstripe.enums.PriceTiersMode,\n                        max_length=9,\n                        null=True,\n                    ),\n                ),\n                (\"transform_usage\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"trial_period_days\",\n                    models.IntegerField(\n                        blank=True,\n                        help_text=\"Number of trial period days granted when subscribing a customer to this plan. Null if the plan has no trial period.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"usage_type\",\n                    djstripe.fields.StripeEnumField(\n                        default=\"licensed\",\n                        enum=djstripe.enums.PriceUsageType,\n                        max_length=8,\n                    ),\n                ),\n            ],\n            options={\"ordering\": [\"amount\"]},\n        ),\n        migrations.CreateModel(\n            name=\"Product\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"name\",\n                    models.TextField(\n                        help_text=\"The product's name, meant to be displayable to the customer. Applicable to both `service` and `good` types.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.ProductType, max_length=7\n                    ),\n                ),\n                (\n                    \"active\",\n                    models.BooleanField(\n                        help_text=\"Whether the product is currently available for purchase. Only applicable to products of `type=good`.\",\n                        null=True,\n                    ),\n                ),\n                (\"attributes\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"caption\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"A short one-line description of the product, meant to be displayableto the customer. Only applicable to products of `type=good`.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\"deactivate_on\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"images\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"package_dimensions\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\n                    \"shippable\",\n                    models.BooleanField(\n                        blank=True,\n                        help_text=\"Whether this product is a shipped good. Only applicable to products of `type=good`.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"url\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"A URL of a publicly-accessible webpage for this product. Only applicable to products of `type=good`.\",\n                        max_length=799,\n                        null=True,\n                    ),\n                ),\n                (\n                    \"statement_descriptor\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Extra information about a product which will appear on your customer's credit card statement. In the case that multiple products are billed at once, the first statement descriptor will be used. Only available on products of type=`service`.\",\n                        max_length=22,\n                    ),\n                ),\n                (\"unit_label\", models.CharField(blank=True, default=\"\", max_length=12)),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"Subscription\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"application_fee_percent\",\n                    djstripe.fields.StripePercentField(\n                        blank=True,\n                        decimal_places=2,\n                        max_digits=5,\n                        null=True,\n                        validators=[\n                            django.core.validators.MinValueValidator(1.0),\n                            django.core.validators.MaxValueValidator(100.0),\n                        ],\n                    ),\n                ),\n                (\n                    \"collection_method\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.InvoiceCollectionMethod, max_length=20\n                    ),\n                ),\n                (\n                    \"billing_cycle_anchor\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"cancel_at_period_end\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"If the subscription has been canceled with the ``at_period_end`` flag set to true, ``cancel_at_period_end`` on the subscription will be true. You can use this attribute to determine whether a subscription that has a status of active is scheduled to be canceled at the end of the current period.\",\n                    ),\n                ),\n                (\n                    \"canceled_at\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\"current_period_end\", djstripe.fields.StripeDateTimeField()),\n                (\"current_period_start\", djstripe.fields.StripeDateTimeField()),\n                (\n                    \"days_until_due\",\n                    models.IntegerField(\n                        blank=True,\n                        help_text=\"Number of days a customer has to pay invoices generated by this subscription. This value will be `null` for subscriptions where `billing=charge_automatically`.\",\n                        null=True,\n                    ),\n                ),\n                (\"discount\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"ended_at\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"next_pending_invoice_item_invoice\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"pending_invoice_item_interval\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\"pending_update\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"quantity\",\n                    models.IntegerField(\n                        blank=True,\n                        help_text=\"The quantity applied to this subscription. This value will be `null` for multi-plan subscriptions\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"start_date\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.SubscriptionStatus, max_length=18\n                    ),\n                ),\n                (\n                    \"tax_percent\",\n                    djstripe.fields.StripePercentField(\n                        blank=True,\n                        decimal_places=2,\n                        max_digits=5,\n                        null=True,\n                        validators=[\n                            django.core.validators.MinValueValidator(1.0),\n                            django.core.validators.MaxValueValidator(100.0),\n                        ],\n                    ),\n                ),\n                (\n                    \"trial_end\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"trial_start\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"subscriptions\",\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The customer associated with this subscription.\",\n                    ),\n                ),\n                (\n                    \"plan\",\n                    models.ForeignKey(\n                        blank=True,\n                        help_text=\"The plan associated with this subscription. This value will be `null` for multi-plan subscriptions\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"subscriptions\",\n                        to=\"djstripe.plan\",\n                    ),\n                ),\n                (\n                    \"billing_thresholds\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\n                    \"cancel_at\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"Transfer\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"amount\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11\n                    ),\n                ),\n                (\n                    \"amount_reversed\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\"destination\", djstripe.fields.StripeIdField(max_length=255)),\n                (\n                    \"destination_payment\",\n                    djstripe.fields.StripeIdField(\n                        blank=True, max_length=255, null=True\n                    ),\n                ),\n                (\n                    \"reversed\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"Whether or not the transfer has been fully reversed. If the transfer is only partially reversed, this attribute will still be false.\",\n                    ),\n                ),\n                (\n                    \"source_transaction\",\n                    djstripe.fields.StripeIdField(max_length=255, null=True),\n                ),\n                (\n                    \"source_type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.LegacySourceType, max_length=16\n                    ),\n                ),\n                (\n                    \"transfer_group\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"A string that identifies this transaction as part of a group.\",\n                        max_length=255,\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"WebhookEventTrigger\",\n            fields=[\n                (\"id\", models.BigAutoField(primary_key=True, serialize=False)),\n                (\n                    \"remote_ip\",\n                    models.GenericIPAddressField(\n                        help_text=\"IP address of the request client.\"\n                    ),\n                ),\n                (\"headers\", djstripe.fields.JSONField()),\n                (\"body\", models.TextField(blank=True)),\n                (\n                    \"valid\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"Whether or not the webhook event has passed validation\",\n                    ),\n                ),\n                (\n                    \"processed\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"Whether or not the webhook event has been successfully processed\",\n                    ),\n                ),\n                (\"exception\", models.CharField(blank=True, max_length=128)),\n                (\n                    \"traceback\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"Traceback if an exception was thrown during processing\",\n                    ),\n                ),\n                (\n                    \"djstripe_version\",\n                    models.CharField(\n                        default=djstripe.models.webhooks._get_version,\n                        help_text=\"The version of dj-stripe when the webhook was received\",\n                        max_length=32,\n                    ),\n                ),\n                (\"created\", models.DateTimeField(auto_now_add=True)),\n                (\"updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"event\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.event\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Event object contained in the (valid) Webhook\",\n                    ),\n                ),\n            ],\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"customer\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"payment_methods\",\n                to=\"djstripe.customer\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"Customer to which this PaymentMethod is saved. This will not be set when the PaymentMethod has not been saved to a Customer.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"plan\",\n            name=\"product\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"djstripe.product\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The product whose pricing this plan determines.\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"Invoice\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"amount_due\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11\n                    ),\n                ),\n                (\n                    \"amount_paid\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"amount_remaining\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"application_fee_amount\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"attempt_count\",\n                    models.IntegerField(\n                        help_text=\"Number of payment attempts made for this invoice, from the perspective of the payment retry schedule. Any payment attempt counts as the first attempt, and subsequently only automatic retries increment the attempt count. In other words, manual payment attempts after the first attempt do not affect the retry schedule.\"\n                    ),\n                ),\n                (\n                    \"attempted\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"Whether or not an attempt has been made to pay the invoice. An invoice is not attempted until 1 hour after the ``invoice.created`` webhook, for example, so you might not want to display that invoice as unpaid to your users.\",\n                    ),\n                ),\n                (\n                    \"collection_method\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.InvoiceCollectionMethod,\n                        max_length=20,\n                        null=True,\n                    ),\n                ),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"due_date\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"ending_balance\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(null=True),\n                ),\n                (\n                    \"hosted_invoice_url\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The URL for the hosted invoice page, which allows customers to view and pay an invoice. If the invoice has not been frozen yet, this will be null.\",\n                        max_length=799,\n                    ),\n                ),\n                (\n                    \"invoice_pdf\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The link to download the PDF for the invoice. If the invoice has not been frozen yet, this will be null.\",\n                        max_length=799,\n                    ),\n                ),\n                (\n                    \"next_payment_attempt\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"number\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"A unique, identifying string that appears on emails sent to the customer for this invoice. This starts with the customer's unique invoice_prefix if it is specified.\",\n                        max_length=64,\n                    ),\n                ),\n                (\n                    \"paid\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"Whether payment was successfully collected for this invoice. An invoice can be paid (most commonly) with a charge or with credit from the customer's account balance.\",\n                    ),\n                ),\n                (\"period_end\", djstripe.fields.StripeDateTimeField()),\n                (\"period_start\", djstripe.fields.StripeDateTimeField()),\n                (\n                    \"receipt_number\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"This is the transaction number that appears on email receipts sent for this invoice.\",\n                        max_length=64,\n                        null=True,\n                    ),\n                ),\n                (\n                    \"starting_balance\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(),\n                ),\n                (\n                    \"statement_descriptor\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"An arbitrary string to be displayed on your customer's credit card statement. The statement description may not include <>\\\"' characters, and will appear on your customer's statement in capital letters. Non-ASCII characters are automatically stripped. While most banks display this information consistently, some may display it incorrectly or not at all.\",\n                        max_length=22,\n                    ),\n                ),\n                (\n                    \"subscription_proration_date\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"subtotal\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11\n                    ),\n                ),\n                (\n                    \"tax\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"tax_percent\",\n                    djstripe.fields.StripePercentField(\n                        blank=True,\n                        decimal_places=2,\n                        max_digits=5,\n                        null=True,\n                        validators=[\n                            django.core.validators.MinValueValidator(1.0),\n                            django.core.validators.MaxValueValidator(100.0),\n                        ],\n                    ),\n                ),\n                (\n                    \"total\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2,\n                        max_digits=11,\n                        verbose_name=\"Total (as decimal) after discount.\",\n                    ),\n                ),\n                (\n                    \"webhooks_delivered_at\",\n                    djstripe.fields.StripeDateTimeField(null=True),\n                ),\n                (\n                    \"charge\",\n                    models.OneToOneField(\n                        help_text=\"The latest charge generated for this invoice, if any.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"latest_%(class)s\",\n                        to=\"djstripe.charge\",\n                    ),\n                ),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"%(class)ss\",\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The customer associated with this invoice.\",\n                    ),\n                ),\n                (\n                    \"subscription\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"%(class)ss\",\n                        to=\"djstripe.subscription\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The subscription that this invoice was prepared for, if any.\",\n                    ),\n                ),\n                (\n                    \"auto_advance\",\n                    models.BooleanField(\n                        help_text=\"Controls whether Stripe will perform automatic collection of the invoice. When false, the invoice's state will not automatically advance without an explicit action.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"status_transitions\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\n                    \"account_country\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The country of the business associated with this invoice, most often the business creating the invoice.\",\n                        max_length=2,\n                    ),\n                ),\n                (\n                    \"account_name\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The public name of the business associated with this invoice, most often the business creating the invoice.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"billing_reason\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.InvoiceBillingReason,\n                        max_length=22,\n                    ),\n                ),\n                (\n                    \"customer_address\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\n                    \"customer_email\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The customer's email. Until the invoice is finalized, this field will equal customer.email. Once the invoice is finalized, this field will no longer be updated.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"customer_name\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The customer's name. Until the invoice is finalized, this field will equal customer.name. Once the invoice is finalized, this field will no longer be updated.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"customer_phone\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The customer's phone number. Until the invoice is finalized, this field will equal customer.phone. Once the invoice is finalized, this field will no longer be updated.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"customer_shipping\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\n                    \"customer_tax_exempt\",\n                    djstripe.fields.StripeEnumField(\n                        default=\"\", enum=djstripe.enums.CustomerTaxExempt, max_length=7\n                    ),\n                ),\n                (\n                    \"footer\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"Footer displayed on the invoice.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"post_payment_credit_notes_amount\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(\n                        blank=True, null=True\n                    ),\n                ),\n                (\n                    \"pre_payment_credit_notes_amount\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(\n                        blank=True, null=True\n                    ),\n                ),\n                (\n                    \"threshold_reason\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.InvoiceStatus,\n                        max_length=13,\n                    ),\n                ),\n                (\n                    \"discount\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n            ],\n            options={\"get_latest_by\": \"created\", \"ordering\": [\"-created\"]},\n        ),\n        migrations.CreateModel(\n            name=\"IdempotencyKey\",\n            fields=[\n                (\n                    \"uuid\",\n                    models.UUIDField(\n                        default=uuid.uuid4,\n                        editable=False,\n                        primary_key=True,\n                        serialize=False,\n                    ),\n                ),\n                (\"action\", models.CharField(max_length=100)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        help_text=\"Whether the key was used in live or test mode.\"\n                    ),\n                ),\n                (\"created\", models.DateTimeField(auto_now_add=True)),\n            ],\n            options={\"unique_together\": {(\"action\", \"livemode\")}},\n        ),\n        migrations.AddField(\n            model_name=\"charge\",\n            name=\"customer\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"charges\",\n                to=\"djstripe.customer\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The customer associated with this charge.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"charge\",\n            name=\"dispute\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"charges\",\n                to=\"djstripe.dispute\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"Details about the dispute if the charge has been disputed.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"charge\",\n            name=\"invoice\",\n            field=djstripe.fields.StripeForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                related_name=\"charges\",\n                to=\"djstripe.invoice\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The invoice this charge is for if one exists.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"charge\",\n            name=\"source\",\n            field=djstripe.fields.PaymentMethodForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"charges\",\n                to=\"djstripe.paymentmethod\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"charge\",\n            name=\"transfer\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"djstripe.transfer\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The transfer to the `destination` account (only applicable if the charge was created using the `destination` parameter).\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"BankAccount\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"account_holder_name\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The name of the person or business that owns the bank account.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"account_holder_type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.BankAccountHolderType, max_length=10\n                    ),\n                ),\n                (\n                    \"bank_name\",\n                    models.CharField(\n                        help_text=\"Name of the bank associated with the routing number (e.g., `WELLS FARGO`).\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"country\",\n                    models.CharField(\n                        help_text=\"Two-letter ISO code representing the country the bank account is located in.\",\n                        max_length=2,\n                    ),\n                ),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"default_for_currency\",\n                    models.BooleanField(\n                        help_text=\"Whether this external account is the default account for its currency.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"fingerprint\",\n                    models.CharField(\n                        help_text=\"Uniquely identifies this particular bank account. You can use this attribute to check whether two bank accounts are the same.\",\n                        max_length=16,\n                    ),\n                ),\n                (\"last4\", models.CharField(max_length=4)),\n                (\n                    \"routing_number\",\n                    models.CharField(\n                        help_text=\"The routing transit number for the bank account.\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.BankAccountStatus, max_length=19\n                    ),\n                ),\n                (\n                    \"account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.PROTECT,\n                        related_name=\"bank_account\",\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"bank_account\",\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"BalanceTransaction\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\"amount\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\"available_on\", djstripe.fields.StripeDateTimeField()),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"exchange_rate\",\n                    models.DecimalField(decimal_places=6, max_digits=8, null=True),\n                ),\n                (\"fee\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\"fee_details\", djstripe.fields.JSONField()),\n                (\"net\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.BalanceTransactionStatus, max_length=9\n                    ),\n                ),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.BalanceTransactionType, max_length=29\n                    ),\n                ),\n                (\n                    \"reporting_category\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.BalanceTransactionReportingCategory,\n                        max_length=29,\n                    ),\n                ),\n                (\"source\", djstripe.fields.StripeIdField(max_length=255)),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"ApplicationFee\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\"amount\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\"amount_refunded\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"refunded\",\n                    models.BooleanField(\n                        help_text=\"Whether the fee has been fully refunded. If the fee is only partially refunded, this attribute will still be false.\"\n                    ),\n                ),\n                (\n                    \"balance_transaction\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.balancetransaction\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Balance transaction that describes the impact on your account balance.\",\n                    ),\n                ),\n                (\n                    \"charge\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.charge\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The charge that the application fee was taken from.\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.AddField(\n            model_name=\"charge\",\n            name=\"balance_transaction\",\n            field=djstripe.fields.StripeForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"djstripe.balancetransaction\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The balance transaction that describes the impact of this charge on your account balance (not including refunds or disputes).\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"transfer\",\n            name=\"balance_transaction\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"djstripe.balancetransaction\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"Balance transaction that describes the impact on your account balance.\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"SetupIntent\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"application\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"ID of the Connect application that created the SetupIntent.\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"cancellation_reason\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        enum=djstripe.enums.SetupIntentCancellationReason,\n                        max_length=21,\n                    ),\n                ),\n                (\n                    \"client_secret\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The client secret of this SetupIntent. Used for client-side retrieval using a publishable key.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\"last_setup_error\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"next_action\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"payment_method_types\", djstripe.fields.JSONField()),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.SetupIntentStatus, max_length=23\n                    ),\n                ),\n                (\n                    \"usage\",\n                    djstripe.fields.StripeEnumField(\n                        default=\"off_session\",\n                        enum=djstripe.enums.IntentUsage,\n                        max_length=11,\n                    ),\n                ),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Customer this SetupIntent belongs to, if one exists.\",\n                    ),\n                ),\n                (\n                    \"on_behalf_of\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"setup_intents\",\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The account (if any) for which the setup is intended.\",\n                    ),\n                ),\n                (\n                    \"payment_method\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.paymentmethod\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Payment method used in this PaymentIntent.\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"PaymentIntent\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\"amount\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\n                    \"amount_capturable\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(),\n                ),\n                (\"amount_received\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\n                    \"canceled_at\",\n                    djstripe.fields.StripeDateTimeField(\n                        blank=True, default=None, null=True\n                    ),\n                ),\n                (\n                    \"cancellation_reason\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        enum=djstripe.enums.PaymentIntentCancellationReason,\n                        max_length=21,\n                    ),\n                ),\n                (\n                    \"capture_method\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.CaptureMethod, max_length=9\n                    ),\n                ),\n                (\n                    \"client_secret\",\n                    models.TextField(\n                        help_text=\"The client secret of this PaymentIntent. Used for client-side retrieval using a publishable key.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"confirmation_method\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.ConfirmationMethod, max_length=9\n                    ),\n                ),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"An arbitrary string attached to the object. Often useful for displaying to users.\",\n                        max_length=1000,\n                    ),\n                ),\n                (\n                    \"last_payment_error\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\"next_action\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"payment_method_types\", djstripe.fields.JSONField()),\n                (\n                    \"receipt_email\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"Email address that the receipt for the resulting payment will be sent to.\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"setup_future_usage\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        enum=djstripe.enums.IntentUsage,\n                        max_length=11,\n                        null=True,\n                    ),\n                ),\n                (\"shipping\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"statement_descriptor\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"For non-card charges, you can use this value as the complete description that appears on your customers' statements. Must contain at least one letter, maximum 22 characters.\",\n                        max_length=22,\n                    ),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.PaymentIntentStatus, max_length=23\n                    ),\n                ),\n                (\"transfer_data\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"transfer_group\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"A string that identifies the resulting payment as part of a group. See the PaymentIntents Connect usage guide for details.\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Customer this PaymentIntent is for if one exists.\",\n                    ),\n                ),\n                (\n                    \"on_behalf_of\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"payment_intents\",\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The account (if any) for which the funds of the PaymentIntent are intended.\",\n                    ),\n                ),\n                (\n                    \"payment_method\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.paymentmethod\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Payment method used in this PaymentIntent.\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.AddField(\n            model_name=\"charge\",\n            name=\"payment_intent\",\n            field=djstripe.fields.StripeForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"charges\",\n                to=\"djstripe.paymentintent\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"PaymentIntent associated with this charge, if one exists.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"invoice\",\n            name=\"payment_intent\",\n            field=models.OneToOneField(\n                help_text=\"The PaymentIntent associated with this invoice. The PaymentIntent is generated when the invoice is finalized, and can then be used to pay the invoice.Note that voiding an invoice will cancel the PaymentIntent\",\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"djstripe.paymentintent\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"subscription\",\n            name=\"pending_setup_intent\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                related_name=\"setup_intents\",\n                to=\"djstripe.setupintent\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"We can use this SetupIntent to collect user authentication when creating a subscription without immediate payment or updating a subscription's payment method, allowing you to optimize for off-session payments.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"charge\",\n            name=\"payment_method\",\n            field=djstripe.fields.StripeForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"charges\",\n                to=\"djstripe.paymentmethod\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"PaymentMethod used in this charge.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"invoice\",\n            name=\"default_payment_method\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"+\",\n                to=\"djstripe.paymentmethod\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"Default payment method for the invoice. It must belong to the customer associated with the invoice. If not set, defaults to the subscription's default payment method, if any, or to the default payment method in the customer's invoice settings.\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"UpcomingInvoice\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"account_country\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The country of the business associated with this invoice, most often the business creating the invoice.\",\n                        max_length=2,\n                    ),\n                ),\n                (\n                    \"account_name\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The public name of the business associated with this invoice, most often the business creating the invoice.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"amount_due\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11\n                    ),\n                ),\n                (\n                    \"amount_paid\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"amount_remaining\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"application_fee_amount\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"attempt_count\",\n                    models.IntegerField(\n                        help_text=\"Number of payment attempts made for this invoice, from the perspective of the payment retry schedule. Any payment attempt counts as the first attempt, and subsequently only automatic retries increment the attempt count. In other words, manual payment attempts after the first attempt do not affect the retry schedule.\"\n                    ),\n                ),\n                (\n                    \"attempted\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"Whether or not an attempt has been made to pay the invoice. An invoice is not attempted until 1 hour after the ``invoice.created`` webhook, for example, so you might not want to display that invoice as unpaid to your users.\",\n                    ),\n                ),\n                (\n                    \"auto_advance\",\n                    models.BooleanField(\n                        help_text=\"Controls whether Stripe will perform automatic collection of the invoice. When false, the invoice's state will not automatically advance without an explicit action.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"billing_reason\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.InvoiceBillingReason,\n                        max_length=22,\n                    ),\n                ),\n                (\n                    \"collection_method\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.InvoiceCollectionMethod,\n                        max_length=20,\n                        null=True,\n                    ),\n                ),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\"customer_address\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"customer_email\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The customer's email. Until the invoice is finalized, this field will equal customer.email. Once the invoice is finalized, this field will no longer be updated.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"customer_name\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The customer's name. Until the invoice is finalized, this field will equal customer.name. Once the invoice is finalized, this field will no longer be updated.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"customer_phone\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The customer's phone number. Until the invoice is finalized, this field will equal customer.phone. Once the invoice is finalized, this field will no longer be updated.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\"customer_shipping\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"customer_tax_exempt\",\n                    djstripe.fields.StripeEnumField(\n                        default=\"\", enum=djstripe.enums.CustomerTaxExempt, max_length=7\n                    ),\n                ),\n                (\n                    \"due_date\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"ending_balance\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(null=True),\n                ),\n                (\n                    \"footer\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"Footer displayed on the invoice.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"hosted_invoice_url\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The URL for the hosted invoice page, which allows customers to view and pay an invoice. If the invoice has not been frozen yet, this will be null.\",\n                        max_length=799,\n                    ),\n                ),\n                (\n                    \"invoice_pdf\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The link to download the PDF for the invoice. If the invoice has not been frozen yet, this will be null.\",\n                        max_length=799,\n                    ),\n                ),\n                (\n                    \"next_payment_attempt\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"number\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"A unique, identifying string that appears on emails sent to the customer for this invoice. This starts with the customer's unique invoice_prefix if it is specified.\",\n                        max_length=64,\n                    ),\n                ),\n                (\n                    \"paid\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"Whether payment was successfully collected for this invoice. An invoice can be paid (most commonly) with a charge or with credit from the customer's account balance.\",\n                    ),\n                ),\n                (\"period_end\", djstripe.fields.StripeDateTimeField()),\n                (\"period_start\", djstripe.fields.StripeDateTimeField()),\n                (\n                    \"post_payment_credit_notes_amount\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(\n                        blank=True, null=True\n                    ),\n                ),\n                (\n                    \"pre_payment_credit_notes_amount\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(\n                        blank=True, null=True\n                    ),\n                ),\n                (\n                    \"receipt_number\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"This is the transaction number that appears on email receipts sent for this invoice.\",\n                        max_length=64,\n                        null=True,\n                    ),\n                ),\n                (\n                    \"starting_balance\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(),\n                ),\n                (\n                    \"statement_descriptor\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"An arbitrary string to be displayed on your customer's credit card statement. The statement description may not include <>\\\"' characters, and will appear on your customer's statement in capital letters. Non-ASCII characters are automatically stripped. While most banks display this information consistently, some may display it incorrectly or not at all.\",\n                        max_length=22,\n                    ),\n                ),\n                (\n                    \"status_transitions\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\n                    \"subscription_proration_date\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"subtotal\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11\n                    ),\n                ),\n                (\n                    \"tax\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"tax_percent\",\n                    djstripe.fields.StripePercentField(\n                        blank=True,\n                        decimal_places=2,\n                        max_digits=5,\n                        null=True,\n                        validators=[\n                            django.core.validators.MinValueValidator(1),\n                            django.core.validators.MaxValueValidator(100),\n                        ],\n                    ),\n                ),\n                (\"threshold_reason\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"total\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2,\n                        max_digits=11,\n                        verbose_name=\"Total (as decimal) after discount.\",\n                    ),\n                ),\n                (\n                    \"webhooks_delivered_at\",\n                    djstripe.fields.StripeDateTimeField(null=True),\n                ),\n                (\n                    \"charge\",\n                    models.OneToOneField(\n                        help_text=\"The latest charge generated for this invoice, if any.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"latest_%(class)s\",\n                        to=\"djstripe.charge\",\n                    ),\n                ),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"%(class)ss\",\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The customer associated with this invoice.\",\n                    ),\n                ),\n                (\n                    \"default_payment_method\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"+\",\n                        to=\"djstripe.paymentmethod\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Default payment method for the invoice. It must belong to the customer associated with the invoice. If not set, defaults to the subscription's default payment method, if any, or to the default payment method in the customer's invoice settings.\",\n                    ),\n                ),\n                (\n                    \"payment_intent\",\n                    models.OneToOneField(\n                        help_text=\"The PaymentIntent associated with this invoice. The PaymentIntent is generated when the invoice is finalized, and can then be used to pay the invoice.Note that voiding an invoice will cancel the PaymentIntent\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.paymentintent\",\n                    ),\n                ),\n                (\n                    \"subscription\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"%(class)ss\",\n                        to=\"djstripe.subscription\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The subscription that this invoice was prepared for, if any.\",\n                    ),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.InvoiceStatus,\n                        max_length=13,\n                    ),\n                ),\n                (\n                    \"default_source\",\n                    djstripe.fields.PaymentMethodForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"upcoming_invoices\",\n                        to=\"djstripe.djstripepaymentmethod\",\n                    ),\n                ),\n                (\"discount\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"get_latest_by\": \"created\", \"ordering\": [\"-created\"]},\n        ),\n        migrations.CreateModel(\n            name=\"TaxRate\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"active\",\n                    models.BooleanField(\n                        default=True,\n                        help_text=\"Defaults to true. When set to false, this tax rate cannot be applied to objects in the API, but will still be applied to subscriptions and invoices that already have it set.\",\n                    ),\n                ),\n                (\n                    \"display_name\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The display name of the tax rates as it will appear to your customer on their receipt email, PDF, and the hosted invoice page.\",\n                        max_length=50,\n                    ),\n                ),\n                (\n                    \"inclusive\",\n                    models.BooleanField(\n                        help_text=\"This specifies if the tax rate is inclusive or exclusive.\"\n                    ),\n                ),\n                (\n                    \"jurisdiction\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The jurisdiction for the tax rate.\",\n                        max_length=50,\n                    ),\n                ),\n                (\n                    \"percentage\",\n                    djstripe.fields.StripePercentField(\n                        decimal_places=2,\n                        max_digits=5,\n                        validators=[\n                            django.core.validators.MinValueValidator(1),\n                            django.core.validators.MaxValueValidator(100),\n                        ],\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\n                \"get_latest_by\": \"created\",\n                \"verbose_name\": \"Tax Rate\",\n            },\n        ),\n        migrations.AddField(\n            model_name=\"invoice\",\n            name=\"default_tax_rates\",\n            field=models.ManyToManyField(\n                blank=True,\n                db_table=\"djstripe_djstripeinvoicedefaulttaxrate\",\n                help_text=\"The tax rates applied to this invoice, if any.\",\n                related_name=\"+\",\n                to=\"djstripe.TaxRate\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"subscription\",\n            name=\"default_tax_rates\",\n            field=models.ManyToManyField(\n                blank=True,\n                db_table=\"djstripe_djstripesubscriptiondefaulttaxrate\",\n                help_text=\"The tax rates that will apply to any subscription item that does not have tax_rates set. Invoices created will have their default_tax_rates populated from the subscription.\",\n                related_name=\"+\",\n                to=\"djstripe.TaxRate\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"DjstripeUpcomingInvoiceTotalTaxAmount\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"amount\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\n                    \"inclusive\",\n                    models.BooleanField(\n                        help_text=\"Whether this tax amount is inclusive or exclusive.\"\n                    ),\n                ),\n                (\n                    \"invoice\",\n                    models.ForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"+\",\n                        to=\"djstripe.upcominginvoice\",\n                    ),\n                ),\n                (\n                    \"tax_rate\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.taxrate\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The tax rate that was applied to get this tax amount.\",\n                    ),\n                ),\n            ],\n            options={\"unique_together\": {(\"invoice\", \"tax_rate\")}},\n        ),\n        migrations.CreateModel(\n            name=\"DjstripeInvoiceTotalTaxAmount\",\n            fields=[\n                (\n                    \"id\",\n                    models.AutoField(\n                        auto_created=True,\n                        primary_key=True,\n                        serialize=False,\n                        verbose_name=\"ID\",\n                    ),\n                ),\n                (\"amount\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\n                    \"inclusive\",\n                    models.BooleanField(\n                        help_text=\"Whether this tax amount is inclusive or exclusive.\"\n                    ),\n                ),\n                (\n                    \"invoice\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"total_tax_amounts\",\n                        to=\"djstripe.invoice\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"tax_rate\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.taxrate\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The tax rate that was applied to get this tax amount.\",\n                    ),\n                ),\n            ],\n            options={\"unique_together\": {(\"invoice\", \"tax_rate\")}},\n        ),\n        migrations.AddField(\n            model_name=\"subscription\",\n            name=\"default_payment_method\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"+\",\n                to=\"djstripe.paymentmethod\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The default payment method for the subscription. It must belong to the customer associated with the subscription. If not set, invoices will use the default payment method in the customer's invoice settings.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"invoice\",\n            name=\"default_source\",\n            field=djstripe.fields.PaymentMethodForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"invoices\",\n                to=\"djstripe.djstripepaymentmethod\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"subscription\",\n            name=\"default_source\",\n            field=djstripe.fields.PaymentMethodForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"subscriptions\",\n                to=\"djstripe.djstripepaymentmethod\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"ApplicationFeeRefund\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\"amount\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"balance_transaction\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.balancetransaction\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Balance transaction that describes the impact on your account balance.\",\n                    ),\n                ),\n                (\n                    \"fee\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"refunds\",\n                        to=\"djstripe.applicationfee\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The application fee that was refunded\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"Card\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"address_city\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"City/District/Suburb/Town/Village.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"address_country\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Billing address country.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"address_line1\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Street address/PO Box/Company name.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"address_line1_check\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.CardCheckResult,\n                        max_length=11,\n                    ),\n                ),\n                (\n                    \"address_line2\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Apartment/Suite/Unit/Building.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"address_state\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"State/County/Province/Region.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"address_zip\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"ZIP or postal code.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"address_zip_check\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.CardCheckResult,\n                        max_length=11,\n                    ),\n                ),\n                (\n                    \"brand\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.CardBrand, max_length=16\n                    ),\n                ),\n                (\n                    \"country\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Two-letter ISO code representing the country of the card.\",\n                        max_length=2,\n                    ),\n                ),\n                (\n                    \"cvc_check\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.CardCheckResult,\n                        max_length=11,\n                    ),\n                ),\n                (\n                    \"dynamic_last4\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"(For tokenized numbers only.) The last four digits of the device account number.\",\n                        max_length=4,\n                    ),\n                ),\n                (\"exp_month\", models.IntegerField(help_text=\"Card expiration month.\")),\n                (\"exp_year\", models.IntegerField(help_text=\"Card expiration year.\")),\n                (\n                    \"fingerprint\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Uniquely identifies this particular card number.\",\n                        max_length=16,\n                    ),\n                ),\n                (\n                    \"funding\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.CardFundingType, max_length=7\n                    ),\n                ),\n                (\n                    \"last4\",\n                    models.CharField(\n                        help_text=\"Last four digits of Card number.\", max_length=4\n                    ),\n                ),\n                (\n                    \"name\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Cardholder name.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"tokenization_method\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.CardTokenizationMethod,\n                        max_length=11,\n                    ),\n                ),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"legacy_cards\",\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.AddField(\n            model_name=\"charge\",\n            name=\"djstripe_owner_account\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"djstripe.account\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The Stripe Account this object belongs to.\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"CountrySpec\",\n            fields=[\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"id\",\n                    models.CharField(max_length=2, primary_key=True, serialize=False),\n                ),\n                (\n                    \"default_currency\",\n                    djstripe.fields.StripeCurrencyCodeField(max_length=3),\n                ),\n                (\"supported_bank_account_currencies\", djstripe.fields.JSONField()),\n                (\"supported_payment_currencies\", djstripe.fields.JSONField()),\n                (\"supported_payment_methods\", djstripe.fields.JSONField()),\n                (\"supported_transfer_countries\", djstripe.fields.JSONField()),\n                (\"verification_fields\", djstripe.fields.JSONField()),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n            ],\n            options={\"abstract\": False},\n        ),\n        migrations.AddField(\n            model_name=\"invoice\",\n            name=\"djstripe_owner_account\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"djstripe.account\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The Stripe Account this object belongs to.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"djstripe_owner_account\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"djstripe.account\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The Stripe Account this object belongs to.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"plan\",\n            name=\"djstripe_owner_account\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"djstripe.account\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The Stripe Account this object belongs to.\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"Refund\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\"amount\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"failure_reason\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.RefundFailureReason,\n                        max_length=24,\n                    ),\n                ),\n                (\n                    \"reason\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.RefundReason,\n                        max_length=25,\n                    ),\n                ),\n                (\n                    \"receipt_number\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The transaction number that appears on email receipts sent for this charge.\",\n                        max_length=9,\n                    ),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True, enum=djstripe.enums.RefundStatus, max_length=9\n                    ),\n                ),\n                (\n                    \"charge\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"refunds\",\n                        to=\"djstripe.charge\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The charge that was refunded\",\n                    ),\n                ),\n                (\n                    \"balance_transaction\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.balancetransaction\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Balance transaction that describes the impact on your account balance.\",\n                    ),\n                ),\n                (\n                    \"failure_balance_transaction\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"failure_refunds\",\n                        to=\"djstripe.balancetransaction\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"If the refund failed, this balance transaction describes the adjustment made on your account balance that reverses the initial balance transaction.\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"ScheduledQueryRun\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\"data_load_time\", djstripe.fields.StripeDateTimeField()),\n                (\"error\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"result_available_until\", djstripe.fields.StripeDateTimeField()),\n                (\n                    \"sql\",\n                    models.TextField(help_text=\"SQL for the query.\", max_length=5000),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.ScheduledQueryRunStatus, max_length=9\n                    ),\n                ),\n                (\n                    \"title\",\n                    models.TextField(help_text=\"Title of the query.\", max_length=5000),\n                ),\n                (\n                    \"file\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.fileupload\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The file object representing the results of the query.\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"Session\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"billing_address_collection\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        enum=djstripe.enums.SessionBillingAddressCollection,\n                        max_length=8,\n                    ),\n                ),\n                (\n                    \"cancel_url\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The URL the customer will be directed to if theydecide to cancel payment and return to your website.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"client_reference_id\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"A unique string to reference the Checkout Session.This can be a customer ID, a cart ID, or similar, andcan be used to reconcile the session with your internal systems.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"customer_email\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"If provided, this value will be used when the Customer object is created.\",\n                        max_length=255,\n                    ),\n                ),\n                (\"display_items\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"locale\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"The IETF language tag of the locale Checkout is displayed in.If blank or auto, the browser's locale is used.\",\n                        max_length=255,\n                    ),\n                ),\n                (\"payment_method_types\", djstripe.fields.JSONField()),\n                (\n                    \"submit_type\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True, enum=djstripe.enums.SubmitTypeStatus, max_length=6\n                    ),\n                ),\n                (\n                    \"success_url\",\n                    models.TextField(\n                        blank=True,\n                        help_text=\"The URL the customer will be directed to after the payment or subscriptioncreation is successful.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Customer this Checkout is for if one exists.\",\n                    ),\n                ),\n                (\n                    \"payment_intent\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.paymentintent\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"PaymentIntent created if SKUs or line items were provided.\",\n                    ),\n                ),\n                (\n                    \"subscription\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.subscription\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Subscription created if one or more plans were provided.\",\n                    ),\n                ),\n                (\n                    \"mode\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True, enum=djstripe.enums.SessionMode, max_length=12\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"Source\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"amount\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"client_secret\",\n                    models.CharField(\n                        help_text=\"The client secret of the source. Used for client-side retrieval using a publishable key.\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"currency\",\n                    djstripe.fields.StripeCurrencyCodeField(\n                        blank=True, default=\"\", max_length=3\n                    ),\n                ),\n                (\n                    \"flow\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.SourceFlow, max_length=17\n                    ),\n                ),\n                (\"owner\", djstripe.fields.JSONField()),\n                (\n                    \"statement_descriptor\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Extra information about a source. This will appear on your customer's statement every time you charge the source.\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.SourceStatus, max_length=10\n                    ),\n                ),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.SourceType, max_length=20\n                    ),\n                ),\n                (\n                    \"usage\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.SourceUsage, max_length=10\n                    ),\n                ),\n                (\"code_verification\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"receiver\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"redirect\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"source_data\", djstripe.fields.JSONField()),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"sources\",\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.AddField(\n            model_name=\"subscription\",\n            name=\"djstripe_owner_account\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"djstripe.account\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The Stripe Account this object belongs to.\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"SubscriptionItem\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"quantity\",\n                    models.PositiveIntegerField(\n                        blank=True,\n                        help_text=\"The quantity of the plan to which the customer should be subscribed.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"plan\",\n                    models.ForeignKey(\n                        help_text=\"The plan the customer is subscribed to.\",\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"subscription_items\",\n                        to=\"djstripe.plan\",\n                    ),\n                ),\n                (\n                    \"subscription\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"items\",\n                        to=\"djstripe.subscription\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The subscription this subscription item belongs to.\",\n                    ),\n                ),\n                (\n                    \"tax_rates\",\n                    models.ManyToManyField(\n                        blank=True,\n                        db_table=\"djstripe_djstripesubscriptionitemtaxrate\",\n                        help_text=\"The tax rates which apply to this subscription_item. When set, the default_tax_rates on the subscription do not apply to this subscription_item.\",\n                        related_name=\"+\",\n                        to=\"djstripe.TaxRate\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n                (\n                    \"billing_thresholds\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.AddField(\n            model_name=\"transfer\",\n            name=\"djstripe_owner_account\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"djstripe.account\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The Stripe Account this object belongs to.\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"TransferReversal\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\"amount\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"balance_transaction\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"transfer_reversals\",\n                        to=\"djstripe.balancetransaction\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Balance transaction that describes the impact on your account balance.\",\n                    ),\n                ),\n                (\n                    \"transfer\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"reversals\",\n                        to=\"djstripe.transfer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The transfer that was reversed.\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"UsageRecord\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"quantity\",\n                    models.PositiveIntegerField(\n                        help_text=\"The quantity of the plan to which the customer should be subscribed.\"\n                    ),\n                ),\n                (\n                    \"subscription_item\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"usage_records\",\n                        to=\"djstripe.subscriptionitem\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The subscription item this usage record contains data for.\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"Price\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"active\",\n                    models.BooleanField(\n                        help_text=\"Whether the price can be used for new purchases.\"\n                    ),\n                ),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"nickname\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"A brief description of the plan, hidden from customers.\",\n                        max_length=250,\n                    ),\n                ),\n                (\n                    \"recurring\",\n                    djstripe.fields.JSONField(blank=True, default=None, null=True),\n                ),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.PriceType, max_length=9\n                    ),\n                ),\n                (\n                    \"unit_amount\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(\n                        blank=True, null=True\n                    ),\n                ),\n                (\n                    \"unit_amount_decimal\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=12, max_digits=19, null=True\n                    ),\n                ),\n                (\n                    \"billing_scheme\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True, enum=djstripe.enums.BillingScheme, max_length=8\n                    ),\n                ),\n                (\"tiers\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"tiers_mode\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        enum=djstripe.enums.PriceTiersMode,\n                        max_length=9,\n                        null=True,\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n                (\n                    \"product\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"prices\",\n                        to=\"djstripe.product\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The product this price is associated with.\",\n                    ),\n                ),\n                (\n                    \"lookup_key\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"A lookup key used to retrieve prices dynamically from a static string.\",\n                        max_length=250,\n                        null=True,\n                    ),\n                ),\n                (\n                    \"transform_quantity\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n            ],\n            options={\"abstract\": False, \"ordering\": [\"unit_amount\"]},\n        ),\n        migrations.AddField(\n            model_name=\"subscriptionitem\",\n            name=\"price\",\n            field=models.ForeignKey(\n                blank=True,\n                help_text=\"The price the customer is subscribed to.\",\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                related_name=\"subscription_items\",\n                to=\"djstripe.price\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"TaxId\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"country\",\n                    models.CharField(\n                        help_text=\"Two-letter ISO code representing the country of the tax ID.\",\n                        max_length=2,\n                    ),\n                ),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.TaxIdType, max_length=7\n                    ),\n                ),\n                (\n                    \"value\",\n                    models.CharField(help_text=\"Value of the tax ID.\", max_length=50),\n                ),\n                (\"verification\", djstripe.fields.JSONField()),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"tax_ids\",\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"get_latest_by\": \"created\", \"verbose_name\": \"Tax ID\"},\n        ),\n        migrations.CreateModel(\n            name=\"InvoiceItem\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"amount\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11\n                    ),\n                ),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\"date\", djstripe.fields.StripeDateTimeField()),\n                (\n                    \"discountable\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"If True, discounts will apply to this invoice item. Always False for prorations.\",\n                    ),\n                ),\n                (\"period\", djstripe.fields.JSONField()),\n                (\"period_end\", djstripe.fields.StripeDateTimeField()),\n                (\"period_start\", djstripe.fields.StripeDateTimeField()),\n                (\n                    \"proration\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"Whether or not the invoice item was created automatically as a proration adjustment when the customer switched plans.\",\n                    ),\n                ),\n                (\n                    \"quantity\",\n                    models.IntegerField(\n                        blank=True,\n                        help_text=\"If the invoice item is a proration, the quantity of the subscription for which the proration was computed.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"invoiceitems\",\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The customer associated with this invoiceitem.\",\n                    ),\n                ),\n                (\n                    \"invoice\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"invoiceitems\",\n                        to=\"djstripe.invoice\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The invoice to which this invoiceitem is attached.\",\n                    ),\n                ),\n                (\n                    \"plan\",\n                    models.ForeignKey(\n                        help_text=\"If the invoice item is a proration, the plan of the subscription for which the proration was computed.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.plan\",\n                    ),\n                ),\n                (\n                    \"subscription\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"invoiceitems\",\n                        to=\"djstripe.subscription\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The subscription that this invoice item has been created for, if any.\",\n                    ),\n                ),\n                (\n                    \"tax_rates\",\n                    models.ManyToManyField(\n                        blank=True,\n                        db_table=\"djstripe_djstripeinvoiceitemtaxrate\",\n                        help_text=\"The tax rates which apply to this invoice item. When set, the default_tax_rates on the invoice do not apply to this invoice item.\",\n                        related_name=\"+\",\n                        to=\"djstripe.TaxRate\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n                (\n                    \"unit_amount\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(\n                        blank=True, null=True\n                    ),\n                ),\n                (\n                    \"unit_amount_decimal\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=12, max_digits=19, null=True\n                    ),\n                ),\n                (\n                    \"price\",\n                    models.ForeignKey(\n                        help_text=\"If the invoice item is a proration, the price of the subscription for which the proration was computed.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"invoiceitems\",\n                        to=\"djstripe.price\",\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.CreateModel(\n            name=\"SubscriptionSchedule\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"canceled_at\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"completed_at\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\"current_phase\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"default_settings\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"end_behavior\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.SubscriptionScheduleEndBehavior,\n                        max_length=7,\n                    ),\n                ),\n                (\"phases\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"released_at\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.SubscriptionScheduleStatus, max_length=11\n                    ),\n                ),\n                (\n                    \"customer\",\n                    models.ForeignKey(\n                        help_text=\"The customer who owns the subscription schedule.\",\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"schedules\",\n                        to=\"djstripe.customer\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n                (\n                    \"released_subscription\",\n                    models.ForeignKey(\n                        blank=True,\n                        help_text=\"The subscription once managed by this subscription schedule (if it is released).\",\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"released_schedules\",\n                        to=\"djstripe.subscription\",\n                    ),\n                ),\n                (\n                    \"billing_thresholds\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n            ],\n            options={\"get_latest_by\": \"created\", \"abstract\": False},\n        ),\n        migrations.AddField(\n            model_name=\"subscription\",\n            name=\"schedule\",\n            field=models.ForeignKey(\n                blank=True,\n                help_text=\"The schedule associated with this subscription.\",\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                related_name=\"subscriptions\",\n                to=\"djstripe.subscriptionschedule\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"Payout\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"amount\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        decimal_places=2, max_digits=11\n                    ),\n                ),\n                (\"arrival_date\", djstripe.fields.StripeDateTimeField()),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\n                    \"failure_code\",\n                    djstripe.fields.StripeEnumField(\n                        blank=True,\n                        default=\"\",\n                        enum=djstripe.enums.PayoutFailureCode,\n                        max_length=23,\n                    ),\n                ),\n                (\n                    \"failure_message\",\n                    models.TextField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Message to user further explaining reason for payout failure if available.\",\n                    ),\n                ),\n                (\n                    \"method\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.PayoutMethod, max_length=8\n                    ),\n                ),\n                (\n                    \"statement_descriptor\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"Extra information about a payout to be displayed on the user's bank statement.\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.PayoutStatus, max_length=10\n                    ),\n                ),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.PayoutType, max_length=12\n                    ),\n                ),\n                (\n                    \"destination\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.PROTECT,\n                        to=\"djstripe.bankaccount\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Bank account or card the payout was sent to.\",\n                    ),\n                ),\n                (\n                    \"balance_transaction\",\n                    djstripe.fields.StripeForeignKey(\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.balancetransaction\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"Balance transaction that describes the impact on your account balance.\",\n                    ),\n                ),\n                (\n                    \"failure_balance_transaction\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        related_name=\"failure_payouts\",\n                        to=\"djstripe.balancetransaction\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"If the payout failed or was canceled, this will be the balance transaction that reversed the initial balance transaction, and puts the funds from the failed payout back in your balance.\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n                (\n                    \"automatic\",\n                    models.BooleanField(\n                        help_text=\"`true` if the payout was created by an automated payout schedule, and `false` if it was requested manually.\",\n                    ),\n                ),\n                (\n                    \"source_type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.PayoutSourceType,\n                        max_length=12,\n                    ),\n                ),\n            ],\n            options={\"abstract\": False, \"get_latest_by\": \"created\"},\n        ),\n        migrations.AddField(\n            model_name=\"charge\",\n            name=\"source_transfer\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                related_name=\"+\",\n                to=\"djstripe.transfer\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The transfer which created this charge. Only present if the charge came from another Stripe account.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"charge\",\n            name=\"application_fee\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"fee_for_charge\",\n                to=\"djstripe.applicationfee\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The application fee (if any) for the charge.\",\n            ),\n        ),\n        migrations.CreateModel(\n            name=\"APIKey\",\n            fields=[\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        help_text=\"Whether the key is valid for live or test mode.\"\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"id\",\n                    models.CharField(\n                        default=djstripe.models.api.generate_api_key_id,\n                        editable=False,\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.APIKeyType, max_length=11\n                    ),\n                ),\n                (\n                    \"name\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"An optional name to identify the key.\",\n                        max_length=100,\n                        verbose_name=\"Key name\",\n                    ),\n                ),\n                (\n                    \"secret\",\n                    models.CharField(\n                        help_text=\"The value of the key.\",\n                        max_length=128,\n                        unique=True,\n                        validators=[\n                            django.core.validators.RegexValidator(\n                                regex=\"^(pk|sk|rk)_(test|live)_([a-zA-Z0-9]{24,99})\"\n                            )\n                        ],\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\"get_latest_by\": \"created\", \"abstract\": False},\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0008_2_5.py",
    "content": "# Generated by Django 3.2.3 on 2021-05-30 23:47\n\nimport django.db.models.deletion\nfrom django.conf import settings\nfrom django.db import migrations, models\n\nimport djstripe.enums\nimport djstripe.fields\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0001_initial\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"subscription\",\n            name=\"tax_percent\",\n        ),\n        migrations.RemoveField(\n            model_name=\"countryspec\",\n            name=\"djstripe_owner_account\",\n        ),\n        migrations.AddField(\n            model_name=\"card\",\n            name=\"account\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                help_text=\"The external account the charge was made on behalf of. Null here indicates that this value was never set.\",\n                null=True,\n                on_delete=django.db.models.deletion.PROTECT,\n                related_name=\"cards\",\n                to=\"djstripe.account\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"card\",\n            name=\"default_for_currency\",\n            field=models.BooleanField(\n                help_text=\"Whether this external account (Card) is the default account for its currency.\",\n                null=True,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"bankaccount\",\n            name=\"account\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                help_text=\"The external account the charge was made on behalf of. Null here indicates that this value was never set.\",\n                null=True,\n                on_delete=django.db.models.deletion.PROTECT,\n                related_name=\"bank_accounts\",\n                to=\"djstripe.account\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"bankaccount\",\n            name=\"default_for_currency\",\n            field=models.BooleanField(\n                help_text=\"Whether this external account (BankAccount) is the default account for its currency.\",\n                null=True,\n            ),\n        ),\n        migrations.RenameModel(\n            old_name=\"FileUpload\",\n            new_name=\"File\",\n        ),\n        migrations.CreateModel(\n            name=\"FileLink\",\n            fields=[\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"created\",\n                    djstripe.fields.StripeDateTimeField(\n                        blank=True,\n                        help_text=\"The datetime this object was created in stripe.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"metadata\",\n                    djstripe.fields.JSONField(\n                        blank=True,\n                        help_text=\"A set of key/value pairs that you can attach to an object. It can be useful for storing additional information about an object in a structured format.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\n                    \"expires_at\",\n                    djstripe.fields.StripeDateTimeField(\n                        blank=True,\n                        help_text=\"Time at which the link expires.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"url\",\n                    models.URLField(\n                        help_text=\"The publicly accessible URL to download the file.\"\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"file\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.file\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n            ],\n            options={\n                \"get_latest_by\": \"created\",\n                \"abstract\": False,\n            },\n        ),\n        migrations.CreateModel(\n            name=\"Mandate\",\n            fields=[\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"created\",\n                    djstripe.fields.StripeDateTimeField(\n                        blank=True,\n                        help_text=\"The datetime this object was created in stripe.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"metadata\",\n                    djstripe.fields.JSONField(\n                        blank=True,\n                        help_text=\"A set of key/value pairs that you can attach to an object. It can be useful for storing additional information about an object in a structured format.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\n                    \"customer_acceptance\",\n                    djstripe.fields.JSONField(\n                        help_text=\"Details about the customer's acceptance of the mandate.\"\n                    ),\n                ),\n                (\n                    \"payment_method_details\",\n                    djstripe.fields.JSONField(\n                        help_text=\"Additional mandate information specific to the payment method type.\"\n                    ),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.MandateStatus,\n                        help_text=\"The status of the mandate, which indicates whether it can be used to initiate a payment.\",\n                        max_length=8,\n                    ),\n                ),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.MandateType,\n                        help_text=\"The status of the mandate, which indicates whether it can be used to initiate a payment.\",\n                        max_length=10,\n                    ),\n                ),\n                (\n                    \"multi_use\",\n                    djstripe.fields.JSONField(\n                        blank=True,\n                        help_text=\"If this is a `multi_use` mandate, this hash contains details about the mandate.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"single_use\",\n                    djstripe.fields.JSONField(\n                        blank=True,\n                        help_text=\"If this is a `single_use` mandate, this hash contains details about the mandate.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"payment_method\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.paymentmethod\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n            ],\n            options={\n                \"get_latest_by\": \"created\",\n                \"abstract\": False,\n            },\n        ),\n        migrations.AlterField(\n            model_name=\"charge\",\n            name=\"source\",\n            field=djstripe.fields.PaymentMethodForeignKey(\n                blank=True,\n                help_text=\"The source used for this charge.\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"charges\",\n                to=\"djstripe.djstripepaymentmethod\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"customer\",\n            name=\"default_source\",\n            field=djstripe.fields.PaymentMethodForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"customers\",\n                to=\"djstripe.djstripepaymentmethod\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0009_2_6.py",
    "content": "# Generated by Django 3.2.4 on 2021-06-22 01:38\n\nimport uuid\n\nimport django.core.validators\nimport django.db.models.deletion\nfrom django.conf import settings\nfrom django.db import migrations, models\n\nimport djstripe.enums\nimport djstripe.fields\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0008_2_5\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"WebhookEndpoint\",\n            fields=[\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"created\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"metadata\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\n                    \"api_version\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"The API version events are rendered as for this webhook endpoint.\",\n                        max_length=10,\n                    ),\n                ),\n                (\n                    \"enabled_events\",\n                    djstripe.fields.JSONField(),\n                ),\n                (\n                    \"secret\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"The endpoint's secret, used to generate webhook signatures.\",\n                        max_length=256,\n                        editable=False,\n                    ),\n                ),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.WebhookEndpointStatus, max_length=8\n                    ),\n                ),\n                (\n                    \"url\",\n                    models.URLField(\n                        help_text=\"The URL of the webhook endpoint.\", max_length=2048\n                    ),\n                ),\n                (\n                    \"application\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"The ID of the associated Connect application.\",\n                        max_length=255,\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n                (\n                    \"djstripe_uuid\",\n                    models.UUIDField(\n                        null=True,\n                        unique=True,\n                        default=uuid.uuid4,\n                        help_text=\"A UUID specific to dj-stripe generated for the endpoint\",\n                    ),\n                ),\n            ],\n            options={\"get_latest_by\": \"created\", \"abstract\": False},\n        ),\n        migrations.CreateModel(\n            name=\"UsageRecordSummary\",\n            fields=[\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"created\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"period\",\n                    djstripe.fields.JSONField(blank=True, null=True),\n                ),\n                (\n                    \"period_end\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"period_start\",\n                    djstripe.fields.StripeDateTimeField(blank=True, null=True),\n                ),\n                (\n                    \"total_usage\",\n                    models.PositiveIntegerField(\n                        help_text=\"The quantity of the plan to which the customer should be subscribed.\"\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n                (\n                    \"invoice\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"usage_record_summaries\",\n                        to=\"djstripe.invoice\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"subscription_item\",\n                    djstripe.fields.StripeForeignKey(\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"usage_record_summaries\",\n                        to=\"djstripe.subscriptionitem\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The subscription item this usage record contains data for.\",\n                    ),\n                ),\n            ],\n            options={\"get_latest_by\": \"created\", \"abstract\": False},\n        ),\n        migrations.AddField(\n            model_name=\"applicationfee\",\n            name=\"account\",\n            field=djstripe.fields.StripeForeignKey(\n                default=1,\n                on_delete=django.db.models.deletion.PROTECT,\n                related_name=\"application_fees\",\n                to=\"djstripe.account\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"ID of the Stripe account this fee was taken from.\",\n            ),\n            preserve_default=False,\n        ),\n        migrations.AddField(\n            model_name=\"customer\",\n            name=\"deleted\",\n            field=models.BooleanField(\n                blank=True,\n                default=False,\n                help_text=\"Whether the Customer instance has been deleted upstream in Stripe or not.\",\n                null=True,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"dispute\",\n            name=\"balance_transaction\",\n            field=djstripe.fields.StripeForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                related_name=\"disputes\",\n                to=\"djstripe.balancetransaction\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"Balance transaction that describes the impact on your account balance.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"dispute\",\n            name=\"charge\",\n            field=djstripe.fields.StripeForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                related_name=\"disputes\",\n                to=\"djstripe.charge\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The charge that was disputed\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"dispute\",\n            name=\"payment_intent\",\n            field=djstripe.fields.StripeForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                related_name=\"disputes\",\n                to=\"djstripe.paymentintent\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The PaymentIntent that was disputed\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"dispute\",\n            name=\"balance_transactions\",\n            field=djstripe.fields.JSONField(\n                default=list,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"taxrate\",\n            name=\"percentage\",\n            field=djstripe.fields.StripePercentField(\n                decimal_places=4,\n                max_digits=7,\n                validators=[\n                    django.core.validators.MinValueValidator(1),\n                    django.core.validators.MaxValueValidator(100),\n                ],\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"transfer\",\n            name=\"destination\",\n            field=djstripe.fields.StripeIdField(\n                max_length=255,\n                null=True,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"usagerecord\",\n            name=\"action\",\n            field=djstripe.fields.StripeEnumField(\n                default=\"increment\",\n                enum=djstripe.enums.UsageAction,\n                max_length=9,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"usagerecord\",\n            name=\"timestamp\",\n            field=djstripe.fields.StripeDateTimeField(\n                blank=True,\n                null=True,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"webhookeventtrigger\",\n            name=\"stripe_trigger_account\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"djstripe.account\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The Stripe Account this object belongs to.\",\n            ),\n        ),\n        migrations.RemoveField(model_name=\"usagerecord\", name=\"description\"),\n        migrations.RemoveField(model_name=\"usagerecord\", name=\"metadata\"),\n        migrations.AlterField(\n            model_name=\"paymentmethod\",\n            name=\"type\",\n            field=djstripe.fields.StripeEnumField(\n                enum=djstripe.enums.PaymentMethodType, max_length=17\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"acss_debit\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"afterpay_clearpay\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"boleto\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"grabpay\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"wechat_pay\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"subscription\",\n            name=\"latest_invoice\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"+\",\n                to=\"djstripe.invoice\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The most recent invoice this subscription has generated.\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"customer\",\n            name=\"delinquent\",\n            field=models.BooleanField(\n                blank=True,\n                default=False,\n                help_text=\"Whether or not the latest charge for the customer's latest invoice has failed.\",\n                null=True,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0010_alter_customer_balance.py",
    "content": "# Generated by Django 4.0.1 on 2022-01-12 13:12\n\nfrom django.db import migrations\n\nimport djstripe.fields\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0009_2_6\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"customer\",\n            name=\"balance\",\n            field=djstripe.fields.StripeQuantumCurrencyAmountField(\n                blank=True, default=0, null=True\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0011_2_7.py",
    "content": "# Generated by Django 3.2.11 on 2022-01-19 04:59\n\nimport django.db.models.deletion\nfrom django.conf import settings\nfrom django.db import migrations, models\n\nimport djstripe.enums\nimport djstripe.fields\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0010_alter_customer_balance\"),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name=\"subscriptionschedule\",\n            name=\"billing_thresholds\",\n        ),\n        migrations.AddField(\n            model_name=\"webhookeventtrigger\",\n            name=\"webhook_endpoint\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                to=\"djstripe.webhookendpoint\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The endpoint this webhook was received on\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"account\",\n            name=\"djstripe_owner_account\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                null=True,\n                on_delete=django.db.models.deletion.CASCADE,\n                to=\"djstripe.account\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                help_text=\"The Stripe Account this object belongs to.\",\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"event\",\n            name=\"api_version\",\n            field=models.CharField(\n                blank=True,\n                help_text=\"the API version at which the event data was rendered. Blank for old entries only, all new entries will have this value\",\n                max_length=64,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"webhookendpoint\",\n            name=\"api_version\",\n            field=models.CharField(\n                max_length=64,\n                blank=True,\n                help_text=\"The API version events are rendered as for this webhook endpoint. Defaults to the configured Stripe API Version.\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"coupon\",\n            name=\"applies_to\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"subscriptionschedule\",\n            name=\"subscription\",\n            field=models.ForeignKey(\n                blank=True,\n                help_text=\"ID of the subscription managed by the subscription schedule.\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"subscriptions\",\n                to=\"djstripe.subscription\",\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"taxrate\",\n            name=\"country\",\n            field=models.CharField(\n                blank=True,\n                default=\"\",\n                help_text=\"Two-letter country code.\",\n                max_length=2,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"taxrate\",\n            name=\"state\",\n            field=models.CharField(\n                blank=True,\n                default=\"\",\n                help_text=\"ISO 3166-2 subdivision code, without country prefix.\",\n                max_length=2,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"taxrate\",\n            name=\"tax_type\",\n            field=models.CharField(\n                blank=True,\n                default=\"\",\n                help_text=\"The high-level tax type, such as vat, gst, sales_tax or custom.\",\n                max_length=50,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"payout\",\n            name=\"failure_code\",\n            field=djstripe.fields.StripeEnumField(\n                blank=True,\n                default=\"\",\n                enum=djstripe.enums.PayoutFailureCode,\n                max_length=32,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"invoice\",\n            name=\"billing_reason\",\n            field=djstripe.fields.StripeEnumField(\n                blank=True,\n                default=\"\",\n                enum=djstripe.enums.InvoiceBillingReason,\n                max_length=38,\n            ),\n        ),\n        migrations.AlterField(\n            model_name=\"upcominginvoice\",\n            name=\"billing_reason\",\n            field=djstripe.fields.StripeEnumField(\n                blank=True,\n                default=\"\",\n                enum=djstripe.enums.InvoiceBillingReason,\n                max_length=38,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"subscriptionitem\",\n            name=\"proration_behavior\",\n            field=djstripe.fields.StripeEnumField(\n                blank=True,\n                default=\"create_prorations\",\n                enum=djstripe.enums.SubscriptionProrationBehavior,\n                help_text=\"Determines how to handle prorations when the billing cycle changes (e.g., when switching plans, resetting billing_cycle_anchor=now, or starting a trial), or if an item’s quantity changes\",\n                max_length=17,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"subscriptionitem\",\n            name=\"proration_date\",\n            field=djstripe.fields.StripeDateTimeField(\n                blank=True,\n                help_text=\"If set, the proration will be calculated as though the subscription was updated at the given time. This can be used to apply exactly the same proration that was previewed with upcoming invoice endpoint. It can also be used to implement custom proration logic, such as prorating by day instead of by second, by providing the time that you wish to use for proration calculations\",\n                null=True,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"subscription\",\n            name=\"proration_behavior\",\n            field=djstripe.fields.StripeEnumField(\n                blank=True,\n                default=\"create_prorations\",\n                enum=djstripe.enums.SubscriptionProrationBehavior,\n                help_text=\"Determines how to handle prorations when the billing cycle changes (e.g., when switching plans, resetting billing_cycle_anchor=now, or starting a trial), or if an item’s quantity changes\",\n                max_length=17,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"subscription\",\n            name=\"proration_date\",\n            field=djstripe.fields.StripeDateTimeField(\n                blank=True,\n                help_text=\"If set, the proration will be calculated as though the subscription was updated at the given time. This can be used to apply exactly the same proration that was previewed with upcoming invoice endpoint. It can also be used to implement custom proration logic, such as prorating by day instead of by second, by providing the time that you wish to use for proration calculations\",\n                null=True,\n            ),\n        ),\n        migrations.AddField(\n            model_name=\"subscription\",\n            name=\"pause_collection\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.CreateModel(\n            name=\"TaxCode\",\n            fields=[\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\n                    \"name\",\n                    models.CharField(\n                        help_text=\"A short name for the tax code.\", max_length=128\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n            ],\n            options={\n                \"get_latest_by\": \"created\",\n                \"verbose_name\": \"Tax Code\",\n            },\n        ),\n        migrations.CreateModel(\n            name=\"Order\",\n            fields=[\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"amount_subtotal\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\"amount_total\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\n                    \"application\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"ID of the Connect application that created the Order, if any.\",\n                        max_length=255,\n                    ),\n                ),\n                (\"automatic_tax\", djstripe.fields.JSONField()),\n                (\"billing_details\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"client_secret\",\n                    models.TextField(\n                        help_text=\"The client secret of this PaymentIntent. Used for client-side retrieval using a publishable key.\",\n                        max_length=5000,\n                    ),\n                ),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\"discounts\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"ip_address\",\n                    models.GenericIPAddressField(\n                        blank=True,\n                        help_text=\"A recent IP address of the purchaser used for tax reporting and tax location inference.\",\n                        null=True,\n                    ),\n                ),\n                (\"line_items\", djstripe.fields.JSONField()),\n                (\"payment\", djstripe.fields.JSONField()),\n                (\"shipping_cost\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"shipping_details\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"status\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.OrderStatus, max_length=10\n                    ),\n                ),\n                (\"tax_details\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"total_details\", djstripe.fields.JSONField()),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The customer which this orders belongs to.\",\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n                (\n                    \"payment_intent\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.SET_NULL,\n                        to=\"djstripe.paymentintent\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"ID of the payment intent associated with this order. Null when the order is open.\",\n                    ),\n                ),\n            ],\n            options={\n                \"get_latest_by\": \"created\",\n                \"abstract\": False,\n            },\n        ),\n        migrations.CreateModel(\n            name=\"ShippingRate\",\n            fields=[\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"active\",\n                    models.BooleanField(\n                        default=True,\n                        help_text=\"Whether the shipping rate can be used for new purchases. Defaults to true\",\n                    ),\n                ),\n                (\n                    \"display_name\",\n                    models.CharField(\n                        blank=True,\n                        default=\"\",\n                        help_text=\"The name of the shipping rate, meant to be displayable to the customer. This will appear on CheckoutSessions.\",\n                        max_length=50,\n                    ),\n                ),\n                (\"fixed_amount\", djstripe.fields.JSONField()),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        default=\"fixed_amount\",\n                        enum=djstripe.enums.ShippingRateType,\n                        max_length=12,\n                    ),\n                ),\n                (\"delivery_estimate\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"tax_behavior\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.ShippingRateTaxBehavior, max_length=11\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                    ),\n                ),\n                (\n                    \"tax_code\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.taxcode\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                        help_text=\"The shipping tax code\",\n                    ),\n                ),\n            ],\n            options={\n                \"get_latest_by\": \"created\",\n                \"verbose_name\": \"Shipping Rate\",\n            },\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0012_auto_20221217_0559.py",
    "content": "# Generated by Django 3.2.15 on 2022-12-17 05:59\n\nfrom django.db import migrations\n\nimport djstripe.enums\nimport djstripe.fields\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0011_2_7\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"affirm\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"blik\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"customer_balance\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"klarna\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"konbini\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"link\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"paynow\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"pix\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"promptpay\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"paymentmethod\",\n            name=\"us_bank_account\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AlterField(\n            model_name=\"account\",\n            name=\"business_type\",\n            field=djstripe.fields.StripeEnumField(\n                blank=True, default=\"\", enum=djstripe.enums.BusinessType, max_length=17\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0013_product_default_price.py",
    "content": "# Generated by Django 3.2.15 on 2022-12-21 10:57\n\nimport django.db.models.deletion\nfrom django.conf import settings\nfrom django.db import migrations\n\nimport djstripe.fields\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0012_auto_20221217_0559\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"product\",\n            name=\"default_price\",\n            field=djstripe.fields.StripeForeignKey(\n                blank=True,\n                help_text=\"The default price this product is associated with.\",\n                null=True,\n                on_delete=django.db.models.deletion.SET_NULL,\n                related_name=\"products\",\n                to=\"djstripe.price\",\n                to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0014_lineitem.py",
    "content": "# Generated by Django 3.2.16 on 2023-01-26 05:14\n\nimport django.db.models.deletion\nfrom django.conf import settings\nfrom django.db import migrations, models\n\nimport djstripe.enums\nimport djstripe.fields\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0013_product_default_price\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"LineItem\",\n            fields=[\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"amount\", djstripe.fields.StripeQuantumCurrencyAmountField()),\n                (\n                    \"amount_excluding_tax\",\n                    djstripe.fields.StripeQuantumCurrencyAmountField(),\n                ),\n                (\"currency\", djstripe.fields.StripeCurrencyCodeField(max_length=3)),\n                (\"discount_amounts\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"discountable\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"If True, discounts will apply to this line item. Always False for prorations.\",\n                    ),\n                ),\n                (\"discounts\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"period\", djstripe.fields.JSONField()),\n                (\"period_end\", djstripe.fields.StripeDateTimeField()),\n                (\"period_start\", djstripe.fields.StripeDateTimeField()),\n                (\"price\", djstripe.fields.JSONField()),\n                (\n                    \"proration\",\n                    models.BooleanField(\n                        default=False,\n                        help_text=\"Whether or not the invoice item was created automatically as a proration adjustment when the customer switched plans.\",\n                    ),\n                ),\n                (\"proration_details\", djstripe.fields.JSONField()),\n                (\"tax_amounts\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"tax_rates\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.LineItem, max_length=12\n                    ),\n                ),\n                (\n                    \"unit_amount_excluding_tax\",\n                    djstripe.fields.StripeDecimalCurrencyAmountField(\n                        blank=True, decimal_places=2, max_digits=11, null=True\n                    ),\n                ),\n                (\n                    \"quantity\",\n                    models.IntegerField(\n                        blank=True,\n                        help_text=\"The quantity of the subscription, if the line item is a subscription or a proration.\",\n                        null=True,\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"invoice_item\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        help_text=\"The ID of the invoice item associated with this line item if any.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.invoiceitem\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"subscription\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        help_text=\"The subscription that the invoice item pertains to, if any.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.subscription\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"subscription_item\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        help_text=\"The subscription item that generated this invoice item. Left empty if the line item is not an explicit result of a subscription.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.subscriptionitem\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n            ],\n            options={\n                \"get_latest_by\": \"created\",\n                \"abstract\": False,\n            },\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0015_alter_payout_destination.py",
    "content": "# Generated by Django 3.2.12 on 2022-03-29 06:49\n\nfrom pathlib import Path\n\nfrom django.db import migrations\n\n\ndef get_sql_for_connection(schema_editor, direction: str) -> str:\n    \"\"\"\n    Returns the vendor and the collection of SQL Statements depending on:\n\n    1. The SQL Engine\n    2. Direction of Migrations: forward or Backward\n    \"\"\"\n    vendor = schema_editor.connection.vendor\n    # Construct Path to SQL\n    file_path = Path(__file__).parent / \"sql\" / f\"migrate_{vendor}_{direction}.sql\"\n    try:\n        sql_statement = Path(file_path).read_text()\n    except FileNotFoundError as error:\n        # In case it's oracle or some other django supported db that we do not support yet.\n        raise RuntimeError(\n            f\"We currently do not support {vendor}. Please open an issue at https://github.com/dj-stripe/dj-stripe/issues/new?assignees=&labels=discussion&template=feature-or-enhancement-proposal.md&title= if you'd like it supported.\",\n        ) from error\n    return vendor, sql_statement\n\n\ndef forwards_func(apps, schema_editor):\n    vendor, sql_statement = get_sql_for_connection(schema_editor, \"forward\")\n    with schema_editor.connection.cursor() as cursor:\n        if vendor == \"sqlite\":\n            cursor.executescript(sql_statement)\n        else:\n            cursor.execute(sql_statement)\n\n\ndef reverse_func(apps, schema_editor):\n    vendor, sql_statement = get_sql_for_connection(schema_editor, \"backward\")\n    with schema_editor.connection.cursor() as cursor:\n        if vendor == \"sqlite\":\n            cursor.executescript(sql_statement)\n        else:\n            cursor.execute(sql_statement)\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0014_lineitem\"),\n    ]\n    operations = [\n        migrations.RunPython(forwards_func, reverse_func),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0016_alter_payout_destination.py",
    "content": "# Generated by Django 3.2.16 on 2023-01-26 14:47\n\nimport django.db.models.deletion\nfrom django.db import migrations\n\nimport djstripe.fields\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0015_alter_payout_destination\"),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name=\"payout\",\n            name=\"destination\",\n            field=djstripe.fields.PaymentMethodForeignKey(\n                null=True,\n                on_delete=django.db.models.deletion.PROTECT,\n                to=\"djstripe.djstripepaymentmethod\",\n            ),\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0017_invoiceorlineitem.py",
    "content": "# Generated by Django 3.2.13 on 2022-07-09 08:04\n\nimport django.db.models.deletion\nfrom django.conf import settings\nfrom django.db import migrations, models\n\nimport djstripe.enums\nimport djstripe.fields\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0016_alter_payout_destination\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"InvoiceOrLineItem\",\n            fields=[\n                (\n                    \"id\",\n                    models.CharField(max_length=255, primary_key=True, serialize=False),\n                ),\n                (\n                    \"type\",\n                    djstripe.fields.StripeEnumField(\n                        enum=djstripe.enums.InvoiceorLineItemType, max_length=12\n                    ),\n                ),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0018_discount.py",
    "content": "# Generated by Django 3.2.16 on 2023-01-28 06:04\n\nimport django.db.models.deletion\nfrom django.conf import settings\nfrom django.db import migrations, models\n\nimport djstripe.fields\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0017_invoiceorlineitem\"),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name=\"Discount\",\n            fields=[\n                (\"djstripe_created\", models.DateTimeField(auto_now_add=True)),\n                (\"djstripe_updated\", models.DateTimeField(auto_now=True)),\n                (\n                    \"djstripe_id\",\n                    models.BigAutoField(\n                        primary_key=True, serialize=False, verbose_name=\"ID\"\n                    ),\n                ),\n                (\"id\", djstripe.fields.StripeIdField(max_length=255, unique=True)),\n                (\n                    \"livemode\",\n                    models.BooleanField(\n                        blank=True,\n                        default=None,\n                        help_text=\"Null here indicates that the livemode status is unknown or was previously unrecorded. Otherwise, this field indicates whether this record comes from Stripe test mode or live mode operation.\",\n                        null=True,\n                    ),\n                ),\n                (\"created\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\"metadata\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\n                    \"description\",\n                    models.TextField(\n                        blank=True, help_text=\"A description of this object.\", null=True\n                    ),\n                ),\n                (\"coupon\", djstripe.fields.JSONField(blank=True, null=True)),\n                (\"end\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\n                    \"promotion_code\",\n                    models.CharField(\n                        blank=True,\n                        help_text=\"The promotion code applied to create this discount.\",\n                        max_length=255,\n                    ),\n                ),\n                (\"start\", djstripe.fields.StripeDateTimeField(blank=True, null=True)),\n                (\n                    \"checkout_session\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        help_text=\"The Checkout session that this coupon is applied to, if it is applied to a particular session in payment mode. Will not be present for subscription mode.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.session\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"customer\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        help_text=\"The ID of the customer associated with this discount.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"customer_discounts\",\n                        to=\"djstripe.customer\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"djstripe_owner_account\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        help_text=\"The Stripe Account this object belongs to.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.account\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"invoice\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        help_text=\"The invoice that the discount’s coupon was applied to, if it was applied directly to a particular invoice.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"invoice_discounts\",\n                        to=\"djstripe.invoice\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n                (\n                    \"invoice_item\",\n                    djstripe.fields.InvoiceOrLineItemForeignKey(\n                        blank=True,\n                        help_text=\"The invoice item id (or invoice line item id for invoice line items of type=‘subscription’) that the discount’s coupon was applied to, if it was applied directly to a particular invoice item or invoice line item.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        to=\"djstripe.invoiceorlineitem\",\n                    ),\n                ),\n                (\n                    \"subscription\",\n                    djstripe.fields.StripeForeignKey(\n                        blank=True,\n                        help_text=\"The subscription that this coupon is applied to, if it is applied to a particular subscription.\",\n                        null=True,\n                        on_delete=django.db.models.deletion.CASCADE,\n                        related_name=\"subscription_discounts\",\n                        to=\"djstripe.subscription\",\n                        to_field=settings.DJSTRIPE_FOREIGN_KEY_TO_FIELD,\n                    ),\n                ),\n            ],\n            options={\n                \"get_latest_by\": \"created\",\n                \"abstract\": False,\n            },\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/0019_add_customer_discount.py",
    "content": "# Generated by Django 3.2.13 on 2022-07-09 08:09\n\nimport django.db.models.deletion\nfrom django.conf import settings\nfrom django.db import migrations\n\nimport djstripe.fields\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        (\"djstripe\", \"0018_discount\"),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name=\"customer\",\n            name=\"discount\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"invoice\",\n            name=\"discounts\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"upcominginvoice\",\n            name=\"discounts\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n        migrations.AddField(\n            model_name=\"invoiceitem\",\n            name=\"discounts\",\n            field=djstripe.fields.JSONField(blank=True, null=True),\n        ),\n    ]\n"
  },
  {
    "path": "djstripe/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "djstripe/migrations/sql/migrate_mysql_backward.sql",
    "content": "--\n-- Rename field destination_clone on payout to destination\n--\nALTER TABLE `djstripe_payout` CHANGE `destination_id` `destination_clone_id` varchar(255) NULL;\n--\n-- Remove field destination from payout\n--\nALTER TABLE `djstripe_payout` ADD COLUMN `destination_id` bigint NULL , ADD CONSTRAINT `djstripe_payout_destination_id_a5fa55c2_fk_djstripe_` FOREIGN KEY (`destination_id`) REFERENCES `djstripe_bankaccount`(`djstripe_id`);\nCREATE INDEX `djstripe_payout_destination_id_a5fa55c2` ON `djstripe_payout` (`destination_id`);\n\n\n--\n-- Raw SQL operation\n--\nUPDATE djstripe_payout SET destination_id = (select djstripe_id from djstripe_bankaccount where djstripe_bankaccount.id = djstripe_payout. destination_clone_id)\nWHERE EXISTS(select * from djstripe_bankaccount where djstripe_bankaccount.id = djstripe_payout.destination_clone_id);\n\n\n--\n-- Add field destination_clone to payout\n--\nALTER TABLE `djstripe_payout` DROP FOREIGN KEY `djstripe_payout_destination_clone_id_ff0fec04_fk_djstripe_`;\nALTER TABLE `djstripe_payout` DROP COLUMN `destination_clone_id`;\n"
  },
  {
    "path": "djstripe/migrations/sql/migrate_mysql_forward.sql",
    "content": "--\n-- Add field destination_clone to payout\n--\nALTER TABLE `djstripe_payout` ADD COLUMN `destination_clone_id` varchar(255) NULL , ADD CONSTRAINT `djstripe_payout_destination_clone_id_ff0fec04_fk_djstripe_` FOREIGN KEY (`destination_clone_id`) REFERENCES `djstripe_djstripepaymentmethod`(`id`);\nCREATE INDEX `djstripe_payout_destination_clone_id_ff0fec04` ON `djstripe_payout` (`destination_clone_id`);\n\n--\n-- Raw SQL operation\n--\nUPDATE djstripe_payout SET destination_clone_id = (SELECT id from djstripe_bankaccount WHERE djstripe_bankaccount.djstripe_id = djstripe_payout.destination_id)\nWHERE EXISTS(SELECT * from djstripe_bankaccount WHERE djstripe_bankaccount.djstripe_id = djstripe_payout.destination_id);\n\n\n--\n-- Remove field destination from payout\n--\nALTER TABLE `djstripe_payout` DROP FOREIGN KEY `djstripe_payout_destination_id_a5fa55c2_fk_djstripe_`;\nALTER TABLE `djstripe_payout` DROP COLUMN `destination_id`;\n--\n-- Rename field destination_clone on payout to destination\n--\nALTER TABLE `djstripe_payout` CHANGE `destination_clone_id` `destination_id` varchar(255) NULL;\n"
  },
  {
    "path": "djstripe/migrations/sql/migrate_postgresql_backward.sql",
    "content": "--\n-- Rename field destination_clone on payout to destination\n--\nSET CONSTRAINTS \"djstripe_payout_destination_clone_id_ff0fec04_fk_djstripe_\" IMMEDIATE;\nALTER TABLE \"djstripe_payout\" DROP CONSTRAINT \"djstripe_payout_destination_clone_id_ff0fec04_fk_djstripe_\";\nALTER TABLE \"djstripe_payout\" RENAME COLUMN \"destination_id\" TO \"destination_clone_id\";\nALTER TABLE \"djstripe_payout\" ADD CONSTRAINT \"djstripe_payout_destination_clone_id_ff0fec04_fk_djstripe_\" FOREIGN KEY (\"destination_clone_id\") REFERENCES \"djstripe_djstripepaymentmethod\" (\"id\") DEFERRABLE INITIALLY DEFERRED;\n\n--\n-- Remove field destination from payout\n--\nALTER TABLE \"djstripe_payout\" ADD COLUMN \"destination_id\" bigint NULL CONSTRAINT \"djstripe_payout_destination_id_a5fa55c2_fk_djstripe_\" REFERENCES \"djstripe_bankaccount\"(\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED; SET CONSTRAINTS \"djstripe_payout_destination_id_a5fa55c2_fk_djstripe_\" IMMEDIATE;\nCREATE INDEX \"djstripe_payout_destination_id_a5fa55c2\" ON \"djstripe_payout\" (\"destination_id\");\n\n--\n-- Raw SQL operation\n--\nUPDATE djstripe_payout SET destination_id = (select djstripe_id from djstripe_bankaccount where djstripe_bankaccount.id = djstripe_payout. destination_clone_id)\nWHERE EXISTS(select * from djstripe_bankaccount where djstripe_bankaccount.id = djstripe_payout.destination_clone_id);\n\n\n--\n-- Add field destination_clone to payout\n--\nALTER TABLE \"djstripe_payout\" DROP COLUMN \"destination_clone_id\" CASCADE;\n"
  },
  {
    "path": "djstripe/migrations/sql/migrate_postgresql_forward.sql",
    "content": "--\n-- Add field destination_clone to payout\n--\nALTER TABLE \"djstripe_payout\" ADD COLUMN \"destination_clone_id\" varchar(255) NULL CONSTRAINT \"djstripe_payout_destination_clone_id_ff0fec04_fk_djstripe_\"\nREFERENCES \"djstripe_djstripepaymentmethod\"(\"id\") DEFERRABLE INITIALLY IMMEDIATE;\nSET CONSTRAINTS \"djstripe_payout_destination_clone_id_ff0fec04_fk_djstripe_\" IMMEDIATE;\nCREATE INDEX \"djstripe_payout_destination_clone_id_ff0fec04\" ON \"djstripe_payout\" (\"destination_clone_id\");\nCREATE INDEX \"djstripe_payout_destination_clone_id_ff0fec04_like\" ON \"djstripe_payout\" (\"destination_clone_id\" varchar_pattern_ops);\n\n\n--\n-- Copy data from old column to new column\n--\nUPDATE djstripe_payout\nSET destination_clone_id = (SELECT id from djstripe_bankaccount WHERE djstripe_bankaccount.djstripe_id = djstripe_payout.destination_id)\nWHERE EXISTS(SELECT * from djstripe_bankaccount WHERE djstripe_bankaccount.djstripe_id = djstripe_payout.destination_id);\n\n\n--\n-- Remove field destination from payout\n--\nALTER TABLE \"djstripe_payout\" DROP COLUMN \"destination_id\" CASCADE;\n\n\n--\n-- Rename field destination_clone on payout to destination\n--\nALTER TABLE \"djstripe_payout\" RENAME COLUMN \"destination_clone_id\" TO \"destination_id\";\n"
  },
  {
    "path": "djstripe/migrations/sql/migrate_sqlite_backward.sql",
    "content": "--\n-- Rename field destination_clone on payout to destination\n--\nCREATE TABLE \"new__djstripe_payout\" (\"destination_clone_id\" varchar(255) NULL REFERENCES \"djstripe_djstripepaymentmethod\" (\"id\") DEFERRABLE INITIALLY DEFERRED, \"djstripe_id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"id\" varchar(255) NOT NULL UNIQUE, \"livemode\" bool NULL, \"created\" datetime NULL, \"metadata\" text NULL CHECK ((JSON_VALID(\"metadata\") OR \"metadata\" IS NULL)), \"description\" text NULL, \"djstripe_created\" datetime NOT NULL, \"djstripe_updated\" datetime NOT NULL, \"amount\" decimal NOT NULL, \"arrival_date\" datetime NOT NULL, \"currency\" varchar(3) NOT NULL, \"failure_code\" varchar(32) NOT NULL, \"failure_message\" text NOT NULL, \"method\" varchar(8) NOT NULL, \"statement_descriptor\" varchar(255) NOT NULL, \"status\" varchar(10) NOT NULL, \"type\" varchar(12) NOT NULL, \"balance_transaction_id\" bigint NULL REFERENCES \"djstripe_balancetransaction\" (\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED, \"failure_balance_transaction_id\" bigint NULL REFERENCES \"djstripe_balancetransaction\" (\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED, \"djstripe_owner_account_id\" bigint NULL REFERENCES \"djstripe_account\" (\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED, \"automatic\" bool NOT NULL, \"source_type\" varchar(12) NOT NULL);\nINSERT INTO \"new__djstripe_payout\" (\"djstripe_id\", \"id\", \"livemode\", \"created\", \"metadata\", \"description\", \"djstripe_created\", \"djstripe_updated\", \"amount\", \"arrival_date\", \"currency\", \"failure_code\", \"failure_message\", \"method\", \"statement_descriptor\", \"status\", \"type\", \"balance_transaction_id\", \"failure_balance_transaction_id\", \"djstripe_owner_account_id\", \"automatic\", \"source_type\", \"destination_clone_id\") SELECT \"djstripe_id\", \"id\", \"livemode\", \"created\", \"metadata\", \"description\", \"djstripe_created\", \"djstripe_updated\", \"amount\", \"arrival_date\", \"currency\", \"failure_code\", \"failure_message\", \"method\", \"statement_descriptor\", \"status\", \"type\", \"balance_transaction_id\", \"failure_balance_transaction_id\", \"djstripe_owner_account_id\", \"automatic\", \"source_type\", \"destination_id\" FROM \"djstripe_payout\";\nDROP TABLE \"djstripe_payout\";\nALTER TABLE \"new__djstripe_payout\" RENAME TO \"djstripe_payout\";\nCREATE INDEX \"djstripe_payout_destination_clone_id_ff0fec04\" ON \"djstripe_payout\" (\"destination_clone_id\");\nCREATE INDEX \"djstripe_payout_balance_transaction_id_a9393fb6\" ON \"djstripe_payout\" (\"balance_transaction_id\");\nCREATE INDEX \"djstripe_payout_failure_balance_transaction_id_77d442db\" ON \"djstripe_payout\" (\"failure_balance_transaction_id\");\nCREATE INDEX \"djstripe_payout_djstripe_owner_account_id_8aac4e8e\" ON \"djstripe_payout\" (\"djstripe_owner_account_id\");\n\n--\n-- Remove field destination from payout\n--\nALTER TABLE \"djstripe_payout\" ADD COLUMN \"destination_id\" bigint NULL REFERENCES \"djstripe_bankaccount\" (\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED;\nCREATE INDEX \"djstripe_payout_destination_id_a5fa55c2\" ON \"djstripe_payout\" (\"destination_id\");\n\n\n\n--\n-- Raw SQL operation\n--\nUPDATE djstripe_payout SET destination_id = (select djstripe_id from djstripe_bankaccount where djstripe_bankaccount.id = djstripe_payout. destination_clone_id)\nWHERE EXISTS(select * from djstripe_bankaccount where djstripe_bankaccount.id = djstripe_payout.destination_clone_id);\n\n\n--\n-- Add field destination_clone to payout\n--\nCREATE TABLE \"new__djstripe_payout\" (\"djstripe_id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"id\" varchar(255) NOT NULL UNIQUE, \"livemode\" bool NULL, \"created\" datetime NULL, \"metadata\" text NULL CHECK ((JSON_VALID(\"metadata\") OR \"metadata\" IS NULL)), \"description\" text NULL, \"djstripe_created\" datetime NOT NULL, \"djstripe_updated\" datetime NOT NULL, \"amount\" decimal NOT NULL, \"arrival_date\" datetime NOT NULL, \"currency\" varchar(3) NOT NULL, \"failure_code\" varchar(32) NOT NULL, \"failure_message\" text NOT NULL, \"method\" varchar(8) NOT NULL, \"statement_descriptor\" varchar(255) NOT NULL, \"status\" varchar(10) NOT NULL, \"type\" varchar(12) NOT NULL, \"destination_id\" bigint NULL REFERENCES \"djstripe_bankaccount\" (\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED, \"balance_transaction_id\" bigint NULL REFERENCES \"djstripe_balancetransaction\" (\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED, \"failure_balance_transaction_id\" bigint NULL REFERENCES \"djstripe_balancetransaction\" (\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED, \"djstripe_owner_account_id\" bigint NULL REFERENCES \"djstripe_account\" (\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED, \"automatic\" bool NOT NULL, \"source_type\" varchar(12) NOT NULL);\nINSERT INTO \"new__djstripe_payout\" (\"djstripe_id\", \"id\", \"livemode\", \"created\", \"metadata\", \"description\", \"djstripe_created\", \"djstripe_updated\", \"amount\", \"arrival_date\", \"currency\", \"failure_code\", \"failure_message\", \"method\", \"statement_descriptor\", \"status\", \"type\", \"destination_id\", \"balance_transaction_id\", \"failure_balance_transaction_id\", \"djstripe_owner_account_id\", \"automatic\", \"source_type\") SELECT \"djstripe_id\", \"id\", \"livemode\", \"created\", \"metadata\", \"description\", \"djstripe_created\", \"djstripe_updated\", \"amount\", \"arrival_date\", \"currency\", \"failure_code\", \"failure_message\", \"method\", \"statement_descriptor\", \"status\", \"type\", \"destination_id\", \"balance_transaction_id\", \"failure_balance_transaction_id\", \"djstripe_owner_account_id\", \"automatic\", \"source_type\" FROM \"djstripe_payout\";\nDROP TABLE \"djstripe_payout\";\nALTER TABLE \"new__djstripe_payout\" RENAME TO \"djstripe_payout\";\nCREATE INDEX \"djstripe_payout_destination_id_a5fa55c2\" ON \"djstripe_payout\" (\"destination_id\");\nCREATE INDEX \"djstripe_payout_balance_transaction_id_a9393fb6\" ON \"djstripe_payout\" (\"balance_transaction_id\");\nCREATE INDEX \"djstripe_payout_failure_balance_transaction_id_77d442db\" ON \"djstripe_payout\" (\"failure_balance_transaction_id\");\nCREATE INDEX \"djstripe_payout_djstripe_owner_account_id_8aac4e8e\" ON \"djstripe_payout\" (\"djstripe_owner_account_id\");\n"
  },
  {
    "path": "djstripe/migrations/sql/migrate_sqlite_forward.sql",
    "content": "--\n-- Add field destination_clone to payout\n--\nALTER TABLE \"djstripe_payout\" ADD COLUMN \"destination_clone_id\" varchar(255) NULL REFERENCES \"djstripe_djstripepaymentmethod\" (\"id\") DEFERRABLE INITIALLY DEFERRED;\nCREATE INDEX \"djstripe_payout_destination_clone_id_ff0fec04\" ON \"djstripe_payout\" (\"destination_clone_id\");\n\n\n--\n-- Raw SQL operation\n--\nUPDATE djstripe_payout SET destination_clone_id = (SELECT id from djstripe_bankaccount WHERE djstripe_bankaccount.djstripe_id = djstripe_payout.destination_id)\nWHERE EXISTS(SELECT * from djstripe_bankaccount WHERE djstripe_bankaccount.djstripe_id = djstripe_payout.destination_id);\n\n\n--\n-- Rename field destination_clone on payout to destination\n--\nCREATE TABLE \"new__djstripe_payout\" (\"destination_id\" varchar(255) NULL REFERENCES \"djstripe_djstripepaymentmethod\" (\"id\") DEFERRABLE INITIALLY DEFERRED, \"djstripe_id\" integer NOT NULL PRIMARY KEY AUTOINCREMENT, \"id\" varchar(255) NOT NULL UNIQUE, \"livemode\" bool NULL, \"created\" datetime NULL, \"metadata\" text NULL CHECK ((JSON_VALID(\"metadata\") OR \"metadata\" IS NULL)), \"description\" text NULL, \"djstripe_created\" datetime NOT NULL, \"djstripe_updated\" datetime NOT NULL, \"amount\" decimal NOT NULL, \"arrival_date\" datetime NOT NULL, \"currency\" varchar(3) NOT NULL, \"failure_code\" varchar(32) NOT NULL, \"failure_message\" text NOT NULL, \"method\" varchar(8) NOT NULL, \"statement_descriptor\" varchar(255) NOT NULL, \"status\" varchar(10) NOT NULL, \"type\" varchar(12) NOT NULL, \"balance_transaction_id\" bigint NULL REFERENCES \"djstripe_balancetransaction\" (\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED, \"failure_balance_transaction_id\" bigint NULL REFERENCES \"djstripe_balancetransaction\" (\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED, \"djstripe_owner_account_id\" bigint NULL REFERENCES \"djstripe_account\" (\"djstripe_id\") DEFERRABLE INITIALLY DEFERRED, \"automatic\" bool NOT NULL, \"source_type\" varchar(12) NOT NULL);\nINSERT INTO \"new__djstripe_payout\" (\"djstripe_id\", \"id\", \"livemode\", \"created\", \"metadata\", \"description\", \"djstripe_created\", \"djstripe_updated\", \"amount\", \"arrival_date\", \"currency\", \"failure_code\", \"failure_message\", \"method\", \"statement_descriptor\", \"status\", \"type\", \"balance_transaction_id\", \"failure_balance_transaction_id\", \"djstripe_owner_account_id\", \"automatic\", \"source_type\", \"destination_id\") SELECT \"djstripe_id\", \"id\", \"livemode\", \"created\", \"metadata\", \"description\", \"djstripe_created\", \"djstripe_updated\", \"amount\", \"arrival_date\", \"currency\", \"failure_code\", \"failure_message\", \"method\", \"statement_descriptor\", \"status\", \"type\", \"balance_transaction_id\", \"failure_balance_transaction_id\", \"djstripe_owner_account_id\", \"automatic\", \"source_type\", \"destination_clone_id\" FROM \"djstripe_payout\";\nDROP TABLE \"djstripe_payout\";\nALTER TABLE \"new__djstripe_payout\" RENAME TO \"djstripe_payout\";\nCREATE INDEX \"djstripe_payout_destination_id_a5fa55c2\" ON \"djstripe_payout\" (\"destination_id\");\nCREATE INDEX \"djstripe_payout_balance_transaction_id_a9393fb6\" ON \"djstripe_payout\" (\"balance_transaction_id\");\nCREATE INDEX \"djstripe_payout_failure_balance_transaction_id_77d442db\" ON \"djstripe_payout\" (\"failure_balance_transaction_id\");\nCREATE INDEX \"djstripe_payout_djstripe_owner_account_id_8aac4e8e\" ON \"djstripe_payout\" (\"djstripe_owner_account_id\");\n"
  },
  {
    "path": "djstripe/mixins.py",
    "content": "\"\"\"\ndj-stripe mixins\n\"\"\"\nimport sys\nimport traceback\n\nfrom .models import Customer, Plan\nfrom .settings import djstripe_settings\n\n\nclass PaymentsContextMixin:\n    \"\"\"Adds plan context to a view.\"\"\"\n\n    def get_context_data(self, **kwargs):\n        \"\"\"Inject STRIPE_PUBLIC_KEY and plans into context_data.\"\"\"\n        context = super().get_context_data(**kwargs)\n        context.update(\n            {\n                \"STRIPE_PUBLIC_KEY\": djstripe_settings.STRIPE_PUBLIC_KEY,\n                \"plans\": Plan.objects.all(),\n            }\n        )\n        return context\n\n\nclass SubscriptionMixin(PaymentsContextMixin):\n    \"\"\"Adds customer subscription context to a view.\"\"\"\n\n    def get_context_data(self, *args, **kwargs):\n        \"\"\"Inject is_plans_plural and customer into context_data.\"\"\"\n        context = super().get_context_data(**kwargs)\n        context[\"is_plans_plural\"] = Plan.objects.count() > 1\n        context[\"customer\"], _created = Customer.get_or_create(\n            subscriber=djstripe_settings.subscriber_request_callback(self.request)\n        )\n        context[\"subscription\"] = context[\"customer\"].subscription\n        return context\n\n\nclass VerbosityAwareOutputMixin:\n    \"\"\"\n    A mixin class to provide verbosity aware output functions for management commands.\n    \"\"\"\n\n    def set_verbosity(self, options):\n        \"\"\"Set the verbosity based off the passed in options.\"\"\"\n        self.verbosity = options[\"verbosity\"]\n\n    def output(self, arg):\n        \"\"\"Print if output is not silenced.\"\"\"\n        if self.verbosity > 0:\n            print(arg)\n\n    def verbose_output(self, arg):\n        \"\"\"Print only if output is verbose.\"\"\"\n        if self.verbosity > 1:\n            print(arg)\n\n    def verbose_traceback(self):\n        \"\"\"Print out a traceback if the output is verbose.\"\"\"\n        if self.verbosity > 1:\n            exc_type, exc_value, exc_traceback = sys.exc_info()\n            traceback.print_exception(exc_type, exc_value, exc_traceback)\n"
  },
  {
    "path": "djstripe/models/__init__.py",
    "content": "from .account import Account\nfrom .api import APIKey\nfrom .base import IdempotencyKey, StripeModel\nfrom .billing import (\n    Coupon,\n    Discount,\n    Invoice,\n    InvoiceItem,\n    InvoiceOrLineItem,\n    LineItem,\n    Plan,\n    ShippingRate,\n    Subscription,\n    SubscriptionItem,\n    SubscriptionSchedule,\n    TaxCode,\n    TaxId,\n    TaxRate,\n    UpcomingInvoice,\n    UsageRecord,\n    UsageRecordSummary,\n)\nfrom .checkout import Session\nfrom .connect import (\n    ApplicationFee,\n    ApplicationFeeRefund,\n    CountrySpec,\n    Transfer,\n    TransferReversal,\n)\nfrom .core import (\n    BalanceTransaction,\n    Charge,\n    Customer,\n    Dispute,\n    Event,\n    File,\n    FileLink,\n    FileUpload,\n    Mandate,\n    PaymentIntent,\n    Payout,\n    Price,\n    Product,\n    Refund,\n    SetupIntent,\n)\nfrom .orders import Order\nfrom .payment_methods import (\n    BankAccount,\n    Card,\n    DjstripePaymentMethod,\n    PaymentMethod,\n    Source,\n)\nfrom .sigma import ScheduledQueryRun\nfrom .webhooks import WebhookEndpoint, WebhookEventTrigger\n\n__all__ = [\n    \"Account\",\n    \"APIKey\",\n    \"ApplicationFee\",\n    \"ApplicationFeeRefund\",\n    \"BalanceTransaction\",\n    \"BankAccount\",\n    \"Card\",\n    \"Charge\",\n    \"CountrySpec\",\n    \"Coupon\",\n    \"Customer\",\n    \"Discount\",\n    \"Dispute\",\n    \"DjstripePaymentMethod\",\n    \"Event\",\n    \"File\",\n    \"FileLink\",\n    \"FileUpload\",\n    \"IdempotencyKey\",\n    \"Invoice\",\n    \"InvoiceItem\",\n    \"LineItem\",\n    \"InvoiceOrLineItem\",\n    \"Mandate\",\n    \"Order\",\n    \"PaymentIntent\",\n    \"PaymentMethod\",\n    \"Payout\",\n    \"Plan\",\n    \"Price\",\n    \"Product\",\n    \"Refund\",\n    \"ShippingRate\",\n    \"ScheduledQueryRun\",\n    \"SetupIntent\",\n    \"Session\",\n    \"Source\",\n    \"StripeModel\",\n    \"Subscription\",\n    \"SubscriptionItem\",\n    \"SubscriptionSchedule\",\n    \"TaxCode\",\n    \"TaxId\",\n    \"TaxRate\",\n    \"Transfer\",\n    \"TransferReversal\",\n    \"UpcomingInvoice\",\n    \"UsageRecord\",\n    \"UsageRecordSummary\",\n    \"WebhookEndpoint\",\n    \"WebhookEventTrigger\",\n]\n"
  },
  {
    "path": "djstripe/models/account.py",
    "content": "import stripe\nfrom django.db import models, transaction\n\nfrom .. import enums\nfrom ..enums import APIKeyType\nfrom ..fields import JSONField, StripeCurrencyCodeField, StripeEnumField\nfrom ..settings import djstripe_settings\nfrom .api import APIKey, get_api_key_details_by_prefix\nfrom .base import StripeModel, logger\n\n\nclass Account(StripeModel):\n    \"\"\"\n    This is an object representing a Stripe account.\n\n    You can retrieve it to see properties on the account like its\n    current e-mail address or if the account is enabled yet to make live charges.\n\n    Stripe documentation: https://stripe.com/docs/api/accounts?lang=python\n    \"\"\"\n\n    stripe_class = stripe.Account\n    business_profile = JSONField(\n        null=True, blank=True, help_text=\"Optional information related to the business.\"\n    )\n    business_type = StripeEnumField(\n        enum=enums.BusinessType, default=\"\", blank=True, help_text=\"The business type.\"\n    )\n    charges_enabled = models.BooleanField(\n        help_text=\"Whether the account can create live charges\"\n    )\n    country = models.CharField(max_length=2, help_text=\"The country of the account\")\n    company = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Information about the company or business. \"\n            \"This field is null unless business_type is set to company.\"\n        ),\n    )\n    default_currency = StripeCurrencyCodeField(\n        help_text=\"The currency this account has chosen to use as the default\"\n    )\n    details_submitted = models.BooleanField(\n        help_text=(\n            \"Whether account details have been submitted. \"\n            \"Standard accounts cannot receive payouts before this is true.\"\n        )\n    )\n    email = models.CharField(\n        max_length=255, help_text=\"The primary user's email address.\"\n    )\n    # TODO external_accounts = ...\n    individual = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Information about the person represented by the account. \"\n            \"This field is null unless business_type is set to individual.\"\n        ),\n    )\n    payouts_enabled = models.BooleanField(\n        null=True, help_text=\"Whether Stripe can send payouts to this account\"\n    )\n    product_description = models.CharField(\n        max_length=255,\n        default=\"\",\n        blank=True,\n        help_text=\"Internal-only description of the product sold or service provided \"\n        \"by the business. It's used by Stripe for risk and underwriting purposes.\",\n    )\n    requirements = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Information about the requirements for the account, \"\n        \"including what information needs to be collected, and by when.\",\n    )\n    settings = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Account options for customizing how the account functions within Stripe.\"\n        ),\n    )\n    type = StripeEnumField(enum=enums.AccountType, help_text=\"The Stripe account type.\")\n    tos_acceptance = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Details on the acceptance of the Stripe Services Agreement\",\n    )\n\n    def get_stripe_dashboard_url(self) -> str:\n        \"\"\"Get the stripe dashboard url for this object.\"\"\"\n        return (\n            f\"https://dashboard.stripe.com/{self.id}/\"\n            f\"{'test/' if not self.livemode else ''}dashboard\"\n        )\n\n    @property\n    def default_api_key(self) -> str:\n        return self.get_default_api_key()\n\n    def get_default_api_key(self, livemode: bool = None) -> str:\n        if livemode is None:\n            livemode = self.livemode\n            api_key = APIKey.objects.filter(\n                djstripe_owner_account=self, type=APIKeyType.secret\n            ).first()\n        else:\n            api_key = APIKey.objects.filter(\n                djstripe_owner_account=self, type=APIKeyType.secret, livemode=livemode\n            ).first()\n\n        if api_key:\n            return api_key.secret\n        return djstripe_settings.get_default_api_key(livemode)\n\n    @property\n    def business_url(self) -> str:\n        \"\"\"\n        The business's publicly available website.\n        \"\"\"\n        if self.business_profile:\n            return self.business_profile.get(\"url\", \"\")\n        return \"\"\n\n    @classmethod\n    def get_default_account(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY):\n        # As of API version 2020-03-02, there is no permission that can allow\n        # restricted keys to call GET /v1/account\n        if djstripe_settings.STRIPE_SECRET_KEY.startswith(\"rk_\"):\n            return None\n\n        account_data = cls.stripe_class.retrieve(\n            api_key=api_key, stripe_version=djstripe_settings.STRIPE_API_VERSION\n        )\n\n        return cls._get_or_create_from_stripe_object(account_data, api_key=api_key)[0]\n\n    @classmethod\n    def get_or_retrieve_for_api_key(cls, api_key: str):\n        with transaction.atomic():\n            apikey_instance, _ = APIKey.objects.get_or_create_by_api_key(api_key)\n            if not apikey_instance.djstripe_owner_account:\n                apikey_instance.refresh_account()\n\n            return apikey_instance.djstripe_owner_account\n\n    def __str__(self):\n        settings = self.settings or {}\n        business_profile = self.business_profile or {}\n        return (\n            settings.get(\"dashboard\", {}).get(\"display_name\")\n            or business_profile.get(\"name\")\n            or super().__str__()\n        )\n\n    def api_reject(self, api_key=None, stripe_account=None, **kwargs):\n        \"\"\"\n        Call the stripe API's reject operation for Account model\n\n        :param api_key: The api key to use for this request.\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n        api_key = api_key or self.default_api_key\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        return self.stripe_class.reject(\n            self.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        )\n\n    @classmethod\n    def _create_from_stripe_object(\n        cls,\n        data,\n        current_ids=None,\n        pending_relations=None,\n        save=True,\n        stripe_account=None,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    ):\n        \"\"\"\n        Set the stripe_account to the id of the Account instance being created.\n\n        This ensures that the foreign-key relations that may exist in stripe are\n        fetched using the appropriate connected account ID.\n        \"\"\"\n        return super()._create_from_stripe_object(\n            data=data,\n            current_ids=current_ids,\n            pending_relations=pending_relations,\n            save=save,\n            stripe_account=data[\"id\"] if not stripe_account else stripe_account,\n            api_key=api_key,\n        )\n\n    # \"Special\" handling of the icon and logo fields\n    # Previously available as properties, they moved to\n    # settings.branding in Stripe 2019-02-19.\n    # Currently, they return a File ID\n    @property\n    def branding_icon(self):\n        from ..models.core import File\n\n        id = self.settings.get(\"branding\", {}).get(\"icon\")\n        return File.objects.filter(id=id).first() if id else None\n\n    @property\n    def branding_logo(self):\n        from ..models.core import File\n\n        id = self.settings.get(\"branding\", {}).get(\"logo\")\n        return File.objects.filter(id=id).first() if id else None\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        pending_relations=None,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    ):\n        from ..models.core import File\n\n        super()._attach_objects_post_save_hook(\n            cls, data, pending_relations=pending_relations, api_key=api_key\n        )\n\n        # set the livemode if not returned by data\n        if \"livemode\" not in data.keys() and self.djstripe_owner_account is not None:\n            # Platform Account\n            if self == self.djstripe_owner_account:\n                self.livemode = None\n            else:\n                # Connected Account\n                _, self.livemode = get_api_key_details_by_prefix(api_key)\n\n        # save the updates\n        self.save()\n\n        # Retrieve and save the Files in the settings.branding object.\n        for field in \"icon\", \"logo\":\n            file_upload_id = self.settings and self.settings.get(\"branding\", {}).get(\n                field\n            )\n            if file_upload_id:\n                try:\n                    File.sync_from_stripe_data(\n                        File(id=file_upload_id).api_retrieve(\n                            stripe_account=self.id, api_key=api_key\n                        ),\n                        api_key=api_key,\n                    )\n                except stripe.error.PermissionError:\n                    # No permission to retrieve the data with the key\n                    logger.warning(\n                        f\"Cannot retrieve business branding {field} for acct {self.id} with the key.\"\n                    )\n                except stripe.error.InvalidRequestError as e:\n                    if \"a similar object exists in\" in str(e):\n                        # HACK around a Stripe bug.\n                        # See #830 and commit c09d25f52bfdcf883e9eec0bf6c25af1771a644a\n                        pass\n                    else:\n                        raise\n                except stripe.error.AuthenticationError:\n                    # This may happen if saving an account that has a logo, using\n                    # a different API key to the default.\n                    # OK, concretely, there is a chicken-and-egg problem here.\n                    # But, the logo file object is not a particularly important thing.\n                    # Until we have a better solution, just ignore this error.\n                    pass\n"
  },
  {
    "path": "djstripe/models/api.py",
    "content": "import re\nfrom base64 import b64encode\nfrom uuid import uuid4\n\nfrom django.core.validators import RegexValidator\nfrom django.db import IntegrityError, models, transaction\nfrom django.forms import ValidationError\n\nfrom ..enums import APIKeyType\nfrom ..exceptions import InvalidStripeAPIKey\nfrom ..fields import StripeEnumField\nfrom ..settings import djstripe_settings\nfrom .base import StripeModel\n\n# A regex to validate API key format\nAPI_KEY_REGEX = r\"^(pk|sk|rk)_(test|live)_([a-zA-Z0-9]{24,99})\"\n\n\ndef generate_api_key_id() -> str:\n    b64_id = b64encode(uuid4().bytes).decode()\n    generated_id = b64_id.rstrip(\"=\").replace(\"+\", \"\").replace(\"/\", \"\")\n    return f\"djstripe_mk_{generated_id}\"\n\n\ndef get_api_key_details_by_prefix(api_key: str):\n    sre = re.match(API_KEY_REGEX, api_key)\n    if not sre:\n        raise InvalidStripeAPIKey(f\"Invalid API key: {api_key!r}\")\n\n    key_type = {\n        \"pk\": APIKeyType.publishable,\n        \"sk\": APIKeyType.secret,\n        \"rk\": APIKeyType.restricted,\n    }.get(sre.group(1), \"\")\n    livemode = {\"test\": False, \"live\": True}.get(sre.group(2))\n\n    return key_type, livemode\n\n\nclass APIKeyManager(models.Manager):\n    def get_or_create_by_api_key(self, secret: str):\n        key_type, livemode = get_api_key_details_by_prefix(secret)\n        return super().get_or_create(\n            secret=secret, defaults={\"type\": key_type, \"livemode\": livemode}\n        )\n\n\nclass APIKey(StripeModel):\n    object = \"api_key\"\n\n    id = models.CharField(max_length=255, default=generate_api_key_id, editable=False)\n    type = StripeEnumField(enum=APIKeyType)\n    name = models.CharField(\n        \"Key name\",\n        max_length=100,\n        blank=True,\n        help_text=\"An optional name to identify the key.\",\n    )\n    secret = models.CharField(\n        max_length=128,\n        validators=[RegexValidator(regex=API_KEY_REGEX)],\n        unique=True,\n        help_text=\"The value of the key.\",\n    )\n\n    livemode = models.BooleanField(\n        help_text=\"Whether the key is valid for live or test mode.\"\n    )\n    description = None\n    metadata = None\n    objects = APIKeyManager()\n\n    def get_stripe_dashboard_url(self):\n        return self._get_base_stripe_dashboard_url() + \"apikeys\"\n\n    def __str__(self):\n        return self.name or self.secret_redacted\n\n    def clean(self):\n        if self.livemode is None or self.type is None:\n            try:\n                self.type, self.livemode = get_api_key_details_by_prefix(self.secret)\n            except InvalidStripeAPIKey as e:\n                raise ValidationError(str(e))\n\n    def refresh_account(self, commit=True):\n        from .account import Account\n\n        if self.type != APIKeyType.secret:\n            return\n\n        account_data = Account.stripe_class.retrieve(\n            api_key=self.secret,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n        # NOTE: Do not immediately use _get_or_create_from_stripe_object() here.\n        # Account needs to exist for things to work. Make a stub if necessary.\n        account, created = Account.objects.get_or_create(\n            id=account_data[\"id\"],\n            defaults={\"charges_enabled\": False, \"details_submitted\": False},\n        )\n        if created:\n            # If it's just been created, now we can sync the account.\n            Account.sync_from_stripe_data(account_data, api_key=self.secret)\n        self.djstripe_owner_account = account\n        if commit:\n            try:\n                # for non-existent accounts, due to djstripe_owner_account search for the\n                # accounts themselves, trigerred by this method, the APIKey gets created before this method\n                # can \"commit\". This results in an Integrity Error\n                with transaction.atomic():\n                    self.save()\n            except IntegrityError:\n                pass\n\n    @property\n    def secret_redacted(self) -> str:\n        \"\"\"\n        Returns a redacted version of the secret, suitable for display purposes.\n\n        Same algorithm used on the Stripe dashboard.\n        \"\"\"\n        secret_prefix, _, secret_part = self.secret.rpartition(\"_\")\n        secret_part = secret_part[-4:]\n        return f\"{secret_prefix}_...{secret_part}\"\n"
  },
  {
    "path": "djstripe/models/base.py",
    "content": "import logging\nimport uuid\nfrom datetime import timedelta\nfrom typing import Dict, List, Optional, Type\n\nfrom django.db import IntegrityError, models, transaction\nfrom django.utils import dateformat, timezone\nfrom stripe.api_resources.abstract.api_resource import APIResource\nfrom stripe.error import InvalidRequestError\nfrom stripe.util import convert_to_stripe_object\n\nfrom ..exceptions import ImpossibleAPIRequest\nfrom ..fields import (\n    JSONField,\n    StripeDateTimeField,\n    StripeForeignKey,\n    StripeIdField,\n    StripePercentField,\n)\nfrom ..managers import StripeModelManager\nfrom ..settings import djstripe_settings\nfrom ..utils import get_id_from_stripe_data\n\nlogger = logging.getLogger(__name__)\n\n\nclass StripeBaseModel(models.Model):\n    stripe_class: Type[APIResource] = APIResource\n\n    djstripe_created = models.DateTimeField(auto_now_add=True, editable=False)\n    djstripe_updated = models.DateTimeField(auto_now=True, editable=False)\n\n    class Meta:\n        abstract = True\n\n    @classmethod\n    def api_list(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        \"\"\"\n        Call the stripe API's list operation for this model.\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n\n        See Stripe documentation for accepted kwargs for each object.\n\n        :returns: an iterator over all items in the query\n        \"\"\"\n\n        return cls.stripe_class.list(\n            api_key=api_key,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        ).auto_paging_iter()\n\n\nclass StripeModel(StripeBaseModel):\n    # This must be defined in descendants of this model/mixin\n    # e.g. Event, Charge, Customer, etc.\n    expand_fields: List[str] = []\n    stripe_dashboard_item_name = \"\"\n\n    objects = models.Manager()\n    stripe_objects = StripeModelManager()\n\n    djstripe_id = models.BigAutoField(\n        verbose_name=\"ID\", serialize=False, primary_key=True\n    )\n    id = StripeIdField(unique=True)\n\n    djstripe_owner_account: Optional[StripeForeignKey] = StripeForeignKey(\n        \"djstripe.Account\",\n        on_delete=models.CASCADE,\n        to_field=\"id\",\n        null=True,\n        blank=True,\n        help_text=\"The Stripe Account this object belongs to.\",\n    )\n\n    livemode = models.BooleanField(\n        null=True,\n        default=None,\n        blank=True,\n        help_text=\"Null here indicates that the livemode status is unknown or was \"\n        \"previously unrecorded. Otherwise, this field indicates whether this record \"\n        \"comes from Stripe test mode or live mode operation.\",\n    )\n    created = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"The datetime this object was created in stripe.\",\n    )\n    metadata = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"A set of key/value pairs that you can attach to an object. \"\n        \"It can be useful for storing additional information about an object in \"\n        \"a structured format.\",\n    )\n    description = models.TextField(\n        null=True, blank=True, help_text=\"A description of this object.\"\n    )\n\n    class Meta(StripeBaseModel.Meta):\n        abstract = True\n        get_latest_by = \"created\"\n\n    def _get_base_stripe_dashboard_url(self):\n        owner_path_prefix = (\n            (self.djstripe_owner_account.id + \"/\")\n            if self.djstripe_owner_account\n            else \"\"\n        )\n        suffix = \"test/\" if not self.livemode else \"\"\n        return f\"https://dashboard.stripe.com/{owner_path_prefix}{suffix}\"\n\n    def get_stripe_dashboard_url(self) -> str:\n        \"\"\"Get the stripe dashboard url for this object.\"\"\"\n        if not self.stripe_dashboard_item_name or not self.id:\n            return \"\"\n        else:\n            base_url = self._get_base_stripe_dashboard_url()\n            item = self.stripe_dashboard_item_name\n            return f\"{base_url}{item}/{self.id}\"\n\n    @property\n    def default_api_key(self) -> str:\n        # If the class is abstract (StripeModel), fall back to default key.\n        if not self._meta.abstract:\n            if self.djstripe_owner_account:\n                return self.djstripe_owner_account.get_default_api_key(self.livemode)\n        return djstripe_settings.get_default_api_key(self.livemode)\n\n    def _get_stripe_account_id(self, api_key=None) -> Optional[str]:\n        \"\"\"\n        Call the stripe API's retrieve operation for this model.\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n        from djstripe.models import Account\n\n        api_key = api_key or self.default_api_key\n\n        try:\n            djstripe_owner_account = self.djstripe_owner_account\n            if djstripe_owner_account is not None:\n                return djstripe_owner_account.id\n        except (AttributeError, KeyError, ValueError):\n            pass\n\n        # Get reverse foreign key relations to Account in case we need to\n        # retrieve ourselves using that Account ID.\n        reverse_account_relations = (\n            field\n            for field in self._meta.get_fields(include_parents=True)\n            if field.is_relation\n            and field.one_to_many\n            and field.related_model is Account\n        )\n\n        # Handle case where we have a reverse relation to Account and should pass\n        # that account ID to the retrieve call.\n        for field in reverse_account_relations:\n            # Grab the related object, using the first one we find.\n            reverse_lookup_attr = field.get_accessor_name()\n            try:\n                account = getattr(self, reverse_lookup_attr).first()\n            except ValueError:\n                if isinstance(self, Account):\n                    # return the id if self is the Account model itself.\n                    return self.id\n            else:\n                if account is not None:\n                    return account.id\n\n        return None\n\n    def api_retrieve(self, api_key=None, stripe_account=None):\n        \"\"\"\n        Call the stripe API's retrieve operation for this model.\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        return self.stripe_class.retrieve(\n            id=self.id,\n            api_key=api_key or self.default_api_key,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            expand=self.expand_fields,\n            stripe_account=stripe_account,\n        )\n\n    @classmethod\n    def _api_create(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        \"\"\"\n        Call the stripe API's create operation for this model.\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        \"\"\"\n\n        return cls.stripe_class.create(\n            api_key=api_key,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        )\n\n    def _api_delete(self, api_key=None, stripe_account=None, **kwargs):\n        \"\"\"\n        Call the stripe API's delete operation for this model\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n        api_key = api_key or self.default_api_key\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        return self.stripe_class.delete(\n            self.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        )\n\n    def _api_update(self, api_key=None, stripe_account=None, **kwargs):\n        \"\"\"\n        Call the stripe API's modify operation for this model\n\n        :param api_key: The api key to use for this request.\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n        api_key = api_key or self.default_api_key\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        return self.stripe_class.modify(\n            self.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        )\n\n    @classmethod\n    def _manipulate_stripe_object_hook(cls, data):\n        \"\"\"\n        Gets called by this object's stripe object conversion method just before\n        conversion.\n        Use this to populate custom fields in a StripeModel from stripe data.\n        \"\"\"\n        return data\n\n    @classmethod\n    def _find_owner_account(cls, data, api_key=djstripe_settings.STRIPE_SECRET_KEY):\n        \"\"\"\n        Fetches the Stripe Account (djstripe_owner_account model field)\n        linked to the class, cls.\n        Tries to retreive using the Stripe_account if given.\n        Otherwise uses the api_key.\n        \"\"\"\n        from .account import Account\n\n        # try to fetch by stripe_account. Also takes care of Stripe Connected Accounts\n        if data:\n            # case of Webhook Event Trigger\n            if data.get(\"object\") == \"event\":\n                # if account key exists and has a not null value\n                stripe_account_id = get_id_from_stripe_data(data.get(\"account\"))\n                if stripe_account_id:\n                    return Account._get_or_retrieve(\n                        id=stripe_account_id, api_key=api_key\n                    )\n\n            else:\n                stripe_account = getattr(data, \"stripe_account\", None)\n                stripe_account_id = get_id_from_stripe_data(stripe_account)\n                if stripe_account_id:\n                    return Account._get_or_retrieve(\n                        id=stripe_account_id, api_key=api_key\n                    )\n\n        # try to fetch by the given api_key.\n        return Account.get_or_retrieve_for_api_key(api_key)\n\n    @classmethod\n    def _stripe_object_to_record(\n        cls,\n        data: dict,\n        current_ids=None,\n        pending_relations: list = None,\n        stripe_account: str = None,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    ) -> Dict:\n        \"\"\"\n        This takes an object, as it is formatted in Stripe's current API for our object\n        type. In return, it provides a dict. The dict can be used to create a record or\n        to update a record\n\n        This function takes care of mapping from one field name to another, converting\n        from cents to dollars, converting timestamps, and eliminating unused fields\n        (so that an objects.create() call would not fail).\n\n        :param data: the object, as sent by Stripe. Parsed from JSON, into a dict\n        :param current_ids: stripe ids of objects that are currently being processed\n        :type current_ids: set\n        :param pending_relations: list of tuples of relations to be attached post-save\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :return: All the members from the input, translated, mutated, etc\n        \"\"\"\n        from .webhooks import WebhookEndpoint\n\n        manipulated_data = cls._manipulate_stripe_object_hook(data)\n        if not cls.is_valid_object(manipulated_data):\n            raise ValueError(\n                \"Trying to fit a %r into %r. Aborting.\"\n                % (manipulated_data.get(\"object\", \"\"), cls.__name__)\n            )\n\n        result = {}\n        if current_ids is None:\n            current_ids = set()\n\n        # Iterate over all the fields that we know are related to Stripe,\n        # let each field work its own magic\n        ignore_fields = [\"date_purged\", \"subscriber\"]  # XXX: Customer hack\n\n        # get all forward and reverse relations for given cls\n        for field in cls._meta.get_fields():\n            if field.name.startswith(\"djstripe_\") or field.name in ignore_fields:\n                continue\n\n            # todo add support reverse ManyToManyField sync\n            if isinstance(\n                field, (models.ManyToManyRel, models.ManyToOneRel)\n            ) and not isinstance(field, models.OneToOneRel):\n                # We don't currently support syncing from\n                # reverse side of Many relationship\n                continue\n\n            # todo for ManyToManyField one would also need to handle the case of an intermediate model being used\n            # todo add support ManyToManyField sync\n            if field.many_to_many:\n                # We don't currently support syncing ManyToManyField\n                continue\n\n            # will work for Forward FK and OneToOneField relations and reverse OneToOneField relations\n            if isinstance(field, (models.ForeignKey, models.OneToOneRel)):\n                field_data, skip, is_nulled = cls._stripe_object_field_to_foreign_key(\n                    field=field,\n                    manipulated_data=manipulated_data,\n                    current_ids=current_ids,\n                    pending_relations=pending_relations,\n                    stripe_account=stripe_account,\n                    api_key=api_key,\n                )\n\n                if skip and not is_nulled:\n                    continue\n            else:\n                if hasattr(field, \"stripe_to_db\"):\n                    field_data = field.stripe_to_db(manipulated_data)\n                else:\n                    field_data = manipulated_data.get(field.name)\n\n                if (\n                    isinstance(field, (models.CharField, models.TextField))\n                    and field_data is None\n                ):\n                    # do not add empty secret field for WebhookEndpoint model\n                    # as stripe does not return the secret except for the CREATE call\n                    if cls is WebhookEndpoint and field.name == \"secret\":\n                        continue\n                    else:\n                        # TODO - this applies to StripeEnumField as well, since it\n                        #  sub-classes CharField, is that intentional?\n                        field_data = \"\"\n\n            result[field.name] = field_data\n\n        # For all objects other than the account object itself, get the API key\n        # attached to the request, and get the matching Account for that key.\n        owner_account = cls._find_owner_account(data, api_key=api_key)\n        if owner_account:\n            result[\"djstripe_owner_account\"] = owner_account\n\n        return result\n\n    @classmethod\n    def _stripe_object_field_to_foreign_key(\n        cls,\n        field,\n        manipulated_data,\n        current_ids=None,\n        pending_relations=None,\n        stripe_account=None,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    ):\n        \"\"\"\n        This converts a stripe API field to the dj stripe object it references,\n        so that foreign keys can be connected up automatically.\n\n        :param field:\n        :type field: models.ForeignKey\n        :param manipulated_data:\n        :type manipulated_data: dict\n        :param current_ids: stripe ids of objects that are currently being processed\n        :type current_ids: set\n        :param pending_relations: list of tuples of relations to be attached post-save\n        :type pending_relations: list\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        :return:\n        \"\"\"\n        from djstripe.models import DjstripePaymentMethod, InvoiceOrLineItem\n\n        field_data = None\n        field_name = field.name\n        refetch = False\n        skip = False\n        # a flag to indicate if the given field is null upstream on Stripe\n        is_nulled = False\n\n        if current_ids is None:\n            current_ids = set()\n\n        if issubclass(\n            field.related_model, (StripeModel, DjstripePaymentMethod, InvoiceOrLineItem)\n        ):\n            if field_name in manipulated_data:\n                raw_field_data = manipulated_data.get(field_name)\n\n                # field's value is None. Skip syncing but set as None.\n                # Otherwise nulled FKs sync gets skipped.\n                if not raw_field_data:\n                    is_nulled = True\n                    skip = True\n\n            else:\n                # field does not exist in manipulated_data dict. Skip Syncing\n                skip = True\n                raw_field_data = None\n\n            id_ = get_id_from_stripe_data(raw_field_data)\n\n            if id_ == raw_field_data:\n                # A field like {\"subscription\": \"sub_6lsC8pt7IcFpjA\", ...}\n                refetch = True\n            else:\n                # A field like {\"subscription\": {\"id\": sub_6lsC8pt7IcFpjA\", ...}}\n                pass\n\n            if id_ in current_ids:\n                # this object is currently being fetched, don't try to fetch again,\n                # to avoid recursion instead, record the relation that should be\n                # created once \"object_id\" object exists\n                if pending_relations is not None:\n                    object_id = manipulated_data[\"id\"]\n                    pending_relations.append((object_id, field, id_))\n                skip = True\n\n            # sync only if field exists and is not null\n            if not skip and not is_nulled:\n                # add the id of the current object to the list\n                # of ids being processed.\n                # This will avoid infinite recursive syncs in case a relatedmodel\n                # requests the same object\n                current_ids.add(id_)\n\n                try:\n                    (\n                        field_data,\n                        _,\n                    ) = field.related_model._get_or_create_from_stripe_object(\n                        manipulated_data,\n                        field_name,\n                        refetch=refetch,\n                        current_ids=current_ids,\n                        pending_relations=pending_relations,\n                        stripe_account=stripe_account,\n                        api_key=api_key,\n                    )\n                except ImpossibleAPIRequest:\n                    # Found to happen in the following situation:\n                    # Customer has a `default_source` set to a `card_` object,\n                    # and neither the Customer nor the Card are present in db.\n                    # This skip is a hack, but it will prevent a crash.\n                    skip = True\n\n                # Remove the id of the current object from the list\n                # after it has been created or retrieved\n                current_ids.remove(id_)\n\n        else:\n            # eg PaymentMethod, handled in hooks\n            skip = True\n\n        return field_data, skip, is_nulled\n\n    @classmethod\n    def is_valid_object(cls, data):\n        \"\"\"\n        Returns whether the data is a valid object for the class\n        \"\"\"\n        # .OBJECT_NAME will not exist on the base type itself\n        object_name: str = getattr(cls.stripe_class, \"OBJECT_NAME\", \"\")\n        if not object_name:\n            return False\n        return data and data.get(\"object\") == object_name\n\n    def _attach_objects_hook(\n        self, cls, data, api_key=djstripe_settings.STRIPE_SECRET_KEY, current_ids=None\n    ):\n        \"\"\"\n        Gets called by this object's create and sync methods just before save.\n        Use this to populate fields before the model is saved.\n\n        :param cls: The target class for the instantiated object.\n        :param data: The data dictionary received from the Stripe API.\n        :type data: dict\n        :param current_ids: stripe ids of objects that are currently being processed\n        :type current_ids: set\n        \"\"\"\n\n        pass\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        pending_relations=None,\n    ):\n        \"\"\"\n        Gets called by this object's create and sync methods just after save.\n        Use this to populate fields after the model is saved.\n\n        :param cls: The target class for the instantiated object.\n        :param data: The data dictionary received from the Stripe API.\n        :type data: dict\n        \"\"\"\n\n        unprocessed_pending_relations = []\n        if pending_relations is not None:\n            for post_save_relation in pending_relations:\n                object_id, field, id_ = post_save_relation\n\n                if self.id == id_:\n                    # the target instance now exists\n                    target = field.model.objects.get(id=object_id)\n                    setattr(target, field.name, self)\n                    if isinstance(field, models.OneToOneRel):\n                        # this is a reverse relationship, so the relation exists on self\n                        self.save()\n                    else:\n                        # this is a forward relation on the target,\n                        # so we need to save it\n                        target.save()\n\n                        # reload so that indirect relations back to this object\n                        # eg self.charge.invoice = self are set\n                        # TODO - reverse the field reference here to avoid hitting the DB?\n                        self.refresh_from_db()\n                else:\n                    unprocessed_pending_relations.append(post_save_relation)\n\n            if len(pending_relations) != len(unprocessed_pending_relations):\n                # replace in place so passed in list is updated in calling method\n                pending_relations[:] = unprocessed_pending_relations\n\n    @classmethod\n    def _create_from_stripe_object(\n        cls,\n        data,\n        current_ids=None,\n        pending_relations=None,\n        save=True,\n        stripe_account=None,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    ):\n        \"\"\"\n        Instantiates a model instance using the provided data object received\n        from Stripe, and saves it to the database if specified.\n\n        :param data: The data dictionary received from the Stripe API.\n        :type data: dict\n        :param current_ids: stripe ids of objects that are currently being processed\n        :type current_ids: set\n        :param pending_relations: list of tuples of relations to be attached post-save\n        :type pending_relations: list\n        :param save: If True, the object is saved after instantiation.\n        :type save: bool\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        :returns: The instantiated object.\n        \"\"\"\n        stripe_data = cls._stripe_object_to_record(\n            data,\n            current_ids=current_ids,\n            pending_relations=pending_relations,\n            stripe_account=stripe_account,\n            api_key=api_key,\n        )\n        try:\n            id_ = get_id_from_stripe_data(stripe_data)\n            if id_ is not None:\n                instance = cls.stripe_objects.get(id=id_)\n            else:\n                # Raise error on purpose to resume the _create_from_stripe_object flow\n                raise cls.DoesNotExist\n\n        except cls.DoesNotExist:\n            # try to create iff instance doesn't already exist in the DB\n            # TODO dictionary unpacking will not work if cls has any ManyToManyField\n            instance = cls(**stripe_data)\n\n            instance._attach_objects_hook(\n                cls, data, api_key=api_key, current_ids=current_ids\n            )\n\n            if save:\n                instance.save()\n\n            instance._attach_objects_post_save_hook(\n                cls, data, api_key=api_key, pending_relations=pending_relations\n            )\n\n        return instance\n\n    @classmethod\n    def _get_or_create_from_stripe_object(\n        cls,\n        data,\n        field_name=\"id\",\n        refetch=True,\n        current_ids=None,\n        pending_relations=None,\n        save=True,\n        stripe_account=None,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    ):\n        \"\"\"\n\n        :param data:\n        :param field_name:\n        :param refetch:\n        :param current_ids: stripe ids of objects that are currently being processed\n        :type current_ids: set\n        :param pending_relations: list of tuples of relations to be attached post-save\n        :type pending_relations: list\n        :param save:\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        :return:\n        :rtype: cls, bool\n        \"\"\"\n        field = data.get(field_name)\n        is_nested_data = field_name != \"id\"\n        should_expand = False\n\n        if pending_relations is None:\n            pending_relations = []\n\n        id_ = get_id_from_stripe_data(field)\n\n        if not field:\n            # An empty field - We need to return nothing here because there is\n            # no way of knowing what needs to be fetched!\n            raise RuntimeError(\n                f\"dj-stripe encountered an empty field {cls.__name__}.{field_name} = {field}\"\n            )\n        elif id_ == field:\n            # A field like {\"subscription\": \"sub_6lsC8pt7IcFpjA\", ...}\n            # We'll have to expand if the field is not \"id\" (= is nested)\n            should_expand = is_nested_data\n        else:\n            # A field like {\"subscription\": {\"id\": sub_6lsC8pt7IcFpjA\", ...}}\n            data = field\n\n        try:\n            return cls.stripe_objects.get(id=id_), False\n        except cls.DoesNotExist:\n            if is_nested_data and refetch:\n                # This is what `data` usually looks like:\n                # {\"id\": \"cus_XXXX\", \"default_source\": \"card_XXXX\"}\n                # Leaving the default field_name (\"id\") will get_or_create the customer.\n                # If field_name=\"default_source\", we get_or_create the card instead.\n                cls_instance = cls(id=id_)\n                try:\n                    data = cls_instance.api_retrieve(\n                        stripe_account=stripe_account, api_key=api_key\n                    )\n                except InvalidRequestError as e:\n                    if \"a similar object exists in\" in str(e):\n                        # HACK around a Stripe bug.\n                        # When a File is retrieved from the Account object,\n                        # a mismatch between live and test mode is possible depending\n                        # on whether the file (usually the logo) was uploaded in live\n                        # or test. Reported to Stripe in August 2020.\n                        # Context: https://github.com/dj-stripe/dj-stripe/issues/830\n                        pass\n                    elif \"No such PaymentMethod:\" in str(e):\n                        # payment methods (card_… etc) can be irretrievably deleted,\n                        # but still present during sync. For example, if a refund is\n                        # issued on a charge whose payment method has been deleted.\n                        return None, False\n                    else:\n                        raise\n                should_expand = False\n\n        # The next thing to happen will be the \"create from stripe object\" call.\n        # At this point, if we don't have data to start with (field is a str),\n        # *and* we didn't refetch by id, then `should_expand` is True and we\n        # don't have the data to actually create the object.\n        # If this happens when syncing Stripe data, it's a djstripe bug. Report it!\n        if should_expand:\n            raise ValueError(f\"No data to create {cls.__name__} from {field_name}\")\n\n        try:\n            # We wrap the `_create_from_stripe_object` in a transaction to\n            # avoid TransactionManagementError on subsequent queries in case\n            # of the IntegrityError catch below. See PR #903\n            with transaction.atomic():\n                return (\n                    cls._create_from_stripe_object(\n                        data,\n                        current_ids=current_ids,\n                        pending_relations=pending_relations,\n                        save=save,\n                        stripe_account=stripe_account,\n                        api_key=api_key,\n                    ),\n                    True,\n                )\n        except IntegrityError:\n            # Handle the race condition that something else created the object\n            # after the `get` and before `_create_from_stripe_object`.\n            # This is common during webhook handling, since Stripe sends\n            # multiple webhook events simultaneously,\n            # each of which will cause recursive syncs. See issue #429\n            return cls.stripe_objects.get(id=id_), False\n\n    @classmethod\n    def _stripe_object_to_customer(\n        cls,\n        target_cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        current_ids=None,\n    ):\n        \"\"\"\n        Search the given manager for the Customer matching this object's\n        ``customer`` field.\n        :param target_cls: The target class\n        :type target_cls: Customer\n        :param data: stripe object\n        :type data: dict\n        :param current_ids: stripe ids of objects that are currently being processed\n        :type current_ids: set\n        \"\"\"\n\n        if \"customer\" in data and data[\"customer\"]:\n            return target_cls._get_or_create_from_stripe_object(\n                data, \"customer\", current_ids=current_ids, api_key=api_key\n            )[0]\n\n    @classmethod\n    def _stripe_object_to_default_tax_rates(\n        cls, target_cls, data, api_key=djstripe_settings.STRIPE_SECRET_KEY\n    ):\n        \"\"\"\n        Retrieves TaxRates for a Subscription or Invoice\n        :param target_cls:\n        :param data:\n        :param instance:\n        :type instance: Union[djstripe.models.Invoice, djstripe.models.Subscription]\n        :return:\n        \"\"\"\n        tax_rates = []\n\n        for tax_rate_data in data.get(\"default_tax_rates\", []):\n            tax_rate, _ = target_cls._get_or_create_from_stripe_object(\n                tax_rate_data, refetch=False, api_key=api_key\n            )\n            tax_rates.append(tax_rate)\n\n        return tax_rates\n\n    @classmethod\n    def _stripe_object_to_tax_rates(\n        cls, target_cls, data, api_key=djstripe_settings.STRIPE_SECRET_KEY\n    ):\n        \"\"\"\n        Retrieves TaxRates for a SubscriptionItem or InvoiceItem\n        :param target_cls:\n        :param data:\n        :return:\n        \"\"\"\n        tax_rates = []\n\n        for tax_rate_data in data.get(\"tax_rates\", []):\n            tax_rate, _ = target_cls._get_or_create_from_stripe_object(\n                tax_rate_data, refetch=False, api_key=api_key\n            )\n            tax_rates.append(tax_rate)\n\n        return tax_rates\n\n    @classmethod\n    def _stripe_object_set_total_tax_amounts(\n        cls, target_cls, data, instance, api_key=djstripe_settings.STRIPE_SECRET_KEY\n    ):\n        \"\"\"\n        Set total tax amounts on Invoice instance\n        :param target_cls:\n        :param data:\n        :param instance:\n        :type instance: djstripe.models.Invoice\n        :return:\n        \"\"\"\n        from .billing import TaxRate\n\n        pks = []\n\n        for tax_amount_data in data.get(\"total_tax_amounts\", []):\n            tax_rate_data = tax_amount_data[\"tax_rate\"]\n            if isinstance(tax_rate_data, str):\n                tax_rate_data = {\"tax_rate\": tax_rate_data}\n\n            tax_rate, _ = TaxRate._get_or_create_from_stripe_object(\n                tax_rate_data,\n                field_name=\"tax_rate\",\n                refetch=True,\n                api_key=api_key,\n            )\n            tax_amount, _ = target_cls.objects.update_or_create(\n                invoice=instance,\n                tax_rate=tax_rate,\n                defaults={\n                    \"amount\": tax_amount_data[\"amount\"],\n                    \"inclusive\": tax_amount_data[\"inclusive\"],\n                },\n            )\n\n            pks.append(tax_amount.pk)\n\n        instance.total_tax_amounts.exclude(pk__in=pks).delete()\n\n    @classmethod\n    def _stripe_object_to_line_items(\n        cls, target_cls, data, invoice, api_key=djstripe_settings.STRIPE_SECRET_KEY\n    ):\n        \"\"\"\n        Retrieves LineItems for an invoice.\n\n        If the line item doesn't exist already then it is created.\n\n        If the invoice is an upcoming invoice that doesn't persist to the\n        database (i.e. ephemeral) then the line items are also not saved.\n\n        :param target_cls: The target class to instantiate per line item.\n        :type target_cls:  Type[djstripe.models.LineItem]\n        :param data: The data dictionary received from the Stripe API.\n        :type data: dict\n        :param invoice: The invoice object that should hold the line items.\n        :type invoice: ``djstripe.models.Invoice``\n        \"\"\"\n        lines = data.get(\"lines\")\n        if not lines:\n            return []\n\n        lineitems = []\n        for line in lines.auto_paging_iter():\n            if invoice.id:\n                save = True\n                line.setdefault(\"invoice\", invoice.id)\n\n            else:\n                # Don't save invoice items for ephemeral invoices\n                save = False\n\n            line.setdefault(\"customer\", invoice.customer.id)\n            line.setdefault(\"date\", int(dateformat.format(invoice.created, \"U\")))\n\n            item, _ = target_cls._get_or_create_from_stripe_object(\n                line, refetch=False, save=save, api_key=api_key\n            )\n            lineitems.append(item)\n\n        return lineitems\n\n    @classmethod\n    def _stripe_object_to_subscription_items(\n        cls, target_cls, data, subscription, api_key=djstripe_settings.STRIPE_SECRET_KEY\n    ):\n        \"\"\"\n        Retrieves SubscriptionItems for a subscription.\n\n        If the subscription item doesn't exist already then it is created.\n\n        :param target_cls: The target class to instantiate per invoice item.\n        :type target_cls: Type[djstripe.models.SubscriptionItem]\n        :param data: The data dictionary received from the Stripe API.\n        :type data: dict\n        :param subscription: The subscription object that should hold the items.\n        :type subscription: djstripe.models.Subscription\n        \"\"\"\n\n        items = data.get(\"items\")\n        if not items:\n            subscription.items.delete()\n            return []\n\n        pks = []\n        subscriptionitems = []\n        for item_data in items.auto_paging_iter():\n            item, _ = target_cls._get_or_create_from_stripe_object(\n                item_data, refetch=False, api_key=api_key\n            )\n\n            # sync the SubscriptionItem\n            target_cls.sync_from_stripe_data(item_data, api_key=api_key)\n\n            pks.append(item.pk)\n            subscriptionitems.append(item)\n        subscription.items.exclude(pk__in=pks).delete()\n\n        return subscriptionitems\n\n    @classmethod\n    def _stripe_object_to_refunds(\n        cls, target_cls, data, charge, api_key=djstripe_settings.STRIPE_SECRET_KEY\n    ):\n        \"\"\"\n        Retrieves Refunds for a charge\n        :param target_cls: The target class to instantiate per refund\n        :type target_cls: Type[djstripe.models.Refund]\n        :param data: The data dictionary received from the Stripe API.\n        :type data: dict\n        :param charge: The charge object that refunds are for.\n        :type charge: djstripe.models.Refund\n        :return:\n        \"\"\"\n        stripe_refunds = convert_to_stripe_object(data.get(\"refunds\"))\n\n        if not stripe_refunds:\n            return []\n\n        refund_objs = []\n\n        for refund_data in stripe_refunds.auto_paging_iter():\n            item, _ = target_cls._get_or_create_from_stripe_object(\n                refund_data,\n                refetch=False,\n                api_key=api_key,\n            )\n            refund_objs.append(item)\n\n        return refund_objs\n\n    @classmethod\n    def sync_from_stripe_data(\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        stripe_version=djstripe_settings.STRIPE_API_VERSION,\n    ):\n        \"\"\"\n        Syncs this object from the stripe data provided.\n\n        Foreign keys will also be retrieved and synced recursively.\n\n        :param data: stripe object\n        :type data: dict\n        :rtype: cls\n        \"\"\"\n        current_ids = set()\n        data_id = data.get(\"id\")\n        stripe_account = getattr(data, \"stripe_account\", None)\n\n        if data_id:\n            # stop nested objects from trying to retrieve this object before\n            # initial sync is complete\n            current_ids.add(data_id)\n\n        instance, created = cls._get_or_create_from_stripe_object(\n            data,\n            current_ids=current_ids,\n            stripe_account=stripe_account,\n            api_key=api_key,\n        )\n\n        if not created:\n            record_data = cls._stripe_object_to_record(\n                data, api_key=api_key, stripe_account=stripe_account\n            )\n            for attr, value in record_data.items():\n                setattr(instance, attr, value)\n            instance._attach_objects_hook(\n                cls, data, api_key=api_key, current_ids=current_ids\n            )\n            instance.save()\n            instance._attach_objects_post_save_hook(cls, data, api_key=api_key)\n\n        for field in instance._meta.concrete_fields:\n            if isinstance(field, (StripePercentField, models.UUIDField)):\n                # get rid of cached values\n                delattr(instance, field.name)\n\n        return instance\n\n    @classmethod\n    def _get_or_retrieve(cls, id, stripe_account=None, **kwargs):\n        \"\"\"\n        Retrieve object from the db, if it exists. If it doesn't, query Stripe to fetch\n        the object and sync with the db.\n        \"\"\"\n        try:\n            return cls.objects.get(id=id)\n        except cls.DoesNotExist:\n            pass\n\n        if stripe_account:\n            kwargs[\"stripe_account\"] = str(stripe_account)\n\n        # If no API key is specified, use the default one for the specified livemode\n        # (or if no livemode is specified, the default one altogether)\n        kwargs.setdefault(\n            \"api_key\",\n            djstripe_settings.get_default_api_key(livemode=kwargs.get(\"livemode\")),\n        )\n        data = cls.stripe_class.retrieve(\n            id=id, stripe_version=djstripe_settings.STRIPE_API_VERSION, **kwargs\n        )\n        instance = cls.sync_from_stripe_data(data, api_key=kwargs.get(\"api_key\"))\n        return instance\n\n    def __str__(self):\n        return f\"<id={self.id}>\"\n\n\nclass IdempotencyKey(models.Model):\n    uuid = models.UUIDField(\n        max_length=36, primary_key=True, editable=False, default=uuid.uuid4\n    )\n    action = models.CharField(max_length=100)\n    livemode = models.BooleanField(\n        help_text=\"Whether the key was used in live or test mode.\"\n    )\n    created = models.DateTimeField(auto_now_add=True)\n\n    class Meta:\n        unique_together = (\"action\", \"livemode\")\n\n    def __str__(self):\n        return str(self.uuid)\n\n    @property\n    def is_expired(self) -> bool:\n        return timezone.now() > self.created + timedelta(hours=24)\n"
  },
  {
    "path": "djstripe/models/billing.py",
    "content": "import logging\nimport warnings\nfrom typing import Optional, Union\n\nimport stripe\nfrom django.db import models\nfrom django.utils import timezone\nfrom django.utils.text import format_lazy\nfrom django.utils.translation import gettext_lazy as _\nfrom stripe.error import InvalidRequestError\n\nfrom .. import enums\nfrom ..fields import (\n    InvoiceOrLineItemForeignKey,\n    JSONField,\n    PaymentMethodForeignKey,\n    StripeCurrencyCodeField,\n    StripeDateTimeField,\n    StripeDecimalCurrencyAmountField,\n    StripeEnumField,\n    StripeForeignKey,\n    StripeIdField,\n    StripePercentField,\n    StripeQuantumCurrencyAmountField,\n)\nfrom ..managers import SubscriptionManager\nfrom ..settings import djstripe_settings\nfrom ..utils import QuerySetMock, get_friendly_currency_amount, get_id_from_stripe_data\nfrom .base import StripeModel\nfrom .core import Customer\n\nlogger = logging.getLogger(__name__)\n\n\n# TODO Mimic stripe-python decorator pattern to easily add and expose CRUD operations like create, update, delete etc on models\n# TODO Add Tests\nclass DjstripeInvoiceTotalTaxAmount(models.Model):\n    \"\"\"\n    An internal model that holds the value of elements of Invoice.total_tax_amounts\n\n    Note that this is named with the prefix Djstripe to avoid potential\n    collision with a Stripe API object name.\n    \"\"\"\n\n    invoice = StripeForeignKey(\n        \"Invoice\", on_delete=models.CASCADE, related_name=\"total_tax_amounts\"\n    )\n\n    amount = StripeQuantumCurrencyAmountField(\n        help_text=\"The amount, in cents, of the tax.\"\n    )\n    inclusive = models.BooleanField(\n        help_text=\"Whether this tax amount is inclusive or exclusive.\"\n    )\n    tax_rate = StripeForeignKey(\n        \"TaxRate\",\n        on_delete=models.CASCADE,\n        help_text=\"The tax rate that was applied to get this tax amount.\",\n    )\n\n    class Meta:\n        unique_together = [\"invoice\", \"tax_rate\"]\n\n\n# TODO Add Tests\nclass DjstripeUpcomingInvoiceTotalTaxAmount(models.Model):\n    \"\"\"\n    As per DjstripeInvoiceTotalTaxAmount, except for UpcomingInvoice\n    \"\"\"\n\n    invoice = models.ForeignKey(\n        # Don't define related_name since property is defined in UpcomingInvoice\n        \"UpcomingInvoice\",\n        on_delete=models.CASCADE,\n        related_name=\"+\",\n    )\n\n    amount = StripeQuantumCurrencyAmountField(\n        help_text=\"The amount, in cents, of the tax.\"\n    )\n    inclusive = models.BooleanField(\n        help_text=\"Whether this tax amount is inclusive or exclusive.\"\n    )\n    tax_rate = StripeForeignKey(\n        \"TaxRate\",\n        on_delete=models.CASCADE,\n        help_text=\"The tax rate that was applied to get this tax amount.\",\n    )\n\n    class Meta:\n        unique_together = [\"invoice\", \"tax_rate\"]\n\n\nclass Coupon(StripeModel):\n    \"\"\"\n    A coupon contains information about a percent-off or amount-off discount you might want to apply to a customer.\n    Coupons may be applied to invoices or orders.\n    Coupons do not work with conventional one-off charges.\n\n    Stripe documentation: https://stripe.com/docs/api/coupons?lang=python\n    \"\"\"\n\n    stripe_class = stripe.Coupon\n    expand_fields = [\"applies_to\"]\n    stripe_dashboard_item_name = \"coupons\"\n\n    id = StripeIdField(max_length=500)\n    applies_to = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Contains information about what this coupon applies to.\",\n    )\n    amount_off = StripeDecimalCurrencyAmountField(\n        null=True,\n        blank=True,\n        help_text=\"Amount (as decimal) that will be taken off the subtotal of any \"\n        \"invoices for this customer.\",\n    )\n    currency = StripeCurrencyCodeField(null=True, blank=True)\n    duration = StripeEnumField(\n        enum=enums.CouponDuration,\n        help_text=(\n            \"Describes how long a customer who applies this coupon \"\n            \"will get the discount.\"\n        ),\n        default=enums.CouponDuration.once,\n    )\n    duration_in_months = models.PositiveIntegerField(\n        null=True,\n        blank=True,\n        help_text=\"If `duration` is `repeating`, the number of months \"\n        \"the coupon applies.\",\n    )\n    max_redemptions = models.PositiveIntegerField(\n        null=True,\n        blank=True,\n        help_text=\"Maximum number of times this coupon can be redeemed, in total, \"\n        \"before it is no longer valid.\",\n    )\n    name = models.TextField(\n        max_length=5000,\n        default=\"\",\n        blank=True,\n        help_text=(\n            \"Name of the coupon displayed to customers on for instance invoices \"\n            \"or receipts.\"\n        ),\n    )\n    percent_off = StripePercentField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Percent that will be taken off the subtotal of any invoices for \"\n            \"this customer for the duration of the coupon. \"\n            \"For example, a coupon with percent_off of 50 will make a \"\n            \"$100 invoice $50 instead.\"\n        ),\n    )\n    redeem_by = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"Date after which the coupon can no longer be redeemed. \"\n        \"Max 5 years in the future.\",\n    )\n    times_redeemed = models.PositiveIntegerField(\n        editable=False,\n        default=0,\n        help_text=\"Number of times this coupon has been applied to a customer.\",\n    )\n    # valid = models.BooleanField(editable=False)\n\n    class Meta(StripeModel.Meta):\n        unique_together = (\"id\", \"livemode\")\n\n    def __str__(self):\n        if self.name:\n            return self.name\n        return self.human_readable\n\n    @property\n    def human_readable_amount(self):\n        if self.percent_off:\n            amount = f\"{self.percent_off}%\"\n        elif self.currency:\n            amount = get_friendly_currency_amount(self.amount_off or 0, self.currency)\n        else:\n            amount = \"(invalid amount)\"\n        return f\"{amount} off\"\n\n    @property\n    def human_readable(self):\n        if self.duration == enums.CouponDuration.repeating:\n            if self.duration_in_months == 1:\n                duration = \"for 1 month\"\n            else:\n                duration = f\"for {self.duration_in_months} months\"\n        else:\n            duration = self.duration\n        return f\"{self.human_readable_amount} {duration}\"\n\n\nclass Discount(StripeModel):\n    \"\"\"\n    A discount represents the actual application of a coupon or promotion code.\n    It contains information about when the discount began,\n    when it will end, and what it is applied to.\n\n    Stripe documentation: https://stripe.com/docs/api/discounts\n    \"\"\"\n\n    expand_fields = [\"customer\"]\n    stripe_class = None\n\n    checkout_session = StripeForeignKey(\n        \"Session\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"The Checkout session that this coupon is applied to, if it is applied to a particular session in payment mode. Will not be present for subscription mode.\",\n    )\n    coupon = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Hash describing the coupon applied to create this discount.\",\n    )\n    customer = StripeForeignKey(\n        \"Customer\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"The ID of the customer associated with this discount.\",\n        related_name=\"customer_discounts\",\n    )\n    end = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"If the coupon has a duration of repeating, the date that this discount will end. If the coupon has a duration of once or forever, this attribute will be null.\"\n        ),\n    )\n    invoice = StripeForeignKey(\n        \"Invoice\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"The invoice that the discount’s coupon was applied to, if it was applied directly to a particular invoice.\",\n        related_name=\"invoice_discounts\",\n    )\n    invoice_item = InvoiceOrLineItemForeignKey(\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"The invoice item id (or invoice line item id for invoice line items of type=‘subscription’) that the discount’s coupon was applied to, if it was applied directly to a particular invoice item or invoice line item.\",\n    )\n    promotion_code = models.CharField(\n        max_length=255,\n        blank=True,\n        help_text=\"The promotion code applied to create this discount.\",\n    )\n    start = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=(\"Date that the coupon was applied.\"),\n    )\n    subscription = StripeForeignKey(\n        \"subscription\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"The subscription that this coupon is applied to, if it is applied to a particular subscription.\",\n        related_name=\"subscription_discounts\",\n    )\n\n    @classmethod\n    def is_valid_object(cls, data):\n        \"\"\"\n        Returns whether the data is a valid object for the class\n        \"\"\"\n        return \"object\" in data and data[\"object\"] == \"discount\"\n\n\nclass BaseInvoice(StripeModel):\n    \"\"\"\n    The abstract base model shared by Invoice and UpcomingInvoice\n\n    Note:\n    Most fields are defined on BaseInvoice so they're available to both models.\n    ManyToManyFields are an exception, since UpcomingInvoice doesn't exist in the db.\n    \"\"\"\n\n    stripe_class = stripe.Invoice\n    stripe_dashboard_item_name = \"invoices\"\n    expand_fields = [\"discounts\"]\n\n    account_country = models.CharField(\n        max_length=2,\n        default=\"\",\n        blank=True,\n        help_text=\"The country of the business associated with this invoice, \"\n        \"most often the business creating the invoice.\",\n    )\n    account_name = models.TextField(\n        max_length=5000,\n        blank=True,\n        help_text=\"The public name of the business associated with this invoice, \"\n        \"most often the business creating the invoice.\",\n    )\n    amount_due = StripeDecimalCurrencyAmountField(\n        help_text=\"Final amount due (as decimal) at this time for this invoice. \"\n        \"If the invoice's total is smaller than the minimum charge amount, \"\n        \"for example, or if there is account credit that can be applied to the \"\n        \"invoice, the amount_due may be 0. If there is a positive starting_balance \"\n        \"for the invoice (the customer owes money), the amount_due will also take that \"\n        \"into account. The charge that gets generated for the invoice will be for \"\n        \"the amount specified in amount_due.\"\n    )\n    amount_paid = StripeDecimalCurrencyAmountField(\n        null=True,  # XXX: This is not nullable, but it's a new field\n        help_text=\"The amount, (as decimal), that was paid.\",\n    )\n    amount_remaining = StripeDecimalCurrencyAmountField(\n        null=True,  # XXX: This is not nullable, but it's a new field\n        help_text=\"The amount remaining, (as decimal), that is due.\",\n    )\n    application_fee_amount = StripeDecimalCurrencyAmountField(\n        null=True,\n        blank=True,\n        help_text=\"The fee (as decimal) that will be applied to the invoice and \"\n        \"transferred to the application owner's \"\n        \"Stripe account when the invoice is paid.\",\n    )\n    attempt_count = models.IntegerField(\n        help_text=\"Number of payment attempts made for this invoice, \"\n        \"from the perspective of the payment retry schedule. \"\n        \"Any payment attempt counts as the first attempt, and subsequently \"\n        \"only automatic retries increment the attempt count. \"\n        \"In other words, manual payment attempts after the first attempt do not affect \"\n        \"the retry schedule.\"\n    )\n    attempted = models.BooleanField(\n        default=False,\n        help_text=\"Whether or not an attempt has been made to pay the invoice. \"\n        \"An invoice is not attempted until 1 hour after the ``invoice.created`` \"\n        \"webhook, for example, so you might not want to display that invoice as \"\n        \"unpaid to your users.\",\n    )\n    auto_advance = models.BooleanField(\n        null=True,\n        help_text=\"Controls whether Stripe will perform automatic collection of the \"\n        \"invoice. When false, the invoice's state will not automatically \"\n        \"advance without an explicit action.\",\n    )\n    billing_reason = StripeEnumField(\n        default=\"\",\n        blank=True,\n        enum=enums.InvoiceBillingReason,\n        help_text=\"Indicates the reason why the invoice was created. \"\n        \"subscription_cycle indicates an invoice created by a subscription advancing \"\n        \"into a new period. subscription_create indicates an invoice created due to \"\n        \"creating a subscription. subscription_update indicates an invoice created due \"\n        \"to updating a subscription. subscription is set for all old invoices to \"\n        \"indicate either a change to a subscription or a period advancement. \"\n        \"manual is set for all invoices unrelated to a subscription \"\n        \"(for example: created via the invoice editor). The upcoming value is \"\n        \"reserved for simulated invoices per the upcoming invoice endpoint. \"\n        \"subscription_threshold indicates an invoice created due to a billing \"\n        \"threshold being reached.\",\n    )\n    charge = models.OneToOneField(\n        \"Charge\",\n        on_delete=models.CASCADE,\n        null=True,\n        # we need to use the %(class)s placeholder to avoid related name\n        # clashes between Invoice and UpcomingInvoice\n        related_name=\"latest_%(class)s\",\n        help_text=\"The latest charge generated for this invoice, if any.\",\n    )\n    collection_method = StripeEnumField(\n        enum=enums.InvoiceCollectionMethod,\n        null=True,\n        help_text=(\n            \"When charging automatically, Stripe will attempt to pay this invoice \"\n            \"using the default source attached to the customer. \"\n            \"When sending an invoice, Stripe will email this invoice to the customer \"\n            \"with payment instructions.\"\n        ),\n    )\n    currency = StripeCurrencyCodeField()\n    customer = StripeForeignKey(\n        \"Customer\",\n        on_delete=models.CASCADE,\n        # we need to use the %(class)s placeholder to avoid related name\n        # clashes between Invoice and UpcomingInvoice\n        related_name=\"%(class)ss\",\n        help_text=\"The customer associated with this invoice.\",\n    )\n    customer_address = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"The customer's address. Until the invoice is finalized, this \"\n        \"field will equal customer.address. Once the invoice is finalized, this field \"\n        \"will no longer be updated.\",\n    )\n    customer_email = models.TextField(\n        max_length=5000,\n        blank=True,\n        help_text=\"The customer's email. Until the invoice is finalized, this field \"\n        \"will equal customer.email. Once the invoice is finalized, this field will no \"\n        \"longer be updated.\",\n    )\n    customer_name = models.TextField(\n        max_length=5000,\n        blank=True,\n        help_text=\"The customer's name. Until the invoice is finalized, this field \"\n        \"will equal customer.name. Once the invoice is finalized, this field will no \"\n        \"longer be updated.\",\n    )\n    customer_phone = models.TextField(\n        max_length=5000,\n        blank=True,\n        help_text=\"The customer's phone number. Until the invoice is finalized, \"\n        \"this field will equal customer.phone. Once the invoice is finalized, \"\n        \"this field will no longer be updated.\",\n    )\n    customer_shipping = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"The customer's shipping information. Until the invoice is \"\n        \"finalized, this field will equal customer.shipping. Once the invoice is \"\n        \"finalized, this field will no longer be updated.\",\n    )\n    customer_tax_exempt = StripeEnumField(\n        enum=enums.CustomerTaxExempt,\n        default=\"\",\n        help_text=\"The customer's tax exempt status. Until the invoice is finalized, \"\n        \"this field will equal customer.tax_exempt. Once the invoice is \"\n        \"finalized, this field will no longer be updated.\",\n    )\n    default_payment_method = StripeForeignKey(\n        \"PaymentMethod\",\n        null=True,\n        blank=True,\n        on_delete=models.SET_NULL,\n        related_name=\"+\",\n        help_text=\"Default payment method for the invoice. It must belong to the \"\n        \"customer associated with the invoice. If not set, defaults to the \"\n        \"subscription's default payment method, if any, or to the default payment \"\n        \"method in the customer's invoice settings.\",\n    )\n    # Note: default_tax_rates is handled in the subclasses since it's a\n    # ManyToManyField, otherwise reverse accessors clash\n    discount = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Deprecated! Please use discounts instead. Describes the current discount applied to this \"\n        \"subscription, if there is one. When billing, a discount applied to a \"\n        \"subscription overrides a discount applied on a customer-wide basis.\",\n    )\n    discounts = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"The discounts applied to the invoice. Line item discounts are applied before invoice discounts.\",\n    )\n    due_date = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"The date on which payment for this invoice is due. \"\n            \"This value will be null for invoices where billing=charge_automatically.\"\n        ),\n    )\n    ending_balance = StripeQuantumCurrencyAmountField(\n        null=True,\n        help_text=\"Ending customer balance (in cents) after attempting to pay invoice. \"\n        \"If the invoice has not been attempted yet, this will be null.\",\n    )\n    footer = models.TextField(\n        max_length=5000, blank=True, help_text=\"Footer displayed on the invoice.\"\n    )\n    hosted_invoice_url = models.TextField(\n        max_length=799,\n        default=\"\",\n        blank=True,\n        help_text=\"The URL for the hosted invoice page, which allows customers to view \"\n        \"and pay an invoice. If the invoice has not been frozen yet, \"\n        \"this will be null.\",\n    )\n    invoice_pdf = models.TextField(\n        max_length=799,\n        default=\"\",\n        blank=True,\n        help_text=(\n            \"The link to download the PDF for the invoice. \"\n            \"If the invoice has not been frozen yet, this will be null.\"\n        ),\n    )\n    # TODO: Implement \"lines\" (InvoiceLineItem related_field)\n    next_payment_attempt = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"The time at which payment will next be attempted.\",\n    )\n    number = models.CharField(\n        max_length=64,\n        default=\"\",\n        blank=True,\n        help_text=(\n            \"A unique, identifying string that appears on emails sent to the customer \"\n            \"for this invoice. \"\n            \"This starts with the customer's unique invoice_prefix if it is specified.\"\n        ),\n    )\n    paid = models.BooleanField(\n        default=False,\n        help_text=(\n            \"Whether payment was successfully collected for this invoice. An invoice \"\n            \"can be paid (most commonly) with a charge or with credit from the \"\n            \"customer's account balance.\"\n        ),\n    )\n    payment_intent = models.OneToOneField(\n        \"PaymentIntent\",\n        on_delete=models.CASCADE,\n        null=True,\n        help_text=(\n            \"The PaymentIntent associated with this invoice. \"\n            \"The PaymentIntent is generated when the invoice is finalized, \"\n            \"and can then be used to pay the invoice.\"\n            \"Note that voiding an invoice will cancel the PaymentIntent\"\n        ),\n    )\n    period_end = StripeDateTimeField(\n        help_text=\"End of the usage period during which invoice items were \"\n        \"added to this invoice.\"\n    )\n    period_start = StripeDateTimeField(\n        help_text=\"Start of the usage period during which invoice items were \"\n        \"added to this invoice.\"\n    )\n    post_payment_credit_notes_amount = StripeQuantumCurrencyAmountField(\n        # This is not nullable, but it's a new field\n        null=True,\n        blank=True,\n        help_text=\"Total amount (in cents) of all post-payment credit notes issued \"\n        \"for this invoice.\",\n    )\n    pre_payment_credit_notes_amount = StripeQuantumCurrencyAmountField(\n        # This is not nullable, but it's a new field\n        null=True,\n        blank=True,\n        help_text=\"Total amount (in cents) of all pre-payment credit notes issued \"\n        \"for this invoice.\",\n    )\n    receipt_number = models.CharField(\n        max_length=64,\n        null=True,\n        blank=True,\n        help_text=(\n            \"This is the transaction number that appears on email receipts \"\n            \"sent for this invoice.\"\n        ),\n    )\n    starting_balance = StripeQuantumCurrencyAmountField(\n        help_text=\"Starting customer balance (in cents) before attempting to pay \"\n        \"invoice. If the invoice has not been attempted yet, this will be the \"\n        \"current customer balance.\"\n    )\n    statement_descriptor = models.CharField(\n        max_length=22,\n        default=\"\",\n        blank=True,\n        help_text=\"An arbitrary string to be displayed on your customer's \"\n        \"credit card statement. The statement description may not include <>\\\"' \"\n        \"characters, and will appear on your customer's statement in capital letters. \"\n        \"Non-ASCII characters are automatically stripped. \"\n        \"While most banks display this information consistently, \"\n        \"some may display it incorrectly or not at all.\",\n    )\n    status = StripeEnumField(\n        default=\"\",\n        blank=True,\n        enum=enums.InvoiceStatus,\n        help_text=\"The status of the invoice, one of draft, open, paid, \"\n        \"uncollectible, or void.\",\n    )\n    status_transitions = JSONField(null=True, blank=True)\n    subscription = StripeForeignKey(\n        \"Subscription\",\n        null=True,\n        # we need to use the %(class)s placeholder to avoid related name\n        # clashes between Invoice and UpcomingInvoice\n        related_name=\"%(class)ss\",\n        on_delete=models.SET_NULL,\n        help_text=\"The subscription that this invoice was prepared for, if any.\",\n    )\n    subscription_proration_date = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"Only set for upcoming invoices that preview prorations. \"\n        \"The time used to calculate prorations.\",\n    )\n    subtotal = StripeDecimalCurrencyAmountField(\n        help_text=\"Total (as decimal) of all subscriptions, invoice items, \"\n        \"and prorations on the invoice before any discount or tax is applied.\"\n    )\n    tax = StripeDecimalCurrencyAmountField(\n        null=True,\n        blank=True,\n        help_text=\"The amount (as decimal) of tax included in the total, calculated \"\n        \"from ``tax_percent`` and the subtotal. If no \"\n        \"``tax_percent`` is defined, this value will be null.\",\n    )\n    tax_percent = StripePercentField(\n        null=True,\n        blank=True,\n        help_text=\"This percentage of the subtotal has been added to the total amount \"\n        \"of the invoice, including invoice line items and discounts. \"\n        \"This field is inherited from the subscription's ``tax_percent`` field, \"\n        \"but can be changed before the invoice is paid. This field defaults to null.\",\n    )\n    threshold_reason = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"If billing_reason is set to subscription_threshold this returns \"\n        \"more information on which threshold rules triggered the invoice.\",\n    )\n    total = StripeDecimalCurrencyAmountField(\"Total (as decimal) after discount.\")\n    webhooks_delivered_at = StripeDateTimeField(\n        null=True,\n        help_text=(\n            \"The time at which webhooks for this invoice were successfully delivered \"\n            \"(if the invoice had no webhooks to deliver, this will match `date`). \"\n            \"Invoice payment is delayed until webhooks are delivered, or until all \"\n            \"webhook delivery attempts have been exhausted.\"\n        ),\n    )\n\n    class Meta(StripeModel.Meta):\n        abstract = True\n        ordering = [\"-created\"]\n\n    def __str__(self):\n        invoice_number = self.number or self.receipt_number or self.id\n        amount = get_friendly_currency_amount(self.amount_paid or 0, self.currency)\n        return f\"Invoice #{invoice_number} for {amount} ({self.status})\"\n\n    @classmethod\n    def upcoming(\n        cls,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        customer=None,\n        subscription=None,\n        subscription_plan=None,\n        **kwargs,\n    ) -> Optional[\"UpcomingInvoice\"]:\n        \"\"\"\n        Gets the upcoming preview invoice (singular) for a customer.\n\n        At any time, you can preview the upcoming\n        invoice for a customer. This will show you all the charges that are\n        pending, including subscription renewal charges, invoice item charges,\n        etc. It will also show you any discount that is applicable to the\n        customer. (Source: https://stripe.com/docs/api#upcoming_invoice)\n\n        .. important:: Note that when you are viewing an upcoming invoice,\n            you are simply viewing a preview.\n\n        :param customer: The identifier of the customer whose upcoming invoice \\\n        you'd like to retrieve.\n        :type customer: Customer or string (customer ID)\n        :param coupon: The code of the coupon to apply.\n        :type coupon: str\n        :param subscription: The identifier of the subscription to retrieve an \\\n        invoice for.\n        :type subscription: Subscription or string (subscription ID)\n        :param subscription_plan: If set, the invoice returned will preview \\\n        updating the subscription given to this plan, or creating a new \\\n        subscription to this plan if no subscription is given.\n        :type subscription_plan: Plan or string (plan ID)\n        \"\"\"\n\n        # Convert Customer to id\n        if customer is not None and isinstance(customer, StripeModel):\n            customer = customer.id\n\n        # Convert Subscription to id\n        if subscription is not None and isinstance(subscription, StripeModel):\n            subscription = subscription.id\n\n        # Convert Plan to id\n        if subscription_plan is not None and isinstance(subscription_plan, StripeModel):\n            subscription_plan = subscription_plan.id\n\n        try:\n            upcoming_stripe_invoice = cls.stripe_class.upcoming(\n                api_key=api_key,\n                customer=customer,\n                subscription=subscription,\n                subscription_plan=subscription_plan,\n                stripe_version=djstripe_settings.STRIPE_API_VERSION,\n                **kwargs,\n            )\n        except InvalidRequestError as exc:\n            if str(exc) != \"Nothing to invoice for customer\":\n                raise\n            return None\n\n        # Workaround for \"id\" being missing (upcoming invoices don't persist).\n        upcoming_stripe_invoice[\"id\"] = \"upcoming\"\n\n        return UpcomingInvoice._create_from_stripe_object(\n            upcoming_stripe_invoice,\n            save=False,\n            api_key=api_key,\n        )\n\n    def retry(self):\n        \"\"\"Retry payment on this invoice if it isn't paid.\"\"\"\n\n        if self.status != enums.InvoiceStatus.paid and self.auto_advance:\n            stripe_invoice = self.api_retrieve()\n            updated_stripe_invoice = (\n                stripe_invoice.pay()\n            )  # pay() throws an exception if the charge is not successful.\n            type(self).sync_from_stripe_data(\n                updated_stripe_invoice, api_key=self.default_api_key\n            )\n            return True\n        return False\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        pending_relations=None,\n    ):\n        super()._attach_objects_post_save_hook(\n            cls, data, api_key=api_key, pending_relations=pending_relations\n        )\n\n        # LineItems need a saved invoice because they're associated via a\n        # RelatedManager, so this must be done as part of the post save hook.\n        cls._stripe_object_to_line_items(\n            target_cls=LineItem, data=data, invoice=self, api_key=api_key\n        )\n        # sync every discount\n        for discount in self.discounts:\n            Discount.sync_from_stripe_data(discount, api_key=api_key)\n\n    @property\n    def plan(self) -> Optional[\"Plan\"]:\n        \"\"\"Gets the associated plan for this invoice.\n\n        In order to provide a consistent view of invoices, the plan object\n        should be taken from the first invoice item that has one, rather than\n        using the plan associated with the subscription.\n\n        Subscriptions (and their associated plan) are updated by the customer\n        and represent what is current, but invoice items are immutable within\n        the invoice and stay static/unchanged.\n\n        In other words, a plan retrieved from an invoice item will represent\n        the plan as it was at the time an invoice was issued.  The plan\n        retrieved from the subscription will be the currently active plan.\n\n        :returns: The associated plan for the invoice.\n        \"\"\"\n\n        for invoiceitem in self.invoiceitems.all():\n            if invoiceitem.plan:\n                return invoiceitem.plan\n\n        if self.subscription:\n            return self.subscription.plan\n\n        return None\n\n\nclass Invoice(BaseInvoice):\n    \"\"\"\n    Invoices are statements of what a customer owes for a particular billing\n    period, including subscriptions, invoice items, and any automatic proration\n    adjustments if necessary.\n\n    Once an invoice is created, payment is automatically attempted. Note that\n    the payment, while automatic, does not happen exactly at the time of invoice\n    creation. If you have configured webhooks, the invoice will wait until one\n    hour after the last webhook is successfully sent (or the last webhook times\n    out after failing).\n\n    Any customer credit on the account is applied before determining how much is\n    due for that invoice (the amount that will be actually charged).\n    If the amount due for the invoice is less than 50 cents (the minimum for a\n    charge), we add the amount to the customer's running account balance to be\n    added to the next invoice. If this amount is negative, it will act as a\n    credit to offset the next invoice. Note that the customer account balance\n    does not include unpaid invoices; it only includes balances that need to be\n    taken into account when calculating the amount due for the next invoice.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#invoices\n    \"\"\"\n\n    default_source = PaymentMethodForeignKey(\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        related_name=\"invoices\",\n        help_text=\"The default payment source for the invoice. \"\n        \"It must belong to the customer associated with the invoice and be \"\n        \"in a chargeable state. If not set, defaults to the subscription's \"\n        \"default source, if any, or to the customer's default source.\",\n    )\n\n    # Note:\n    # Most fields are defined on BaseInvoice so they're shared with UpcomingInvoice.\n    # ManyToManyFields are an exception, since UpcomingInvoice doesn't exist in the db.\n    default_tax_rates = models.ManyToManyField(\n        \"TaxRate\",\n        # explicitly specify the joining table name as though the joining model\n        # was defined with through=\"DjstripeInvoiceDefaultTaxRate\"\n        db_table=\"djstripe_djstripeinvoicedefaulttaxrate\",\n        related_name=\"+\",\n        blank=True,\n        help_text=\"The tax rates applied to this invoice, if any.\",\n    )\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        pending_relations=None,\n    ):\n        super()._attach_objects_post_save_hook(\n            cls, data, api_key=api_key, pending_relations=pending_relations\n        )\n\n        self.default_tax_rates.set(\n            cls._stripe_object_to_default_tax_rates(\n                target_cls=TaxRate, data=data, api_key=api_key\n            )\n        )\n\n        cls._stripe_object_set_total_tax_amounts(\n            target_cls=DjstripeInvoiceTotalTaxAmount,\n            data=data,\n            instance=self,\n            api_key=api_key,\n        )\n\n\nclass UpcomingInvoice(BaseInvoice):\n    \"\"\"\n    The preview of an upcoming invoice - does not exist in the Django database.\n\n    See BaseInvoice.upcoming()\n\n    Logically it should be set abstract, but that doesn't quite work since we\n    do actually want to instantiate the model and use relations.\n    \"\"\"\n\n    default_source = PaymentMethodForeignKey(\n        on_delete=models.SET_NULL,\n        null=True,\n        related_name=\"upcoming_invoices\",\n        help_text=\"The default payment source for the invoice. \"\n        \"It must belong to the customer associated with the invoice and be \"\n        \"in a chargeable state. If not set, defaults to the subscription's \"\n        \"default source, if any, or to the customer's default source.\",\n    )\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self._lineitems = []\n        self._default_tax_rates = []\n        self._total_tax_amounts = []\n\n    def get_stripe_dashboard_url(self):\n        return \"\"\n\n    def _attach_objects_hook(\n        self, cls, data, api_key=djstripe_settings.STRIPE_SECRET_KEY, current_ids=None\n    ):\n        super()._attach_objects_hook(\n            cls, data, api_key=api_key, current_ids=current_ids\n        )\n\n        self._lineitems = cls._stripe_object_to_line_items(\n            target_cls=LineItem, data=data, invoice=self, api_key=api_key\n        )\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        pending_relations=None,\n    ):\n        super()._attach_objects_post_save_hook(\n            cls, data, api_key=api_key, pending_relations=pending_relations\n        )\n\n        self._default_tax_rates = cls._stripe_object_to_default_tax_rates(\n            target_cls=TaxRate, data=data, api_key=api_key\n        )\n\n        total_tax_amounts = []\n\n        for tax_amount_data in data.get(\"total_tax_amounts\", []):\n            tax_rate_id = tax_amount_data[\"tax_rate\"]\n            if not isinstance(tax_rate_id, str):\n                tax_rate_id = tax_rate_id[\"tax_rate\"]\n\n            tax_rate = TaxRate._get_or_retrieve(id=tax_rate_id, api_key=api_key)\n\n            tax_amount = DjstripeUpcomingInvoiceTotalTaxAmount(\n                invoice=self,\n                amount=tax_amount_data[\"amount\"],\n                inclusive=tax_amount_data[\"inclusive\"],\n                tax_rate=tax_rate,\n            )\n\n            total_tax_amounts.append(tax_amount)\n\n        self._total_tax_amounts = total_tax_amounts\n\n    @property\n    def invoiceitems(self):\n        \"\"\"\n        Gets the invoice items associated with this upcoming invoice.\n\n        This differs from normal (non-upcoming) invoices, in that upcoming\n        invoices are in-memory and do not persist to the database. Therefore,\n        all of the data comes from the Stripe API itself.\n\n        Instead of returning a normal queryset for the invoiceitems, this will\n        return a mock of a queryset, but with the data fetched from Stripe - It\n        will act like a normal queryset, but mutation will silently fail.\n        \"\"\"\n        # filter lineitems with type=\"invoice_item\" and fetch all the actual InvoiceItem objects\n        items = []\n        for item in self._lineitems:\n            if item.type == \"invoice_item\":\n                items.append(item.invoice_item)\n\n        return QuerySetMock.from_iterable(InvoiceItem, items)\n\n    @property\n    def lineitems(self):\n        \"\"\"\n        Gets the line items associated with this upcoming invoice.\n\n        This differs from normal (non-upcoming) invoices, in that upcoming\n        invoices are in-memory and do not persist to the database. Therefore,\n        all of the data comes from the Stripe API itself.\n\n        Instead of returning a normal queryset for the lineitems, this will\n        return a mock of a queryset, but with the data fetched from Stripe - It\n        will act like a normal queryset, but mutation will silently fail.\n        \"\"\"\n        return QuerySetMock.from_iterable(LineItem, self._lineitems)\n\n    @property\n    def default_tax_rates(self):\n        \"\"\"\n        Gets the default tax rates associated with this upcoming invoice.\n        :return:\n        \"\"\"\n        return QuerySetMock.from_iterable(TaxRate, self._default_tax_rates)\n\n    @property\n    def total_tax_amounts(self):\n        \"\"\"\n        Gets the total tax amounts associated with this upcoming invoice.\n        :return:\n        \"\"\"\n        return QuerySetMock.from_iterable(\n            DjstripeUpcomingInvoiceTotalTaxAmount, self._total_tax_amounts\n        )\n\n    @property\n    def id(self):\n        return None\n\n    @id.setter\n    def id(self, value):\n        return  # noop\n\n    def save(self, *args, **kwargs):\n        return  # noop\n\n\nclass InvoiceItem(StripeModel):\n    \"\"\"\n    Sometimes you want to add a charge or credit to a customer but only actually\n    charge the customer's card at the end of a regular billing cycle.\n    This is useful for combining several charges to minimize per-transaction fees\n    or having Stripe tabulate your usage-based billing totals.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#invoiceitems\n    \"\"\"\n\n    stripe_class = stripe.InvoiceItem\n    expand_fields = [\"discounts\"]\n\n    amount = StripeDecimalCurrencyAmountField(help_text=\"Amount invoiced (as decimal).\")\n    currency = StripeCurrencyCodeField()\n    customer = StripeForeignKey(\n        \"Customer\",\n        on_delete=models.CASCADE,\n        related_name=\"invoiceitems\",\n        help_text=\"The customer associated with this invoiceitem.\",\n    )\n    date = StripeDateTimeField(help_text=\"The date on the invoiceitem.\")\n    discountable = models.BooleanField(\n        default=False,\n        help_text=\"If True, discounts will apply to this invoice item. \"\n        \"Always False for prorations.\",\n    )\n    discounts = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"The discounts which apply to the invoice item. Item discounts are applied before invoice discounts.\",\n    )\n    invoice = StripeForeignKey(\n        \"Invoice\",\n        on_delete=models.CASCADE,\n        null=True,\n        related_name=\"invoiceitems\",\n        help_text=\"The invoice to which this invoiceitem is attached.\",\n    )\n    period = JSONField()\n    period_end = StripeDateTimeField(\n        help_text=\"Might be the date when this invoiceitem's invoice was sent.\"\n    )\n    period_start = StripeDateTimeField(\n        help_text=\"Might be the date when this invoiceitem was added to the invoice\"\n    )\n    plan = models.ForeignKey(\n        \"Plan\",\n        null=True,\n        on_delete=models.SET_NULL,\n        help_text=\"If the invoice item is a proration, the plan of the subscription \"\n        \"for which the proration was computed.\",\n    )\n    price = models.ForeignKey(\n        \"Price\",\n        null=True,\n        related_name=\"invoiceitems\",\n        on_delete=models.SET_NULL,\n        help_text=\"If the invoice item is a proration, the price of the subscription \"\n        \"for which the proration was computed.\",\n    )\n    proration = models.BooleanField(\n        default=False,\n        help_text=\"Whether or not the invoice item was created automatically as a \"\n        \"proration adjustment when the customer switched plans.\",\n    )\n    quantity = models.IntegerField(\n        null=True,\n        blank=True,\n        help_text=\"If the invoice item is a proration, the quantity of the \"\n        \"subscription for which the proration was computed.\",\n    )\n    subscription = StripeForeignKey(\n        \"Subscription\",\n        null=True,\n        related_name=\"invoiceitems\",\n        on_delete=models.SET_NULL,\n        help_text=\"The subscription that this invoice item has been created for, \"\n        \"if any.\",\n    )\n    # XXX: subscription_item\n    tax_rates = models.ManyToManyField(\n        \"TaxRate\",\n        # explicitly specify the joining table name as though the joining model\n        # was defined with through=\"DjstripeInvoiceItemTaxRate\"\n        db_table=\"djstripe_djstripeinvoiceitemtaxrate\",\n        related_name=\"+\",\n        blank=True,\n        help_text=\"The tax rates which apply to this invoice item. When set, \"\n        \"the default_tax_rates on the invoice do not apply to this \"\n        \"invoice item.\",\n    )\n    unit_amount = StripeQuantumCurrencyAmountField(\n        null=True,\n        blank=True,\n        help_text=\"Unit amount (in the `currency` specified) of the invoice item.\",\n    )\n    unit_amount_decimal = StripeDecimalCurrencyAmountField(\n        null=True,\n        blank=True,\n        max_digits=19,\n        decimal_places=12,\n        help_text=(\n            \"Same as `unit_amount`, but contains a decimal value with \"\n            \"at most 12 decimal places.\"\n        ),\n    )\n\n    @classmethod\n    def _manipulate_stripe_object_hook(cls, data):\n        data[\"period_start\"] = data[\"period\"][\"start\"]\n        data[\"period_end\"] = data[\"period\"][\"end\"]\n\n        return data\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        pending_relations=None,\n    ):\n        super()._attach_objects_post_save_hook(\n            cls, data, api_key=api_key, pending_relations=pending_relations\n        )\n\n        if self.pk:\n            # only call .set() on saved instance (ie don't on items of UpcomingInvoice)\n            self.tax_rates.set(\n                cls._stripe_object_to_tax_rates(\n                    target_cls=TaxRate, data=data, api_key=api_key\n                )\n            )\n\n        # sync every discount\n        for discount in self.discounts:\n            Discount.sync_from_stripe_data(discount, api_key=api_key)\n\n    def __str__(self):\n        return self.description\n\n    def get_stripe_dashboard_url(self):\n        return self.invoice.get_stripe_dashboard_url()\n\n    def api_retrieve(self, *args, **kwargs):\n        if \"-il_\" in self.id:\n            warnings.warn(\n                f\"Attempting to retrieve InvoiceItem with id={self.id!r}\"\n                \" will most likely fail. \"\n                \"Run manage.py djstripe_update_invoiceitem_ids if this is a problem.\"\n            )\n\n        return super().api_retrieve(*args, **kwargs)\n\n\nclass LineItem(StripeModel):\n    \"\"\"\n    The individual line items that make up the invoice.\n\n    Stripe documentation: https://stripe.com/docs/api/invoices/line_item\n    \"\"\"\n\n    stripe_class = stripe.InvoiceLineItem\n    expand_fields = [\"discounts\"]\n\n    amount = StripeQuantumCurrencyAmountField(help_text=\"The amount, in cents.\")\n    amount_excluding_tax = StripeQuantumCurrencyAmountField(\n        help_text=\"The integer amount in cents representing the amount for this line item, excluding all tax and discounts.\"\n    )\n    currency = StripeCurrencyCodeField()\n    discount_amounts = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"The amount of discount calculated per discount for this line item.\",\n    )\n    discountable = models.BooleanField(\n        default=False,\n        help_text=\"If True, discounts will apply to this line item. \"\n        \"Always False for prorations.\",\n    )\n    discounts = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"The discounts applied to the invoice line item. Line item discounts are applied before invoice discounts.\",\n    )\n    invoice_item = StripeForeignKey(\n        \"InvoiceItem\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"The ID of the invoice item associated with this line item if any.\",\n    )\n    period = JSONField(\n        help_text=\"The period this line_item covers. For subscription line items, this is the subscription period. For prorations, this starts when the proration was calculated, and ends at the period end of the subscription. For invoice items, this is the time at which the invoice item was created or the period of the item.\"\n    )\n    period_end = StripeDateTimeField(\n        help_text=\"The end of the period, which must be greater than or equal to the start.\"\n    )\n    period_start = StripeDateTimeField(help_text=\"The start of the period.\")\n    price = JSONField(\n        help_text=\"The price of the line item.\",\n    )\n    proration = models.BooleanField(\n        default=False,\n        help_text=\"Whether or not the invoice item was created automatically as a \"\n        \"proration adjustment when the customer switched plans.\",\n    )\n    proration_details = JSONField(\n        help_text=\"Additional details for proration line items\"\n    )\n    subscription = StripeForeignKey(\n        \"Subscription\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"The subscription that the invoice item pertains to, if any.\",\n    )\n    subscription_item = StripeForeignKey(\n        \"SubscriptionItem\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"The subscription item that generated this invoice item. Left empty if the line item is not an explicit result of a subscription.\",\n    )\n    tax_amounts = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"The amount of tax calculated per tax rate for this line item\",\n    )\n    tax_rates = JSONField(\n        null=True, blank=True, help_text=\"The tax rates which apply to the line item.\"\n    )\n    type = StripeEnumField(enum=enums.LineItem)\n    unit_amount_excluding_tax = StripeDecimalCurrencyAmountField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"The amount in cents representing the unit amount for this line item, excluding all tax and discounts.\"\n        ),\n    )\n    quantity = models.IntegerField(\n        null=True,\n        blank=True,\n        help_text=\"The quantity of the subscription, if the line item is a subscription or a proration.\",\n    )\n\n    @classmethod\n    def _manipulate_stripe_object_hook(cls, data):\n        data[\"period_start\"] = data[\"period\"][\"start\"]\n        data[\"period_end\"] = data[\"period\"][\"end\"]\n\n        return data\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        pending_relations=None,\n    ):\n        super()._attach_objects_post_save_hook(\n            cls, data, api_key=api_key, pending_relations=pending_relations\n        )\n\n        # sync every discount\n        for discount in self.discounts:\n            Discount.sync_from_stripe_data(discount, api_key=api_key)\n\n    @classmethod\n    def api_list(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        \"\"\"\n        Call the stripe API's list operation for this model.\n        Note that we only iterate and sync the LineItem associated with the\n        passed in Invoice.\n\n        Upcoming invoices are virtual and are not saved and hence their\n        line items are also not retrieved and synced\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n\n        See Stripe documentation for accepted kwargs for each object.\n\n        :returns: an iterator over all items in the query\n        \"\"\"\n        # get current invoice if any\n        invoice_id = kwargs.pop(\"id\")\n\n        # get expand parameter that needs to be passed to invoice.lines.list call\n        expand_fields = kwargs.pop(\"expand\")\n\n        invoice = Invoice.stripe_class.retrieve(invoice_id, api_key=api_key, **kwargs)\n\n        # iterate over all the line items on the current invoice\n        return invoice.lines.list(\n            api_key=api_key, expand=expand_fields, **kwargs\n        ).auto_paging_iter()\n\n\nclass InvoiceOrLineItem(models.Model):\n    \"\"\"An Internal Model that abstracts InvoiceItem and lineItem\n    objects\n\n    Contains two fields: `id` and `type`:\n    - `id` is the id of the Stripe object.\n    - `type` can be `line_item`, `invoice_item` or `unsupported`\n    \"\"\"\n\n    id = models.CharField(max_length=255, primary_key=True)\n    type = StripeEnumField(\n        enum=enums.InvoiceorLineItemType,\n        help_text=\"Indicates whether the underlying model is LineItem or InvoiceItem. Can be one of: 'invoice_item', 'line_item' or 'unsupported'\",\n    )\n\n    @classmethod\n    def _model_type(cls, id_):\n        if id_.startswith(\"ii\"):\n            return InvoiceItem, \"invoice_item\"\n        elif id_.startswith(\"il\"):\n            return LineItem, \"line_item\"\n        raise ValueError(f\"Unknown object type with id: {id_}\")\n\n    @classmethod\n    def _get_or_create_from_stripe_object(\n        cls,\n        data,\n        field_name=\"id\",\n        refetch=True,\n        current_ids=None,\n        pending_relations=None,\n        save=True,\n        stripe_account=None,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    ):\n        raw_field_data = data.get(field_name)\n        id_ = get_id_from_stripe_data(raw_field_data)\n\n        try:\n            object_cls, object_type = cls._model_type(id_)\n        except ValueError:\n            # This may happen if we have object types we don't know about.\n            # Let's not make dj-stripe entirely unusable if that happens.\n            logger.warning(f\"Unknown Object. Could not sync object with id: {id_}\")\n            return cls.objects.get_or_create(id=id_, defaults={\"type\": \"unsupported\"})\n\n        # call model's _get_or_create_from_stripe_object to ensure\n        # that object exists before getting or creating its InvoiceorLineItem mapping\n        object_cls._get_or_create_from_stripe_object(\n            data,\n            field_name,\n            refetch=refetch,\n            current_ids=current_ids,\n            pending_relations=pending_relations,\n            stripe_account=stripe_account,\n            api_key=api_key,\n        )\n\n        return cls.objects.get_or_create(id=id_, defaults={\"type\": object_type})\n\n\nclass Plan(StripeModel):\n    \"\"\"\n    A subscription plan contains the pricing information for different\n    products and feature levels on your site.\n\n    Stripe documentation: https://stripe.com/docs/api/plans?lang=python\n\n    NOTE: The Stripe Plans API has been deprecated in favor of the Prices API.\n    You may want to upgrade to use the Price model instead of the Plan model.\n    \"\"\"\n\n    stripe_class = stripe.Plan\n    expand_fields = [\"product\", \"tiers\"]\n    stripe_dashboard_item_name = \"plans\"\n\n    active = models.BooleanField(\n        help_text=\"Whether the plan can be used for new purchases.\"\n    )\n    aggregate_usage = StripeEnumField(\n        enum=enums.PlanAggregateUsage,\n        default=\"\",\n        blank=True,\n        help_text=(\n            \"Specifies a usage aggregation strategy for plans of usage_type=metered. \"\n            \"Allowed values are `sum` for summing up all usage during a period, \"\n            \"`last_during_period` for picking the last usage record reported within a \"\n            \"period, `last_ever` for picking the last usage record ever (across period \"\n            \"bounds) or max which picks the usage record with the maximum reported \"\n            \"usage during a period. Defaults to `sum`.\"\n        ),\n    )\n    amount = StripeDecimalCurrencyAmountField(\n        null=True,\n        blank=True,\n        help_text=\"Amount (as decimal) to be charged on the interval specified.\",\n    )\n    amount_decimal = StripeDecimalCurrencyAmountField(\n        null=True,\n        blank=True,\n        max_digits=19,\n        decimal_places=12,\n        help_text=(\n            \"The unit amount in cents to be charged, represented as a decimal \"\n            \"string with at most 12 decimal places.\"\n        ),\n    )\n    billing_scheme = StripeEnumField(\n        enum=enums.BillingScheme,\n        default=\"\",\n        blank=True,\n        help_text=(\n            \"Describes how to compute the price per period. \"\n            \"Either `per_unit` or `tiered`. \"\n            \"`per_unit` indicates that the fixed amount (specified in amount) \"\n            \"will be charged per unit in quantity \"\n            \"(for plans with `usage_type=licensed`), or per unit of total \"\n            \"usage (for plans with `usage_type=metered`). \"\n            \"`tiered` indicates that the unit pricing will be computed using \"\n            \"a tiering strategy as defined using the tiers and tiers_mode attributes.\"\n        ),\n    )\n    currency = StripeCurrencyCodeField()\n    interval = StripeEnumField(\n        enum=enums.PlanInterval,\n        help_text=\"The frequency with which a subscription should be billed.\",\n    )\n    interval_count = models.PositiveIntegerField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"The number of intervals (specified in the interval property) \"\n            \"between each subscription billing.\"\n        ),\n    )\n    nickname = models.TextField(\n        max_length=5000,\n        default=\"\",\n        blank=True,\n        help_text=\"A brief description of the plan, hidden from customers.\",\n    )\n    product = StripeForeignKey(\n        \"Product\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        help_text=\"The product whose pricing this plan determines.\",\n    )\n    tiers = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Each element represents a pricing tier. \"\n            \"This parameter requires `billing_scheme` to be set to `tiered`.\"\n        ),\n    )\n    tiers_mode = StripeEnumField(\n        enum=enums.PriceTiersMode,\n        null=True,\n        blank=True,\n        help_text=(\n            \"Defines if the tiering price should be `graduated` or `volume` based. \"\n            \"In `volume`-based tiering, the maximum quantity within a period \"\n            \"determines the per unit price, in `graduated` tiering pricing can \"\n            \"successively change as the quantity grows.\"\n        ),\n    )\n    transform_usage = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Apply a transformation to the reported usage or set quantity \"\n            \"before computing the billed price. Cannot be combined with `tiers`.\"\n        ),\n    )\n    trial_period_days = models.IntegerField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Number of trial period days granted when subscribing a customer \"\n            \"to this plan. Null if the plan has no trial period.\"\n        ),\n    )\n    usage_type = StripeEnumField(\n        enum=enums.PriceUsageType,\n        default=enums.PriceUsageType.licensed,\n        help_text=(\n            \"Configures how the quantity per period should be determined, \"\n            \"can be either `metered` or `licensed`. `licensed` will automatically \"\n            \"bill the `quantity` set for a plan when adding it to a subscription, \"\n            \"`metered` will aggregate the total usage based on usage records. \"\n            \"Defaults to `licensed`.\"\n        ),\n    )\n\n    class Meta(object):\n        ordering = [\"amount\"]\n\n    @classmethod\n    def get_or_create(cls, **kwargs):\n        \"\"\"Get or create a Plan.\"\"\"\n\n        try:\n            return cls.objects.get(id=kwargs[\"id\"]), False\n        except cls.DoesNotExist:\n            return cls.create(**kwargs), True\n\n    @classmethod\n    def create(cls, **kwargs):\n        # A few minor things are changed in the api-version of the create call\n        api_kwargs = dict(kwargs)\n        api_kwargs[\"amount\"] = int(api_kwargs[\"amount\"] * 100)\n\n        if isinstance(api_kwargs.get(\"product\"), StripeModel):\n            api_kwargs[\"product\"] = api_kwargs[\"product\"].id\n\n        stripe_plan = cls._api_create(**api_kwargs)\n        api_key = api_kwargs.get(\"api_key\") or djstripe_settings.STRIPE_SECRET_KEY\n        plan = cls.sync_from_stripe_data(stripe_plan, api_key=api_key)\n\n        return plan\n\n    def __str__(self):\n        if self.product and self.product.name:\n            return f\"{self.human_readable_price} for {self.product.name}\"\n        return self.human_readable_price\n\n    @property\n    def amount_in_cents(self):\n        return int(self.amount * 100)\n\n    @property\n    def human_readable_price(self) -> str:\n        if self.billing_scheme == \"per_unit\":\n            unit_amount = self.amount\n            amount = get_friendly_currency_amount(unit_amount, self.currency)\n        else:\n            # tiered billing scheme\n            tier_1 = self.tiers[0]\n            flat_amount_tier_1 = tier_1[\"flat_amount\"]\n            formatted_unit_amount_tier_1 = get_friendly_currency_amount(\n                (tier_1[\"unit_amount\"] or 0) / 100, self.currency\n            )\n            amount = f\"Starts at {formatted_unit_amount_tier_1} per unit\"\n\n            # stripe shows flat fee even if it is set to 0.00\n            if flat_amount_tier_1 is not None:\n                formatted_flat_amount_tier_1 = get_friendly_currency_amount(\n                    flat_amount_tier_1 / 100, self.currency\n                )\n                amount = f\"{amount} + {formatted_flat_amount_tier_1}\"\n\n        format_args = {\"amount\": amount}\n\n        interval_count = self.interval_count\n        if interval_count == 1:\n            interval = {\n                \"day\": _(\"day\"),\n                \"week\": _(\"week\"),\n                \"month\": _(\"month\"),\n                \"year\": _(\"year\"),\n            }[self.interval]\n            template = _(\"{amount}/{interval}\")\n            format_args[\"interval\"] = interval\n        else:\n            interval = {\n                \"day\": _(\"days\"),\n                \"week\": _(\"weeks\"),\n                \"month\": _(\"months\"),\n                \"year\": _(\"years\"),\n            }[self.interval]\n            template = _(\"{amount} / every {interval_count} {interval}\")\n            format_args[\"interval\"] = interval\n            format_args[\"interval_count\"] = interval_count\n\n        return str(format_lazy(template, **format_args))\n\n\nclass Subscription(StripeModel):\n    \"\"\"\n    Subscriptions allow you to charge a customer's card on a recurring basis.\n    A subscription ties a customer to a particular plan you've created.\n\n    A subscription still in its trial period is ``trialing`` and moves to ``active``\n    when the trial period is over.\n\n    When payment to renew the subscription fails, the subscription becomes ``past_due``.\n    After Stripe has exhausted all payment retry attempts, the subscription ends up\n    with a status of either ``canceled`` or ``unpaid`` depending on your retry settings.\n\n    Note that when a subscription has a status of ``unpaid``, no subsequent invoices\n    will be attempted (invoices will be created, but then immediately\n    automatically closed.\n\n    Additionally, updating customer card details will not lead to Stripe retrying the\n    latest invoice.).\n    After receiving updated card details from a customer, you may choose to reopen and\n    pay their closed invoices.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#subscriptions\n    \"\"\"\n\n    stripe_class = stripe.Subscription\n    stripe_dashboard_item_name = \"subscriptions\"\n\n    application_fee_percent = StripePercentField(\n        null=True,\n        blank=True,\n        help_text=\"A positive decimal that represents the fee percentage of the \"\n        \"subscription invoice amount that will be transferred to the application \"\n        \"owner's Stripe account each billing period.\",\n    )\n    billing_cycle_anchor = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Determines the date of the first full invoice, and, for plans \"\n            \"with `month` or `year` intervals, the day of the month for subsequent \"\n            \"invoices.\"\n        ),\n    )\n    billing_thresholds = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Define thresholds at which an invoice will be sent, and the \"\n        \"subscription advanced to a new billing period.\",\n    )\n    cancel_at = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"A date in the future at which the subscription will automatically \"\n        \"get canceled.\",\n    )\n    cancel_at_period_end = models.BooleanField(\n        default=False,\n        help_text=\"If the subscription has been canceled with the ``at_period_end`` \"\n        \"flag set to true, ``cancel_at_period_end`` on the subscription will be true. \"\n        \"You can use this attribute to determine whether a subscription that has a \"\n        \"status of active is scheduled to be canceled at the end of the \"\n        \"current period.\",\n    )\n    canceled_at = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"If the subscription has been canceled, the date of that \"\n        \"cancellation. If the subscription was canceled with ``cancel_at_period_end``, \"\n        \"canceled_at will still reflect the date of the initial cancellation request, \"\n        \"not the end of the subscription period when the subscription is automatically \"\n        \"moved to a canceled state.\",\n    )\n    collection_method = StripeEnumField(\n        enum=enums.InvoiceCollectionMethod,\n        help_text=\"Either `charge_automatically`, or `send_invoice`. When charging \"\n        \"automatically, Stripe will attempt to pay this subscription at the end of the \"\n        \"cycle using the default source attached to the customer. \"\n        \"When sending an invoice, Stripe will email your customer an invoice with \"\n        \"payment instructions.\",\n    )\n    current_period_end = StripeDateTimeField(\n        help_text=\"End of the current period for which the subscription has been \"\n        \"invoiced. At the end of this period, a new invoice will be created.\"\n    )\n    current_period_start = StripeDateTimeField(\n        help_text=\"Start of the current period for which the subscription has \"\n        \"been invoiced.\"\n    )\n    customer = StripeForeignKey(\n        \"Customer\",\n        on_delete=models.CASCADE,\n        related_name=\"subscriptions\",\n        help_text=\"The customer associated with this subscription.\",\n    )\n    days_until_due = models.IntegerField(\n        null=True,\n        blank=True,\n        help_text=\"Number of days a customer has to pay invoices generated by this \"\n        \"subscription. This value will be `null` for subscriptions where \"\n        \"`billing=charge_automatically`.\",\n    )\n    default_payment_method = StripeForeignKey(\n        \"PaymentMethod\",\n        null=True,\n        blank=True,\n        on_delete=models.SET_NULL,\n        related_name=\"+\",\n        help_text=\"The default payment method for the subscription. \"\n        \"It must belong to the customer associated with the subscription. \"\n        \"If not set, invoices will use the default payment method in the \"\n        \"customer's invoice settings.\",\n    )\n    default_source = PaymentMethodForeignKey(\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        related_name=\"subscriptions\",\n        help_text=\"The default payment source for the subscription. \"\n        \"It must belong to the customer associated with the subscription \"\n        \"and be in a chargeable state. If not set, defaults to the customer's \"\n        \"default source.\",\n    )\n    default_tax_rates = models.ManyToManyField(\n        \"TaxRate\",\n        # explicitly specify the joining table name as though the joining model\n        # was defined with through=\"DjstripeSubscriptionDefaultTaxRate\"\n        db_table=\"djstripe_djstripesubscriptiondefaulttaxrate\",\n        related_name=\"+\",\n        blank=True,\n        help_text=\"The tax rates that will apply to any subscription item \"\n        \"that does not have tax_rates set. Invoices created will have their \"\n        \"default_tax_rates populated from the subscription.\",\n    )\n    discount = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Describes the current discount applied to this subscription, if there is one. When billing, a discount applied to a subscription overrides a discount applied on a customer-wide basis.\",\n    )\n    ended_at = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"If the subscription has ended (either because it was canceled or \"\n        \"because the customer was switched to a subscription to a new plan), \"\n        \"the date the subscription ended.\",\n    )\n    latest_invoice = StripeForeignKey(\n        \"Invoice\",\n        null=True,\n        blank=True,\n        related_name=\"+\",\n        on_delete=models.SET_NULL,\n        help_text=\"The most recent invoice this subscription has generated.\",\n    )\n    next_pending_invoice_item_invoice = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"Specifies the approximate timestamp on which any pending \"\n        \"invoice items will be billed according to the schedule provided at \"\n        \"pending_invoice_item_interval.\",\n    )\n    pause_collection = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"If specified, payment collection for this subscription will be paused.\",\n    )\n    pending_invoice_item_interval = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Specifies an interval for how often to bill for any \"\n        \"pending invoice items. It is analogous to calling Create an invoice \"\n        \"for the given subscription at the specified interval.\",\n    )\n    pending_setup_intent = StripeForeignKey(\n        \"SetupIntent\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        related_name=\"setup_intents\",\n        help_text=\"We can use this SetupIntent to collect user authentication \"\n        \"when creating a subscription without immediate payment or updating a \"\n        \"subscription's payment method, allowing you to \"\n        \"optimize for off-session payments.\",\n    )\n    pending_update = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"If specified, pending updates that will be applied to the \"\n        \"subscription once the latest_invoice has been paid.\",\n    )\n    plan = models.ForeignKey(\n        \"Plan\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        related_name=\"subscriptions\",\n        help_text=\"The plan associated with this subscription. This value will be \"\n        \"`null` for multi-plan subscriptions\",\n    )\n    proration_behavior = StripeEnumField(\n        enum=enums.SubscriptionProrationBehavior,\n        help_text=\"Determines how to handle prorations when the billing cycle changes (e.g., when switching plans, resetting billing_cycle_anchor=now, or starting a trial), or if an item’s quantity changes\",\n        default=enums.SubscriptionProrationBehavior.create_prorations,\n        blank=True,\n    )\n    proration_date = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"If set, the proration will be calculated as though the subscription was updated at the given time. This can be used to apply exactly the same proration that was previewed with upcoming invoice endpoint. It can also be used to implement custom proration logic, such as prorating by day instead of by second, by providing the time that you wish to use for proration calculations\",\n    )\n    quantity = models.IntegerField(\n        null=True,\n        blank=True,\n        help_text=\"The quantity applied to this subscription. This value will be \"\n        \"`null` for multi-plan subscriptions\",\n    )\n    schedule = models.ForeignKey(\n        \"SubscriptionSchedule\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        related_name=\"subscriptions\",\n        help_text=\"The schedule associated with this subscription.\",\n    )\n    start_date = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"Date when the subscription was first created. The date \"\n        \"might differ from the created date due to backdating.\",\n    )\n    status = StripeEnumField(\n        enum=enums.SubscriptionStatus, help_text=\"The status of this subscription.\"\n    )\n    trial_end = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"If the subscription has a trial, the end of that trial.\",\n    )\n    trial_start = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"If the subscription has a trial, the beginning of that trial.\",\n    )\n\n    objects = SubscriptionManager()\n\n    @classmethod\n    def api_list(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        \"\"\"\n        Call the stripe API's list operation for this model.\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        See Stripe documentation for accepted kwargs for each object.\n        :returns: an iterator over all items in the query\n        \"\"\"\n        if not kwargs.get(\"status\"):\n            # special case: https://stripe.com/docs/api/subscriptions/list#list_subscriptions-status\n            # See Issue: https://github.com/dj-stripe/dj-stripe/issues/1763\n            kwargs[\"status\"] = \"all\"\n        return super().api_list(api_key=api_key, **kwargs)\n\n    def update(self, plan: Union[StripeModel, str] = None, **kwargs):\n        \"\"\"\n        See `Customer.subscribe() <#djstripe.models.Customer.subscribe>`__\n\n        :param plan: The plan to which to subscribe the customer.\n        :type plan: Plan or string (plan ID)\n\n        .. important:: Updating a subscription by changing the plan or quantity \\\n            creates a new ``Subscription`` in \\\n            Stripe (and dj-stripe).\n        \"\"\"\n\n        # Convert Plan to id\n        if plan is not None and isinstance(plan, StripeModel):\n            plan = plan.id\n\n        stripe_subscription = self._api_update(plan=plan, **kwargs)\n\n        api_key = kwargs.get(\"api_key\") or self.default_api_key\n        return Subscription.sync_from_stripe_data(stripe_subscription, api_key=api_key)\n\n    def extend(self, delta):\n        \"\"\"\n        Extends this subscription by the provided delta.\n\n        :param delta: The timedelta by which to extend this subscription.\n        :type delta: timedelta\n        \"\"\"\n\n        if delta.total_seconds() < 0:\n            raise ValueError(\"delta must be a positive timedelta.\")\n\n        if self.trial_end is not None and self.trial_end > timezone.now():\n            period_end = self.trial_end\n        else:\n            period_end = self.current_period_end\n\n        period_end += delta\n\n        return self.update(proration_behavior=\"none\", trial_end=period_end)\n\n    def cancel(self, at_period_end: bool = False, **kwargs):\n        \"\"\"\n        Cancels this subscription. If you set the at_period_end parameter to true,\n        the subscription will remain active until the end of the period, at which point\n        it will be canceled and not renewed. By default, the subscription is terminated\n        immediately. In either case, the customer will not be charged again for\n        the subscription. Note, however, that any pending invoice items or metered\n        usage will still be charged at the end of the period unless manually\n        deleted.\n\n        Depending on how `proration_behavior` is set, any pending prorations will\n        also be left in place and collected at the end of the period.\n        However, if the subscription is set to cancel immediately, you can pass the\n        `prorate` and `invoice_now` flags in `kwargs` to configure how the pending\n        metered usage is invoiced and how proration must work.\n\n        By default, all unpaid invoices for the customer will be closed upon\n        subscription cancellation. We do this in order to prevent unexpected payment\n        retries once the customer has canceled a subscription. However, you can\n        reopen the invoices manually after subscription cancellation to have us proceed\n        with automatic retries, or you could even re-attempt payment yourself on all\n        unpaid invoices before allowing the customer to cancel the\n        subscription at all.\n\n        :param at_period_end: A flag that if set to true will delay the cancellation \\\n            of the subscription until the end of the current period. Default is False.\n        :type at_period_end: boolean\n\n        .. important:: If a subscription is canceled during a trial period, \\\n        the ``at_period_end`` flag will be overridden to False so that the trial ends \\\n        immediately and the customer's card isn't charged.\n        \"\"\"\n\n        # If plan has trial days and customer cancels before\n        # trial period ends, then end subscription now,\n        # i.e. at_period_end=False\n        if self.trial_end and self.trial_end > timezone.now():\n            at_period_end = False\n\n        if at_period_end:\n            stripe_subscription = self._api_update(cancel_at_period_end=True)\n        else:\n            try:\n                stripe_subscription = self._api_delete(**kwargs)\n            except InvalidRequestError as exc:\n                if \"No such subscription:\" in str(exc):\n                    # cancel() works by deleting the subscription. The object still\n                    # exists in Stripe however, and can still be retrieved.\n                    # If the subscription was already canceled (status=canceled),\n                    # that api_retrieve() call will fail with \"No such subscription\".\n                    # However, this may also happen if the subscription legitimately\n                    # does not exist, in which case the following line will re-raise.\n                    stripe_subscription = self.api_retrieve()\n                else:\n                    raise\n\n        return Subscription.sync_from_stripe_data(\n            stripe_subscription, api_key=self.default_api_key\n        )\n\n    def reactivate(self):\n        \"\"\"\n        Reactivates this subscription.\n\n        If a customer's subscription is canceled with ``at_period_end`` set to True and\n        it has not yet reached the end of the billing period, it can be reactivated.\n        Subscriptions canceled immediately cannot be reactivated.\n        (Source: https://stripe.com/docs/billing/subscriptions/cancel)\n\n        .. warning:: Reactivating a fully canceled Subscription will fail silently. \\\n        Be sure to check the returned Subscription's status.\n        \"\"\"\n        stripe_subscription = self.api_retrieve()\n        stripe_subscription.plan = self.plan.id\n        stripe_subscription.cancel_at_period_end = False\n\n        return Subscription.sync_from_stripe_data(stripe_subscription.save())\n\n    def is_period_current(self):\n        \"\"\"\n        Returns True if this subscription's period is current, false otherwise.\n        \"\"\"\n\n        return self.current_period_end > timezone.now() or (\n            self.trial_end and self.trial_end > timezone.now()\n        )\n\n    def is_status_current(self):\n        \"\"\"\n        Returns True if this subscription's status is current (active or trialing),\n        false otherwise.\n        \"\"\"\n\n        return self.status in [\"trialing\", \"active\"]\n\n    def is_status_temporarily_current(self):\n        \"\"\"\n        A status is temporarily current when the subscription is canceled with the\n        ``at_period_end`` flag.\n        The subscription is still active, but is technically canceled and we're just\n        waiting for it to run out.\n\n        You could use this method to give customers limited service after they've\n        canceled. For example, a video on demand service could only allow customers\n        to download their libraries and do nothing else when their\n        subscription is temporarily current.\n        \"\"\"\n\n        return (\n            self.canceled_at\n            and self.cancel_at_period_end\n            and timezone.now() < self.current_period_end\n        )\n\n    def is_valid(self):\n        \"\"\"\n        Returns True if this subscription's status and period are current,\n        false otherwise.\n        \"\"\"\n\n        if not self.is_status_current():\n            return False\n\n        if not self.is_period_current():\n            return False\n\n        return True\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        pending_relations=None,\n    ):\n        super()._attach_objects_post_save_hook(\n            cls, data, api_key=api_key, pending_relations=pending_relations\n        )\n\n        cls._stripe_object_to_subscription_items(\n            target_cls=SubscriptionItem, data=data, subscription=self, api_key=api_key\n        )\n\n        self.default_tax_rates.set(\n            cls._stripe_object_to_default_tax_rates(\n                target_cls=TaxRate, data=data, api_key=api_key\n            )\n        )\n\n\nclass SubscriptionItem(StripeModel):\n    \"\"\"\n    Subscription items allow you to create customer subscriptions\n    with more than one plan, making it easy to represent complex billing relationships.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#subscription_items\n    \"\"\"\n\n    stripe_class = stripe.SubscriptionItem\n\n    billing_thresholds = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Define thresholds at which an invoice will be sent, and the \"\n        \"related subscription advanced to a new billing period.\",\n    )\n    plan = models.ForeignKey(\n        \"Plan\",\n        on_delete=models.CASCADE,\n        related_name=\"subscription_items\",\n        help_text=\"The plan the customer is subscribed to.\",\n    )\n    price = models.ForeignKey(\n        \"Price\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        related_name=\"subscription_items\",\n        help_text=\"The price the customer is subscribed to.\",\n    )\n    proration_behavior = StripeEnumField(\n        enum=enums.SubscriptionProrationBehavior,\n        help_text=\"Determines how to handle prorations when the billing cycle changes (e.g., when switching plans, resetting billing_cycle_anchor=now, or starting a trial), or if an item’s quantity changes\",\n        default=enums.SubscriptionProrationBehavior.create_prorations,\n        blank=True,\n    )\n    proration_date = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"If set, the proration will be calculated as though the subscription was updated at the given time. This can be used to apply exactly the same proration that was previewed with upcoming invoice endpoint. It can also be used to implement custom proration logic, such as prorating by day instead of by second, by providing the time that you wish to use for proration calculations\",\n    )\n    quantity = models.PositiveIntegerField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"The quantity of the plan to which the customer should be subscribed.\"\n        ),\n    )\n    subscription = StripeForeignKey(\n        \"Subscription\",\n        on_delete=models.CASCADE,\n        related_name=\"items\",\n        help_text=\"The subscription this subscription item belongs to.\",\n    )\n    tax_rates = models.ManyToManyField(\n        \"TaxRate\",\n        # explicitly specify the joining table name as though the joining model\n        # was defined with through=\"DjstripeSubscriptionItemTaxRate\"\n        db_table=\"djstripe_djstripesubscriptionitemtaxrate\",\n        related_name=\"+\",\n        blank=True,\n        help_text=\"The tax rates which apply to this subscription_item. When set, \"\n        \"the default_tax_rates on the subscription do not apply to this \"\n        \"subscription_item.\",\n    )\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        pending_relations=None,\n    ):\n        super()._attach_objects_post_save_hook(\n            cls, data, api_key=api_key, pending_relations=pending_relations\n        )\n\n        self.tax_rates.set(\n            cls._stripe_object_to_tax_rates(\n                target_cls=TaxRate, data=data, api_key=api_key\n            )\n        )\n\n\nclass SubscriptionSchedule(StripeModel):\n    \"\"\"\n    Subscription schedules allow you to create and manage the lifecycle\n    of a subscription by predefining expected changes.\n\n    Stripe documentation: https://stripe.com/docs/api/subscription_schedules?lang=python\n    \"\"\"\n\n    stripe_class = stripe.SubscriptionSchedule\n    stripe_dashboard_item_name = \"subscription_schedules\"\n\n    canceled_at = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"Time at which the subscription schedule was canceled.\",\n    )\n    completed_at = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"Time at which the subscription schedule was completed.\",\n    )\n    current_phase = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Object representing the start and end dates for the \"\n        \"current phase of the subscription schedule, if it is `active`.\",\n    )\n    customer = models.ForeignKey(\n        \"Customer\",\n        on_delete=models.CASCADE,\n        related_name=\"schedules\",\n        help_text=\"The customer who owns the subscription schedule.\",\n    )\n    default_settings = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Object representing the subscription schedule's default settings.\",\n    )\n    end_behavior = StripeEnumField(\n        enum=enums.SubscriptionScheduleEndBehavior,\n        help_text=\"Behavior of the subscription schedule and underlying \"\n        \"subscription when it ends.\",\n    )\n    phases = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Configuration for the subscription schedule's phases.\",\n    )\n    released_at = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"Time at which the subscription schedule was released.\",\n    )\n    released_subscription = models.ForeignKey(\n        \"Subscription\",\n        null=True,\n        blank=True,\n        on_delete=models.SET_NULL,\n        related_name=\"released_schedules\",\n        help_text=\"The subscription once managed by this subscription schedule \"\n        \"(if it is released).\",\n    )\n    status = StripeEnumField(\n        enum=enums.SubscriptionScheduleStatus,\n        help_text=\"The present status of the subscription schedule. Possible \"\n        \"values are `not_started`, `active`, `completed`, `released`, and \"\n        \"`canceled`.\",\n    )\n    subscription = models.ForeignKey(\n        \"Subscription\",\n        null=True,\n        blank=True,\n        on_delete=models.SET_NULL,\n        related_name=\"subscriptions\",\n        help_text=\"ID of the subscription managed by the subscription schedule.\",\n    )\n\n    def release(self, api_key=None, stripe_account=None, **kwargs):\n        \"\"\"\n        Releases the subscription schedule immediately, which will stop scheduling\n        of its phases, but leave any existing subscription in place.\n        A schedule can only be released if its status is not_started or active.\n        If the subscription schedule is currently associated with a subscription,\n        releasing it will remove its subscription property and set the subscription’s\n        ID to the released_subscription property\n        and returns the Released SubscriptionSchedule.\n        :param api_key: The api key to use for this request.\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n\n        api_key = api_key or self.default_api_key\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        stripe_subscription_schedule = self.stripe_class.release(\n            self.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        )\n\n        return SubscriptionSchedule.sync_from_stripe_data(stripe_subscription_schedule)\n\n    def cancel(self, api_key=None, stripe_account=None, **kwargs):\n        \"\"\"\n        Cancels a subscription schedule and its associated subscription immediately\n        (if the subscription schedule has an active subscription). A subscription schedule can only be canceled if its status is not_started or active\n        and returns the Canceled SubscriptionSchedule.\n        :param api_key: The api key to use for this request.\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n\n        api_key = api_key or self.default_api_key\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        stripe_subscription_schedule = self.stripe_class.cancel(\n            self.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        )\n\n        return SubscriptionSchedule.sync_from_stripe_data(stripe_subscription_schedule)\n\n    def update(self, api_key=None, stripe_account=None, **kwargs):\n        \"\"\"\n        Updates an existing subscription schedule\n        and returns the updated SubscriptionSchedule.\n        :param api_key: The api key to use for this request.\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n        stripe_subscription_schedule = self._api_update(\n            api_key=api_key, stripe_account=stripe_account, **kwargs\n        )\n        return SubscriptionSchedule.sync_from_stripe_data(stripe_subscription_schedule)\n\n\nclass ShippingRate(StripeModel):\n    \"\"\"\n    Shipping rates describe the price of shipping presented\n    to your customers and can be applied to Checkout Sessions\n    to collect shipping costs.\n\n    Stripe documentation: https://stripe.com/docs/api/shipping_rates\n    \"\"\"\n\n    stripe_class = stripe.ShippingRate\n    stripe_dashboard_item_name = \"shipping-rates\"\n    description = None\n\n    active = models.BooleanField(\n        default=True,\n        help_text=\"Whether the shipping rate can be used for new purchases. Defaults to true\",\n    )\n    display_name = models.CharField(\n        max_length=50,\n        default=\"\",\n        blank=True,\n        help_text=\"The name of the shipping rate, meant to be displayable to the customer. This will appear on CheckoutSessions.\",\n    )\n    fixed_amount = JSONField(\n        help_text=\"Describes a fixed amount to charge for shipping. Must be present if type is fixed_amount\",\n    )\n    type = StripeEnumField(\n        enum=enums.ShippingRateType,\n        default=enums.ShippingRateType.fixed_amount,\n        help_text=_(\n            \"The type of calculation to use on the shipping rate. Can only be fixed_amount for now.\"\n        ),\n    )\n    delivery_estimate = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"The estimated range for how long shipping will take, meant to be displayable to the customer. This will appear on CheckoutSessions.\",\n    )\n    tax_behavior = StripeEnumField(\n        enum=enums.ShippingRateTaxBehavior,\n        help_text=_(\n            \"Specifies whether the rate is considered inclusive of taxes or exclusive of taxes.\"\n        ),\n    )\n    tax_code = StripeForeignKey(\n        \"TaxCode\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"The shipping tax code\",\n    )\n\n    class Meta(StripeModel.Meta):\n        verbose_name = \"Shipping Rate\"\n\n    def __str__(self):\n        amount = get_friendly_currency_amount(\n            self.fixed_amount.get(\"amount\") / 100, self.fixed_amount.get(\"currency\")\n        )\n        if self.active:\n            return f\"{self.display_name} - {amount} (Active)\"\n        else:\n            return f\"{self.display_name} - {amount} (Archived)\"\n\n\nclass TaxCode(StripeModel):\n    \"\"\"\n    Tax codes classify goods and services for tax purposes.\n\n    Stripe documentation: https://stripe.com/docs/api/tax_codes\n    \"\"\"\n\n    stripe_class = stripe.TaxCode\n    metadata = None\n\n    name = models.CharField(\n        max_length=128,\n        help_text=\"A short name for the tax code.\",\n    )\n\n    class Meta(StripeModel.Meta):\n        verbose_name = \"Tax Code\"\n\n    def __str__(self):\n        return f\"{self.name}: {self.id}\"\n\n    @classmethod\n    def _find_owner_account(cls, data, api_key=djstripe_settings.STRIPE_SECRET_KEY):\n        # Tax Codes do not belong to any Stripe Account\n        pass\n\n\nclass TaxId(StripeModel):\n    \"\"\"\n    Add one or multiple tax IDs to a customer.\n    A customer's tax IDs are displayed on invoices and\n    credit notes issued for the customer.\n\n    Stripe documentation: https://stripe.com/docs/api/customer_tax_ids?lang=python\n    \"\"\"\n\n    stripe_class = stripe.TaxId\n    description = None\n    metadata = None\n\n    country = models.CharField(\n        max_length=2,\n        help_text=\"Two-letter ISO code representing the country of the tax ID.\",\n    )\n    customer = StripeForeignKey(\n        \"djstripe.customer\", on_delete=models.CASCADE, related_name=\"tax_ids\"\n    )\n    type = StripeEnumField(\n        enum=enums.TaxIdType, help_text=\"The status of this subscription.\"\n    )\n    value = models.CharField(max_length=50, help_text=\"Value of the tax ID.\")\n    verification = JSONField(help_text=\"Tax ID verification information.\")\n\n    def __str__(self):\n        return f\"{enums.TaxIdType.humanize(self.type)} {self.value} ({self.verification.get('status')})\"\n\n    class Meta(StripeModel.Meta):\n        verbose_name = \"Tax ID\"\n\n    @classmethod\n    def _api_create(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        \"\"\"\n        Call the stripe API's create operation for this model.\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        \"\"\"\n\n        if not kwargs.get(\"id\"):\n            raise KeyError(\"Customer Object ID is missing\")\n\n        try:\n            Customer.objects.get(id=kwargs[\"id\"])\n        except Customer.DoesNotExist:\n            raise\n\n        return stripe.Customer.create_tax_id(api_key=api_key, **kwargs)\n\n    def api_retrieve(self, api_key=None, stripe_account=None):\n        \"\"\"\n        Call the stripe API's retrieve operation for this model.\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n        nested_id = self.id\n        id = self.customer.id\n\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        return stripe.Customer.retrieve_tax_id(\n            id=id,\n            nested_id=nested_id,\n            api_key=api_key or self.default_api_key,\n            expand=self.expand_fields,\n            stripe_account=stripe_account,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @classmethod\n    def api_list(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        \"\"\"\n        Call the stripe API's list operation for this model.\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        See Stripe documentation for accepted kwargs for each object.\n        :returns: an iterator over all items in the query\n        \"\"\"\n        return stripe.Customer.list_tax_ids(\n            api_key=api_key,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        ).auto_paging_iter()\n\n\nclass TaxRate(StripeModel):\n    \"\"\"\n    Tax rates can be applied to invoices and subscriptions to collect tax.\n\n    Stripe documentation: https://stripe.com/docs/api/tax_rates?lang=python\n    \"\"\"\n\n    stripe_class = stripe.TaxRate\n    stripe_dashboard_item_name = \"tax-rates\"\n\n    active = models.BooleanField(\n        default=True,\n        help_text=\"Defaults to true. When set to false, this tax rate cannot be \"\n        \"applied to objects in the API, but will still be applied to subscriptions \"\n        \"and invoices that already have it set.\",\n    )\n    country = models.CharField(\n        max_length=2,\n        default=\"\",\n        blank=True,\n        help_text=\"Two-letter country code.\",\n    )\n    display_name = models.CharField(\n        max_length=50,\n        default=\"\",\n        blank=True,\n        help_text=\"The display name of the tax rates as it will appear to your \"\n        \"customer on their receipt email, PDF, and the hosted invoice page.\",\n    )\n    inclusive = models.BooleanField(\n        help_text=\"This specifies if the tax rate is inclusive or exclusive.\"\n    )\n    jurisdiction = models.CharField(\n        max_length=50,\n        default=\"\",\n        blank=True,\n        help_text=\"The jurisdiction for the tax rate.\",\n    )\n    percentage = StripePercentField(\n        decimal_places=4,\n        max_digits=7,\n        help_text=\"This represents the tax rate percent out of 100.\",\n    )\n    state = models.CharField(\n        max_length=2,\n        default=\"\",\n        blank=True,\n        help_text=\"ISO 3166-2 subdivision code, without country prefix.\",\n    )\n    tax_type = models.CharField(\n        default=\"\",\n        blank=True,\n        max_length=50,\n        help_text=\"The high-level tax type, such as vat, gst, sales_tax or custom.\",\n    )\n\n    def __str__(self):\n        return f\"{self.display_name} at {self.percentage}%\"\n\n    class Meta(StripeModel.Meta):\n        verbose_name = \"Tax Rate\"\n\n\nclass UsageRecord(StripeModel):\n    \"\"\"\n    Usage records allow you to continually report usage and metrics to\n    Stripe for metered billing of plans.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#usage_records\n    \"\"\"\n\n    description = None\n    metadata = None\n\n    stripe_class = stripe.UsageRecord\n\n    quantity = models.PositiveIntegerField(\n        help_text=(\n            \"The quantity of the plan to which the customer should be subscribed.\"\n        )\n    )\n    subscription_item = StripeForeignKey(\n        \"SubscriptionItem\",\n        on_delete=models.CASCADE,\n        related_name=\"usage_records\",\n        help_text=\"The subscription item this usage record contains data for.\",\n    )\n\n    timestamp = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"The timestamp for the usage event. This timestamp must be within the current billing period of the subscription of the provided subscription_item.\",\n    )\n\n    action = StripeEnumField(\n        enum=enums.UsageAction,\n        default=enums.UsageAction.increment,\n        help_text=\"When using increment the specified quantity will be added to the usage at the specified timestamp. The set action will overwrite the usage quantity at that timestamp. If the subscription has billing thresholds, increment is the only allowed value.\",\n    )\n\n    def __str__(self):\n        return f\"Usage for {self.subscription_item} ({self.action}) is {self.quantity}\"\n\n    @classmethod\n    def _api_create(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        \"\"\"\n        Call the stripe API's create operation for this model.\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        \"\"\"\n\n        if not kwargs.get(\"id\"):\n            raise KeyError(\"SubscriptionItem Object ID is missing\")\n\n        try:\n            SubscriptionItem.objects.get(id=kwargs[\"id\"])\n        except SubscriptionItem.DoesNotExist:\n            raise\n\n        usage_stripe_data = stripe.SubscriptionItem.create_usage_record(\n            api_key=api_key, **kwargs\n        )\n\n        # ! Hack: there is no way to retrieve a UsageRecord object from Stripe,\n        # ! which is why we create and sync it right here\n        cls.sync_from_stripe_data(usage_stripe_data, api_key=api_key)\n\n        return usage_stripe_data\n\n    @classmethod\n    def create(cls, **kwargs):\n        \"\"\"\n        A wrapper around _api_create() to allow one to create and sync UsageRecord Objects\n        \"\"\"\n        return cls._api_create(**kwargs)\n\n\nclass UsageRecordSummary(StripeModel):\n    \"\"\"\n    Usage record summaries provides usage information that's been summarized\n    from multiple usage records and over a subscription billing period\n    (e.g., 15 usage records in the month of September).\n    Since new usage records can still be added, the returned summary information for the subscription item's ID\n    should be seen as unstable until the subscription billing period ends.\n\n    Stripe documentation: https://stripe.com/docs/api/usage_records/subscription_item_summary_list?lang=python\n    \"\"\"\n\n    stripe_class = stripe.UsageRecordSummary\n\n    description = None\n    metadata = None\n\n    invoice = StripeForeignKey(\n        \"Invoice\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        related_name=\"usage_record_summaries\",\n    )\n    period = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Subscription Billing period for the SubscriptionItem\",\n    )\n    period_end = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"End of the Subscription Billing period for the SubscriptionItem\",\n    )\n    period_start = StripeDateTimeField(\n        null=True,\n        blank=True,\n        help_text=\"Start of the Subscription Billing period for the SubscriptionItem\",\n    )\n    total_usage = models.PositiveIntegerField(\n        help_text=(\n            \"The quantity of the plan to which the customer should be subscribed.\"\n        )\n    )\n    subscription_item = StripeForeignKey(\n        \"SubscriptionItem\",\n        on_delete=models.CASCADE,\n        related_name=\"usage_record_summaries\",\n        help_text=\"The subscription item this usage record contains data for.\",\n    )\n\n    def __str__(self):\n        return f\"Usage Summary for {self.subscription_item} ({self.invoice}) is {self.total_usage}\"\n\n    @classmethod\n    def _manipulate_stripe_object_hook(cls, data):\n        data[\"period_start\"] = data[\"period\"][\"start\"]\n        data[\"period_end\"] = data[\"period\"][\"end\"]\n\n        return data\n\n    @classmethod\n    def api_list(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        \"\"\"\n        Call the stripe API's list operation for this model.\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n\n        See Stripe documentation for accepted kwargs for each object.\n\n        :returns: an iterator over all items in the query\n        \"\"\"\n        if not kwargs.get(\"id\"):\n            raise KeyError(\"SubscriptionItem Object ID is missing\")\n\n        try:\n            SubscriptionItem.objects.get(id=kwargs[\"id\"])\n        except SubscriptionItem.DoesNotExist:\n            raise\n\n        return stripe.SubscriptionItem.list_usage_record_summaries(\n            api_key=api_key,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        ).auto_paging_iter()\n"
  },
  {
    "path": "djstripe/models/checkout.py",
    "content": "import stripe\nfrom django.db import models\n\nfrom djstripe.settings import djstripe_settings\n\nfrom .. import enums\nfrom ..fields import JSONField, StripeEnumField, StripeForeignKey\nfrom .base import StripeModel\n\n\nclass Session(StripeModel):\n    \"\"\"\n    A Checkout Session represents your customer's session as they pay\n    for one-time purchases or subscriptions through Checkout.\n\n    Stripe documentation: https://stripe.com/docs/api/checkout/sessions?lang=python\n    \"\"\"\n\n    stripe_class = stripe.checkout.Session\n\n    billing_address_collection = StripeEnumField(\n        enum=enums.SessionBillingAddressCollection,\n        blank=True,\n        help_text=(\n            \"The value (auto or required) for whether Checkout\"\n            \"collected the customer's billing address.\"\n        ),\n    )\n    cancel_url = models.TextField(\n        max_length=5000,\n        blank=True,\n        help_text=(\n            \"The URL the customer will be directed to if they\"\n            \"decide to cancel payment and return to your website.\"\n        ),\n    )\n    client_reference_id = models.TextField(\n        max_length=5000,\n        blank=True,\n        help_text=(\n            \"A unique string to reference the Checkout Session.\"\n            \"This can be a customer ID, a cart ID, or similar, and\"\n            \"can be used to reconcile the session with your internal systems.\"\n        ),\n    )\n    customer = StripeForeignKey(\n        \"Customer\",\n        null=True,\n        on_delete=models.SET_NULL,\n        help_text=(\"Customer this Checkout is for if one exists.\"),\n    )\n    customer_email = models.CharField(\n        max_length=255,\n        blank=True,\n        help_text=(\n            \"If provided, this value will be used when the Customer object is created.\"\n        ),\n    )\n    display_items = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\"The line items, plans, or SKUs purchased by the customer.\"),\n    )\n    locale = models.CharField(\n        max_length=255,\n        blank=True,\n        help_text=(\n            \"The IETF language tag of the locale Checkout is displayed in.\"\n            \"If blank or auto, the browser's locale is used.\"\n        ),\n    )\n    mode = StripeEnumField(\n        enum=enums.SessionMode,\n        blank=True,\n        help_text=\"The mode of the Checkout Session, \"\n        \"one of payment, setup, or subscription.\",\n    )\n    payment_intent = StripeForeignKey(\n        \"PaymentIntent\",\n        null=True,\n        on_delete=models.SET_NULL,\n        help_text=(\"PaymentIntent created if SKUs or line items were provided.\"),\n    )\n    payment_method_types = JSONField(\n        help_text=\"The list of payment method types (e.g. card) that this \"\n        \"Checkout Session is allowed to accept.\"\n    )\n    submit_type = StripeEnumField(\n        enum=enums.SubmitTypeStatus,\n        blank=True,\n        help_text=\"Describes the type of transaction being performed by Checkout\"\n        \"in order to customize relevant text on the page, such as the submit button.\",\n    )\n    subscription = StripeForeignKey(\n        \"Subscription\",\n        null=True,\n        on_delete=models.SET_NULL,\n        help_text=(\"Subscription created if one or more plans were provided.\"),\n    )\n    success_url = models.TextField(\n        max_length=5000,\n        blank=True,\n        help_text=(\n            \"The URL the customer will be directed to after the payment or subscription\"\n            \"creation is successful.\"\n        ),\n    )\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        pending_relations=None,\n    ):\n        from ..event_handlers import update_customer_helper\n\n        super()._attach_objects_post_save_hook(\n            cls, data, api_key=api_key, pending_relations=pending_relations\n        )\n\n        # only update if customer and metadata exist\n        if self.customer and self.metadata:\n            key = djstripe_settings.SUBSCRIBER_CUSTOMER_KEY\n            current_value = self.metadata.get(key)\n\n            # only update if metadata has the SUBSCRIBER_CUSTOMER_KEY\n            if current_value:\n                metadata = {key: current_value}\n\n                # Update the customer with ONLY the customer specific metadata\n                update_customer_helper(\n                    metadata,\n                    self.customer.id,\n                    key,\n                )\n\n                # Update metadata in the Upstream Customer Object on Stripe\n                self.customer._api_update(metadata=metadata)\n"
  },
  {
    "path": "djstripe/models/connect.py",
    "content": "import stripe\nfrom django.db import models\n\nfrom djstripe.utils import get_friendly_currency_amount\n\nfrom .. import enums\nfrom ..fields import (\n    JSONField,\n    StripeCurrencyCodeField,\n    StripeDecimalCurrencyAmountField,\n    StripeEnumField,\n    StripeForeignKey,\n    StripeIdField,\n    StripeQuantumCurrencyAmountField,\n)\nfrom ..managers import TransferManager\nfrom ..settings import djstripe_settings\nfrom .base import StripeBaseModel, StripeModel\n\n\n# TODO Implement Full Webhook event support for ApplicationFee and ApplicationFee Refund Objects\nclass ApplicationFee(StripeModel):\n    \"\"\"\n    When you collect a transaction fee on top of a charge made for your\n    user (using Connect), an ApplicationFee is created in your account.\n\n    Please note the model field charge exists on the Stripe Connected Account\n    while the application_fee modelfield on Charge model exists on the Platform Account!\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#application_fees\n    \"\"\"\n\n    stripe_class = stripe.ApplicationFee\n    account = StripeForeignKey(\n        \"Account\",\n        on_delete=models.PROTECT,\n        related_name=\"application_fees\",\n        help_text=\"ID of the Stripe account this fee was taken from.\",\n    )\n    amount = StripeQuantumCurrencyAmountField(help_text=\"Amount earned, in cents.\")\n    amount_refunded = StripeQuantumCurrencyAmountField(\n        help_text=\"Amount in cents refunded (can be less than the amount attribute \"\n        \"on the fee if a partial refund was issued)\"\n    )\n    # TODO application = ...\n    # balance_transaction exists on the platform account\n    balance_transaction = StripeForeignKey(\n        \"BalanceTransaction\",\n        on_delete=models.CASCADE,\n        help_text=\"Balance transaction that describes the impact on your account\"\n        \" balance.\",\n    )\n    # charge exists on the Stripe Connected Account and not the Platform Account\n    charge = StripeForeignKey(\n        \"Charge\",\n        on_delete=models.CASCADE,\n        help_text=\"The charge that the application fee was taken from.\",\n    )\n    currency = StripeCurrencyCodeField()\n    # TODO originating_transaction = ... (refs. both Charge and Transfer)\n    refunded = models.BooleanField(\n        help_text=(\n            \"Whether the fee has been fully refunded. If the fee is only \"\n            \"partially refunded, this attribute will still be false.\"\n        )\n    )\n\n\n# TODO Add Tests\nclass ApplicationFeeRefund(StripeModel):\n    \"\"\"\n    ApplicationFeeRefund objects allow you to refund an ApplicationFee that\n    has previously been created but not yet refunded.\n    Funds will be refunded to the Stripe account from which the fee was\n    originally collected.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#fee_refunds\n    \"\"\"\n\n    description = None\n    stripe_class = stripe.ApplicationFeeRefund\n\n    amount = StripeQuantumCurrencyAmountField(help_text=\"Amount refunded, in cents.\")\n    balance_transaction = StripeForeignKey(\n        \"BalanceTransaction\",\n        on_delete=models.CASCADE,\n        help_text=\"Balance transaction that describes the impact on your account \"\n        \"balance.\",\n    )\n    currency = StripeCurrencyCodeField()\n    fee = StripeForeignKey(\n        \"ApplicationFee\",\n        on_delete=models.CASCADE,\n        related_name=\"refunds\",\n        help_text=\"The application fee that was refunded\",\n    )\n\n\nclass CountrySpec(StripeBaseModel):\n    \"\"\"\n    Stripe documentation: https://stripe.com/docs/api?lang=python#country_specs\n    \"\"\"\n\n    stripe_class = stripe.CountrySpec\n\n    id = models.CharField(max_length=2, primary_key=True, serialize=True)\n\n    default_currency = StripeCurrencyCodeField(\n        help_text=(\n            \"The default currency for this country. \"\n            \"This applies to both payment methods and bank accounts.\"\n        )\n    )\n    supported_bank_account_currencies = JSONField(\n        help_text=\"Currencies that can be accepted in the specific country\"\n        \" (for transfers).\"\n    )\n    supported_payment_currencies = JSONField(\n        help_text=\"Currencies that can be accepted in the specified country\"\n        \" (for payments).\"\n    )\n    supported_payment_methods = JSONField(\n        help_text=\"Payment methods available in the specified country.\"\n    )\n    supported_transfer_countries = JSONField(\n        help_text=\"Countries that can accept transfers from the specified country.\"\n    )\n    verification_fields = JSONField(\n        help_text=\"Lists the types of verification data needed to keep an account open.\"\n    )\n\n    @classmethod\n    def sync_from_stripe_data(\n        cls, data, api_key=djstripe_settings.STRIPE_SECRET_KEY\n    ) -> \"CountrySpec\":\n        \"\"\"\n        Syncs this object from the stripe data provided.\n\n        Foreign keys will also be retrieved and synced recursively.\n\n        :param data: stripe object\n        :type data: dict\n        :rtype: cls\n        \"\"\"\n        data_id = data[\"id\"]\n\n        supported_fields = (\n            \"default_currency\",\n            \"supported_bank_account_currencies\",\n            \"supported_payment_currencies\",\n            \"supported_payment_methods\",\n            \"supported_transfer_countries\",\n            \"verification_fields\",\n        )\n\n        instance, created = cls.objects.get_or_create(\n            id=data_id,\n            defaults={k: data[k] for k in supported_fields},\n        )\n\n        return instance\n\n    def api_retrieve(self, api_key: str = None, stripe_account=None):\n        if api_key is None:\n            api_key = djstripe_settings.get_default_api_key(livemode=None)\n\n        return self.stripe_class.retrieve(\n            id=self.id,\n            api_key=api_key,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            stripe_account=stripe_account,\n        )\n\n\nclass Transfer(StripeModel):\n    \"\"\"\n    When Stripe sends you money or you initiate a transfer to a bank account,\n    debit card, or connected Stripe account, a transfer object will be created.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#transfers\n    \"\"\"\n\n    stripe_class = stripe.Transfer\n    expand_fields = [\"balance_transaction\"]\n    stripe_dashboard_item_name = \"transfers\"\n\n    objects = TransferManager()\n\n    amount = StripeDecimalCurrencyAmountField(help_text=\"The amount transferred\")\n    amount_reversed = StripeDecimalCurrencyAmountField(\n        null=True,\n        blank=True,\n        help_text=\"The amount (as decimal) reversed (can be less than the amount \"\n        \"attribute on the transfer if a partial reversal was issued).\",\n    )\n    balance_transaction = StripeForeignKey(\n        \"BalanceTransaction\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        help_text=\"Balance transaction that describes the impact on your account\"\n        \" balance.\",\n    )\n    currency = StripeCurrencyCodeField()\n\n    destination = StripeIdField(\n        max_length=255,\n        null=True,\n        help_text=\"ID of the bank account, card, or Stripe account the transfer was sent to.\",\n    )\n\n    # todo implement payment model (for some reason py ids are showing up in the charge model)\n    destination_payment = StripeIdField(\n        null=True,\n        blank=True,\n        help_text=\"If the destination is a Stripe account, this will be the ID of the \"\n        \"payment that the destination account received for the transfer.\",\n    )\n    reversed = models.BooleanField(\n        default=False,\n        help_text=\"Whether or not the transfer has been fully reversed. \"\n        \"If the transfer is only partially reversed, this attribute will still \"\n        \"be false.\",\n    )\n    source_transaction = StripeIdField(\n        null=True,\n        help_text=\"ID of the charge (or other transaction) that was used to fund \"\n        \"the transfer. If null, the transfer was funded from the available balance.\",\n    )\n    source_type = StripeEnumField(\n        enum=enums.LegacySourceType,\n        help_text=\"The source balance from which this transfer came.\",\n    )\n    transfer_group = models.CharField(\n        max_length=255,\n        default=\"\",\n        blank=True,\n        help_text=\"A string that identifies this transaction as part of a group.\",\n    )\n\n    @property\n    def fee(self):\n        if self.balance_transaction:\n            return self.balance_transaction.fee\n\n    def __str__(self):\n        amount = get_friendly_currency_amount(self.amount, self.currency)\n        if self.reversed:\n            # Complete Reversal\n            return f\"{amount} Reversed\"\n        elif self.amount_reversed:\n            # Partial Reversal\n            return f\"{amount} Partially Reversed\"\n        # No Reversal\n        return f\"{amount}\"\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        pending_relations=None,\n    ):\n        \"\"\"\n        Iterate over reversals on the Transfer object to create and/or sync\n        TransferReversal objects\n        \"\"\"\n\n        super()._attach_objects_post_save_hook(\n            cls, data, api_key=api_key, pending_relations=pending_relations\n        )\n\n        # Transfer Reversals exist as a list on the Transfer Object\n        for reversals_data in data.get(\"reversals\").auto_paging_iter():\n            TransferReversal.sync_from_stripe_data(reversals_data, api_key=api_key)\n\n    def get_stripe_dashboard_url(self) -> str:\n        return (\n            f\"{self._get_base_stripe_dashboard_url()}\"\n            f\"connect/{self.stripe_dashboard_item_name}/{self.id}\"\n        )\n\n\n# TODO Add Tests\nclass TransferReversal(StripeModel):\n    \"\"\"\n    Stripe documentation: https://stripe.com/docs/api?lang=python#transfer_reversals\n    \"\"\"\n\n    expand_fields = [\"balance_transaction\", \"transfer\"]\n    stripe_dashboard_item_name = \"transfer_reversals\"\n\n    # TransferReversal classmethods are derived from\n    # and attached to the stripe.Transfer class\n    stripe_class = stripe.Transfer\n\n    amount = StripeQuantumCurrencyAmountField(help_text=\"Amount, in cents.\")\n    balance_transaction = StripeForeignKey(\n        \"BalanceTransaction\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        related_name=\"transfer_reversals\",\n        help_text=\"Balance transaction that describes the impact on your account \"\n        \"balance.\",\n    )\n    currency = StripeCurrencyCodeField()\n    transfer = StripeForeignKey(\n        \"Transfer\",\n        on_delete=models.CASCADE,\n        help_text=\"The transfer that was reversed.\",\n        related_name=\"reversals\",\n    )\n\n    def __str__(self):\n        return str(self.transfer)\n\n    @classmethod\n    def _api_create(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        \"\"\"\n        Call the stripe API's create operation for this model.\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        \"\"\"\n\n        if not kwargs.get(\"id\"):\n            raise KeyError(\"Transfer Object ID is missing\")\n\n        try:\n            Transfer.objects.get(id=kwargs[\"id\"])\n        except Transfer.DoesNotExist:\n            raise\n\n        return stripe.Transfer.create_reversal(\n            api_key=api_key,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        )\n\n    def api_retrieve(self, api_key=None, stripe_account=None):\n        \"\"\"\n        Call the stripe API's retrieve operation for this model.\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n        nested_id = self.id\n        id = self.transfer.id\n\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        return stripe.Transfer.retrieve_reversal(\n            id=id,\n            nested_id=nested_id,\n            api_key=api_key or self.default_api_key,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            expand=self.expand_fields,\n            stripe_account=stripe_account,\n        )\n\n    @classmethod\n    def api_list(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        \"\"\"\n        Call the stripe API's list operation for this model.\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        See Stripe documentation for accepted kwargs for each object.\n        :returns: an iterator over all items in the query\n        \"\"\"\n        return stripe.Transfer.list_reversals(\n            api_key=api_key,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        ).auto_paging_iter()\n\n    @classmethod\n    def is_valid_object(cls, data):\n        \"\"\"\n        Returns whether the data is a valid object for the class\n        \"\"\"\n        return data and data.get(\"object\") == \"transfer_reversal\"\n"
  },
  {
    "path": "djstripe/models/core.py",
    "content": "from decimal import Decimal\nfrom typing import Optional, Union\n\nimport stripe\nfrom django.apps import apps\nfrom django.db import models, transaction\nfrom django.utils import timezone\nfrom django.utils.functional import cached_property\nfrom django.utils.text import format_lazy\nfrom django.utils.translation import gettext_lazy as _\nfrom stripe.error import InvalidRequestError\n\nfrom .. import enums, webhooks\nfrom ..exceptions import MultipleSubscriptionException\nfrom ..fields import (\n    JSONField,\n    PaymentMethodForeignKey,\n    StripeCurrencyCodeField,\n    StripeDateTimeField,\n    StripeDecimalCurrencyAmountField,\n    StripeEnumField,\n    StripeForeignKey,\n    StripeIdField,\n    StripeQuantumCurrencyAmountField,\n)\nfrom ..managers import ChargeManager\nfrom ..settings import djstripe_settings\nfrom ..signals import WEBHOOK_SIGNALS\nfrom ..utils import get_friendly_currency_amount, get_id_from_stripe_data\nfrom .base import IdempotencyKey, StripeModel, logger\n\n\ndef _sanitise_price(price=None, plan=None, **kwargs):\n    \"\"\"\n    Helper for Customer.subscribe()\n    \"\"\"\n\n    if price and plan:\n        raise TypeError(\"price and plan arguments cannot both be defined.\")\n\n    price = price or plan\n\n    if not price:\n        raise TypeError(\"you need to set either price or plan\")\n\n    # Convert Price to id\n    if isinstance(price, StripeModel):\n        price = price.id\n\n    return price, kwargs\n\n\nclass BalanceTransaction(StripeModel):\n    \"\"\"\n    A single transaction that updates the Stripe balance.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#balance_transaction_object\n    \"\"\"\n\n    stripe_class = stripe.BalanceTransaction\n\n    amount = StripeQuantumCurrencyAmountField(\n        help_text=\"Gross amount of the transaction, in cents.\"\n    )\n    available_on = StripeDateTimeField(\n        help_text=(\n            \"The date the transaction's net funds \"\n            \"will become available in the Stripe balance.\"\n        )\n    )\n    currency = StripeCurrencyCodeField()\n    exchange_rate = models.DecimalField(null=True, decimal_places=6, max_digits=8)\n    fee = StripeQuantumCurrencyAmountField(\n        help_text=\"Fee (in cents) paid for this transaction.\"\n    )\n    fee_details = JSONField()\n    net = StripeQuantumCurrencyAmountField(\n        help_text=\"Net amount of the transaction, in cents.\"\n    )\n    source = StripeIdField()\n    reporting_category = StripeEnumField(\n        enum=enums.BalanceTransactionReportingCategory,\n        help_text=(\n            \"More information: https://stripe.com/docs/reports/reporting-categories\"\n        ),\n    )\n    status = StripeEnumField(enum=enums.BalanceTransactionStatus)\n    type = StripeEnumField(enum=enums.BalanceTransactionType)\n\n    def __str__(self):\n        amount = get_friendly_currency_amount(self.amount / 100, self.currency)\n        status = enums.BalanceTransactionStatus.humanize(self.status)\n        return f\"{amount} ({status})\"\n\n    def get_source_class(self):\n        try:\n            return apps.get_model(\"djstripe\", self.type)\n        except LookupError:\n            raise\n\n    def get_source_instance(self):\n        return self.get_source_class().objects.get(id=self.source)\n\n    def get_stripe_dashboard_url(self):\n        return self.get_source_instance().get_stripe_dashboard_url()\n\n\nclass Charge(StripeModel):\n    \"\"\"\n    To charge a credit or a debit card, you create a charge object. You can\n    retrieve and refund individual charges as well as list all charges. Charges\n    are identified by a unique random ID.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#charges\n    \"\"\"\n\n    stripe_class = stripe.Charge\n    expand_fields = [\"balance_transaction\"]\n    stripe_dashboard_item_name = \"payments\"\n\n    amount = StripeDecimalCurrencyAmountField(help_text=\"Amount charged (as decimal).\")\n    amount_captured = StripeDecimalCurrencyAmountField(\n        null=True,\n        help_text=(\n            \"Amount (as decimal) captured (can be less than the amount attribute \"\n            \"on the charge if a partial capture was issued).\"\n        ),\n    )\n    amount_refunded = StripeDecimalCurrencyAmountField(\n        help_text=(\n            \"Amount (as decimal) refunded (can be less than the amount attribute on \"\n            \"the charge if a partial refund was issued).\"\n        )\n    )\n    application = models.CharField(\n        max_length=255,\n        blank=True,\n        help_text=\"ID of the Connect application that created the charge.\",\n    )\n    application_fee = StripeForeignKey(\n        \"ApplicationFee\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        related_name=\"fee_for_charge\",\n        help_text=\"The application fee (if any) for the charge.\",\n    )\n    application_fee_amount = StripeDecimalCurrencyAmountField(\n        null=True,\n        blank=True,\n        help_text=\"The amount (as decimal) of the application fee (if any) \"\n        \"requested for the charge.\",\n    )\n    balance_transaction = StripeForeignKey(\n        \"BalanceTransaction\",\n        on_delete=models.SET_NULL,\n        null=True,\n        help_text=(\n            \"The balance transaction that describes the impact of this charge \"\n            \"on your account balance (not including refunds or disputes).\"\n        ),\n    )\n    billing_details = JSONField(\n        null=True,\n        help_text=\"Billing information associated with the PaymentMethod at the \"\n        \"time of the transaction.\",\n    )\n    calculated_statement_descriptor = models.CharField(\n        max_length=22,\n        default=\"\",\n        help_text=\"The full statement descriptor that is passed to card networks, \"\n        \"and that is displayed on your customers' credit card and bank statements. \"\n        \"Allows you to see what the statement descriptor looks like after the \"\n        \"static and dynamic portions are combined.\",\n    )\n    captured = models.BooleanField(\n        default=False,\n        help_text=\"If the charge was created without capturing, this boolean \"\n        \"represents whether or not it is still uncaptured or has since been captured.\",\n    )\n    currency = StripeCurrencyCodeField(\n        help_text=\"The currency in which the charge was made.\"\n    )\n    customer = StripeForeignKey(\n        \"Customer\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        related_name=\"charges\",\n        help_text=\"The customer associated with this charge.\",\n    )\n\n    dispute = StripeForeignKey(\n        \"Dispute\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        related_name=\"charges\",\n        help_text=\"Details about the dispute if the charge has been disputed.\",\n    )\n    disputed = models.BooleanField(\n        default=False,\n        help_text=\"Whether the charge has been disputed.\",\n    )\n    failure_code = StripeEnumField(\n        enum=enums.ApiErrorCode,\n        default=\"\",\n        blank=True,\n        help_text=\"Error code explaining reason for charge failure if available.\",\n    )\n    failure_message = models.TextField(\n        max_length=5000,\n        default=\"\",\n        blank=True,\n        help_text=\"Message to user further explaining reason \"\n        \"for charge failure if available.\",\n    )\n    fraud_details = JSONField(\n        help_text=\"Hash with information on fraud assessments for the charge.\",\n        null=True,\n        blank=True,\n    )\n    invoice = StripeForeignKey(\n        \"Invoice\",\n        on_delete=models.CASCADE,\n        null=True,\n        related_name=\"charges\",\n        help_text=\"The invoice this charge is for if one exists.\",\n    )\n    # TODO: order (requires Order model)\n    on_behalf_of = StripeForeignKey(\n        \"Account\",\n        on_delete=models.CASCADE,\n        null=True,\n        blank=True,\n        related_name=\"charges\",\n        help_text=\"The account (if any) the charge was made on behalf of \"\n        \"without triggering an automatic transfer.\",\n    )\n    outcome = JSONField(\n        help_text=\"Details about whether or not the payment was accepted, and why.\",\n        null=True,\n        blank=True,\n    )\n    paid = models.BooleanField(\n        default=False,\n        help_text=\"True if the charge succeeded, \"\n        \"or was successfully authorized for later capture, False otherwise.\",\n    )\n    payment_intent = StripeForeignKey(\n        \"PaymentIntent\",\n        null=True,\n        on_delete=models.SET_NULL,\n        related_name=\"charges\",\n        help_text=\"PaymentIntent associated with this charge, if one exists.\",\n    )\n    payment_method = StripeForeignKey(\n        \"PaymentMethod\",\n        null=True,\n        on_delete=models.SET_NULL,\n        related_name=\"charges\",\n        help_text=\"PaymentMethod used in this charge.\",\n    )\n    payment_method_details = JSONField(\n        help_text=\"Details about the payment method at the time of the transaction.\",\n        null=True,\n        blank=True,\n    )\n    receipt_email = models.TextField(\n        max_length=800,  # yup, 800.\n        default=\"\",\n        blank=True,\n        help_text=\"The email address that the receipt for this charge was sent to.\",\n    )\n    receipt_number = models.CharField(\n        max_length=14,\n        default=\"\",\n        blank=True,\n        help_text=\"The transaction number that appears \"\n        \"on email receipts sent for this charge.\",\n    )\n    receipt_url = models.TextField(\n        max_length=5000,\n        default=\"\",\n        blank=True,\n        help_text=\"This is the URL to view the receipt for this charge. \"\n        \"The receipt is kept up-to-date to the latest state of the charge, \"\n        \"including any refunds. If the charge is for an Invoice, \"\n        \"the receipt will be stylized as an Invoice receipt.\",\n    )\n    refunded = models.BooleanField(\n        default=False,\n        help_text=\"Whether or not the charge has been fully refunded. \"\n        \"If the charge is only partially refunded, \"\n        \"this attribute will still be false.\",\n    )\n    # TODO: review (requires Review model)\n    shipping = JSONField(\n        null=True, blank=True, help_text=\"Shipping information for the charge\"\n    )\n    source = PaymentMethodForeignKey(\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        related_name=\"charges\",\n        help_text=\"The source used for this charge.\",\n    )\n    source_transfer = StripeForeignKey(\n        \"Transfer\",\n        null=True,\n        blank=True,\n        on_delete=models.CASCADE,\n        help_text=\"The transfer which created this charge. Only present if the \"\n        \"charge came from another Stripe account.\",\n        related_name=\"+\",\n    )\n    statement_descriptor = models.CharField(\n        max_length=22,\n        null=True,\n        blank=True,\n        help_text=\"For card charges, use statement_descriptor_suffix instead. \"\n        \"Otherwise, you can use this value as the complete description of a \"\n        \"charge on your customers' statements. Must contain at least one letter, \"\n        \"maximum 22 characters.\",\n    )\n    statement_descriptor_suffix = models.CharField(\n        max_length=22,\n        null=True,\n        blank=True,\n        help_text=\"Provides information about the charge that customers see on \"\n        \"their statements. Concatenated with the prefix (shortened descriptor) \"\n        \"or statement descriptor that's set on the account to form the \"\n        \"complete statement descriptor. \"\n        \"Maximum 22 characters for the concatenated descriptor.\",\n    )\n    status = StripeEnumField(\n        enum=enums.ChargeStatus, help_text=\"The status of the payment.\"\n    )\n    transfer = StripeForeignKey(\n        \"Transfer\",\n        on_delete=models.CASCADE,\n        null=True,\n        blank=True,\n        help_text=(\n            \"The transfer to the `destination` account (only applicable if \"\n            \"the charge was created using the `destination` parameter).\"\n        ),\n    )\n    transfer_data = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"An optional dictionary including the account to automatically \"\n        \"transfer to as part of a destination charge.\",\n    )\n    transfer_group = models.CharField(\n        max_length=255,\n        null=True,\n        blank=True,\n        help_text=\"A string that identifies this transaction as part of a group.\",\n    )\n\n    objects = ChargeManager()\n\n    def __str__(self):\n        amount = get_friendly_currency_amount(self.amount, self.currency)\n        return f\"{amount} ({self.human_readable_status})\"\n\n    @property\n    def fee(self):\n        if self.balance_transaction:\n            return self.balance_transaction.fee\n\n    @property\n    def human_readable_status(self) -> str:\n        if not self.captured:\n            return \"Uncaptured\"\n        elif self.disputed:\n            return \"Disputed\"\n        elif self.refunded:\n            return \"Refunded\"\n        return enums.ChargeStatus.humanize(self.status)\n\n    @property\n    def fraudulent(self) -> bool:\n        return (\n            self.fraud_details and list(self.fraud_details.values())[0] == \"fraudulent\"\n        )\n\n    def _calculate_refund_amount(self, amount: Optional[Decimal]) -> int:\n        \"\"\"\n        Returns the amount that can be refunded (in cents)\n        \"\"\"\n        eligible_to_refund = self.amount - (self.amount_refunded or 0)\n        amount_to_refund = (\n            min(eligible_to_refund, amount) if amount else eligible_to_refund\n        )\n\n        return int(amount_to_refund * 100)\n\n    def refund(self, amount: Decimal = None, reason: str = None) -> \"Charge\":\n        \"\"\"\n        Initiate a refund. Returns the charge object.\n\n        :param amount: A positive decimal amount representing how much of this charge\n            to refund. If amount is not provided, then this will be a full refund.\n            Can only refund up to the unrefunded amount remaining of the charge.\n        :param reason: String indicating the reason for the refund.\n            If set, possible values are ``duplicate``, ``fraudulent``,\n            and ``requested_by_customer``. Specifying ``fraudulent`` as the reason\n            when you believe the charge to be fraudulent will\n            help Stripe improve their fraud detection algorithms.\n        \"\"\"\n        charge_obj = self.api_retrieve().refund(\n            amount=self._calculate_refund_amount(amount=amount), reason=reason\n        )\n        return self.__class__.sync_from_stripe_data(\n            charge_obj, api_key=self.default_api_key\n        )\n\n    def capture(self, **kwargs) -> \"Charge\":\n        \"\"\"\n        Capture the payment of an existing, uncaptured, charge.\n        This is the second half of the two-step payment flow, where first you\n        created a charge with the capture option set to False.\n\n        See https://stripe.com/docs/api#capture_charge\n        \"\"\"\n\n        captured_charge = self.api_retrieve().capture(**kwargs)\n        return self.__class__.sync_from_stripe_data(\n            captured_charge, api_key=self.default_api_key\n        )\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        pending_relations=None,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    ):\n        super()._attach_objects_post_save_hook(\n            cls, data, pending_relations=pending_relations, api_key=api_key\n        )\n\n        cls._stripe_object_to_refunds(\n            target_cls=Refund, data=data, charge=self, api_key=api_key\n        )\n\n\n# TODO Add Tests\nclass Mandate(StripeModel):\n    \"\"\"\n    A Mandate is a record of the permission a customer has given you to debit their payment method.\n\n    https://stripe.com/docs/api/mandates\n    \"\"\"\n\n    stripe_class = stripe.Mandate\n\n    customer_acceptance = JSONField(\n        help_text=\"Details about the customer's acceptance of the mandate.\"\n    )\n    payment_method = StripeForeignKey(\"paymentmethod\", on_delete=models.CASCADE)\n    payment_method_details = JSONField(\n        help_text=\"Additional mandate information specific to the payment method type.\"\n    )\n    status = StripeEnumField(\n        enum=enums.MandateStatus,\n        help_text=\"The status of the mandate, which indicates whether it can be used to initiate a payment.\",\n    )\n    type = StripeEnumField(\n        enum=enums.MandateType,\n        help_text=\"The status of the mandate, which indicates whether it can be used to initiate a payment.\",\n    )\n    multi_use = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"If this is a `multi_use` mandate, this hash contains details about the mandate.\",\n    )\n    single_use = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"If this is a `single_use` mandate, this hash contains details about the mandate.\",\n    )\n\n\nclass Product(StripeModel):\n    \"\"\"\n    Products describe the specific goods or services you offer to your customers.\n    For example, you might offer a Standard and Premium version of your goods or service;\n    each version would be a separate Product. They can be used in conjunction with\n    Prices to configure pricing in Payment Links, Checkout, and Subscriptions.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#products\n    \"\"\"\n\n    stripe_class = stripe.Product\n    stripe_dashboard_item_name = \"products\"\n\n    # Fields applicable to both `good` and `service`\n    name = models.TextField(\n        max_length=5000,\n        help_text=(\n            \"The product's name, meant to be displayable to the customer. \"\n            \"Applicable to both `service` and `good` types.\"\n        ),\n    )\n    default_price = StripeForeignKey(\n        \"Price\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        related_name=\"products\",\n        help_text=\"The default price this product is associated with.\",\n    )\n    type = StripeEnumField(\n        enum=enums.ProductType,\n        help_text=(\n            \"The type of the product. The product is either of type `good`, which is \"\n            \"eligible for use with Orders and SKUs, or `service`, which is eligible \"\n            \"for use with Subscriptions and Plans.\"\n        ),\n    )\n\n    # Fields applicable to `good` only\n    active = models.BooleanField(\n        null=True,\n        help_text=(\n            \"Whether the product is currently available for purchase. \"\n            \"Only applicable to products of `type=good`.\"\n        ),\n    )\n    attributes = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"A list of up to 5 attributes that each SKU can provide values for \"\n            '(e.g., `[\"color\", \"size\"]`). Only applicable to products of `type=good`.'\n        ),\n    )\n    caption = models.TextField(\n        default=\"\",\n        blank=True,\n        max_length=5000,\n        help_text=(\n            \"A short one-line description of the product, meant to be displayable\"\n            \"to the customer. Only applicable to products of `type=good`.\"\n        ),\n    )\n    deactivate_on = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"An array of connect application identifiers that cannot purchase \"\n            \"this product. Only applicable to products of `type=good`.\"\n        ),\n    )\n    images = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"A list of up to 8 URLs of images for this product, meant to be \"\n            \"displayable to the customer. Only applicable to products of `type=good`.\"\n        ),\n    )\n    package_dimensions = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"The dimensions of this product for shipping purposes. \"\n            \"A SKU associated with this product can override this value by having its \"\n            \"own `package_dimensions`. Only applicable to products of `type=good`.\"\n        ),\n    )\n    shippable = models.BooleanField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Whether this product is a shipped good. \"\n            \"Only applicable to products of `type=good`.\"\n        ),\n    )\n    url = models.CharField(\n        max_length=799,\n        null=True,\n        blank=True,\n        help_text=(\n            \"A URL of a publicly-accessible webpage for this product. \"\n            \"Only applicable to products of `type=good`.\"\n        ),\n    )\n\n    # Fields available to `service` only\n    statement_descriptor = models.CharField(\n        max_length=22,\n        default=\"\",\n        blank=True,\n        help_text=(\n            \"Extra information about a product which will appear on your customer's \"\n            \"credit card statement. In the case that multiple products are billed at \"\n            \"once, the first statement descriptor will be used. \"\n            \"Only available on products of type=`service`.\"\n        ),\n    )\n    unit_label = models.CharField(max_length=12, default=\"\", blank=True)\n\n    def __str__(self):\n        # 1 product can have 1 or more than 1 related price\n        price_qs = self.prices.all()\n        price_count = price_qs.count()\n\n        if price_count > 1:\n            return f\"{self.name} ({price_count} prices)\"\n        elif price_count == 1:\n            return f\"{self.name} ({price_qs[0].human_readable_price})\"\n        else:\n            return self.name\n\n\nclass Customer(StripeModel):\n    \"\"\"\n    Customer objects allow you to perform recurring charges and track multiple\n    charges that are associated with the same customer.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#customers\n    \"\"\"\n\n    stripe_class = stripe.Customer\n    expand_fields = [\"default_source\", \"sources\"]\n    stripe_dashboard_item_name = \"customers\"\n\n    address = JSONField(null=True, blank=True, help_text=\"The customer's address.\")\n    balance = StripeQuantumCurrencyAmountField(\n        null=True,\n        blank=True,\n        default=0,\n        help_text=(\n            \"Current balance (in cents), if any, being stored on the customer's \"\n            \"account. \"\n            \"If negative, the customer has credit to apply to the next invoice. \"\n            \"If positive, the customer has an amount owed that will be added to the \"\n            \"next invoice. The balance does not refer to any unpaid invoices; it \"\n            \"solely takes into account amounts that have yet to be successfully \"\n            \"applied to any invoice. This balance is only taken into account for \"\n            \"recurring billing purposes (i.e., subscriptions, invoices, invoice items).\"\n        ),\n    )\n    currency = StripeCurrencyCodeField(\n        blank=True,\n        default=\"\",\n        help_text=\"The currency the customer can be charged in for \"\n        \"recurring billing purposes\",\n    )\n    default_source = PaymentMethodForeignKey(\n        on_delete=models.SET_NULL, null=True, blank=True, related_name=\"customers\"\n    )\n    delinquent = models.BooleanField(\n        null=True,\n        blank=True,\n        default=False,\n        help_text=\"Whether or not the latest charge for the customer's \"\n        \"latest invoice has failed.\",\n    )\n    # Stripe API returns deleted customers like so:\n    # {\n    #   \"id\": \"cus_KX439W5dKrpi22\",\n    #   \"object\": \"customer\",\n    #   \"deleted\": true,\n    # }\n    deleted = models.BooleanField(\n        default=False,\n        null=True,\n        blank=True,\n        help_text=\"Whether the Customer instance has been deleted upstream in Stripe or not.\",\n    )\n    # <discount>\n    coupon = models.ForeignKey(\n        \"Coupon\", null=True, blank=True, on_delete=models.SET_NULL\n    )\n    coupon_start = StripeDateTimeField(\n        null=True,\n        blank=True,\n        editable=False,\n        help_text=\"If a coupon is present, the date at which it was applied.\",\n    )\n    coupon_end = StripeDateTimeField(\n        null=True,\n        blank=True,\n        editable=False,\n        help_text=\"If a coupon is present and has a limited duration, \"\n        \"the date that the discount will end.\",\n    )\n    # </discount>\n    discount = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Describes the current discount active on the customer, if there is one.\",\n    )\n\n    email = models.TextField(max_length=5000, default=\"\", blank=True)\n    invoice_prefix = models.CharField(\n        default=\"\",\n        blank=True,\n        max_length=255,\n        help_text=(\n            \"The prefix for the customer used to generate unique invoice numbers.\"\n        ),\n    )\n    invoice_settings = JSONField(\n        null=True, blank=True, help_text=\"The customer's default invoice settings.\"\n    )\n    # default_payment_method is actually nested inside invoice_settings\n    # this field is a convenience to provide the foreign key\n    default_payment_method = StripeForeignKey(\n        \"PaymentMethod\",\n        null=True,\n        blank=True,\n        on_delete=models.SET_NULL,\n        related_name=\"+\",\n        help_text=\"default payment method used for subscriptions and invoices \"\n        \"for the customer.\",\n    )\n    name = models.TextField(\n        max_length=5000,\n        default=\"\",\n        blank=True,\n        help_text=\"The customer's full name or business name.\",\n    )\n    phone = models.TextField(\n        max_length=5000,\n        default=\"\",\n        blank=True,\n        help_text=\"The customer's phone number.\",\n    )\n    preferred_locales = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"The customer's preferred locales (languages), ordered by preference.\"\n        ),\n    )\n    shipping = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Shipping information associated with the customer.\",\n    )\n    tax_exempt = StripeEnumField(\n        enum=enums.CustomerTaxExempt,\n        default=\"\",\n        help_text=\"Describes the customer's tax exemption status. When set to reverse, \"\n        'invoice and receipt PDFs include the text \"Reverse charge\".',\n    )\n\n    # dj-stripe fields\n    subscriber = models.ForeignKey(\n        djstripe_settings.get_subscriber_model_string(),\n        blank=True,\n        null=True,\n        on_delete=models.SET_NULL,\n        related_name=\"djstripe_customers\",\n    )\n    date_purged = models.DateTimeField(null=True, editable=False)\n\n    class Meta(StripeModel.Meta):\n        unique_together = (\"subscriber\", \"livemode\", \"djstripe_owner_account\")\n\n    def __str__(self):\n        if self.subscriber:\n            return str(self.subscriber)\n\n        return self.name or self.description or self.id\n\n    @classmethod\n    def _manipulate_stripe_object_hook(cls, data):\n        # stripe adds a deleted attribute if the Customer has been deleted upstream\n        if data.get(\"deleted\"):\n            logger.warning(\n                f\"This customer ({data.get('id')}) has been deleted upstream, in Stripe\"\n            )\n\n        else:\n            # set \"deleted\" key to False (default)\n            data[\"deleted\"] = False\n\n        discount = data.get(\"discount\")\n        if discount:\n            data[\"coupon_start\"] = discount[\"start\"]\n            data[\"coupon_end\"] = discount[\"end\"]\n\n        # Populate the object id for our default_payment_method field (or set it None)\n        data[\"default_payment_method\"] = data.get(\"invoice_settings\", {}).get(\n            \"default_payment_method\"\n        )\n\n        return data\n\n    @classmethod\n    def get_or_create(\n        cls,\n        subscriber,\n        livemode=djstripe_settings.STRIPE_LIVE_MODE,\n        stripe_account=None,\n    ):\n        \"\"\"\n        Get or create a dj-stripe customer.\n\n        :param subscriber: The subscriber model instance for which to get or\n            create a customer.\n        :type subscriber: User\n\n        :param livemode: Whether to get the subscriber in live or test mode.\n        :type livemode: bool\n        \"\"\"\n\n        try:\n            return cls.objects.get(subscriber=subscriber, livemode=livemode), False\n        except cls.DoesNotExist:\n            action = f\"create:{subscriber.pk}\"\n            idempotency_key = djstripe_settings.get_idempotency_key(\n                \"customer\", action, livemode\n            )\n            return (\n                cls.create(\n                    subscriber,\n                    idempotency_key=idempotency_key,\n                    stripe_account=stripe_account,\n                ),\n                True,\n            )\n\n    @classmethod\n    def create(cls, subscriber, idempotency_key=None, stripe_account=None):\n        metadata = {}\n        subscriber_key = djstripe_settings.SUBSCRIBER_CUSTOMER_KEY\n        if subscriber_key not in (\"\", None):\n            metadata[subscriber_key] = subscriber.pk\n\n        stripe_customer = cls._api_create(\n            email=subscriber.email,\n            idempotency_key=idempotency_key,\n            metadata=metadata,\n            stripe_account=stripe_account,\n        )\n        customer, created = cls.objects.get_or_create(\n            id=stripe_customer[\"id\"],\n            defaults={\n                \"subscriber\": subscriber,\n                \"livemode\": stripe_customer[\"livemode\"],\n                \"balance\": stripe_customer.get(\"balance\", 0),\n                \"delinquent\": stripe_customer.get(\"delinquent\", False),\n            },\n        )\n\n        return customer\n\n    @property\n    def credits(self):\n        \"\"\"\n        The customer is considered to have credits if their balance is below 0.\n        \"\"\"\n        return abs(min(self.balance, 0))\n\n    @property\n    def customer_payment_methods(self):\n        \"\"\"\n        An iterable of all of the customer's payment methods\n        (sources, then legacy cards)\n        \"\"\"\n        for source in self.sources.iterator():\n            yield source\n\n        for card in self.legacy_cards.iterator():\n            yield card\n\n    @property\n    def pending_charges(self):\n        \"\"\"\n        The customer is considered to have pending charges if their balance is above 0.\n        \"\"\"\n        return max(self.balance, 0)\n\n    def subscribe(self, *, items=None, price=None, plan=None, **kwargs):\n        \"\"\"\n        Subscribes this customer to all the prices or plans in the items dict (Recommended).\n\n        :param items: A list of up to 20 subscription items, each with an attached price\n        :type list:\n            :param items: A dictionary of Plan (or Plan ID) or Price (or Price ID)\n            :type dict:  The price or plan to which to subscribe the customer.\n\n        :param price: The price to which to subscribe the customer.\n        :type price: Price or string (price ID)\n\n        :param plan: The plan to which to subscribe the customer.\n        :type plan: Plan or string (plan ID)\n        \"\"\"\n        from .billing import Subscription\n\n        if (items and price) or (items and plan) or (price and plan):\n            raise TypeError(\"Please define only one of items, price or plan arguments.\")\n\n        if items is None:\n            _items = [{\"price\": price}]\n        else:\n            _items = []\n            for item in items:\n                price = item.get(\"price\", \"\")\n                plan = item.get(\"plan\", \"\")\n                price, kwargs = _sanitise_price(price, plan, **kwargs)\n                if \"price\" in item:\n                    _items.append({\"price\": price})\n                if \"plan\" in item:\n                    _items.append({\"plan\": price})\n\n        stripe_subscription = Subscription._api_create(\n            items=_items, customer=self.id, **kwargs\n        )\n\n        api_key = kwargs.get(\"api_key\") or self.default_api_key\n        return Subscription.sync_from_stripe_data(stripe_subscription, api_key=api_key)\n\n    def charge(\n        self,\n        amount: Decimal,\n        *,\n        application_fee: Decimal = None,\n        source: Union[str, StripeModel] = None,\n        **kwargs,\n    ) -> Charge:\n        \"\"\"\n        Creates a charge for this customer.\n\n        :param amount: The amount to charge.\n        :type amount: Decimal. Precision is 2; anything more will be ignored.\n        :param source: The source to use for this charge.\n            Must be a source attributed to this customer. If None, the customer's\n            default source is used. Can be either the id of the source or\n            the source object itself.\n        :type source: string, Source\n        \"\"\"\n\n        if not isinstance(amount, Decimal):\n            raise ValueError(\"You must supply a decimal value representing dollars.\")\n\n        # Convert Source to id\n        if source and isinstance(source, StripeModel):\n            source = source.id\n\n        stripe_charge = Charge._api_create(\n            customer=self.id,\n            amount=int(amount * 100),  # Convert dollars into cents\n            application_fee=int(application_fee * 100)\n            if application_fee\n            else None,  # Convert dollars into cents\n            source=source,\n            **kwargs,\n        )\n\n        api_key = kwargs.get(\"api_key\") or self.default_api_key\n        return Charge.sync_from_stripe_data(stripe_charge, api_key=api_key)\n\n    def add_invoice_item(\n        self,\n        amount,\n        currency,\n        description=None,\n        discountable=None,\n        invoice=None,\n        metadata=None,\n        subscription=None,\n    ):\n        \"\"\"\n        Adds an arbitrary charge or credit to the customer's upcoming invoice.\n        Different than creating a charge. Charges are separate bills that get\n        processed immediately. Invoice items are appended to the customer's next\n        invoice. This is extremely useful when adding surcharges to subscriptions.\n\n        :param amount: The amount to charge.\n        :type amount: Decimal. Precision is 2; anything more will be ignored.\n        :param currency: 3-letter ISO code for currency\n        :type currency: string\n        :param description: An arbitrary string.\n        :type description: string\n        :param discountable: Controls whether discounts apply to this invoice item.\n            Defaults to False for prorations or negative invoice items,\n            and True for all other invoice items.\n        :type discountable: boolean\n        :param invoice: An existing invoice to add this invoice item to.\n            When left blank, the invoice item will be added to the next upcoming \\\n             scheduled invoice. \\\n             Use this when adding invoice items in response to an \\\n             ``invoice.created`` webhook. You cannot add an invoice \\\n            item to an invoice that has already been paid, attempted or closed.\n        :type invoice: Invoice or string (invoice ID)\n        :param metadata: A set of key/value pairs useful for storing\n            additional information.\n        :type metadata: dict\n        :param subscription: A subscription to add this invoice item to.\n            When left blank, the invoice item will be be added to the next upcoming \\\n            scheduled invoice. When set, scheduled invoices for subscriptions other \\\n            than the specified subscription will ignore the invoice item. \\\n            Use this when you want to express that an invoice item has been accrued \\\n            within the context of a particular subscription.\n        :type subscription: Subscription or string (subscription ID)\n\n        .. Notes:\n        .. if you're using ``Customer.add_invoice_item()`` instead of\n        .. ``Customer.add_invoice_item()``, ``invoice`` and ``subscriptions``\n        .. can only be strings\n        \"\"\"\n        from .billing import InvoiceItem\n\n        if not isinstance(amount, Decimal):\n            raise ValueError(\"You must supply a decimal value representing dollars.\")\n\n        # Convert Invoice to id\n        if invoice is not None and isinstance(invoice, StripeModel):\n            invoice = invoice.id\n\n        # Convert Subscription to id\n        if subscription is not None and isinstance(subscription, StripeModel):\n            subscription = subscription.id\n\n        stripe_invoiceitem = InvoiceItem._api_create(\n            amount=int(amount * 100),  # Convert dollars into cents\n            currency=currency,\n            customer=self.id,\n            description=description,\n            discountable=discountable,\n            invoice=invoice,\n            metadata=metadata,\n            subscription=subscription,\n        )\n\n        return InvoiceItem.sync_from_stripe_data(\n            stripe_invoiceitem, api_key=self.default_api_key\n        )\n\n    def add_card(self, source, set_default=True):\n        \"\"\"\n        Adds a card to this customer's account.\n\n        :param source: Either a token, like the ones returned by our Stripe.js, or a\n            dictionary containing a user's credit card details.\n            Stripe will automatically validate the card.\n        :type source: string, dict\n        :param set_default: Whether or not to set the source as the customer's\n            default source\n        :type set_default: boolean\n\n        \"\"\"\n        from .payment_methods import DjstripePaymentMethod\n\n        stripe_customer = self.api_retrieve()\n        new_stripe_payment_method = stripe_customer.sources.create(source=source)\n\n        if set_default:\n            stripe_customer.default_source = new_stripe_payment_method[\"id\"]\n            stripe_customer.save()\n\n        new_payment_method = DjstripePaymentMethod.from_stripe_object(\n            new_stripe_payment_method\n        )\n\n        # Change the default source\n        if set_default:\n            self.default_source = new_payment_method\n            self.save()\n\n        return new_payment_method.resolve()\n\n    def add_payment_method(self, payment_method, set_default=True):\n        \"\"\"\n        Adds an already existing payment method to this customer's account\n\n        :param payment_method: PaymentMethod to be attached to the customer\n        :type payment_method: str, PaymentMethod\n        :param set_default: If true, this will be set as the default_payment_method\n        :type set_default: bool\n        :rtype: PaymentMethod\n        \"\"\"\n        from .payment_methods import PaymentMethod\n\n        stripe_customer = self.api_retrieve()\n        payment_method = PaymentMethod.attach(payment_method, stripe_customer)\n\n        if set_default:\n            stripe_customer[\"invoice_settings\"][\n                \"default_payment_method\"\n            ] = payment_method.id\n            stripe_customer.save()\n\n            # Refresh self from the stripe customer, this should have two effects:\n            # 1) sets self.default_payment_method (we rely on logic in\n            # Customer._manipulate_stripe_object_hook to do this)\n            # 2) updates self.invoice_settings.default_payment_methods\n            self.sync_from_stripe_data(stripe_customer, api_key=self.default_api_key)\n            self.refresh_from_db()\n\n        return payment_method\n\n    def purge(self):\n        \"\"\"Customers are soft deleted as deleted customers are still accessible by the\n        Stripe API and sync for all RelatedModels would fail\"\"\"\n        try:\n            self._api_delete()\n        except InvalidRequestError as exc:\n            if \"No such customer:\" in str(exc):\n                # The exception was thrown because the stripe customer was already\n                # deleted on the stripe side, ignore the exception\n                pass\n            else:\n                # The exception was raised for another reason, re-raise it\n                raise\n\n        # toggle the deleted flag on Customer to indicate it has been\n        # deleted upstream in Stripe\n        self.deleted = True\n\n        if self.subscriber:\n            # Delete the idempotency key used by Customer.create()\n            # So re-creating a customer for this subscriber before the key expires\n            # doesn't return the older Customer data\n            idempotency_key_action = f\"customer:create:{self.subscriber.pk}\"\n            IdempotencyKey.objects.filter(action=idempotency_key_action).delete()\n\n        self.subscriber = None\n\n        # Remove sources\n        self.default_source = None\n        for source in self.legacy_cards.all():\n            source.remove()\n\n        for source in self.sources.all():\n            source.detach()\n\n        self.date_purged = timezone.now()\n        self.save()\n\n    def _get_valid_subscriptions(self):\n        \"\"\"Get a list of this customer's valid subscriptions.\"\"\"\n\n        return [\n            subscription\n            for subscription in self.subscriptions.all()\n            if subscription.is_valid()\n        ]\n\n    def is_subscribed_to(self, product: Union[Product, str]) -> bool:\n        \"\"\"\n        Checks to see if this customer has an active subscription to the given product.\n\n        :param product: The product for which to check for an active subscription.\n        :type product: Product or string (product ID)\n\n        :returns: True if there exists an active subscription, False otherwise.\n        \"\"\"\n\n        if isinstance(product, StripeModel):\n            product = product.id\n\n        for subscription in self._get_valid_subscriptions():\n            for item in subscription.items.all():\n                if item.price and item.price.product.id == product:\n                    return True\n        return False\n\n    def has_any_active_subscription(self):\n        \"\"\"\n        Checks to see if this customer has an active subscription to any plan.\n\n        :returns: True if there exists an active subscription, False otherwise.\n        \"\"\"\n\n        return len(self._get_valid_subscriptions()) != 0\n\n    @property\n    def active_subscriptions(self):\n        \"\"\"\n        Returns active subscriptions\n        (subscriptions with an active status that end in the future).\n        \"\"\"\n        return self.subscriptions.filter(\n            status=enums.SubscriptionStatus.active,\n            current_period_end__gt=timezone.now(),\n        )\n\n    @property\n    def valid_subscriptions(self):\n        \"\"\"\n        Returns this customer's valid subscriptions\n        (subscriptions that aren't canceled or incomplete_expired).\n        \"\"\"\n        return self.subscriptions.exclude(\n            status__in=[\n                enums.SubscriptionStatus.canceled,\n                enums.SubscriptionStatus.incomplete_expired,\n            ]\n        )\n\n    @property\n    def subscription(self):\n        \"\"\"\n        Shortcut to get this customer's subscription.\n\n        :returns: None if the customer has no subscriptions, the subscription if\n            the customer has a subscription.\n        :raises MultipleSubscriptionException: Raised if the customer has multiple\n            subscriptions.\n            In this case, use ``Customer.subscriptions`` instead.\n        \"\"\"\n\n        subscriptions = self.valid_subscriptions\n\n        if subscriptions.count() > 1:\n            raise MultipleSubscriptionException(\n                \"This customer has multiple subscriptions. Use Customer.subscriptions \"\n                \"to access them.\"\n            )\n        else:\n            return subscriptions.first()\n\n    def send_invoice(self):\n        \"\"\"\n        Pay and send the customer's latest invoice.\n\n        :returns: True if an invoice was able to be created and paid, False otherwise\n            (typically if there was nothing to invoice).\n        \"\"\"\n        from .billing import Invoice\n\n        try:\n            invoice = Invoice._api_create(customer=self.id)\n            invoice.pay()\n            return True\n        except InvalidRequestError:  # TODO: Check this for a more\n            #                           specific error message.\n            return False  # There was nothing to invoice\n\n    def retry_unpaid_invoices(self):\n        \"\"\"Attempt to retry collecting payment on the customer's unpaid invoices.\"\"\"\n\n        self._sync_invoices()\n        for invoice in self.invoices.filter(auto_advance=True).exclude(status=\"paid\"):\n            try:\n                invoice.retry()  # Always retry unpaid invoices\n            except InvalidRequestError as exc:\n                if str(exc) != \"Invoice is already paid\":\n                    raise\n\n    def add_coupon(self, coupon, idempotency_key=None):\n        \"\"\"\n        Add a coupon to a Customer.\n\n        The coupon can be a Coupon object, or a valid Stripe Coupon ID.\n        \"\"\"\n        if isinstance(coupon, StripeModel):\n            coupon = coupon.id\n\n        stripe_customer = self.api_retrieve()\n        stripe_customer[\"coupon\"] = coupon\n        stripe_customer.save(idempotency_key=idempotency_key)\n        return self.__class__.sync_from_stripe_data(\n            stripe_customer, api_key=self.default_api_key\n        )\n\n    def upcoming_invoice(self, **kwargs):\n        \"\"\"Gets the upcoming preview invoice (singular) for this customer.\n\n        See `Invoice.upcoming() <#djstripe.Invoice.upcoming>`__.\n\n        The ``customer`` argument to the ``upcoming()`` call is automatically set\n         by this method.\n        \"\"\"\n        from .billing import Invoice\n\n        kwargs[\"customer\"] = self\n        return Invoice.upcoming(**kwargs)\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        pending_relations=None,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    ):\n        from .billing import Coupon\n        from .payment_methods import DjstripePaymentMethod\n\n        super()._attach_objects_post_save_hook(\n            cls, data, pending_relations=pending_relations, api_key=api_key\n        )\n\n        save = False\n\n        customer_sources = data.get(\"sources\")\n        sources = {}\n        if customer_sources:\n            # Have to create sources before we handle the default_source\n            # We save all of them in the `sources` dict, so that we can find them\n            # by id when we look at the default_source (we need the source type).\n            for source in customer_sources[\"data\"]:\n                obj, _ = DjstripePaymentMethod._get_or_create_source(\n                    source, source[\"object\"], api_key=api_key\n                )\n                sources[source[\"id\"]] = obj\n\n        discount = data.get(\"discount\")\n        if discount:\n            coupon, _created = Coupon._get_or_create_from_stripe_object(\n                discount, \"coupon\", api_key=api_key\n            )\n            if coupon and coupon != self.coupon:\n                self.coupon = coupon\n                save = True\n        elif self.coupon:\n            self.coupon = None\n            save = True\n\n        if save:\n            self.save()\n\n    def _attach_objects_hook(\n        self, cls, data, current_ids=None, api_key=djstripe_settings.STRIPE_SECRET_KEY\n    ):\n        # When we save a customer to Stripe, we add a reference to its Django PK\n        # in the `django_account` key. If we find that, we re-attach that PK.\n        subscriber_key = djstripe_settings.SUBSCRIBER_CUSTOMER_KEY\n        if subscriber_key in (\"\", None):\n            # Disabled. Nothing else to do.\n            return\n\n        subscriber_id = data.get(\"metadata\", {}).get(subscriber_key)\n        if subscriber_id:\n            cls = djstripe_settings.get_subscriber_model()\n            try:\n                # We have to perform a get(), instead of just attaching the PK\n                # blindly as the object may have been deleted or not exist.\n                # Attempting to save that would cause an IntegrityError.\n                self.subscriber = cls.objects.get(pk=subscriber_id)\n            except (cls.DoesNotExist, ValueError):\n                logger.warning(\n                    \"Could not find subscriber %r matching customer %r\",\n                    subscriber_id,\n                    self.id,\n                )\n                self.subscriber = None\n\n    # SYNC methods should be dropped in favor of the master sync infrastructure proposed\n    def _sync_invoices(self, **kwargs):\n        from .billing import Invoice\n\n        api_key = kwargs.get(\"api_key\") or self.default_api_key\n        for stripe_invoice in Invoice.api_list(customer=self.id, **kwargs):\n            Invoice.sync_from_stripe_data(stripe_invoice, api_key=api_key)\n\n    def _sync_charges(self, **kwargs):\n        api_key = kwargs.get(\"api_key\") or self.default_api_key\n        for stripe_charge in Charge.api_list(customer=self.id, **kwargs):\n            Charge.sync_from_stripe_data(stripe_charge, api_key=api_key)\n\n    def _sync_cards(self, **kwargs):\n        from .payment_methods import Card\n\n        api_key = kwargs.get(\"api_key\") or self.default_api_key\n        for stripe_card in Card.api_list(customer=self, **kwargs):\n            Card.sync_from_stripe_data(stripe_card, api_key=api_key)\n\n    def _sync_subscriptions(self, **kwargs):\n        from .billing import Subscription\n\n        api_key = kwargs.get(\"api_key\") or self.default_api_key\n        for stripe_subscription in Subscription.api_list(\n            customer=self.id, status=\"all\", **kwargs\n        ):\n            Subscription.sync_from_stripe_data(stripe_subscription, api_key=api_key)\n\n\nclass Dispute(StripeModel):\n    \"\"\"\n    A dispute occurs when a customer questions your charge with their\n    card issuer. When this happens, you're given the opportunity to\n    respond to the dispute with evidence that shows that the charge is legitimate\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#disputes\n    \"\"\"\n\n    stripe_class = stripe.Dispute\n    stripe_dashboard_item_name = \"payments\"\n\n    amount = StripeQuantumCurrencyAmountField(\n        help_text=(\n            \"Disputed amount (in cents). Usually the amount of the charge, \"\n            \"but can differ \"\n            \"(usually because of currency fluctuation or because only part of \"\n            \"the order is disputed).\"\n        )\n    )\n    balance_transaction = StripeForeignKey(\n        \"BalanceTransaction\",\n        null=True,\n        on_delete=models.CASCADE,\n        related_name=\"disputes\",\n        help_text=\"Balance transaction that describes the impact on your \"\n        \"account balance.\",\n    )\n    balance_transactions = JSONField(\n        default=list,\n        help_text=\"List of 0, 1 or 2 Balance Transactions that show funds withdrawn and reinstated to your Stripe account as a result of this dispute.\",\n    )\n    # charge is nullable to avoid infinite sync as Charge model has a dispute field as well\n    charge = StripeForeignKey(\n        \"Charge\",\n        null=True,\n        on_delete=models.CASCADE,\n        related_name=\"disputes\",\n        help_text=\"The charge that was disputed\",\n    )\n    currency = StripeCurrencyCodeField()\n    evidence = JSONField(help_text=\"Evidence provided to respond to a dispute.\")\n    evidence_details = JSONField(help_text=\"Information about the evidence submission.\")\n    is_charge_refundable = models.BooleanField(\n        help_text=(\n            \"If true, it is still possible to refund the disputed payment. \"\n            \"Once the payment has been fully refunded, no further funds will \"\n            \"be withdrawn from your Stripe account as a result of this dispute.\"\n        )\n    )\n    payment_intent = StripeForeignKey(\n        \"PaymentIntent\",\n        null=True,\n        on_delete=models.CASCADE,\n        related_name=\"disputes\",\n        help_text=\"The PaymentIntent that was disputed\",\n    )\n    reason = StripeEnumField(enum=enums.DisputeReason)\n    status = StripeEnumField(enum=enums.DisputeStatus)\n\n    def __str__(self):\n        amount = get_friendly_currency_amount(self.amount / 100, self.currency)\n        status = enums.DisputeStatus.humanize(self.status)\n        return f\"{amount} ({status}) \"\n\n    def get_stripe_dashboard_url(self) -> str:\n        \"\"\"Get the stripe dashboard url for this object.\"\"\"\n        return (\n            f\"{self._get_base_stripe_dashboard_url()}\"\n            f\"{self.stripe_dashboard_item_name}/{self.payment_intent.id}\"\n        )\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        pending_relations=None,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    ):\n        super()._attach_objects_post_save_hook(\n            cls, data, pending_relations=pending_relations, api_key=api_key\n        )\n\n        # Retrieve and save files from the dispute.evidence object.\n        # todo find a better way of retrieving and syncing File Type fields from Dispute object\n        for field in (\n            \"cancellation_policy\",\n            \"customer_communication\",\n            \"customer_signature\",\n            \"duplicate_charge_documentation\",\n            \"receipt\",\n            \"refund_policy\",\n            \"service_documentation\",\n            \"shipping_documentation\",\n            \"uncategorized_file\",\n        ):\n            file_upload_id = self.evidence.get(field, None)\n            if file_upload_id:\n                try:\n                    File.sync_from_stripe_data(\n                        File(id=file_upload_id).api_retrieve(api_key=api_key),\n                        api_key=api_key,\n                    )\n                except stripe.error.PermissionError:\n                    # No permission to retrieve the data with the key\n                    # Log a warning message\n                    logger.warning(\n                        \"No permission to retrieve the File Evidence Object.\"\n                    )\n                except stripe.error.InvalidRequestError:\n                    raise\n\n        # iterate and sync every balance transaction\n        for stripe_balance_transaction in self.balance_transactions:\n            BalanceTransaction.sync_from_stripe_data(\n                stripe_balance_transaction, api_key=api_key\n            )\n\n\nclass Event(StripeModel):\n    \"\"\"\n    Events are Stripe's way of letting you know when something interesting\n    happens in your account.\n    When an interesting event occurs, a new Event object is created and POSTed\n    to the configured webhook URL if the Event type matches.\n\n    Stripe documentation: https://stripe.com/docs/api/events?lang=python\n    \"\"\"\n\n    stripe_class = stripe.Event\n    stripe_dashboard_item_name = \"events\"\n\n    api_version = models.CharField(\n        max_length=64,\n        blank=True,\n        help_text=\"the API version at which the event data was \"\n        \"rendered. Blank for old entries only, all new entries will have this value\",\n    )\n    data = JSONField(\n        help_text=\"data received at webhook. data should be considered to be garbage \"\n        \"until validity check is run and valid flag is set\"\n    )\n    request_id = models.CharField(\n        max_length=50,\n        help_text=\"Information about the request that triggered this event, \"\n        \"for traceability purposes. If empty string then this is an old entry \"\n        \"without that data. If Null then this is not an old entry, but a Stripe \"\n        \"'automated' event with no associated request.\",\n        default=\"\",\n        blank=True,\n    )\n    idempotency_key = models.TextField(default=\"\", blank=True)\n    type = models.CharField(max_length=250, help_text=\"Stripe's event description code\")\n\n    def __str__(self):\n        return f\"type={self.type}, id={self.id}\"\n\n    def _attach_objects_hook(\n        self, cls, data, current_ids=None, api_key=djstripe_settings.STRIPE_SECRET_KEY\n    ):\n        if self.api_version is None:\n            # as of api version 2017-02-14, the account.application.deauthorized\n            # event sends None as api_version.\n            # If we receive that, store an empty string instead.\n            # Remove this hack if this gets fixed upstream.\n            self.api_version = \"\"\n\n        request_obj = data.get(\"request\", None)\n        if isinstance(request_obj, dict):\n            # Format as of 2017-05-25\n            self.request_id = request_obj.get(\"id\") or \"\"\n            self.idempotency_key = request_obj.get(\"idempotency_key\") or \"\"\n        else:\n            # Format before 2017-05-25\n            self.request_id = request_obj or \"\"\n\n    @classmethod\n    def process(cls, data, api_key=djstripe_settings.STRIPE_SECRET_KEY):\n        qs = cls.objects.filter(id=data[\"id\"])\n        if qs.exists():\n            return qs.first()\n\n        # Rollback any DB operations in the case of failure so\n        # we will retry creating and processing the event the\n        # next time the webhook fires.\n        with transaction.atomic():\n            # process the event and create an Event Object\n            ret = cls._create_from_stripe_object(data, api_key=api_key)\n            ret.invoke_webhook_handlers()\n            return ret\n\n    def invoke_webhook_handlers(self):\n        \"\"\"\n        Invokes any webhook handlers that have been registered for this event\n        based on event type or event sub-type.\n\n        See event handlers registered in the ``djstripe.event_handlers`` module\n        (or handlers registered in djstripe plugins or contrib packages).\n        \"\"\"\n\n        webhooks.call_handlers(event=self)\n\n        signal = WEBHOOK_SIGNALS.get(self.type)\n        if signal:\n            return signal.send(sender=Event, event=self)\n\n    @cached_property\n    def parts(self):\n        \"\"\"Gets the event category/verb as a list of parts.\"\"\"\n        return str(self.type).split(\".\")\n\n    @cached_property\n    def category(self):\n        \"\"\"Gets the event category string (e.g. 'customer').\"\"\"\n        return self.parts[0]\n\n    @cached_property\n    def verb(self):\n        \"\"\"Gets the event past-tense verb string (e.g. 'updated').\"\"\"\n        return \".\".join(self.parts[1:])\n\n    @property\n    def customer(self):\n        data = self.data[\"object\"]\n        if data[\"object\"] == \"customer\":\n            customer_id = get_id_from_stripe_data(data.get(\"id\"))\n        else:\n            customer_id = get_id_from_stripe_data(data.get(\"customer\"))\n\n        if customer_id:\n            return Customer._get_or_retrieve(\n                id=customer_id,\n                stripe_account=getattr(self.djstripe_owner_account, \"id\", None),\n                api_key=self.default_api_key,\n            )\n\n\nclass File(StripeModel):\n    \"\"\"\n    This is an object representing a file hosted on Stripe's servers.\n    The file may have been uploaded by yourself using the create file request\n    (for example, when uploading dispute evidence) or it may have been created by\n    Stripe (for example, the results of a Sigma scheduled query).\n\n    Stripe documentation: https://stripe.com/docs/api/files?lang=python\n    \"\"\"\n\n    stripe_class = stripe.File\n\n    filename = models.CharField(\n        max_length=255,\n        help_text=\"A filename for the file, suitable for saving to a filesystem.\",\n    )\n    purpose = StripeEnumField(\n        enum=enums.FilePurpose, help_text=\"The purpose of the uploaded file.\"\n    )\n    size = models.IntegerField(help_text=\"The size in bytes of the file upload object.\")\n    type = StripeEnumField(\n        enum=enums.FileType, help_text=\"The type of the file returned.\"\n    )\n    url = models.CharField(\n        max_length=200,\n        help_text=\"A read-only URL where the uploaded file can be accessed.\",\n    )\n\n    @classmethod\n    def is_valid_object(cls, data):\n        return data and data.get(\"object\") in (\"file\", \"file_upload\")\n\n    def __str__(self):\n        return f\"{self.filename}, {enums.FilePurpose.humanize(self.purpose)}\"\n\n\n# Alias for compatibility\n# Stripe's SDK has the same alias.\n# Do not remove/deprecate as long as it's present there.\nFileUpload = File\n\n\nclass FileLink(StripeModel):\n    \"\"\"\n    To share the contents of a File object with non-Stripe users,\n    you can create a FileLink. FileLinks contain a URL that can be used\n    to retrieve the contents of the file without authentication.\n\n    Stripe documentation: https://stripe.com/docs/api/file_links?lang=python\n    \"\"\"\n\n    stripe_class = stripe.FileLink\n\n    expires_at = StripeDateTimeField(\n        null=True, blank=True, help_text=\"Time at which the link expires.\"\n    )\n    file = StripeForeignKey(\"File\", on_delete=models.CASCADE)\n    url = models.URLField(help_text=\"The publicly accessible URL to download the file.\")\n\n    def __str__(self):\n        return f\"{self.file.filename}, {self.url}\"\n\n\nclass PaymentIntent(StripeModel):\n    \"\"\"\n    A PaymentIntent guides you through the process of collecting a payment\n    from your customer. We recommend that you create exactly one PaymentIntent for each order\n    or customer session in your system. You can reference the PaymentIntent later to\n    see the history of payment attempts for a particular session.\n\n    A PaymentIntent transitions through multiple statuses throughout its lifetime as\n    it interfaces with Stripe.js to perform authentication flows and ultimately\n    creates at most one successful charge.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#payment_intents\n    \"\"\"\n\n    stripe_class = stripe.PaymentIntent\n    stripe_dashboard_item_name = \"payments\"\n\n    amount = StripeQuantumCurrencyAmountField(\n        help_text=\"Amount (in cents) intended to be collected by this PaymentIntent.\"\n    )\n    amount_capturable = StripeQuantumCurrencyAmountField(\n        help_text=\"Amount (in cents) that can be captured from this PaymentIntent.\"\n    )\n    amount_received = StripeQuantumCurrencyAmountField(\n        help_text=\"Amount (in cents) that was collected by this PaymentIntent.\"\n    )\n    # application\n    # application_fee_amount\n    canceled_at = StripeDateTimeField(\n        null=True,\n        blank=True,\n        default=None,\n        help_text=(\n            \"Populated when status is canceled, this is the time at which the \"\n            \"PaymentIntent was canceled. Measured in seconds since the Unix epoch.\"\n        ),\n    )\n\n    cancellation_reason = StripeEnumField(\n        enum=enums.PaymentIntentCancellationReason,\n        blank=True,\n        help_text=(\n            \"Reason for cancellation of this PaymentIntent, either user-provided \"\n            \"(duplicate, fraudulent, requested_by_customer, or abandoned) or \"\n            \"generated by Stripe internally (failed_invoice, void_invoice, \"\n            \"or automatic).\"\n        ),\n    )\n    capture_method = StripeEnumField(\n        enum=enums.CaptureMethod,\n        help_text=\"Capture method of this PaymentIntent, one of automatic or manual.\",\n    )\n    client_secret = models.TextField(\n        max_length=5000,\n        help_text=(\n            \"The client secret of this PaymentIntent. \"\n            \"Used for client-side retrieval using a publishable key.\"\n        ),\n    )\n    confirmation_method = StripeEnumField(\n        enum=enums.ConfirmationMethod,\n        help_text=(\n            \"Confirmation method of this PaymentIntent, one of manual or automatic.\"\n        ),\n    )\n    currency = StripeCurrencyCodeField()\n    customer = StripeForeignKey(\n        \"Customer\",\n        null=True,\n        on_delete=models.CASCADE,\n        help_text=\"Customer this PaymentIntent is for if one exists.\",\n    )\n    description = models.TextField(\n        max_length=1000,\n        default=\"\",\n        blank=True,\n        help_text=(\n            \"An arbitrary string attached to the object. \"\n            \"Often useful for displaying to users.\"\n        ),\n    )\n    last_payment_error = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"The payment error encountered in the previous PaymentIntent confirmation.\"\n        ),\n    )\n    next_action = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"If present, this property tells you what actions you need to take \"\n            \"in order for your customer to fulfill a payment using the provided source.\"\n        ),\n    )\n    on_behalf_of = StripeForeignKey(\n        \"Account\",\n        on_delete=models.CASCADE,\n        null=True,\n        blank=True,\n        help_text=\"The account (if any) for which the funds of the \"\n        \"PaymentIntent are intended.\",\n        related_name=\"payment_intents\",\n    )\n    payment_method = StripeForeignKey(\n        \"PaymentMethod\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        help_text=\"Payment method used in this PaymentIntent.\",\n    )\n    payment_method_types = JSONField(\n        help_text=(\n            \"The list of payment method types (e.g. card) that this \"\n            \"PaymentIntent is allowed to use.\"\n        )\n    )\n    receipt_email = models.CharField(\n        blank=True,\n        max_length=255,\n        help_text=(\n            \"Email address that the receipt for the resulting payment will be sent to.\"\n        ),\n    )\n    # TODO: Add `review` field after we add Review model.\n    setup_future_usage = StripeEnumField(\n        enum=enums.IntentUsage,\n        null=True,\n        blank=True,\n        help_text=(\n            \"Indicates that you intend to make future payments with this \"\n            \"PaymentIntent's payment method. \"\n            \"If present, the payment method used with this PaymentIntent can \"\n            \"be attached to a Customer, even after the transaction completes. \"\n            \"Use `on_session` if you intend to only reuse the payment method \"\n            \"when your customer is present in your checkout flow. Use `off_session` \"\n            \"if your customer may or may not be in your checkout flow. \"\n            \"Stripe uses `setup_future_usage` to dynamically optimize \"\n            \"your payment flow and comply with regional legislation and network rules. \"\n            \"For example, if your customer is impacted by SCA, using `off_session` \"\n            \"will ensure that they are authenticated while processing this \"\n            \"PaymentIntent. You will then be able to make later off-session payments \"\n            \"for this customer.\"\n        ),\n    )\n    shipping = JSONField(\n        null=True, blank=True, help_text=\"Shipping information for this PaymentIntent.\"\n    )\n    statement_descriptor = models.CharField(\n        max_length=22,\n        blank=True,\n        help_text=(\n            \"For non-card charges, you can use this value as the complete description \"\n            \"that appears on your customers' statements. Must contain at least one \"\n            \"letter, maximum 22 characters.\"\n        ),\n    )\n    status = StripeEnumField(\n        enum=enums.PaymentIntentStatus,\n        help_text=(\n            \"Status of this PaymentIntent, one of requires_payment_method, \"\n            \"requires_confirmation, requires_action, processing, requires_capture, \"\n            \"canceled, or succeeded. \"\n            \"You can read more about PaymentIntent statuses here.\"\n        ),\n    )\n    transfer_data = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"The data with which to automatically create a Transfer when the payment \"\n            \"is finalized. \"\n            \"See the PaymentIntents Connect usage guide for details.\"\n        ),\n    )\n    transfer_group = models.CharField(\n        blank=True,\n        max_length=255,\n        help_text=(\n            \"A string that identifies the resulting payment as part of a group. \"\n            \"See the PaymentIntents Connect usage guide for details.\"\n        ),\n    )\n\n    def __str__(self):\n        account = self.on_behalf_of\n        customer = self.customer\n        amount = get_friendly_currency_amount(self.amount / 100, self.currency)\n        status = enums.PaymentIntentStatus.humanize(self.status)\n\n        if account and customer:\n            return f\"{amount} ({status}) for {account} by {customer}\"\n        if account:\n            return f\"{amount} for {account}. {status}\"\n        if customer:\n            return f\"{amount} by {customer}. {status}\"\n\n        return f\"{amount} ({status})\"\n\n    def update(self, api_key=None, **kwargs):\n        \"\"\"\n        Call the stripe API's modify operation for this model\n\n        :param api_key: The api key to use for this request.\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        \"\"\"\n        api_key = api_key or self.default_api_key\n        response = self.api_retrieve(api_key=api_key)\n        return response.modify(response.stripe_id, api_key=api_key, **kwargs)\n\n    def _api_cancel(self, api_key=None, **kwargs):\n        \"\"\"\n        Call the stripe API's cancel operation for this model\n\n        :param api_key: The api key to use for this request.\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        \"\"\"\n        api_key = api_key or self.default_api_key\n\n        return self.api_retrieve(api_key=api_key).cancel(**kwargs)\n\n    def _api_confirm(self, api_key=None, **kwargs):\n        \"\"\"\n        Call the stripe API's confirm operation for this model.\n\n        Confirm that your customer intends to pay with current or\n        provided payment method. Upon confirmation, the PaymentIntent\n        will attempt to initiate a payment.\n\n        :param api_key: The api key to use for this request.\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        \"\"\"\n        api_key = api_key or self.default_api_key\n\n        return self.api_retrieve(api_key=api_key).confirm(**kwargs)\n\n\nclass SetupIntent(StripeModel):\n    \"\"\"\n    A SetupIntent guides you through the process of setting up a customer's\n    payment credentials for future payments. For example, you could use a SetupIntent\n    to set up your customer's card without immediately collecting a payment.\n    Later, you can use PaymentIntents to drive the payment flow.\n\n    NOTE: You should not maintain long-lived, unconfirmed SetupIntents.\n    For security purposes, SetupIntents older than 24 hours may no longer be valid.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#setup_intents\n    \"\"\"\n\n    stripe_class = stripe.SetupIntent\n\n    application = models.CharField(\n        max_length=255,\n        blank=True,\n        help_text=\"ID of the Connect application that created the SetupIntent.\",\n    )\n    cancellation_reason = StripeEnumField(\n        enum=enums.SetupIntentCancellationReason,\n        blank=True,\n        help_text=(\n            \"Reason for cancellation of this SetupIntent, one of abandoned, \"\n            \"requested_by_customer, or duplicate\"\n        ),\n    )\n    client_secret = models.TextField(\n        max_length=5000,\n        blank=True,\n        help_text=(\n            \"The client secret of this SetupIntent. \"\n            \"Used for client-side retrieval using a publishable key.\"\n        ),\n    )\n    customer = StripeForeignKey(\n        \"Customer\",\n        null=True,\n        blank=True,\n        on_delete=models.SET_NULL,\n        help_text=\"Customer this SetupIntent belongs to, if one exists.\",\n    )\n    last_setup_error = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"The error encountered in the previous SetupIntent confirmation.\",\n    )\n    next_action = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"If present, this property tells you what actions you need to take in\"\n            \"order for your customer to continue payment setup.\"\n        ),\n    )\n    on_behalf_of = StripeForeignKey(\n        \"Account\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        help_text=\"The account (if any) for which the setup is intended.\",\n        related_name=\"setup_intents\",\n    )\n    payment_method = StripeForeignKey(\n        \"PaymentMethod\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        help_text=\"Payment method used in this PaymentIntent.\",\n    )\n    payment_method_types = JSONField(\n        help_text=(\n            \"The list of payment method types (e.g. card) that this PaymentIntent is \"\n            \"allowed to use.\"\n        )\n    )\n    status = StripeEnumField(\n        enum=enums.SetupIntentStatus,\n        help_text=(\n            \"Status of this SetupIntent, one of requires_payment_method, \"\n            \"requires_confirmation, requires_action, processing, \"\n            \"canceled, or succeeded.\"\n        ),\n    )\n    usage = StripeEnumField(\n        enum=enums.IntentUsage,\n        default=enums.IntentUsage.off_session,\n        help_text=(\n            \"Indicates how the payment method is intended to be used in the future.\"\n        ),\n    )\n\n    def __str__(self):\n        account = self.on_behalf_of\n        customer = self.customer\n\n        if account and customer:\n            return (\n                f\"{self.payment_method} ({enums.SetupIntentStatus.humanize(self.status)}) \"\n                f\"for {account} \"\n                f\"by {customer}\"\n            )\n\n        if account:\n            return f\"{self.payment_method} for {account}. {enums.SetupIntentStatus.humanize(self.status)}\"\n        if customer:\n            return f\"{self.payment_method} by {customer}. {enums.SetupIntentStatus.humanize(self.status)}\"\n        return (\n            f\"{self.payment_method} ({enums.SetupIntentStatus.humanize(self.status)})\"\n        )\n\n\n# TODO Add Tests\nclass Payout(StripeModel):\n    \"\"\"\n    A Payout object is created when you receive funds from Stripe, or when you initiate\n    a payout to either a bank account or debit card of a connected Stripe account.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#payouts\n    \"\"\"\n\n    expand_fields = [\"destination\"]\n    stripe_class = stripe.Payout\n    stripe_dashboard_item_name = \"payouts\"\n\n    amount = StripeDecimalCurrencyAmountField(\n        help_text=\"Amount (as decimal) to be transferred to your bank account or \"\n        \"debit card.\"\n    )\n    arrival_date = StripeDateTimeField(\n        help_text=(\n            \"Date the payout is expected to arrive in the bank. \"\n            \"This factors in delays like weekends or bank holidays.\"\n        )\n    )\n    automatic = models.BooleanField(\n        help_text=(\n            \"`true` if the payout was created by an automated payout schedule, \"\n            \"and `false` if it was requested manually.\"\n        )\n    )\n    balance_transaction = StripeForeignKey(\n        \"BalanceTransaction\",\n        on_delete=models.SET_NULL,\n        null=True,\n        help_text=\"Balance transaction that describes the impact on your \"\n        \"account balance.\",\n    )\n    currency = StripeCurrencyCodeField()\n    destination = PaymentMethodForeignKey(\n        on_delete=models.PROTECT,\n        null=True,\n        help_text=\"Bank account or card the payout was sent to.\",\n    )\n    failure_balance_transaction = StripeForeignKey(\n        \"BalanceTransaction\",\n        on_delete=models.SET_NULL,\n        related_name=\"failure_payouts\",\n        null=True,\n        blank=True,\n        help_text=(\n            \"If the payout failed or was canceled, this will be the balance \"\n            \"transaction that reversed the initial balance transaction, and \"\n            \"puts the funds from the failed payout back in your balance.\"\n        ),\n    )\n    failure_code = StripeEnumField(\n        enum=enums.PayoutFailureCode,\n        default=\"\",\n        blank=True,\n        help_text=(\n            \"Error code explaining reason for transfer failure if available. \"\n            \"See https://stripe.com/docs/api?lang=python#transfer_failures.\"\n        ),\n    )\n    failure_message = models.TextField(\n        default=\"\",\n        blank=True,\n        help_text=(\n            \"Message to user further explaining reason for \"\n            \"payout failure if available.\"\n        ),\n    )\n    method = StripeEnumField(\n        max_length=8,\n        enum=enums.PayoutMethod,\n        help_text=(\n            \"The method used to send this payout. \"\n            \"`instant` is only supported for payouts to debit cards.\"\n        ),\n    )\n    # TODO: `original_payout` impl as OneToOne, with `reversed_by` reverse relation\n    # original_payout = StripeForeignKey(\n    #     \"Payout\",\n    #     on_delete=models.SET_NULL,\n    #     null=True,\n    #     blank=True,\n    #     help_text=\"If the payout reverses another, this is the original payout.\",\n    # )\n    source_type = StripeEnumField(\n        enum=enums.PayoutSourceType,\n        help_text=\"The source balance this payout came from.\",\n    )\n    statement_descriptor = models.CharField(\n        max_length=255,\n        default=\"\",\n        blank=True,\n        help_text=\"Extra information about a payout to be displayed \"\n        \"on the user's bank statement.\",\n    )\n    status = StripeEnumField(\n        enum=enums.PayoutStatus,\n        help_text=(\n            \"Current status of the payout. \"\n            \"A payout will be `pending` until it is submitted to the bank, \"\n            \"at which point it becomes `in_transit`. \"\n            \"It will then change to paid if the transaction goes through. \"\n            \"If it does not go through successfully, \"\n            \"its status will change to `failed` or `canceled`.\"\n        ),\n    )\n    type = StripeEnumField(enum=enums.PayoutType)\n\n    def __str__(self):\n        return f\"{self.amount} ({enums.PayoutStatus.humanize(self.status)})\"\n\n\nclass Price(StripeModel):\n    \"\"\"\n    Prices define the unit cost, currency, and (optional) billing cycle for\n    both recurring and one-time purchases of products.\n\n    Price and Plan objects are the same, but use a different representation.\n    Creating a recurring Price in Stripe also makes a Plan available, and vice versa.\n    This is not the case for a Price with interval=one_time.\n\n    Price objects are a more recent API representation, support more features\n    and its usage is encouraged instead of Plan objects.\n\n    Stripe documentation:\n    - https://stripe.com/docs/api/prices\n    - https://stripe.com/docs/billing/prices-guide\n    \"\"\"\n\n    stripe_class = stripe.Price\n    expand_fields = [\"product\", \"tiers\"]\n    stripe_dashboard_item_name = \"prices\"\n\n    active = models.BooleanField(\n        help_text=\"Whether the price can be used for new purchases.\"\n    )\n    currency = StripeCurrencyCodeField()\n    nickname = models.CharField(\n        max_length=250,\n        blank=True,\n        help_text=\"A brief description of the plan, hidden from customers.\",\n    )\n    product = StripeForeignKey(\n        \"Product\",\n        on_delete=models.CASCADE,\n        related_name=\"prices\",\n        help_text=\"The product this price is associated with.\",\n    )\n    recurring = JSONField(\n        default=None,\n        blank=True,\n        null=True,\n        help_text=(\n            \"The recurring components of a price such as `interval` and `usage_type`.\"\n        ),\n    )\n    type = StripeEnumField(\n        enum=enums.PriceType,\n        help_text=(\n            \"Whether the price is for a one-time purchase or a recurring \"\n            \"(subscription) purchase.\"\n        ),\n    )\n    unit_amount = StripeQuantumCurrencyAmountField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"The unit amount in cents to be charged, represented as a whole \"\n            \"integer if possible. Null if a sub-cent precision is required.\"\n        ),\n    )\n    unit_amount_decimal = StripeDecimalCurrencyAmountField(\n        null=True,\n        blank=True,\n        max_digits=19,\n        decimal_places=12,\n        help_text=(\n            \"The unit amount in cents to be charged, represented as a decimal \"\n            \"string with at most 12 decimal places.\"\n        ),\n    )\n\n    # More attributes…\n    billing_scheme = StripeEnumField(\n        enum=enums.BillingScheme,\n        blank=True,\n        help_text=(\n            \"Describes how to compute the price per period. \"\n            \"Either `per_unit` or `tiered`. \"\n            \"`per_unit` indicates that the fixed amount (specified in `unit_amount` \"\n            \"or `unit_amount_decimal`) will be charged per unit in `quantity` \"\n            \"(for prices with `usage_type=licensed`), or per unit of total \"\n            \"usage (for prices with `usage_type=metered`). \"\n            \"`tiered` indicates that the unit pricing will be computed using \"\n            \"a tiering strategy as defined using the `tiers` and `tiers_mode` \"\n            \"attributes.\"\n        ),\n    )\n    lookup_key = models.CharField(\n        max_length=250,\n        null=True,\n        blank=True,\n        help_text=\"A lookup key used to retrieve prices dynamically from a \"\n        \"static string.\",\n    )\n    tiers = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Each element represents a pricing tier. \"\n            \"This parameter requires `billing_scheme` to be set to `tiered`.\"\n        ),\n    )\n    tiers_mode = StripeEnumField(\n        enum=enums.PriceTiersMode,\n        null=True,\n        blank=True,\n        help_text=(\n            \"Defines if the tiering price should be `graduated` or `volume` based. \"\n            \"In `volume`-based tiering, the maximum quantity within a period \"\n            \"determines the per unit price, in `graduated` tiering pricing can \"\n            \"successively change as the quantity grows.\"\n        ),\n    )\n    transform_quantity = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Apply a transformation to the reported usage or set quantity \"\n            \"before computing the amount billed. Cannot be combined with `tiers`.\"\n        ),\n    )\n\n    class Meta(object):\n        ordering = [\"unit_amount\"]\n\n    @classmethod\n    def get_or_create(cls, **kwargs):\n        \"\"\"Get or create a Price.\"\"\"\n\n        try:\n            return cls.objects.get(id=kwargs[\"id\"]), False\n        except cls.DoesNotExist:\n            return cls.create(**kwargs), True\n\n    @classmethod\n    def create(cls, **kwargs):\n        # A few minor things are changed in the api-version of the create call\n        api_kwargs = dict(kwargs)\n        if api_kwargs[\"unit_amount\"]:\n            api_kwargs[\"unit_amount\"] = int(api_kwargs[\"unit_amount\"] * 100)\n\n        if isinstance(api_kwargs.get(\"product\"), StripeModel):\n            api_kwargs[\"product\"] = api_kwargs[\"product\"].id\n\n        stripe_price = cls._api_create(**api_kwargs)\n\n        api_key = api_kwargs.get(\"api_key\") or djstripe_settings.STRIPE_SECRET_KEY\n        price = cls.sync_from_stripe_data(stripe_price, api_key=api_key)\n\n        return price\n\n    def __str__(self):\n        return f\"{self.human_readable_price} for {self.product.name}\"\n\n    @property\n    def human_readable_price(self):\n        if self.billing_scheme == \"per_unit\":\n            unit_amount = (self.unit_amount or 0) / 100\n            amount = get_friendly_currency_amount(unit_amount, self.currency)\n        else:\n            # tiered billing scheme\n            tier_1 = self.tiers[0]\n            formatted_unit_amount_tier_1 = get_friendly_currency_amount(\n                (tier_1[\"unit_amount\"] or 0) / 100, self.currency\n            )\n            amount = f\"Starts at {formatted_unit_amount_tier_1} per unit\"\n\n            # stripe shows flat fee even if it is set to 0.00\n            flat_amount_tier_1 = tier_1[\"flat_amount\"]\n            if flat_amount_tier_1 is not None:\n                formatted_flat_amount_tier_1 = get_friendly_currency_amount(\n                    flat_amount_tier_1 / 100, self.currency\n                )\n                amount = f\"{amount} + {formatted_flat_amount_tier_1}\"\n\n        format_args = {\"amount\": amount}\n\n        if self.recurring:\n            interval_count = self.recurring[\"interval_count\"]\n            if interval_count == 1:\n                interval = {\n                    \"day\": _(\"day\"),\n                    \"week\": _(\"week\"),\n                    \"month\": _(\"month\"),\n                    \"year\": _(\"year\"),\n                }[self.recurring[\"interval\"]]\n                template = _(\"{amount}/{interval}\")\n                format_args[\"interval\"] = interval\n            else:\n                interval = {\n                    \"day\": _(\"days\"),\n                    \"week\": _(\"weeks\"),\n                    \"month\": _(\"months\"),\n                    \"year\": _(\"years\"),\n                }[self.recurring[\"interval\"]]\n                template = _(\"{amount} / every {interval_count} {interval}\")\n                format_args[\"interval\"] = interval\n                format_args[\"interval_count\"] = interval_count\n\n        else:\n            template = _(\"{amount} (one time)\")\n\n        return format_lazy(template, **format_args)\n\n\nclass Refund(StripeModel):\n    \"\"\"\n    Refund objects allow you to refund a charge that has previously been created\n    but not yet refunded. Funds will be refunded to the credit or debit card\n    that was originally charged.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#refund_object\n    \"\"\"\n\n    stripe_class = stripe.Refund\n\n    amount = StripeQuantumCurrencyAmountField(help_text=\"Amount, in cents.\")\n    balance_transaction = StripeForeignKey(\n        \"BalanceTransaction\",\n        on_delete=models.SET_NULL,\n        null=True,\n        help_text=\"Balance transaction that describes the impact on your account \"\n        \"balance.\",\n    )\n    charge = StripeForeignKey(\n        \"Charge\",\n        on_delete=models.CASCADE,\n        related_name=\"refunds\",\n        help_text=\"The charge that was refunded\",\n    )\n    currency = StripeCurrencyCodeField()\n    failure_balance_transaction = StripeForeignKey(\n        \"BalanceTransaction\",\n        on_delete=models.SET_NULL,\n        related_name=\"failure_refunds\",\n        null=True,\n        blank=True,\n        help_text=\"If the refund failed, this balance transaction describes the \"\n        \"adjustment made on your account balance that reverses the initial \"\n        \"balance transaction.\",\n    )\n    failure_reason = StripeEnumField(\n        enum=enums.RefundFailureReason,\n        default=\"\",\n        blank=True,\n        help_text=\"If the refund failed, the reason for refund failure if known.\",\n    )\n    reason = StripeEnumField(\n        enum=enums.RefundReason,\n        blank=True,\n        default=\"\",\n        help_text=\"Reason for the refund.\",\n    )\n    receipt_number = models.CharField(\n        max_length=9,\n        default=\"\",\n        blank=True,\n        help_text=\"The transaction number that appears on email receipts sent \"\n        \"for this charge.\",\n    )\n    status = StripeEnumField(\n        blank=True, enum=enums.RefundStatus, help_text=\"Status of the refund.\"\n    )\n    # todo implement source_transfer_reversal and transfer_reversal\n\n    def get_stripe_dashboard_url(self):\n        return self.charge.get_stripe_dashboard_url()\n\n    def __str__(self):\n        amount = get_friendly_currency_amount(self.amount / 100, self.currency)\n        status = enums.RefundStatus.humanize(self.status)\n        return f\"{amount} ({status})\"\n"
  },
  {
    "path": "djstripe/models/fraud.py",
    "content": ""
  },
  {
    "path": "djstripe/models/orders.py",
    "content": "import stripe\nfrom django.db import models\n\nfrom djstripe.models.billing import Discount\nfrom djstripe.settings import djstripe_settings\n\nfrom ..enums import OrderStatus\nfrom ..fields import (\n    JSONField,\n    StripeCurrencyCodeField,\n    StripeEnumField,\n    StripeForeignKey,\n    StripeQuantumCurrencyAmountField,\n)\nfrom ..settings import djstripe_settings\nfrom .base import StripeModel\n\n\nclass Order(StripeModel):\n    \"\"\"\n    An Order describes a purchase being made by a customer,\n    including the products & quantities being purchased, the order status,\n    the payment information, and the billing/shipping details.\n\n    Stripe documentation: https://stripe.com/docs/api/orders_v2/object?lang=python\n    \"\"\"\n\n    stripe_class = stripe.Order\n    expand_fields = [\"customer\", \"line_items\", \"discounts\", \"total_details.breakdown\"]\n    stripe_dashboard_item_name = \"orders\"\n\n    amount_subtotal = StripeQuantumCurrencyAmountField(\n        help_text=\"Order cost before any discounts or taxes are applied. A positive integer representing the subtotal of the order in the smallest currency unit (e.g., 100 cents to charge $1.00 or 100 to charge ¥100, a zero-decimal currency).\"\n    )\n    amount_total = StripeQuantumCurrencyAmountField(\n        help_text=\"Total order cost after discounts and taxes are applied. A positive integer representing the cost of the order in the smallest currency unit (e.g., 100 cents to charge $1.00 or 100 to charge ¥100, a zero-decimal currency). To submit an order, the total must be either 0 or at least $0.50 USD or equivalent in charge currency.\"\n    )\n    application = models.CharField(\n        max_length=255,\n        blank=True,\n        help_text=\"ID of the Connect application that created the Order, if any.\",\n    )\n    automatic_tax = JSONField(\n        help_text=\"Settings and latest results for automatic tax lookup for this Order.\"\n    )\n    billing_details = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Customer billing details associated with the order.\",\n    )\n    client_secret = models.TextField(\n        max_length=5000,\n        help_text=(\n            \"The client secret of this PaymentIntent. \"\n            \"Used for client-side retrieval using a publishable key.\"\n        ),\n    )\n    currency = StripeCurrencyCodeField(\n        help_text=\"Three-letter ISO currency code, in lowercase. Must be a supported currency.\"\n    )\n    # not deleting order when customer is deleted, because order may be important for taxation and audit purposes\n    customer = StripeForeignKey(\n        \"Customer\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        help_text=\"The customer which this orders belongs to.\",\n    )\n    discounts = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"The discounts applied to the order.\",\n    )\n    ip_address = models.GenericIPAddressField(\n        null=True,\n        blank=True,\n        help_text=\"A recent IP address of the purchaser used for tax reporting and tax location inference.\",\n    )\n    line_items = JSONField(\n        help_text=\"A list of line items the customer is ordering. Each line item includes information about the product, the quantity, and the resulting cost. There is a maximum of 100 line items.\",\n    )\n    payment = JSONField(\n        help_text=\"Payment information associated with the order. Includes payment status, settings, and a PaymentIntent ID\",\n    )\n    payment_intent = StripeForeignKey(\n        \"PaymentIntent\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        help_text=\"ID of the payment intent associated with this order. Null when the order is open.\",\n    )\n    shipping_cost = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"The details of the customer cost of shipping, including the customer chosen ShippingRate.\",\n    )\n    shipping_details = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Customer shipping information associated with the order.\",\n    )\n    status = StripeEnumField(\n        enum=OrderStatus, help_text=\"The overall status of the order.\"\n    )\n    tax_details = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Tax details about the purchaser for this order.\",\n    )\n    total_details = JSONField(\n        help_text=\"Tax, discount, and shipping details for the computed total amount of this order.\",\n    )\n\n    def __str__(self):\n        template = f\"on {self.created.strftime('%m/%d/%Y')} ({self.status})\"\n        if self.status in (OrderStatus.open, OrderStatus.canceled):\n            return \"Created \" + template\n        elif self.status in (\n            OrderStatus.submitted,\n            OrderStatus.complete,\n            OrderStatus.processing,\n        ):\n            return \"Placed \" + template\n        return self.id\n\n    @classmethod\n    def _manipulate_stripe_object_hook(cls, data):\n        data[\"payment_intent\"] = data[\"payment\"][\"payment_intent\"]\n        return data\n\n    def _attach_objects_post_save_hook(\n        self,\n        cls,\n        data,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        pending_relations=None,\n    ):\n        super()._attach_objects_post_save_hook(\n            cls, data, api_key=api_key, pending_relations=pending_relations\n        )\n\n        # sync every discount\n        for discount in self.discounts:\n            Discount.sync_from_stripe_data(discount, api_key=api_key)\n\n    def cancel(self, api_key=None, stripe_account=None, **kwargs):\n        \"\"\"\n        Cancels the order as well as the payment intent if one is attached.\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n        api_key = api_key or self.default_api_key\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        return self.stripe_class.cancel(\n            self.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        )\n\n    def reopen(self, api_key=None, stripe_account=None, **kwargs):\n        \"\"\"\n        Reopens a submitted order.\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n        api_key = api_key or self.default_api_key\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        return self.stripe_class.reopen(\n            self.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        )\n\n    def submit(self, api_key=None, stripe_account=None, **kwargs):\n        \"\"\"\n        Submitting an Order transitions the status to processing and creates a PaymentIntent object\n        so the order can be paid.\n        If the Order has an amount_total of 0, no PaymentIntent object will be created.\n        Once the order is submitted, its contents cannot be changed,\n        unless the reopen method is called.\n\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        :param stripe_account: The optional connected account \\\n            for which this request is being made.\n        :type stripe_account: string\n        \"\"\"\n        api_key = api_key or self.default_api_key\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        return self.stripe_class.submit(\n            self.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        )\n"
  },
  {
    "path": "djstripe/models/payment_methods.py",
    "content": "from typing import Optional, Union\n\nimport stripe\nfrom django.db import models, transaction\nfrom stripe.error import InvalidRequestError\n\nfrom .. import enums\nfrom ..exceptions import ImpossibleAPIRequest, StripeObjectManipulationException\nfrom ..fields import (\n    JSONField,\n    StripeCurrencyCodeField,\n    StripeDecimalCurrencyAmountField,\n    StripeEnumField,\n    StripeForeignKey,\n)\nfrom ..settings import djstripe_settings\nfrom ..utils import get_id_from_stripe_data\nfrom .account import Account\nfrom .base import StripeModel, logger\nfrom .core import Customer\n\n\nclass DjstripePaymentMethod(models.Model):\n    \"\"\"\n    An internal model that abstracts the legacy Card and BankAccount\n    objects with Source objects.\n\n    Contains two fields: `id` and `type`:\n    - `id` is the id of the Stripe object.\n    - `type` can be `card`, `bank_account` `account` or `source`.\n    \"\"\"\n\n    id = models.CharField(max_length=255, primary_key=True)\n    type = models.CharField(max_length=50, db_index=True)\n\n    @classmethod\n    def from_stripe_object(cls, data):\n        source_type = data[\"object\"]\n        model = cls._model_for_type(source_type)\n\n        with transaction.atomic():\n            model.sync_from_stripe_data(data)\n            instance, _ = cls.objects.get_or_create(\n                id=data[\"id\"], defaults={\"type\": source_type}\n            )\n\n        return instance\n\n    @classmethod\n    def _get_or_create_source(\n        cls, data, source_type=None, api_key=djstripe_settings.STRIPE_SECRET_KEY\n    ):\n        # prefer passed in source_type\n        if not source_type:\n            source_type = data[\"object\"]\n\n        try:\n            model = cls._model_for_type(source_type)\n            model._get_or_create_from_stripe_object(data, api_key=api_key)\n        except ValueError as e:\n            # This may happen if we have source types we don't know about.\n            # Let's not make dj-stripe entirely unusable if that happens.\n            logger.warning(\"Could not sync source of type %r: %s\", source_type, e)\n\n        return cls.objects.get_or_create(id=data[\"id\"], defaults={\"type\": source_type})\n\n    @classmethod\n    def _model_for_type(cls, type):\n        if type == \"card\":\n            return Card\n        elif type == \"source\":\n            return Source\n        elif type == \"bank_account\":\n            return BankAccount\n        elif type == \"account\":\n            return Account\n\n        raise ValueError(f\"Unknown source type: {type}\")\n\n    @property\n    def object_model(self):\n        return self._model_for_type(self.type)\n\n    def resolve(self):\n        return self.object_model.objects.get(id=self.id)\n\n    @classmethod\n    def _get_or_create_from_stripe_object(\n        cls,\n        data,\n        field_name=\"id\",\n        refetch=True,\n        current_ids=None,\n        pending_relations=None,\n        save=True,\n        stripe_account=None,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    ):\n        raw_field_data = data.get(field_name)\n        id_ = get_id_from_stripe_data(raw_field_data)\n        if not id_:\n            raise ValueError(f\"ID not found in Stripe data: {raw_field_data!r}\")\n\n        if id_.startswith(\"card\"):\n            source_cls = Card\n            source_type = \"card\"\n        elif id_.startswith(\"src\"):\n            source_cls = Source\n            source_type = \"source\"\n        elif id_.startswith(\"ba\"):\n            source_cls = BankAccount\n            source_type = \"bank_account\"\n        elif id_.startswith(\"acct\"):\n            source_cls = Account\n            source_type = \"account\"\n        else:\n            # This may happen if we have source types we don't know about.\n            # Let's not make dj-stripe entirely unusable if that happens.\n            logger.warning(f\"Unknown Object. Could not sync source with id: {id_}\")\n            return cls.objects.get_or_create(\n                id=id_, defaults={\"type\": f\"UNSUPPORTED_{id_}\"}\n            )\n\n        # call model's _get_or_create_from_stripe_object to ensure\n        # that object exists before getting or creating its source object\n        source_cls._get_or_create_from_stripe_object(\n            data,\n            field_name,\n            refetch=refetch,\n            current_ids=current_ids,\n            pending_relations=pending_relations,\n            stripe_account=stripe_account,\n            api_key=api_key,\n        )\n\n        return cls.objects.get_or_create(id=id_, defaults={\"type\": source_type})\n\n\nclass LegacySourceMixin:\n    \"\"\"\n    Mixin for functionality shared between the legacy Card & BankAccount sources\n    \"\"\"\n\n    customer: Optional[StripeForeignKey]\n    account: Optional[StripeForeignKey]\n    id: str\n    default_api_key: str\n\n    @classmethod\n    def _get_customer_or_account_from_kwargs(cls, **kwargs):\n        account = kwargs.get(\"account\")\n        customer = kwargs.get(\"customer\")\n\n        if not account and not customer:\n            raise StripeObjectManipulationException(\n                f\"{cls.__name__} objects must be manipulated through either a \"\n                \"Stripe Connected Account or a customer. \"\n                \"Pass a Customer or an Account object into this call.\"\n            )\n\n        if account and not isinstance(account, Account):\n            raise StripeObjectManipulationException(\n                f\"{cls.__name__} objects must be manipulated through a Stripe Connected Account. \"\n                \"Pass an Account object into this call.\"\n            )\n\n        if customer and not isinstance(customer, Customer):\n            raise StripeObjectManipulationException(\n                f\"{cls.__name__} objects must be manipulated through a Customer. \"\n                \"Pass a Customer object into this call.\"\n            )\n\n        if account:\n            del kwargs[\"account\"]\n        if customer:\n            del kwargs[\"customer\"]\n\n        return account, customer, kwargs\n\n    @classmethod\n    def _api_create(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        # OVERRIDING the parent version of this function\n        # Cards & Bank Accounts must be manipulated through a customer or account.\n\n        account, customer, clean_kwargs = cls._get_customer_or_account_from_kwargs(\n            **kwargs\n        )\n\n        # First we try to retrieve by customer attribute,\n        # then by account attribute\n        if customer and account:\n            try:\n                # retrieve by customer\n                return customer.api_retrieve(api_key=api_key).sources.create(\n                    api_key=api_key, **clean_kwargs\n                )\n            except Exception as customer_exc:\n                try:\n                    # retrieve by account\n                    return account.api_retrieve(\n                        api_key=api_key\n                    ).external_accounts.create(api_key=api_key, **clean_kwargs)\n                except Exception:\n                    raise customer_exc\n\n        if customer:\n            return customer.api_retrieve(api_key=api_key).sources.create(\n                api_key=api_key, **clean_kwargs\n            )\n\n        if account:\n            return account.api_retrieve(api_key=api_key).external_accounts.create(\n                api_key=api_key, **clean_kwargs\n            )\n\n    @classmethod\n    def api_list(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        # OVERRIDING the parent version of this function\n        # Cards & Bank Accounts must be manipulated through a customer or account.\n\n        account, customer, clean_kwargs = cls._get_customer_or_account_from_kwargs(\n            **kwargs\n        )\n\n        object_name = cls.stripe_class.OBJECT_NAME\n\n        # First we try to retrieve by customer attribute,\n        # then by account attribute\n        if customer and account:\n            try:\n                # retrieve by customer\n                return (\n                    customer.api_retrieve(api_key=api_key)\n                    .sources.list(object=object_name, **clean_kwargs)\n                    .auto_paging_iter()\n                )\n            except Exception as customer_exc:\n                try:\n                    # retrieve by account\n                    return (\n                        account.api_retrieve(api_key=api_key)\n                        .external_accounts.list(object=object_name, **clean_kwargs)\n                        .auto_paging_iter()\n                    )\n                except Exception:\n                    raise customer_exc\n\n        if customer:\n            return (\n                customer.api_retrieve(api_key=api_key)\n                .sources.list(object=object_name, **clean_kwargs)\n                .auto_paging_iter()\n            )\n\n        if account:\n            return (\n                account.api_retrieve(api_key=api_key)\n                .external_accounts.list(object=object_name, **clean_kwargs)\n                .auto_paging_iter()\n            )\n\n        raise ImpossibleAPIRequest(\n            f\"Can't list {object_name} without a customer or account object.\"\n            \" This may happen if not all accounts or customer objects are in the db.\"\n            ' Please run \"python manage.py djstripe_sync_models Account Customer\" as a potential fix.'\n        )\n\n    def get_stripe_dashboard_url(self) -> str:\n        if self.customer:\n            return self.customer.get_stripe_dashboard_url()\n        elif self.account:\n            return f\"https://dashboard.stripe.com/{self.account.id}/settings/payouts\"\n        else:\n            return \"\"\n\n    def remove(self):\n        \"\"\"\n        Removes a legacy source from this customer's account.\n        \"\"\"\n\n        # First, wipe default source on all customers that use this card.\n        Customer.objects.filter(default_source=self.id).update(default_source=None)\n\n        try:\n            self._api_delete()\n        except InvalidRequestError as exc:\n            if \"No such source:\" in str(exc) or \"No such customer:\" in str(exc):\n                # The exception was thrown because the stripe customer or card\n                # was already deleted on the stripe side, ignore the exception\n                pass\n            else:\n                # The exception was raised for another reason, re-raise it\n                raise\n\n        self.delete()\n\n    def api_retrieve(self, api_key=None, stripe_account=None):\n        # OVERRIDING the parent version of this function\n        # Cards & Banks Accounts must be manipulated through a customer or account.\n\n        api_key = api_key or self.default_api_key\n\n        if self.customer:\n            return stripe.Customer.retrieve_source(\n                self.customer.id,\n                self.id,\n                expand=self.expand_fields,\n                stripe_account=stripe_account,\n                api_key=api_key,\n                stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            )\n\n        # try to retrieve by account attribute if retrieval by customer fails.\n        if self.account:\n            return stripe.Account.retrieve_external_account(\n                self.account.id,\n                self.id,\n                expand=self.expand_fields,\n                stripe_account=stripe_account,\n                api_key=api_key,\n                stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            )\n\n        raise ImpossibleAPIRequest(\n            f\"Can't retrieve {self.__class__} without a customer or account object.\"\n            \" This may happen if not all accounts or customer objects are in the db.\"\n            ' Please run \"python manage.py djstripe_sync_models Account Customer\" as a potential fix.'\n        )\n\n    def _api_delete(self, api_key=None, stripe_account=None, **kwargs):\n        # OVERRIDING the parent version of this function\n        # Cards & Banks Accounts must be manipulated through a customer or account.\n\n        api_key = api_key or self.default_api_key\n        # Prefer passed in stripe_account if set.\n        if not stripe_account:\n            stripe_account = self._get_stripe_account_id(api_key)\n\n        if self.customer:\n            return stripe.Customer.delete_source(\n                self.customer.id,\n                self.id,\n                api_key=api_key,\n                stripe_account=stripe_account,\n                **kwargs,\n            )\n\n        if self.account:\n            return stripe.Account.delete_external_account(\n                self.account.id,\n                self.id,\n                api_key=api_key,\n                stripe_account=stripe_account,\n                **kwargs,\n            )\n\n        raise ImpossibleAPIRequest(\n            f\"Can't delete {self.__class__} without a customer or account object.\"\n            \" This may happen if not all accounts or customer objects are in the db.\"\n            ' Please run \"python manage.py djstripe_sync_models Account Customer\" as a potential fix.'\n        )\n\n\nclass BankAccount(LegacySourceMixin, StripeModel):\n    \"\"\"\n    These bank accounts are payment methods on Customer objects.\n    On the other hand External Accounts are transfer destinations on Account\n    objects for Custom accounts. They can be bank accounts or debit cards as well.\n\n    Stripe documentation:https://stripe.com/docs/api/customer_bank_accounts\n    \"\"\"\n\n    stripe_class = stripe.BankAccount\n\n    account = StripeForeignKey(\n        \"Account\",\n        on_delete=models.PROTECT,\n        null=True,\n        blank=True,\n        related_name=\"bank_accounts\",\n        help_text=\"The external account the charge was made on behalf of. Null here indicates \"\n        \"that this value was never set.\",\n    )\n    account_holder_name = models.TextField(\n        max_length=5000,\n        blank=True,\n        help_text=\"The name of the person or business that owns the bank account.\",\n    )\n    account_holder_type = StripeEnumField(\n        enum=enums.BankAccountHolderType,\n        help_text=\"The type of entity that holds the account.\",\n    )\n    bank_name = models.CharField(\n        max_length=255,\n        help_text=\"Name of the bank associated with the routing number \"\n        \"(e.g., `WELLS FARGO`).\",\n    )\n    country = models.CharField(\n        max_length=2,\n        help_text=\"Two-letter ISO code representing the country the bank account \"\n        \"is located in.\",\n    )\n    currency = StripeCurrencyCodeField()\n    customer = StripeForeignKey(\n        \"Customer\", on_delete=models.SET_NULL, null=True, related_name=\"bank_account\"\n    )\n    default_for_currency = models.BooleanField(\n        null=True,\n        help_text=\"Whether this external account (BankAccount) is the default account for \"\n        \"its currency.\",\n    )\n    fingerprint = models.CharField(\n        max_length=16,\n        help_text=(\n            \"Uniquely identifies this particular bank account. \"\n            \"You can use this attribute to check whether two bank accounts are \"\n            \"the same.\"\n        ),\n    )\n    last4 = models.CharField(max_length=4)\n    routing_number = models.CharField(\n        max_length=255, help_text=\"The routing transit number for the bank account.\"\n    )\n    status = StripeEnumField(enum=enums.BankAccountStatus)\n\n    def __str__(self):\n        default = False\n        # prefer to show it by customer format if present\n        if self.customer:\n            default_source = self.customer.default_source\n            default_payment_method = self.customer.default_payment_method\n\n            if (default_payment_method and self.id == default_payment_method.id) or (\n                default_source and self.id == default_source.id\n            ):\n                # current card is the default payment method or source\n                default = True\n\n            customer_template = f\"{self.bank_name} {self.routing_number} ({self.human_readable_status}) {'Default' if default else ''} {self.currency}\"\n            return customer_template\n\n        default = getattr(self, \"default_for_currency\", False)\n        account_template = f\"{self.bank_name} {self.currency} {'Default' if default else ''} {self.routing_number} {self.last4}\"\n        return account_template\n\n    @property\n    def human_readable_status(self):\n        if self.status == \"new\":\n            return \"Pending Verification\"\n        return enums.BankAccountStatus.humanize(self.status)\n\n    def api_retrieve(self, **kwargs):\n        if not self.customer and not self.account:\n            raise ImpossibleAPIRequest(\n                \"Can't retrieve a bank account without a customer or account object.\"\n                \" This may happen if not all accounts or customer objects are in the db.\"\n                ' Please run \"python manage.py djstripe_sync_models Account Customer\" as a potential fix.'\n            )\n\n        return super().api_retrieve(**kwargs)\n\n\nclass Card(LegacySourceMixin, StripeModel):\n    \"\"\"\n    You can store multiple cards on a customer in order to charge the customer later.\n\n    This is a legacy model which only applies to the \"v2\" Stripe API (eg. Checkout.js).\n    You should strive to use the Stripe \"v3\" API (eg. Stripe Elements).\n    Also see: https://stripe.com/docs/stripe-js/elements/migrating\n    When using Elements, you will not be using Card objects. Instead, you will use\n    Source objects.\n    A Source object of type \"card\" is equivalent to a Card object. However, Card\n    objects cannot be converted into Source objects by Stripe at this time.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#cards\n    \"\"\"\n\n    stripe_class = stripe.Card\n    # Stripe Custom Connected Accounts can have cards as \"Payout Sources\"\n    account = StripeForeignKey(\n        \"Account\",\n        on_delete=models.PROTECT,\n        null=True,\n        blank=True,\n        related_name=\"cards\",\n        help_text=\"The external account the charge was made on behalf of. Null here indicates \"\n        \"that this value was never set.\",\n    )\n    address_city = models.TextField(\n        max_length=5000,\n        blank=True,\n        default=\"\",\n        help_text=\"City/District/Suburb/Town/Village.\",\n    )\n    address_country = models.TextField(\n        max_length=5000, blank=True, default=\"\", help_text=\"Billing address country.\"\n    )\n    address_line1 = models.TextField(\n        max_length=5000,\n        blank=True,\n        default=\"\",\n        help_text=\"Street address/PO Box/Company name.\",\n    )\n    address_line1_check = StripeEnumField(\n        enum=enums.CardCheckResult,\n        blank=True,\n        default=\"\",\n        help_text=\"If `address_line1` was provided, results of the check.\",\n    )\n    address_line2 = models.TextField(\n        max_length=5000,\n        blank=True,\n        default=\"\",\n        help_text=\"Apartment/Suite/Unit/Building.\",\n    )\n    address_state = models.TextField(\n        max_length=5000,\n        blank=True,\n        default=\"\",\n        help_text=\"State/County/Province/Region.\",\n    )\n    address_zip = models.TextField(\n        max_length=5000, blank=True, default=\"\", help_text=\"ZIP or postal code.\"\n    )\n    address_zip_check = StripeEnumField(\n        enum=enums.CardCheckResult,\n        blank=True,\n        default=\"\",\n        help_text=\"If `address_zip` was provided, results of the check.\",\n    )\n    brand = StripeEnumField(enum=enums.CardBrand, help_text=\"Card brand.\")\n    country = models.CharField(\n        max_length=2,\n        default=\"\",\n        blank=True,\n        help_text=\"Two-letter ISO code representing the country of the card.\",\n    )\n    customer = StripeForeignKey(\n        \"Customer\", on_delete=models.SET_NULL, null=True, related_name=\"legacy_cards\"\n    )\n    cvc_check = StripeEnumField(\n        enum=enums.CardCheckResult,\n        default=\"\",\n        blank=True,\n        help_text=\"If a CVC was provided, results of the check.\",\n    )\n    default_for_currency = models.BooleanField(\n        null=True,\n        help_text=\"Whether this external account (Card) is the default account for \"\n        \"its currency.\",\n    )\n    dynamic_last4 = models.CharField(\n        max_length=4,\n        default=\"\",\n        blank=True,\n        help_text=\"(For tokenized numbers only.) The last four digits of the device \"\n        \"account number.\",\n    )\n    exp_month = models.IntegerField(help_text=\"Card expiration month.\")\n    exp_year = models.IntegerField(help_text=\"Card expiration year.\")\n    fingerprint = models.CharField(\n        default=\"\",\n        blank=True,\n        max_length=16,\n        help_text=\"Uniquely identifies this particular card number.\",\n    )\n    funding = StripeEnumField(\n        enum=enums.CardFundingType, help_text=\"Card funding type.\"\n    )\n    last4 = models.CharField(max_length=4, help_text=\"Last four digits of Card number.\")\n    name = models.TextField(\n        max_length=5000, default=\"\", blank=True, help_text=\"Cardholder name.\"\n    )\n    tokenization_method = StripeEnumField(\n        enum=enums.CardTokenizationMethod,\n        default=\"\",\n        blank=True,\n        help_text=\"If the card number is tokenized, this is the method that was used.\",\n    )\n\n    def __str__(self):\n        default = False\n        # prefer to show it by customer format if present\n        if self.customer:\n            default_source = self.customer.default_source\n            default_payment_method = self.customer.default_payment_method\n\n            if (default_payment_method and self.id == default_payment_method.id) or (\n                default_source and self.id == default_source.id\n            ):\n                # current card is the default payment method or source\n                default = True\n\n            customer_template = f\"{enums.CardBrand.humanize(self.brand)} {self.last4} {'Default' if default else ''} Expires {self.exp_month} {self.exp_year}\"\n            return customer_template\n\n        elif self.account:\n            default = getattr(self, \"default_for_currency\", False)\n            account_template = f\"{enums.CardBrand.humanize(self.brand)} {self.account.default_currency} {'Default' if default else ''} {self.last4}\"\n            return account_template\n\n        return self.id or \"\"\n\n    @classmethod\n    def create_token(\n        cls,\n        number: str,\n        exp_month: int,\n        exp_year: int,\n        cvc: str,\n        api_key: str = djstripe_settings.STRIPE_SECRET_KEY,\n        **kwargs,\n    ) -> stripe.Token:\n        \"\"\"\n        Creates a single use token that wraps the details of a credit card.\n        This token can be used in place of a credit card dictionary with any API method.\n        These tokens can only be used once: by creating a new charge object,\n        or attaching them to a customer.\n        (Source: https://stripe.com/docs/api?lang=python#create_card_token)\n\n        :param number: The card number without any separators (no spaces)\n        :param exp_month: The card's expiration month. (two digits)\n        :param exp_year: The card's expiration year. (four digits)\n        :param cvc: Card security code.\n        :param api_key: The API key to use\n        \"\"\"\n\n        card = {\n            \"number\": number,\n            \"exp_month\": exp_month,\n            \"exp_year\": exp_year,\n            \"cvc\": cvc,\n        }\n        card.update(kwargs)\n\n        return stripe.Token.create(api_key=api_key, card=card)\n\n\nclass Source(StripeModel):\n    \"\"\"\n    Source objects allow you to accept a variety of payment methods.\n    They represent a customer's payment instrument, and can be used with\n    the Stripe API just like a Card object: once chargeable,\n    they can be charged, or can be attached to customers.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#sources\n    \"\"\"\n\n    amount = StripeDecimalCurrencyAmountField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Amount (as decimal) associated with the source. \"\n            \"This is the amount for which the source will be chargeable once ready. \"\n            \"Required for `single_use` sources.\"\n        ),\n    )\n    client_secret = models.CharField(\n        max_length=255,\n        help_text=(\n            \"The client secret of the source. \"\n            \"Used for client-side retrieval using a publishable key.\"\n        ),\n    )\n    currency = StripeCurrencyCodeField(default=\"\", blank=True)\n    flow = StripeEnumField(\n        enum=enums.SourceFlow, help_text=\"The authentication flow of the source.\"\n    )\n    owner = JSONField(\n        help_text=(\n            \"Information about the owner of the payment instrument that may be \"\n            \"used or required by particular source types.\"\n        )\n    )\n    statement_descriptor = models.CharField(\n        max_length=255,\n        default=\"\",\n        blank=True,\n        help_text=\"Extra information about a source. This will appear on your \"\n        \"customer's statement every time you charge the source.\",\n    )\n    status = StripeEnumField(\n        enum=enums.SourceStatus,\n        help_text=\"The status of the source. Only `chargeable` sources can be used \"\n        \"to create a charge.\",\n    )\n    type = StripeEnumField(enum=enums.SourceType, help_text=\"The type of the source.\")\n    usage = StripeEnumField(\n        enum=enums.SourceUsage,\n        help_text=\"Whether this source should be reusable or not. \"\n        \"Some source types may or may not be reusable by construction, \"\n        \"while other may leave the option at creation.\",\n    )\n\n    # Flows\n    code_verification = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Information related to the code verification flow. \"\n        \"Present if the source is authenticated by a verification code \"\n        \"(`flow` is `code_verification`).\",\n    )\n    receiver = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Information related to the receiver flow. \"\n        \"Present if the source is a receiver (`flow` is `receiver`).\",\n    )\n    redirect = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Information related to the redirect flow. \"\n        \"Present if the source is authenticated by a redirect (`flow` is `redirect`).\",\n    )\n\n    source_data = JSONField(help_text=\"The data corresponding to the source type.\")\n\n    customer = StripeForeignKey(\n        \"Customer\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        related_name=\"sources\",\n    )\n\n    stripe_class = stripe.Source\n    stripe_dashboard_item_name = \"sources\"\n\n    def __str__(self):\n        return f\"{self.type} {self.id}\"\n\n    @classmethod\n    def _manipulate_stripe_object_hook(cls, data):\n        # The source_data dict is an alias of all the source types\n        data[\"source_data\"] = data[data[\"type\"]]\n        return data\n\n    def _attach_objects_hook(\n        self, cls, data, api_key=djstripe_settings.STRIPE_SECRET_KEY, current_ids=None\n    ):\n        customer = None\n        # \"customer\" key could be like \"cus_6lsBvm5rJ0zyHc\" or {\"id\": \"cus_6lsBvm5rJ0zyHc\"}\n        customer_id = get_id_from_stripe_data(data.get(\"customer\"))\n\n        if current_ids is None or customer_id not in current_ids:\n            customer = cls._stripe_object_to_customer(\n                target_cls=Customer, data=data, current_ids=current_ids, api_key=api_key\n            )\n\n        if customer:\n            self.customer = customer\n        else:\n            self.customer = None\n\n    def detach(self) -> bool:\n        \"\"\"\n        Detach the source from its customer.\n        \"\"\"\n\n        # First, wipe default source on all customers that use this.\n        Customer.objects.filter(default_source=self.id).update(default_source=None)\n        api_key = self.default_api_key\n        try:\n            # TODO - we could use the return value of sync_from_stripe_data\n            #  or call its internals - self._sync/_attach_objects_hook etc here\n            #  to update `self` at this point?\n            self.sync_from_stripe_data(\n                self.api_retrieve(api_key=api_key).detach(), api_key=api_key\n            )\n            return True\n        except InvalidRequestError:\n            # The source was already detached. Resyncing.\n            self.sync_from_stripe_data(\n                self.api_retrieve(api_key=self.default_api_key),\n                api_key=self.default_api_key,\n            )\n            return False\n\n    @classmethod\n    def api_list(cls, api_key=djstripe_settings.STRIPE_SECRET_KEY, **kwargs):\n        \"\"\"\n        Call the stripe API's list operation for this model.\n        :param api_key: The api key to use for this request. \\\n            Defaults to djstripe_settings.STRIPE_SECRET_KEY.\n        :type api_key: string\n        See Stripe documentation for accepted kwargs for each object.\n        :returns: an iterator over all items in the query\n        \"\"\"\n        return Customer.stripe_class.list_sources(\n            object=\"source\",\n            api_key=api_key,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **kwargs,\n        ).auto_paging_iter()\n\n\nclass PaymentMethod(StripeModel):\n    \"\"\"\n    PaymentMethod objects represent your customer's payment instruments.\n    You can use them with PaymentIntents to collect payments or save them\n    to Customer objects to store instrument details for future payments.\n\n    Stripe documentation: https://stripe.com/docs/api?lang=python#payment_methods\n    \"\"\"\n\n    stripe_class = stripe.PaymentMethod\n    description = None\n\n    billing_details = JSONField(\n        help_text=(\n            \"Billing information associated with the PaymentMethod that may be used or \"\n            \"required by particular types of payment methods.\"\n        )\n    )\n    customer = StripeForeignKey(\n        \"Customer\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        related_name=\"payment_methods\",\n        help_text=(\n            \"Customer to which this PaymentMethod is saved. \"\n            \"This will not be set when the PaymentMethod has \"\n            \"not been saved to a Customer.\"\n        ),\n    )\n    type = StripeEnumField(\n        enum=enums.PaymentMethodType,\n        help_text=\"The type of the PaymentMethod.\",\n    )\n    acss_debit = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `acss_debit`\",\n    )\n    affirm = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `affirm`\",\n    )\n    afterpay_clearpay = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `afterpay_clearpay`\",\n    )\n    alipay = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `alipay`\",\n    )\n    au_becs_debit = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `au_becs_debit`\",\n    )\n    bacs_debit = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `bacs_debit`\",\n    )\n    bancontact = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `bancontact`\",\n    )\n    blik = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `blik`\",\n    )\n    boleto = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `boleto`\",\n    )\n    card = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `card`\",\n    )\n    card_present = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `card_present`\",\n    )\n    customer_balance = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `customer_balance`\",\n    )\n    eps = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `eps`\",\n    )\n    fpx = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `fpx`\",\n    )\n    giropay = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `giropay`\",\n    )\n    grabpay = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `grabpay`\",\n    )\n    ideal = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `ideal`\",\n    )\n    interac_present = JSONField(\n        null=True,\n        blank=True,\n        help_text=(\n            \"Additional information for payment methods of type `interac_present`\"\n        ),\n    )\n    klarna = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `klarna`\",\n    )\n    konbini = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `konbini`\",\n    )\n    link = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `link`\",\n    )\n    oxxo = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `oxxo`\",\n    )\n    p24 = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `p24`\",\n    )\n    paynow = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `paynow`\",\n    )\n    pix = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `pix`\",\n    )\n    promptpay = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `promptpay`\",\n    )\n    sepa_debit = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `sepa_debit`\",\n    )\n    sofort = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `sofort`\",\n    )\n    us_bank_account = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `us_bank_account`\",\n    )\n    wechat_pay = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"Additional information for payment methods of type `wechat_pay`\",\n    )\n\n    def __str__(self):\n        if self.customer:\n            return f\"{enums.PaymentMethodType.humanize(self.type)} for {self.customer}\"\n        return f\"{enums.PaymentMethodType.humanize(self.type)} is not associated with any customer\"\n\n    def get_stripe_dashboard_url(self) -> str:\n        if self.customer:\n            return self.customer.get_stripe_dashboard_url()\n        return \"\"\n\n    def _attach_objects_hook(\n        self, cls, data, api_key=djstripe_settings.STRIPE_SECRET_KEY, current_ids=None\n    ):\n        customer = None\n        # \"customer\" key could be like \"cus_6lsBvm5rJ0zyHc\" or {\"id\": \"cus_6lsBvm5rJ0zyHc\"}\n        customer_id = get_id_from_stripe_data(data.get(\"customer\"))\n\n        if current_ids is None or customer_id not in current_ids:\n            customer = cls._stripe_object_to_customer(\n                target_cls=Customer, data=data, current_ids=current_ids, api_key=api_key\n            )\n\n        if customer:\n            self.customer = customer\n        else:\n            self.customer = None\n\n    @classmethod\n    def attach(\n        cls,\n        payment_method: Union[str, \"PaymentMethod\"],\n        customer: Union[str, Customer],\n        api_key: str = djstripe_settings.STRIPE_SECRET_KEY,\n    ) -> \"PaymentMethod\":\n        \"\"\"\n        Attach a payment method to a customer\n        \"\"\"\n\n        if isinstance(payment_method, StripeModel):\n            payment_method = payment_method.id\n\n        if isinstance(customer, StripeModel):\n            customer = customer.id\n\n        extra_kwargs = {}\n        if not isinstance(payment_method, stripe.PaymentMethod):\n            # send api_key if we're not passing in a Stripe object\n            # avoids \"Received unknown parameter: api_key\" since api uses the\n            # key cached in the Stripe object\n            extra_kwargs = {\"api_key\": api_key}\n\n        stripe_payment_method = stripe.PaymentMethod.attach(\n            payment_method, customer=customer, **extra_kwargs\n        )\n        return cls.sync_from_stripe_data(stripe_payment_method, api_key=api_key)\n\n    def detach(self):\n        \"\"\"\n        Detach the payment method from its customer.\n\n        :return: Returns true if the payment method was newly detached, \\\n                 false if it was already detached\n        :rtype: bool\n        \"\"\"\n        # Find customers that use this\n        customers = Customer.objects.filter(default_payment_method=self).all()\n        changed = True\n\n        # special handling is needed for legacy \"card\"-type PaymentMethods,\n        # since detaching them deletes them within Stripe.\n        # see https://github.com/dj-stripe/dj-stripe/pull/967\n        is_legacy_card = self.id.startswith(\"card_\")\n\n        try:\n            self.sync_from_stripe_data(self.api_retrieve().detach())\n\n            # resync customer to update .default_payment_method and\n            # .invoice_settings.default_payment_method\n            for customer in customers:\n                Customer.sync_from_stripe_data(customer.api_retrieve())\n\n        except (InvalidRequestError,):\n            # The source was already detached. Resyncing.\n\n            if self.pk and not is_legacy_card:\n                self.sync_from_stripe_data(self.api_retrieve())\n            changed = False\n\n        if self.pk:\n            if is_legacy_card:\n                self.delete()\n            else:\n                self.refresh_from_db()\n\n        return changed\n"
  },
  {
    "path": "djstripe/models/sigma.py",
    "content": "import stripe\nfrom django.db import models\n\nfrom .. import enums\nfrom ..fields import JSONField, StripeDateTimeField, StripeEnumField, StripeForeignKey\nfrom .base import StripeModel\n\n\n# TODO Add Tests\nclass ScheduledQueryRun(StripeModel):\n    \"\"\"\n    Stripe documentation: https://stripe.com/docs/api?lang=python#scheduled_queries\n    \"\"\"\n\n    stripe_class = stripe.sigma.ScheduledQueryRun\n\n    data_load_time = StripeDateTimeField(\n        help_text=\"When the query was run, Sigma contained a snapshot of your \"\n        \"Stripe data at this time.\"\n    )\n    error = JSONField(\n        null=True,\n        blank=True,\n        help_text=\"If the query run was not succeesful, contains information \"\n        \"about the failure.\",\n    )\n    file = StripeForeignKey(\n        \"file\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        help_text=\"The file object representing the results of the query.\",\n    )\n    result_available_until = StripeDateTimeField(\n        help_text=\"Time at which the result expires and is no longer available \"\n        \"for download.\"\n    )\n    sql = models.TextField(max_length=5000, help_text=\"SQL for the query.\")\n    status = StripeEnumField(\n        enum=enums.ScheduledQueryRunStatus, help_text=\"The query's execution status.\"\n    )\n    title = models.TextField(max_length=5000, help_text=\"Title of the query.\")\n\n    # TODO Write corresponding test\n    def __str__(self):\n        return f\"{self.title or self.id} ({self.status})\"\n"
  },
  {
    "path": "djstripe/models/webhooks.py",
    "content": "\"\"\"\nModule for dj-stripe Webhook models\n\"\"\"\n\nimport json\nimport warnings\nfrom traceback import format_exc\nfrom uuid import uuid4\n\nimport stripe\nfrom django.db import models\nfrom django.utils.datastructures import CaseInsensitiveMapping\nfrom django.utils.functional import cached_property\n\nfrom .. import signals\nfrom ..enums import WebhookEndpointStatus\nfrom ..fields import JSONField, StripeEnumField, StripeForeignKey\nfrom ..settings import djstripe_settings\nfrom .base import StripeModel, logger\nfrom .core import Event\n\n\n# TODO: Add Tests\nclass WebhookEndpoint(StripeModel):\n    stripe_class = stripe.WebhookEndpoint\n    stripe_dashboard_item_name = \"webhooks\"\n\n    api_version = models.CharField(\n        max_length=64,\n        blank=True,\n        help_text=\"The API version events are rendered as for this webhook endpoint. Defaults to the configured Stripe API Version.\",\n    )\n    enabled_events = JSONField(\n        help_text=(\n            \"The list of events to enable for this endpoint. \"\n            \"['*'] indicates that all events are enabled, except those that require explicit selection.\"\n        )\n    )\n    secret = models.CharField(\n        max_length=256,\n        blank=True,\n        editable=False,\n        help_text=\"The endpoint's secret, used to generate webhook signatures.\",\n    )\n    status = StripeEnumField(\n        enum=WebhookEndpointStatus,\n        help_text=\"The status of the webhook. It can be enabled or disabled.\",\n    )\n    url = models.URLField(help_text=\"The URL of the webhook endpoint.\", max_length=2048)\n    application = models.CharField(\n        max_length=255,\n        blank=True,\n        help_text=\"The ID of the associated Connect application.\",\n    )\n\n    djstripe_uuid = models.UUIDField(\n        null=True,\n        unique=True,\n        default=uuid4,\n        help_text=\"A UUID specific to dj-stripe generated for the endpoint\",\n    )\n\n    def __str__(self):\n        return self.url or str(self.djstripe_uuid)\n\n    def _attach_objects_hook(\n        self, cls, data, current_ids=None, api_key=djstripe_settings.STRIPE_SECRET_KEY\n    ):\n        \"\"\"\n        Gets called by this object's create and sync methods just before save.\n        Use this to populate fields before the model is saved.\n        \"\"\"\n        super()._attach_objects_hook(\n            cls, data, current_ids=current_ids, api_key=api_key\n        )\n\n        self.djstripe_uuid = data.get(\"metadata\", {}).get(\"djstripe_uuid\")\n\n\ndef _get_version():\n    from ..apps import __version__\n\n    return __version__\n\n\ndef get_remote_ip(request):\n    \"\"\"Given the HTTPRequest object return the IP Address of the client\n\n    :param request: client request\n    :type request: HTTPRequest\n\n    :Returns: the client ip address\n    \"\"\"\n\n    # x-forwarded-for is relevant for django running behind a proxy\n    x_forwarded_for = request.headers.get(\"x-forwarded-for\")\n    if x_forwarded_for:\n        ip = x_forwarded_for.split(\",\")[0]\n    else:\n        ip = request.META.get(\"REMOTE_ADDR\")\n\n    if not ip:\n        warnings.warn(\n            \"Could not determine remote IP (missing REMOTE_ADDR). \"\n            \"This is likely an issue with your wsgi/server setup.\"\n        )\n        ip = \"0.0.0.0\"\n\n    return ip\n\n\nclass WebhookEventTrigger(models.Model):\n    \"\"\"\n    An instance of a request that reached the server endpoint for Stripe webhooks.\n\n    Webhook Events are initially **UNTRUSTED**, as it is possible for any web entity to\n    post any data to our webhook url. Data posted may be valid Stripe information,\n    garbage, or even malicious.\n    The 'valid' flag in this model monitors this.\n    \"\"\"\n\n    id = models.BigAutoField(primary_key=True)\n    remote_ip = models.GenericIPAddressField(\n        help_text=\"IP address of the request client.\"\n    )\n    headers = JSONField()\n    body = models.TextField(blank=True)\n    valid = models.BooleanField(\n        default=False,\n        help_text=\"Whether or not the webhook event has passed validation\",\n    )\n    processed = models.BooleanField(\n        default=False,\n        help_text=\"Whether or not the webhook event has been successfully processed\",\n    )\n    exception = models.CharField(max_length=128, blank=True)\n    traceback = models.TextField(\n        blank=True, help_text=\"Traceback if an exception was thrown during processing\"\n    )\n    event = StripeForeignKey(\n        \"Event\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        help_text=\"Event object contained in the (valid) Webhook\",\n    )\n    djstripe_version = models.CharField(\n        max_length=32,\n        default=_get_version,  # Needs to be a callable, otherwise it's a db default.\n        help_text=\"The version of dj-stripe when the webhook was received\",\n    )\n    created = models.DateTimeField(auto_now_add=True)\n    updated = models.DateTimeField(auto_now=True)\n    stripe_trigger_account = StripeForeignKey(\n        \"djstripe.Account\",\n        on_delete=models.CASCADE,\n        to_field=\"id\",\n        null=True,\n        blank=True,\n        help_text=\"The Stripe Account this object belongs to.\",\n    )\n    webhook_endpoint = StripeForeignKey(\n        \"WebhookEndpoint\",\n        on_delete=models.SET_NULL,\n        null=True,\n        blank=True,\n        help_text=\"The endpoint this webhook was received on\",\n    )\n\n    def __str__(self):\n        return f\"id={self.id}, valid={self.valid}, processed={self.processed}\"\n\n    @classmethod\n    def from_request(cls, request, *, webhook_endpoint: WebhookEndpoint = None):\n        \"\"\"\n        Create, validate and process a WebhookEventTrigger given a Django\n        request object.\n\n        The process is three-fold:\n        1. Create a WebhookEventTrigger object from a Django request.\n        2. Validate the WebhookEventTrigger as a Stripe event using the API.\n        3. If valid, process it into an Event object (and child resource).\n        \"\"\"\n\n        try:\n            body = request.body.decode(request.encoding or \"utf-8\")\n        except Exception:\n            body = \"(error decoding body)\"\n\n        ip = get_remote_ip(request)\n\n        try:\n            data = json.loads(body)\n        except ValueError:\n            data = {}\n\n        if webhook_endpoint is None:\n            stripe_account = StripeModel._find_owner_account(data=data)\n            secret = djstripe_settings.WEBHOOK_SECRET\n        else:\n            stripe_account = webhook_endpoint.djstripe_owner_account\n            secret = webhook_endpoint.secret\n\n        obj = cls.objects.create(\n            headers=dict(request.headers),\n            body=body,\n            remote_ip=ip,\n            stripe_trigger_account=stripe_account,\n            webhook_endpoint=webhook_endpoint,\n        )\n        api_key = (\n            stripe_account.default_api_key\n            or djstripe_settings.get_default_api_key(obj.livemode)\n        )\n\n        try:\n            # Validate the webhook first\n            signals.webhook_pre_validate.send(sender=cls, instance=obj)\n            obj.valid = obj.validate(secret=secret, api_key=api_key)\n            signals.webhook_post_validate.send(\n                sender=cls, instance=obj, valid=obj.valid\n            )\n\n            if obj.valid:\n                signals.webhook_pre_process.send(sender=cls, instance=obj)\n                if djstripe_settings.WEBHOOK_EVENT_CALLBACK:\n                    # If WEBHOOK_EVENT_CALLBACK, pass it for processing\n                    djstripe_settings.WEBHOOK_EVENT_CALLBACK(obj, api_key=api_key)\n                else:\n                    # Process the item (do not save it, it'll get saved below)\n                    obj.process(save=False, api_key=api_key)\n                signals.webhook_post_process.send(\n                    sender=cls, instance=obj, api_key=api_key\n                )\n        except Exception as e:\n            max_length = cls._meta.get_field(\"exception\").max_length\n            obj.exception = str(e)[:max_length]\n            obj.traceback = format_exc()\n\n            # Send the exception as the webhook_processing_error signal\n            signals.webhook_processing_error.send(\n                sender=cls,\n                instance=obj,\n                api_key=api_key,\n                exception=e,\n                data=getattr(e, \"http_body\", \"\"),\n            )\n\n            # re-raise the exception so Django sees it\n            raise e\n        finally:\n            obj.save()\n\n        return obj\n\n    @cached_property\n    def json_body(self):\n        try:\n            return json.loads(self.body)\n        except ValueError:\n            return {}\n\n    @property\n    def is_test_event(self):\n        event_id = self.json_body.get(\"id\")\n        return event_id and event_id.endswith(\"_00000000000000\")\n\n    def verify_signature(\n        self, secret: str, tolerance: int = djstripe_settings.WEBHOOK_TOLERANCE\n    ) -> bool:\n        if not secret:\n            raise ValueError(\"Cannot verify event signature without a secret\")\n\n        # HTTP headers are case-insensitive, but we store them as a dict.\n        headers = CaseInsensitiveMapping(self.headers)\n        signature = headers.get(\"stripe-signature\")\n        local_cli_signing_secret = headers.get(\"x-djstripe-webhook-secret\")\n        try:\n            # check if the x-djstripe-webhook-secret Custom Header exists\n            if local_cli_signing_secret:\n                # Set Endpoint Signing Secret to the output of Stripe CLI\n                # for signature verification\n                secret = local_cli_signing_secret\n\n            stripe.WebhookSignature.verify_header(\n                self.body, signature, secret, tolerance\n            )\n        except stripe.error.SignatureVerificationError:\n            logger.exception(\"Failed to verify header\")\n            return False\n        else:\n            return True\n\n    def validate(\n        self,\n        api_key: str = None,\n        secret: str = djstripe_settings.WEBHOOK_SECRET,\n        tolerance: int = djstripe_settings.WEBHOOK_TOLERANCE,\n        validation_method=djstripe_settings.WEBHOOK_VALIDATION,\n    ):\n        \"\"\"\n        The original contents of the Event message must be confirmed by\n        refetching it and comparing the fetched data with the original data.\n\n        This function makes an API call to Stripe to redownload the Event data\n        and returns whether or not it matches the WebhookEventTrigger data.\n        \"\"\"\n\n        local_data = self.json_body\n        if \"id\" not in local_data or \"livemode\" not in local_data:\n            logger.error(\n                '\"id\" not in json body or \"livemode\" not in json body(%s)', local_data\n            )\n            return False\n\n        if self.is_test_event:\n            logger.info(\"Test webhook received and discarded: %s\", local_data)\n            return False\n\n        if validation_method is None:\n            # validation disabled\n            warnings.warn(\"WEBHOOK VALIDATION is disabled.\")\n            return True\n        elif validation_method == \"verify_signature\":\n            return self.verify_signature(secret=secret)\n\n        livemode = local_data[\"livemode\"]\n        api_key = api_key or djstripe_settings.get_default_api_key(livemode)\n\n        # Retrieve the event using the api_version specified in itself\n        remote_data = Event.stripe_class.retrieve(\n            id=local_data[\"id\"],\n            api_key=api_key,\n            stripe_version=local_data[\"api_version\"],\n        )\n\n        return local_data[\"data\"] == remote_data[\"data\"]\n\n    def process(self, save=True, api_key: str = None):\n        # Reset traceback and exception in case of reprocessing\n        self.exception = \"\"\n        self.traceback = \"\"\n\n        self.event = Event.process(self.json_body, api_key=api_key)\n        self.processed = True\n        if save:\n            self.save()\n\n        return self.event\n"
  },
  {
    "path": "djstripe/settings.py",
    "content": "\"\"\"\ndj-stripe settings\n\"\"\"\nimport stripe\nfrom django.apps import apps as django_apps\nfrom django.conf import settings\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.utils.module_loading import import_string\n\n\nclass DjstripeSettings:\n    \"\"\"Container for Dj-stripe settings\n\n    :return: Initialised settings for Dj-stripe.\n    :rtype: object\n\n    \"\"\"\n\n    DEFAULT_STRIPE_API_VERSION = \"2020-08-27\"\n\n    ZERO_DECIMAL_CURRENCIES = {\n        \"bif\",\n        \"clp\",\n        \"djf\",\n        \"gnf\",\n        \"jpy\",\n        \"kmf\",\n        \"krw\",\n        \"mga\",\n        \"pyg\",\n        \"rwf\",\n        \"vnd\",\n        \"vuv\",\n        \"xaf\",\n        \"xof\",\n        \"xpf\",\n    }\n\n    def __init__(self):\n        # Set STRIPE_API_HOST if you want to use a different Stripe API server\n        # Example: https://github.com/stripe/stripe-mock\n        if hasattr(settings, \"STRIPE_API_HOST\"):\n            stripe.api_base = getattr(settings, \"STRIPE_API_HOST\")\n\n    # generic setter and deleter methods to ensure object patching works\n    def __setattr__(self, name, value):\n        self.__dict__[name] = value\n\n    def __delattr__(self, name):\n        del self.__dict__[name]\n\n    @property\n    def subscriber_request_callback(self):\n        return self.get_callback_function(\n            \"DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK\",\n            default=(lambda request: request.user),\n        )\n\n    @property\n    def get_idempotency_key(self):\n        return self.get_callback_function(\n            \"DJSTRIPE_IDEMPOTENCY_KEY_CALLBACK\", self._get_idempotency_key\n        )\n\n    @property\n    def DJSTRIPE_WEBHOOK_URL(self):\n        return getattr(settings, \"DJSTRIPE_WEBHOOK_URL\", r\"^webhook/$\")\n\n    @property\n    def WEBHOOK_TOLERANCE(self):\n        return getattr(\n            settings, \"DJSTRIPE_WEBHOOK_TOLERANCE\", stripe.Webhook.DEFAULT_TOLERANCE\n        )\n\n    @property\n    def WEBHOOK_VALIDATION(self):\n        return getattr(settings, \"DJSTRIPE_WEBHOOK_VALIDATION\", \"verify_signature\")\n\n    @property\n    def WEBHOOK_SECRET(self):\n        return getattr(settings, \"DJSTRIPE_WEBHOOK_SECRET\", \"\")\n\n    # Webhook event callbacks allow an application to take control of what happens\n    # when an event from Stripe is received.  One suggestion is to put the event\n    # onto a task queue (such as celery) for asynchronous processing.\n    @property\n    def WEBHOOK_EVENT_CALLBACK(self):\n        return self.get_callback_function(\"DJSTRIPE_WEBHOOK_EVENT_CALLBACK\")\n\n    @property\n    def SUBSCRIBER_CUSTOMER_KEY(self):\n        return getattr(\n            settings, \"DJSTRIPE_SUBSCRIBER_CUSTOMER_KEY\", \"djstripe_subscriber\"\n        )\n\n    @property\n    def TEST_API_KEY(self):\n        return getattr(settings, \"STRIPE_TEST_SECRET_KEY\", \"\")\n\n    @property\n    def LIVE_API_KEY(self):\n        return getattr(settings, \"STRIPE_LIVE_SECRET_KEY\", \"\")\n\n    # Determines whether we are in live mode or test mode\n    @property\n    def STRIPE_LIVE_MODE(self):\n        return getattr(settings, \"STRIPE_LIVE_MODE\", False)\n\n    @property\n    def STRIPE_SECRET_KEY(self):\n        # Default secret key\n        if hasattr(settings, \"STRIPE_SECRET_KEY\"):\n            STRIPE_SECRET_KEY = settings.STRIPE_SECRET_KEY\n        else:\n            STRIPE_SECRET_KEY = (\n                self.LIVE_API_KEY if self.STRIPE_LIVE_MODE else self.TEST_API_KEY\n            )\n        return STRIPE_SECRET_KEY\n\n    @property\n    def STRIPE_PUBLIC_KEY(self):\n        # Default public key\n        if hasattr(settings, \"STRIPE_PUBLIC_KEY\"):\n            STRIPE_PUBLIC_KEY = settings.STRIPE_PUBLIC_KEY\n        elif self.STRIPE_LIVE_MODE:\n            STRIPE_PUBLIC_KEY = getattr(settings, \"STRIPE_LIVE_PUBLIC_KEY\", \"\")\n        else:\n            STRIPE_PUBLIC_KEY = getattr(settings, \"STRIPE_TEST_PUBLIC_KEY\", \"\")\n        return STRIPE_PUBLIC_KEY\n\n    @property\n    def STRIPE_API_VERSION(self) -> str:\n        \"\"\"\n        Get the desired API version to use for Stripe requests.\n        \"\"\"\n        version = getattr(settings, \"STRIPE_API_VERSION\", stripe.api_version)\n        return version or self.DEFAULT_STRIPE_API_VERSION\n\n    def get_callback_function(self, setting_name, default=None):\n        \"\"\"\n        Resolve a callback function based on a setting name.\n\n        If the setting value isn't set, default is returned.  If the setting value\n        is already a callable function, that value is used - If the setting value\n        is a string, an attempt is made to import it.  Anything else will result in\n        a failed import causing ImportError to be raised.\n\n        :param setting_name: The name of the setting to resolve a callback from.\n        :type setting_name: string (``str``/``unicode``)\n        :param default: The default to return if setting isn't populated.\n        :type default: ``bool``\n        :returns: The resolved callback function (if any).\n        :type: ``callable``\n        \"\"\"\n        func = getattr(settings, setting_name, None)\n        if not func:\n            return default\n\n        if callable(func):\n            return func\n\n        if isinstance(func, str):\n            func = import_string(func)\n\n        if not callable(func):\n            raise ImproperlyConfigured(f\"{setting_name} must be callable.\")\n\n        return func\n\n    def _get_idempotency_key(self, object_type, action, livemode) -> str:\n        from .models import IdempotencyKey\n\n        action = f\"{object_type}:{action}\"\n        idempotency_key, _created = IdempotencyKey.objects.get_or_create(\n            action=action, livemode=livemode\n        )\n        return str(idempotency_key.uuid)\n\n    def get_default_api_key(self, livemode) -> str:\n        \"\"\"\n        Returns the default API key for a value of `livemode`.\n        \"\"\"\n        if livemode is None:\n            # Livemode is unknown. Use the default secret key.\n            return self.STRIPE_SECRET_KEY\n        elif livemode:\n            # Livemode is true, use the live secret key\n            return self.LIVE_API_KEY or self.STRIPE_SECRET_KEY\n        else:\n            # Livemode is false, use the test secret key\n            return self.TEST_API_KEY or self.STRIPE_SECRET_KEY\n\n    def get_subscriber_model_string(self) -> str:\n        \"\"\"Get the configured subscriber model as a module path string.\"\"\"\n        return getattr(settings, \"DJSTRIPE_SUBSCRIBER_MODEL\", settings.AUTH_USER_MODEL)  # type: ignore\n\n    def get_subscriber_model(self):\n        \"\"\"\n        Attempt to pull settings.DJSTRIPE_SUBSCRIBER_MODEL.\n\n        Users have the option of specifying a custom subscriber model via the\n        DJSTRIPE_SUBSCRIBER_MODEL setting.\n\n        This methods falls back to AUTH_USER_MODEL if DJSTRIPE_SUBSCRIBER_MODEL is not set.\n\n        Returns the subscriber model that is active in this project.\n        \"\"\"\n        model_name = self.get_subscriber_model_string()\n\n        # Attempt a Django 1.7 app lookup\n        try:\n            subscriber_model = django_apps.get_model(model_name)\n        except ValueError:\n            raise ImproperlyConfigured(\n                \"DJSTRIPE_SUBSCRIBER_MODEL must be of the form 'app_label.model_name'.\"\n            )\n        except LookupError:\n            raise ImproperlyConfigured(\n                f\"DJSTRIPE_SUBSCRIBER_MODEL refers to model '{model_name}' \"\n                \"that has not been installed.\"\n            )\n\n        if (\n            \"email\"\n            not in [field_.name for field_ in subscriber_model._meta.get_fields()]\n        ) and not hasattr(subscriber_model, \"email\"):\n            raise ImproperlyConfigured(\n                \"DJSTRIPE_SUBSCRIBER_MODEL must have an email attribute.\"\n            )\n\n        if model_name != settings.AUTH_USER_MODEL:\n            # Custom user model detected. Make sure the callback is configured.\n            func = self.get_callback_function(\n                \"DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK\"\n            )\n            if not func:\n                raise ImproperlyConfigured(\n                    \"DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK must be implemented \"\n                    \"if a DJSTRIPE_SUBSCRIBER_MODEL is defined.\"\n                )\n\n        return subscriber_model\n\n\n# initialise the settings object\ndjstripe_settings = DjstripeSettings()\n"
  },
  {
    "path": "djstripe/signals.py",
    "content": "\"\"\"\nsignals are sent for each event Stripe sends to the app\n\nStripe docs for Webhooks: https://stripe.com/docs/webhooks\n\"\"\"\nfrom django.dispatch import Signal\n\n# providing_args=[\"instance\", \"api_key\"]\nwebhook_pre_validate = Signal()\n\n# providing_args=[\"instance\", \"api_key\", \"valid\"]\nwebhook_post_validate = Signal()\n\n# providing_args=[\"instance\", \"api_key\"]\nwebhook_pre_process = Signal()\n\n# providing_args=[\"instance\", \"api_key\"]\nwebhook_post_process = Signal()\n\n# providing_args=[\"instance\", \"api_key\", \"exception\", \"data\"]\nwebhook_processing_error = Signal()\n\nENABLED_EVENTS = [\n    # Update this by copy-pasting the \"enabled_events\" enum values from\n    # https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json\n    \"*\",\n    \"account.application.authorized\",\n    \"account.application.deauthorized\",\n    \"account.external_account.created\",\n    \"account.external_account.deleted\",\n    \"account.external_account.updated\",\n    \"account.updated\",\n    \"application_fee.created\",\n    \"application_fee.refund.updated\",\n    \"application_fee.refunded\",\n    \"balance.available\",\n    \"billing_portal.configuration.created\",\n    \"billing_portal.configuration.updated\",\n    \"billing_portal.session.created\",\n    \"capability.updated\",\n    \"cash_balance.funds_available\",\n    \"charge.captured\",\n    \"charge.dispute.closed\",\n    \"charge.dispute.created\",\n    \"charge.dispute.funds_reinstated\",\n    \"charge.dispute.funds_withdrawn\",\n    \"charge.dispute.updated\",\n    \"charge.expired\",\n    \"charge.failed\",\n    \"charge.pending\",\n    \"charge.refund.updated\",\n    \"charge.refunded\",\n    \"charge.succeeded\",\n    \"charge.updated\",\n    \"checkout.session.async_payment_failed\",\n    \"checkout.session.async_payment_succeeded\",\n    \"checkout.session.completed\",\n    \"checkout.session.expired\",\n    \"coupon.created\",\n    \"coupon.deleted\",\n    \"coupon.updated\",\n    \"credit_note.created\",\n    \"credit_note.updated\",\n    \"credit_note.voided\",\n    \"customer.created\",\n    \"customer.deleted\",\n    \"customer.discount.created\",\n    \"customer.discount.deleted\",\n    \"customer.discount.updated\",\n    \"customer.source.created\",\n    \"customer.source.deleted\",\n    \"customer.source.expiring\",\n    \"customer.source.updated\",\n    \"customer.subscription.created\",\n    \"customer.subscription.deleted\",\n    \"customer.subscription.pending_update_applied\",\n    \"customer.subscription.pending_update_expired\",\n    \"customer.subscription.trial_will_end\",\n    \"customer.subscription.updated\",\n    \"customer.tax_id.created\",\n    \"customer.tax_id.deleted\",\n    \"customer.tax_id.updated\",\n    \"customer.updated\",\n    \"file.created\",\n    \"identity.verification_session.canceled\",\n    \"identity.verification_session.created\",\n    \"identity.verification_session.processing\",\n    \"identity.verification_session.redacted\",\n    \"identity.verification_session.requires_input\",\n    \"identity.verification_session.verified\",\n    \"invoice.created\",\n    \"invoice.deleted\",\n    \"invoice.finalization_failed\",\n    \"invoice.finalized\",\n    \"invoice.marked_uncollectible\",\n    \"invoice.paid\",\n    \"invoice.payment_action_required\",\n    \"invoice.payment_failed\",\n    \"invoice.payment_succeeded\",\n    \"invoice.sent\",\n    \"invoice.upcoming\",\n    \"invoice.updated\",\n    \"invoice.voided\",\n    \"invoiceitem.created\",\n    \"invoiceitem.deleted\",\n    \"invoiceitem.updated\",\n    \"issuing_authorization.created\",\n    \"issuing_authorization.request\",\n    \"issuing_authorization.updated\",\n    \"issuing_card.created\",\n    \"issuing_card.updated\",\n    \"issuing_cardholder.created\",\n    \"issuing_cardholder.updated\",\n    \"issuing_dispute.closed\",\n    \"issuing_dispute.created\",\n    \"issuing_dispute.funds_reinstated\",\n    \"issuing_dispute.submitted\",\n    \"issuing_dispute.updated\",\n    \"issuing_transaction.created\",\n    \"issuing_transaction.updated\",\n    \"mandate.updated\",\n    \"order.canceled\",\n    \"order.completed\",\n    \"order.created\",\n    \"order.inventory_reservation_expired\",\n    \"order.payment_completed\",\n    \"order.payment_failed\",\n    \"order.payment_succeeded\",\n    \"order.processing\",\n    \"order.reopened\",\n    \"order.submitted\",\n    \"order.updated\",\n    \"order_return.created\",\n    \"payment_intent.amount_capturable_updated\",\n    \"payment_intent.canceled\",\n    \"payment_intent.created\",\n    \"payment_intent.partially_funded\",\n    \"payment_intent.payment_failed\",\n    \"payment_intent.processing\",\n    \"payment_intent.requires_action\",\n    \"payment_intent.succeeded\",\n    \"payment_link.created\",\n    \"payment_link.updated\",\n    \"payment_method.attached\",\n    \"payment_method.automatically_updated\",\n    \"payment_method.detached\",\n    \"payment_method.updated\",\n    \"payout.canceled\",\n    \"payout.created\",\n    \"payout.failed\",\n    \"payout.paid\",\n    \"payout.updated\",\n    \"person.created\",\n    \"person.deleted\",\n    \"person.updated\",\n    \"plan.created\",\n    \"plan.deleted\",\n    \"plan.updated\",\n    \"price.created\",\n    \"price.deleted\",\n    \"price.updated\",\n    \"product.created\",\n    \"product.deleted\",\n    \"product.updated\",\n    \"promotion_code.created\",\n    \"promotion_code.updated\",\n    \"quote.accepted\",\n    \"quote.canceled\",\n    \"quote.created\",\n    \"quote.finalized\",\n    \"radar.early_fraud_warning.created\",\n    \"radar.early_fraud_warning.updated\",\n    \"recipient.created\",\n    \"recipient.deleted\",\n    \"recipient.updated\",\n    \"reporting.report_run.failed\",\n    \"reporting.report_run.succeeded\",\n    \"reporting.report_type.updated\",\n    \"review.closed\",\n    \"review.opened\",\n    \"setup_intent.canceled\",\n    \"setup_intent.created\",\n    \"setup_intent.requires_action\",\n    \"setup_intent.setup_failed\",\n    \"setup_intent.succeeded\",\n    \"sigma.scheduled_query_run.created\",\n    \"sku.created\",\n    \"sku.deleted\",\n    \"sku.updated\",\n    \"source.canceled\",\n    \"source.chargeable\",\n    \"source.failed\",\n    \"source.mandate_notification\",\n    \"source.refund_attributes_required\",\n    \"source.transaction.created\",\n    \"source.transaction.updated\",\n    \"subscription_schedule.aborted\",\n    \"subscription_schedule.canceled\",\n    \"subscription_schedule.completed\",\n    \"subscription_schedule.created\",\n    \"subscription_schedule.expiring\",\n    \"subscription_schedule.released\",\n    \"subscription_schedule.updated\",\n    \"tax_rate.created\",\n    \"tax_rate.updated\",\n    \"terminal.reader.action_failed\",\n    \"terminal.reader.action_succeeded\",\n    \"test_helpers.test_clock.advancing\",\n    \"test_helpers.test_clock.created\",\n    \"test_helpers.test_clock.deleted\",\n    \"test_helpers.test_clock.internal_failure\",\n    \"test_helpers.test_clock.ready\",\n    \"topup.canceled\",\n    \"topup.created\",\n    \"topup.failed\",\n    \"topup.reversed\",\n    \"topup.succeeded\",\n    \"transfer.created\",\n    \"transfer.failed\",\n    \"transfer.paid\",\n    \"transfer.reversed\",\n    \"transfer.updated\",\n    \"treasury.credit_reversal.created\",\n    \"treasury.credit_reversal.posted\",\n    \"treasury.debit_reversal.completed\",\n    \"treasury.debit_reversal.created\",\n    \"treasury.debit_reversal.initial_credit_granted\",\n    \"treasury.financial_account.closed\",\n    \"treasury.financial_account.created\",\n    \"treasury.financial_account.features_status_updated\",\n    \"treasury.inbound_transfer.canceled\",\n    \"treasury.inbound_transfer.created\",\n    \"treasury.inbound_transfer.failed\",\n    \"treasury.inbound_transfer.succeeded\",\n    \"treasury.outbound_payment.canceled\",\n    \"treasury.outbound_payment.created\",\n    \"treasury.outbound_payment.expected_arrival_date_updated\",\n    \"treasury.outbound_payment.failed\",\n    \"treasury.outbound_payment.posted\",\n    \"treasury.outbound_payment.returned\",\n    \"treasury.outbound_transfer.canceled\",\n    \"treasury.outbound_transfer.created\",\n    \"treasury.outbound_transfer.expected_arrival_date_updated\",\n    \"treasury.outbound_transfer.failed\",\n    \"treasury.outbound_transfer.posted\",\n    \"treasury.outbound_transfer.returned\",\n    \"treasury.received_credit.created\",\n    \"treasury.received_credit.failed\",\n    \"treasury.received_credit.reversed\",\n    \"treasury.received_credit.succeeded\",\n    \"treasury.received_debit.created\",\n    # deprecated (no longer in events_types list) - TODO can be deleted?\n    \"checkout_beta.session_succeeded\",\n    \"issuer_fraud_record.created\",\n    \"payment_intent.requires_capture\",\n    \"payment_method.card_automatically_updated\",\n    \"issuing_dispute.created\",\n    \"issuing_dispute.updated\",\n    \"issuing_settlement.created\",\n    \"issuing_settlement.updated\",\n    # special case? - TODO can be deleted?\n    \"ping\",\n]\n\n\n# A signal for each Event type. See https://stripe.com/docs/api/events/types\n\nWEBHOOK_SIGNALS = dict(\n    [  # providing_args=[\"event\"]\n        (hook, Signal()) for hook in ENABLED_EVENTS if hook != \"*\"\n    ]\n)\n"
  },
  {
    "path": "djstripe/sync.py",
    "content": "\"\"\"\nUtility functions used for syncing data.\n\"\"\"\nfrom stripe.error import InvalidRequestError\n\nfrom .models import Customer\n\n\ndef sync_subscriber(subscriber):\n    \"\"\"Sync a Customer with Stripe api data.\"\"\"\n    customer, _created = Customer.get_or_create(subscriber=subscriber)\n    try:\n        customer.sync_from_stripe_data(customer.api_retrieve())\n        customer._sync_subscriptions()\n        customer._sync_invoices()\n        customer._sync_cards()\n        customer._sync_charges()\n    except InvalidRequestError as e:\n        print(\"ERROR: \" + str(e))\n    return customer\n"
  },
  {
    "path": "djstripe/templates/djstripe/admin/add_form.html",
    "content": "{% extends \"./change_form.html\" %}\n\n\n\n{% block djstripewarning %}\n    <p class=\"stripe-confirmation-warning\">{{opts.verbose_name | capfirst}} created here will <strong>not</strong> be created on your Stripe Account.</p>\n{% endblock djstripewarning %}\n"
  },
  {
    "path": "djstripe/templates/djstripe/admin/change_form.html",
    "content": "{% extends \"admin/change_form.html\" %}\n{% load i18n %}\n\n{% block object-tools-items %}\n    {{ block.super }}\n    {% with original.get_stripe_dashboard_url as stripe_url %}\n    {% if stripe_url %}\n        <li><a href=\"{{ stripe_url }}\" target=\"_blank\" id=\"djstripe-view-on-stripe\"><i class=\"icon-user icon-alpha75\"></i>{% trans \"View on Stripe Dashboard\" %}</a></li>\n    {% endif %}\n    {% endwith %}\n{% endblock %}\n\n\n\n{% block extrastyle %}\n{{ block.super }}\n<style>\n    .stripe-danger-warning {\n        padding: 1rem;\n        background: var(--message-error-bg);\n    }\n\n    .stripe-confirmation-warning {\n        padding: 1rem;\n        background: var(--message-warning-bg);\n    }\n</style>\n{% endblock extrastyle %}\n\n{% block content %}\n    {% block djstripewarning %}\n        <p class=\"stripe-confirmation-warning\">Any Changes made here will <strong>not</strong> be updated on your Stripe Account.</p>\n    {% endblock djstripewarning %}\n    {{ block.super }}\n{% endblock content %}\n"
  },
  {
    "path": "djstripe/templates/djstripe/admin/confirm_action.html",
    "content": "{% extends \"admin/base_site.html\" %}\n{% load i18n static l10n admin_urls %}\n\n\n{% block extrahead %}\n    {{ block.super }}\n    {{ media.js }}\n    {% comment %} Load Jquery shipped with Django {% endcomment %}\n    <script src=\"{% static 'admin/js/vendor/jquery/jquery.min.js' %}\" ></script>\n    <script src=\"{% static 'admin/js/jquery.init.js' %}\" ></script>\n\n    <script src=\"{% static 'admin/js/cancel.js' %}\" async></script>\n{% endblock %}\n\n{% block extrastyle %}\n{{ block.super }}\n<style>\n    .stripe-confirmation-warning {\n        padding: 1rem;\n        background: var(--message-warning-bg);\n    }\n</style>\n{% endblock extrastyle %}\n\n\n{% block bodyclass %}{{ block.super }} delete-confirmation{% endblock %}\n\n\n{% block content %}\n    {{ block.super }}\n\n    {% comment %} Page Loader Icon {% endcomment %}\n    <div id=\"div_spinner\" style=\"display: none;\">\n        <div style=\"min-height: 100vh; display: flex; align-items: center; justify-content: center;\">\n            <div id=\"spinner\" role=\"status\">\n                    <span>Loading...</span>\n            </div>\n        </div>\n    </div>\n\n\n    <div id=\"main_content\">\n\n        <h1>Are you Sure? </h1>\n\n        {% if action_name == \"_resync_instances\" %}\n            <p class=\"stripe-confirmation-warning\">Are you sure you want to sync the selected instances? All of the following objects and their related items will be synced.</p>\n        {% elif action_name == \"_sync_all_instances\" %}\n            <p class=\"stripe-confirmation-warning\">Are you sure you want to sync all instances? Please note this will be a best effort sync and we will silence any errors encountered.</p>\n            {% if info %}<p>All of the following objects and their related items will be synced:</p>{% endif %}\n        {% elif action_name == \"_cancel\" %}\n            <p class=\"stripe-confirmation-warning\">Are you sure you want to cancel the selected subscriptions? The following subscriptions will be cancelled.</p>\n        {% endif %}\n\n        <ul>\n            {% for instance in info %}\n            <li>{{instance}}</li>\n            {% endfor %}\n        </ul>\n\n        <form id=\"form_custom_action\" method=\"post\" onsubmit=\"django.jQuery('#main_content').hide(); django.jQuery('.messagelist').hide(); django.jQuery('#div_spinner').show();\" action=\"{% url 'admin:djstripe_custom_action' action_name=action_name model_name=model_name %}\">\n            {% csrf_token %}\n            {{form}}\n\n            <div>\n                <input type=\"submit\" value=\"{% translate 'Yes, I’m sure' %}\">\n                <a href=\"{{changelist_url}}\" class=\"button cancel-link\" onclick=\"django.jQuery('#main_content').hide(); django.jQuery('#div_spinner').show()\">{% translate \"No, take me back\" %}</a>\n            </div>\n        </form>\n    </div>\n\n\n{% endblock content %}\n"
  },
  {
    "path": "djstripe/templates/djstripe/admin/webhook_endpoint/add_form.html",
    "content": "{% extends \"./change_form.html\" %}\n\n\n{% block djstripewarning %}\n    <p class=\"stripe-danger-warning\">{{opts.verbose_name | capfirst}} created here will also be created on your Stripe Account. Proceed with caution.</p>\n{% endblock djstripewarning %}\n"
  },
  {
    "path": "djstripe/templates/djstripe/admin/webhook_endpoint/change_form.html",
    "content": "{% extends \"../change_form.html\" %}\n\n\n{% block djstripewarning %}\n    <p class=\"stripe-danger-warning\">Any Changes made here will also be updated on your Stripe Account. Proceed with Caution.</p>\n{% endblock djstripewarning %}\n"
  },
  {
    "path": "djstripe/templates/djstripe/admin/webhook_endpoint/delete_confirmation.html",
    "content": "{% extends \"admin/delete_confirmation.html\" %}\n{% block extrahead %}\n    {{ block.super }}\n    <script>\n\n    // Handler for .ready() called.\n    django.jQuery(function() {\n        // show loader whenever a link is clicked\n        django.jQuery('a').\n        on('click', function(e) {\n            django.jQuery('#div_spinner').show();\n        });\n\n        // show the spinner whenever a form is submitted\n        django.jQuery('form').on('submit', function(e) {\n            django.jQuery('#div_spinner').show();\n        });\n\n    });\n\n    </script>\n\n{% endblock %}\n\n\n{% block extrastyle %}\n{{ block.super }}\n<style>\n    .stripe-confirmation-warning {\n        padding: 1rem;\n        background: var(--message-warning-bg);\n    }\n</style>\n{% endblock extrastyle %}\n\n{% block content %}\n\n{% comment %} Page Loader Icon {% endcomment %}\n<div id=\"div_spinner\" style=\"display: none;\">\n    <div style=\"min-height: 100vh; display: flex; align-items: center; justify-content: center;\">\n        <div id=\"spinner\" role=\"status\">\n            <span>Loading...</span>\n        </div>\n    </div>\n</div>\n\n<p class=\"stripe-confirmation-warning\">\n    <strong>Warning:</strong> This {{ object_name }} will also be deleted from Stripe.\n</p>\n\n{{ block.super }}\n{% endblock %}\n"
  },
  {
    "path": "djstripe/urls.py",
    "content": "\"\"\"\nUrls related to the djstripe app.\n\nWire this into the root URLConf this way::\n\n    path(\"stripe/\", include(\"djstripe.urls\", namespace=\"djstripe\")),\n    # url can be changed\n    # Call to 'djstripe.urls' and 'namespace' must stay as is\n\"\"\"\nfrom django.urls import path, re_path\n\nfrom . import views\nfrom .settings import djstripe_settings as app_settings\n\napp_name = \"djstripe\"\n\nurlpatterns = [\n    # Webhook\n    re_path(\n        app_settings.DJSTRIPE_WEBHOOK_URL,\n        views.ProcessWebhookView.as_view(),\n        name=\"webhook\",\n    ),\n    path(\n        \"webhook/<uuid:uuid>/\",\n        views.ProcessWebhookView.as_view(),\n        name=\"djstripe_webhook_by_uuid\",\n    ),\n]\n"
  },
  {
    "path": "djstripe/utils.py",
    "content": "\"\"\"\nUtility functions related to the djstripe app.\n\"\"\"\nimport datetime\nfrom typing import Optional\n\nimport stripe\nfrom django.apps import apps\nfrom django.conf import settings\nfrom django.contrib.humanize.templatetags.humanize import intcomma\nfrom django.db.models.query import QuerySet\nfrom django.utils import timezone\n\n\ndef get_supported_currency_choices(api_key):\n    \"\"\"\n    Pull a stripe account's supported currencies and returns a choices tuple of those\n    supported currencies.\n\n    :param api_key: The api key associated with the account from which to pull data.\n    :type api_key: str\n    \"\"\"\n    account = stripe.Account.retrieve(api_key=api_key)\n    supported_payment_currencies = stripe.CountrySpec.retrieve(\n        account[\"country\"], api_key=api_key\n    )[\"supported_payment_currencies\"]\n\n    return [(currency, currency.upper()) for currency in supported_payment_currencies]\n\n\ndef clear_expired_idempotency_keys():\n    from .models import IdempotencyKey\n\n    threshold = timezone.now() - datetime.timedelta(hours=24)\n    IdempotencyKey.objects.filter(created__lt=threshold).delete()\n\n\ndef convert_tstamp(response) -> Optional[datetime.datetime]:\n    \"\"\"\n    Convert a Stripe API timestamp response (unix epoch) to a native datetime.\n    \"\"\"\n    if response is None:\n        # Allow passing None to convert_tstamp()\n        return response\n\n    # Overrides the set timezone to UTC - I think...\n    tz = get_timezone_utc() if settings.USE_TZ else None\n\n    return datetime.datetime.fromtimestamp(response, tz)\n\n\n# TODO: Finish this.\nCURRENCY_SIGILS = {\"CAD\": \"$\", \"EUR\": \"€\", \"GBP\": \"£\", \"USD\": \"$\"}\n\n\ndef get_friendly_currency_amount(amount, currency: str) -> str:\n    currency = currency.upper()\n    sigil = CURRENCY_SIGILS.get(currency, \"\")\n    amount_two_decimals = f\"{amount:.2f}\"\n    return f\"{sigil}{intcomma(amount_two_decimals)} {currency}\"\n\n\nclass QuerySetMock(QuerySet):\n    \"\"\"\n    A mocked QuerySet class that does not handle updates.\n    Used by UpcomingInvoice.invoiceitems (deprecated) and UpcomingInvoice.lineitems.\n    \"\"\"\n\n    @classmethod\n    def from_iterable(cls, model, iterable):\n        instance = cls(model)\n        instance._result_cache = list(iterable)\n        instance._prefetch_done = True\n        return instance\n\n    def _clone(self):\n        return self.__class__.from_iterable(self.model, self._result_cache)\n\n    def update(self):\n        return 0\n\n    def delete(self):\n        return 0\n\n\ndef get_id_from_stripe_data(data):\n    \"\"\"\n    Extract stripe id from stripe field data\n    \"\"\"\n\n    if isinstance(data, str):\n        # data like \"sub_6lsC8pt7IcFpjA\"\n        return data\n    elif data:\n        # data like {\"id\": sub_6lsC8pt7IcFpjA\", ...}\n        return data.get(\"id\")\n    else:\n        return None\n\n\ndef get_model(model_name):\n    app_label = \"djstripe\"\n    app_config = apps.get_app_config(app_label)\n    model = app_config.get_model(model_name)\n    return model\n\n\ndef get_queryset(pks, model_name):\n    model = get_model(model_name)\n    return model.objects.filter(pk__in=pks)\n\n\ndef get_timezone_utc():\n    \"\"\"\n    Returns UTC attribute in a backwards compatible way.\n\n    UTC attribute has been moved from django.utils.timezone module to\n    datetime.timezone class\n    \"\"\"\n    try:\n        # Django 4+\n        return datetime.timezone.utc\n    except AttributeError:\n        return timezone.utc\n"
  },
  {
    "path": "djstripe/views.py",
    "content": "\"\"\"\ndj-stripe - Views related to the djstripe app.\n\"\"\"\nimport logging\n\nfrom django.http import HttpResponse, HttpResponseBadRequest\nfrom django.shortcuts import get_object_or_404\nfrom django.utils.decorators import method_decorator\nfrom django.views.decorators.csrf import csrf_exempt\nfrom django.views.generic import View\n\nfrom .models import WebhookEndpoint, WebhookEventTrigger\n\nlogger = logging.getLogger(__name__)\n\n\n@method_decorator(csrf_exempt, name=\"dispatch\")\nclass ProcessWebhookView(View):\n    \"\"\"\n    A Stripe Webhook handler view.\n\n    This will create a WebhookEventTrigger instance, verify it,\n    then attempt to process it.\n\n    If the webhook cannot be verified, returns HTTP 400.\n\n    If an exception happens during processing, returns HTTP 500.\n    \"\"\"\n\n    def post(self, request, uuid=None):\n        # https://stripe.com/docs/webhooks/signatures\n        if \"stripe-signature\" not in request.headers:\n            # Do not even attempt to process/store the event if there is\n            # no signature in the headers so we avoid overfilling the db.\n            logger.error(\"HTTP_STRIPE_SIGNATURE is missing\")\n            return HttpResponseBadRequest()\n\n        # uuid is passed for new-style webhook views only.\n        # old-style defaults to no account.\n        if uuid:\n            # If the UUID is invalid (does not exist), this will throw a 404.\n            # Note that this happens after the HTTP_STRIPE_SIGNATURE check on purpose.\n            webhook_endpoint = get_object_or_404(WebhookEndpoint, djstripe_uuid=uuid)\n        else:\n            webhook_endpoint = None\n\n        trigger = WebhookEventTrigger.from_request(\n            request, webhook_endpoint=webhook_endpoint\n        )\n\n        if trigger.is_test_event:\n            # Since we don't do signature verification, we have to skip trigger.valid\n            return HttpResponse(\"Test webhook successfully received and discarded!\")\n\n        if not trigger.valid:\n            # Webhook Event did not validate, return 400\n            logger.error(\"Trigger object did not validate\")\n            return HttpResponseBadRequest()\n\n        return HttpResponse(str(trigger.id))\n"
  },
  {
    "path": "djstripe/webhooks.py",
    "content": "\"\"\"\nUtils related to processing or registering for webhooks\n\nA model registers itself here if it wants to be in the list of processing\nfunctions for a particular webhook. Each processor will have the ability\nto modify the event object, access event data, and do what it needs to do\n\nregistrations are keyed by top-level event type (e.g. \"invoice\", \"customer\", etc)\nEach registration entry is a list of processors\nEach processor in these lists is a function to be called\nThe function signature is:\n    <Event object>\n\nThere is also a \"global registry\" which is just a list of processors (as defined above)\n\nNOTE: global processors are called before other processors.\n\"\"\"\nimport functools\nimport itertools\nfrom collections import defaultdict\n\n__all__ = [\"handler\", \"handler_all\", \"call_handlers\"]\n\n\nregistrations = defaultdict(list)\nregistrations_global = []\n\n# Legacy. In previous versions of Stripe API, all test events used this ID.\n# Check out issue #779 for more information.\nTEST_EVENT_ID = \"evt_00000000000000\"\n\n\ndef handler(*event_types):\n    \"\"\"\n    Decorator that registers a function as a webhook handler.\n\n    Functions can be registered for event types (e.g. 'customer') or\n    fully qualified event sub-types (e.g. 'customer.subscription.deleted').\n\n    If an event type is specified, the handler will receive callbacks for\n    ALL webhook events of that type.  For example, if 'customer' is specified,\n    the handler will receive events for 'customer.subscription.created',\n    'customer.subscription.updated', etc.\n\n    :param event_types: The event type(s) that should be handled.\n    :type event_types: str.\n    \"\"\"\n\n    def decorator(func):\n        for event_type in event_types:\n            registrations[event_type].append(func)\n        return func\n\n    return decorator\n\n\ndef handler_all(func=None):\n    \"\"\"\n    Decorator that registers a function as a webhook handler for ALL webhook events.\n\n    Handles all webhooks regardless of event type or sub-type.\n    \"\"\"\n    if not func:\n        return functools.partial(handler_all)\n\n    registrations_global.append(func)\n\n    return func\n\n\ndef call_handlers(event):\n    \"\"\"\n    Invoke all handlers for the provided event type/sub-type.\n\n    The handlers are invoked in the following order:\n\n    1. Global handlers\n    2. Event type handlers\n    3. Event sub-type handlers\n\n    Handlers within each group are invoked in order of registration.\n\n    :param event: The event model object.\n    :type event: ``djstripe.models.Event``\n    \"\"\"\n    chain = [registrations_global]\n\n    # Build up a list of handlers with each qualified part of the event\n    # category and verb.  For example, \"customer.subscription.created\" creates:\n    #   1. \"customer\"\n    #   2. \"customer.subscription\"\n    #   3. \"customer.subscription.created\"\n    for index, _ in enumerate(event.parts):\n        qualified_event_type = \".\".join(event.parts[: (index + 1)])\n        chain.append(registrations[qualified_event_type])\n\n    for handler_func in itertools.chain(*chain):\n        handler_func(event=event)\n"
  },
  {
    "path": "docs/CONTRIBUTING.md",
    "content": "# Contributing\n\nContributions are welcome, and they are greatly appreciated! Every little bit helps, and\ncredit will always be given.\n\nYou can contribute in many ways:\n\n## Types of Contributions\n\n### Report Bugs\n\nReport bugs at <https://github.com/dj-stripe/dj-stripe/issues>.\n\nIf you are reporting a bug, please include:\n\n-   The version of python and Django you're running\n-   Detailed steps to reproduce the bug.\n\n### Fix Bugs\n\nLook through the GitHub issues for bugs. Anything tagged with \"bug\" is open to whoever\nwants to implement it.\n\n### Implement Features\n\nLook through the GitHub issues for features. Anything tagged with \"feature\" is open to\nwhoever wants to implement it.\n\n### Write Documentation\n\ndj-stripe could always use more documentation, whether as part of the official dj-stripe\ndocs, in docstrings, or even on the web in blog posts, articles, and such.\n\nTo see the project's documentation live, run the following command:\n\n    mkdocs serve\n\nThe documentation site will then be served on <http://127.0.0.1:8000>.\n\n!!! attention \"In case of any installation error\"\n\n    In case you get the error that some plugin is not installed, please run:\n        ``` bash\n        poetry install --with docs\n        ```\n\nIf you wish to just generate the documentation, you can replace `serve` with `build`,\nand the docs will be generated into the `site/` folder.\n\n### Submit Feedback\n\nThe best way to send feedback is to file an issue at\n<https://github.com/dj-stripe/dj-stripe/issues>.\n\nIf you are proposing a feature:\n\n-   Explain in detail how it would work.\n-   Keep the scope as narrow as possible, to make it easier to implement.\n-   Remember that this is a volunteer-driven project, and that contributions are welcome\n    :)\n\n### Contributor Discussion\n\nFor questions regarding contributions to dj-stripe, another avenue is our Discord\nchannel at <https://discord.gg/UJY8fcc>.\n\n## Get Started!\n\nReady to contribute? Here's how to set up local development.\n\n1.  Fork [dj-stripe on Github](https://github.com/dj-stripe/dj-stripe).\n\n1.  Clone your fork locally:\n\n        $ git clone git@github.com:your_name_here/dj-stripe.git\n\n1.  Set up [pre-commit](https://pre-commit.com/):\n\n        $ git init # A git repo is required to install pre-commit\n        $ pre-commit install\n\n1.  Set up your test database. If you're running tests using PostgreSQL:\n\n        $ createdb djstripe\n\n    or if you want to test vs sqlite (for convenience) or MySQL, they can be selected by\n    setting this environment variable:\n\n        $ export DJSTRIPE_TEST_DB_VENDOR=sqlite\n        # or: export DJSTRIPE_TEST_DB_VENDOR=mysql\n\n    For postgres and mysql, the database host,port,username and password can be set with\n    environment variables, see `tests/settings.py`\n\n1.  Install [Poetry](https://python-poetry.org/) if you do not have it already.\n\n    You can set up a virtual environment with:\n\n        $ poetry install\n\n    You can then, at any time, open a shell into that environment with:\n\n        $ poetry shell\n\n1.  When you're done making changes, check that your changes pass the tests. A quick\n    test run can be done as follows:\n\n        $ DJSTRIPE_TEST_DB_VENDOR=sqlite poetry run pytest --reuse-db\n\n    You should also check that the tests pass with other python and Django versions with\n    tox. pytest will output both command line and html coverage statistics and will warn\n    you if your changes caused code coverage to drop.:\n\n        $ pip install tox\n        $ tox\n\n1.  If your changes altered the models you may need to generate Django migrations:\n\n        $ DJSTRIPE_TEST_DB_VENDOR=sqlite poetry run ./manage.py makemigrations\n\n1.  Commit your changes and push your branch to GitHub:\n\n        $ git add .\n        $ git commit -m \"Your detailed description of your changes.\"\n        $ git push\n\n1.  Submit a pull request through the GitHub website.\n\nCongratulations, you're now a dj-stripe contributor! Have some ♥ from us.\n\n## Preferred Django Model Field Types\n\nWhen mapping from Stripe API field types to Django model fields, we try to follow Django\nbest practises where practical.\n\nThe following types should be preferred for fields that map to the Stripe API (which is\nalmost all fields in our models).\n\n### Strings\n\n-   Stripe API string fields have a [default maximum length of 5,000\n    characters](https://github.com/stripe/openapi/issues/26#issuecomment-392957633).\n-   In some cases a maximum length (`maxLength`) is specified in the [Stripe OpenAPI\n    schema](https://github.com/stripe/openapi/tree/master/openapi).\n-   We follow [Django's\n    recommendation](https://docs.djangoproject.com/en/dev/ref/models/fields/#null) and\n    avoid using null on string fields (which means we store `\"\"` for string fields that\n    are `null` in stripe). Note that is enforced in the sync logic in\n    [StripeModel.\\_stripe_object_to_record](https://github.com/dj-stripe/dj-stripe/blob/master/djstripe/models/base.py).\n-   For long string fields (eg above 255 characters) we prefer `TextField` over\n    `Charfield`.\n\nTherefore the default type for string fields that don't have a maxLength specified in\nthe [Stripe OpenAPI schema](https://github.com/stripe/openapi/tree/master/openapi)\nshould usually be:\n\n    str_field = TextField(max_length=5000, default=\", blank=True, help_text=\"...\")\n\n### Enumerations\n\nFields that have a defined set of values can be implemented using `StripeEnumField`.\n\n### Hash (dictionaries)\n\nUse the `JSONField` in `djstripe.fields`.\n\n### Currency amounts\n\nStripe handles all currency amounts as integer cents, we currently have a mixture of\nfields as integer cents and decimal (eg dollar, euro etc) values, but we are aiming to\nstandardise on cents (see <https://github.com/dj-stripe/dj-stripe/issues/955>).\n\nAll new currency amount fields should use `StripeQuantumCurrencyAmountField`.\n\n### Dates and Datetimes\n\nThe Stripe API uses an integer timestamp (seconds since the Unix epoch) for dates and\ndatetimes. We store this as a datetime field, using `StripeDateTimeField`.\n\n## Django Migration Policy\n\nMigrations are considered a breaking change, so it's not usually not acceptable to add a\nmigration to a stable branch, it will be a new `MAJOR.MINOR.0` release.\n\nA workaround to this in the case that the Stripe API data isn't compatible with out\nmodel (eg Stripe is sending `null` to a non-null field) is to implement the\n`_manipulate_stripe_object_hook` classmethod on the model.\n\n### Avoid new migrations with non-schema changes\n\nIf a code change produces a migration that doesn't alter the database schema (eg\nchanging `help_text`) then instead of adding a new migration you can edit the most\nrecent migration that affects the field in question.\n\ne.g.:\n<https://github.com/dj-stripe/dj-stripe/commit/e2762c38918a90f00c42ecf21187a920bd3a2087>\n\n## Pull Request Guidelines\n\nBefore you submit a pull request, check that it meets these guidelines:\n\n1.  The pull request should include tests.\n1.  The pull request must not drop code coverage below the current level.\n1.  If the pull request adds functionality, the docs should be updated. Put your new\n    functionality into a function with a docstring.\n1.  If the pull request makes changes to a model, include Django migrations.\n1.  The pull request should work for Python 3.6+. Check [Github\n    Actions](https://github.com/dj-stripe/dj-stripe/actions) and make sure that the\n    tests pass for all supported Python versions.\n1.  Code formatting: Make sure to install `pre-commit` to automatically run it on `staged files` or run manually with `pre-commit run --all-files` at the dj-stripe root to keep a consistent style.\n"
  },
  {
    "path": "docs/README.md",
    "content": "# dj-stripe - Django + Stripe Made Easy\n\n[![Stripe Verified Partner](https://img.shields.io/static/v1?label=Stripe&message=Verified%20Partner&color=red&style=for-the-badge)](https://stripe.com/docs/libraries#community-libraries)\n<br>\n\n[![CI tests](https://github.com/dj-stripe/dj-stripe/actions/workflows/ci.yml/badge.svg)](https://github.com/dj-stripe/dj-stripe/actions/workflows/ci.yml)\n[![Package Downloads](https://img.shields.io/pypi/dm/dj-stripe)](https://pypi.org/project/dj-stripe/)\n[![Documentation](https://img.shields.io/static/v1?label=Docs&message=READ&color=informational&style=plastic)](https://dj-stripe.github.io/dj-stripe/)\n[![Sponsor dj-stripe](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=red&style=plastic)](https://github.com/sponsors/dj-stripe)\n[![MIT License](https://img.shields.io/static/v1?label=License&message=MIT&color=informational&style=plastic)](https://github.com/sponsors/dj-stripe)\n\nStripe Models for Django.\n\n## Introduction\n\ndj-stripe implements all of the Stripe models, for Django. Set up your\nwebhook endpoint and start receiving model updates. You will then have\na copy of all the Stripe models available in Django models, as soon as\nthey are updated!\n\nThe full documentation is available [on Read the Docs](https://dj-stripe.github.io/dj-stripe/).\n\n## Features\n\n-   Stripe Core\n-   Stripe Billing\n-   Stripe Cards (JS v2) and Sources (JS v3)\n-   Payment Methods and Payment Intents (SCA support)\n-   Support for multiple accounts and API keys\n-   Stripe Connect (partial support)\n-   Tested with Stripe API `2020-08-27` (see [API versions](api_versions.md#dj-stripe_latest_tested_version))\n\n## Requirements\n\n-   Django >=3.2\n-   Python >=3.8\n-   PostgreSQL engine (recommended) >=9.6\n-   MySQL engine: MariaDB >=10.2 or MySQL >=5.7\n-   SQLite: Not recommended in production. Version >=3.26 required.\n\n## Installation\n\nSee [installation](installation.md) instructions.\n\n## Changelog\n\n[See release notes on Read the Docs](history/2_7_0/).\n\n<!-- This link *will* get stale again eventually. There should be an index page for the\n     changelog that can be linked to.\n\n     For example:\n     https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/#section-index-pages -->\n\n## Funding and Support\n\n<a href=\"https://stripe.com\">\n  <img alt=\"Stripe Logo\" src=\"./logos/stripe_blurple.svg\" width=\"250px\" />\n</a>\n\nYou can now become a sponsor to dj-stripe with [GitHub Sponsors](https://github.com/sponsors/dj-stripe).\n\nWe've been bringing dj-stripe to the world for over 7 years and are excited to be able to start\ndedicating some real resources to the project.\n\nYour sponsorship helps us keep a team of maintainers actively working to improve dj-stripe and\nensure it stays up-to-date with the latest Stripe changes. If you use dj-stripe commercially, we would encourage you to invest in its continued\ndevelopment by [signing up for a paid plan](https://github.com/sponsors/dj-stripe).\nCorporate sponsors [receive priority support and development time](project/support.md).\n\nAll contributions through GitHub sponsors flow into our [Open Collective](https://opencollective.com/dj-stripe), which holds our funds and keeps\nan open ledger on how donations are spent.\n\n## Our Gold sponsors\n\n<a href=\"https://stripe.com\">\n  <img alt=\"Stripe Logo\" src=\"./logos/stripe_blurple.svg\" width=\"250px\" />\n</a>\n\n## Similar libraries\n\n-   [dj-paypal](https://github.com/HearthSim/dj-paypal)\n    ([PayPal](https://www.paypal.com/))\n-   [dj-paddle](https://github.com/paddle-python/dj-paddle)\n    ([Paddle](https://paddle.com/))\n"
  },
  {
    "path": "docs/__init__.py",
    "content": ""
  },
  {
    "path": "docs/api_keys.md",
    "content": "# Managing Stripe API keys\n\nStripe API keys are stored in the database, and editable from the Django admin.\n\n!!! attention \"Important Note\"\n    By default, keys are visible by anyone who has access to the\n    dj-stripe administration.\n\n\n## Adding new API keys\n\nYou may add new API keys via the Dj-Stripe \"API key\" administration. The only required\nvalue is the key's \"secret\" value itself. Example:\n\n![Adding an API key from the Django administration](https://user-images.githubusercontent.com/235410/99198962-2a1f2e00-279c-11eb-96cc-96dee0ba03ac.png)\n\nOnce saved, Dj-Stripe will automatically detect whether the key is a public, restricted\nor secret key, and whether it's for live or test mode. If it's a secret key, the\nmatching Account object will automatically be fetched as well and the key will be\nassociated with it, so that it can be used to communicate with the Stripe API when\ndealing with objects belonging to that Account.\n\n## Updating the API keys\n\nWhen expiring or rolling new secret keys, you should create the new API key in Stripe,\nthen add it from the Django administration. Whenever you are ready, you may delete the\nold key. (It is safe to keep it around, as long as it hasn't expired. Keeping expired\nkeys in the database may result in errors during usage).\n\n## FAQ\n\n### Why store them in the database?\n\nAs we work on supporting multiple Stripe accounts per instance, it is vital for\ndj-stripe to have a mechanism to store more than one Stripe API key. It also became\nobvious that we may want proper programmatic access to create and delete keys.\nFurthermore, API keys are a legitimate upstream Stripe object, and it is not unlikely\nthe API may allow access to listing other API keys in the future, in which case we will\nwant to move them to the database anyway.\n\n### Isn't that insecure?\n\nPlease do keep your billing database encrypted. There's a copy of all your customers'\nbilling data on it!\n\nYou may also instead create a read-only restricted key with all-read permissions for\ndj-stripe. There is no added risk there, given that dj-stripe holds a copy of all your\ndata regardless.\n\n### I'm using environment variables. Do I need to change anything?\n\nNot at this time. The settings `STRIPE_LIVE_SECRET_KEY` and `STRIPE_TEST_SECRET_KEY` can\nstill be used. Their values will however be automatically saved to the database at the\nearliest opportunity.\n\n### What about public keys?\n\nSetting `STRIPE_LIVE_PUBLIC_KEY` and `STRIPE_TEST_PUBLIC_KEY` will be deprecated in\n2.5.0. You do not risk anything by leaving them in your settings: They are not used by\nDj-Stripe outside of the Dj-Stripe mixins, which are now themselves deprecated. So you\ncan safely leave them in your settings, or you can move them to the database as well\n(Keys beginning in `pk_test_` and `pk_live_` will be detected as publishable keys).\n"
  },
  {
    "path": "docs/api_versions.md",
    "content": "# A note on Stripe API versions\n\nA point that can cause confusion to new users of dj-stripe is that there\nare several different Stripe API versions in play at once.\n\n## Your Stripe account's API version\n\nThis is the version used by Stripe when sending webhook data to you and the default version used by the Stripe API.\nYou can find this on [your Stripe dashboard](https://dashboard.stripe.com/developers) labelled \"**default**\".\nNew Stripe accounts are always on the latest version.\n\nRead more about it on [stripe.com/docs/api/versioning](https://stripe.com/docs/api/versioning).\n\n\n## Stripe's current latest API version\n\nYou can find this on your Stripe dashboard labelled \"**latest**\" or in\n[Stripe's API documentation](https://stripe.com/docs/upgrades#api-changelog)\n\nSee [stripe.com/docs/upgrades](https://stripe.com/docs/upgrades#how-can-i-upgrade-my-api) on how to upgrade your Stripe API version.\nStripe will only allow upgrades to the **latest** version.\n\n## Dj-stripe API version\n\nThis is the Stripe API version used by dj-stripe in all communication\nwith Stripe, including when processing webhooks (though webhook data is\nsent to you by Stripe with your API version, we re-fetch the data with\ndj-stripe's API version), this is because the API schema needs to match\ndj-stripe's Django model schema.\n\nIt is defined by [`STRIPE_API_VERSION`](reference/settings.md#stripe_api_version-2020-08-27) with a default of\n[`DEFAULT_STRIPE_API_VERSION`][djstripe.settings.DjstripeSettings.DEFAULT_STRIPE_API_VERSION].\nYou mustn't change this as it ensures that\ndj-stripe receives data in the format it expects.\n\n!!! note\n    dj-stripe will always use `STRIPE_API_VERSION` in its requests\n    regardless of what `stripe.api_version` is set to.\n\n## Dj-stripe Latest Tested Version\n\nThis is the most recent Stripe account API version used by the\nmaintainers during testing, more recent versions account versions are\nprobably fine though.\n"
  },
  {
    "path": "docs/history/0_x.md",
    "content": "# dj-stripe 0.x release notes\n\n## 0.8.0 (2015-12-30)\n\n-   better plan ordering documentation (Thanks @cjrh)\n-   added a confirmation page when choosing a subscription (Thanks\n    @chrissmejia, @areski)\n-   setup.py reverse dependency fix (\\#258/\\#268) (Thanks @ticosax)\n-   Dropped official support for Django 1.7 (no code changes were made)\n-   Python 3.5 support, Django 1.9.1 support\n-   Migration improvements (Thanks @michi88)\n-   Fixed \"Invoice matching query does not exist\" bug (\\#263)\n    (Thanks @mthornhill)\n-   Fixed duplicate content in account view (Thanks @areski)\n\n## 0.7.0 (2015-09-22)\n\n-   dj-stripe now responds to the invoice.created event\n    (Thanks @wahuneke)\n-   dj-stripe now cancels subscriptions and purges customers during sync\n    if they were deleted from the stripe dashboard (Thanks @unformatt)\n-   dj-stripe now checks for an active stripe subscription in the\n    `update_plan_quantity` call (Thanks @ctrengove)\n-   Event processing is now handled by \"event handlers\" - functions\n    outside of models that respond to various event types and subtypes.\n    Documentation on how to tie into the event handler system coming\n    soon. (Thanks @wahuneke)\n-   Experimental Python 3.5 support\n-   Support for Django 1.6 and lower is now officially gone.\n-   Much, much more!\n\n## 0.6.0 (2015-07-12)\n\n-   Support for Django 1.6 and lower is now deprecated.\n-   Improved test harness now tests coverage and pep8\n-   SubscribeFormView and ChangePlanView no longer populate self.error\n    with form errors\n-   InvoiceItems.plan can now be null (as it is with individual\n    charges), resolving \\#140 (Thanks @awechsler and @MichelleGlauser\n    for help troubleshooting)\n-   Email templates are now packaged during distribution.\n-   sync_plans now takes an optional api_key\n-   100% test coverage\n-   Stripe ID is now returned as part of each model's str method\n    (Thanks @areski)\n-   Customer model now stores card expiration month and year\n    (Thanks @jpadilla)\n-   Ability to extend subscriptions (Thanks @TigerDX)\n-   Support for plan heirarchies (Thanks @chrissmejia)\n-   Rest API endpoints for Subscriptions \\[contrib\\]\n    (Thanks @philippeluickx)\n-   Admin interface search by email funtionality is removed (\\#221)\n    (Thanks @jpadilla)\n\n## 0.5.0 (2015-05-25)\n\n-   Began deprecation of support for Django 1.6 and lower.\n-   Added formal support for Django 1.8.\n-   Removed the StripeSubscriptionSignupForm\n-   Removed `djstripe.safe_settings`. Settings are now all located in\n    `djstripe.settings`\n-   `DJSTRIPE_TRIAL_PERIOD_FOR_SUBSCRIBER_CALLBACK` can no longer be a\n    module string\n-   The sync_subscriber argument has been renamed from\n    subscriber_model to subscriber\n-   Moved available currencies to the DJSTRIPE_CURRENCIES setting\n    (Thanks @martinhill)\n-   Allow passing of extra parameters to stripe Charge API\n    (Thanks @mthornhill)\n-   Support for all available arguments when syncing plans\n    (Thanks @jamesbrobb)\n-   charge.refund() now returns the refunded charge object\n    (Thanks @mthornhill)\n-   Charge model now has captured field and a capture method\n    (Thanks @mthornhill)\n-   Subscription deleted webhook bugfix\n-   South migrations are now up to date (Thanks @Tyrdall)\n\n## 0.4.0 (2015-04-05)\n\n-   Formal Python 3.3+/Django 1.7 Support (including migrations)\n-   Removed Python 2.6 from Travis CI build. (Thanks @audreyr)\n-   Dropped Django 1.4 support. (Thanks @audreyr)\n-   Deprecated the `djstripe.forms.StripeSubscriptionSignupForm`. Making\n    this form work easily with both `dj-stripe` and `django-allauth`\n    required too much abstraction. It will be removed in the 0.5.0\n    release.\n-   Add the ability to add invoice items for a customer (Thanks @kavdev)\n-   Add the ability to use a custom customer model (Thanks @kavdev)\n-   Added setting to disable Invoice receipt emails (Thanks Chris\n    Halpert)\n-   Enable proration when customer upgrades plan, and pass proration\n    policy and cancellation at period end for upgrades in settings.\n    (Thanks Yasmine Charif)\n-   Removed the redundant context processor. (Thanks @kavdev)\n-   Fixed create a token call in change_card.html (Thanks @dollydagr)\n-   Fix `charge.dispute.closed` typo. (Thanks @ipmb)\n-   Fix contributing docs formatting. (Thanks @audreyr)\n-   Fix subscription canceled_at_period_end field sync on plan\n    upgrade (Thanks @nigma)\n-   Remove \"account\" bug in Middleware (Thanks @sromero84)\n-   Fix correct plan selection on subscription in subscribe_form\n    template. (Thanks Yasmine Charif)\n-   Fix subscription status in account, \\_subscription_status, and\n    cancel_subscription templates. (Thanks Yasmine Charif)\n-   Now using `user.get_username()` instead of `user.username`, to\n    support custom User models. (Thanks @shvechikov)\n-   Update remaining DOM Ids for Bootstrap 3. (Thanks Yasmine Charif)\n-   Update publish command in setup.py. (Thanks @pydanny)\n-   Explicitly specify tox's virtual environment names.\n    (Thanks @audreyr)\n-   Manually call django.setup() to populate apps registry.\n    (Thanks @audreyr)\n\n## 0.3.5 (2014-05-01)\n\n-   Fixed `djstripe_init_customers` management command so it works with\n    custom user models.\n\n## 0.3.4 (2014-05-01)\n\n-   Clarify documentation for redirects on app_name.\n-   If settings.DEBUG is True, then django-debug-toolbar is exempt from\n    redirect to subscription form.\n-   Use collections.OrderedDict to ensure that plans are listed in order\n    of price.\n-   Add `ordereddict` library to support Python 2.6 users.\n-   Switch from `__unicode__` to `__str__` methods on models to better\n    support Python 3.\n-   Add `python_2_unicode_compatible` decorator to Models.\n-   Check for PY3 so the `unicode(self.user)` in models.Customer doesn't\n    blow up in Python 3.\n\n## 0.3.3 (2014-04-24)\n\n-   Increased the extendability of the views by removing as many\n    hard-coded URLs as possible and replacing them with `success_url`\n    and other attributes/methods.\n-   Added single unit purchasing to the cookbook\n\n## 0.3.2 (2014-01-16)\n\n-   Made Yasmine Charif a core committer\n-   Take into account trial days in a subscription plan (Thanks Yasmine\n    Charif)\n-   Correct invoice period end value (Thanks Yasmine Charif)\n-   Make plan cancellation and plan change consistently not prorating\n    (Thanks Yasmine Charif)\n-   Fix circular import when ACCOUNT_SIGNUP_FORM_CLASS is defined\n    (Thanks Dustin Farris)\n-   Add send e-mail receipt action in charges admin panel (Thanks Buddy\n    Lindsay)\n-   Add `created` field to all ModelAdmins to help with internal\n    auditing (Thanks Kulbir Singh)\n\n## 0.3.1 (2013-11-14)\n\n-   Cancellation fix (Thanks Yasmine Charif)\n-   Add setup.cfg for wheel generation (Thanks Charlie Denton)\n\n## 0.3.0 (2013-11-12)\n\n-   Fully tested against Django 1.6, 1.5, and 1.4\n-   Fix boolean default issue in models (from now on they are all\n    default to `False`).\n-   Replace duplicated code with\n    `djstripe.utils.user_has_active_subscription`.\n\n## 0.2.9 (2013-09-06)\n\n-   Cancellation added to views.\n-   Support for kwargs on charge and invoice fetching.\n-   def charge() now supports send_receipt flag, default to True.\n-   Fixed templates to work with Bootstrap 3.0.0 column design.\n\n## 0.2.8 (2013-09-02)\n\n-   Improved usage documentation.\n-   Corrected order of fields in StripeSubscriptionSignupForm.\n-   Corrected transaction history template layout.\n-   Updated models to take into account when settings.USE_TZ is\n    disabled.\n\n## 0.2.7 (2013-08-24)\n\n-   Add handy rest_framework permission class.\n-   Fixing attribution for django-stripe-payments.\n-   Add new status to Invoice model.\n\n## 0.2.6 (2013-08-20)\n\n-   Changed name of division tag to djdiv.\n-   Added `safe_setting.py` module to handle edge cases when working\n    with custom user models.\n-   Added cookbook page in the documentation.\n\n## 0.2.5 (2013-08-18)\n\n-   Fixed bug in initial checkout\n-   You can't purchase the same plan that you currently have.\n\n## 0.2.4 (2013-08-18)\n\n-   Recursive package finding.\n\n## 0.2.3 (2013-08-16)\n\n-   Fix packaging so all submodules are loaded\n\n## 0.2.2 (2013-08-15)\n\n-   Added Registration + Subscription form\n\n## 0.2.1 (2013-08-12)\n\n-   Fixed a bug on CurrentSubscription tests\n-   Improved usage documentation\n-   Added to migration from other tools documentation\n\n## 0.2.0 (2013-08-12)\n\n-   Cancellation of plans now works.\n-   Upgrades and downgrades of plans now work.\n-   Changing of cards now works.\n-   Added breadcrumbs to improve navigation.\n-   Improved installation instructions.\n-   Consolidation of test instructions.\n-   Minor improvement to django-stripe-payments documentation\n-   Added coverage.py to test process.\n-   Added south migrations.\n-   Fixed the subscription_payment_required function-based view\n    decorator.\n-   Removed unnecessary django-crispy-forms\n\n## 0.1.7 (2013-08-08)\n\n-   Middleware excepts all of the djstripe namespaced URLs. This way\n    people can pay.\n\n## 0.1.6 (2013-08-08)\n\n-   Fixed a couple template paths\n-   Fixed the manifest so we include html, images.\n\n## 0.1.5 (2013-08-08)\n\n-   Fixed the manifest so we include html, css, js, images.\n\n## 0.1.4 (2013-08-08)\n\n-   Change PaymentRequiredMixin to SubscriptionPaymentRequiredMixin\n-   Add subscription_payment_required function-based view decorator\n-   Added SubscriptionPaymentRedirectMiddleware\n-   Much nicer accounts view display\n-   Much improved subscription form display\n-   Payment plans can have decimals\n-   Payment plans can have custom images\n\n## 0.1.3 (2013-08-7)\n\n-   Added account view\n-   Added Customer.get_or_create method\n-   Added djstripe_sync_customers management command\n-   sync file for all code that keeps things in sync with stripe\n-   Use client-side JavaScript to get history data asynchronously\n-   More user friendly action views\n\n## 0.1.2 (2013-08-6)\n\n-   Admin working\n-   Better publish statement\n-   Fix dependencies\n\n## 0.1.1 (2013-08-6)\n\n-   Ported internals from django-stripe-payments\n-   Began writing the views\n-   Travis-CI\n-   All tests passing on Python 2.7 and 3.3\n-   All tests passing on Django 1.4 and 1.5\n-   Began model cleanup\n-   Better form\n-   Provide better response from management commands\n\n## 0.1.0 (2013-08-5)\n\n-   First release on PyPI.\n"
  },
  {
    "path": "docs/history/1_x.md",
    "content": "# dj-stripe 1.x release notes\n\n## 1.2.4 (2019-02-27)\n\nThis is a bugfix-only version:\n\n-   Allow billing_cycle_anchor argument when creating a subscription\n    (\\#814)\n-   Fixup plan amount null with tier plans (\\#781)\n-   Update Cancel subscription view tests to match backport in f64af57\n-   Implement Invoice.\\_manipulate_stripe_object_hook for\n    compatability with API 2018-11-08 (\\#771)\n-   Fix product webhook for type=\"good\" (\\#724)\n-   Add trial_from_plan, trial_period_days args to\n    Customer.subscribe() (\\#709)\n\n## 1.2.3 (2018-10-13)\n\nThis is a bugfix-only version:\n\n-   Updated Subscription.cancel() for compatibility with Stripe\n    2018-08-23 (\\#723)\n\n## 1.2.2 (2018-08-11)\n\nThis is a bugfix-only version:\n\n-   Fixed an error with request.urlconf in some setups (\\#562)\n-   Always save text-type fields as empty strings in db instead of null\n    (\\#713)\n-   Fix support for DJSTRIPE_SUBSCRIBER_MODEL_MIGRATION_DEPENDENCY\n    (\\#707)\n-   Fix reactivate() with Stripe API 2018-02-28 and above\n\n## 1.2.1 (2018-07-18)\n\nThis is a bugfix-only version:\n\n-   Fixed various Python 2.7 compatibility issues\n-   Fixed issues with max_length of receipt_number\n-   Fixed various fields incorrectly marked as required\n-   Handle product webhook calls\n-   Fix compatibility with stripe-python 2.0.0\n\n## 1.2.0 (2018-06-11)\n\nThe dj-stripe 1.2.0 release resets all migrations.\n\n**Do not upgrade to 1.2.0 directly from 1.0.1 or below. You must upgrade\nto 1.1.0 first.**\n\nPlease read the 1.1.0 release notes below for more information.\n\n## 1.1.0 (2018-06-11)\n\nIn dj-stripe 1.1.0, we made a _lot_ of changes to models in order to\nbring the dj-stripe model state much closer to the upstream API objects.\nIf you are a current user of dj-stripe, you will most likely have to\nmake changes in order to upgrade. Please read the full changelog below.\nIf you are having trouble upgrading, you may ask for help [by filing an\nissue on GitHub](https://github.com/dj-stripe/dj-stripe/issues).\n\n### Migration reset\n\nThe next version of dj-stripe, **1.2.0**, will reset all the migrations\nto `0001_initial`. Migrations are currently in an unmaintainable state.\n\n**What this means is you will not be able to upgrade directly to\ndj-stripe 1.2.0. You must go through 1.1.0 first, run \\`\\`manage.py\nmigrate djstripe\\`\\`, then upgrade to 1.2.0.**\n\n### Python 2.7 end-of-life\n\ndj-stripe 1.1.0 drops support for Django 1.10 and adds support for\nDjango 2.0. Django 1.11+ and Python 2.7+ or 3.4+ are required.\n\nSupport for Python versions older than 3.5, and Django versions older\nthan 2.0, will be dropped in dj-stripe 2.0.0.\n\n### Backwards-incompatible changes and deprecations\n\n#### Removal of polymorphic models\n\nThe model architecture of dj-stripe has been simplified. Polymorphic\nmodels have been dropped and the old base StripeCustomer, StripeCharge,\nStripeInvoice, etc models have all been merged into the top-level\nCustomer, Charge, Invoice, etc models.\n\nImporting those legacy models from `djstripe.stripe_objects` will yield\nthe new ones. This is deprecated and support for this will be dropped in\ndj-stripe 2.0.0.\n\n#### Full support for Stripe Sources (Support for v3 stripe.js)\n\nStripe sources (`src_XXXX`) are objects that can arbitrarily reference\nany of the payment method types that Stripe supports. However, the\nlegacy `Card` object (with object IDs like `card_XXXX` or `cc_XXXX`) is\nnot a Source object, and cannot be turned into a Source object at this\ntime.\n\nIn order to support both Card and Source objects in ForeignKeys, a new\nmodel `PaymentMethod` has been devised (renamed to\n`DjstripePaymentMethod` in 2.0). That model can resolve into a Card, a\nSource, or a BankAccount object.\n\n-   **The \\`\\`default_source\\`\\` attribute on \\`\\`Customer\\`\\` now\n    refers to a \\`\\`PaymentMethod\\`\\` object**. You will need to call\n    `.resolve()` on it to get the Card or Source in question.\n-   References to `Customer.sources` expecting a queryset of Card\n    objects should be updated to `Customer.legacy_cards`.\n-   The legacy `StripeSource` name refers to the `Card` model. This will\n    be removed in dj-stripe 2.0.0. Update your references to either\n    `Card` or `Source`.\n-   `enums.SourceType` has been renamed to `enums.LegacySourceType`.\n    `enums.SourceType` now refers to the actual Stripe Source types\n    enum.\n\n#### Core fields renamed\n\n-   The numeric `id` field has been renamed to `djstripe_id`. This\n    avoids a clash with the upstream stripe id. Accessing `.id` is\n    deprecated and \\*\\*will reference the upstream `stripe_id` in\n    dj-stripe 2.0.0\n\n## 1.0.0 (2017-08-12)\n\nIt's finally here! We've made significant changes to the codebase and\nare now compliant with stripe API version **2017-06-05**.\n\nI want to give a huge thanks to all of our contributors for their help\nin making this happen, especially Bill Huneke (@wahuneke) for his\nimpressive design work and @jleclanche for really pushing this release\nalong.\n\nI also want to welcome onboard two more maintainers, @jleclanche and\n@lskillen. They've stepped up and have graciously dedicated their\nresources to making dj-stripe such an amazing package.\n\nAlmost all methods now mimic the parameters of those same methods in the\nstripe API. Note that some methods do not have some parameters\nimplemented. This is intentional. That being said, expect all method\nsignatures to be different than those in previous versions of dj-stripe.\n\nFinally, please note that there is still a bit of work ahead of us. Not\neverything in the Stripe API is currently supported by dj-stripe --\nwe're working on it. That said, v1.0.0 has been thoroughly tested and is\nverified stable in production applications.\n\n### A few things to get excited for\n\n-   Multiple subscription support (finally)\n-   Multiple sources support (currently limited to Cards)\n-   Idempotency support (See \\#455, \\#460 for discussion -- big thanks\n    to @jleclanche)\n-   Full model documentation\n-   Objects that come through webhooks are now tied to the API version\n    set in dj-stripe. No more errors if dj-stripe falls behind the\n    newest stripe API version.\n-   Any create/update action on an object automatically syncs the\n    object.\n-   Concurrent LIVE and TEST mode support (Thanks to @jleclanche). Note\n    that you'll run into issues if `livemode` isn't set on your existing\n    customer objects.\n-   All choices are now enum-based (Thanks @jleclanche, See \\#520).\n    Access them from the new `djstripe.enums` module. The ability to\n    check against model property based choices will be deprecated in 1.1\n-   Support for the Coupon model, and coupons on Customer objects.\n-   Support for the [Payout/Transfer\n    split](https://stripe.com/docs/transfer-payout-split) from api\n    version `2017-04-06`.\n\n### What still needs to be done (in v1.1.0)\n\n-   **Documentation**. Our original documentation was not very helpful,\n    but it covered the important bits. It will be very out of date after\n    this update and will need to be rewritten. If you feel like helping,\n    we could use all the help we can get to get this pushed out asap.\n\n-   **Master sync re-write**. This sounds scary, but really isn't. The\n    current management methods run sync methods on Customer that aren't\n    very helpful and are due for removal. My plan is to write something\n    that first updates local data (via `api_retrieve` and\n    `sync_from_stripe_data`) and then pulls all objects from Stripe and\n    populates the local database with any records that don't already\n    exist there.\n\n    You might be wondering, \"Why are they releasing this if there are\n    only a few things left?\" Well, that thinking turned this into a two\n    year release... Trust me, this is a good thing.\n\n### Significant changes (mostly backwards-incompatible)\n\n-   **Idempotency**. \\#460 introduces idempotency keys and implements\n    idempotency for `Customer.get_or_create()`. Idempotency will be\n    enabled for all calls that need it.\n-   **Improved Admin Interface**. This is almost complete. See \\#451 and\n    \\#452.\n-   **Drop non-trivial endpoint views**. We're dropping everything\n    except the webhook endpoint and the subscription cancel endpoint.\n    See \\#428.\n-   **Drop support for sending receipts**. Stripe now handles this for\n    you. See \\#478.\n-   **Drop support for plans as settings**, including custom plan\n    hierarchy (if you want this, write something custom) and the dynamic\n    trial callback. We've decided to gut having plans as settings.\n    Stripe should be your source of truth; create your plans there and\n    sync them down manually. If you need to create plans locally for\n    testing, etc., simply use the ORM to create Plan models. The sync\n    rewrite will make this drop less annoying.\n-   **Orphan Customer Sync**. We will now sync Customer objects from\n    Stripe even if they aren't linked to local subscriber objects. You\n    can link up subscribers to those Customers manually.\n-   **Concurrent Live and Test Mode**. dj-stripe now supports test-mode\n    and live-mode Customer objects concurrently. As a result, the\n    User.customer One-to-One reverse-relationship is now the\n    User.djstripe_customers RelatedManager. (Thanks @jleclanche) \\#440.\n    You'll run into some dj-stripe check issues if you don't update your\n    KEY settings accordingly. Check our GitHub issue tracker for help on\n    this.\n\n### SETTINGS\n\n-   The `PLAN_CHOICES`, `PLAN_LIST`, and `PAYMENT_PLANS` objects are\n    removed. Use Plan.objects.all() instead.\n-   The `plan_from_stripe_id` function is removed. Use\n    Plan.objects.get(stripe_id=)\n\n### SYNCING\n\n-   sync_plans no longer takes an api_key\n-   sync methods no longer take a `cu` parameter\n-   All sync methods are now private. We're in the process of building a\n    better syncing mechanism.\n\n### UTILITIES\n\n-   dj-stripe decorators now take a plan argument. If you're passing in\n    a custom test function to `subscriber_passes_pay_test`, be sure to\n    account for this new argument.\n\n### MIXINS\n\n-   The context provided by dj-stripe's mixins has changed.\n    `PaymentsContextMixin` now provides `STRIPE_PUBLIC_KEY` and `plans`\n    (changed to `Plan.objects.all()`). `SubscriptionMixin` now provides\n    `customer` and `is_plans_plural`.\n-   We've removed the SubscriptionPaymentRequiredMixin. Use\n    `@method_decorator(\"dispatch\",`[subscription_payment_required](https://github.com/kavdev/dj-stripe/blob/1.0.0/djstripe/decorators.py#L39)`)`\n    instead.\n\n### MIDDLEWARE\n\n-   dj-stripe middleware doesn't support multiple subscriptions.\n\n### SIGNALS\n\n-   Local custom signals are deprecated in favor of Stripe webhooks:\n-   `cancelled` -&gt;\n    WEBHOOK_SIGNALS\\[\"customer.subscription.deleted\"\\]\n-   `card_changed` -&gt; WEBHOOK_SIGNALS\\[\"customer.source.updated\"\\]\n-   `subscription_made` -&gt;\n    WEBHOOK_SIGNALS\\[\"customer.subscription.created\"\\]\n\n### WEBHOOK EVENTS\n\n-   The Event Handlers designed by @wahuneke are the new way to handle\n    events that come through webhooks. Definitely take a look at\n    `event_handlers.py` and `webhooks.py`.\n\n### EXCEPTIONS\n\n-   `SubscriptionUpdateFailure` and `SubscriptionCancellationFailure`\n    exceptions are removed. There should no longer be a case where they\n    would have been useful. Catch native stripe errors in their place\n    instead.\n\n### MODELS\n\n> **CHARGE**\n\n-   `Charge.charge_created` -&gt; `Charge.stripe_timestamp`\n\n-   `Charge.card_last_4` and `Charge.card_kind` are removed. Use\n    `Charge.source.last4` and `Charge.source.brand` (if the source is a\n    Card)\n\n-   `Charge.invoice` is no longer a foreign key to the Invoice model.\n    `Invoice` now has a OneToOne relationship with `Charge`.\n    (`Charge.invoice` will still work, but will no longer be represented\n    in the database).\n\n    **CUSTOMER**\n\n-   dj-stripe now supports test mode and live mode Customer objects\n    concurrently (See \\#440). As a result, the\n    `<subscriber_model>.customer` OneToOne reverse relationship is no\n    longer a thing. You should now instead add a `customer` property to\n    your subscriber model that checks whether you're in live or test\n    mode (see djstripe.settings.STRIPE_LIVE_MODE as an example) and\n    grabs the customer from `<subscriber_model>.djstripe_customers` with\n    a simple `livemode=` filter.\n\n-   Customer no longer has a `current_subscription` property. We've\n    added a `subscription` property that should suit your needs.\n\n-   With the advent of multiple subscriptions, the behavior of\n    `Customer.subscribe()` has changed. Before, `calling subscribe()`\n    when a customer was already subscribed to a plan would switch the\n    customer to the new plan with an option to prorate. Now calling\n    `subscribe()` simply subscribes that customer to a new plan in\n    addition to it's current subsription. Use `Subscription.update()` to\n    change a subscription's plan instead.\n\n-   `Customer.cancel_subscription()` is removed. Use\n    `Subscription.cancel()` instead.\n\n-   The `Customer.update_plan_quantity()` method is removed. Use\n    `Subscription.update()` instead.\n\n-   `CustomerManager` is now `SubscriptionManager` and works on the\n    `Subscription` model instead of the `Customer` model.\n\n-   `Customer.has_valid_card()` is now `Customer.has_valid_source()`.\n\n-   `Customer.update_card()` now takes an id. If the id is not supplied,\n    the default source is updated.\n\n-   `Customer.stripe_customer` property is removed. Use\n    `Customer.api_retrieve()` instead.\n\n-   The `at_period_end` parameter of `Customer.cancel_subscription()`\n    now actually follows the\n    [DJSTRIPE_PRORATION_POLICY](../reference/settings.md#djstripe_proration_policy-false)\n    setting.\n\n-   `Customer.card_fingerprint`, `Customer.card_last_4`,\n    `Customer.card_kind`, `Customer.card_exp_month`,\n    `Customer.card_exp_year` are all removed. Check\n    `Customer.default_source` (if it's a Card) or one of the sources in\n    `Customer.sources` (again, if it's a Card) instead.\n\n-   The `invoice_id` parameter of `Customer.add_invoice_item` is now\n    named `invoice` and can be either an Invoice object or the\n    stripe_id of an Invoice.\n\n    **EVENT**\n\n-   `Event.kind` -&gt; `Event.type`\n\n-   Removed `Event.validated_message`. Just check if the event is valid\n\n    -   no need to double check (we do that for you)\n\n    **TRANSFER**\n\n-   Removed `Transfer.update_status()`\n\n-   Removed `Transfer.event`\n\n-   `TransferChargeFee` is removed. It hasn't been used in a while due\n    to a broken API version. Use `Transfer.fee_details` instead.\n\n-   Any fields that were in `Transfer.summary` no longer exist and are\n    therefore deprecated (unused but not removed from the database).\n    Because of this, `TransferManager` now only aggregates `total_sum`\n\n    **INVOICE**\n\n-   `Invoice.attempts` -&gt; `Invoice.attempt_count`\n\n-   InvoiceItems are no longer created when Invoices are synced. You\n    must now sync InvoiceItems directly.\n\n    **INVOICEITEM**\n\n-   Removed `InvoiceItem.line_type`\n\n    **PLAN**\n\n-   Plan no longer has a `stripe_plan` property. Use `api_retrieve()`\n    instead.\n\n-   `Plan.currency` no longer uses choices. Use the\n    `get_supported_currency_choices()` utility and create your own\n    custom choices list instead.\n\n-   Plan interval choices are now in `Plan.INTERVAL_TYPE_CHOICES`\n\n    **SUBSCRIPTION**\n\n-   `Subscription.is_period_current()` now checks for a current trial\n    end if the current period has ended. This change means subscriptions\n    extended with `Subscription.extend()` will now be seen as valid.\n\n### MIGRATIONS\n\nWe'll sync your current records with Stripe in a migration. It will take\na while, but it's the only way we can ensure data integrity. There were\nsome fields for which we needed to temporarily add placeholder defaults,\nso just make sure you have a customer with ID 1 and a plan with ID 1 and\nyou shouldn't run into any issues (create dummy values for these if need\nbe and delete them after the migration).\n\n### BIG HUGE NOTE - DON'T OVERLOOK THIS\n\n!!! warning\n\n    Subscription and InvoiceItem migration is not possible because old\n    records don't have Stripe IDs (so we can't sync them). Our approach is\n    to delete all local subscription and invoiceitem objects and re-sync\n    them from Stripe.\n\n    We 100% recommend you create a backup of your database before performing\n    this upgrade.\n\n### Other changes\n\n-   Postgres users now have access to the\n    `DJSTRIPE_USE_NATIVE_JSONFIELD` setting. (Thanks @jleclanche) \\#517,\n    \\#523\n-   Charge receipts now take `DJSTRIPE_SEND_INVOICE_RECEIPT_EMAILS` into\n    account (Thanks @r0fls)\n-   Clarified/modified installation documentation (Thanks @pydanny)\n-   Corrected and revised ANONYMOUS_USER_ERROR_MSG (Thanks @pydanny)\n-   Added fnmatching to `SubscriptionPaymentMiddleware`\n    (Thanks @pydanny)\n-   `SubscriptionPaymentMiddleware.process_request()` functionality\n    broken up into multiple methods, making local customizations easier\n    (Thanks @pydanny)\n-   Fully qualified events are now supported by event handlers as\n    strings e.g. 'customer.subscription.deleted' (Thanks @lskillen)\n    \\#316\n-   runtests now accepts positional arguments for declaring which tests\n    to run (Thanks @lskillen) \\#317\n-   It is now possible to reprocess events in both code and the admin\n    interface (Thanks @lskillen) \\#318\n-   The confirm page now checks that a valid card exists.\n    (Thanks @scream4ik) \\#325\n-   Added support for viewing upcoming invoices (Thanks @lskillen) \\#320\n-   Event handler improvements and bugfixes (Thanks @lskillen) \\#321\n-   API list() method bugfixes (Thanks @lskillen) \\#322\n-   Added support for a custom webhook event handler (Thanks @lskillen)\n    \\#323\n-   Django REST Framework contrib package improvements\n    (Thanks @aleccool213) \\#334\n-   Added `tax_percent` to CreateSubscriptionSerializer\n    (Thanks @aleccool213) \\#349\n-   Fixed incorrectly assigned `application_fee` in Charge calls\n    (Thanks @kronok) \\#382\n-   Fixed bug caused by API change (Thanks @jessamynsmith) \\#353\n-   Added inline documentation to pretty much everything and enforced\n    docsytle via flake8 (Thanks @aleccool213)\n-   Fixed outdated method call in template (Thanks @kandoio) \\#391\n-   Customer is correctly purged when subscriber is deleted, regardless\n    of how the deletion happened (Thanks @lskillen) \\#396\n-   Test webhooks are now properly captured and logged. No more bounced\n    requests to Stripe! (Thanks @jameshiew) \\#408\n-   CancelSubscriptionView redirect is now more flexible\n    (Thanks @jleclanche) \\#418\n-   Customer.sync_cards() (Thanks @jleclanche) \\#438\n-   Many stability fixes, bugfixes, and code cleanup\n    (Thanks @jleclanche)\n-   Support syncing canceled subscriptions (Thanks @jleclanche) \\#443\n-   Improved admin interface (Thanks @jleclanche with @jameshiew) \\#451\n-   Support concurrent TEST + LIVE API keys (Fix webhook event\n    processing for both modes) (Thanks @jleclanche) \\#461\n-   Added Stripe Dashboard link to admin change panel\n    (Thanks @jleclanche) \\#465\n-   Implemented `Plan.amount_in_cents` (Thanks @jleclanche) \\#466\n-   Implemented `Subscription.reactivate()` (Thanks @jleclanche) \\#470\n-   Added `Plan.human_readable_price` (Thanks @jleclanche) \\#498\n-   (Re)attach the Subscriber when we find it's id attached to a\n    customer on Customer sync (Thanks @jleclanche) \\#500\n-   Made API version configurable (with dj-stripe recommended default)\n    (Thanks @lskillen) \\#504\n"
  },
  {
    "path": "docs/history/2_4_0.md",
    "content": "# dj-stripe 2.4.0 release notes (2020-11-19)\n\n!!! attention\n\n    To upgrade to 2.4.0 from older versions of dj-stripe, scroll down to the [Upgrade Guide](#upgrade-guide).\n\n## Introducing sponsorships and our first sponsor\n\nWe're excited to introduce our [Sponsorship\ntiers](https://github.com/sponsors/dj-stripe). Individuals may back dj-stripe to assist\nwith development. Larger backers may choose one the [paid support plans\navailable](../project/support.md#support_plans) to receive support on top of ensuring the long-term\nviability of the project!\n\nAnd this release was made possible by none other than… [Stripe](https://stripe.com)! Our\nvery first Gold sponsor. Their financial backing has allowed us to pour a lot of work\nthat could not have otherwise happened.\n\n## Release notes\n\n-   Support for Django 3.1 and Python 3.8.\n-   Minimum stripe-python version is now 2.48.0.\n-   Default Stripe API version is now `2020-08-27`.\n-   First-class support for the Price model, replacing Plans.\n-   Support multi-item subscriptions.\n-   Support for API keys in the database (see [Managing Stripe API\n    keys](../api_keys.md#managing_stripe_api_keys)).\n-   Support for syncing objects for multiple, different Stripe accounts.\n-   Use Django 3.1 native JSONField when available.\n-   The field `djstripe_owner_account` has been added to all Stripe models, and is\n    automatically populated with the Account that owns the API key used to retrieve it.\n-   Support for subscription schedules (#899).\n-   Add support for Reporting categories and TaxIds\n-   Update many models to match latest version of the Stripe API.\n-   Fixed Account.get_default_account() for Restricted API Keys.\n-   Allow passing arbitrary arguments (any valid SDK argument) to the following methods:\n    -   `Customer.charge()`\n    -   `Customer.subscribe()`,\n    -   `Charge.capture()`\n    -   `Subscription.update()`\n-   New management command: `djstripe_update_invoiceitem_ids`. This command migrates\n    InvoiceItems using Stripe's old IDs to the new ones.\n-   Hundreds of other bugfixes.\n\n## New feature: in-database Stripe API keys\n\nStripe API keys are now stored in the database, and are now editable in the admin.\n\n!!! warning\n\n    By default, all keys are visible by anyone who has access to the dj-stripe\n    administration.\n\n### Why?\n\nAs we work on supporting multiple Stripe accounts per instance, it is vital for\ndj-stripe to have a mechanism to store more than one Stripe API key. It also became\nobvious that we may want proper programmatic access to create and delete keys.\nFurthermore, API keys are a legitimate upstream Stripe object, and it is not unlikely\nthe API may allow access to listing other API keys in the future, in which case we will\nwant to move them to the database anyway.\n\nIn the next release, we are planning to make WebhookEndpoints (and thus webhook secrets)\nmanageable via the database as well.\n\n### Do I need to change anything?\n\nNot at this time. The settings `STRIPE_LIVE_SECRET_KEY` and `STRIPE_TEST_SECRET_KEY` can\nstill be used. Their values will however be automatically saved to the database at the\nearliest opportunity.\n\n### What about public keys?\n\nSetting `STRIPE_LIVE_PUBLIC_KEY` and `STRIPE_TEST_PUBLIC_KEY` will be deprecated next\nrelease. You do not risk anything by leaving them in your settings: They are not used by\nDj-Stripe outside of the Dj-Stripe mixins, which are now themselves deprecated. So you\ncan safely leave them in your settings, or you can move them to the database as well\n(Keys beginning in `pk_test_` and `pk_live_` will be detected as publishable keys).\n\n## Deprecated features\n\nNobody likes features being removed. However, the last few releases we have had to\nremove features that were not core to what dj-stripe does, or simply poorly-maintained.\nTo keep up with the trend, we are making three major deprecations this release:\n\n### Creating Plans from the Django Admin is no longer supported\n\nThe `Plan` model was special cased in various places, including being the only one which\nsupported being created from the Django administration. This is no longer supported. We\nhave plans to allow creating arbitrary Stripe objects from the Django Admin, but until\nit can be done consistently, we have decided to remove the feature for Plans (which are\ndeprecated by Stripe anyway). The only object type you should be dealing with from the\nadmin is the new APIKey model.\n\nAlong with this, we are also deprecating the `djstripe_sync_plans_from_stripe`\nmanagement command. You can instead use the `djstripe_sync_models` management command,\nwhich supports arbitrary models.\n\n### Deprecating the REST API\n\nWe are dropping all support for the REST API and will be fully removing it in 2.5.0.\nWe're doing this because we wish to keep such an API separate from dj-stripe. Work has\nalready started on a new project, and we will be sharing more details about it soon. If\nyou're interested in helping out, please reach out on\n[Github](https://github.com/dj-stripe/dj-stripe/issues/new)!\n\n### Deprecating `djstripe.middleware.SubscriptionPaymentMiddleware`\n\nLarge parts of dj-stripe, including this middleware, were designed before Stripe's major\nrevamps of the old Plan model into Prices, Products, and multi-plan subscriptions. The\nfunctionality offered by the middleware is no longer adequate, and building on top of it\nwould not be particularly robust. We may bring similar functionality back in the future,\nbut the middleware as it is is going away (as well as the undocumented\n`djstripe.utils.subscriber_has_active_subscription` utility function).\n\nIf you want to keep the functionality for your project, you may wish to [copy the latest\nversion of the\nmiddleware](https://github.com/dj-stripe/dj-stripe/blob/2.4.0/djstripe/middleware.py).\n\n### Deprecating `djstripe.mixins`\n\nThis is being deprecated for similar reasons as the SubscriptionPaymentMiddleware.\nHowever, the mixins module was undocumented and never officially supported.\n\n### Other deprecations\n\n-   The `account` field on `Charge` has been renamed to `on_behalf_of`, to be consistent\n    with Stripe's upstream model. Note that this field is separate from\n    `djstripe_owner_account`, which is set by dj-stripe itself to match the account of\n    the API key used.\n-   `Account.get_connected_account_from_token()` is deprecated in favour of\n    `Account.get_or_retrieve_for_api_key()`, which supports more than just Connect\n    accounts.\n-   `Customer.has_active_subscription()` is deprecated in favour of\n    `Customer.is_subscribed_to()`. Note that the former takes a plan as argument,\n    whereas the latter takes a product as argument.\n-   The `tax_percent` attribute of `Invoice` is no longer populated and will be removed\n    in 2.5.0. You may want to use `Invoice.default_tax_rates` instead, which uses the\n    new TaxId functionality.\n-   `Customer.business_vat_id` is being deprecated in favour of using TaxId models\n    directly.\n\n## Breaking changes\n\n-   Rename PlanBillingScheme to BillingScheme.\n-   Remove `Plan.update_name()` and these previously-deprecated fields:\n\n    -   `Customer.business_vat_id`\n    -   `Subscription.start`\n    -   `Subscription.billing`\n\n## Upgrade Guide\n\nBefore you upgrade to dj-stripe 2.4.0, we recommend upgrading to dj-stripe 2.3.0.\nUpgrading one major release at a time minimizes the risk of issues arising.\n\nUpgrading directly to 2.4.0 from dj-stripe versions older than 2.2.0 is unsupported.\n\nTo upgrade dj-stripe, run `pip install --upgrade dj-stripe==2.4.0`.\n\nOnce installed, you can run `manage.py migrate djstripe` to migrate the database models.\n\n!!! attention\n\n    If you are doing multiple major dj-stripe upgrades in a row, remember to run the\n    migrate command after every upgrade. Skipping this step WILL cause errors.\n\n!!! note\n\n    Migrating the database models may take a long time on databases with large amounts of customers.\n\n### Settings changes\n\nA new mandatory setting `DJSTRIPE_FOREIGN_KEY_TO_FIELD` has been added. If you are\nupgrading from an older version, you need to set it to `\"djstripe_id\"`.\n\nSetting it to `\"id\"` will make dj-stripe use the Stripe IDs as foreign keys. Although\nthis is recommended for new installations, there is currently no migration available for\ngoing from `\"djstripe_id\"` to `\"id\"`.\n\nFor more information on this setting, see\n[Settings](../reference/settings.md#djstripe_foreign_key_to_field).\n"
  },
  {
    "path": "docs/history/2_4_x.md",
    "content": "# dj-stripe 2.4.4 release notes (2021-05-22)\n\n-   Fix syncing of tax IDs in management commands\n-   Set `default_auto_field` in migrations to prevent creation of extra migrations\n-   Misc test and documentation fixes\n\n# dj-stripe 2.4.3 release notes (2021-02-08)\n\n-   Fix webhook error when processing events that contain a reference to a deleted\n    payment method (such as a refund on a payment whose card has been detached or\n    removed)\n-   Fix a couple of regressions in `djstripe_sync_models` management command.\n\n# dj-stripe 2.4.2 release notes (2021-01-24)\n\n## Release notes\n\n-   Fix error in `Customer.add_card()` due to Stripe's `sources` deprecation. (#1293)\n-   Fix `Subscription.update()` usage of the deprecated Stripe `prorate` argument.\n    dj-stripe now explicitly uses `proration_behavior`, setting it to `\"none\"` when\n    `prorate` is `False`, and `\"create_prorations\"` when `prorate` is `True`.\n\n# dj-stripe 2.4.1 release notes (2020-11-29)\n\n## Release notes\n\n-   Upgrade default Stripe API version to `2020-08-27`. Although we documented doing so\n    in 2.4.0, it was not correctly set as such. This has been fixed for consistency.\n-   The `Price` model was incorrectly released with an `amount_in_cents` property,\n    matching that of the `Plan` model. However, Price amounts are already in cent. The\n    property has been removed, use `unit_amount` instead.\n-   Fix `Price.human_readable_price` calculation\n-   Fix non-blank nullable `Charge` fields\n-   Fix Price.tiers not being synced correctly with `djstripe_sync_models` (#1284)\n-   Fix sync model recursion loop (see #1288)\n"
  },
  {
    "path": "docs/history/2_5_0.md",
    "content": "# dj-stripe 2.5.0 (2021-06-06)\n\n!!! attention\n\n    It is not possible to upgrade to dj-stripe 2.5.0 from versions older than 2.2.2.\n    To upgrade from an older version, first upgrade to `dj-stripe 2.2.2`.\n\n## Release notes\n\n-   Minimum Python version is now 3.6.2.\n-   Support for Python 3.9 and Django 3.2.\n-   In keeping with upstream's cycle, Django 3.0 is no longer officially supported.\n    (Note that it will still work, because Django 2.2 LTS is still supported.)\n-   SQLite versions older than 3.26 are no longer supported.\n-   New models: FileLink, Mandate\n-   Cards and Bank Accounts are now visible in the admin interface.\n-   Lots of model sync fixes since 2.4.0.\n\n## Deprecated features\n\n-   The `FileUpload` model has been renamed `File`, for consistency with Stripe's SDK.\n    Although the old name is still supported, it will eventually be removed.\n-   Deprecate `charge_immediately` argument to `Customer.subscribe()`. It did not behave\n    as expected on recent versions of Stripe. If you were using it set to\n    `charge_immediately=False`, you can instead pass `collection_method=\"send_invoice\"`,\n    which will send the Customer the invoice to manually pay, instead.\n\n## Breaking changes\n\n-   When calling `Customer.delete()` in prior versions of dj-stripe, the Customer object\n    would be deleted in the upstream API and the Customer object would be retained but\n    with a `date_purged` attribute. This was the only model behaving this way, and it is\n    no longer the case. If you wish to purge a customer like before, you may call\n    `Customer.purge()` instead, though that method may be removed in future versions as\n    well.\n-   Remove deprecated DRF integration (`djstripe.contrib.rest_framework`)\n-   Remove deprecated `djstripe.decorators` module\n-   Remove deprecated `djstripe.middleware` module\n-   Remove deprecated fields `Account.business_vat_id` and `Subscription.tax_percent`\n-   Remove deprecated method `Account.get_connected_account_from_token()`. Use\n    `Account.get_or_retrieve_for_api_key()` instead.\n-   Remove deprecated `Charge.account` property. Use `Charge.on_behalf_of` instead.\n-   Remove deprecated `Customer.has_active_subscription()` method. Use\n    `Customer.is_subscribed_to(product)` instead.\n-   `FileUploadPurpose` enum has been renamed `FilePurpose`.\n-   `FileUploadType` enum has been renamed `FileType`.\n"
  },
  {
    "path": "docs/history/2_5_x.md",
    "content": "# dj-stripe 2.5.1 (2021-07-02)\n\n## Release notes\n\n-   Fixed migration issue for new setups using custom `DJSTRIPE_CUSTOMER_MODEL`.\n-   Display correct JSON for JSONFields in the Django admin.\n-   Fix manual syncing of `SubscriptionItem`.\n"
  },
  {
    "path": "docs/history/2_6_0.md",
    "content": "# dj-stripe 2.6.0 (2022-01-15)\n\n!!! attention\n\n    It is not possible to upgrade to dj-stripe 2.6.0 from versions older than 2.3.0.\n    To upgrade from an older version, first upgrade to `dj-stripe 2.3.0`.\n\n## Release highlights\n\n-   Support for Python 3.10 and Django 4.0.\n-   New models: Mandate, Payout, UsageRecordSummary, WebhookEndpoint (unused)\n-   Significant improvements and fixes to Stripe Connect features.\n-   Storing Stripe API keys by adding them to the Admin is now supported.\n    This allows for use of multiple Stripe API keys (multiple Stripe accounts).\n-   Support for syncing Connect accounts via `djstripe_sync_models`.\n\n## Deprecated features\n\n-   The use of the old `jsonfield`-based `JSONField` is deprecated and support for it\n    will be dropped in dj-stripe 2.8.0. `django.models.JSONField` is available since\n    Django 3.1.0. To switch to the newer JSONFields, set `DJSTRIPE_USE_NATIVE_JSONFIELD`\n    to `True`. Set it to `False` to remain on the `jsonfield`-powered text-based fields.\n    A manual migration is necessary to convert existing databases from text to json.\n-   The `DJSTRIPE_PRORATION_POLICY` setting is deprecated and will be ignored in 2.8.\n    Specify `proration_policy` in the `Subscription.update()` method explicitly instead.\n-   `Customer.can_charge()` is now deprecated. This was a very misleading method which\n    resulted in incorrect behaviour when Customers had multiple payment methods. It will\n    be removed in dj-stripe 2.8.0. You can use `Customer.payment_methods.all()` instead.\n-   For similar reasons, `Customer.has_valid_source()` is deprecated and will be removed\n    in dj-stripe 2.8.0. You can use `Customer.sources.all()` instead.\n\n## Breaking changes\n\n-   Python 3.6 is no longer supported. The new minimum version of Python is 3.7.12.\n-   Django 2.2 and 3.1 are no longer supported.\n-   `DJSTRIPE_USE_NATIVE_JSONFIELD` now defaults to `True`. If you previously had it set\n    to `False`, or did not have it set, you may want to explicitly set it to `False` in\n    order to support a pre-existing database. A migration path will later be provided\n    for this use case.\n-   The undocumented `get_stripe_api_version()` helper function has been removed.\n-   Settings for dj-stripe are now in `djstripe.settings.djstripe_settings` (as opposed\n    to top-level in `djstripe.settings`)\n-   `Customer.subscribe()` method no longer accepts positional arguments, only keywords.\n-   `charge_immediately` support in Customer.subscribe() has been removed (deprecated\n    in 2.4). Set `collection_method` instead.\n-   The `at_period_end` argument to `Subscription.cancel()` now defaults to `False`,\n    instead of the value of `DJSTRIPE_PRORATION_POLICY`.\n\n## Other changes\n\n-   The Stripe Account that triggered an Event is now available on the field\n    `WebhookEventTrigger.stripe_trigger_account`.\n-   Fixed recursive fetch/update loop errors in `djstripe_sync_models`.\n-   Migrations have been optimized and should be faster.\n-   dj-stripe now checks the apparent validity of API keys used and will raise\n    `InvalidStripeAPIKey` if the API key looks completely incorrect.\n-   `Customers` can now be subscribed to multiple prices and/or plans by passing the `items` argument\n    to `Customer.subscribe()`.\n-   Checkout Session metadata can be used to create/link a Stripe `Customer` to the\n    `Customer` instance specified by the `djstripe_settings.SUBSCRIBER_CUSTOMER_KEY`.\n"
  },
  {
    "path": "docs/history/2_6_x.md",
    "content": "# dj-stripe 2.6.2 (2022-07-02)\n\nThis is a maintenance release to remove the generation of an unnecessary migration when\nrunning dj-stripe on Django 4.0.\nThis release does not guarantee Django 4.0 compatibility. Run at your own risk.\n\n## Release notes\n\n-   Update migrations to be compatible with Django 4.0\n\n# dj-stripe 2.6.1 (2022-02-07)\n\n## Release notes\n\n-   Fix issue saving a new WebhookEndpoint from the admin\n-   Fix potential IntegrityError when syncing models\n"
  },
  {
    "path": "docs/history/2_7_0.md",
    "content": "# dj-stripe 2.7.0 (2022-10-17)\n\n!!! attention\n\n    It is not possible to upgrade to dj-stripe 2.7.0 from versions older than 2.4.0.\n    To upgrade from an older version, first upgrade to dj-stripe 2.4.0.\n\nThis release focuses on Webhook Endpoints. For more information on the reasoning behind\nthe changes, please see the discussion on Github:\n\n<https://github.com/dj-stripe/dj-stripe/discussions/1437>\n\n## Release highlights\n\n-   Webhook Endpoints are now configured via the Django administration.\n-   Multiple Webhook Endpoints are now supported.\n-   Webhook Endpoints now have a unique, non-guessable URL.\n\n## Deprecated features\n\n-   The `DJSTRIPE_WEBHOOK_URL` setting is deprecated. It will be removed in dj-stripe\n    2.9. It was added to give a way of \"hiding\" the webhook endpoint URL, but that is no\n    longer necessary with the new webhook endpoint system.\n\n## Breaking changes\n\n-   Remove the deprecated middleware `djstripe.middleware.SubscriptionPaymentMiddleware`\n-   Remove support for the deprecated `DJSTRIPE_SUBSCRIPTION_REDIRECT` setting\n-   Remove support for the `DJSTRIPE_SUBSCRIPTION_REQUIRED_EXCEPTION_URLS` setting\n\n## Other changes\n\n-   Many Stripe Connect related fixes (Special thanks to Dominik Bartenstein of Zemtu)\n-   Allow passing stripe kwargs in Subscription.cancel()\n-   Various admin improvements\n-   Add support for managing subscription schedules from the admin\n"
  },
  {
    "path": "docs/history/2_7_x.md",
    "content": "# dj-stripe 2.7.2 (2022-10-21)\n\n## Release notes\n\n-   Fix installing with Poetry on Django 4.0 and higher\n\n# dj-stripe 2.7.1 (2022-10-20)\n\n## Release notes\n\n-   Remove an enum value generating an extra migration\n-   Allow Django 4.1 as a dependency (Note: Running dj-stripe 2.7.x with Django 4.1 is\n    untested)\n"
  },
  {
    "path": "docs/history/2_8_0.md",
    "content": "# dj-stripe 2.8.0 (202X-XX-XX)\n\n!!! attention\n\n    It is not possible to upgrade to dj-stripe 2.8.0 from versions older than 2.5.0.\n    To upgrade from an older version, first upgrade to dj-stripe 2.5.0.\n\n## Release highlights\n\n-   Python 3.11 is now supported.\n-   Django 4.1 is now supported.\n-   Python 3.7 is no longer supported. Python 3.8 or higher is required.\n-   Added `LineItem` model.\n-   Added `Discount` model.\n-   New webhook signals are available:\n    -   `djstripe.signals.webhook_pre_validate(instance, api_key)`: Fired before webhook validation\n    -   `djstripe.signals.webhook_post_validate(instance, api_key, valid)`: Fired after validation (even unsuccessful validations)\n    -   `djstripe.signals.webhook_pre_process(instance, api_key)`: Fired before webhook processing. Not fired if the validation failed.\n    -   `djstripe.signals.webhook_post_process(instance, api_key)`: Fired after webhook successful processing.\n-   `djstripe.signals.webhook_processing_error` now also takes `instance` and `api_key` arguments\n-   `stripe.api_version` is no longer manipulated by dj-stripe.\n-   Resolved ambiguity between `LineItem` and `InvoiceItem` models. It was incorrectly assumed that the `lines` List object on `Invoice` and `UpcomingInvoice` models only return `InvoiceItem` objects. Moreover `LineItem` objects can also be of type `subscription` if the user adds a Subscription to their `Invoice` as a lineitem.\n\n## Deprecated features\n\n-   `DJSTRIPE_WEBHOOK_EVENT_CALLBACK` is deprecated in favour of the new webhook signals.\n\n## Breaking changes\n\n-   Remove legacy JSONField support. This drops support for installations with the\n    `DJSTRIPE_USE_NATIVE_JSONFIELD` setting set to `False`.\n    NOTE: No migration path is available yet.\n    https://github.com/dj-stripe/dj-stripe/issues/1820\n-   Remove `djstripe_sync_plans_from_stripe` command (deprecated in 2.4.0).\n    Use `djstripe_sync_models price` instead.\n-   Remove `Customer.can_charge()`, `Customer.has_valid_source()` ()\n-   Remove `DJSTRIPE_PRORATION_POLICY` setting (deprecated in 2.6.0)\n-   Remove deprecated `prorate` argument to `Subscription.update()` (Use Stripe's\n    `proration_behavior` argument instead)\n-   Remove undocumented `set_stripe_api_version()` helper function\n    and context manager `stripe_temporary_api_version()`.\n    The API version is now set on each request individually.\n\n\n## Other changes\n\n-  Updated `check_stripe_api_key` django system check to not be a blocker for new dj-stripe users by raising Info warnings on the console. If the Stripe keys were not defined in the settings file, the `Critical` warning was preventing users to add them directly from the admin as mentioned in the docs. This was creating a chicken-egg situation where one could only add keys in the admin before they were defined in settings.\n- `check_stripe_api_key` will raise appropriate warnings on the console directing users to add keys directly from the django admin.\n-  Swapped Critical Error to Info for `_check_webhook_endpoint_validation` check to allow the users to use the django admin.\n- `LineItem` instances can also get synced using the `djstripe_sync_models` management command.\n"
  },
  {
    "path": "docs/history/2_x.md",
    "content": "# dj-stripe 2.0 ~ 2.3 release notes\n\n## 2.3.0 (2020-04-19)\n\n-   The minimum version of Django is now 2.1, and Python 3.6.\n-   Changed `JSONField` dependency back to\n    [jsonfield](https://github.com/rpkilby/jsonfield/) from\n    [jsonfield2](https://github.com/rpkilby/jsonfield2/) (see [Warning\n    about safe uninstall of jsonfield2 on\n    upgrade](#warning-about-safe-uninstall-of-jsonfield2-on-upgrade)).\n-   Fixed handling of `TaxRate` events (#1094).\n-   Fixed pagination issue in `Invoice.sync_from_stripe_data` (#1052).\n-   Fixed pagination issues in `Subscription` & `Charge`\n    `.sync_from_stripe_data` (#1054).\n-   Tidyup `_stripe_object_set_total_tax_amounts` unique handling (#1139).\n-   Dropped previously-deprecated `Invoice` fields (see <https://stripe.com/docs/upgrades#2018-11-08> ):\n    -   `.closed`\n    -   `.forgiven`\n    -   `.billing` (renamed to `.collection_method`)\n-   Dropped previously-deprecated `enums.InvoiceStatus` (#1020).\n-   Deprecated the following fields - will be removed in 2.4 (#1087):\n    -   `Subscription.billing` (use `.collection_method` instead)\n    -   `Subscription.start` (use `.start_date` instead)\n    -   `Subscription.tax_percent` (use `.default_tax_rates` instead)\n-   Added `Invoice.status` and `enums.InvoiceStatus` (#1020).\n-   Added new `Invoice` fields (\\#1020, \\#1087):\n    -   `.discount`\n    -   `.default_source`\n    -   `.status`\n-   Added new `Subscription` fields (\\#1087):\n    -   `.default_payment_method`\n    -   `.default_source`\n    -   `.next_pending_invoice_item_invoice`\n    -   `.pending_invoice_item_interval`\n    -   `.pending_update`\n    -   `.start_date`\n\n### Warning about safe uninstall of jsonfield2 on upgrade\n\nBoth **jsonfield** and **jsonfield2** use the same import path, so if\nupgrading from dj-stripe\\~=2.2.0 in an existing virtualenv, be sure to\nuninstall jsonfield2 first. eg:\n\n```bash\n\n    # ensure jsonfield is uninstalled before we install jsonfield2\n    pip uninstall jsonfield2 -y && pip install \"dj-stripe>=2.3.0dev\"\n```\n\nOtherwise, `pip uninstall jsonfield2` will remove jsonfield's\n`jsonfield` module from `site-packages`, which would cause errors like\n`ImportError: cannot import name 'JSONField' from 'jsonfield' (unknown location)`\n\nIf you have hit this ImportError already after upgrading, running this\nshould resolve it:\n\n```bash\n# remove both jsonfield packages before reinstall to fix ImportError:\npip uninstall jsonfield jsonfield2 -y && pip install \"dj-stripe>=2.3.0\"\n```\n\nNote that this is only necessary if upgrading from dj-stripe 2.2.x,\nwhich temporarily depended on jsonfield2. This process is not necessary\nif upgrading from an earlier version of dj-stripe.\n\n## 2.2.2 (2020-01-20)\n\nThis is a bugfix-only version:\n\n-   Fixed handling of `TaxRate` events (#1094).\n\n## 2.2.1 (2020-01-14)\n\nThis is a bugfix-only version:\n\n-   Fixed bad package build.\n\n## 2.2.0 (2020-01-13)\n\n-   Changed `JSONField` dependency package from\n    [jsonfield](https://github.com/rpkilby/jsonfield/) to\n    [jsonfield2](https://github.com/rpkilby/jsonfield2/), for Django 3\n    compatibility (see [Warning about safe uninstall of jsonfield on\n    upgrade](#warning-about-safe-uninstall-of-jsonfield-on-upgrade)).\n    Note that Django 2.1 requires jsonfield&lt;3.1.\n-   Added support for Django 3.0 (requires jsonfield2&gt;=3.0.3).\n-   Added support for python 3.8.\n-   Refactored `UpcomingInvoice`, so it's no longer a subclass of\n    `Invoice` (to allow `Invoice` to use `ManyToManyFields`).\n-   Dropped previously-deprecated `Account` fields (see <https://stripe.com/docs/upgrades#2019-02-19> ):\n    -   `.business_name`\n    -   `.business_primary_color`\n    -   `.business_url` (changed to a property)\n    -   `.debit_negative_balances`\n    -   `.decline_charge_on`\n    -   `.display_name`\n    -   `.legal_entity`\n    -   `.payout_schedule`\n    -   `.payout_statement_descriptor`\n    -   `.statement_descriptor`\n    -   `.support_email`\n    -   `.support_phone`\n    -   `.support_url`\n    -   `.timezone`\n    -   `.verification`\n-   Dropped previously-deprecated `Account.business_logo` property\n    (renamed to `.branding_icon`)\n-   Dropped previously-deprecated `Customer.account_balance` property\n    (renamed to `.balance`)\n-   Dropped previously-deprecated properties `Invoice.application_fee`,\n    `Invoice.date`\n-   Dropped previously-deprecated enum `PaymentMethodType` (use\n    `DjstripePaymentMethodType` instead)\n-   Renamed `Invoice.billing` to `.collection_method` (added deprecated\n    property for the old name).\n-   Updated `Invoice` model to add missing fields.\n-   Added `TaxRate` model, and `Invoice.default_tax_rates`,\n    `InvoiceItem.tax_rates`, `Invoice.total_tax_amounts`,\n    `Subscription.default_tax_rates`, `SubscriptionItem.tax_rates`\n    (#1027).\n-   Change urls.py to use the new style urls.\n-   Update forward relation fields in the admin to be raw id fields.\n-   Updated `StripeQuantumCurrencyAmountField` and\n    `StripeDecimalCurrencyAmountField` to support Stripe Large Charges\n    (#1045).\n-   Update event handling so `customer.subscription.deleted` updates\n    subscriptions to `status=\"canceled\"` instead of deleting it from our\n    database, to match Stripe's behaviour (#599).\n-   Added missing `Refund.reason` value, increases field width (#1075).\n-   Fixed `Refund.status` definition, reduces field width (#1076).\n-   Deprecated non-standard `Invoice.status` (renamed to\n    `Invoice.legacy_status`) to make way for the Stripe field\n    (preparation for #1020).\n\n### Warning about safe uninstall of jsonfield on upgrade\n\nBoth **jsonfield** and **jsonfield2** use the same import path, so if\nupgrading to dj-stripe&gt;=2.2 in an existing virtualenv, be sure to\nuninstall jsonfield first. eg:\n\n```bash\n# ensure jsonfield is uninstalled before we install jsonfield2\npip uninstall jsonfield -y && pip install \"dj-stripe>=2.2.0\"\n```\n\nOtherwise, `pip uninstall jsonfield` will remove jsonfield2's\n`jsonfield` module from `site-packages`, which would cause errors like\n`ImportError: cannot import name 'JSONField' from 'jsonfield' (unknown location)`\n\nIf you have hit this ImportError already after upgrading, running this\nshould resolve it:\n\n```bash\n# remove both jsonfield packages before reinstall to fix ImportError:\npip uninstall jsonfield jsonfield2 -y && pip install \"dj-stripe>=2.2.0\"\n```\n\n### Note on usage of Stripe Elements JS\n\nSee [Integrating Stripe Elements](https://dj-stripe.github.io/dj-stripe/en/master/stripe_elements_js/)\nfor notes about usage of the Stripe Elements frontend JS library.\n\nIn summary: If you haven't yet migrated to PaymentIntents, prefer\n`stripe.createSource()` to `stripe.createToken()`.\n\n## 2.1.1 (2019-10-01)\n\nThis is a bugfix-only release:\n\n-   Updated webhook signals list (#1000).\n-   Fixed issue syncing PaymentIntent with destination charge (#960).\n-   Fixed `Customer.subscription` and `.valid_subscriptions()` to ignore\n    `status=incomplete_expired` (#1006).\n-   Fixed error on `paymentmethod.detached` event with `card_xxx`\n    payment methods (#967).\n-   Added `PaymentMethod.detach()` (#943).\n-   Updated `help_text` on all currency fields to make it clear if\n    they're holding integer cents (`StripeQuantumCurrencyAmountField`)\n    or decimal dollar (or euro, pound etc)\n    (`StripeDecimalCurrencyAmountField`) (#999)\n-   Documented our preferred Django model field types (#986)\n\n### Upcoming migration of currency fields (storage as cents instead of dollars)\n\nPlease be aware that we're looking at standardising our currency storage\nfields as integer quanta (cents) instead of Decimal (dollar) values, to\nmatch stripe.\n\nThis is intended to be part of the 3.0 release, since it will involve\nsome breaking changes. See \\#955 for details and discussion.\n\n## 2.1.0 (2019-09-12)\n\n-   Dropped Django 2.0 support\n-   The Python stripe library minimum version is now `2.32.0`, also\n    `2.36.0` is excluded due to a regression (#991).\n-   Dropped previously-deprecated `Charge.fee_details` property.\n-   Dropped previously-deprecated `Transfer.fee_details` property.\n-   Dropped previously-deprecated `field_name` parameter to\n    `sync_from_stripe_data`\n-   Dropped previously-deprecated alias `StripeObject` of `StripeModel`\n-   Dropped previously-deprecated alias `PaymentMethod` of `DjstripePaymentMethod`\n-   Dropped previously-deprecated properties `Charge.source_type` and\n    `Charge.source_stripe_id`\n-   `enums.PaymentMethodType` has been deprecated, use `enums.DjstripePaymentMethodType`\n-   Made `SubscriptionItem.quantity` nullable as per Plans with\n    `usage_type=\"metered\"` (follow-up to #865)\n-   Added manage commands `djstripe_sync_models` and\n    `djstripe_process_events` (#727, #89)\n-   Fixed issue with re-creating a customer after `Customer.purge()` (#916)\n-   Fixed sync of Customer Bank Accounts (#829)\n-   Fixed `Subscription.is_status_temporarily_current()` (#852)\n-   New models\n    -   Payment Intent\n    -   Setup Intent\n    -   Payment Method\n    -   Session\n-   Added fields to `Customer` model: `address`, `invoice_prefix`,\n    `invoice_settings`, `phone`, `preferred_locales`, `tax_exempt`\n\nChanges from API 2018-11-08:\n\n-   Added `Invoice.auto_advance`, deprecated `Invoice.closed` and\n    `Invoice.forgiven`, see\n    <https://stripe.com/docs/billing/migration/invoice-states#upgrade-checklist>\n\nChanges from API 2019-02-19:\n\n-   Major changes to Account fields, see\n    <https://stripe.com/docs/upgrades#2019-02-19> , updated Account\n    fields to match API 2019-02-19:\n\n-   Added `Account.business_profile`, `.business_type`, `.company`,\n    `.individual`, `.requirements`, `.settings`\n-   Deprecated the existing fields, to be removed in 2.2\n-   Special handling of the icon and logo fields:\n    > -   Renamed `Account.business_logo` to `Account.branding_icon`\n    >     (note that in Stripe's API `Account.business_logo` was renamed\n    >     to `Account.settings.branding_icon`, and\n    >     `Account.business_logo_large` (which we didn't have a field\n    >     for) was renamed to `Account.settings.branding_logo`)\n    > -   Added deprecated property for `Account.business_logo`\n    > -   Added `Account.branding_logo` as a ForeignKey\n    > -   Populate `Account.branding_icon` and `.branding_logo` from the\n    >     new `Account.settings.branding.icon` and `.logo`\n\nChanges from API 2019-03-14:\n\n-   Renamed `Invoice.application_fee` to\n    `Invoice.application_fee_amount` (added deprecated property for the\n    old name)\n-   Removed `Invoice.date`, in place of `Invoice.created` (added\n    deprecated property for the old name)\n-   Added `Invoice.status_transitions`\n-   Renamed `Customer.account_balance` to `Customer.balance` (added\n    deprecated property for the old name)\n-   Renamed `Customer.payment_methods` to\n    `Customer.customer_payment_methods`\n-   Added new `SubscriptionStatus.incomplete` and\n    `SubscriptionStatus.incomplete_expired` statuses (#974)\n-   Added new `BalanceTransactionType` values (#983)\n\n### Squashed dev migrations\n\nAs per our [migration policy](../project/contributing.md#django_migration_policy), unreleased migrations on the master\nbranch have been squashed.\n\nIf you have been using the 2.1.0dev branch from master, you'll need to\nrun the squashed migrations migrations before upgrading to >=2.1.0.\n\nThe simplest way to do this is to `pip install dj-stripe==2.1.0rc0` and\nmigrate, alternatively check out the `2.1.0rc0` git tag.\n\n## 2.0.5 (2019-09-12)\n\nThis is a bugfix-only version:\n\n-   Avoid stripe==2.36.0 due to regression (#991)\n\n## 2.0.4 (2019-09-09)\n\nThis is a bugfix-only version:\n\n-   Fixed irreversible migration (#909)\n\n## 2.0.3 (2019-06-11)\n\nThis is a bugfix-only version:\n\n-   In `_get_or_create_from_stripe_object`, wrap create `_create_from_stripe_object` in\n    transaction, fixes `TransactionManagementError` on race condition in webhook\n    processing (#877, #903).\n\n## 2.0.2 (2019-06-09)\n\nThis is a bugfix-only version:\n\n-   Don't save event objects if the webhook processing fails (#832).\n-   Fixed IntegrityError when `REMOTE_ADDR` is an empty string.\n-   Deprecated `field_name` parameter to `sync_from_stripe_data`\n\n## 2.0.1 (2019-04-29)\n\nThis is a bugfix-only version:\n\n-   Fixed an error on `invoiceitem.updated` (#848).\n-   Handle test webhook properly in recent versions of Stripe API\n    (#779). At some point 2018 Stripe silently changed the ID used for\n    test events and `evt_00000000000000` is not used anymore.\n-   Fixed OperationalError seen in migration 0003 on postgres (#850).\n-   Fixed issue with migration 0003 not being unapplied correctly (#882).\n-   Fixed missing `SubscriptionItem.quantity` on metered Plans (#865).\n-   Fixed `Plan.create()` (#870).\n\n## 2.0.0 (2019-03-01)\n\n-   The Python stripe library minimum version is now `2.3.0`.\n-   `PaymentMethod` has been renamed to `DjstripePaymentMethod` (#841). An alias remains\n    but will be removed in the next version.\n-   Dropped support for Django<2.0, Python<3.4.\n-   Dropped previously-deprecated `stripe_objects` module.\n-   Dropped previously-deprecated `stripe_timestamp` field.\n-   Dropped previously-deprecated `Charge.receipt_number` field.\n-   Dropped previously-deprecated `StripeSource` alias for `Card`\n-   Dropped previously-deprecated `SubscriptionView`, `CancelSubscriptionView` and\n    `CancelSubscriptionForm`.\n-   Removed the default value from `DJSTRIPE_SUBSCRIPTION_REDIRECT`.\n-   All `stripe_id` fields have been renamed `id`.\n-   `Charge.source_type` has been deprecated. Use `Charge.source.type`.\n-   `Charge.source_stripe_id` has been deprecated. Use `Charge.source.id`.\n-   All deprecated Transfer fields (Stripe API 2017-04-06 and older), have been dropped.\n    This includes `date`, `destination_type` (`type`), `failure_code`,\n    `failure_message`, `statement_descriptor` and `status`.\n-   Fixed IntegrityError when `REMOTE_ADDR` is missing (#640).\n-   New models:\n    -   `ApplicationFee`\n    -   `ApplicationFeeRefund`\n    -   `BalanceTransaction`\n    -   `CountrySpec`\n    -   `ScheduledQuery`\n    -   `SubscriptionItem`\n    -   `TransferReversal`\n    -   `UsageRecord`\n-   The `fee` and `fee_details` attributes of both the `Charge` and `Transfer` objects\n    are no longer stored in the database. Instead, they access their respective new\n    `balance_transaction` foreign key. Note that `fee_details` has been deprecated on\n    both models.\n-   The `fraudulent` attribute on `Charge` is now a property that checks the\n    `fraud_details` field.\n-   Object key validity is now always enforced (\\#503).\n-   `Customer.sources` no longer refers to a Card queryset, but to a Source queryset. In\n    order to correctly transition, you should change all your references to\n    `customer.sources` to `customer.legacy_cards` instead. The `legacy_cards` attribute\n    already exists in 1.2.0.\n-   `Customer.sources_v3` is now named `Customer.sources`.\n-   A new property `Customer.payment_methods` is now available, which allows you to\n    iterate over all of a customer's payment methods (sources then cards).\n-   `Card.customer` is now nullable and cards are no longer deleted when their\n    corresponding customer is deleted (#654).\n-   Webhook signature verification is now available and is preferred. Set the\n    `DJSTRIPE_WEBHOOK_SECRET` setting to your secret to start using it.\n-   `StripeObject` has been renamed `StripeModel`. An alias remains but will be removed\n    in the next version.\n-   The metadata key used in the `Customer` object can now be configured by changing the\n    `DJSTRIPE_SUBSCRIBER_CUSTOMER_KEY` setting. Setting this to None or an empty string\n    now also disables the behaviour altogether.\n-   Text-type fields in dj-stripe will no longer ever be None. Instead, any falsy text\n    field will return an empty string.\n-   Switched test runner to pytest-django\n-   `StripeModel.sync_from_stripe_data()` will now automatically retrieve related\n    objects and populate foreign keys (#681)\n-   Added `Coupon.name`\n-   Added `Transfer.balance_transaction`\n-   Exceptions in webhooks are now re-raised as well as saved in the database (#833)\n"
  },
  {
    "path": "docs/installation.md",
    "content": "## Installation\n\n### Get the distribution\n\nInstall dj-stripe with pip:\n```bash\n\n    pip install dj-stripe\n```\n\nOr with [Poetry](https://python-poetry.org/) (recommended):\n```bash\n    poetry add dj-stripe\n```\n\n### Configuration\n\nAdd `djstripe` to your `INSTALLED_APPS`:\n```bash\n    INSTALLED_APPS =(\n        ...\n        \"djstripe\",\n        ...\n    )\n```\n\nAdd to urls.py:\n\n```bash\n\n    path(\"stripe/\", include(\"djstripe.urls\", namespace=\"djstripe\")),\n```\n\nTell Stripe about the webhook (Stripe webhook docs can be found\n[here](https://stripe.com/docs/webhooks)) using the full URL of your\nendpoint from the urls.py step above (e.g.\n`https://example.com/stripe/webhook`).\n\nAdd your Stripe keys and set the operating mode:\n```bash\n\n    STRIPE_LIVE_SECRET_KEY = os.environ.get(\"STRIPE_LIVE_SECRET_KEY\", \"<your secret key>\")\n    STRIPE_TEST_SECRET_KEY = os.environ.get(\"STRIPE_TEST_SECRET_KEY\", \"<your secret key>\")\n    STRIPE_LIVE_MODE = False  # Change to True in production\n    DJSTRIPE_WEBHOOK_SECRET = \"whsec_xxx\"  # Get it from the section in the Stripe dashboard where you added the webhook endpoint\n    DJSTRIPE_FOREIGN_KEY_TO_FIELD = \"id\"\n```\n\n!!! note\n\n    djstripe expects `STRIPE_LIVE_MODE` to be a Boolean Type. In case you use `Bash env vars or equivalent` to inject its value, make sure to convert it to a Boolean type. We highly recommended the library [django-environ](https://django-environ.readthedocs.io/en/latest/)\n\n\nSync data from Stripe:\n\n!!! note\n\n    djstripe expects `APIKeys` of all Stripe Accounts you'd like to sync data for to already be in the DB. They can be Added from Django Admin.\n\n\nRun the commands:\n\n```bash\n    python manage.py migrate\n\n    python manage.py djstripe_sync_models\n```\n\nSee [here](stripe_elements_js.md#integrating_stripe_elements-js_sdk) for notes about usage of the Stripe Elements\nfrontend JS library.\n\n### Running Tests\n\nAssuming the tests are run against PostgreSQL:\n\n```bash\n    createdb djstripe\n    pip install tox\n    tox\n```\n"
  },
  {
    "path": "docs/project/authors.md",
    "content": "# Credits\n\n## Core contributors\n\n-   [Alexander Kavanaugh](https://github.com/kavdev) (Co-maintainer)\n-   [Jerome Leclanche](https://github.com/jleclanche) (Co-maintainer)\n-   [Arnav Choudhury](https://github.com/arnav13081994)\n\n## Former core contributors\n\n-   [John Carter](https://github.com/therefromhere)\n-   [Pablo Castellano](https://github.com/PabloCastellano)\n-   [Daniel Greenfeld](https://github.com/pydanny)\n-   [Lee Skillen](https://github.com/lskillen)\n\n## Contributors\n\ndj-stripe is brought to you by many more open source contributors.\n\n[See the complete list on Github](https://github.com/dj-stripe/dj-stripe/graphs/contributors).\n"
  },
  {
    "path": "docs/project/release_process.md",
    "content": "# Release Process\n\n!!! note\n\n    Before `MAJOR` or `MINOR` releases:\n\n    -   Review deprecation notes (eg search for \"deprecated\") and remove\n        deprecated features as appropriate\n    -   Squash migrations (ONLY on unreleased migrations) - see below\n\n## Squash migrations\n\nIf there's more than one unreleased migration on master consider\nsquashing them with `squashmigrations`, immediately before tagging the\nnew release:\n\n-   Create a new squashed migration with `./manage.py squashmigrations`\n    (only squash migrations that have never been in a tagged release)\n\n-   Commit the squashed migration on master with a commit message like\n    \"Squash x.y.0dev migrations\" (this will allow users who running\n    master to safely upgrade, see note below about rc package)\n\n-   Then transition the squashed migration to a normal migration as per Django:\n\n    -   Delete all the migration files it replaces\n    -   Update all migrations that depend on the deleted migrations to\n        depend on the squashed migration instead\n    -   Remove the `replaces` attribute in the Migration class of the\n        squashed migration (this is how Django tells that it is a\n        squashed migration)\n\n-   Commit these changes to master with a message like \"Transition\n    squashed migration to normal migration\"\n\n-   Then do the normal release process - bump version as another commit\n    and tag the release\n\nSee\n<https://docs.djangoproject.com/en/dev/topics/migrations/#migration-squashing>\n\n## Tag + package squashed migrations as rc package (optional)\n\nAs a convenience to users who are running master, an rc version can be\ncreated to package the squashed migration.\n\nTo do this, immediately after the \"Squash x.y.0dev migrations\" commit,\nfollow the steps below but with a x.y.0rc0 version to tag and package a\nrc version.\n\nUsers who have been using the x.y.0dev code from master can then run the\nsquashed migrations migrations before upgrading to &gt;=x.y.0.\n\nThe simplest way to do this is to `pip install dj-stripe==x.y.0rc0` and\nmigrate, or alternatively check out the `x.y.0rc0` git tag and migrate.\n\n## Prepare changes for the release commit\n\n-   Choose your version number (using <https://semver.org/> )\n\n    -   if there's a new migration, it should be a `MAJOR.0.0` or\n        `MAJOR.MINOR.0` version.\n\n-   Review and update `HISTORY.md`\n\n    -   Add a section for this release version\n    -   Set date on this release version\n    -   Check that summary of feature/fixes is since the last release is\n        up to date\n\n-   Update package version number in `setup.cfg`\n\n-   Review and update supported API version in `README.md`\n    (this is the most recent Stripe account version tested against, not\n    `DEFAULT_STRIPE_API_VERSION`)\n\n-   `git add` to stage these changes\n\n## Create signed release commit tag\n\n!!! note\n\n    Before doing this you should have a GPG key set up on github\n\n    If you don't have a GPG key already, one method is via\n    <https://keybase.io/> , and then add it to your github profile.\n\n-   Create a release tag with the above staged changes (where `$VERSION`\n    is the version number to be released:\n\n        $ git commit -m \"Release $VERSION\"\n        $ git tag -fsm \"Release $VERSION\" $VERSION\n\nThis can be expressed as a bash function as follows:\n\n    git_release() { git commit -m \"Release $1\" && git tag -fsm \"Release $1\" $1; }\n\n-   Push the commit and tag:\n\n        $ git push --follow-tags\n\n## Update/create stable branch\n\nPush these changes to the appropriate `stable/MAJOR.MINOR` version\nbranch (eg `stable/2.0`) if they're not already - note that this will\ntrigger the readthedocs build\n\n## Release on pypi\n\nSee\n<https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives>\n"
  },
  {
    "path": "docs/project/sponsors.md",
    "content": "# Sponsors\n\n## Gold Sponsors\n\n[![Stripe Logo](../logos/stripe_blurple.svg)](https://stripe.com)\n\nThis project is sponsored by none other than [Stripe](https://stripe.com/),\nsince August 2020.\n\nWe're thankful for their contribution, which has allowed us to greatly improve the\nproject, increase QA and testing, and push forward with the dj-stripe 2.4.0 release.\n\n## Silver Sponsors\n\nWe do not currently have any Silver sponsors.\n\n[Want to be the first?](https://github.com/sponsors/dj-stripe)\n"
  },
  {
    "path": "docs/project/support.md",
    "content": "# Support\n\n## Support plans\n\ndj-stripe offers paid support plans via Github Sponsors:\n\n<https://github.com/sponsors/dj-stripe>\n\nAll issues and feature requests raised by corporate sponsors will be prioritized.\nGold Sponsors also get a dedicated developer point of contact, to help with any\nquestions, issues, or general inquiries relating to dj-stripe.\n\n## Bug reports and feature requests\n\nPlease report any issues you come across\n[on the Github issue tracker](https://github.com/dj-stripe/dj-stripe/issues).\n\nDo not hesitate to leave feedback and suggestions there as well.\nYou may also ask usage questions on the issue tracker.\n"
  },
  {
    "path": "docs/project/test_fixtures.md",
    "content": "# Test Fixtures\n\ndj-stripe's unit tests rely on fixtures to represent Stripe API and\nwebhook data.\n\n## Rationale\n\nThese fixtures are partly hand-coded and partly generated by creating\nobjects in Stripe and then retrieved via the API.\n\nEach approach has pros and cons:\n\nHand-coding the fixtures allows them to be crafted specifically for a\ntest case. They can also be terse, and nested objects can be done by\nreference to avoid duplication. But maintaining or upgrading them is a\npainstaking manual process.\n\nGenerating the fixtures via Stripe gives the big advantage that Stripe\nschema changes are automatically represented in the fixtures, which\nshould allow us to upgrade dj-stripe's schema to match Stripe much more\neasily. This would be done by updating dj-stripe's targeted API version\n(`DEFAULT_STRIPE_API_VERSION`),\nregenerating the fixtures, and updating the model to match the fixture\nchanges. The down side is it's tricky to regenerate fixture files\nwithout introducing big changes (eg to object ids) - the script does\nthis by mapping a dummy id to various objects.\n\n## Regenerating the test fixtures\n\nTo regenerate the test fixtures (e.g. to populate the fixtures with new\nAPI fields from Stripe), do the following:\n\n1.  (one time only) Create a new Stripe account called \"dj-stripe\n    scratch\", with country set to United States. (we use US so the\n    currency matches the existing fixtures matches, in the future it\n    would be good to test for other countries).\n\n2.  If you already had this account ready and want to start again from\n    scratch, you can delete all of the test data via the button in\n    Settings &gt; Data <https://dashboard.stripe.com/account/data>\n\n3.  Activate a virtualenv with the dj-stripe project (see Getting\n    Started)\n\n4.  Set the dj-stripe secret key environment variable to the secret key\n    for this account (`export STRIPE_SECRET_KEY=sk_test_...`)\n\n5.  Run the manage command to create the test objects in your stripe\n    account if they don't already exist, and regenerate the local\n    fixture files from them:\n\n        $ ./manage.py regenerate_test_fixtures\n\nThe command tries to avoid inconsequential changes to the fixtures (e.g\nthe `created` timestamp) by restoring a\nwhitelist of values from the existing fixtures.\n\nThis functionality can be disabled by passing `--update-sideeffect-fields`.\n"
  },
  {
    "path": "docs/reference/enums.md",
    "content": "# Enumerations\n\n\n::: djstripe.enums\n"
  },
  {
    "path": "docs/reference/managers.md",
    "content": "# Managers\n\n::: djstripe.managers\n"
  },
  {
    "path": "docs/reference/models.md",
    "content": "# Models\n\nModels hold the bulk of the functionality included in the dj-stripe\npackage. Each model is tied closely to its corresponding object in the\nstripe dashboard. Fields that are not implemented for each model have a\nshort reason behind the decision in the docstring for each model.\n\n## Core Resources\n\n::: djstripe.models.core\n\n\n## Payment Methods\n\n<!-- DO NOT INCLUDE LegacySourceMixin AND DjstripePaymentMethod -->\n::: djstripe.models.payment_methods\n    selection:\n        filters: [\"!LegacySourceMixin$\", \"!DjstripePaymentMethod$\"]\n\n\n\n## Billing\n\n<!-- DO NOT INCLUDE DjstripeInvoiceTotalTaxAmount, DjstripeUpcomingInvoiceTotalTaxAmount AND DJBaseInvoiceSTRIPEPAYMENTMETHOD -->\n::: djstripe.models.billing\n    selection:\n        filters: [\"!DjstripeInvoiceTotalTaxAmount$\", \"!DjstripeUpcomingInvoiceTotalTaxAmount$\",\n        \"!BaseInvoice$\"]\n\n\n## Connect\n\n::: djstripe.models.account\n\n::: djstripe.models.connect\n\n\n## Fraud\n\n::: djstripe.models.fraud\n\n## Orders\n\n::: djstripe.models.orders\n\n## Sigma\n\n::: djstripe.models.sigma\n\n\n## Webhooks\n\n::: djstripe.models.webhooks\n"
  },
  {
    "path": "docs/reference/project.md",
    "content": "\n<!-- We load the djstripe and tests packages in order for mkdocstrings to create links to them and their sub-packages and modules, but we don't expose their contents directy to users in the documentation  -->\n::: djstripe\n::: tests\n"
  },
  {
    "path": "docs/reference/settings.md",
    "content": "# Settings\n\n## STRIPE_API_VERSION (='2020-08-27')\n\nThe API version used to communicate with the Stripe API is configurable, and defaults to\nthe latest version that has been tested as working. Using a value other than the default\nis allowed, as a string in the format of YYYY-MM-DD.\n\nFor example, you can specify `\"2020-03-02\"` to use that API version:\n\n```py\nSTRIPE_API_VERSION = \"2020-03-02\"\n```\n\nHowever you do so at your own risk, as using a value other than the default might result\nin incompatibilities between Stripe and this library, especially if Stripe has labelled\nthe differences between API versions as \"Major\". Even small differences such as a new\nenumeration value might cause issues.\n\nFor this reason it is best to assume that only the default version is supported.\n\nFor more information on API versioning, see the [stripe\ndocumentation](https://stripe.com/docs/upgrades).\n\nSee also [API Versions](../api_versions.md#a_note_on_stripe_api_versions).\n\n## DJSTRIPE_FOREIGN_KEY_TO_FIELD\n\n_(Introduced in 2.4.0)_\n\n`DJSTRIPE_FOREIGN_KEY_TO_FIELD` is a setting introduced in dj-stripe version 2.4.0. You\nare required to set it in 2.4.0: It does not have a default value. In 3.0.0, the default\nwill be \"id\", and we recommend setting it to \"id\" for new installations. Older\ninstallations should set it to \"djstripe_id\". Explanation below.\n\nIn dj-stripe 2.3 and before, foreign keys for Stripe models were set to point to the\nforeign model's djstripe_id field, a numeric integer generated by the local database.\nThis new setting allows dj-stripe users to change it to use the \"id\" field, which is the\nupstream, non-numeric Stripe identifier.\n\nWhen using the Stripe identifier as a foreign key, synchronization between Stripe and\ndj-stripe can be made far more efficient and robust. Furthermore, it removes the\nper-installation instability of a critical value. The plan is to get rid of djstripe_id\naltogether for the 3.0 release (we may retain the field itself until 4.0, but it will no\nlonger be a primary key).\n\n**How to migrate older installations from \"djstripe_id\" to \"id\"?**\n\nSuch a migration path has not been designed at the moment. Currently if you want to\nswitch an older installation to \"id\", the easiest way is to wipe the djstripe db and\nsync again from scratch. This is obviously not ideal, and we will design a proper\nmigration path before 3.0.\n\n## DJSTRIPE_IDEMPOTENCY_KEY_CALLBACK (=djstripe.settings.djstripe_settings.\\_get_idempotency_key)\n\nA function which will return an idempotency key for a particular object_type and action\npair. By default, this is set to a function which will create a\n`djstripe.IdempotencyKey` object and return its `uuid`. You may want to customize this\nif you want to give your idempotency keys a different lifecycle than they normally would\nget.\n\nThe function takes the following signature:\n\n```py\ndef get_idempotency_key(object_type: str, action: str, livemode: bool):\n    return \"<idempotency key>\"\n```\n\nThe function MUST return a string suitably random for the object_type/action pair, and\nusable in the Stripe `Idempotency-Key` HTTP header. For more information, see the\n[stripe documentation](https://stripe.com/docs/upgrades).\n\n## DJSTRIPE_SUBSCRIBER_CUSTOMER_KEY (=\"djstripe_subscriber\")\n\nEvery Customer object created in Stripe is tagged with\n[metadata](https://stripe.com/docs/api#metadata) This setting controls what the name of\nthe key in Stripe should be. The key name must be a string no more than 40 characters\nlong.\n\nYou may set this to `None` or `\"\"` to disable that behaviour altogether. This is\nprobably not something you want to do, though.\n\n## DJSTRIPE_SUBSCRIBER_MODEL (=settings.AUTH_USER_MODEL)\n\nIf the AUTH_USER_MODEL doesn't represent the object your application's subscription\nholder, you may define a subscriber model to use here. It should be a string in the form\nof 'app.model'.\n\n!!! note\n\n    DJSTRIPE_SUBSCRIBER_MODEL must have an `email` field. If your\n    existing model has no email field, add an email property that\n    defines an email address to use.\n\nExample Model:\n\n```py\nclass Organization(models.Model):\n    name = CharField(max_length=200, unique=True)\n    admin = ForeignKey(settings.AUTH_USER_MODEL, on_delete=CASCADE)\n\n    @property\n    def email(self):\n        return self.admin.email\n```\n\n## DJSTRIPE_SUBSCRIBER_MODEL_MIGRATION_DEPENDENCY (=\"\\_\\_first\\_\\_\")\n\nIf the model referenced in DJSTRIPE_SUBSCRIBER_MODEL is not created in the `__first__`\nmigration of an app you can specify the migration name to depend on here. For example:\n\"0003_here_the_subscriber_model_was_added\"\n\n## DJSTRIPE_WEBHOOK_URL (=r\"^webhook/$\")\n\n!!! warning\n\n    This setting is deprecated and will be removed in dj-stripe 2.9.\n\nThis is where you can tell Stripe to send webhook responses. You can set this to what\nyou want to prevent unnecessary hijinks from unfriendly people.\n\nAs this is embedded in the URLConf, this must be a resolvable regular expression.\n\n## DJSTRIPE_WEBHOOK_SECRET (=\"\")\n\nIf this is set to a non-empty value, webhook signatures will be verified.\n\n[Learn more about webhook signature\nverification](https://stripe.com/docs/webhooks/signatures).\n\n## DJSTRIPE_WEBHOOK_VALIDATION= (=\"verify_signature\")\n\nThis setting controls which type of validation is done on webhooks. Value can be\n`\"verify_signature\"` for signature verification (recommended default),\n`\"retrieve_event\"` for event retrieval (makes an extra HTTP request), or `None` for no\nvalidation at all.\n\n## DJSTRIPE_WEBHOOK_TOLERANCE (=300)\n\nControls the milliseconds tolerance which wards against replay attacks. Leave this to\nits default value unless you know what you're doing.\n\n## DJSTRIPE_WEBHOOK_EVENT_CALLBACK (=None)\n\nWebhook event callbacks allow an application to take control of what happens when an\nevent from Stripe is received. It must be a callable or importable string to a callable\nthat takes an event object.\n\nOne suggestion is to put the event onto a task queue (such as celery) for asynchronous\nprocessing.\n\nExamples:\n\n```py\n# callbacks.py\ndef webhook_event_callback(event, api_key):\n    \"\"\" Dispatches the event to celery for processing. \"\"\"\n    from . import tasks\n    # Ansychronous hand-off to celery so that we can continue immediately\n    tasks.process_webhook_event.s(event.pk).apply_async()\n```\n\n```py\n# tasks.py\nfrom djstripe.models import WebhookEventTrigger\nfrom stripe.error import StripeError\n\n@shared_task(bind=True)\ndef process_webhook_event(self, pk):\n    \"\"\" Processes events from Stripe asynchronously. \"\"\"\n    logger.info(f\"Processing Stripe event: {pk}\")\n    try:\n        # get the event\n        obj = WebhookEventTrigger.objects.get(pk=pk)\n        # process the event.\n        # internally, this creates a Stripe WebhookEvent Object and invokes the respective Webhooks\n        event = obj.process()\n    except StripeError as exc:\n        logger.error(f\"Failed to process Stripe event: {pk}. Retrying in 60 seconds.\")\n        raise self.retry(exc=exc, countdown=60)  # retry after 60 seconds\n    except WebhookEventTrigger.DoesNotExist as exc:\n        # This can happen in case the celery task got executed before the actual model got saved to the DB\n        raise self.retry(exc=exc, countdown=10)  # retry after 10 seconds\n\n    return event.type or \"Stripe Event Processed\"\n```\n\n```py\n# settings.py\nDJSTRIPE_WEBHOOK_EVENT_CALLBACK = 'callbacks.webhook_event_callback'\n```\n\n## STRIPE_API_HOST (= unset)\n\nIf set, this sets the base API host for Stripe. You may want to set this to, for\nexample, `\"http://localhost:12111\"` if you are running\n[stripe-mock](https://github.com/stripe/stripe-mock).\n\nIf this is set in production (DEBUG=False), a warning will be raised on `manage.py check`.\n\n## Source Code\n\n::: djstripe.settings\nselection:\nfilters: - \"!^_[^_]\"\n"
  },
  {
    "path": "docs/reference/utils.md",
    "content": "# Utilities\n\n\n::: djstripe.utils\n"
  },
  {
    "path": "docs/stripe_elements_js.md",
    "content": "# Integrating Stripe Elements (JS SDK)\n\n!!! tip\n\n    TLDR: If you haven't yet migrated to PaymentIntents, prefer\n    `stripe.createSource()` over `stripe.createToken()` for better\n    compatibility with PaymentMethods.\n\n\n!!! attention\n    A point that can cause confusion when integrating Stripe on the web is\n    that there are multiple generations of frontend JS APIs that use Stripe\n    Elements with stripe js v3.\n\n## In descending order of preference these are:\n\n### [Payment Intents](https://stripe.com/docs/payments/payment-intents) (SCA compliant)\n\nThe newest and preferred way of handling payments, which supports SCA\ncompliance (3D secure etc).\n\n\n### [Charges using stripe.createSource()](https://stripe.com/docs/js/tokens_sources/create_source)\n\nThis creates Source objects within Stripe, and can be used for various different methods of payment (including, but not limited to cards), but isn't SCA compliant.\n\nThe [Card Elements Quickstart JS](https://stripe.com/docs/payments/accept-a-payment-charges?platform=web) example can be used, except use `stripe.createSource` instead of `stripe.createToken` and the `result.source` instead of `result.token`. [`Checkout a working example of this`][tests.apps.example.views.PurchaseSubscriptionView]\n\n\n\n### Charges using stripe.createToken()\n\nThis predates `stripe.createSource`, and creates legacy Card objects within Stripe, which have some compatibility issues with Payment Methods.\n\nIf you're using `stripe.createToken`, see if you can upgrade to\n`stripe.createSource` or ideally to Payment Intents .\n\n!!! tip\n    Checkout [Card Elements Quickstart JS](https://stripe.com/docs/payments/accept-a-payment-charges?platform=web)\n"
  },
  {
    "path": "docs/usage/creating_individual_charges.md",
    "content": "# Creating individual charges\n\nOn the subscriber's customer object, use the [`charge`][djstripe.models.core.Customer.charge] method to generate a\nStripe charge. In this example, we're using the user named `admin` as the\nsubscriber.\n\n```python\nfrom decimal import Decimal\nfrom django.contrib.auth import get_user_model\nfrom djstripe.models import Customer\n\nuser = get_user_model().objects.get(username=\"admin\")\ncustomer, created = Customer.get_or_create(subscriber=user)\ncustomer.charge(Decimal(\"10.00\"), currency=\"usd\")  # Create charge for 10.00 USD\n```\n"
  },
  {
    "path": "docs/usage/creating_usage_record.md",
    "content": "# Create a Stripe Usage Record\n\nUsage records allow you to report customer usage and metrics to Stripe for metered billing of subscription prices\n\nUsage records created using Djstripe's [`UsageRecord.create()`][djstripe.models.billing.UsageRecord.create] method will both create and sync the created `UsageRecord` object with your db.\n\n\n!!! note\n     UsageRecord objects created directly will not sync because Stripe does not expose a way to retrieve UsageRecord objects directly. They can thus only be synced at creation time.\n\n## Code:\n\n```python\nfrom djstripe.models import UsageRecord\n\n# create and sync UsageRecord object\nUsageRecord.create(id=<SUBSCRIPTION_ITEM_ID>, quantity=<SUBSCRIPTION_ITEM_QUANTITY>, timestamp=timestamp)\n\n```\n"
  },
  {
    "path": "docs/usage/local_webhook_testing.md",
    "content": "# Local Webhook Testing\n\nThe [Stripe CLI][cli] allows receiving webhooks events from Stripe on your local machine via a direct connection to Stripe's API.\n\nSet the `--forward-to` flag to the URL of a local webhook endpoint\nyou created via the Django admin or the Stripe Dashboard.\nNew Style `UUID` urls are also supported from `v2.7` onwards.\nFor example:\n\n```sh\nstripe listen --forward-to http://localhost:8000/stripe/webhook/<UUID>\n```\n\nThe [signatures of events sent by Stripe to the webhooks are verified][signatures]\nto prevent third-parties from interacting with the endpoints.\nEvents will be signed with a webhook secret different from existing endpoints\n(because Stripe CLI doesn't require a webhook endpoint to be set up).\nYou can obtain this secret by looking at the output of `stripe listen`\nor by running `stripe listen --print-secret`.\n\nIn order to let dj-stripe know about the secret key to verify the signature,\nit can be passed as an HTTP header;\ndj-stripe looks for a header called `X-Djstripe-Webhook-Secret`:\n\n```sh\nstripe listen \\\n  --forward-to http://localhost:8000/stripe/webhook/<UUID> \\\n  -H \"x-djstripe-webhook-secret: $(stripe listen --print-secret)\"\n```\n\nFrom now on, whenever you make changes on the Stripe Dashboard,\nthe webhook endpoint you specified with `--forward-to` will called\nwith the respective changes.\n\n!!! hint\n    If the webhook secret is not passed to dj-stripe,\n    signature validation will fail with an HTTP status code 400\n    and the message \"Failed to verify header\".\n\nStripe events can now be triggered like so:\n\n```sh\nstripe trigger customer.created\n```\n\n[cli]: https://stripe.com/docs/cli\n[signatures]: https://stripe.com/docs/webhooks/signatures\n"
  },
  {
    "path": "docs/usage/managing_subscriptions.md",
    "content": "# Managing subscriptions and payment sources\n\n## Extending subscriptions\n\nFor your convenience, dj-stripe provides a [`Subscription.extend(*delta*)`][djstripe.models.billing.Subscription.extend] method\n\n\nSubscriptions can be extended by using the `Subscription.extend` method,\nwhich takes a positive `timedelta` as its only property. This method is\nuseful if you want to offer time-cards, gift-cards, or some other\nexternal way of subscribing users or extending subscriptions, while\nkeeping the billing handling within Stripe.\n\n!!! warning\n\n    Subscription extensions are achieved by manipulating the `trial_end` of\n    the subscription instance, which means that Stripe will change the\n    status to `trialing`.\n"
  },
  {
    "path": "docs/usage/manually_syncing_with_stripe.md",
    "content": "# Manually syncing data with Stripe\n\nIf you're using dj-stripe's webhook handlers then data will be\nautomatically synced from Stripe to the Django database, but in some\ncircumstances you may want to manually sync Stripe API data as well.\n\n## Command line\n\nYou can sync your database with stripe using the management command\n[`djstripe_sync_models`][djstripe.management.commands.djstripe_sync_models], e.g. to populate an empty database from an\nexisting Stripe account.\n```bash\n\n    ./manage.py djstripe_sync_models\n```\nWith no arguments this will sync all supported models for all in database API Keys , or a list of\nmodels to sync can also be provided.\n```bash\n    ./manage.py djstripe_sync_models Invoice Subscription\n```\nNote that this may be redundant since we recursively sync related\nobjects.\n\nA list of models to sync can also be provided along with the API Keys.\n```bash\n    ./manage.py djstripe_sync_models Invoice Subscription --api-keys sk_test_XXX sk_test_YYY\n```\nThis will sync all the Invoice and Subscription data for the given API Keys. Please note that the API Keys sk_test_YYY and sk_test_XXX need to be in the database.\n\nYou can manually reprocess events using the management commands\n[`djstripe_process_events`][djstripe.management.commands.djstripe_process_events]. By default this processes all events, but\noptions can be passed to limit the events processed. Note the Stripe API\ndocuments a limitation where events are only guaranteed to be available\nfor 30 days.\n\n```bash\n    # all events\n    ./manage.py djstripe_process_events\n    # failed events (events with pending webhooks or where all webhook delivery attempts failed)\n    ./manage.py djstripe_process_events --failed\n    # filter by event type (all payment_intent events in this example)\n    ./manage.py djstripe_process_events --type payment_intent.*\n    # specific events by ID\n    ./manage.py djstripe_process_events --ids evt_foo evt_bar\n    # more output for debugging processing failures\n    ./manage.py djstripe_process_events -v 2\n```\n\n## In Code\n\nTo sync in code, for example if you write to the Stripe API and want to\nwork with the resulting dj-stripe object without having to wait for the\nwebhook trigger.\n\nThis can be done using the classmethod [`sync_from_stripe_data`][djstripe.models.base.StripeModel.sync_from_stripe_data] that\nexists on all dj-stripe model classes.\n\nE.g. creating a product using the Stripe API, and then syncing the API\nreturn data to Django using dj-stripe:\n"
  },
  {
    "path": "docs/usage/subscribing_customers.md",
    "content": "# Subscribing a customer to one or more prices (or plans)\n\n## Recommended Approach\n\n```python\n# Recommended Approach to use items dict with Prices\n## This will subscribe <customer> to both <price_1> and <price_2>\nprice_1 = Price.objects.get(nickname=\"one_price\")\nprice_2 = Price.objects.get(nickname=\"two_price\")\ncustomer = Customer.objects.first()\ncustomer.subscribe(items=[{\"price\": price_1}, {\"price\": price_2}])\n\n## This will subscribe <customer> to <price_1>\nprice_1 = Price.objects.get(nickname=\"one_price\")\ncustomer = Customer.objects.first()\ncustomer.subscribe(items=[{\"price\": price_1}])\n\n```\n\n## Alternate Approach 1 (with legacy Plans)\n\n```python\n## (Alternate Approach) This will subscribe <customer> to <price_1>\nprice_1 = Price.objects.get(nickname=\"one_price\")\ncustomer = Customer.objects.first()\ncustomer.subscribe(price=price_1)\n\n# If you still use legacy Plans...\n## This will subscribe <customer> to both <plan_1> and <plan_2>\nplan_1 = Plan.objects.get(nickname=\"one_plan\")\nplan_2 = Plan.objects.get(nickname=\"two_plan\")\ncustomer = Customer.objects.first()\ncustomer.subscribe(items=[{\"plan\": plan_1}, {\"plan\": plan_2}])\n\n## This will subscribe <customer> to <plan_1>\nplan_1 = Plan.objects.get(nickname=\"one_plan\")\ncustomer = Customer.objects.first()\ncustomer.subscribe(items=[{\"plan\": plan_1}])\n```\n\n## Alternate Approach 2\n\n```python\n\n## (Alternate Approach) This will subscribe <customer> to <plan_1>\nplan_1 = Plan.objects.get(nickname=\"one_plan\")\ncustomer = Customer.objects.first()\ncustomer.subscribe(plan=plan_1)\n```\n\nHowever in some cases `subscribe()` might not\nsupport all the arguments you need for your implementation. When this\nhappens you can just call the official `stripe.Customer.subscribe()`.\n\n!!! tip\n    Check out the following examples:\n\n    -   [`form_valid view example`][tests.apps.example.views.PurchaseSubscriptionView.form_valid]\n    -   [`djstripe.models.Customer.add_payment_method`][djstripe.models.core.Customer.add_payment_method]\n\n\n    Note that PaymentMethods can be used instead of Cards/Source by\n    substituting\n\n    ```py\n    # Add the payment method customer's default\n    customer.add_payment_method(payment_method)\n    ```\n\n    instead of\n\n    ```py\n    # Add the source as the customer's default card\n    customer.add_card(stripe_source)\n    ```\n\n    in the above example.\n"
  },
  {
    "path": "docs/usage/using_stripe_checkout.md",
    "content": "# Create a Stripe Checkout Session\n\n\nFor your convenience, dj-stripe has provided an example implementation on how to use [`Checkouts`][tests.apps.example.views.CreateCheckoutSessionView]\n\n\n\nPlease note that in order for dj-stripe to create a link between your `customers` and your `subscribers`, you need to add the `DJSTRIPE_SUBSCRIBER_CUSTOMER_KEY` key to the `metadata` parameter of `Checkout`. This has also been demonstrated in the aforementioned [example][tests.apps.example.views.CreateCheckoutSessionView]\n"
  },
  {
    "path": "docs/usage/using_with_docker.md",
    "content": "# Using with Docker\n\nA [Docker image](https://hub.docker.com/r/stripe/stripe-cli) allows you to run the Stripe CLI in a container.\n\nHere is a sample `docker-compose.yaml` file that sets up all the services to use `Stripe CLI` in a `dockerised django container (with djstripe)`\n\n\n```yaml\nversion: \"3.9\"\n\n\nvolumes:\n    postgres-data: {}\n\n\nservices:\n\n  db:\n    image: postgres:12\n    volumes:\n      - postgres-data:/var/lib/postgresql/data\n    environment:\n      - POSTGRES_DB=random_number\n      - POSTGRES_USER=root\n      - POSTGRES_PASSWORD=random_number\n\n\n  web:\n    build:\n      context: .\n      dockerfile: <PATH_TO_DOCKERFILE>\n    command: python manage.py runserver 0.0.0.0:8000\n    volumes:\n      - .:/app\n    ports:\n      - \"8000:8000\"\n    depends_on:\n      - db\n    environment:\n        # Stripe specific keys\n        - STRIPE_PUBLIC_KEY=pk_test_******\n        - STRIPE_SECRET_KEY=sk_test_******\n        - DJSTRIPE_TEST_WEBHOOK_SECRET=whsec_******\n\n        # Database Specific Settings\n        - DJSTRIPE_TEST_DB_VENDOR=postgres\n        - DJSTRIPE_TEST_DB_PORT=5432\n        - DJSTRIPE_TEST_DB_USER=root\n        - DJSTRIPE_TEST_DB_NAME=random_number\n        - DJSTRIPE_TEST_DB_PASS=random_number\n        - DJSTRIPE_TEST_DB_HOST=db\n\n  stripe:\n    image: stripe/stripe-cli:v1.7.4\n    # In case Stripe CLI is used to perform local webhook testing, set x-djstripe-webhook-secret custom header to output of Stripe CLI.\n    command: [\"listen\", \"-H\", \"x-djstripe-webhook-secret: whsec_******\", \"--forward-to\", \"http://web:8000/djstripe/webhook/\"]\n    depends_on:\n      - web\n    environment:\n      - STRIPE_API_KEY=sk_test_******\n      - STRIPE_DEVICE_NAME=djstripe_docker\n\n```\n\n!!! note\n\n    In case the `Stripe CLI` is used to perform local webhook testing, set `x-djstripe-webhook-secret` Custom Header in Stripe `listen` to the `Webhook Signing Secret` output of `Stripe CLI`. That is what Stripe expects and uses to create the `stripe-signature` header.\n"
  },
  {
    "path": "docs/usage/webhooks.md",
    "content": "# Using Stripe Webhooks\n\n## Setting up a new webhook endpoint in dj-stripe\n\nAs of dj-stripe 2.7.0, dj-stripe can create its own webhook endpoints on Stripe from the\nDjango administration.\n\nCreate a new webhook endpoint from the Django administration by going to dj-stripe\n-> Webhook endpoints -> Add webhook endpoint (or `/admin/djstripe/webhookendpoint/add/`).\n\nFrom there, you can choose an account to create the endpoint for.\nIf no account is chosen, the default Stripe API key will be used to create the endpoint.\nYou can also choose to create the endpoint in test mode or live mode.\n\nYou may want to change the base URL of the endpoint. This field will be prefilled with\nthe current site. If you're running on the local development server, you may see\n`http://localhost:8000` or similar in there. Stripe won't let you save webhook endpoints\nwith such a value, so you will want to change it to a real website URL.\n\nWhen saved from the admin, the endpoint will be created in Stripe with a dj-stripe\nspecific UUID which will be part of the URL, making it impossible to guess externally\nby brute-force.\n\n## Legacy setup\n\nBefore dj-stripe 2.7.0, dj-stripe included a global webhook endpoint URL, which uses the\nsetting [`DJSTRIPE_WEBHOOK_SECRET`][djstripe.settings.DjstripeSettings.WEBHOOK_SECRET]\nto validate incoming webhooks.\n\nThis is not recommended as it makes the URL guessable, and may be removed in the future.\n\n## Extra configuration\n\ndj-stripe provides the following settings to tune how your webhooks work:\n\n-   [`DJSTRIPE_WEBHOOK_VALIDATION`][djstripe.settings.DjstripeSettings.WEBHOOK_VALIDATION]\n-   [`DJSTRIPE_WEBHOOK_TOLERANCE`][djstripe.settings.DjstripeSettings.WEBHOOK_TOLERANCE]\n-   [`DJSTRIPE_WEBHOOK_EVENT_CALLBACK`][djstripe.settings.DjstripeSettings.WEBHOOK_EVENT_CALLBACK]\n\n## Advanced usage\n\ndj-stripe comes with native support for webhooks as event listeners.\n\nEvents allow you to do things like sending an email to a customer when\nhis payment has\n[failed](https://stripe.com/docs/receipts#failed-payment-alerts)\nor trial period is ending.\n\nThis is how you use them:\n\n```python\n    from djstripe import webhooks\n\n    @webhooks.handler(\"customer.subscription.trial_will_end\")\n    def my_handler(event, **kwargs):\n        print(\"We should probably notify the user at this point\")\n```\n\nYou can handle all events related to customers like this:\n\n```py\n    from djstripe import webhooks\n\n    @webhooks.handler(\"customer\")\n    def my_handler(event, **kwargs):\n        print(\"We should probably notify the user at this point\")\n```\n\nYou can also handle different events in the same handler:\n\n```py\nfrom djstripe import webhooks\n\n@webhooks.handler(\"price\", \"product\")\ndef my_handler(event, **kwargs):\n    print(\"Triggered webhook \" + event.type)\n```\n\n!!! warning\n\n    In order to get registrations picked up, you need to put them in a\n    module that is imported like models.py or make sure you import it manually.\n\nWebhook event creation and processing is now wrapped in a\n`transaction.atomic()` block to better handle webhook errors. This will\nprevent any additional database modifications you may perform in your\ncustom handler from being committed should something in the webhook\nprocessing chain fail. You can also take advantage of Django's\n`transaction.on_commit()` function to only perform an action if the\ntransaction successfully commits (meaning the Event processing worked):\n\n```py\nfrom django.db import transaction\nfrom djstripe import webhooks\n\ndef do_something():\n    pass  # send a mail, invalidate a cache, fire off a Celery task, etc.\n\n@webhooks.handler(\"price\", \"product\")\ndef my_handler(event, **kwargs):\n    transaction.on_commit(do_something)\n```\n\n## Official documentation\n\nStripe docs for types of Events:\n<https://stripe.com/docs/api/events/types>\n\nStripe docs for Webhooks: <https://stripe.com/docs/webhooks>\n\nDjango docs for transactions:\n<https://docs.djangoproject.com/en/dev/topics/db/transactions/#performing-actions-after-commit>\n"
  },
  {
    "path": "manage.py",
    "content": "#!/usr/bin/env python\nimport os\nimport sys\n\ntry:\n    from django.core.management import execute_from_command_line\nexcept ImportError as exc:\n    raise ImportError(\n        \"Couldn't import Django. \"\n        \"Run `poetry shell` to activate a virtual environment first.\"\n    ) from exc\n\n\ndef main():\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"tests.settings\")\n    execute_from_command_line(sys.argv)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: Dj-Stripe\nsite_url: https://dj-stripe.github.io/dj-stripe/\nsite_description: Django + Stripe Made Easy\nsite_author: Dj-Stripe Team\n\nrepo_url: https://github.com/dj-stripe/dj-stripe/\n\ntheme:\n  name: readthedocs\n  features:\n    - search.suggest\n    - search.highligh\n\nmarkdown_extensions:\n  - admonition\n  - codehilite\n  - pymdownx.highlight\n  - pymdownx.inlinehilite\n  - pymdownx.superfences\n  - pymdownx.snippets\n\nplugins:\n  - autorefs\n  - search\n  - mkdocstrings:\n      default_handler: python\n      handlers:\n        python:\n          options:\n            # show_root_heading: true\n            show_object_full_path: true\n            show_category_heading: true\n            show_if_no_docstring: true\n          setup_commands:\n            - import os\n            - import sys\n            - import django\n            - sys.path.insert(0, os.path.abspath(\".\"))\n            - os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"tests.settings\")\n            - django.setup()\n      watch:\n        - .\n      enable_inventory: true\n  - mike:\n      canonical_version: \"2.7\"\n\nnav:\n  - Home: README.md\n  - Sponsors: project/sponsors.md\n  - Getting Started:\n      - Installation: installation.md\n      - Managing Stripe API Keys: api_keys.md\n      - A note on Stripe API Versions: api_versions.md\n      - Integrating Stripe Elements: stripe_elements_js.md\n  - Release notes:\n      - dj-stripe 2.7 release notes: history/2_7_0.md\n      - dj-stripe 2.6 release notes: history/2_6_0.md\n      - dj-stripe 2.5 release notes: history/2_5_0.md\n      - dj-stripe 2.4.1 release notes: history/2_4_x.md\n      - dj-stripe 2.4 release notes: history/2_4_0.md\n      - dj-stripe 2.0 ~ 2.3 release notes: history/2_x.md\n      - dj-stripe 1.x release notes: history/1_x.md\n      - dj-stripe 0.x release notes: history/0_x.md\n  - Usage:\n      - Using Stripe Webhooks: usage/webhooks.md\n      - Subscribing a customer to a plan: usage/subscribing_customers.md\n      - Managing subscriptions and payment sources: usage/managing_subscriptions.md\n      - Manually syncing data with Stripe: usage/manually_syncing_with_stripe.md\n      - Creating individual charges: usage/creating_individual_charges.md\n      - Creating Usage Records: usage/creating_usage_record.md\n      - Using Stripe Checkout: usage/using_stripe_checkout.md\n      - Using with Docker: usage/using_with_docker.md\n      - Development with local webhooks: usage/local_webhook_testing.md\n  - Project:\n      - Contributing: project/contributing.md\n      - Test Fixtures: project/test_fixtures.md\n      - Credits: project/authors.md\n      - Support: project/support.md\n      - Release Process: project/release_process.md\n  - Reference:\n      - Enumerations: reference/enums.md\n      - Managers: reference/managers.md\n      - Models: reference/models.md\n      - Settings: reference/settings.md\n      - Utilities: reference/utils.md\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.poetry]\nname = \"dj-stripe\"\nversion = \"2.8.0-dev.0\"\ndescription = \"Django + Stripe made easy\"\nlicense = \"MIT\"\nauthors = [\n    \"Alexander Kavanaugh <alex@kavdev.io>\",\n    \"Jerome Leclanche <jerome@leclan.ch>\",\n]\nreadme = \"docs/README.md\"\nhomepage = \"https://dj-stripe.dev\"\nrepository = \"https://github.com/dj-stripe/dj-stripe\"\ndocumentation = \"https://dj-stripe.github.io/dj-stripe/\"\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Environment :: Web Environment\",\n    \"Intended Audience :: Developers\",\n    \"Topic :: Office/Business :: Financial\",\n    \"Topic :: Software Development :: Libraries :: Python Modules\",\n    \"Framework :: Django\",\n    \"Framework :: Django :: 3.2\",\n    \"Framework :: Django :: 4.0\",\n    \"Framework :: Django :: 4.1\",\n]\n\npackages = [\n    { include = \"djstripe\" }\n]\ninclude = [\n    \"AUTHORS.md\",\n    \"CONTRIBUTING.md\",\n    \"HISTORY.md\",\n    \"LICENSE\",\n]\nexclude = [\n    \"manage.py\"\n]\n\n[tool.poetry.urls]\n\"Funding\" = \"https://github.com/sponsors/dj-stripe\"\n\n\n[tool.poetry.dependencies]\npython = \"^3.8.0\"\ndjango = \">=3.2\"\nstripe = \">=2.48.0,<5.0.0\"\npsycopg2 = { version = \"^2.8.5\", optional = true }\nmysqlclient = { version = \">=1.4.0\", optional = true }\n\n[tool.poetry.group.dev]\noptional = true\n[tool.poetry.group.dev.dependencies]\nblack = \">=22.6.0\"\nisort = \">=5.10.1\"\npytest = \">=7.1.2\"\npytest-django = \">=4.5.2\"\nmypy = \">=0.971\"\n\n# flake8 = \">=5.0.4\"\n# Why is flake8 commented out?\n# The short version: flake8 is pinned to an old version of importlib-metadata\n# which clashes with other dependencies (such as mkdocs in our case).\n#\n# For the longer version, you have to look at these links:\n# https://github.com/PyCQA/flake8/pull/1438\n# https://github.com/PyCQA/flake8/issues/1474\n# https://github.com/PyCQA/flake8/issues/1503\n# https://github.com/PyCQA/flake8/issues/1522\n# https://github.com/PyCQA/flake8/issues/1526\n#\n# So the flake8 team ran into a deprecation path they didn't like, and got so\n# upset about it that… it seems they just decided to never unpin it, even though\n# the maintainers of importlib-metadata have since fixed the issue flake8 was\n# complaining about.\n#\n# Now, they're getting issue, after issue, after issue opened on Github because:\n# - importlib-metadata 4.4+ is required by many popular libs (markdown, sphinx…)\n# - The pin looks unnecessary at first glance\n# - They don't document the reasoning for their choice\n# - There probably ISN'T any valid reasoning for their choice\n# - They are closing & locking any reference to this as off-topic\n# - They refuse to address the issue, and complain about anyone who brings it up.\n#\n# The reasonable approach would of course be to remove the useless pin, but by\n# doing so they fear they would \"increase the maintenance burden\" because they're\n# a small team (in other words, they're worried about more bug tracker activity).\n# Obviously this makes zero sense since the issue is gone, and they are instead\n# generating lots of bug tracker activity, which they're promptly ignoring.\n#\n# So they're wasting my time, and everybody else's, including yours who's reading\n# this little rant right now. But I'd like this to serve as a lesson on how not\n# to maintain a popular open source project.\n# Many OSS devs are finding out that, with increased popularity comes increased\n# responsibility. If you don't want the responsibility, you are not forced to\n# continue maintaining the library; but as long as you do, your reach puts you in\n# a position of power.\n# Wasting your users' time is abusing that position of power.\n# Think of the underpaid bureaucrat who, by the stroke of a pen, makes or breaks\n# the lives of others. Who was a little too grumpy at work and decided to take it\n# out on someone and waste hours of their life by making illogical demands, just\n# out of spite.\n#\n# I'm grateful flake8 exists. But it's not a useful enough tool to allow it to\n# waste our time like this, and it's largely been displaced by the other tools:\n# mypy, isort, and black. So let's stick to those instead.\npre-commit = \"^3.0.4\"\n\n[tool.poetry.group.docs]\noptional = true\n[tool.poetry.group.docs.dependencies]\nmkdocs = \">=1.3.1\"\nmkdocs-material = \">=8.4.2\"\nmkdocs-autorefs = \">=0.4.1\"\nmkdocstrings = {extras = [\"python\"], version = \">=0.19.0\"}\nmike = \">=1.1.2\"\njinja2 = \"<3.1.0\"\n\n\n[tool.poetry.group.ci]\noptional = true\n[tool.poetry.group.ci.dependencies]\ncoverage = {version = \"^6.5.0\", extras = [\"toml\"]}\ntox = \"^3.26.0\"\ntox-gh-actions = \"^2.10.0\"\n\n[tool.poetry.extras]\npostgres = [\"psycopg2\"]\nmysql = [\"mysqlclient\"]\n\n[tool.isort]\nprofile = \"black\"\n\n[build-system]\nrequires = [\"poetry_core>=1.1.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "\"\"\"\nA Fake or multiple fakes for each stripe object.\n\nOriginally collected using API VERSION 2015-07-28.\nUpdated to API VERSION 2016-03-07 with bogus fields.\n\"\"\"\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport datetime\nimport json\nimport logging\nimport os\nfrom copy import deepcopy\nfrom pathlib import Path\nfrom typing import Any, Dict\n\nfrom django.core.exceptions import ObjectDoesNotExist\nfrom django.db import models\nfrom django.utils import dateformat\n\nfrom djstripe.utils import get_timezone_utc\nfrom djstripe.webhooks import TEST_EVENT_ID\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"tests.settings\")\nlogger = logging.getLogger(__name__)\n\nFUTURE_DATE = datetime.datetime(2100, 4, 30, tzinfo=get_timezone_utc())\n\nFIXTURE_DIR_PATH = Path(__file__).parent.joinpath(\"fixtures\")\n\n\nclass AssertStripeFksMixin:\n    def _get_field_str(self, field) -> str:\n        if isinstance(field, models.OneToOneRel):\n            if field.parent_link:\n                return \"\"\n            else:\n                reverse_id_name = str(field.remote_field.foreign_related_fields[0])\n                return (\n                    reverse_id_name.replace(\"djstripe_id\", field.name)\n                    + \" (related name)\"\n                )\n\n        elif isinstance(field, models.ForeignKey):\n            return str(field)\n\n        else:\n            return \"\"\n\n    def assert_fks(self, obj, expected_blank_fks, processed_stripe_ids=None):\n        \"\"\"\n        Recursively walk through fks on obj, asserting they're not-none\n        :param obj:\n        :param expected_blank_fks: fields that are expected to be None\n        :param processed_stripe_ids: set of objects ids already processed\n        :return:\n        \"\"\"\n\n        if processed_stripe_ids is None:\n            processed_stripe_ids = set()\n\n        processed_stripe_ids.add(obj.id)\n\n        for field in obj._meta.get_fields():\n            field_str = self._get_field_str(field)\n            if not field_str or field_str.endswith(\".djstripe_owner_account\"):\n                continue\n\n            try:\n                field_value = getattr(obj, field.name)\n            except ObjectDoesNotExist:\n                field_value = None\n\n            if field_str in expected_blank_fks:\n                self.assertIsNone(field_value, field_str)\n            else:\n                self.assertIsNotNone(field_value, field_str)\n\n                if field_value.id not in processed_stripe_ids:\n                    # recurse into the object if it's not already been checked\n                    self.assert_fks(\n                        field_value, expected_blank_fks, processed_stripe_ids\n                    )\n\n                logger.warning(\"checked %s\", field_str)\n\n\ndef load_fixture(filename):\n    with FIXTURE_DIR_PATH.joinpath(filename).open(\"r\") as f:\n        return json.load(f)\n\n\ndef datetime_to_unix(datetime_):\n    return int(dateformat.format(datetime_, \"U\"))\n\n\nclass StripeItem(dict):\n    \"\"\"Flexible class built to mock any generic Stripe object.\n\n    Implements object access + deletion methods to match the behavior\n    of Stripe's library, which allows both object + dictionary access.\n\n    Has a delete method since (most) Stripe objects can be deleted.\n    \"\"\"\n\n    def __getattr__(self, name):\n        \"\"\"Give StripeItem normal object access to match Stripe behavior.\"\"\"\n        if name in self:\n            return self[name]\n        else:\n            raise AttributeError(\"No such attribute: \" + name)\n\n    def __setattr__(self, name, value):\n        self[name] = value\n\n    def __delattr__(self, name):\n        if name in self:\n            del self[name]\n        else:\n            raise AttributeError(\"No such attribute: \" + name)\n\n    def delete(self) -> bool:\n        \"\"\"Superficial mock that adds a deleted attribute.\"\"\"\n        self.deleted = True\n\n        return self.deleted\n\n    @classmethod\n    def class_url(cls):\n        return \"/v1/test-items/\"\n\n    def instance_url(self):\n        \"\"\"Superficial mock that emulates instance_url.\"\"\"\n        id = self.get(\"id\")\n        base = self.class_url()\n        return \"%s/%s\" % (base, id)\n\n    def request(self, method, url, params) -> Dict:\n        \"\"\"Superficial mock that emulates request method.\"\"\"\n        assert method == \"post\"\n        for key, value in params.items():\n            self.__setattr__(key, value)\n        return self\n\n\nclass StripeList(dict):\n    \"\"\"Mock a generic Stripe Iterable.\n\n    It has the relevant attributes of a stripe iterable (has_more, data).\n\n    This mock is important so we can use stripe's `list` method in our testing.\n    StripeList.list() will return the StripeList.\n\n    Additionally, iterating over instances of MockStripeIterable will iterate over\n    the data attribute, just like Stripe iterables.\n\n    Attributes:\n        has_more: mock has_more flag. Default False.\n        **kwargs: all of the fields of the stripe object, generally as a dictionary.\n    \"\"\"\n\n    object = \"list\"\n    url = \"/v1/fakes\"\n    has_more = False\n\n    def __getattr__(self, name):\n        \"\"\"Give StripeItem normal object access to match Stripe behavior.\"\"\"\n        if name in self:\n            return self[name]\n        else:\n            raise AttributeError(\"No such attribute: \" + name)\n\n    def __setattr__(self, name, value):\n        self[name] = value\n\n    def __delattr__(self, name):\n        if name in self:\n            del self[name]\n        else:\n            raise AttributeError(\"No such attribute: \" + name)\n\n    def __iter__(self) -> Any:\n        \"\"\"Make StripeList an iterable, to match the Stripe iterable behavior.\"\"\"\n        self.iter_copy = self.data.copy()\n        return self\n\n    def __next__(self) -> StripeItem:\n        \"\"\"Define iteration for StripeList.\"\"\"\n        if len(self.iter_copy) > 0:\n            return self.iter_copy.pop(0)\n        else:\n            raise StopIteration()\n\n    def list(self, **kwargs: Any) -> \"StripeList\":\n        \"\"\"Add a list method to the StripeList which returns itself.\n\n        list() accepts arbitrary kwargs, be careful is you expect the\n        argument-accepting functionality of Stripe's list() method.\n        \"\"\"\n        return self\n\n    def auto_paging_iter(self) -> \"StripeList\":\n        \"\"\"Add an auto_paging_iter method to the StripeList which returns itself.\n\n        The StripeList is an iterable, so this mimics the real behavior.\n        \"\"\"\n        return self\n\n    @property\n    def total_count(self):\n        return len(self.data)\n\n\nclass ExternalAccounts(object):\n    def __init__(self, external_account_fakes):\n        self.external_account_fakes = external_account_fakes\n\n    def create(self, source, api_key=None):\n        for fake_external_account in self.external_account_fakes:\n            if fake_external_account[\"id\"] == source:\n                return fake_external_account\n\n    def retrieve(self, id, expand=None):\n        for fake_external_account in self.external_account_fakes:\n            if fake_external_account[\"id\"] == id:\n                return fake_external_account\n\n    def list(self, **kwargs):\n        return StripeList(data=self.external_account_fakes)\n\n\nclass AccountDict(dict):\n    def save(self, idempotency_key=None):\n        return self\n\n    @property\n    def external_accounts(self):\n        return ExternalAccounts(\n            external_account_fakes=self[\"external_accounts\"][\"data\"]\n        )\n\n    def create(self):\n        from djstripe.models import Account\n\n        return Account.sync_from_stripe_data(self)\n\n\nFAKE_STANDARD_ACCOUNT = AccountDict(\n    load_fixture(\"account_standard_acct_1Fg9jUA3kq9o1aTc.json\")\n)\n\n# Stripe Platform Account to which the STRIPE_SECRET_KEY belongs to\nFAKE_PLATFORM_ACCOUNT = deepcopy(FAKE_STANDARD_ACCOUNT)\nFAKE_PLATFORM_ACCOUNT[\"settings\"][\"dashboard\"][\"display_name\"] = \"djstripe-platform\"\n\nFAKE_CUSTOM_ACCOUNT = AccountDict(\n    load_fixture(\"account_custom_acct_1IuHosQveW0ONQsd.json\")\n)\n\nFAKE_EXPRESS_ACCOUNT = AccountDict(\n    load_fixture(\"account_express_acct_1IuHosQveW0ONQsd.json\")\n)\n\n\nFAKE_BALANCE_TRANSACTION = load_fixture(\n    \"balance_transaction_txn_fake_ch_fakefakefakefakefake0001.json\"\n)\n\nFAKE_BALANCE_TRANSACTION_II = {\n    \"id\": \"txn_16g5h62eZvKYlo2CQ2AHA89s\",\n    \"object\": \"balance_transaction\",\n    \"amount\": 65400,\n    \"available_on\": 1441670400,\n    \"created\": 1441079064,\n    \"currency\": \"usd\",\n    \"description\": None,\n    \"fee\": 1927,\n    \"fee_details\": [\n        {\n            \"amount\": 1927,\n            \"currency\": \"usd\",\n            \"type\": \"stripe_fee\",\n            \"description\": \"Stripe processing fees\",\n            \"application\": None,\n        }\n    ],\n    \"net\": 63473,\n    \"source\": \"ch_16g5h62eZvKYlo2CMRXkSqa0\",\n    \"sourced_transfers\": {\n        \"object\": \"list\",\n        \"total_count\": 0,\n        \"has_more\": False,\n        \"url\": \"/v1/transfers?source_transaction=ch_16g5h62eZvKYlo2CMRXkSqa0\",\n        \"data\": [],\n    },\n    \"status\": \"pending\",\n    \"type\": \"charge\",\n}\n\nFAKE_BALANCE_TRANSACTION_III = {\n    \"id\": \"txn_16g5h62eZvKYlo2CQ2AHA89s\",\n    \"object\": \"balance_transaction\",\n    \"amount\": 2000,\n    \"available_on\": 1441670400,\n    \"created\": 1441079064,\n    \"currency\": \"usd\",\n    \"description\": None,\n    \"fee\": 1927,\n    \"fee_details\": [\n        {\n            \"amount\": 1927,\n            \"currency\": \"usd\",\n            \"type\": \"stripe_fee\",\n            \"description\": \"Stripe processing fees\",\n            \"application\": None,\n        }\n    ],\n    \"net\": 73,\n    \"source\": \"ch_16g5h62eZvKYlo2CMRXkSqa0\",\n    \"sourced_transfers\": {\n        \"object\": \"list\",\n        \"total_count\": 0,\n        \"has_more\": False,\n        \"url\": \"/v1/transfers?source_transaction=ch_16g5h62eZvKYlo2CMRXkSqa0\",\n        \"data\": [],\n    },\n    \"status\": \"pending\",\n    \"type\": \"charge\",\n}\n\nFAKE_BALANCE_TRANSACTION_IV = {\n    \"id\": \"txn_16g5h62eZvKYlo2CQ2AHA89s\",\n    \"object\": \"balance_transaction\",\n    \"amount\": 19010,\n    \"available_on\": 1441670400,\n    \"created\": 1441079064,\n    \"currency\": \"usd\",\n    \"description\": None,\n    \"fee\": 1927,\n    \"fee_details\": [\n        {\n            \"amount\": 1927,\n            \"currency\": \"usd\",\n            \"type\": \"stripe_fee\",\n            \"description\": \"Stripe processing fees\",\n            \"application\": None,\n        }\n    ],\n    \"net\": 17083,\n    \"source\": \"ch_16g5h62eZvKYlo2CMRXkSqa0\",\n    \"sourced_transfers\": {\n        \"object\": \"list\",\n        \"total_count\": 0,\n        \"has_more\": False,\n        \"url\": \"/v1/transfers?source_transaction=ch_16g5h62eZvKYlo2CMRXkSqa0\",\n        \"data\": [],\n    },\n    \"status\": \"pending\",\n    \"type\": \"charge\",\n}\n\n\nclass LegacySourceDict(dict):\n    def delete(self):\n        return self\n\n\nclass BankAccountDict(LegacySourceDict):\n    pass\n\n\nFAKE_BANK_ACCOUNT = {\n    \"id\": \"ba_16hTzo2eZvKYlo2CeSjfb0tS\",\n    \"object\": \"bank_account\",\n    \"account_holder_name\": None,\n    \"account_holder_type\": None,\n    \"bank_name\": \"STRIPE TEST BANK\",\n    \"country\": \"US\",\n    \"currency\": \"usd\",\n    \"fingerprint\": \"1JWtPxqbdX5Gamtc\",\n    \"last4\": \"6789\",\n    \"routing_number\": \"110000000\",\n    \"status\": \"new\",\n}\n\nFAKE_BANK_ACCOUNT_II = {\n    \"id\": \"ba_17O4Tz2eZvKYlo2CMYsxroV5\",\n    \"object\": \"bank_account\",\n    \"account_holder_name\": None,\n    \"account_holder_type\": None,\n    \"bank_name\": None,\n    \"country\": \"US\",\n    \"currency\": \"usd\",\n    \"fingerprint\": \"1JWtPxqbdX5Gamtc\",\n    \"last4\": \"6789\",\n    \"routing_number\": \"110000000\",\n    \"status\": \"new\",\n}\n\n# Stripe Customer Bank Account Payment Source\nFAKE_BANK_ACCOUNT_SOURCE = BankAccountDict(\n    load_fixture(\"bank_account_ba_fakefakefakefakefake0003.json\")\n)\nFAKE_BANK_ACCOUNT_IV = BankAccountDict(\n    load_fixture(\"bank_account_ba_fakefakefakefakefake0004.json\")\n)\n\n\nclass CardDict(LegacySourceDict):\n    pass\n\n\nFAKE_CARD = CardDict(load_fixture(\"card_card_fakefakefakefakefake0001.json\"))\n\nFAKE_CARD_II = CardDict(load_fixture(\"card_card_fakefakefakefakefake0002.json\"))\n\nFAKE_CARD_III = CardDict(load_fixture(\"card_card_fakefakefakefakefake0003.json\"))\n\n# Stripe Custom Connected Account Card Payout Source\nFAKE_CARD_IV = CardDict(load_fixture(\"card_card_fakefakefakefakefake0004.json\"))\n\n\nclass SourceDict(dict):\n    def detach(self):\n        self.pop(\"customer\")\n        self.update({\"status\": \"consumed\"})\n        return self\n\n\n# Attached, chargeable source\nFAKE_SOURCE = SourceDict(load_fixture(\"source_src_fakefakefakefakefake0001.json\"))\n\n# Detached, consumed source\nFAKE_SOURCE_II = SourceDict(\n    {\n        \"id\": \"src_1DuuGjkE6hxDGaasasjdlajl\",\n        \"object\": \"source\",\n        \"amount\": None,\n        \"card\": {\n            \"address_line1_check\": None,\n            \"address_zip_check\": \"pass\",\n            \"brand\": \"Visa\",\n            \"country\": \"US\",\n            \"cvc_check\": \"pass\",\n            \"dynamic_last4\": None,\n            \"exp_month\": 10,\n            \"exp_year\": 2029,\n            \"fingerprint\": \"TmOrYzPdAoO6YFNB\",\n            \"funding\": \"credit\",\n            \"last4\": \"4242\",\n            \"name\": None,\n            \"three_d_secure\": \"optional\",\n            \"tokenization_method\": None,\n        },\n        \"client_secret\": \"src_client_secret_ENg5dyB1KTXCAEJGJQWEf67X\",\n        \"created\": 1548046215,\n        \"currency\": None,\n        \"flow\": \"none\",\n        \"livemode\": False,\n        \"metadata\": {\"djstripe_test_fake_id\": \"src_fakefakefakefakefake0002\"},\n        \"owner\": {\n            \"address\": {\n                \"city\": None,\n                \"country\": None,\n                \"line1\": None,\n                \"line2\": None,\n                \"postal_code\": \"90210\",\n                \"state\": None,\n            },\n            \"email\": None,\n            \"name\": None,\n            \"phone\": None,\n            \"verified_address\": None,\n            \"verified_email\": None,\n            \"verified_name\": None,\n            \"verified_phone\": None,\n        },\n        \"statement_descriptor\": None,\n        \"status\": \"consumed\",\n        \"type\": \"card\",\n        \"usage\": \"reusable\",\n    }\n)\n\n\nFAKE_PAYMENT_INTENT_I = load_fixture(\"payment_intent_pi_fakefakefakefakefake0001.json\")\n\nFAKE_PAYMENT_INTENT_II = deepcopy(FAKE_PAYMENT_INTENT_I)\nFAKE_PAYMENT_INTENT_II[\"customer\"] = \"cus_4UbFSo9tl62jqj\"  # FAKE_CUSTOMER_II\n\nFAKE_PAYMENT_INTENT_DESTINATION_CHARGE = load_fixture(\n    \"payment_intent_pi_destination_charge.json\"\n)\n\n\nclass PaymentMethodDict(dict):\n    def detach(self):\n        self.pop(\"customer\")\n        return self\n\n\nFAKE_PAYMENT_METHOD_I = PaymentMethodDict(\n    load_fixture(\"payment_method_pm_fakefakefakefake0001.json\")\n)\n\nFAKE_PAYMENT_METHOD_II = deepcopy(FAKE_PAYMENT_METHOD_I)\nFAKE_PAYMENT_METHOD_II[\"customer\"] = \"cus_4UbFSo9tl62jqj\"  # FAKE_CUSTOMER_II\n\n# FAKE_CARD, but accessed as a PaymentMethod\nFAKE_CARD_AS_PAYMENT_METHOD = PaymentMethodDict(\n    load_fixture(\"payment_method_card_fakefakefakefakefake0001.json\")\n)\n\n\nFAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT = load_fixture(\n    \"order_order_fakefakefakefake0001.json\"\n)\n\n\nFAKE_ORDER_WITHOUT_CUSTOMER_WITH_PAYMENT_INTENT = deepcopy(\n    FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT\n)\nFAKE_ORDER_WITHOUT_CUSTOMER_WITH_PAYMENT_INTENT[\"customer\"] = None\n\n\nFAKE_ORDER_WITH_CUSTOMER_WITHOUT_PAYMENT_INTENT = deepcopy(\n    FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT\n)\nFAKE_ORDER_WITH_CUSTOMER_WITHOUT_PAYMENT_INTENT[\"payment_intent\"] = None\nFAKE_ORDER_WITH_CUSTOMER_WITHOUT_PAYMENT_INTENT[\"payment\"][\"payment_intent\"] = None\n\n\nFAKE_ORDER_WITHOUT_CUSTOMER_WITHOUT_PAYMENT_INTENT = deepcopy(\n    FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT\n)\nFAKE_ORDER_WITHOUT_CUSTOMER_WITHOUT_PAYMENT_INTENT[\"customer\"] = None\nFAKE_ORDER_WITHOUT_CUSTOMER_WITHOUT_PAYMENT_INTENT[\"payment_intent\"] = None\nFAKE_ORDER_WITHOUT_CUSTOMER_WITHOUT_PAYMENT_INTENT[\"payment\"][\"payment_intent\"] = None\n\n\n# Created Orders have their status=\"open\"\nFAKE_EVENT_ORDER_CREATED = {\n    \"id\": \"evt_16igNU2eZvKYlo2CYyMkYvet\",\n    \"object\": \"event\",\n    \"api_version\": \"2016-03-07\",\n    \"created\": 1441696732,\n    \"data\": {\"object\": deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITHOUT_PAYMENT_INTENT)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6wZW9MskhYU15Y\",\n    \"type\": \"order.created\",\n}\nFAKE_EVENT_ORDER_CREATED[\"data\"][\"object\"][\"status\"] = \"open\"\n\n\nFAKE_EVENT_ORDER_UPDATED = {\n    \"id\": \"evt_16igNU2eZvKYlo2CYyMkYvet\",\n    \"object\": \"event\",\n    \"api_version\": \"2016-03-07\",\n    \"created\": 1441696732,\n    \"data\": {\"object\": deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6wZW9MskhYU15Y\",\n    \"type\": \"order.created\",\n}\n\nFAKE_EVENT_ORDER_UPDATED[\"data\"][\"object\"][\"status\"] = \"open\"\nFAKE_EVENT_ORDER_UPDATED[\"type\"] = \"order.updated\"\nFAKE_EVENT_ORDER_UPDATED[\"data\"][\"object\"][\"billing_details\"][\n    \"email\"\n] = \"testuser@example.com\"\n\n\nFAKE_EVENT_ORDER_SUBMITTED = deepcopy(FAKE_EVENT_ORDER_UPDATED)\nFAKE_EVENT_ORDER_SUBMITTED[\"type\"] = \"order.submitted\"\nFAKE_EVENT_ORDER_SUBMITTED[\"data\"][\"object\"][\"status\"] = \"submitted\"\n\n\nFAKE_EVENT_ORDER_PROCESSING = deepcopy(FAKE_EVENT_ORDER_UPDATED)\nFAKE_EVENT_ORDER_PROCESSING[\"type\"] = \"order.processing\"\nFAKE_EVENT_ORDER_PROCESSING[\"data\"][\"object\"][\"status\"] = \"processing\"\n\n\nFAKE_EVENT_ORDER_CANCELLED = deepcopy(FAKE_EVENT_ORDER_UPDATED)\nFAKE_EVENT_ORDER_CANCELLED[\"type\"] = \"order.canceled\"\nFAKE_EVENT_ORDER_CANCELLED[\"data\"][\"object\"][\"status\"] = \"canceled\"\n\n\nFAKE_EVENT_ORDER_COMPLETED = deepcopy(FAKE_EVENT_ORDER_UPDATED)\nFAKE_EVENT_ORDER_COMPLETED[\"type\"] = \"order.complete\"\nFAKE_EVENT_ORDER_COMPLETED[\"data\"][\"object\"][\"status\"] = \"complete\"\n\n# TODO - add to regenerate_test_fixtures and replace this with a JSON fixture\nFAKE_SETUP_INTENT_I = {\n    \"id\": \"seti_fakefakefakefake0001\",\n    \"object\": \"setup_intent\",\n    \"cancellation_reason\": None,\n    \"payment_method_types\": [\"card\"],\n    \"status\": \"requires_payment_method\",\n    \"usage\": \"off_session\",\n    \"payment_method\": None,\n    \"on_behalf_of\": None,\n    \"customer\": None,\n}\n\nFAKE_SETUP_INTENT_II = {\n    \"application\": None,\n    \"cancellation_reason\": None,\n    \"client_secret\": \"seti_1J0g0WJSZQVUcJYgWE2XSi1K_secret_Jdxw2mOaIEHBdE6eTsfJ2IfmamgNJaF\",\n    \"created\": 1623301244,\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"description\": None,\n    \"id\": \"seti_1J0g0WJSZQVUcJYgWE2XSi1K\",\n    \"last_setup_error\": None,\n    \"latest_attempt\": \"setatt_1J0g0WJSZQVUcJYgsrFgwxVh\",\n    \"livemode\": False,\n    \"mandate\": None,\n    \"metadata\": {},\n    \"next_action\": None,\n    \"object\": \"setup_intent\",\n    \"on_behalf_of\": None,\n    \"payment_method\": \"pm_fakefakefakefake0001\",\n    \"payment_method_options\": {\"card\": {\"request_three_d_secure\": \"automatic\"}},\n    \"payment_method_types\": [\"card\"],\n    \"single_use_mandate\": None,\n    \"status\": \"succeeded\",\n    \"usage\": \"off_session\",\n}\n\nFAKE_SETUP_INTENT_DESTINATION_CHARGE = load_fixture(\n    \"setup_intent_pi_destination_charge.json\"\n)\n\n\n# TODO - add to regenerate_test_fixtures and replace this with a JSON fixture\n#  (will need to use a different payment_intent fixture)\nFAKE_SESSION_I = {\n    \"id\": \"cs_test_OAgNmy75Td25OeREvKUs8XZ7SjMPO9qAplqHO1sBaEjOg9fYbaeMh2nA\",\n    \"object\": \"checkout.session\",\n    \"billing_address_collection\": None,\n    \"cancel_url\": \"https://example.com/cancel\",\n    \"client_reference_id\": None,\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"customer_email\": None,\n    \"display_items\": [\n        {\n            \"amount\": 1500,\n            \"currency\": \"usd\",\n            \"custom\": {\n                \"description\": \"Comfortable cotton t-shirt\",\n                \"images\": None,\n                \"name\": \"T-shirt\",\n            },\n            \"quantity\": 2,\n            \"type\": \"custom\",\n        }\n    ],\n    \"livemode\": False,\n    \"locale\": None,\n    \"mode\": None,\n    \"payment_intent\": FAKE_PAYMENT_INTENT_I[\"id\"],\n    \"payment_method_types\": [\"card\"],\n    \"setup_intent\": None,\n    \"submit_type\": None,\n    \"subscription\": None,\n    \"success_url\": \"https://example.com/success\",\n    \"metadata\": {},\n}\n\n\nclass ChargeDict(StripeItem):\n    def __init__(self, *args, **kwargs):\n        \"\"\"Match Stripe's behavior: return a stripe iterable on `charge.refunds`.\"\"\"\n        super().__init__(*args, **kwargs)\n        self.refunds = StripeList(self.refunds)\n\n    def refund(self, amount=None, reason=None):\n        self.update({\"refunded\": True, \"amount_refunded\": amount})\n        return self\n\n    def capture(self):\n        self.update({\"captured\": True})\n        return self\n\n\nFAKE_CHARGE = ChargeDict(load_fixture(\"charge_ch_fakefakefakefakefake0001.json\"))\n\n\nFAKE_CHARGE_II = ChargeDict(\n    {\n        \"id\": \"ch_16ag432eZvKYlo2CGDe6lvVs\",\n        \"object\": \"charge\",\n        \"amount\": 3000,\n        \"amount_captured\": 0,\n        \"amount_refunded\": 0,\n        \"application_fee\": None,\n        \"application_fee_amount\": None,\n        \"balance_transaction\": FAKE_BALANCE_TRANSACTION[\"id\"],\n        \"billing_details\": {\n            \"address\": {\n                \"city\": None,\n                \"country\": \"US\",\n                \"line1\": None,\n                \"line2\": None,\n                \"postal_code\": \"92082\",\n                \"state\": None,\n            },\n            \"email\": \"kyoung@hotmail.com\",\n            \"name\": \"John Foo\",\n            \"phone\": None,\n        },\n        \"calculated_statement_descriptor\": \"Stripe\",\n        \"captured\": False,\n        \"created\": 1439788903,\n        \"currency\": \"usd\",\n        \"customer\": \"cus_4UbFSo9tl62jqj\",\n        \"description\": None,\n        \"destination\": None,\n        \"dispute\": None,\n        \"disputed\": False,\n        \"failure_code\": \"expired_card\",\n        \"failure_message\": \"Your card has expired.\",\n        \"fraud_details\": {},\n        \"invoice\": \"in_16af5A2eZvKYlo2CJjANLL81\",\n        \"livemode\": False,\n        \"metadata\": {},\n        \"on_behalf_of\": None,\n        \"order\": None,\n        \"outcome\": {\n            \"network_status\": \"declined_by_network\",\n            \"reason\": \"expired_card\",\n            \"risk_level\": \"normal\",\n            \"risk_score\": 1,\n            \"seller_message\": \"The bank returned the decline code `expired_card`.\",\n            \"type\": \"issuer_declined\",\n        },\n        \"paid\": False,\n        \"payment_intent\": FAKE_PAYMENT_INTENT_II[\"id\"],\n        \"payment_method\": FAKE_CARD_AS_PAYMENT_METHOD[\"id\"],\n        \"payment_method_details\": {\n            \"card\": {\n                \"brand\": \"visa\",\n                \"checks\": {\n                    \"address_line1_check\": None,\n                    \"address_postal_code_check\": None,\n                    \"cvc_check\": None,\n                },\n                \"country\": \"US\",\n                \"exp_month\": 6,\n                \"exp_year\": 2021,\n                \"fingerprint\": \"88PuXw9tEmvYe69o\",\n                \"funding\": \"credit\",\n                \"installments\": None,\n                \"last4\": \"4242\",\n                \"network\": \"visa\",\n                \"three_d_secure\": None,\n                \"wallet\": None,\n            },\n            \"type\": \"card\",\n        },\n        \"receipt_email\": None,\n        \"receipt_number\": None,\n        \"receipt_url\": None,\n        \"refunded\": False,\n        \"refunds\": {\n            \"object\": \"list\",\n            \"total_count\": 0,\n            \"has_more\": False,\n            \"url\": \"/v1/charges/ch_16ag432eZvKYlo2CGDe6lvVs/refunds\",\n            \"data\": [],\n        },\n        \"review\": None,\n        \"shipping\": None,\n        \"source\": deepcopy(FAKE_CARD_II),\n        \"source_transfer\": None,\n        \"statement_descriptor\": None,\n        \"statement_descriptor_suffix\": None,\n        \"status\": \"failed\",\n        \"transfer_data\": None,\n        \"transfer_group\": None,\n    }\n)\n\nFAKE_CHARGE_REFUNDED = deepcopy(FAKE_CHARGE)\nFAKE_CHARGE_REFUNDED = FAKE_CHARGE_REFUNDED.refund(\n    amount=FAKE_CHARGE_REFUNDED[\"amount\"]\n)\n\nFAKE_REFUND = {\n    \"id\": \"re_1E0he8KatMEEd8456454S01Vc\",\n    \"object\": \"refund\",\n    \"amount\": FAKE_CHARGE_REFUNDED[\"amount_refunded\"],\n    \"balance_transaction\": \"txn_1E0he8KaGRDEd998TDswMZuN\",\n    \"charge\": FAKE_CHARGE_REFUNDED[\"id\"],\n    \"created\": 1549425864,\n    \"currency\": \"usd\",\n    \"metadata\": {},\n    \"reason\": None,\n    \"receipt_number\": None,\n    \"source_transfer_reversal\": None,\n    \"status\": \"succeeded\",\n    \"transfer_reversal\": None,\n}\n\n# Balance transaction associated with the refund\nFAKE_BALANCE_TRANSACTION_REFUND = {\n    \"id\": \"txn_1E0he8KaGRDEd998TDswMZuN\",\n    \"amount\": -1 * FAKE_CHARGE_REFUNDED[\"amount_refunded\"],\n    \"available_on\": 1549425864,\n    \"created\": 1549425864,\n    \"currency\": \"usd\",\n    \"description\": \"REFUND FOR CHARGE (Payment for invoice G432DF1C-0028)\",\n    \"exchange_rate\": None,\n    \"fee\": 0,\n    \"fee_details\": [],\n    \"net\": -1 * FAKE_CHARGE_REFUNDED[\"amount_refunded\"],\n    \"object\": \"balance_transaction\",\n    \"source\": FAKE_REFUND[\"id\"],\n    \"status\": \"available\",\n    \"type\": \"refund\",\n}\n\n\nFAKE_CHARGE_REFUNDED[\"refunds\"].update(\n    {\"total_count\": 1, \"data\": [deepcopy(FAKE_REFUND)]}\n)\n\n\nFAKE_COUPON = {\n    \"id\": \"fake-coupon-1\",\n    \"object\": \"coupon\",\n    \"applies_to\": {\"products\": [\"prod_fake1\"]},\n    \"amount_off\": None,\n    \"created\": 1490157071,\n    \"currency\": None,\n    \"duration\": \"once\",\n    \"duration_in_months\": None,\n    \"livemode\": False,\n    \"max_redemptions\": None,\n    \"metadata\": {},\n    \"percent_off\": 1,\n    \"redeem_by\": None,\n    \"times_redeemed\": 0,\n    \"valid\": True,\n}\n\n\nFAKE_DISPUTE_CHARGE = load_fixture(\"dispute_ch_fakefakefakefake01.json\")\n\nFAKE_DISPUTE_BALANCE_TRANSACTION = load_fixture(\"dispute_txn_fakefakefakefake01.json\")\n\n# case when a dispute gets closed and the funds get reinstated (full)\nFAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_FULL = deepcopy(\n    FAKE_DISPUTE_BALANCE_TRANSACTION\n)\nFAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_FULL[\"amount\"] = (\n    -1 * FAKE_DISPUTE_BALANCE_TRANSACTION[\"amount\"]\n)\nFAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_FULL[\"fee\"] = (\n    -1 * FAKE_DISPUTE_BALANCE_TRANSACTION[\"fee\"]\n)\nFAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_FULL[\"net\"] = (\n    -1 * FAKE_DISPUTE_BALANCE_TRANSACTION[\"net\"]\n)\nFAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_FULL[\"fee_details\"][0][\"amount\"] = (\n    -1 * FAKE_DISPUTE_BALANCE_TRANSACTION[\"fee_details\"][0][\"amount\"]\n)\n\n# case when a dispute gets closed and the funds get reinstated (partial)\nFAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_PARTIAL = deepcopy(\n    FAKE_DISPUTE_BALANCE_TRANSACTION\n)\nFAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_PARTIAL[\"amount\"] = (\n    -0.9 * FAKE_DISPUTE_BALANCE_TRANSACTION[\"amount\"]\n)\nFAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_PARTIAL[\"fee\"] = (\n    -0.9 * FAKE_DISPUTE_BALANCE_TRANSACTION[\"fee\"]\n)\nFAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_PARTIAL[\"net\"] = (\n    -0.9 * FAKE_DISPUTE_BALANCE_TRANSACTION[\"net\"]\n)\nFAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_PARTIAL[\"fee_details\"][0][\"amount\"] = (\n    -0.9 * FAKE_DISPUTE_BALANCE_TRANSACTION[\"fee_details\"][0][\"amount\"]\n)\n\n\nFAKE_DISPUTE_PAYMENT_INTENT = load_fixture(\"dispute_pi_fakefakefakefake01.json\")\n\nFAKE_DISPUTE_PAYMENT_METHOD = load_fixture(\"dispute_pm_fakefakefakefake01.json\")\n\n# case when dispute gets created\nFAKE_DISPUTE_I = load_fixture(\"dispute_dp_fakefakefakefake01.json\")\n\n# case when funds get withdrawn from platform account due to dispute\nFAKE_DISPUTE_II = load_fixture(\"dispute_dp_fakefakefakefake02.json\")\n\n# case when dispute gets updated\nFAKE_DISPUTE_III = deepcopy(FAKE_DISPUTE_II)\nFAKE_DISPUTE_III[\"evidence\"][\"receipt\"] = \"file_4hshrsKatMEEd6736724HYAXyj\"\n\n# case when dispute gets closed\nFAKE_DISPUTE_IV = deepcopy(FAKE_DISPUTE_II)\nFAKE_DISPUTE_IV[\"evidence\"][\"receipt\"] = \"file_4hshrsKatMEEd6736724HYAXyj\"\nFAKE_DISPUTE_IV[\"status\"] = \"won\"\n\n# case when dispute funds get reinstated (partial)\nFAKE_DISPUTE_V_PARTIAL = load_fixture(\"dispute_dp_funds_reinstated_full.json\")\nFAKE_DISPUTE_V_PARTIAL[\"balance_transactions\"][\n    1\n] = FAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_PARTIAL\n\n\n# case when dispute funds get reinstated (full)\nFAKE_DISPUTE_V_FULL = load_fixture(\"dispute_dp_funds_reinstated_full.json\")\nFAKE_DISPUTE_V_FULL[\"balance_transactions\"][\n    1\n] = FAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_FULL\n\n\nFAKE_PRODUCT = load_fixture(\"product_prod_fake1.json\")\n\nFAKE_PLAN = load_fixture(\"plan_gold21323.json\")\nFAKE_PLAN_II = load_fixture(\"plan_silver41294.json\")\n\nfor plan in (FAKE_PLAN, FAKE_PLAN_II):\n    # sanity check\n    assert plan[\"product\"] == FAKE_PRODUCT[\"id\"]\n\n\nFAKE_TIER_PLAN = {\n    \"id\": \"tier21323\",\n    \"object\": \"plan\",\n    \"active\": True,\n    \"amount\": None,\n    \"created\": 1386247539,\n    \"currency\": \"usd\",\n    \"interval\": \"month\",\n    \"interval_count\": 1,\n    \"livemode\": False,\n    \"metadata\": {},\n    \"nickname\": \"New plan name\",\n    \"product\": FAKE_PRODUCT[\"id\"],\n    \"trial_period_days\": None,\n    \"usage_type\": \"licensed\",\n    \"tiers_mode\": \"graduated\",\n    \"tiers\": [\n        {\"flat_amount\": 4900, \"unit_amount\": 1000, \"up_to\": 5},\n        {\"flat_amount\": None, \"unit_amount\": 900, \"up_to\": None},\n    ],\n}\n\nFAKE_PLAN_METERED = {\n    \"id\": \"plan_fakemetered\",\n    \"billing_scheme\": \"per_unit\",\n    \"object\": \"plan\",\n    \"active\": True,\n    \"aggregate_usage\": \"sum\",\n    \"amount\": 200,\n    \"collection_method\": \"per_unit\",\n    \"created\": 1552632817,\n    \"currency\": \"usd\",\n    \"interval\": \"month\",\n    \"interval_count\": 1,\n    \"livemode\": False,\n    \"metadata\": {},\n    \"nickname\": \"Sum Metered Plan\",\n    \"product\": FAKE_PRODUCT[\"id\"],\n    \"tiers\": None,\n    \"tiers_mode\": None,\n    \"transform_usage\": None,\n    \"trial_period_days\": None,\n    \"usage_type\": \"metered\",\n}\n\n\nFAKE_PRICE = load_fixture(\"price_gold21323.json\")\nFAKE_PRICE_II = load_fixture(\"price_silver41294.json\")\n\nfor price in (FAKE_PRICE, FAKE_PRICE_II):\n    # sanity check\n    assert price[\"product\"] == FAKE_PRODUCT[\"id\"]\n\n\nFAKE_PRICE_TIER = {\n    \"active\": True,\n    \"billing_scheme\": \"tiered\",\n    \"created\": 1386247539,\n    \"currency\": \"usd\",\n    \"id\": \"price_tier21323\",\n    \"livemode\": False,\n    \"lookup_key\": None,\n    \"metadata\": {},\n    \"nickname\": \"New price name\",\n    \"object\": \"price\",\n    \"product\": FAKE_PRODUCT[\"id\"],\n    \"recurring\": {\n        \"aggregate_usage\": None,\n        \"interval\": \"month\",\n        \"interval_count\": 1,\n        \"trial_period_days\": None,\n        \"usage_type\": \"licensed\",\n    },\n    \"tiers\": [\n        {\n            \"flat_amount\": 4900,\n            \"flat_amount_decimal\": \"4900\",\n            \"unit_amount\": 1000,\n            \"unit_amount_decimal\": \"1000\",\n            \"up_to\": 5,\n        },\n        {\n            \"flat_amount\": None,\n            \"flat_amount_decimal\": None,\n            \"unit_amount\": 900,\n            \"unit_amount_decimal\": \"900\",\n            \"up_to\": None,\n        },\n    ],\n    \"tiers_mode\": \"graduated\",\n    \"transform_quantity\": None,\n    \"type\": \"recurring\",\n    \"unit_amount\": None,\n    \"unit_amount_decimal\": None,\n}\n\nFAKE_PRICE_METERED = {\n    \"active\": True,\n    \"billing_scheme\": \"per_unit\",\n    \"created\": 1552632817,\n    \"currency\": \"usd\",\n    \"id\": \"price_fakemetered\",\n    \"livemode\": False,\n    \"lookup_key\": None,\n    \"metadata\": {},\n    \"nickname\": \"Sum Metered Price\",\n    \"object\": \"price\",\n    \"product\": FAKE_PRODUCT[\"id\"],\n    \"recurring\": {\n        \"aggregate_usage\": \"sum\",\n        \"interval\": \"month\",\n        \"interval_count\": 1,\n        \"trial_period_days\": None,\n        \"usage_type\": \"metered\",\n    },\n    \"tiers_mode\": None,\n    \"transform_quantity\": None,\n    \"type\": \"recurring\",\n    \"unit_amount\": 200,\n    \"unit_amount_decimal\": \"200\",\n}\n\nFAKE_PRICE_ONETIME = {\n    \"active\": True,\n    \"billing_scheme\": \"per_unit\",\n    \"created\": 1552632818,\n    \"currency\": \"usd\",\n    \"id\": \"price_fakeonetime\",\n    \"livemode\": False,\n    \"lookup_key\": None,\n    \"metadata\": {},\n    \"nickname\": \"One-Time Price\",\n    \"object\": \"price\",\n    \"product\": FAKE_PRODUCT[\"id\"],\n    \"recurring\": None,\n    \"tiers_mode\": None,\n    \"transform_quantity\": None,\n    \"type\": \"one_time\",\n    \"unit_amount\": 2000,\n    \"unit_amount_decimal\": \"2000\",\n}\n\n\nclass SubscriptionDict(StripeItem):\n    def __init__(self, *args, **kwargs):\n        \"\"\"Match Stripe's behavior: return a stripe iterable on `subscription.items`.\"\"\"\n        super().__init__(*args, **kwargs)\n        self[\"items\"] = StripeList(self[\"items\"])\n\n    def __setattr__(self, name, value):\n        if type(value) == datetime.datetime:\n            value = datetime_to_unix(value)\n\n        # Special case for price and plan\n        if name == \"price\":\n            for price in [\n                FAKE_PRICE,\n                FAKE_PRICE_II,\n                FAKE_PRICE_TIER,\n                FAKE_PRICE_METERED,\n            ]:\n                if value == price[\"id\"]:\n                    value = price\n        elif name == \"plan\":\n            for plan in [FAKE_PLAN, FAKE_PLAN_II, FAKE_TIER_PLAN, FAKE_PLAN_METERED]:\n                if value == plan[\"id\"]:\n                    value = plan\n\n        self[name] = value\n\n    def delete(self, **kwargs):\n        if \"at_period_end\" in kwargs:\n            self[\"cancel_at_period_end\"] = kwargs[\"at_period_end\"]\n\n        return self\n\n    def save(self, idempotency_key=None):\n        return self\n\n\nFAKE_SUBSCRIPTION = SubscriptionDict(\n    load_fixture(\"subscription_sub_fakefakefakefakefake0001.json\")\n)\n\nFAKE_SUBSCRIPTION_NOT_PERIOD_CURRENT = deepcopy(FAKE_SUBSCRIPTION)\nFAKE_SUBSCRIPTION_NOT_PERIOD_CURRENT.update(\n    {\"current_period_end\": 1441907581, \"current_period_start\": 1439229181}\n)\n\nFAKE_SUBSCRIPTION_CANCELED = deepcopy(FAKE_SUBSCRIPTION)\nFAKE_SUBSCRIPTION_CANCELED[\"status\"] = \"canceled\"\nFAKE_SUBSCRIPTION_CANCELED[\"canceled_at\"] = 1440907580\n\nFAKE_SUBSCRIPTION_CANCELED_AT_PERIOD_END = deepcopy(FAKE_SUBSCRIPTION)\nFAKE_SUBSCRIPTION_CANCELED_AT_PERIOD_END[\"canceled_at\"] = 1440907580\nFAKE_SUBSCRIPTION_CANCELED_AT_PERIOD_END[\"cancel_at_period_end\"] = True\n\nFAKE_SUBSCRIPTION_II = SubscriptionDict(\n    load_fixture(\"subscription_sub_fakefakefakefakefake0002.json\")\n)\n\n\nFAKE_SUBSCRIPTION_III = SubscriptionDict(\n    load_fixture(\"subscription_sub_fakefakefakefakefake0003.json\")\n)\n\n\nFAKE_SUBSCRIPTION_MULTI_PLAN = SubscriptionDict(\n    load_fixture(\"subscription_sub_fakefakefakefakefake0004.json\")\n)\n\n\nFAKE_SUBSCRIPTION_METERED = SubscriptionDict(\n    {\n        \"id\": \"sub_1rn1dp7WgjMtx9\",\n        \"object\": \"subscription\",\n        \"application_fee_percent\": None,\n        \"collection_method\": \"charge_automatically\",\n        \"cancel_at_period_end\": False,\n        \"canceled_at\": None,\n        \"current_period_end\": 1441907581,\n        \"current_period_start\": 1439229181,\n        \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n        \"discount\": None,\n        \"ended_at\": None,\n        \"metadata\": {\"djstripe_test_fake_id\": \"sub_fakefakefakefakefake0005\"},\n        \"items\": {\n            \"data\": [\n                {\n                    \"created\": 1441907581,\n                    \"id\": \"si_UXYmKmJp6aWTw6\",\n                    \"metadata\": {},\n                    \"object\": \"subscription_item\",\n                    \"plan\": deepcopy(FAKE_PLAN_METERED),\n                    \"subscription\": \"sub_1rn1dp7WgjMtx9\",\n                }\n            ]\n        },\n        \"pause_collection\": None,\n        \"plan\": deepcopy(FAKE_PLAN_METERED),\n        \"quantity\": 1,\n        \"start\": 1439229181,\n        \"start_date\": 1439229181,\n        \"status\": \"active\",\n        \"tax_percent\": None,\n        \"trial_end\": None,\n        \"trial_start\": None,\n    }\n)\n\n\nFAKE_SUBSCRIPTION_ITEM_METERED = {\n    \"id\": \"si_JiphMAMFxZKW8s\",\n    \"object\": \"subscription_item\",\n    \"metadata\": {},\n    \"billing_thresholds\": \"\",\n    \"created\": 1441907581,\n    \"plan\": deepcopy(FAKE_PLAN_METERED),\n    \"price\": deepcopy(FAKE_PRICE_METERED),\n    \"quantity\": 1,\n    \"subscription\": FAKE_SUBSCRIPTION_METERED[\"id\"],\n    \"tax_rates\": [],\n}\n\nFAKE_SUBSCRIPTION_ITEM_MULTI_PLAN = {\n    \"id\": \"si_JiphMAMFxZKW8s\",\n    \"object\": \"subscription_item\",\n    \"metadata\": {},\n    \"billing_thresholds\": \"\",\n    \"created\": 1441907581,\n    \"plan\": deepcopy(FAKE_PLAN),\n    \"price\": deepcopy(FAKE_PRICE),\n    \"quantity\": 1,\n    \"subscription\": FAKE_SUBSCRIPTION_MULTI_PLAN[\"id\"],\n    \"tax_rates\": [],\n}\n\nFAKE_SUBSCRIPTION_ITEM_TAX_RATES = {\n    \"id\": \"si_JiphMAMFxZKW8s\",\n    \"object\": \"subscription_item\",\n    \"metadata\": {},\n    \"billing_thresholds\": \"\",\n    \"created\": 1441907581,\n    \"plan\": deepcopy(FAKE_PLAN_II),\n    \"price\": deepcopy(FAKE_PRICE_II),\n    \"quantity\": 1,\n    \"subscription\": FAKE_SUBSCRIPTION_II[\"id\"],\n    \"tax_rates\": [\n        {\n            \"id\": \"txr_fakefakefakefakefake0001\",\n            \"object\": \"tax_rate\",\n            \"active\": True,\n            \"created\": 1593225980,\n            \"description\": None,\n            \"display_name\": \"VAT\",\n            \"inclusive\": True,\n            \"jurisdiction\": \"Example1\",\n            \"livemode\": False,\n            \"metadata\": {\"djstripe_test_fake_id\": \"txr_fakefakefakefakefake0001\"},\n            \"percentage\": 15.0,\n        }\n    ],\n}\n\n\nFAKE_SUBSCRIPTION_SCHEDULE = {\n    \"id\": \"sub_sched_1Hm7q6Fz0jfFqjGs2OxOSCzD\",\n    \"object\": \"subscription_schedule\",\n    \"canceled_at\": None,\n    \"completed_at\": None,\n    \"created\": 1605056974,\n    \"current_phase\": {},\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",  # FAKE_CUSTOMER\n    \"default_settings\": {\n        \"billing_cycle_anchor\": \"automatic\",\n        \"billing_thresholds\": None,\n        \"collection_method\": \"charge_automatically\",\n        \"default_payment_method\": None,\n        \"default_source\": None,\n        \"invoice_settings\": None,\n        \"transfer_data\": None,\n    },\n    \"end_behavior\": \"release\",\n    \"livemode\": False,\n    \"metadata\": {},\n    \"phases\": [\n        {\n            \"add_invoice_items\": [],\n            \"application_fee_percent\": None,\n            \"billing_cycle_anchor\": None,\n            \"billing_thresholds\": None,\n            \"collection_method\": None,\n            \"coupon\": None,\n            \"default_payment_method\": None,\n            \"default_tax_rates\": [],\n            \"end_date\": 1637195591,\n            \"invoice_settings\": None,\n            \"plans\": [\n                {\n                    \"billing_thresholds\": None,\n                    \"plan\": FAKE_PLAN_II[\"id\"],\n                    \"price\": FAKE_PRICE_II[\"id\"],\n                    \"quantity\": None,\n                    \"tax_rates\": [],\n                }\n            ],\n            \"prorate\": True,\n            \"proration_behavior\": \"create_prorations\",\n            \"start_date\": 1605659591,\n            \"tax_percent\": None,\n            \"transfer_data\": None,\n            \"trial_end\": None,\n        }\n    ],\n    \"released_at\": None,\n    \"released_subscription\": None,\n    \"renewal_interval\": None,\n    \"status\": \"not_started\",\n    \"subscription\": FAKE_SUBSCRIPTION[\"id\"],\n}\n\n\nFAKE_SHIPPING_RATE = load_fixture(\"shipping_rate_shr_fakefakefakefakefake0001.json\")\nFAKE_SHIPPING_RATE_WITH_TAX_CODE = load_fixture(\n    \"shipping_rate_shr_fakefakefakefakefake0002.json\"\n)\n\n\nclass Sources(object):\n    def __init__(self, card_fakes):\n        self.card_fakes = card_fakes\n\n    def create(self, source, api_key=None):\n        for fake_card in self.card_fakes:\n            if fake_card[\"id\"] == source:\n                return fake_card\n\n    def retrieve(self, id, expand=None):\n        for fake_card in self.card_fakes:\n            if fake_card[\"id\"] == id:\n                return fake_card\n\n    def list(self, **kwargs):\n        return StripeList(data=self.card_fakes)\n\n\ndef convert_source_dict(data):\n    if data:\n        source_type = data[\"object\"]\n        if source_type == \"card\":\n            data = CardDict(data)\n        elif source_type == \"bank_account\":\n            data = BankAccountDict(data)\n        elif source_type == \"source\":\n            data = SourceDict(data)\n        else:\n            raise ValueError(f\"Unknown source type: {source_type}\")\n\n    return data\n\n\nclass CustomerDict(dict):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n        self[\"default_source\"] = convert_source_dict(self[\"default_source\"])\n\n        for n, d in enumerate(self[\"sources\"].get(\"data\", [])):\n            self[\"sources\"][\"data\"][n] = convert_source_dict(d)\n\n    def save(self, idempotency_key=None):\n        return self\n\n    def delete(self):\n        return self\n\n    @property\n    def sources(self):\n        return Sources(card_fakes=self[\"sources\"][\"data\"])\n\n    def create_for_user(self, user):\n        from djstripe.models import Customer\n\n        stripe_customer = Customer.sync_from_stripe_data(self)\n        stripe_customer.subscriber = user\n        stripe_customer.save()\n        return stripe_customer\n\n\nFAKE_CUSTOMER = CustomerDict(load_fixture(\"customer_cus_6lsBvm5rJ0zyHc.json\"))\n\n\n# Customer with multiple subscriptions (all licensed usagetype)\nFAKE_CUSTOMER_II = CustomerDict(load_fixture(\"customer_cus_4UbFSo9tl62jqj.json\"))\n\n\n# Customer with a Source (instead of Card) as default_source\nFAKE_CUSTOMER_III = CustomerDict(load_fixture(\"customer_cus_4QWKsZuuTHcs7X.json\"))\n\n\n# Customer with a Bank Account as default_source\nFAKE_CUSTOMER_IV = CustomerDict(\n    load_fixture(\"customer_cus_example_with_bank_account.json\")\n)\n\n\nFAKE_DISCOUNT = {\n    \"id\": \"di_fakefakefakefakefake0001\",\n    \"object\": \"discount\",\n    \"description\": \"\",\n    \"checkout_session\": None,\n    \"coupon\": deepcopy(FAKE_COUPON),\n    \"customer\": FAKE_CUSTOMER,\n    \"end\": None,\n    \"invoice\": None,\n    \"invoice_item\": None,\n    \"promotion_code\": \"\",\n    \"start\": 1493206114,\n    \"subscription\": \"sub_fakefakefakefakefake0001\",\n}\n\nFAKE_DISCOUNT_CUSTOMER = {\n    \"id\": \"di_fakefakefakefakefake0002\",\n    \"object\": \"discount\",\n    \"coupon\": deepcopy(FAKE_COUPON),\n    \"customer\": deepcopy(FAKE_CUSTOMER),\n    \"start\": 1493206114,\n    \"end\": None,\n    \"subscription\": None,\n}\n\n\nFAKE_LINE_ITEM = load_fixture(\"line_item_il_invoice_item_fakefakefakefakefake0001.json\")\nFAKE_LINE_ITEM[\"discounts\"] = [deepcopy(FAKE_DISCOUNT_CUSTOMER)]\n\nFAKE_LINE_ITEM_SUBSCRIPTION = load_fixture(\n    \"line_item_il_invoice_item_fakefakefakefakefake0002.json\"\n)\nFAKE_LINE_ITEM_SUBSCRIPTION[\"discounts\"] = [deepcopy(FAKE_DISCOUNT_CUSTOMER)]\nFAKE_LINE_ITEM_SUBSCRIPTION[\"discounts\"][0][\"subscription\"] = \"sub_1rn1dp7WgjMtx9\"\n\n\nclass InvoiceDict(StripeItem):\n    def __init__(self, *args, **kwargs):\n        \"\"\"Match Stripe's behavior: return a stripe iterable on `invoice.lines`.\"\"\"\n        super().__init__(*args, **kwargs)\n        self.lines = StripeList(self.lines)\n\n    def pay(self):\n        return self\n\n\nFAKE_INVOICE = load_fixture(\"invoice_in_fakefakefakefakefake0001.json\")\nFAKE_INVOICE[\"lines\"] = {\n    \"object\": \"list\",\n    \"data\": [deepcopy(FAKE_LINE_ITEM)],\n    \"has_more\": False,\n    \"total_count\": 1,\n    \"url\": \"/v1/invoices/in_fakefakefakefakefake0001/lines\",\n}\nFAKE_INVOICE = InvoiceDict(FAKE_INVOICE)\n\n\nFAKE_INVOICE_IV = InvoiceDict(load_fixture(\"invoice_in_fakefakefakefakefake0004.json\"))\n\n\nFAKE_INVOICE_II = InvoiceDict(\n    {\n        \"id\": \"in_16af5A2eZvKYlo2CJjANLL81\",\n        \"object\": \"invoice\",\n        \"amount_due\": 3000,\n        \"amount_paid\": 0,\n        \"amount_remaining\": 3000,\n        \"application_fee_amount\": None,\n        \"attempt_count\": 1,\n        \"attempted\": True,\n        \"auto_advance\": True,\n        \"collection_method\": \"charge_automatically\",\n        \"charge\": FAKE_CHARGE_II[\"id\"],\n        \"currency\": \"usd\",\n        \"customer\": \"cus_4UbFSo9tl62jqj\",\n        \"created\": 1439785128,\n        \"description\": None,\n        \"discount\": None,\n        \"discounts\": [],\n        \"due_date\": None,\n        \"ending_balance\": 0,\n        \"lines\": {\n            \"data\": [deepcopy(FAKE_LINE_ITEM_SUBSCRIPTION)],\n            \"total_count\": 1,\n            \"object\": \"list\",\n            \"url\": \"/v1/invoices/in_16af5A2eZvKYlo2CJjANLL81/lines\",\n        },\n        \"livemode\": False,\n        \"metadata\": {},\n        \"next_payment_attempt\": 1440048103,\n        \"number\": \"XXXXXXX-0002\",\n        \"paid\": False,\n        \"period_end\": 1439784771,\n        \"period_start\": 1439698371,\n        \"receipt_number\": None,\n        \"starting_balance\": 0,\n        \"statement_descriptor\": None,\n        \"subscription\": FAKE_SUBSCRIPTION_III[\"id\"],\n        \"subtotal\": 3000,\n        \"tax\": None,\n        \"tax_percent\": None,\n        \"total\": 3000,\n        \"webhooks_delivered_at\": 1439785139,\n    }\n)\n\n\nFAKE_INVOICE_III = InvoiceDict(\n    {\n        \"id\": \"in_16Z9dP2eZvKYlo2CgFHgFx2Z\",\n        \"object\": \"invoice\",\n        \"amount_due\": 0,\n        \"amount_paid\": 0,\n        \"amount_remaining\": 0,\n        \"application_fee_amount\": None,\n        \"attempt_count\": 0,\n        \"attempted\": True,\n        \"auto_advance\": True,\n        \"collection_method\": \"charge_automatically\",\n        \"charge\": None,\n        \"created\": 1439425915,\n        \"currency\": \"usd\",\n        \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n        \"description\": None,\n        \"discount\": None,\n        \"discounts\": [],\n        \"due_date\": None,\n        \"ending_balance\": 20,\n        \"lines\": {\n            \"data\": [deepcopy(FAKE_LINE_ITEM_SUBSCRIPTION)],\n            \"total_count\": 1,\n            \"object\": \"list\",\n            \"url\": \"/v1/invoices/in_16Z9dP2eZvKYlo2CgFHgFx2Z/lines\",\n        },\n        \"livemode\": False,\n        \"metadata\": {},\n        \"next_payment_attempt\": None,\n        \"number\": \"XXXXXXX-0003\",\n        \"paid\": False,\n        \"period_end\": 1439424571,\n        \"period_start\": 1436746171,\n        \"receipt_number\": None,\n        \"starting_balance\": 0,\n        \"statement_descriptor\": None,\n        \"subscription\": FAKE_SUBSCRIPTION[\"id\"],\n        \"subtotal\": 20,\n        \"tax\": None,\n        \"tax_percent\": None,\n        \"total\": 20,\n        \"webhooks_delivered_at\": 1439426955,\n    }\n)\n\nFAKE_INVOICE_METERED_SUBSCRIPTION_USAGE = deepcopy(FAKE_SUBSCRIPTION_METERED)\nFAKE_INVOICE_METERED_SUBSCRIPTION_USAGE[\"customer\"] = FAKE_CUSTOMER_II[\"id\"]\n\n\nFAKE_SUBSCRIPTION_ITEM = {\n    \"id\": \"si_JiphMAMFxZKW8s\",\n    \"object\": \"subscription_item\",\n    \"metadata\": {},\n    \"billing_thresholds\": \"\",\n    \"created\": 1441907581,\n    \"plan\": deepcopy(FAKE_PLAN_METERED),\n    \"price\": deepcopy(FAKE_PRICE_METERED),\n    \"quantity\": 1,\n    \"subscription\": FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE[\"id\"],\n    \"tax_rates\": [],\n}\n\n\nFAKE_INVOICE_METERED_SUBSCRIPTION = InvoiceDict(\n    {\n        \"id\": \"in_1JGGM6JSZQVUcJYgpWqfBOIl\",\n        \"livemode\": False,\n        \"created\": 1439425915,\n        \"metadata\": {},\n        \"description\": \"\",\n        \"amount_due\": \"1.05\",\n        \"amount_paid\": \"1.05\",\n        \"amount_remaining\": \"0.00\",\n        \"application_fee_amount\": None,\n        \"attempt_count\": 1,\n        \"attempted\": True,\n        \"auto_advance\": False,\n        \"collection_method\": \"charge_automatically\",\n        \"currency\": \"usd\",\n        \"customer\": FAKE_CUSTOMER_II[\"id\"],\n        \"object\": \"invoice\",\n        \"charge\": None,\n        \"discount\": None,\n        \"discounts\": [],\n        \"due_date\": None,\n        \"ending_balance\": 0,\n        \"lines\": {\n            \"data\": [deepcopy(FAKE_LINE_ITEM_SUBSCRIPTION)],\n            \"total_count\": 1,\n            \"object\": \"list\",\n            \"url\": \"/v1/invoices/in_1JGGM6JSZQVUcJYgpWqfBOIl/lines\",\n        },\n        \"next_payment_attempt\": None,\n        \"number\": \"84DE1540-0004\",\n        \"paid\": True,\n        \"period_end\": 1439424571,\n        \"period_start\": 1436746171,\n        \"receipt_number\": None,\n        \"starting_balance\": 0,\n        \"statement_descriptor\": None,\n        \"subscription\": FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE[\"id\"],\n        \"subtotal\": \"1.00\",\n        \"tax\": None,\n        \"tax_percent\": None,\n        \"total\": \"1.00\",\n        \"webhooks_delivered_at\": 1439426955,\n    }\n)\n\n\nFAKE_UPCOMING_INVOICE = InvoiceDict(\n    {\n        \"id\": \"in\",\n        \"object\": \"invoice\",\n        \"amount_due\": 2000,\n        \"amount_paid\": 0,\n        \"amount_remaining\": 2000,\n        \"application_fee_amount\": None,\n        \"attempt_count\": 1,\n        \"attempted\": False,\n        \"collection_method\": \"charge_automatically\",\n        \"charge\": None,\n        \"created\": 1439218864,\n        \"currency\": \"usd\",\n        \"customer\": FAKE_CUSTOMER[\"id\"],\n        \"description\": None,\n        \"default_tax_rates\": [\n            {\n                \"id\": \"txr_fakefakefakefakefake0001\",\n                \"object\": \"tax_rate\",\n                \"active\": True,\n                \"created\": 1570921289,\n                \"description\": None,\n                \"display_name\": \"VAT\",\n                \"inclusive\": True,\n                \"jurisdiction\": \"Example1\",\n                \"livemode\": False,\n                \"metadata\": {\"djstripe_test_fake_id\": \"txr_fakefakefakefakefake0001\"},\n                \"percentage\": 15.0,\n            }\n        ],\n        \"discount\": None,\n        \"discounts\": [],\n        \"due_date\": None,\n        \"ending_balance\": None,\n        \"lines\": {\n            \"data\": [deepcopy(FAKE_LINE_ITEM_SUBSCRIPTION)],\n            \"total_count\": 1,\n            \"object\": \"list\",\n            \"url\": \"/v1/invoices/in_fakefakefakefakefake0001/lines\",\n        },\n        \"livemode\": False,\n        \"metadata\": {},\n        \"next_payment_attempt\": 1439218689,\n        \"number\": None,\n        \"paid\": False,\n        \"period_end\": 1439218689,\n        \"period_start\": 1439132289,\n        \"receipt_number\": None,\n        \"starting_balance\": 0,\n        \"statement_descriptor\": None,\n        \"subscription\": FAKE_SUBSCRIPTION[\"id\"],\n        \"subtotal\": 2000,\n        \"tax\": 261,\n        \"tax_percent\": None,\n        \"total\": 2000,\n        \"total_tax_amounts\": [\n            {\n                \"amount\": 261,\n                \"inclusive\": True,\n                \"tax_rate\": \"txr_fakefakefakefakefake0001\",\n            }\n        ],\n        \"webhooks_delivered_at\": 1439218870,\n    }\n)\n\nFAKE_TAX_RATE_EXAMPLE_1_VAT = load_fixture(\"tax_rate_txr_fakefakefakefakefake0001.json\")\nFAKE_TAX_RATE_EXAMPLE_2_SALES = load_fixture(\n    \"tax_rate_txr_fakefakefakefakefake0002.json\"\n)\n\nFAKE_TAX_ID = load_fixture(\"tax_id_txi_fakefakefakefakefake0001.json\")\n\n\nFAKE_EVENT_TAX_ID_CREATED = {\n    \"id\": \"evt_16YKQi2eZvKYlo2CT2oe5ff3\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_TAX_ID)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_ZoH080M8fny6yR\",\n    \"type\": \"customer.tax_id.created\",\n}\n\nFAKE_TAX_ID_UPDATED = deepcopy(FAKE_TAX_ID)\nFAKE_TAX_ID_UPDATED[\"verification\"] = {\n    \"status\": \"verified\",\n    \"verified_address\": None,\n    \"verified_name\": \"Test\",\n}\n\nFAKE_EVENT_TAX_ID_UPDATED = {\n    \"id\": \"evt_1J6Fy3JSZQVUcJYgnddjnMzx\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_TAX_ID_UPDATED)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_ZoH080M8fny6yR\",\n    \"type\": \"customer.tax_id.updated\",\n}\n\nFAKE_EVENT_TAX_ID_DELETED = deepcopy(FAKE_EVENT_TAX_ID_UPDATED)\nFAKE_EVENT_TAX_ID_DELETED[\"type\"] = \"customer.tax_id.deleted\"\n\nFAKE_TAX_CODE = load_fixture(\"tax_code_txcd_fakefakefakefakefake0001.json\")\n\nFAKE_INVOICEITEM_II = {\n    \"id\": \"ii_fakefakefakefakefake0001\",\n    \"object\": \"invoiceitem\",\n    \"amount\": 2000,\n    \"currency\": \"usd\",\n    \"customer\": FAKE_CUSTOMER_II[\"id\"],\n    \"date\": 1439033216,\n    \"description\": \"One-time setup fee\",\n    \"discountable\": True,\n    \"discounts\": [],\n    \"invoice\": FAKE_INVOICE_II[\"id\"],\n    \"livemode\": False,\n    \"metadata\": {\"key1\": \"value1\", \"key2\": \"value2\"},\n    \"period\": {\"start\": 1439033216, \"end\": 1439033216},\n    \"plan\": None,\n    \"price\": None,\n    \"proration\": False,\n    \"quantity\": None,\n    \"subscription\": None,\n    \"subscription_item\": None,\n    \"tax_rates\": [],\n    \"unit_amount\": 2000,\n    \"unit_amount_decimal\": \"2000\",\n}\n\nFAKE_INVOICEITEM = {\n    \"id\": \"ii_fakefakefakefakefake0001\",  # todo make these ids unique as well\n    \"object\": \"invoiceitem\",\n    \"amount\": 2000,\n    \"currency\": \"usd\",\n    \"customer\": FAKE_CUSTOMER[\"id\"],\n    \"date\": 1439033216,\n    \"description\": \"One-time setup fee\",\n    \"discountable\": True,\n    \"discounts\": [],\n    \"invoice\": FAKE_INVOICE[\"id\"],\n    \"livemode\": False,\n    \"metadata\": {\"key1\": \"value1\", \"key2\": \"value2\"},\n    \"period\": {\"start\": 1439033216, \"end\": 1439033216},\n    \"plan\": None,\n    \"price\": None,\n    \"proration\": False,\n    \"quantity\": None,\n    \"subscription\": None,\n    \"subscription_item\": None,\n    \"tax_rates\": [],\n    \"unit_amount\": 2000,\n    \"unit_amount_decimal\": \"2000\",\n}\n\n# Invoice item with tax_rates\n# TODO generate this\nFAKE_INVOICEITEM_III = {\n    \"id\": \"ii_fakefakefakefakefake0001\",  # todo make these ids unique as well\n    \"object\": \"invoiceitem\",\n    \"amount\": 2000,\n    \"currency\": \"usd\",\n    \"customer\": FAKE_CUSTOMER_II[\"id\"],\n    \"date\": 1439033216,\n    \"description\": \"One-time setup fee\",\n    \"discountable\": True,\n    \"discounts\": [],\n    \"invoice\": FAKE_INVOICE_II[\"id\"],\n    \"livemode\": False,\n    \"metadata\": {\"key1\": \"value1\", \"key2\": \"value2\"},\n    \"period\": {\"start\": 1439033216, \"end\": 1439033216},\n    \"plan\": None,\n    \"price\": None,\n    \"proration\": False,\n    \"quantity\": None,\n    \"subscription\": None,\n    \"subscription_item\": None,\n    \"tax_rates\": [FAKE_TAX_RATE_EXAMPLE_1_VAT],\n    \"unit_amount\": 2000,\n    \"unit_amount_decimal\": \"2000\",\n}\n\n\nFAKE_TRANSFER = {\n    \"id\": \"tr_16Y9BK2eZvKYlo2CR0ySu1BA\",\n    \"object\": \"transfer\",\n    \"amount\": 100,\n    \"amount_reversed\": 0,\n    \"application_fee_amount\": None,\n    \"balance_transaction\": deepcopy(FAKE_BALANCE_TRANSACTION_II),\n    \"created\": 1439185846,\n    \"currency\": \"usd\",\n    \"description\": \"Test description - 1439185984\",\n    \"destination\": FAKE_STANDARD_ACCOUNT[\"id\"],\n    \"destination_payment\": \"py_16Y9BKFso9hLaeLueFmWAYUi\",\n    \"livemode\": False,\n    \"metadata\": {},\n    \"recipient\": None,\n    \"reversals\": {\n        \"object\": \"list\",\n        \"total_count\": 0,\n        \"has_more\": False,\n        \"url\": \"/v1/transfers/tr_16Y9BK2eZvKYlo2CR0ySu1BA/reversals\",\n        \"data\": [],\n    },\n    \"reversed\": False,\n    \"source_transaction\": None,\n    \"source_type\": \"bank_account\",\n}\n\nFAKE_TRANSFER_WITH_1_REVERSAL = {\n    \"id\": \"tr_16Y9BK2eZvKYlo2CR0ySu1BA\",\n    \"object\": \"transfer\",\n    \"amount\": 100,\n    \"amount_reversed\": 0,\n    \"application_fee_amount\": None,\n    \"balance_transaction\": deepcopy(FAKE_BALANCE_TRANSACTION_II),\n    \"created\": 1439185846,\n    \"currency\": \"usd\",\n    \"description\": \"Test description - 1439185984\",\n    \"destination\": FAKE_STANDARD_ACCOUNT[\"id\"],\n    \"destination_payment\": \"py_16Y9BKFso9hLaeLueFmWAYUi\",\n    \"livemode\": False,\n    \"metadata\": {},\n    \"recipient\": None,\n    \"reversals\": {\n        \"object\": \"list\",\n        \"total_count\": 1,\n        \"has_more\": False,\n        \"url\": \"/v1/transfers/tr_16Y9BK2eZvKYlo2CR0ySu1BA/reversals\",\n        \"data\": [\n            {\n                \"id\": \"trr_1J5UlFJSZQVUcJYgb38m1OZO\",\n                \"object\": \"transfer_reversal\",\n                \"amount\": 20,\n                \"balance_transaction\": deepcopy(FAKE_BALANCE_TRANSACTION_II),\n                \"created\": 1624449653,\n                \"currency\": \"usd\",\n                \"destination_payment_refund\": \"pyr_1J5UlFR44xKqawmIBvFa6gW9\",\n                \"metadata\": {},\n                \"source_refund\": None,\n                \"transfer\": deepcopy(FAKE_TRANSFER),\n            }\n        ],\n    },\n    \"reversed\": False,\n    \"source_transaction\": None,\n    \"source_type\": \"bank_account\",\n}\n\n\nFAKE_USAGE_RECORD = {\n    \"id\": \"mbur_1JPJz2JSZQVUcJYgK4otTE2V\",\n    \"livemode\": False,\n    \"object\": \"usage_record\",\n    \"quantity\": 100,\n    \"subscription_item\": FAKE_SUBSCRIPTION_ITEM[\"id\"],\n    \"timestamp\": 1629174774,\n    \"action\": \"increment\",\n}\n\n\nclass UsageRecordSummaryDict(StripeItem):\n    pass\n\n\nFAKE_USAGE_RECORD_SUMMARY = UsageRecordSummaryDict(\n    load_fixture(\"usage_record_summary_sis_fakefakefakefakefake0001.json\")\n)\n\n\nclass WebhookEndpointDict(StripeItem):\n    pass\n\n\nFAKE_WEBHOOK_ENDPOINT_1 = WebhookEndpointDict(\n    load_fixture(\"webhook_endpoint_fake0001.json\")\n)\n\n\nclass PayoutDict(StripeItem):\n    pass\n\n\nFAKE_PAYOUT_CUSTOM_BANK_ACCOUNT = PayoutDict(\n    load_fixture(\"payout_custom_bank_account.json\")\n)\nFAKE_PAYOUT_CUSTOM_CARD = PayoutDict(load_fixture(\"payout_custom_card.json\"))\n\n\nFAKE_ACCOUNT = {\n    \"id\": \"acct_1032D82eZvKYlo2C\",\n    \"object\": \"account\",\n    \"business_profile\": {\n        \"name\": \"dj-stripe\",\n        \"support_email\": \"djstripe@example.com\",\n        \"support_phone\": None,\n        \"support_url\": \"https://djstripe.com/support/\",\n        \"url\": \"https://djstripe.com\",\n    },\n    \"settings\": {\n        \"branding\": {\n            \"icon\": \"file_4hshrsKatMEEd6736724HYAXyj\",\n            \"logo\": \"file_1E3fssKatMEEd6736724HYAXyj\",\n            \"primary_color\": \"#092e20\",\n        },\n        \"dashboard\": {\"display_name\": \"dj-stripe\", \"timezone\": \"Etc/UTC\"},\n        \"payments\": {\"statement_descriptor\": \"DJSTRIPE\"},\n    },\n    \"charges_enabled\": True,\n    \"country\": \"US\",\n    \"default_currency\": \"usd\",\n    \"details_submitted\": True,\n    \"email\": \"djstripe@example.com\",\n    \"payouts_enabled\": True,\n    \"type\": \"standard\",\n}\n\nFAKE_FILEUPLOAD_LOGO = {\n    \"created\": 1550134074,\n    \"filename\": \"logo_preview.png\",\n    \"id\": \"file_1E3fssKatMEEd6736724HYAXyj\",\n    \"links\": {\n        \"data\": [\n            {\n                \"created\": 1550134074,\n                \"expired\": False,\n                \"expires_at\": 1850134074,\n                \"file\": \"file_1E3fssKatMEEd6736724HYAXyj\",\n                \"id\": \"link_1E3fssKatMEEd673672V0JSH\",\n                \"livemode\": False,\n                \"metadata\": {},\n                \"object\": \"file_link\",\n                \"url\": (\n                    \"https://files.stripe.com/links/fl_test_69vG4ISDx9Chjklasrf06BJeQo\"\n                ),\n            }\n        ],\n        \"has_more\": False,\n        \"object\": \"list\",\n        \"url\": \"/v1/file_links?file=file_1E3fssKatMEEd6736724HYAXyj\",\n    },\n    \"object\": \"file_upload\",\n    \"purpose\": \"business_logo\",\n    \"size\": 6650,\n    \"type\": \"png\",\n    \"url\": \"https://files.stripe.com/files/f_test_BTJFKcS7VDahgkjqw8EVNWlM\",\n}\n\n\nFAKE_FILEUPLOAD_ICON = {\n    \"created\": 1550134074,\n    \"filename\": \"icon_preview.png\",\n    \"id\": \"file_4hshrsKatMEEd6736724HYAXyj\",\n    \"links\": {\n        \"data\": [\n            {\n                \"created\": 1550134074,\n                \"expired\": False,\n                \"expires_at\": 1850134074,\n                \"file\": \"file_4hshrsKatMEEd6736724HYAXyj\",\n                \"id\": \"link_4jsdgsKatMEEd673672V0JSH\",\n                \"livemode\": False,\n                \"metadata\": {},\n                \"object\": \"file_link\",\n                \"url\": (\n                    \"https://files.stripe.com/links/fl_test_69vG4ISDx9Chjklasrf06BJeQo\"\n                ),\n            }\n        ],\n        \"has_more\": False,\n        \"object\": \"list\",\n        \"url\": \"/v1/file_links?file=file_4hshrsKatMEEd6736724HYAXyj\",\n    },\n    \"object\": \"file_upload\",\n    # Note that purpose=\"business_logo\" for both icon and logo fields\n    \"purpose\": \"business_logo\",\n    \"size\": 6650,\n    \"type\": \"png\",\n    \"url\": \"https://files.stripe.com/files/f_test_BTJFKcS7VDahgkjqw8EVNWlM\",\n}\n\nFAKE_EVENT_FILE_CREATED = {\n    \"id\": \"evt_1J5TusR44xKqawmIQVXSrGyf\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_FILEUPLOAD_ICON)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_sTSstDDIOpKi2w\",\n    \"type\": \"file.created\",\n}\n\n\nFAKE_EVENT_ACCOUNT_APPLICATION_DEAUTHORIZED = dict(\n    load_fixture(\"event_account_application_deauthorized.json\")\n)\n\nFAKE_EVENT_ACCOUNT_APPLICATION_AUTHORIZED = dict(\n    load_fixture(\"event_account_application_authorized.json\")\n)\n\nFAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_CREATED = dict(\n    load_fixture(\"event_external_account_bank_account_created.json\")\n)\nFAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_CREATED = dict(\n    load_fixture(\"event_external_account_card_created.json\")\n)\n\nFAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_DELETED = dict(\n    load_fixture(\"event_external_account_bank_account_deleted.json\")\n)\nFAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_DELETED = dict(\n    load_fixture(\"event_external_account_card_deleted.json\")\n)\n\nFAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_UPDATED = dict(\n    load_fixture(\"event_external_account_bank_account_updated.json\")\n)\nFAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_UPDATED = dict(\n    load_fixture(\"event_external_account_card_updated.json\")\n)\n\nFAKE_EVENT_STANDARD_ACCOUNT_UPDATED = dict(\n    load_fixture(\"event_account_updated_standard.json\")\n)\n\n\nFAKE_EVENT_EXPRESS_ACCOUNT_UPDATED = dict(\n    load_fixture(\"event_account_updated_express.json\")\n)\n\nFAKE_EVENT_CUSTOM_ACCOUNT_UPDATED = dict(\n    load_fixture(\"event_account_updated_custom.json\")\n)\n\n# 2017-05-25 api changed request from id to object with id and idempotency_key\n# issue #541\nFAKE_EVENT_PLAN_REQUEST_IS_OBJECT = {\n    \"id\": \"evt_1AcdbXXXXXXXXXXXXXXXXXXX\",\n    \"object\": \"event\",\n    \"api_version\": \"2017-06-05\",\n    \"created\": 1499361420,\n    \"data\": {\"object\": FAKE_PLAN, \"previous_attributes\": {\"name\": \"Plan anual test4\"}},\n    \"livemode\": False,\n    \"pending_webhooks\": 1,\n    \"request\": {\"id\": \"req_AyamqQWoi5AMR2\", \"idempotency_key\": None},\n    \"type\": \"plan.updated\",\n}\n\nFAKE_EVENT_CHARGE_SUCCEEDED = {\n    \"id\": \"evt_16YKQi2eZvKYlo2CT2oe5ff3\",\n    \"object\": \"event\",\n    \"api_version\": \"2016-03-07\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_CHARGE)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6lsB7hkicwhaDj\",\n    \"type\": \"charge.succeeded\",\n}\n\nFAKE_EVENT_TEST_CHARGE_SUCCEEDED = deepcopy(FAKE_EVENT_CHARGE_SUCCEEDED)\nFAKE_EVENT_TEST_CHARGE_SUCCEEDED[\"id\"] = TEST_EVENT_ID\n\nFAKE_EVENT_CUSTOMER_CREATED = {\n    \"id\": \"evt_38DHch3whaDvKYlo2CT2oe5ff3\",\n    \"object\": \"event\",\n    \"api_version\": \"2016-03-07; orders_beta=v3\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_CUSTOMER)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6l38DHch3whaDj\",\n    \"type\": \"customer.created\",\n}\n\nFAKE_EVENT_CUSTOMER_UPDATED = deepcopy(FAKE_EVENT_CUSTOMER_CREATED)\nFAKE_EVENT_CUSTOMER_UPDATED[\"type\"] = \"customer.updated\"\n\n\nFAKE_EVENT_CUSTOMER_DELETED = deepcopy(FAKE_EVENT_CUSTOMER_CREATED)\nFAKE_EVENT_CUSTOMER_DELETED.update(\n    {\"id\": \"evt_38DHch3whaDvKYlo2jksfsFFxy\", \"type\": \"customer.deleted\"}\n)\n\nFAKE_EVENT_CUSTOMER_DISCOUNT_CREATED = {\n    \"id\": \"AGBWvF5zBm4sMCsLLPZrw9YY\",\n    \"object\": \"event\",\n    \"api_version\": \"2018-05-21\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_DISCOUNT_CUSTOMER)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6l38DHch3whaDj\",\n    \"type\": \"customer.discount.created\",\n}\n\n\nFAKE_EVENT_CUSTOMER_DISCOUNT_DELETED = {\n    \"id\": \"AGBWvF5zBm4sMCsLLPZrw9XX\",\n    \"type\": \"customer.discount.deleted\",\n    \"api_version\": \"2017-02-14\",\n    \"created\": 1439229084,\n    \"object\": \"event\",\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6l38DHch3whaDj\",\n    \"data\": {\"object\": deepcopy(FAKE_DISCOUNT_CUSTOMER)},\n}\n\nFAKE_EVENT_CUSTOMER_SOURCE_CREATED = {\n    \"id\": \"evt_DvKYlo38huDvKYlo2C7SXedrZk\",\n    \"object\": \"event\",\n    \"api_version\": \"2016-03-07\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_CARD)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_o3whaDvh3whaDj\",\n    \"type\": \"customer.source.created\",\n}\n\nFAKE_EVENT_CUSTOMER_SOURCE_DELETED = deepcopy(FAKE_EVENT_CUSTOMER_SOURCE_CREATED)\nFAKE_EVENT_CUSTOMER_SOURCE_DELETED.update(\n    {\"id\": \"evt_DvKYlo38huDvKYlo2C7SXedrYk\", \"type\": \"customer.source.deleted\"}\n)\n\nFAKE_EVENT_CUSTOMER_SOURCE_DELETED_DUPE = deepcopy(FAKE_EVENT_CUSTOMER_SOURCE_DELETED)\nFAKE_EVENT_CUSTOMER_SOURCE_DELETED_DUPE.update({\"id\": \"evt_DvKYlo38huDvKYlo2C7SXedzAk\"})\n\nFAKE_EVENT_CUSTOMER_SUBSCRIPTION_CREATED = {\n    \"id\": \"evt_38DHch3wHD2eZvKYlCT2oe5ff3\",\n    \"object\": \"event\",\n    \"api_version\": \"2016-03-07\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_SUBSCRIPTION)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6l87IHch3diaDj\",\n    \"type\": \"customer.subscription.created\",\n}\n\nFAKE_EVENT_CUSTOMER_SUBSCRIPTION_DELETED = deepcopy(\n    FAKE_EVENT_CUSTOMER_SUBSCRIPTION_CREATED\n)\nFAKE_EVENT_CUSTOMER_SUBSCRIPTION_DELETED.update(\n    {\"id\": \"evt_38DHch3wHD2eZvKYlCT2oeryaf\", \"type\": \"customer.subscription.deleted\"}\n)\n\nFAKE_EVENT_DISPUTE_CREATED = {\n    \"id\": \"evt_16YKQi2eZvKYlo2CT2oe5ff3\",\n    \"object\": \"event\",\n    \"api_version\": \"2017-08-15\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_DISPUTE_I)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6lsB7hkicwhaDj\",\n    \"type\": \"charge.dispute.created\",\n}\n\n\nFAKE_EVENT_DISPUTE_FUNDS_WITHDRAWN = {\n    \"id\": \"evt_1JAyTxJSZQVUcJYgNk1Jqu8o\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_DISPUTE_II)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6lsB7hkicwhaDj\",\n    \"type\": \"charge.dispute.funds_withdrawn\",\n}\n\n\nFAKE_EVENT_DISPUTE_UPDATED = {\n    \"id\": \"evt_1JAyTxJSZQVUcJYgNk1Jqu8o\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_DISPUTE_III)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6lsB7hkicwhaDj\",\n    \"type\": \"charge.dispute.funds_withdrawn\",\n}\n\nFAKE_EVENT_DISPUTE_CLOSED = {\n    \"id\": \"evt_1JAyTxJSZQVUcJYgNk1Jqu8o\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_DISPUTE_IV)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6lsB7hkicwhaDj\",\n    \"type\": \"charge.dispute.closed\",\n}\n\nFAKE_EVENT_DISPUTE_FUNDS_REINSTATED_FULL = {\n    \"id\": \"evt_1JAyTxJSZQVUcJYgNk1Jqu8o\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_DISPUTE_V_FULL)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6lsB7hkicwhaDj\",\n    \"type\": \"charge.dispute.funds_reinstated\",\n}\n\nFAKE_EVENT_DISPUTE_FUNDS_REINSTATED_PARTIAL = {\n    \"id\": \"evt_1JAyTxJSZQVUcJYgNk1Jqu8o\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_DISPUTE_V_PARTIAL)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6lsB7hkicwhaDj\",\n    \"type\": \"charge.dispute.funds_reinstated\",\n}\n\nFAKE_EVENT_SESSION_COMPLETED = {\n    \"id\": \"evt_1JAyTxJSZQVUcJYgNk1Jqu8o\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1439229084,\n    \"data\": {\"object\": deepcopy(FAKE_SESSION_I)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6lsB7hkicwhaDj\",\n    \"type\": \"checkout.session.completed\",\n}\n\n\nFAKE_EVENT_INVOICE_CREATED = {\n    \"id\": \"evt_187IHD2eZvKYlo2C6YKQi2eZ\",\n    \"object\": \"event\",\n    \"api_version\": \"2016-03-07\",\n    \"created\": 1462338623,\n    \"data\": {\"object\": deepcopy(FAKE_INVOICE)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_8O4sB7hkDobVT\",\n    \"type\": \"invoice.created\",\n}\n\nFAKE_EVENT_INVOICE_DELETED = deepcopy(FAKE_EVENT_INVOICE_CREATED)\nFAKE_EVENT_INVOICE_DELETED.update(\n    {\"id\": \"evt_187IHD2eZvKYlo2Cjkjsr34H\", \"type\": \"invoice.deleted\"}\n)\n\nFAKE_EVENT_INVOICE_UPCOMING = {\n    \"id\": \"evt_187IHD2eZvKYlo2C6YKQi2bc\",\n    \"object\": \"event\",\n    \"api_version\": \"2017-02-14\",\n    \"created\": 1501859641,\n    \"data\": {\"object\": deepcopy(FAKE_INVOICE)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_8O4sB7hkDobZA\",\n    \"type\": \"invoice.upcoming\",\n}\ndel FAKE_EVENT_INVOICE_UPCOMING[\"data\"][\"object\"][\"id\"]\n\n\nFAKE_EVENT_INVOICEITEM_CREATED = {\n    \"id\": \"evt_187IHD2eZvKYlo2C7SXedrZk\",\n    \"object\": \"event\",\n    \"api_version\": \"2016-03-07\",\n    \"created\": 1462338623,\n    \"data\": {\"object\": deepcopy(FAKE_INVOICEITEM)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_8O4Qbs2EDobDVT\",\n    \"type\": \"invoiceitem.created\",\n}\n\nFAKE_EVENT_INVOICEITEM_DELETED = deepcopy(FAKE_EVENT_INVOICEITEM_CREATED)\nFAKE_EVENT_INVOICEITEM_DELETED.update(\n    {\"id\": \"evt_187IHD2eZvKYloJfdsnnfs34\", \"type\": \"invoiceitem.deleted\"}\n)\n\nFAKE_EVENT_PAYMENT_METHOD_ATTACHED = {\n    \"id\": \"evt_1FDOwDKatMEEd998o5FyxxAB\",\n    \"object\": \"event\",\n    \"api_version\": \"2019-08-14\",\n    \"created\": 1567228549,\n    \"data\": {\"object\": deepcopy(FAKE_PAYMENT_METHOD_I)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": {\"id\": \"req_9c9djVqxUZIKNr\", \"idempotency_key\": None},\n    \"type\": \"payment_method.attached\",\n}\n\nFAKE_EVENT_PAYMENT_METHOD_DETACHED = {\n    \"id\": \"evt_1FDOwDKatMEEd998o5Fdadfds\",\n    \"object\": \"event\",\n    \"api_version\": \"2019-08-14\",\n    \"created\": 1567228549,\n    \"data\": {\"object\": deepcopy(FAKE_PAYMENT_METHOD_I)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": {\"id\": \"req_9c9djVqxcxgdfg\", \"idempotency_key\": None},\n    \"type\": \"payment_method.detached\",\n}\nFAKE_EVENT_PAYMENT_METHOD_DETACHED[\"data\"][\"object\"][\"customer\"] = None\n\nFAKE_EVENT_CARD_PAYMENT_METHOD_ATTACHED = {\n    \"id\": \"evt_1FDOwDKatMEEd998o5Fghgfh\",\n    \"object\": \"event\",\n    \"api_version\": \"2019-08-14\",\n    \"created\": 1567228549,\n    \"data\": {\"object\": deepcopy(FAKE_CARD_AS_PAYMENT_METHOD)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": {\"id\": \"req_9c9djVqxUhgfh\", \"idempotency_key\": None},\n    \"type\": \"payment_method.attached\",\n}\n\nFAKE_EVENT_CARD_PAYMENT_METHOD_DETACHED = {\n    \"id\": \"evt_1FDOwDKatMEEd998o5435345\",\n    \"object\": \"event\",\n    \"api_version\": \"2019-08-14\",\n    \"created\": 1567228549,\n    \"data\": {\"object\": deepcopy(FAKE_CARD_AS_PAYMENT_METHOD)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": {\"id\": \"req_9c9djVqx6tgeg\", \"idempotency_key\": None},\n    \"type\": \"payment_method.detached\",\n}\n# Note that the event from Stripe doesn't have customer = None\n\n\nFAKE_EVENT_PLAN_CREATED = {\n    \"id\": \"evt_1877X72eZvKYlo2CLK6daFxu\",\n    \"object\": \"event\",\n    \"api_version\": \"2016-03-07\",\n    \"created\": 1462297325,\n    \"data\": {\"object\": deepcopy(FAKE_PLAN)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_8NtJXPttxSvFyM\",\n    \"type\": \"plan.created\",\n}\n\nFAKE_EVENT_PLAN_DELETED = deepcopy(FAKE_EVENT_PLAN_CREATED)\nFAKE_EVENT_PLAN_DELETED.update(\n    {\"id\": \"evt_1877X72eZvKYl2jkds32jJFc\", \"type\": \"plan.deleted\"}\n)\n\nFAKE_EVENT_PRICE_CREATED = {\n    \"id\": \"evt_1HlZWCFz0jfFqjGsXOiPW10r\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-03-02\",\n    \"created\": 1604925044,\n    \"data\": {\"object\": deepcopy(FAKE_PRICE)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": {\"id\": \"req_Nq7dDuP0HRrqcP\", \"idempotency_key\": None},\n    \"type\": \"price.created\",\n}\n\nFAKE_EVENT_PRICE_UPDATED = {\n    \"id\": \"evt_1HlZbxFz0jfFqjGsZwiHHf7h\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-03-02\",\n    \"created\": 1604925401,\n    \"data\": {\n        \"object\": FAKE_PRICE,\n        \"previous_attributes\": {\"unit_amount\": 2000, \"unit_amount_decimal\": \"2000\"},\n    },\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": {\"id\": \"req_78pnxbwPMvOIwe\", \"idempotency_key\": None},\n    \"type\": \"price.updated\",\n}\n\nFAKE_EVENT_PRICE_DELETED = deepcopy(FAKE_EVENT_PRICE_CREATED)\nFAKE_EVENT_PRICE_DELETED.update(\n    {\"id\": \"evt_1HlZelFz0jfFqjGs0F4BML2l\", \"type\": \"price.deleted\"}\n)\n\nFAKE_EVENT_TRANSFER_CREATED = {\n    \"id\": \"evt_16igNU2eZvKYlo2CYyMkYvet\",\n    \"object\": \"event\",\n    \"api_version\": \"2016-03-07\",\n    \"created\": 1441696732,\n    \"data\": {\"object\": deepcopy(FAKE_TRANSFER)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": \"req_6wZW9MskhYU15Y\",\n    \"type\": \"transfer.created\",\n}\n\nFAKE_EVENT_TRANSFER_DELETED = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\nFAKE_EVENT_TRANSFER_DELETED.update(\n    {\"id\": \"evt_16igNU2eZvKjklfsdjk232Mf\", \"type\": \"transfer.deleted\"}\n)\n\nFAKE_TOKEN = {\n    \"id\": \"tok_16YDIe2eZvKYlo2CPvqprIJd\",\n    \"object\": \"token\",\n    \"card\": deepcopy(FAKE_CARD),\n    \"client_ip\": None,\n    \"created\": 1439201676,\n    \"livemode\": False,\n    \"type\": \"card\",\n    \"used\": False,\n}\n\n\nFAKE_EVENT_PAYMENT_INTENT_SUCCEEDED_DESTINATION_CHARGE = {\n    \"id\": \"evt_1FG74XB7kbjcJ8Qq22i2BPdt\",\n    \"object\": \"event\",\n    \"api_version\": \"2019-05-16\",\n    \"created\": 1567874857,\n    \"data\": {\"object\": deepcopy(FAKE_PAYMENT_INTENT_DESTINATION_CHARGE)},\n    \"livemode\": False,\n    \"pending_webhooks\": 1,\n    \"request\": {\"id\": \"req_AJAmnJE4eiPIzb\", \"idempotency_key\": None},\n    \"type\": \"payment_intent.succeeded\",\n}\n\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED = {\n    \"id\": \"evt_1Hm7q6Fz0jfFqjGsJSG4N91w\",\n    \"object\": \"event\",\n    \"api_version\": \"2020-03-02\",\n    \"created\": 1605056974,\n    \"data\": {\"object\": deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)},\n    \"livemode\": False,\n    \"pending_webhooks\": 0,\n    \"request\": {\n        \"id\": \"req_Pttj3aW5RJwees\",\n        \"idempotency_key\": \"d2a77191-cc07-4c60-abab-5fb11357bd63\",\n    },\n    \"type\": \"subscription_schedule.created\",\n}\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED[\"data\"][\"object\"][\"status\"] = \"active\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED[\"data\"][\"object\"][\"current_phase\"][\n    \"start_data\"\n] = 1602464974\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED[\"data\"][\"object\"][\"current_phase\"][\n    \"end_data\"\n] = 1605056974\n\n\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_UPDATED = deepcopy(\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED\n)\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_UPDATED[\"id\"] = \"sub_sched_1Hm86MFz0jfFqjGsc5iEdZee\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_UPDATED[\"type\"] = \"subscription_schedule.updated\"\n\n\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_RELEASED = deepcopy(\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED\n)\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_RELEASED[\"id\"] = \"evt_1Hm878Fz0jfFqjGsClU9gE79\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_RELEASED[\"type\"] = \"subscription_schedule.released\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_RELEASED[\"data\"][\"object\"][\"released_at\"] = 1605058030\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_RELEASED[\"data\"][\"object\"][\"status\"] = \"released\"\n\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_CANCELED = deepcopy(\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED\n)\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_CANCELED[\"id\"] = \"evt_1Hm80YFz0jfFqjGs7kKvT7RE\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_CANCELED[\"type\"] = \"subscription_schedule.canceled\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_CANCELED[\"data\"][\"object\"][\"canceled_at\"] = 1605057622\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_CANCELED[\"data\"][\"object\"][\"status\"] = \"canceled\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_CANCELED[\"data\"][\"previous_attributes\"] = {\n    \"released_at\": None,\n    \"status\": \"not_started\",\n}\n\n\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_COMPLETED = deepcopy(\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED\n)\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_COMPLETED[\"id\"] = \"evt_1Hm80YFz0jfFqjGs7kKvT7RE\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_COMPLETED[\"type\"] = \"subscription_schedule.completed\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_COMPLETED[\"data\"][\"object\"][\n    \"completed_at\"\n] = 1605057622\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_COMPLETED[\"data\"][\"object\"][\"status\"] = \"completed\"\n\n\n# would get emmited 7 days before the scheduled end_date\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_EXPIRING = deepcopy(\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED\n)\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_EXPIRING[\"id\"] = \"evt_1Hm80YFz0jfFqjGs7kKvT7RE\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_EXPIRING[\"type\"] = \"subscription_schedule.expiring\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_EXPIRING[\"created\"] = 1602464900\n\n\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_ABORTED = deepcopy(\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED\n)\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_ABORTED[\"id\"] = \"evt_1Hm80YFz0jfFqjGs7kKvT7RE\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_ABORTED[\"type\"] = \"subscription_schedule.aborted\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_ABORTED[\"data\"][\"object\"][\"canceled_at\"] = 1605057622\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_ABORTED[\"data\"][\"object\"][\"status\"] = \"canceled\"\nFAKE_EVENT_SUBSCRIPTION_SCHEDULE_ABORTED[\"data\"][\"previous_attributes\"] = {\n    \"released_at\": None,\n    \"status\": \"not_started\",\n}\n"
  },
  {
    "path": "tests/apps/__init__.py",
    "content": ""
  },
  {
    "path": "tests/apps/example/__init__.py",
    "content": ""
  },
  {
    "path": "tests/apps/example/forms.py",
    "content": "from django import forms\n\nimport djstripe.models\n\n\nclass PurchaseSubscriptionForm(forms.Form):\n    email = forms.EmailField()\n    plan = forms.ModelChoiceField(queryset=djstripe.models.Plan.objects.all())\n    stripe_source = forms.CharField(\n        max_length=\"255\", widget=forms.HiddenInput(), required=False\n    )\n\n\nclass PaymentIntentForm(forms.Form):\n    pass\n"
  },
  {
    "path": "tests/apps/example/management/__init__.py",
    "content": ""
  },
  {
    "path": "tests/apps/example/management/commands/__init__.py",
    "content": ""
  },
  {
    "path": "tests/apps/example/management/commands/regenerate_test_fixtures.py",
    "content": "import json\nfrom copy import deepcopy\n\nimport stripe.api_resources\nimport stripe.stripe_object\nfrom django.core.management import BaseCommand\nfrom stripe.error import InvalidRequestError\n\nimport djstripe.models\nimport tests\nfrom djstripe import settings as djstripe_settings\nfrom djstripe.utils import get_id_from_stripe_data\n\n\"\"\"\nKey used to store fake ids in the real stripe object's metadata dict\n\"\"\"\nFAKE_ID_METADATA_KEY = \"djstripe_test_fake_id\"\n\n\nclass Command(BaseCommand):\n    \"\"\"\n    This does the following:\n\n    1) Load existing fixtures from JSON files\n    2) Attempts to read the corresponding objects from Stripe\n    3) If found, for types Stripe doesn't allow us to choose ids for,\n        we build a map between the fake ids in the fixtures and real Stripe ids\n    3) If not found, creates objects in Stripe from the fixtures\n    4) Save objects back as fixtures, using fake ids if available\n\n    The rationale for this is so that the fixtures can automatically be updated\n    with Stripe schema changes running this command.\n\n    This should make keeping our tests and model schema compatible with Stripe\n    schema changes less pain-staking and simplify the process of upgrading\n    the targeted Stripe API version.\n    \"\"\"\n\n    help = \"Command to update test fixtures using a real Stripe account.\"\n\n    fake_data_map = {}  # type: Dict[Type[djstripe.models.StripeModel], List]\n    fake_id_map = {}  # type: Dict[str, str]\n\n    def add_arguments(self, parser):\n        parser.add_argument(\n            \"--delete-stale\",\n            action=\"store_true\",\n            help=\"Delete any untouched fixtures in the directory\",\n        )\n        parser.add_argument(\n            \"--update-sideeffect-fields\",\n            action=\"store_true\",\n            help=\"Don't preserve sideeffect fields such as 'created'\",\n        )\n\n    def handle(self, *args, **options):\n        do_delete_stale_fixtures = options[\"delete_stale\"]\n        do_preserve_sideeffect_fields = not options[\"update_sideeffect_fields\"]\n        common_readonly_fields = [\"object\", \"created\", \"updated\", \"livemode\"]\n        common_sideeffect_fields = [\"created\"]\n\n        # TODO - is it be possible to get a list of which fields are writable from\n        #  the API?  maybe using https://github.com/stripe/openapi ?\n        #  (though that's only for current version)\n\n        \"\"\"\n        Fields that we treat as read-only.\n        Most of these will cause an error if sent to the Stripe API.\n        \"\"\"\n        model_extra_readonly_fields = {\n            djstripe.models.Account: [\"id\"],\n            djstripe.models.Customer: [\n                \"account_balance\",\n                \"currency\",\n                \"default_source\",\n                \"delinquent\",\n                \"invoice_prefix\",\n                \"subscriptions\",\n                \"sources\",\n            ],\n            djstripe.models.BankAccount: [\n                \"id\",\n                \"bank_name\",\n                \"customer\",\n                \"last4\",\n                \"fingerprint\",\n                \"status\",\n            ],\n            djstripe.models.Card: [\n                \"id\",\n                \"address_line1_check\",\n                \"address_zip_check\",\n                \"brand\",\n                \"country\",\n                \"customer\",\n                \"cvc_check\",\n                \"dynamic_last4\",\n                \"exp_month\",\n                \"exp_year\",\n                \"fingerprint\",\n                \"funding\",\n                \"last4\",\n                \"tokenization_method\",\n            ],\n            djstripe.models.PaymentIntent: [\"id\"],\n            djstripe.models.PaymentMethod: [\"id\"],\n            djstripe.models.Plan: [\n                # Can only specify one of amount and amount_decimal\n                \"amount_decimal\"\n            ],\n            djstripe.models.Source: [\n                \"id\",\n                \"amount\",\n                \"card\",\n                \"client_secret\",\n                \"currency\",\n                \"customer\",\n                \"flow\",\n                \"owner\",\n                \"statement_descriptor\",\n                \"status\",\n                \"type\",\n                \"usage\",\n            ],\n            djstripe.models.Subscription: [\n                \"id\",\n                # not actually read-only\n                \"billing_cycle_anchor\",\n                \"billing\",\n                \"current_period_end\",\n                \"current_period_start\",\n                # workaround for \"the\n                # `invoice_customer_balance_settings[consume_applied_balance_on_void]`\n                # parameter is only supported in API version 2019-11-05 and below.\n                # See\n                # https://stripe.com/docs/api#versioning and\n                # https://stripe.com/docs/upgrades#2019-12-03 for more detail.\n                \"invoice_customer_balance_settings\",\n                \"latest_invoice\",\n                \"start\",\n                \"start_date\",\n                \"status\",\n            ],\n            djstripe.models.TaxRate: [\"id\"],\n        }  # type: Dict[Type[djstripe.models.StripeModel], List[str]]\n\n        \"\"\"\n        Fields that we don't care about the value of, and that preserving\n        allows us to avoid churn in the fixtures\n        \"\"\"\n        model_sideeffect_fields = {\n            djstripe.models.BalanceTransaction: [\"available_on\"],\n            djstripe.models.Source: [\"client_secret\"],\n            djstripe.models.Charge: [\"receipt_url\"],\n            djstripe.models.Subscription: [\n                \"billing_cycle_anchor\",\n                \"current_period_start\",\n                \"current_period_end\",\n                \"start\",\n                \"start_date\",\n            ],\n            djstripe.models.SubscriptionItem: [\n                # we don't currently track separate fixtures for SubscriptionItems\n                \"id\"\n            ],\n            djstripe.models.Product: [\"updated\"],\n            djstripe.models.Invoice: [\n                \"date\",\n                \"finalized_at\",\n                \"hosted_invoice_url\",\n                \"invoice_pdf\",\n                \"webhooks_delivered_at\",\n                \"period_start\",\n                \"period_end\",\n                # we don't currently track separate fixtures for SubscriptionItems\n                \"subscription_item\",\n            ],\n        }  # type: Dict[Type[djstripe.models.StripeModel], List[str]]\n\n        object_sideeffect_fields = {\n            model.stripe_class.OBJECT_NAME: set(v)\n            for model, v in model_sideeffect_fields.items()\n        }  # type: Dict[str, Set[str]]\n\n        self.fake_data_map = {\n            # djstripe.models.Account: [tests.FAKE_ACCOUNT],\n            djstripe.models.Customer: [\n                tests.FAKE_CUSTOMER,\n                tests.FAKE_CUSTOMER_II,\n                tests.FAKE_CUSTOMER_III,\n                tests.FAKE_CUSTOMER_IV,\n            ],\n            djstripe.models.BankAccount: [tests.FAKE_BANK_ACCOUNT_SOURCE],\n            djstripe.models.Card: [\n                tests.FAKE_CARD,\n                tests.FAKE_CARD_II,\n                tests.FAKE_CARD_III,\n            ],\n            djstripe.models.Source: [tests.FAKE_SOURCE],\n            djstripe.models.Plan: [tests.FAKE_PLAN, tests.FAKE_PLAN_II],\n            djstripe.models.Price: [tests.FAKE_PRICE, tests.FAKE_PRICE_II],\n            djstripe.models.Product: [tests.FAKE_PRODUCT],\n            djstripe.models.TaxRate: [\n                tests.FAKE_TAX_RATE_EXAMPLE_1_VAT,\n                tests.FAKE_TAX_RATE_EXAMPLE_2_SALES,\n            ],\n            djstripe.models.Subscription: [\n                tests.FAKE_SUBSCRIPTION,\n                tests.FAKE_SUBSCRIPTION_II,\n                tests.FAKE_SUBSCRIPTION_III,\n                tests.FAKE_SUBSCRIPTION_MULTI_PLAN,\n            ],\n            djstripe.models.SubscriptionSchedule: [\n                tests.FAKE_SUBSCRIPTION_SCHEDULE,\n            ],\n            djstripe.models.Invoice: [tests.FAKE_INVOICE, tests.FAKE_INVOICE_IV],\n            djstripe.models.Charge: [tests.FAKE_CHARGE],\n            djstripe.models.PaymentIntent: [tests.FAKE_PAYMENT_INTENT_I],\n            djstripe.models.PaymentMethod: [\n                tests.FAKE_PAYMENT_METHOD_I,\n                tests.FAKE_CARD_AS_PAYMENT_METHOD,\n            ],\n            djstripe.models.BalanceTransaction: [tests.FAKE_BALANCE_TRANSACTION],\n        }\n\n        self.init_fake_id_map()\n\n        objs = []\n\n        # Regenerate each of the fixture objects via Stripe\n        # We re-fetch objects in a second pass if they were created during\n        # the first pass, to ensure nested objects are up to date\n        # (eg Customer.subscriptions),\n        for n in range(2):\n            any_created = False\n            self.stdout.write(f\"Updating fixture objects, pass {n}\")\n\n            # reset the objects list since we don't want to keep those from\n            # the first pass\n            objs.clear()\n\n            for model_class, old_objs in self.fake_data_map.items():\n                readonly_fields = (\n                    common_readonly_fields\n                    + model_extra_readonly_fields.get(model_class, [])\n                )\n\n                for old_obj in old_objs:\n                    created, obj = self.update_fixture_obj(\n                        old_obj=deepcopy(old_obj),\n                        model_class=model_class,\n                        readonly_fields=readonly_fields,\n                        do_preserve_sideeffect_fields=do_preserve_sideeffect_fields,\n                        object_sideeffect_fields=object_sideeffect_fields,\n                        common_sideeffect_fields=common_sideeffect_fields,\n                    )\n\n                    objs.append(obj)\n                    any_created = created or any_created\n\n            if not any_created:\n                # nothing created on this pass, no need to continue\n                break\n        else:\n            self.stderr.write(\n                \"Warning, unexpected behaviour - some fixtures still being created \"\n                \"in second pass?\"\n            )\n\n        # Now the fake_id_map should be complete and the objs should be up to date,\n        # save all the fixtures\n        paths = set()\n        for obj in objs:\n            path = self.save_fixture(obj)\n            paths.add(path)\n\n        if do_delete_stale_fixtures:\n            for path in tests.FIXTURE_DIR_PATH.glob(\"*.json\"):\n                if path in paths:\n                    continue\n                else:\n                    self.stdout.write(f\"deleting {path!r}\")\n                    path.unlink()\n\n    def init_fake_id_map(self):\n        \"\"\"\n        Build a mapping between fake ids stored in Stripe metadata and those obj's\n        actual ids\n\n        We do this so we can have fixtures with stable ids for objects Stripe doesn't\n        allow us to specify an id for (eg Card).\n\n        Fixtures and tests will use the fake ids, when we talk to stripe we use the\n        real ids\n        :return:\n        \"\"\"\n\n        for fake_customer in self.fake_data_map[djstripe.models.Customer]:\n            try:\n                # can only access Cards via the customer\n                customer = djstripe.models.Customer(\n                    id=fake_customer[\"id\"]\n                ).api_retrieve()\n            except InvalidRequestError:\n                self.stdout.write(\n                    f\"Fake customer {fake_customer['id']} doesn't exist in Stripe yet\"\n                )\n                return\n\n            # assume that test customers don't have more than 100 cards...\n            for card in customer.sources.list(limit=100):\n                self.update_fake_id_map(card)\n\n            for payment_method in djstripe.models.PaymentMethod.api_list(\n                customer=customer.id, type=\"card\"\n            ):\n                self.update_fake_id_map(payment_method)\n\n            for subscription in customer[\"subscriptions\"][\"data\"]:\n                self.update_fake_id_map(subscription)\n\n        for tax_rate in djstripe.models.TaxRate.api_list():\n            self.update_fake_id_map(tax_rate)\n\n    def update_fake_id_map(self, obj):\n        fake_id = self.get_fake_id(obj)\n        actual_id = obj[\"id\"]\n\n        if fake_id:\n            if fake_id in self.fake_id_map:\n                assert self.fake_id_map[fake_id] == actual_id, (\n                    f\"Duplicate fake_id {fake_id} - reset your test Stripe data at \"\n                    f\"https://dashboard.stripe.com/account/data\"\n                )\n\n            self.fake_id_map[fake_id] = actual_id\n\n            return fake_id\n        else:\n            return actual_id\n\n    def get_fake_id(self, obj):\n        \"\"\"\n        Get a stable fake id from a real Stripe object, we use this so that fixtures\n         are stable\n        :param obj:\n        :return:\n        \"\"\"\n        fake_id = None\n\n        if isinstance(obj, str):\n            real_id = obj\n            real_id_map = {v: k for k, v in self.fake_id_map.items()}\n\n            fake_id = real_id_map.get(real_id)\n        elif \"metadata\" in obj:\n            # Note: not all objects have a metadata dict\n            # (eg Account, BalanceTransaction don't)\n            fake_id = obj.get(\"metadata\", {}).get(FAKE_ID_METADATA_KEY)\n        elif obj.get(\"object\") == \"balance_transaction\":\n            # assume for purposes of fixture generation that 1 balance_transaction per\n            # source charge (etc)\n            fake_source_id = self.get_fake_id(obj[\"source\"])\n\n            fake_id = f\"txn_fake_{fake_source_id}\"\n\n        return fake_id\n\n    def fake_json_ids(self, json_str):\n        \"\"\"\n        Replace real ids with fakes ones in the JSON fixture\n\n        Do this on the serialized JSON string since it's a simple string replace\n        :param json_str:\n        :return:\n        \"\"\"\n        for fake_id, actual_id in self.fake_id_map.items():\n            json_str = json_str.replace(actual_id, fake_id)\n\n        return json_str\n\n    def unfake_json_ids(self, json_str):\n        \"\"\"\n        Replace fake ids with actual ones in the JSON fixture\n\n        Do this on the serialized JSON string since it's a simple string replace\n        :param json_str:\n        :return:\n        \"\"\"\n        for fake_id, actual_id in self.fake_id_map.items():\n            json_str = json_str.replace(fake_id, actual_id)\n\n            # special-case: undo the replace for the djstripe_test_fake_id in metadata\n            json_str = json_str.replace(\n                f'\"{FAKE_ID_METADATA_KEY}\": \"{actual_id}\"',\n                f'\"{FAKE_ID_METADATA_KEY}\": \"{fake_id}\"',\n            )\n\n        return json_str\n\n    def update_fixture_obj(\n        self,\n        old_obj,\n        model_class,\n        readonly_fields,\n        do_preserve_sideeffect_fields,\n        object_sideeffect_fields,\n        common_sideeffect_fields,\n    ):\n        \"\"\"\n        Given a fixture object, update it via stripe\n        :param model_class:\n        :param old_obj:\n        :param readonly_fields:\n        :return:\n        \"\"\"\n\n        # restore real ids from Stripe\n        old_obj = json.loads(self.unfake_json_ids(json.dumps(old_obj)))\n\n        id_ = old_obj[\"id\"]\n\n        self.stdout.write(f\"{model_class.__name__} {id_}\", ending=\"\")\n\n        # For objects that we can't directly choose the ids of\n        # (and that will thus vary between stripe accounts)\n        # we fetch the id from a related object\n        if issubclass(model_class, djstripe.models.Account):\n            created, obj = self.get_or_create_stripe_account(\n                old_obj=old_obj, readonly_fields=readonly_fields\n            )\n        elif issubclass(model_class, djstripe.models.BankAccount):\n            created, obj = self.get_or_create_stripe_bank_account(\n                old_obj=old_obj, readonly_fields=readonly_fields\n            )\n        elif issubclass(model_class, djstripe.models.Card):\n            created, obj = self.get_or_create_stripe_card(\n                old_obj=old_obj, readonly_fields=readonly_fields\n            )\n        elif issubclass(model_class, djstripe.models.Source):\n            created, obj = self.get_or_create_stripe_source(\n                old_obj=old_obj, readonly_fields=readonly_fields\n            )\n        elif issubclass(model_class, djstripe.models.Invoice):\n            created, obj = self.get_or_create_stripe_invoice(\n                old_obj=old_obj, writable_fields=[\"metadata\"]\n            )\n        elif issubclass(model_class, djstripe.models.Charge):\n            created, obj = self.get_or_create_stripe_charge(\n                old_obj=old_obj, writable_fields=[\"metadata\"]\n            )\n        elif issubclass(model_class, djstripe.models.PaymentIntent):\n            created, obj = self.get_or_create_stripe_payment_intent(\n                old_obj=old_obj, writable_fields=[\"metadata\"]\n            )\n        elif issubclass(model_class, djstripe.models.PaymentMethod):\n            created, obj = self.get_or_create_stripe_payment_method(\n                old_obj=old_obj, writable_fields=[\"metadata\"]\n            )\n        elif issubclass(model_class, djstripe.models.BalanceTransaction):\n            created, obj = self.get_or_create_stripe_balance_transaction(\n                old_obj=old_obj\n            )\n        else:\n            try:\n                # fetch from Stripe, using the active API version\n                # this allows us regenerate the fixtures from Stripe\n                # and hopefully, automatically get schema changes\n                obj = model_class(id=id_).api_retrieve()\n                created = False\n\n                self.stdout.write(\"    found\")\n            except InvalidRequestError:\n                self.stdout.write(\"    creating\")\n\n                create_obj = deepcopy(old_obj)\n\n                # create in Stripe\n                for k in readonly_fields:\n                    create_obj.pop(k, None)\n\n                if issubclass(model_class, djstripe.models.Subscription):\n                    create_obj = self.pre_process_subscription(create_obj=create_obj)\n\n                obj = model_class._api_create(**create_obj)\n                created = True\n\n        self.update_fake_id_map(obj)\n\n        if do_preserve_sideeffect_fields:\n            obj = self.preserve_old_sideeffect_values(\n                old_obj=old_obj,\n                new_obj=obj,\n                object_sideeffect_fields=object_sideeffect_fields,\n                common_sideeffect_fields=common_sideeffect_fields,\n            )\n\n        return created, obj\n\n    def get_or_create_stripe_account(self, old_obj, readonly_fields):\n        obj = djstripe.models.Account().api_retrieve()\n\n        return True, obj\n\n    def get_or_create_stripe_bank_account(self, old_obj, readonly_fields):\n        customer_id = old_obj[\"customer\"]\n\n        try:\n            obj = stripe.Customer.retrieve_source(customer_id, old_obj[\"id\"])\n            created = False\n\n            self.stdout.write(\"    found\")\n        except InvalidRequestError:\n            self.stdout.write(\"    creating\")\n\n            create_obj = deepcopy(old_obj)\n\n            # create in Stripe\n            for k in readonly_fields:\n                create_obj.pop(k, None)\n\n            # see https://stripe.com/docs/connect/testing#account-numbers\n            # we've stash the account number in the metadata\n            # so we can regenerate the fixture\n            create_obj[\"account_number\"] = old_obj[\"metadata\"][\n                \"djstripe_test_fixture_account_number\"\n            ]\n            create_obj[\"object\"] = \"bank_account\"\n\n            obj = stripe.Customer.create_source(customer_id, source=create_obj)\n\n            created = True\n\n        return created, obj\n\n    def get_or_create_stripe_card(self, old_obj, readonly_fields):\n        customer_id = old_obj[\"customer\"]\n\n        try:\n            obj = stripe.Customer.retrieve_source(customer_id, old_obj[\"id\"])\n            created = False\n\n            self.stdout.write(\"    found\")\n        except InvalidRequestError:\n            self.stdout.write(\"    creating\")\n\n            create_obj = deepcopy(old_obj)\n\n            # create in Stripe\n            for k in readonly_fields:\n                create_obj.pop(k, None)\n\n            obj = stripe.Customer.create_source(**{\"source\": \"tok_visa\"})\n\n            for k, v in create_obj.items():\n                setattr(obj, k, v)\n\n            obj.save()\n            created = True\n\n        return created, obj\n\n    def get_or_create_stripe_source(self, old_obj, readonly_fields):\n        customer_id = old_obj[\"customer\"]\n\n        try:\n            obj = stripe.Customer.retrieve_source(customer_id, old_obj[\"id\"])\n            created = False\n\n            self.stdout.write(\"    found\")\n        except InvalidRequestError:\n            self.stdout.write(\"    creating\")\n\n            create_obj = deepcopy(old_obj)\n\n            # create in Stripe\n            for k in readonly_fields:\n                create_obj.pop(k, None)\n\n            source_obj = djstripe.models.Source._api_create(\n                **{\"token\": \"tok_visa\", \"type\": \"card\"}\n            )\n\n            obj = stripe.Customer.create_source(**{\"source\": source_obj.id})\n\n            for k, v in create_obj.items():\n                setattr(obj, k, v)\n\n            obj.save()\n            created = True\n\n        return created, obj\n\n    def get_or_create_stripe_invoice(self, old_obj, writable_fields):\n        subscription = djstripe.models.Subscription(\n            id=old_obj[\"subscription\"]\n        ).api_retrieve()\n        id_ = subscription[\"latest_invoice\"]\n\n        try:\n            obj = djstripe.models.Invoice(id=id_).api_retrieve()\n            created = False\n\n            self.stdout.write(f\"    found {id_}\")\n        except InvalidRequestError:\n            assert False, \"Expected to find invoice via subscription\"\n\n        for k in writable_fields:\n            if isinstance(obj.get(k), dict):\n                # merge dicts (eg metadata)\n                obj[k].update(old_obj.get(k, {}))\n            else:\n                obj[k] = old_obj[k]\n\n        obj.save()\n\n        return created, obj\n\n    def get_or_create_stripe_charge(self, old_obj, writable_fields):\n        invoice = djstripe.models.Invoice(id=old_obj[\"invoice\"]).api_retrieve()\n        id_ = invoice[\"charge\"]\n\n        try:\n            obj = djstripe.models.Charge(id=id_).api_retrieve()\n            created = False\n\n            self.stdout.write(f\"    found {id_}\")\n        except InvalidRequestError:\n            assert False, \"Expected to find charge via invoice\"\n\n        for k in writable_fields:\n            if isinstance(obj.get(k), dict):\n                # merge dicts (eg metadata)\n                obj[k].update(old_obj.get(k, {}))\n            else:\n                obj[k] = old_obj[k]\n\n        obj.save()\n\n        return created, obj\n\n    def get_or_create_stripe_payment_intent(self, old_obj, writable_fields):\n        invoice = djstripe.models.Invoice(id=old_obj[\"invoice\"]).api_retrieve()\n        id_ = invoice[\"payment_intent\"]\n\n        try:\n            obj = djstripe.models.PaymentIntent(id=id_).api_retrieve()\n            created = False\n\n            self.stdout.write(f\"    found {id_}\")\n        except InvalidRequestError:\n            assert False, \"Expected to find payment_intent via invoice\"\n\n        for k in writable_fields:\n            if isinstance(obj.get(k), dict):\n                # merge dicts (eg metadata)\n                obj[k].update(old_obj.get(k, {}))\n            else:\n                obj[k] = old_obj[k]\n\n        obj.save()\n\n        return created, obj\n\n    def get_or_create_stripe_payment_method(self, old_obj, writable_fields):\n        id_ = old_obj[\"id\"]\n        customer_id = old_obj[\"customer\"]\n        type_ = old_obj[\"type\"]\n\n        try:\n            obj = djstripe.models.PaymentMethod(id=id_).api_retrieve()\n            created = False\n\n            self.stdout.write(\"    found\")\n        except InvalidRequestError:\n            self.stdout.write(\"    creating\")\n\n            obj = djstripe.models.PaymentMethod()._api_create(\n                type=type_, card={\"token\": \"tok_visa\"}\n            )\n\n            stripe.PaymentMethod.attach(\n                obj[\"id\"],\n                customer=customer_id,\n                api_key=djstripe_settings.djstripe_settings.STRIPE_SECRET_KEY,\n            )\n\n            for k in writable_fields:\n                if isinstance(obj.get(k), dict):\n                    # merge dicts (eg metadata)\n                    obj[k].update(old_obj.get(k, {}))\n                else:\n                    obj[k] = old_obj[k]\n\n            obj.save()\n\n            created = True\n\n        return created, obj\n\n    def get_or_create_stripe_balance_transaction(self, old_obj):\n        source = old_obj[\"source\"]\n\n        if source.startswith(\"ch_\"):\n            charge = djstripe.models.Charge(id=source).api_retrieve()\n            id_ = get_id_from_stripe_data(charge[\"balance_transaction\"])\n\n        try:\n            obj = djstripe.models.BalanceTransaction(id=id_).api_retrieve()\n            created = False\n\n            self.stdout.write(f\"    found {id_}\")\n        except InvalidRequestError:\n            assert False, \"Expected to find balance transaction via source\"\n\n        return created, obj\n\n    def save_fixture(self, obj):\n        type_name = obj[\"object\"]\n        id_ = self.update_fake_id_map(obj)\n\n        fixture_path = tests.FIXTURE_DIR_PATH.joinpath(f\"{type_name}_{id_}.json\")\n\n        with fixture_path.open(\"w\") as f:\n            json_str = self.fake_json_ids(json.dumps(obj, indent=4))\n\n            f.write(json_str)\n\n        return fixture_path\n\n    def pre_process_subscription(self, create_obj):\n        # flatten plan/items/tax rates on create\n\n        items = create_obj.get(\"items\", {}).get(\"data\", [])\n\n        if len(items):\n            # don't try and create with both plan and item (list of plans)\n            create_obj.pop(\"plan\", None)\n            create_obj.pop(\"quantity\", None)\n\n            # TODO - move this to SubscriptionItem handling?\n            subscription_item_create_fields = {\n                \"plan\",\n                \"billing_thresholds\",\n                \"metadata\",\n                \"quantity\",\n                \"tax_rates\",\n            }\n            create_items = []\n\n            for item in items:\n                create_item = {\n                    k: v\n                    for k, v in item.items()\n                    if k in subscription_item_create_fields\n                }\n\n                create_item[\"plan\"] = get_id_from_stripe_data(create_item[\"plan\"])\n\n                if create_item.get(\"tax_rates\", []):\n                    create_item[\"tax_rates\"] = [\n                        get_id_from_stripe_data(t) for t in create_item[\"tax_rates\"]\n                    ]\n\n                create_items.append(create_item)\n\n            create_obj[\"items\"] = create_items\n        else:\n            # don't try and send empty items list\n            create_obj.pop(\"items\", None)\n            create_obj[\"plan\"] = get_id_from_stripe_data(create_obj[\"plan\"])\n\n        if create_obj.get(\"default_tax_rates\", []):\n            create_obj[\"default_tax_rates\"] = [\n                get_id_from_stripe_data(t) for t in create_obj[\"default_tax_rates\"]\n            ]\n\n            # don't send both default_tax_rates and tax_percent\n            create_obj.pop(\"tax_percent\", None)\n\n        return create_obj\n\n    def preserve_old_sideeffect_values(\n        self, old_obj, new_obj, object_sideeffect_fields, common_sideeffect_fields\n    ):\n        \"\"\"\n        Try to preserve values of side-effect fields from old_obj,\n        to reduce churn in fixtures\n        \"\"\"\n        object_name = new_obj.get(\"object\")\n        sideeffect_fields = object_sideeffect_fields.get(object_name, set()).union(\n            set(common_sideeffect_fields)\n        )\n\n        old_obj = old_obj or {}\n\n        for f, old_val in old_obj.items():\n            try:\n                new_val = new_obj[f]\n            except KeyError:\n                continue\n\n            if isinstance(new_val, stripe.api_resources.ListObject):\n                # recursively process nested lists\n                for n, (old_val_item, new_val_item) in enumerate(\n                    zip(old_val.get(\"data\", []), new_val.data)\n                ):\n                    new_val.data[n] = self.preserve_old_sideeffect_values(\n                        old_obj=old_val_item,\n                        new_obj=new_val_item,\n                        object_sideeffect_fields=object_sideeffect_fields,\n                        common_sideeffect_fields=common_sideeffect_fields,\n                    )\n            elif isinstance(new_val, stripe.stripe_object.StripeObject):\n                # recursively process nested objects\n                new_obj[f] = self.preserve_old_sideeffect_values(\n                    old_obj=old_val,\n                    new_obj=new_val,\n                    object_sideeffect_fields=object_sideeffect_fields,\n                    common_sideeffect_fields=common_sideeffect_fields,\n                )\n            elif (\n                f in sideeffect_fields\n                and type(old_val) == type(new_val)\n                and old_val != new_val\n            ):\n                # only preserve old values if the type is the same\n                new_obj[f] = old_val\n\n        return new_obj\n"
  },
  {
    "path": "tests/apps/example/templates/checkout.html",
    "content": "<html>\n  <head>\n    <title>Checkout</title>\n    <script src=\"https://js.stripe.com/v3/\"></script>\n  </head>\n  <body>\n    <script type=\"text/javascript\">\n      // Create a Stripe client.\n      var stripe = Stripe(\"{{STRIPE_PUBLIC_KEY}}\");\n      stripe.redirectToCheckout({\n        // Get the id field from the Checkout Session creation API response\n            sessionId: '{{CHECKOUT_SESSION_ID}}'\n        })\n        .then(function (result) {\n            // If `redirectToCheckout` fails due to a browser or network\n            // error, display the localized error message to your customer\n            // using `result.error.message`.\n            if (result.error) {\n                alert(result.error.message);\n            }\n        })\n        .catch(function (error) {\n          console.error(\"Error:\", error);\n       });\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "tests/apps/example/templates/checkout_success.html",
    "content": "{% extends \"base.html\" %}\n\n{% block title %}Thanks for your order!{% endblock %}\n\n{% block content %}\n    <p>\n      We appreciate your business! If you have any questions, please email\n      <a href=\"mailto:orders@example.com\">orders@example.com</a>.\n    </p>\n{% endblock content %}\n"
  },
  {
    "path": "tests/apps/example/templates/example_base.html",
    "content": "{% extends \"base.html\" %}\n\n\n{% block header_css %}\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css\">\n<script defer src=\"https://use.fontawesome.com/releases/v5.3.1/js/all.js\"></script>\n\n{{ block.super }}\n\n{% endblock %}\n"
  },
  {
    "path": "tests/apps/example/templates/payment_intent.html",
    "content": "{% extends \"example_base.html\" %}\n\n{% block header_js %}\n{{ block.super }}\n\n<script src=\"https://js.stripe.com/v3/\"></script>\n{% endblock %}\n\n{% block header_css %}\n{{ block.super }}\n\n<style>\n    /**\n     * The CSS shown here will not be introduced in the Quickstart guide, but shows\n     * how you can use CSS to style your Element's container.\n     */\n    .StripeElement {\n        box-sizing: border-box;\n\n        height: 40px;\n\n        padding: 10px 12px;\n\n        border: 1px solid transparent;\n        border-radius: 4px;\n        background-color: white;\n\n        box-shadow: 0 1px 3px 0 #e6ebf1;\n        -webkit-transition: box-shadow 150ms ease;\n        transition: box-shadow 150ms ease;\n    }\n\n    .StripeElement--focus {\n        box-shadow: 0 1px 3px 0 #cfd7df;\n    }\n\n    .StripeElement--invalid {\n        border-color: #fa755a;\n    }\n\n    .StripeElement--webkit-autofill {\n        background-color: #fefde5 !important;\n    }\n</style>\n\n{% endblock %}\n\n{% block content %}\n<section class=\"section\">\n    <div class=\"container\">\n\n        <h1 class=\"is-size-3\">Example Payment Intent Manual Configuration</h1>\n\n        <input id=\"cardholder-name\" type=\"text\" placeholder=\"Cardholder Name\">\n        <p class=\"form-row\">\n            <label for=\"card-element\">\n                Credit or debit card\n            </label>\n        <div id=\"card-element\">\n            <!-- A Stripe Element will be inserted here. -->\n        </div>\n\n        <!-- Used to display form errors. -->\n        <div id=\"card-errors\" role=\"alert\"></div>\n        <div id=\"card-success\" role=\"alert\">\n            <span id=\"success-message\"></span>\n        </div>\n\n        <button id=\"card-button\">Submit Payment</button>\n\n    </div>\n</section>\n\n<script>\n    // Create a Stripe client.\n    var stripe = Stripe('{{ STRIPE_PUBLIC_KEY }}');\n\n    // Create an instance of Elements.\n    var elements = stripe.elements();\n\n    // Custom styling can be passed to options when creating an Element.\n    // (Note that this demo uses a wider set of styles than the guide below.)\n    var style = {\n        base: {\n            color: '#32325d',\n            fontFamily: '\"Helvetica Neue\", Helvetica, sans-serif',\n            fontSmoothing: 'antialiased',\n            fontSize: '16px',\n            '::placeholder': {\n                color: '#aab7c4'\n            }\n        },\n        invalid: {\n            color: '#fa755a',\n            iconColor: '#fa755a'\n        }\n    };\n\n    // Create an instance of the card Element.\n    var cardElement = elements.create('card', {style: style});\n    cardElement.mount('#card-element');\n\n    var cardholderName = document.getElementById('cardholder-name');\n    var cardButton = document.getElementById('card-button');\n\n    cardButton.addEventListener('click', function (ev) {\n        stripe.createPaymentMethod('card', cardElement, {\n            billing_details: {name: cardholderName.value}\n        }).then(function (result) {\n            if (result.error) {\n                // Inform the user if there was an error.\n                var errorElement = document.getElementById('card-errors');\n                errorElement.textContent = result.error.message;\n            } else {\n                // Otherwise send paymentMethod.id to your server.\n                fetch(\"{% url 'djstripe_example:payment_intent' %}\", {\n                    method: 'POST',\n                    headers: {\n                        \"X-CSRFToken\": getCookie(\"csrftoken\"),\n                        \"Accept\": \"application/json\",\n                        \"Content-Type\": \"application/json\",\n                        \"X-Requested-With\": \"XMLHttpRequest\"\n                    },\n                    body: JSON.stringify({'payment_method_id': result.paymentMethod.id})\n                }).then(function (result) {\n                    // Handle server response.\n                    result.json().then(function (json) {\n                        handleServerResponse(json);\n                    })\n                });\n            }\n        });\n    });\n\n    function getCookie(name) {\n        if (!document.cookie) {\n            return null;\n        }\n        const xsrfCookies = document.cookie.split(';')\n            .map(c => c.trim())\n            .filter(c => c.startsWith(name + '='));\n        if (xsrfCookies.length === 0) {\n            return null;\n        }\n        return decodeURIComponent(xsrfCookies[0].split('=')[1]);\n    }\n\n    function handleServerResponse(response) {\n        if (response.error) {\n            var errorElement = document.getElementById('card-errors');\n            errorElement.textContent = response.error;\n        } else if (response.requires_action) {\n            // Use Stripe.js to handle required card action\n            stripe.handleCardAction(\n                response.payment_intent_client_secret\n            ).then(function (result) {\n                if (result.error) {\n                    // Show error in payment form\n                    var errorElement = document.getElementById('card-errors');\n                    errorElement.textContent = result.error.message;\n                } else {\n                    // The card action has been handled\n                    // The PaymentIntent can be confirmed again on the server\n                    fetch(\"{% url 'djstripe_example:payment_intent' %}\", {\n                        method: 'POST',\n                        headers: {\n                            \"X-CSRFToken\": getCookie(\"csrftoken\"),\n                            \"Accept\": \"application/json\",\n                            \"Content-Type\": \"application/json\",\n                            \"X-Requested-With\": \"XMLHttpRequest\"\n                        },\n                        body: JSON.stringify({payment_intent_id: result.paymentIntent.id})\n                    }).then(function (confirmResult) {\n                        confirmResult.json().then(function (json) {\n                            handleServerResponse(json)\n                        })\n                    });\n                }\n            });\n        } else {\n            // Show success message\n            document.getElementById('success-message').innerHTML = 'Success!!';\n        }\n    }\n\n</script>\n\n{% endblock %}\n"
  },
  {
    "path": "tests/apps/example/templates/purchase_subscription.html",
    "content": "{% extends \"example_base.html\" %}\n\n{% comment%}\nexample subscription purchase page, very closely based on https://stripe.com/docs/stripe-js/elements/quickstart\n{% endcomment %}\n\n{% block header_js %}\n{{ block.super }}\n\n<script src=\"https://js.stripe.com/v3/\"></script>\n{% endblock %}\n\n{% block header_css %}\n{{ block.super }}\n\n<style>\n    /**\n     * The CSS shown here will not be introduced in the Quickstart guide, but shows\n     * how you can use CSS to style your Element's container.\n     */\n    .StripeElement {\n        box-sizing: border-box;\n\n        height: 40px;\n\n        padding: 10px 12px;\n\n        border: 1px solid transparent;\n        border-radius: 4px;\n        background-color: white;\n\n        box-shadow: 0 1px 3px 0 #e6ebf1;\n        -webkit-transition: box-shadow 150ms ease;\n        transition: box-shadow 150ms ease;\n    }\n\n    .StripeElement--focus {\n        box-shadow: 0 1px 3px 0 #cfd7df;\n    }\n\n    .StripeElement--invalid {\n        border-color: #fa755a;\n    }\n\n    .StripeElement--webkit-autofill {\n        background-color: #fefde5 !important;\n    }\n</style>\n\n{% endblock %}\n\n{% block content %}\n\n<section class=\"section\">\n    <div class=\"container\">\n\n        <h1 class=\"is-size-3\">Example purchase of a Subscription</h1>\n\n\n        <form action=\"\" method=\"post\" id=\"payment-form\">\n            {% csrf_token %}\n            {{form}}\n\n            <div class=\"form-row\">\n                <label for=\"card-element\">\n                    Credit or debit card\n                </label>\n                <div id=\"card-element\">\n                    <!-- A Stripe Element will be inserted here. -->\n                </div>\n\n                <!-- Used to display form errors. -->\n                <div id=\"card-errors\" role=\"alert\"></div>\n            </div>\n\n            <button class=\"button\">Submit Payment</button>\n        </form>\n\n    </div>\n</section>\n\n<script>\n    // Create a Stripe client.\n    var stripe = Stripe('{{ STRIPE_PUBLIC_KEY }}');\n\n    // Create an instance of Elements.\n    var elements = stripe.elements();\n\n    // Custom styling can be passed to options when creating an Element.\n    // (Note that this demo uses a wider set of styles than the guide below.)\n    var style = {\n        base: {\n            color: '#32325d',\n            fontFamily: '\"Helvetica Neue\", Helvetica, sans-serif',\n            fontSmoothing: 'antialiased',\n            fontSize: '16px',\n            '::placeholder': {\n                color: '#aab7c4'\n            }\n        },\n        invalid: {\n            color: '#fa755a',\n            iconColor: '#fa755a'\n        }\n    };\n\n    // Create an instance of the card Element.\n    var card = elements.create('card', {style: style});\n\n    // Add an instance of the card Element into the `card-element` <div>.\n    card.mount('#card-element');\n\n    // Handle real-time validation errors from the card Element.\n    card.addEventListener('change', function (event) {\n        var displayError = document.getElementById('card-errors');\n        if (event.error) {\n            displayError.textContent = event.error.message;\n        } else {\n            displayError.textContent = '';\n        }\n    });\n\n    // Handle form submission.\n    var form = document.getElementById('payment-form');\n    form.addEventListener('submit', function (event) {\n        event.preventDefault();\n\n        // Note - slightly different from the Stripe JS Quickstart because\n        // we use stripe.createSource instead of createToken\n        stripe.createSource(card).then(function (result) {\n            if (result.error) {\n                // Inform the user if there was an error.\n                var errorElement = document.getElementById('card-errors');\n                errorElement.textContent = result.error.message;\n            } else {\n                // Send the token to your server.\n                stripeSourceHandler(result.source);\n            }\n        });\n\n        // stripe.createToken(card).then(function(result) {\n        // if (result.error) {\n        //   // Inform the user if there was an error.\n        //   var errorElement = document.getElementById('card-errors');\n        //   errorElement.textContent = result.error.message;\n        // } else {\n        //   // Send the token to your server.\n        //   stripeTokenHandler(result.token);\n        // }\n        // });\n    });\n\n    // Submit the form with the source ID.\n    function stripeSourceHandler(source) {\n        // Insert the source ID into the form so it gets submitted to the server\n        var form = document.getElementById('payment-form');\n        var hiddenInput = form.elements[\"stripe_source\"];\n        hiddenInput.setAttribute('value', source.id);\n        // Submit the form\n        form.submit();\n    }\n\n    // Submit the form with the token ID.\n    // function stripeTokenHandler(token) {\n    //   // Insert the token ID into the form so it gets submitted to the server\n    //   var form = document.getElementById('payment-form');\n    //   var hiddenInput = document.createElement('input');\n    //   hiddenInput.setAttribute('type', 'hidden');\n    //   hiddenInput.setAttribute('name', 'stripeToken');\n    //   hiddenInput.setAttribute('value', token.id);\n    //   form.appendChild(hiddenInput);\n    //\n    //   // Submit the form\n    //   form.submit();\n    // }\n</script>\n\n{% endblock %}\n"
  },
  {
    "path": "tests/apps/example/templates/purchase_subscription_success.html",
    "content": "{% extends \"example_base.html\" %}\n\n{% block content %}\n\n<section class=\"section\">\n    <div class=\"container\">\n        Subscription \"{{ subscription }}\" created\n    </div>\n\n    <div class=\"container\">\n        <a href=\"{% url \" djstripe_example:purchase_subscription\" %}\" >\n        Back to purchase page\n        </a>\n    </div>\n</section>\n\n{% endblock %}}\n"
  },
  {
    "path": "tests/apps/example/urls.py",
    "content": "from django.urls import path\n\nfrom . import views\n\napp_name = \"djstripe_example\"\n\nurlpatterns = [\n    path(\n        \"checkout/\",\n        views.CreateCheckoutSessionView.as_view(),\n        name=\"checkout\",\n    ),\n    path(\"success/\", views.CheckoutSessionSuccessView.as_view(), name=\"success\"),\n    path(\n        \"purchase-subscription\",\n        views.PurchaseSubscriptionView.as_view(),\n        name=\"purchase_subscription\",\n    ),\n    path(\n        \"purchase-subscription-success/<id>\",\n        views.PurchaseSubscriptionSuccessView.as_view(),\n        name=\"purchase_subscription_success\",\n    ),\n    path(\"payment-intent\", views.create_payment_intent, name=\"payment_intent\"),\n]\n"
  },
  {
    "path": "tests/apps/example/views.py",
    "content": "import json\nimport logging\n\nimport stripe\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.http import HttpResponse\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse\nfrom django.views.generic import DetailView, FormView\nfrom django.views.generic.base import TemplateView\n\nfrom djstripe import models\nfrom djstripe import settings as djstripe_settings\n\nfrom . import forms\n\nlogger = logging.getLogger(__name__)\n\n\nUser = get_user_model()\nstripe.api_key = djstripe_settings.djstripe_settings.STRIPE_SECRET_KEY\n\n\nclass CreateCheckoutSessionView(LoginRequiredMixin, TemplateView):\n    \"\"\"\n    Example View to demonstrate how to use dj-stripe to:\n\n     * Create a Stripe Checkout Session (for a new and a returning customer)\n     * Add SUBSCRIBER_CUSTOMER_KEY to metadata to populate customer.subscriber model field\n     * Fill out Payment Form and Complete Payment\n\n    Redirects the User to Stripe Checkout Session.\n    This does a logged in purchase for a new and a returning customer using Stripe Checkout\n    \"\"\"\n\n    template_name = \"checkout.html\"\n\n    def get_context_data(self, **kwargs):\n        \"\"\"\n        Creates and returns a Stripe Checkout Session\n        \"\"\"\n        # Get Parent Context\n        context = super().get_context_data(**kwargs)\n\n        # to initialise Stripe.js on the front end\n        context[\n            \"STRIPE_PUBLIC_KEY\"\n        ] = djstripe_settings.djstripe_settings.STRIPE_PUBLIC_KEY\n\n        success_url = self.request.build_absolute_uri(\n            reverse(\"djstripe_example:success\")\n        )\n        cancel_url = self.request.build_absolute_uri(reverse(\"home\"))\n\n        # get the id of the Model instance of djstripe_settings.djstripe_settings.get_subscriber_model()\n        # here we have assumed it is the Django User model. It could be a Team, Company model too.\n        # note that it needs to have an email field.\n        id = self.request.user.id\n\n        # example of how to insert the SUBSCRIBER_CUSTOMER_KEY: id in the metadata\n        # to add customer.subscriber to the newly created/updated customer.\n        metadata = {\n            f\"{djstripe_settings.djstripe_settings.SUBSCRIBER_CUSTOMER_KEY}\": id\n        }\n\n        try:\n            # retreive the Stripe Customer.\n            customer = models.Customer.objects.get(subscriber=self.request.user)\n\n            print(\"Customer Object in DB.\")\n\n            # ! Note that Stripe will always create a new Customer Object if customer id not provided\n            # ! even if customer_email is provided!\n            session = stripe.checkout.Session.create(\n                payment_method_types=[\"card\"],\n                customer=customer.id,\n                # payment_method_types=[\"bacs_debit\"],  # for bacs_debit\n                payment_intent_data={\n                    \"setup_future_usage\": \"off_session\",\n                    # so that the metadata gets copied to the associated Payment Intent and Charge Objects\n                    \"metadata\": metadata,\n                },\n                line_items=[\n                    {\n                        \"price_data\": {\n                            \"currency\": \"usd\",\n                            # \"currency\": \"gbp\",  # for bacs_debit\n                            \"unit_amount\": 2000,\n                            \"product_data\": {\n                                \"name\": \"Sample Product Name\",\n                                \"images\": [\"https://i.imgur.com/EHyR2nP.png\"],\n                                \"description\": \"Sample Description\",\n                            },\n                        },\n                        \"quantity\": 1,\n                    },\n                ],\n                mode=\"payment\",\n                success_url=success_url,\n                cancel_url=cancel_url,\n                metadata=metadata,\n            )\n\n        except models.Customer.DoesNotExist:\n            print(\"Customer Object not in DB.\")\n\n            session = stripe.checkout.Session.create(\n                payment_method_types=[\"card\"],\n                # payment_method_types=[\"bacs_debit\"],  # for bacs_debit\n                payment_intent_data={\n                    \"setup_future_usage\": \"off_session\",\n                    # so that the metadata gets copied to the associated Payment Intent and Charge Objects\n                    \"metadata\": metadata,\n                },\n                line_items=[\n                    {\n                        \"price_data\": {\n                            \"currency\": \"usd\",\n                            # \"currency\": \"gbp\",  # for bacs_debit\n                            \"unit_amount\": 2000,\n                            \"product_data\": {\n                                \"name\": \"Sample Product Name\",\n                                \"images\": [\"https://i.imgur.com/EHyR2nP.png\"],\n                                \"description\": \"Sample Description\",\n                            },\n                        },\n                        \"quantity\": 1,\n                    },\n                ],\n                mode=\"payment\",\n                success_url=success_url,\n                cancel_url=cancel_url,\n                metadata=metadata,\n            )\n\n        context[\"CHECKOUT_SESSION_ID\"] = session.id\n\n        return context\n\n\nclass CheckoutSessionSuccessView(TemplateView):\n    \"\"\"\n    Template View for showing Checkout Payment Success\n    \"\"\"\n\n    template_name = \"checkout_success.html\"\n\n\nclass PurchaseSubscriptionView(FormView):\n    \"\"\"\n    Example view to demonstrate how to use dj-stripe to:\n\n    * create a Customer\n    * add a card to the Customer\n    * create a Subscription using that card\n\n    This does a non-logged in purchase for the user of the provided email\n    \"\"\"\n\n    template_name = \"purchase_subscription.html\"\n\n    form_class = forms.PurchaseSubscriptionForm\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        if models.Plan.objects.count() == 0:\n            raise Exception(\n                \"No Product Plans in the dj-stripe database - create some in your \"\n                \"stripe account and then \"\n                \"run `./manage.py djstripe_sync_models Plan` \"\n                \"(or use the dj-stripe webhooks)\"\n            )\n\n        context[\n            \"STRIPE_PUBLIC_KEY\"\n        ] = djstripe_settings.djstripe_settings.STRIPE_PUBLIC_KEY\n\n        return context\n\n    def form_valid(self, form):\n        stripe_source = form.cleaned_data[\"stripe_source\"]\n        email = form.cleaned_data[\"email\"]\n        plan = form.cleaned_data[\"plan\"]\n\n        # Guest checkout with the provided email\n        try:\n            user = User.objects.get(email=email)\n        except User.DoesNotExist:\n            user = User.objects.create(username=email, email=email)\n\n        # Create the stripe Customer, by default subscriber Model is User,\n        # this can be overridden with djstripe_settings.djstripe_settings.DJSTRIPE_SUBSCRIBER_MODEL\n        customer, created = models.Customer.get_or_create(subscriber=user)\n\n        # Add the source as the customer's default card\n        customer.add_card(stripe_source)\n\n        # Using the Stripe API, create a subscription for this customer,\n        # using the customer's default payment source\n        stripe_subscription = stripe.Subscription.create(\n            customer=customer.id,\n            items=[{\"plan\": plan.id}],\n            collection_method=\"charge_automatically\",\n            # tax_percent=15,\n            api_key=djstripe_settings.djstripe_settings.STRIPE_SECRET_KEY,\n        )\n\n        # Sync the Stripe API return data to the database,\n        # this way we don't need to wait for a webhook-triggered sync\n        subscription = models.Subscription.sync_from_stripe_data(stripe_subscription)\n\n        self.request.subscription = subscription\n\n        return super().form_valid(form)\n\n    def get_success_url(self):\n        return reverse(\n            \"djstripe_example:purchase_subscription_success\",\n            kwargs={\"id\": self.request.subscription.id},\n        )\n\n\nclass PurchaseSubscriptionSuccessView(DetailView):\n    template_name = \"purchase_subscription_success.html\"\n\n    queryset = models.Subscription.objects.all()\n    slug_field = \"id\"\n    slug_url_kwarg = \"id\"\n    context_object_name = \"subscription\"\n\n\ndef create_payment_intent(request):\n    if request.method == \"POST\":\n        intent = None\n        data = json.loads(request.body)\n        try:\n            if \"payment_method_id\" in data:\n                # Create the PaymentIntent\n                intent = stripe.PaymentIntent.create(\n                    payment_method=data[\"payment_method_id\"],\n                    amount=1099,\n                    currency=\"usd\",\n                    confirmation_method=\"manual\",\n                    confirm=True,\n                    api_key=djstripe_settings.djstripe_settings.STRIPE_SECRET_KEY,\n                )\n            elif \"payment_intent_id\" in data:\n                intent = stripe.PaymentIntent.confirm(\n                    data[\"payment_intent_id\"],\n                    api_key=djstripe_settings.djstripe_settings.STRIPE_SECRET_KEY,\n                )\n        except stripe.error.CardError as e:\n            # Display error on client\n            return_data = json.dumps({\"error\": e.user_message}), 200\n            return HttpResponse(\n                return_data[0], content_type=\"application/json\", status=return_data[1]\n            )\n\n        if (\n            intent.status == \"requires_action\"\n            and intent.next_action.type == \"use_stripe_sdk\"\n        ):\n            # Tell the client to handle the action\n            return_data = (\n                json.dumps(\n                    {\n                        \"requires_action\": True,\n                        \"payment_intent_client_secret\": intent.client_secret,\n                    }\n                ),\n                200,\n            )\n        elif intent.status == \"succeeded\":\n            # The payment did not need any additional actions and completed!\n            # Handle post-payment fulfillment\n            return_data = json.dumps({\"success\": True}), 200\n        else:\n            # Invalid status\n            return_data = json.dumps({\"error\": \"Invalid PaymentIntent status\"}), 500\n        return HttpResponse(\n            return_data[0], content_type=\"application/json\", status=return_data[1]\n        )\n\n    else:\n        context = {\n            \"STRIPE_PUBLIC_KEY\": djstripe_settings.djstripe_settings.STRIPE_PUBLIC_KEY\n        }\n        return TemplateResponse(request, \"payment_intent.html\", context)\n"
  },
  {
    "path": "tests/apps/testapp/__init__.py",
    "content": ""
  },
  {
    "path": "tests/apps/testapp/models.py",
    "content": "from django.db.models.base import Model\nfrom django.db.models.fields import CharField, EmailField\n\n\nclass Organization(Model):\n    \"\"\"Model used to test the new custom model setting.\"\"\"\n\n    email = EmailField()\n\n\nclass StaticEmailOrganization(Model):\n    \"\"\"Model used to test the new custom model setting.\"\"\"\n\n    name = CharField(max_length=200, unique=True)\n\n    @property\n    def email(self):\n        return \"static@example.com\"\n\n\nclass NoEmailOrganization(Model):\n    \"\"\"Model used to test the new custom model setting.\"\"\"\n\n    name = CharField(max_length=200, unique=True)\n"
  },
  {
    "path": "tests/apps/testapp/urls.py",
    "content": "from django.http import HttpResponse\nfrom django.urls import include, path\n\n\ndef empty_view(request):\n    return HttpResponse()\n\n\nurlpatterns = [\n    path(\"\", empty_view, name=\"test_url_name\"),\n    path(\"djstripe/\", include(\"djstripe.urls\", namespace=\"djstripe\")),\n]\n"
  },
  {
    "path": "tests/apps/testapp_content/README.md",
    "content": "Represents protected content\n"
  },
  {
    "path": "tests/apps/testapp_content/__init__.py",
    "content": ""
  },
  {
    "path": "tests/apps/testapp_content/models.py",
    "content": ""
  },
  {
    "path": "tests/apps/testapp_content/urls.py",
    "content": "\"\"\"\nRepresents protected content\n\"\"\"\n\nfrom django.http import HttpResponse\nfrom django.urls import path\n\n\ndef testview(request):\n    return HttpResponse()\n\n\nurlpatterns = [path(\"\", testview, name=\"test_url_content\")]\n"
  },
  {
    "path": "tests/apps/testapp_namespaced/__init__.py",
    "content": ""
  },
  {
    "path": "tests/apps/testapp_namespaced/models.py",
    "content": ""
  },
  {
    "path": "tests/apps/testapp_namespaced/urls.py",
    "content": "from django.http import HttpResponse\nfrom django.urls import path\n\n\ndef testview(request):\n    return HttpResponse()\n\n\napp_name = \"testapp_namespaced\"\n\nurlpatterns = [path(\"\", testview, name=\"test_url_namespaced\")]\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "\"\"\"\nModule for creating re-usable fixtures to be used across the test suite\n\"\"\"\nimport pytest\nimport stripe\nfrom django.contrib.auth import get_user_model\n\nfrom . import FAKE_CUSTOMER, FAKE_PLATFORM_ACCOUNT\n\npytestmark = pytest.mark.django_db\n\n\n@pytest.fixture(autouse=True)\ndef create_account(monkeypatch):\n    \"\"\"\n    Fixture to automatically create and assign the default testing keys to the Platform Account\n    \"\"\"\n\n    def mock_account_retrieve(*args, **kwargs):\n        return FAKE_PLATFORM_ACCOUNT\n\n    monkeypatch.setattr(stripe.Account, \"retrieve\", mock_account_retrieve)\n\n    # create a Stripe Platform Account\n    FAKE_PLATFORM_ACCOUNT.create()\n\n\n@pytest.fixture\ndef fake_user():\n    user = get_user_model().objects.create_user(\n        username=\"testuser\", email=\"testuser@example.com\"\n    )\n    return user\n\n\n@pytest.fixture\ndef fake_customer(fake_user):\n    customer = FAKE_CUSTOMER.create_for_user(fake_user)\n    return customer\n"
  },
  {
    "path": "tests/fields/admin.py",
    "content": "from django.contrib import admin\nfrom django.shortcuts import render\n\nfrom djstripe.admin.admin import StripeModelAdmin\nfrom djstripe.admin.forms import CustomActionForm\n\nfrom .models import CustomActionModel\n\n\n@admin.register(CustomActionModel)\nclass CustomActionModelAdmin(StripeModelAdmin):\n    def get_actions(self, request):\n        # get all actions\n        actions = super().get_actions(request)\n\n        # For Subscription model's custom action, _cancel\n        actions[\"_cancel\"] = self.get_action(\"_cancel\")\n\n        # For SubscriptionSchedule's custom action, _release_subscription_schedule\n        actions[\"_release_subscription_schedule\"] = self.get_action(\n            \"_release_subscription_schedule\"\n        )\n        # For SubscriptionSchedule's custom action, _cancel_subscription_schedule\n        actions[\"_cancel_subscription_schedule\"] = self.get_action(\n            \"_cancel_subscription_schedule\"\n        )\n        return actions\n\n    @admin.action(description=\"Cancel selected subscriptions\")\n    def _cancel(self, request, queryset):\n        \"\"\"Cancel a subscription.\"\"\"\n        context = self.get_admin_action_context(queryset, \"_cancel\", CustomActionForm)\n        return render(request, \"djstripe/admin/confirm_action.html\", context)\n\n    @admin.display(description=\"Release Selected Subscription Schedules\")\n    def _release_subscription_schedule(self, request, queryset):\n        \"\"\"Release a SubscriptionSchedule.\"\"\"\n        context = self.get_admin_action_context(\n            queryset, \"_release_subscription_schedule\", CustomActionForm\n        )\n        return render(request, \"djstripe/admin/confirm_action.html\", context)\n\n    def _cancel_subscription_schedule(self, request, queryset):\n        \"\"\"Cancel a SubscriptionSchedule.\"\"\"\n        context = self.get_admin_action_context(\n            queryset, \"_cancel_subscription_schedule\", CustomActionForm\n        )\n        return render(request, \"djstripe/admin/confirm_action.html\", context)\n"
  },
  {
    "path": "tests/fields/models.py",
    "content": "\"\"\"Models used exclusively for testing\"\"\"\n\nfrom django.db import models\n\nfrom djstripe.fields import StripePercentField\nfrom djstripe.models import StripeModel\n\n\nclass ExampleDecimalModel(models.Model):\n    noval = StripePercentField()\n\n\nclass MockStripeClass:\n    def retrieve(self):\n        return self\n\n\nclass CustomActionModel(StripeModel):\n    # for some reason having a FK here throws relation doesn't exist even though\n    # djstripe is also one of the installed apps in tests.settings\n    djstripe_owner_account = None\n    stripe_class = MockStripeClass\n\n    # For Subscription model's custom action, _cancel\n    def cancel(self, at_period_end: bool = False, **kwargs):\n        pass\n"
  },
  {
    "path": "tests/fixtures/account_custom_acct_1IuHosQveW0ONQsd.json",
    "content": "{\n    \"id\": \"acct_1IuHosQveW0ONQsd\",\n    \"object\": \"account\",\n    \"business_profile\": {\n        \"mcc\": null,\n        \"name\": null,\n        \"product_description\": null,\n        \"support_address\": null,\n        \"support_email\": null,\n        \"support_phone\": null,\n        \"support_url\": null,\n        \"url\": null\n    },\n    \"business_type\": \"individual\",\n    \"capabilities\": {\n        \"transfers\": \"inactive\"\n    },\n    \"charges_enabled\": false,\n    \"company\": {\n        \"address\": {\n            \"city\": null,\n            \"country\": \"US\",\n            \"line1\": null,\n            \"line2\": null,\n            \"postal_code\": null,\n            \"state\": null\n        },\n        \"directors_provided\": true,\n        \"executives_provided\": true,\n        \"name\": null,\n        \"owners_provided\": true,\n        \"tax_id_provided\": false,\n        \"verification\": {\n            \"document\": {\n                \"back\": null,\n                \"details\": null,\n                \"details_code\": null,\n                \"front\": null\n            }\n        }\n    },\n    \"country\": \"US\",\n    \"created\": 1621778298,\n    \"default_currency\": \"usd\",\n    \"details_submitted\": false,\n    \"email\": null,\n    \"external_accounts\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"ba_1IuVozQveW0ONQsd3dWG85e2\",\n                \"object\": \"bank_account\",\n                \"account\": \"acct_1IuHosQveW0ONQsd\",\n                \"account_holder_name\": \"Jenny Rosen\",\n                \"account_holder_type\": \"individual\",\n                \"available_payout_methods\": [\n                    \"standard\"\n                ],\n                \"bank_name\": \"STRIPE TEST BANK\",\n                \"country\": \"US\",\n                \"currency\": \"usd\",\n                \"default_for_currency\": false,\n                \"fingerprint\": \"OAI2ZEI2iIJVbF1o\",\n                \"last4\": \"1116\",\n                \"metadata\": {},\n                \"routing_number\": \"110000000\",\n                \"status\": \"verified\"\n            },\n            {\n                \"id\": \"card_1IuVlSQveW0ONQsdkXBUUHyE\",\n                \"object\": \"card\",\n                \"account\": \"acct_1IuHosQveW0ONQsd\",\n                \"address_city\": null,\n                \"address_country\": null,\n                \"address_line1\": null,\n                \"address_line1_check\": null,\n                \"address_line2\": null,\n                \"address_state\": null,\n                \"address_zip\": null,\n                \"address_zip_check\": null,\n                \"available_payout_methods\": [\n                    \"standard\",\n                    \"instant\"\n                ],\n                \"brand\": \"MasterCard\",\n                \"country\": \"US\",\n                \"currency\": \"usd\",\n                \"cvc_check\": null,\n                \"default_for_currency\": true,\n                \"dynamic_last4\": null,\n                \"exp_month\": 5,\n                \"exp_year\": 2022,\n                \"fingerprint\": \"SgIrwXiq2B8M5ab2\",\n                \"funding\": \"debit\",\n                \"last4\": \"8210\",\n                \"metadata\": {},\n                \"name\": null,\n                \"tokenization_method\": null\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 2,\n        \"url\": \"/v1/accounts/acct_1IuHosQveW0ONQsd/external_accounts\"\n    },\n    \"metadata\": {},\n    \"payouts_enabled\": false,\n    \"requirements\": {\n        \"current_deadline\": null,\n        \"currently_due\": [\n            \"business_profile.url\",\n            \"individual.first_name\",\n            \"individual.last_name\"\n        ],\n        \"disabled_reason\": \"requirements.past_due\",\n        \"errors\": [],\n        \"eventually_due\": [\n            \"business_profile.url\",\n            \"individual.dob.day\",\n            \"individual.dob.month\",\n            \"individual.dob.year\",\n            \"individual.first_name\",\n            \"individual.last_name\",\n            \"individual.ssn_last_4\"\n        ],\n        \"past_due\": [\n            \"business_profile.url\",\n            \"individual.first_name\",\n            \"individual.last_name\"\n        ],\n        \"pending_verification\": []\n    },\n    \"settings\": {\n        \"bacs_debit_payments\": {},\n        \"branding\": {\n            \"icon\": null,\n            \"logo\": null,\n            \"primary_color\": null,\n            \"secondary_color\": null\n        },\n        \"card_issuing\": {\n            \"tos_acceptance\": {\n                \"date\": null,\n                \"ip\": null\n            }\n        },\n        \"card_payments\": {\n            \"decline_on\": {\n                \"avs_failure\": false,\n                \"cvc_failure\": false\n            },\n            \"statement_descriptor_prefix\": null\n        },\n        \"dashboard\": {\n            \"display_name\": \"djstripe-custom\",\n            \"timezone\": \"Etc/UTC\"\n        },\n        \"payments\": {\n            \"statement_descriptor\": null,\n            \"statement_descriptor_kana\": null,\n            \"statement_descriptor_kanji\": null\n        },\n        \"payouts\": {\n            \"debit_negative_balances\": false,\n            \"schedule\": {\n                \"delay_days\": 2,\n                \"interval\": \"daily\"\n            },\n            \"statement_descriptor\": null\n        },\n        \"sepa_debit_payments\": {}\n    },\n    \"tos_acceptance\": {\n        \"date\": 1621778297,\n        \"ip\": \"127.0.0.1\",\n        \"user_agent\": null\n    },\n    \"type\": \"custom\"\n}\n"
  },
  {
    "path": "tests/fixtures/account_express_acct_1IuHosQveW0ONQsd.json",
    "content": "{\n    \"business_profile\": {\n        \"mcc\": null,\n        \"name\": \"Express Account\",\n        \"support_address\": null,\n        \"support_email\": null,\n        \"support_phone\": null,\n        \"support_url\": null,\n        \"url\": \"https://djstripe.com/\"\n    },\n    \"capabilities\": {\n        \"transfers\": \"active\"\n    },\n    \"charges_enabled\": true,\n    \"country\": \"US\",\n    \"created\": 1622034893,\n    \"default_currency\": \"usd\",\n    \"details_submitted\": true,\n    \"email\": \"djstripe@example.com\",\n    \"external_accounts\": {\n        \"data\": [\n            {\n                \"account\": \"acct_1IuHosQveW0ONQsd\",\n                \"account_holder_name\": null,\n                \"account_holder_type\": null,\n                \"available_payout_methods\": [\n                    \"standard\"\n                ],\n                \"bank_name\": \"STRIPE TEST BANK\",\n                \"country\": \"US\",\n                \"currency\": \"usd\",\n                \"default_for_currency\": true,\n                \"fingerprint\": \"E4D818AgpkNPGkvl\",\n                \"id\": \"ba_1IuVozQveW0ONQsd3dWG85e2\",\n                \"last4\": \"6789\",\n                \"metadata\": {},\n                \"object\": \"bank_account\",\n                \"routing_number\": \"110000000\",\n                \"status\": \"new\"\n            },\n            {\n                \"id\": \"card_1IuVlSQveW0ONQsdkXBUUHyE\",\n                \"object\": \"card\",\n                \"account\": \"acct_1IuHosQveW0ONQsd\",\n                \"address_city\": null,\n                \"address_country\": null,\n                \"address_line1\": null,\n                \"address_line1_check\": null,\n                \"address_line2\": null,\n                \"address_state\": null,\n                \"address_zip\": null,\n                \"address_zip_check\": null,\n                \"available_payout_methods\": [\n                  \"standard\",\n                  \"instant\"\n                ],\n                \"brand\": \"MasterCard\",\n                \"country\": \"US\",\n                \"currency\": \"usd\",\n                \"cvc_check\": null,\n                \"default_for_currency\": true,\n                \"dynamic_last4\": null,\n                \"exp_month\": 5,\n                \"exp_year\": 2022,\n                \"fingerprint\": \"SgIrwXiq2B8M5ab2\",\n                \"funding\": \"debit\",\n                \"last4\": \"8210\",\n                \"metadata\": {},\n                \"name\": null,\n                \"tokenization_method\": null\n              }\n        ],\n        \"has_more\": false,\n        \"object\": \"list\",\n        \"total_count\": 1,\n        \"url\": \"/v1/accounts/acct_1IuHosQveW0ONQsd/external_accounts\"\n    },\n    \"id\": \"acct_1IuHosQveW0ONQsd\",\n    \"login_links\": {\n        \"data\": [],\n        \"has_more\": false,\n        \"object\": \"list\",\n        \"total_count\": 0,\n        \"url\": \"/v1/accounts/acct_1IuHosQveW0ONQsd/login_links\"\n    },\n    \"metadata\": {},\n    \"object\": \"account\",\n    \"payouts_enabled\": true,\n    \"requirements\": {\n        \"current_deadline\": null,\n        \"currently_due\": [],\n        \"disabled_reason\": null,\n        \"errors\": [],\n        \"eventually_due\": [\n            \"individual.verification.document\"\n        ],\n        \"past_due\": [],\n        \"pending_verification\": []\n    },\n    \"settings\": {\n        \"bacs_debit_payments\": {},\n        \"branding\": {\n            \"icon\": null,\n            \"logo\": null,\n            \"primary_color\": null,\n            \"secondary_color\": null\n        },\n        \"card_issuing\": {\n            \"tos_acceptance\": {\n                \"date\": null,\n                \"ip\": null\n            }\n        },\n        \"card_payments\": {\n            \"decline_on\": {\n                \"avs_failure\": false,\n                \"cvc_failure\": false\n            },\n            \"statement_descriptor_prefix\": null\n        },\n        \"dashboard\": {\n            \"display_name\": \"Djstripe\",\n            \"timezone\": \"Etc/UTC\"\n        },\n        \"payments\": {\n            \"statement_descriptor\": \"DJSTRIPE.COM\",\n            \"statement_descriptor_kana\": null,\n            \"statement_descriptor_kanji\": null\n        },\n        \"payouts\": {\n            \"debit_negative_balances\": true,\n            \"schedule\": {\n                \"delay_days\": 2,\n                \"interval\": \"daily\"\n            },\n            \"statement_descriptor\": null\n        },\n        \"sepa_debit_payments\": {}\n    },\n    \"tos_acceptance\": {\n        \"date\": 1622034889\n    },\n    \"type\": \"express\"\n}\n"
  },
  {
    "path": "tests/fixtures/account_standard_acct_1Fg9jUA3kq9o1aTc.json",
    "content": "{\n    \"id\": \"acct_1Fg9jUA3kq9o1aTc\",\n    \"object\": \"account\",\n    \"metadata\": {},\n    \"business_profile\": {\n        \"name\": \"dj-stripe\",\n        \"support_email\": \"djstripe@example.com\",\n        \"support_phone\": null,\n        \"support_url\": \"https://djstripe.com/support/\",\n        \"url\": \"https://djstripe.com\"\n    },\n    \"capabilities\": {},\n    \"charges_enabled\": true,\n    \"country\": \"US\",\n    \"default_currency\": \"usd\",\n    \"details_submitted\": true,\n    \"payouts_enabled\": true,\n    \"settings\": {\n        \"bacs_debit_payments\": {},\n        \"branding\": {\n            \"icon\":null,\n            \"logo\":null,\n            \"primary_color\": null,\n            \"secondary_color\": null\n        },\n        \"card_issuing\": {\n            \"tos_acceptance\": {\n                \"date\": null,\n                \"ip\": null\n            }\n        },\n        \"card_payments\": {\n            \"statement_descriptor_prefix\": null\n        },\n        \"dashboard\": {\n            \"display_name\": \"djstripe-standard\",\n            \"timezone\": \"America/Los_Angeles\"\n        },\n        \"payments\": {\n            \"statement_descriptor\": \"DJSTRIPE\",\n            \"statement_descriptor_kana\": null,\n            \"statement_descriptor_kanji\": null\n        },\n        \"payouts\": {\n            \"debit_negative_balances\": true,\n            \"schedule\": {\n                \"delay_days\": 2,\n                \"interval\": \"daily\"\n            },\n            \"statement_descriptor\": null\n        },\n        \"sepa_debit_payments\": {}\n    },\n    \"type\": \"standard\",\n    \"email\": \"djstripe@example.com\"\n}\n"
  },
  {
    "path": "tests/fixtures/balance_transaction_txn_fake_ch_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"txn_fake_ch_fakefakefakefakefake0001\",\n    \"object\": \"balance_transaction\",\n    \"amount\": 2000,\n    \"available_on\": 1558569600,\n    \"created\": 1557995177,\n    \"currency\": \"usd\",\n    \"description\": \"Subscription creation\",\n    \"exchange_rate\": null,\n    \"fee\": 88,\n    \"fee_details\": [\n        {\n            \"amount\": 88,\n            \"application\": null,\n            \"currency\": \"usd\",\n            \"description\": \"Stripe processing fees\",\n            \"type\": \"stripe_fee\"\n        }\n    ],\n    \"net\": 1912,\n    \"reporting_category\": \"charge\",\n    \"source\": \"ch_fakefakefakefakefake0001\",\n    \"status\": \"pending\",\n    \"type\": \"charge\"\n}\n"
  },
  {
    "path": "tests/fixtures/bank_account_ba_fakefakefakefakefake0003.json",
    "content": "{\n    \"id\": \"ba_fakefakefakefakefake0003\",\n    \"object\": \"bank_account\",\n    \"account_holder_name\": \"Jane Austen\",\n    \"account_holder_type\": \"individual\",\n    \"bank_name\": \"STRIPE TEST BANK\",\n    \"country\": \"US\",\n    \"currency\": \"usd\",\n    \"customer\": \"cus_example_with_bank_account\",\n    \"fingerprint\": \"PzC4f6ki9HTDR1cc\",\n    \"last4\": \"6789\",\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"ba_fakefakefakefakefake0003\",\n        \"djstripe_test_fixture_account_number\": \"000123456789\"\n    },\n    \"routing_number\": \"110000000\",\n    \"status\": \"new\"\n}\n"
  },
  {
    "path": "tests/fixtures/bank_account_ba_fakefakefakefakefake0004.json",
    "content": "{\n    \"id\": \"ba_1IuVozQveW0ONQsd3dWG85e2\",\n    \"object\": \"bank_account\",\n    \"account\": \"acct_1IuHosQveW0ONQsd\",\n    \"account_holder_name\": \"Jenny Rosen\",\n    \"account_holder_type\": \"individual\",\n    \"available_payout_methods\": [\n        \"standard\"\n    ],\n    \"bank_name\": \"STRIPE TEST BANK\",\n    \"country\": \"US\",\n    \"currency\": \"usd\",\n    \"default_for_currency\": false,\n    \"fingerprint\": \"OAI2ZEI2iIJVbF1o\",\n    \"last4\": \"1116\",\n    \"metadata\": {},\n    \"routing_number\": \"110000000\",\n    \"status\": \"verified\"\n}\n"
  },
  {
    "path": "tests/fixtures/card_card_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"card_fakefakefakefakefake0001\",\n    \"object\": \"card\",\n    \"account\": null,\n    \"address_city\": null,\n    \"address_country\": null,\n    \"address_line1\": null,\n    \"address_line1_check\": null,\n    \"address_line2\": null,\n    \"address_state\": null,\n    \"address_zip\": null,\n    \"address_zip_check\": null,\n    \"brand\": \"Visa\",\n    \"country\": \"US\",\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"cvc_check\": null,\n    \"dynamic_last4\": null,\n    \"exp_month\": 6,\n    \"exp_year\": 2021,\n    \"fingerprint\": \"88PuXw9tEmvYe69o\",\n    \"funding\": \"credit\",\n    \"last4\": \"4242\",\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"card_fakefakefakefakefake0001\"\n    },\n    \"name\": \"alex-nesnes@hotmail.fr\",\n    \"tokenization_method\": null\n}\n"
  },
  {
    "path": "tests/fixtures/card_card_fakefakefakefakefake0002.json",
    "content": "{\n    \"id\": \"card_fakefakefakefakefake0002\",\n    \"object\": \"card\",\n    \"account\": null,\n    \"address_city\": null,\n    \"address_country\": null,\n    \"address_line1\": null,\n    \"address_line1_check\": null,\n    \"address_line2\": null,\n    \"address_state\": null,\n    \"address_zip\": null,\n    \"address_zip_check\": null,\n    \"brand\": \"Visa\",\n    \"country\": \"US\",\n    \"customer\": \"cus_4UbFSo9tl62jqj\",\n    \"cvc_check\": null,\n    \"dynamic_last4\": null,\n    \"exp_month\": 6,\n    \"exp_year\": 2021,\n    \"fingerprint\": \"88PuXw9tEmvYe69o\",\n    \"funding\": \"credit\",\n    \"last4\": \"4242\",\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"card_fakefakefakefakefake0002\"\n    },\n    \"name\": null,\n    \"tokenization_method\": null\n}\n"
  },
  {
    "path": "tests/fixtures/card_card_fakefakefakefakefake0003.json",
    "content": "{\n    \"id\": \"card_fakefakefakefakefake0003\",\n    \"object\": \"card\",\n    \"account\": null,\n    \"address_city\": null,\n    \"address_country\": null,\n    \"address_line1\": null,\n    \"address_line1_check\": null,\n    \"address_line2\": null,\n    \"address_state\": null,\n    \"address_zip\": null,\n    \"address_zip_check\": null,\n    \"brand\": \"Visa\",\n    \"country\": \"US\",\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"cvc_check\": null,\n    \"dynamic_last4\": null,\n    \"exp_month\": 6,\n    \"exp_year\": 2021,\n    \"fingerprint\": \"88PuXw9tEmvYe69o\",\n    \"funding\": \"credit\",\n    \"last4\": \"4242\",\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"card_fakefakefakefakefake0003\"\n    },\n    \"name\": null,\n    \"tokenization_method\": null\n}\n"
  },
  {
    "path": "tests/fixtures/card_card_fakefakefakefakefake0004.json",
    "content": "{\n    \"id\": \"card_1IuVlSQveW0ONQsdkXBUUHyE\",\n    \"object\": \"card\",\n    \"account\": \"acct_1IuHosQveW0ONQsd\",\n    \"address_city\": null,\n    \"address_country\": null,\n    \"address_line1\": null,\n    \"address_line1_check\": null,\n    \"address_line2\": null,\n    \"address_state\": null,\n    \"address_zip\": null,\n    \"address_zip_check\": null,\n    \"available_payout_methods\": [\n        \"standard\",\n        \"instant\"\n    ],\n    \"brand\": \"MasterCard\",\n    \"country\": \"US\",\n    \"currency\": \"usd\",\n    \"cvc_check\": null,\n    \"default_for_currency\": true,\n    \"dynamic_last4\": null,\n    \"exp_month\": 5,\n    \"exp_year\": 2022,\n    \"fingerprint\": \"SgIrwXiq2B8M5ab2\",\n    \"funding\": \"debit\",\n    \"last4\": \"8210\",\n    \"metadata\": {},\n    \"name\": null,\n    \"tokenization_method\": null\n}\n"
  },
  {
    "path": "tests/fixtures/charge_ch_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"ch_fakefakefakefakefake0001\",\n    \"object\": \"charge\",\n    \"amount\": 2000,\n    \"amount_captured\": 0,\n    \"amount_refunded\": 0,\n    \"application\": null,\n    \"application_fee\": null,\n    \"application_fee_amount\": null,\n    \"balance_transaction\": \"txn_fake_ch_fakefakefakefakefake0001\",\n    \"billing_details\": {\n        \"address\": {\n            \"city\": null,\n            \"country\": null,\n            \"line1\": null,\n            \"line2\": null,\n            \"postal_code\": null,\n            \"state\": null\n        },\n        \"email\": null,\n        \"name\": \"alex-nesnes@hotmail.fr\",\n        \"phone\": null\n    },\n    \"calculated_statement_descriptor\": \"Stripe\",\n    \"captured\": true,\n    \"created\": 1557995177,\n    \"currency\": \"usd\",\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"description\": \"Subscription creation\",\n    \"destination\": null,\n    \"dispute\": null,\n    \"disputed\": false,\n    \"failure_code\": null,\n    \"failure_message\": null,\n    \"fraud_details\": {},\n    \"invoice\": \"in_fakefakefakefakefake0001\",\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"ch_fakefakefakefakefake0001\"\n    },\n    \"on_behalf_of\": null,\n    \"order\": null,\n    \"outcome\": {\n        \"network_status\": \"approved_by_network\",\n        \"reason\": null,\n        \"risk_level\": \"normal\",\n        \"risk_score\": 2,\n        \"seller_message\": \"Payment complete.\",\n        \"type\": \"authorized\"\n    },\n    \"paid\": true,\n    \"payment_intent\": \"pi_fakefakefakefakefake0001\",\n    \"payment_method\": \"card_fakefakefakefakefake0001\",\n    \"payment_method_details\": {\n        \"card\": {\n            \"brand\": \"visa\",\n            \"checks\": {\n                \"address_line1_check\": null,\n                \"address_postal_code_check\": null,\n                \"cvc_check\": null\n            },\n            \"country\": \"US\",\n            \"exp_month\": 6,\n            \"exp_year\": 2021,\n            \"fingerprint\": \"88PuXw9tEmvYe69o\",\n            \"funding\": \"credit\",\n            \"installments\": null,\n            \"last4\": \"4242\",\n            \"network\": \"visa\",\n            \"three_d_secure\": null,\n            \"wallet\": null\n        },\n        \"type\": \"card\"\n    },\n    \"receipt_email\": null,\n    \"receipt_number\": null,\n    \"receipt_url\": \"https://pay.stripe.com/receipts/acct_1EaetYCOCguPTL2B/ch_fakefakefakefakefake0001/rcpt_F4oXnrfNz0ijmlgXiDK31ArpYEgLEko\",\n    \"refunded\": false,\n    \"refunds\": {\n        \"object\": \"list\",\n        \"data\": [],\n        \"has_more\": false,\n        \"url\": \"/v1/charges/ch_fakefakefakefakefake0001/refunds\"\n    },\n    \"review\": null,\n    \"shipping\": null,\n    \"source\": {\n        \"id\": \"card_fakefakefakefakefake0001\",\n        \"object\": \"card\",\n        \"address_city\": null,\n        \"address_country\": null,\n        \"address_line1\": null,\n        \"address_line1_check\": null,\n        \"address_line2\": null,\n        \"address_state\": null,\n        \"address_zip\": null,\n        \"address_zip_check\": null,\n        \"brand\": \"Visa\",\n        \"country\": \"US\",\n        \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n        \"cvc_check\": null,\n        \"dynamic_last4\": null,\n        \"exp_month\": 6,\n        \"exp_year\": 2021,\n        \"fingerprint\": \"88PuXw9tEmvYe69o\",\n        \"funding\": \"credit\",\n        \"last4\": \"4242\",\n        \"metadata\": {\n            \"djstripe_test_fake_id\": \"card_fakefakefakefakefake0001\"\n        },\n        \"name\": \"alex-nesnes@hotmail.fr\",\n        \"tokenization_method\": null\n    },\n    \"source_transfer\": null,\n    \"statement_descriptor\": null,\n    \"statement_descriptor_suffix\": null,\n    \"status\": \"succeeded\",\n    \"transfer_data\": null,\n    \"transfer_group\": null\n}\n"
  },
  {
    "path": "tests/fixtures/customer_cus_4QWKsZuuTHcs7X.json",
    "content": "{\n    \"id\": \"cus_4QWKsZuuTHcs7X\",\n    \"object\": \"customer\",\n    \"address\": null,\n    \"balance\": 0,\n    \"created\": 1557995167,\n    \"currency\": null,\n    \"default_source\": {\n        \"id\": \"src_fakefakefakefakefake0001\",\n        \"object\": \"source\",\n        \"amount\": null,\n        \"card\": {\n            \"exp_month\": 6,\n            \"exp_year\": 2021,\n            \"last4\": \"4242\",\n            \"country\": \"US\",\n            \"brand\": \"Visa\",\n            \"funding\": \"credit\",\n            \"fingerprint\": \"88PuXw9tEmvYe69o\",\n            \"three_d_secure\": \"optional\",\n            \"name\": null,\n            \"address_line1_check\": null,\n            \"address_zip_check\": null,\n            \"cvc_check\": null,\n            \"tokenization_method\": null,\n            \"dynamic_last4\": null\n        },\n        \"client_secret\": \"src_client_secret_F5psHouOOldEtvBHgyz6y3FC\",\n        \"created\": 1558230761,\n        \"currency\": null,\n        \"customer\": \"cus_4QWKsZuuTHcs7X\",\n        \"flow\": \"none\",\n        \"livemode\": false,\n        \"metadata\": {\n            \"djstripe_test_fake_id\": \"src_fakefakefakefakefake0001\"\n        },\n        \"owner\": {\n            \"address\": null,\n            \"email\": null,\n            \"name\": null,\n            \"phone\": null,\n            \"verified_address\": null,\n            \"verified_email\": null,\n            \"verified_name\": null,\n            \"verified_phone\": null\n        },\n        \"statement_descriptor\": null,\n        \"status\": \"chargeable\",\n        \"type\": \"card\",\n        \"usage\": \"reusable\"\n    },\n    \"delinquent\": false,\n    \"description\": \"John Doe\",\n    \"discount\": null,\n    \"email\": \"john.doe@example.com\",\n    \"invoice_prefix\": \"90175264\",\n    \"invoice_settings\": {\n        \"custom_fields\": null,\n        \"default_payment_method\": null,\n        \"footer\": null\n    },\n    \"livemode\": false,\n    \"metadata\": {},\n    \"name\": null,\n    \"next_invoice_sequence\": 1,\n    \"phone\": null,\n    \"preferred_locales\": [],\n    \"shipping\": null,\n    \"sources\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"src_fakefakefakefakefake0001\",\n                \"object\": \"source\",\n                \"amount\": null,\n                \"card\": {\n                    \"exp_month\": 6,\n                    \"exp_year\": 2021,\n                    \"last4\": \"4242\",\n                    \"country\": \"US\",\n                    \"brand\": \"Visa\",\n                    \"funding\": \"credit\",\n                    \"fingerprint\": \"88PuXw9tEmvYe69o\",\n                    \"three_d_secure\": \"optional\",\n                    \"name\": null,\n                    \"address_line1_check\": null,\n                    \"address_zip_check\": null,\n                    \"cvc_check\": null,\n                    \"tokenization_method\": null,\n                    \"dynamic_last4\": null\n                },\n                \"client_secret\": \"src_client_secret_F5psHouOOldEtvBHgyz6y3FC\",\n                \"created\": 1558230761,\n                \"currency\": null,\n                \"customer\": \"cus_4QWKsZuuTHcs7X\",\n                \"flow\": \"none\",\n                \"livemode\": false,\n                \"metadata\": {\n                    \"djstripe_test_fake_id\": \"src_fakefakefakefakefake0001\"\n                },\n                \"owner\": {\n                    \"address\": null,\n                    \"email\": null,\n                    \"name\": null,\n                    \"phone\": null,\n                    \"verified_address\": null,\n                    \"verified_email\": null,\n                    \"verified_name\": null,\n                    \"verified_phone\": null\n                },\n                \"statement_descriptor\": null,\n                \"status\": \"chargeable\",\n                \"type\": \"card\",\n                \"usage\": \"reusable\"\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 1,\n        \"url\": \"/v1/customers/cus_4QWKsZuuTHcs7X/sources\"\n    },\n    \"subscriptions\": {},\n    \"tax_exempt\": \"none\",\n    \"tax_ids\": {}\n}\n"
  },
  {
    "path": "tests/fixtures/customer_cus_4UbFSo9tl62jqj.json",
    "content": "{\n    \"id\": \"cus_4UbFSo9tl62jqj\",\n    \"object\": \"customer\",\n    \"address\": null,\n    \"balance\": 0,\n    \"created\": 1557995167,\n    \"currency\": \"usd\",\n    \"default_source\": {\n        \"id\": \"card_fakefakefakefakefake0002\",\n        \"object\": \"card\",\n        \"address_city\": null,\n        \"address_country\": null,\n        \"address_line1\": null,\n        \"address_line1_check\": null,\n        \"address_line2\": null,\n        \"address_state\": null,\n        \"address_zip\": null,\n        \"address_zip_check\": null,\n        \"brand\": \"Visa\",\n        \"country\": \"US\",\n        \"customer\": \"cus_4UbFSo9tl62jqj\",\n        \"cvc_check\": null,\n        \"dynamic_last4\": null,\n        \"exp_month\": 6,\n        \"exp_year\": 2021,\n        \"fingerprint\": \"88PuXw9tEmvYe69o\",\n        \"funding\": \"credit\",\n        \"last4\": \"4242\",\n        \"metadata\": {\n            \"djstripe_test_fake_id\": \"card_fakefakefakefakefake0002\"\n        },\n        \"name\": null,\n        \"tokenization_method\": null\n    },\n    \"delinquent\": false,\n    \"description\": \"John Snow\",\n    \"discount\": null,\n    \"email\": \"john.snow@thewall.com\",\n    \"invoice_prefix\": \"D7B0B3B6\",\n    \"invoice_settings\": {\n        \"custom_fields\": null,\n        \"default_payment_method\": null,\n        \"footer\": null\n    },\n    \"livemode\": false,\n    \"metadata\": {},\n    \"name\": null,\n    \"next_invoice_sequence\": 3,\n    \"phone\": null,\n    \"preferred_locales\": [],\n    \"shipping\": null,\n    \"sources\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"card_fakefakefakefakefake0002\",\n                \"object\": \"card\",\n                \"address_city\": null,\n                \"address_country\": null,\n                \"address_line1\": null,\n                \"address_line1_check\": null,\n                \"address_line2\": null,\n                \"address_state\": null,\n                \"address_zip\": null,\n                \"address_zip_check\": null,\n                \"brand\": \"Visa\",\n                \"country\": \"US\",\n                \"customer\": \"cus_4UbFSo9tl62jqj\",\n                \"cvc_check\": null,\n                \"dynamic_last4\": null,\n                \"exp_month\": 6,\n                \"exp_year\": 2021,\n                \"fingerprint\": \"88PuXw9tEmvYe69o\",\n                \"funding\": \"credit\",\n                \"last4\": \"4242\",\n                \"metadata\": {\n                    \"djstripe_test_fake_id\": \"card_fakefakefakefakefake0002\"\n                },\n                \"name\": null,\n                \"tokenization_method\": null\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 1,\n        \"url\": \"/v1/customers/cus_4UbFSo9tl62jqj/sources\"\n    },\n    \"subscriptions\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"sub_fakefakefakefakefake0004\",\n                \"object\": \"subscription\",\n                \"application_fee_percent\": null,\n                \"billing_cycle_anchor\": 1558230771,\n                \"billing_thresholds\": null,\n                \"cancel_at\": null,\n                \"cancel_at_period_end\": false,\n                \"canceled_at\": null,\n                \"collection_method\": \"charge_automatically\",\n                \"created\": 1558230771,\n                \"current_period_end\": 1560909171,\n                \"current_period_start\": 1558230771,\n                \"customer\": \"cus_4UbFSo9tl62jqj\",\n                \"days_until_due\": null,\n                \"default_payment_method\": null,\n                \"default_source\": null,\n                \"default_tax_rates\": [],\n                \"discount\": null,\n                \"ended_at\": null,\n                \"items\": {\n                    \"object\": \"list\",\n                    \"data\": [\n                        {\n                            \"id\": \"si_F5uk9HMrUwrmUJ\",\n                            \"object\": \"subscription_item\",\n                            \"billing_thresholds\": null,\n                            \"created\": 1558230772,\n                            \"metadata\": {},\n                            \"plan\": {\n                                \"id\": \"gold21323\",\n                                \"object\": \"plan\",\n                                \"active\": true,\n                                \"aggregate_usage\": null,\n                                \"amount\": 2000,\n                                \"amount_decimal\": \"2000\",\n                                \"billing_scheme\": \"per_unit\",\n                                \"created\": 1558230763,\n                                \"currency\": \"usd\",\n                                \"interval\": \"month\",\n                                \"interval_count\": 1,\n                                \"livemode\": false,\n                                \"metadata\": {},\n                                \"nickname\": \"New plan name\",\n                                \"product\": \"prod_fake1\",\n                                \"tiers\": null,\n                                \"tiers_mode\": null,\n                                \"transform_usage\": null,\n                                \"trial_period_days\": null,\n                                \"usage_type\": \"licensed\"\n                            },\n                            \"price\": {\n                                \"id\": \"gold21323\",\n                                \"object\": \"price\",\n                                \"active\": true,\n                                \"billing_scheme\": \"per_unit\",\n                                \"created\": 1593225979,\n                                \"currency\": \"usd\",\n                                \"livemode\": false,\n                                \"lookup_key\": null,\n                                \"metadata\": {},\n                                \"nickname\": \"New plan name\",\n                                \"product\": \"prod_fake1\",\n                                \"recurring\": {\n                                    \"aggregate_usage\": null,\n                                    \"interval\": \"month\",\n                                    \"interval_count\": 1,\n                                    \"trial_period_days\": null,\n                                    \"usage_type\": \"licensed\"\n                                },\n                                \"tiers_mode\": null,\n                                \"transform_quantity\": null,\n                                \"type\": \"recurring\",\n                                \"unit_amount\": 2000,\n                                \"unit_amount_decimal\": \"2000\"\n                            },\n                            \"quantity\": 1,\n                            \"subscription\": \"sub_fakefakefakefakefake0004\",\n                            \"tax_rates\": []\n                        },\n                        {\n                            \"id\": \"si_F5uk81B1xGi3Vr\",\n                            \"object\": \"subscription_item\",\n                            \"billing_thresholds\": null,\n                            \"created\": 1558230772,\n                            \"metadata\": {},\n                            \"plan\": {\n                                \"id\": \"silver41294\",\n                                \"object\": \"plan\",\n                                \"active\": true,\n                                \"aggregate_usage\": null,\n                                \"amount\": 4000,\n                                \"amount_decimal\": \"4000\",\n                                \"billing_scheme\": \"per_unit\",\n                                \"created\": 1558230763,\n                                \"currency\": \"usd\",\n                                \"interval\": \"month\",\n                                \"interval_count\": 1,\n                                \"livemode\": false,\n                                \"metadata\": {},\n                                \"nickname\": \"New plan name\",\n                                \"product\": \"prod_fake1\",\n                                \"tiers\": null,\n                                \"tiers_mode\": null,\n                                \"transform_usage\": null,\n                                \"trial_period_days\": 12,\n                                \"usage_type\": \"licensed\"\n                            },\n                            \"price\": {\n                                \"id\": \"silver41294\",\n                                \"object\": \"price\",\n                                \"active\": true,\n                                \"billing_scheme\": \"per_unit\",\n                                \"created\": 1593225979,\n                                \"currency\": \"usd\",\n                                \"livemode\": false,\n                                \"lookup_key\": null,\n                                \"metadata\": {},\n                                \"nickname\": \"New plan name\",\n                                \"product\": \"prod_fake1\",\n                                \"recurring\": {\n                                    \"aggregate_usage\": null,\n                                    \"interval\": \"month\",\n                                    \"interval_count\": 1,\n                                    \"trial_period_days\": 12,\n                                    \"usage_type\": \"licensed\"\n                                },\n                                \"tiers_mode\": null,\n                                \"transform_quantity\": null,\n                                \"type\": \"recurring\",\n                                \"unit_amount\": 4000,\n                                \"unit_amount_decimal\": \"4000\"\n                            },\n                            \"quantity\": 1,\n                            \"subscription\": \"sub_fakefakefakefakefake0004\",\n                            \"tax_rates\": []\n                        }\n                    ],\n                    \"has_more\": false,\n                    \"total_count\": 2,\n                    \"url\": \"/v1/subscription_items?subscription=sub_fakefakefakefakefake0004\"\n                },\n                \"latest_invoice\": \"in_1GyU3kCOCguPTL2BO0EVwuzj\",\n                \"livemode\": false,\n                \"metadata\": {\n                    \"djstripe_test_fake_id\": \"sub_fakefakefakefakefake0004\"\n                },\n                \"next_pending_invoice_item_invoice\": null,\n                \"pause_collection\": null,\n                \"pending_invoice_item_interval\": null,\n                \"pending_setup_intent\": null,\n                \"pending_update\": null,\n                \"plan\": null,\n                \"quantity\": null,\n                \"schedule\": null,\n                \"start_date\": 1559476706,\n                \"status\": \"active\",\n                \"tax_percent\": null,\n                \"transfer_data\": null,\n                \"trial_end\": null,\n                \"trial_start\": null\n            },\n            {\n                \"id\": \"sub_fakefakefakefakefake0003\",\n                \"object\": \"subscription\",\n                \"application_fee_percent\": null,\n                \"billing_cycle_anchor\": 1558230769,\n                \"billing_thresholds\": null,\n                \"cancel_at\": null,\n                \"cancel_at_period_end\": false,\n                \"canceled_at\": null,\n                \"collection_method\": \"charge_automatically\",\n                \"created\": 1558230769,\n                \"current_period_end\": 1560909169,\n                \"current_period_start\": 1558230769,\n                \"customer\": \"cus_4UbFSo9tl62jqj\",\n                \"days_until_due\": null,\n                \"default_payment_method\": null,\n                \"default_source\": null,\n                \"default_tax_rates\": [],\n                \"discount\": null,\n                \"ended_at\": null,\n                \"items\": {\n                    \"object\": \"list\",\n                    \"data\": [\n                        {\n                            \"id\": \"si_F5ukGdpR4EejF9\",\n                            \"object\": \"subscription_item\",\n                            \"billing_thresholds\": null,\n                            \"created\": 1558230770,\n                            \"metadata\": {},\n                            \"plan\": {\n                                \"id\": \"gold21323\",\n                                \"object\": \"plan\",\n                                \"active\": true,\n                                \"aggregate_usage\": null,\n                                \"amount\": 2000,\n                                \"amount_decimal\": \"2000\",\n                                \"billing_scheme\": \"per_unit\",\n                                \"created\": 1558230763,\n                                \"currency\": \"usd\",\n                                \"interval\": \"month\",\n                                \"interval_count\": 1,\n                                \"livemode\": false,\n                                \"metadata\": {},\n                                \"nickname\": \"New plan name\",\n                                \"product\": \"prod_fake1\",\n                                \"tiers\": null,\n                                \"tiers_mode\": null,\n                                \"transform_usage\": null,\n                                \"trial_period_days\": null,\n                                \"usage_type\": \"licensed\"\n                            },\n                            \"price\": {\n                                \"id\": \"gold21323\",\n                                \"object\": \"price\",\n                                \"active\": true,\n                                \"billing_scheme\": \"per_unit\",\n                                \"created\": 1593225979,\n                                \"currency\": \"usd\",\n                                \"livemode\": false,\n                                \"lookup_key\": null,\n                                \"metadata\": {},\n                                \"nickname\": \"New plan name\",\n                                \"product\": \"prod_fake1\",\n                                \"recurring\": {\n                                    \"aggregate_usage\": null,\n                                    \"interval\": \"month\",\n                                    \"interval_count\": 1,\n                                    \"trial_period_days\": null,\n                                    \"usage_type\": \"licensed\"\n                                },\n                                \"tiers_mode\": null,\n                                \"transform_quantity\": null,\n                                \"type\": \"recurring\",\n                                \"unit_amount\": 2000,\n                                \"unit_amount_decimal\": \"2000\"\n                            },\n                            \"quantity\": 1,\n                            \"subscription\": \"sub_fakefakefakefakefake0003\",\n                            \"tax_rates\": []\n                        }\n                    ],\n                    \"has_more\": false,\n                    \"total_count\": 1,\n                    \"url\": \"/v1/subscription_items?subscription=sub_fakefakefakefakefake0003\"\n                },\n                \"latest_invoice\": \"in_1GyU3iCOCguPTL2BDgqo4dj7\",\n                \"livemode\": false,\n                \"metadata\": {\n                    \"djstripe_test_fake_id\": \"sub_fakefakefakefakefake0003\"\n                },\n                \"next_pending_invoice_item_invoice\": null,\n                \"pause_collection\": null,\n                \"pending_invoice_item_interval\": null,\n                \"pending_setup_intent\": null,\n                \"pending_update\": null,\n                \"plan\": {\n                    \"id\": \"gold21323\",\n                    \"object\": \"plan\",\n                    \"active\": true,\n                    \"aggregate_usage\": null,\n                    \"amount\": 2000,\n                    \"amount_decimal\": \"2000\",\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1558230763,\n                    \"currency\": \"usd\",\n                    \"interval\": \"month\",\n                    \"interval_count\": 1,\n                    \"livemode\": false,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"tiers\": null,\n                    \"tiers_mode\": null,\n                    \"transform_usage\": null,\n                    \"trial_period_days\": null,\n                    \"usage_type\": \"licensed\"\n                },\n                \"quantity\": 1,\n                \"schedule\": null,\n                \"start_date\": 1559476704,\n                \"status\": \"active\",\n                \"tax_percent\": null,\n                \"transfer_data\": null,\n                \"trial_end\": null,\n                \"trial_start\": null\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 2,\n        \"url\": \"/v1/customers/cus_4UbFSo9tl62jqj/subscriptions\"\n    },\n    \"tax_exempt\": \"none\",\n    \"tax_ids\": {}\n}\n"
  },
  {
    "path": "tests/fixtures/customer_cus_6lsBvm5rJ0zyHc.json",
    "content": "{\n    \"id\": \"cus_6lsBvm5rJ0zyHc\",\n    \"object\": \"customer\",\n    \"address\": null,\n    \"balance\": 0,\n    \"created\": 1557995166,\n    \"currency\": \"usd\",\n    \"default_source\": {\n        \"id\": \"card_fakefakefakefakefake0001\",\n        \"object\": \"card\",\n        \"address_city\": null,\n        \"address_country\": null,\n        \"address_line1\": null,\n        \"address_line1_check\": null,\n        \"address_line2\": null,\n        \"address_state\": null,\n        \"address_zip\": null,\n        \"address_zip_check\": null,\n        \"brand\": \"Visa\",\n        \"country\": \"US\",\n        \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n        \"cvc_check\": null,\n        \"dynamic_last4\": null,\n        \"exp_month\": 6,\n        \"exp_year\": 2021,\n        \"fingerprint\": \"88PuXw9tEmvYe69o\",\n        \"funding\": \"credit\",\n        \"last4\": \"4242\",\n        \"metadata\": {\n            \"djstripe_test_fake_id\": \"card_fakefakefakefakefake0001\"\n        },\n        \"name\": \"alex-nesnes@hotmail.fr\",\n        \"tokenization_method\": null\n    },\n    \"delinquent\": false,\n    \"description\": \"Michael Smith\",\n    \"discount\": null,\n    \"email\": \"michael.smith@example.com\",\n    \"invoice_prefix\": \"E5B23224\",\n    \"invoice_settings\": {\n        \"custom_fields\": null,\n        \"default_payment_method\": null,\n        \"footer\": null\n    },\n    \"livemode\": false,\n    \"metadata\": {},\n    \"name\": null,\n    \"next_invoice_sequence\": 3,\n    \"phone\": null,\n    \"preferred_locales\": [],\n    \"shipping\": null,\n    \"sources\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"card_fakefakefakefakefake0001\",\n                \"object\": \"card\",\n                \"account\": null,\n                \"address_city\": null,\n                \"address_country\": null,\n                \"address_line1\": null,\n                \"address_line1_check\": null,\n                \"address_line2\": null,\n                \"address_state\": null,\n                \"address_zip\": null,\n                \"address_zip_check\": null,\n                \"brand\": \"Visa\",\n                \"country\": \"US\",\n                \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n                \"cvc_check\": null,\n                \"dynamic_last4\": null,\n                \"exp_month\": 6,\n                \"exp_year\": 2021,\n                \"fingerprint\": \"88PuXw9tEmvYe69o\",\n                \"funding\": \"credit\",\n                \"last4\": \"4242\",\n                \"metadata\": {\n                    \"djstripe_test_fake_id\": \"card_fakefakefakefakefake0001\"\n                },\n                \"name\": \"alex-nesnes@hotmail.fr\",\n                \"tokenization_method\": null\n            },\n            {\n                \"id\": \"card_fakefakefakefakefake0003\",\n                \"object\": \"card\",\n                \"account\": null,\n                \"address_city\": null,\n                \"address_country\": null,\n                \"address_line1\": null,\n                \"address_line1_check\": null,\n                \"address_line2\": null,\n                \"address_state\": null,\n                \"address_zip\": null,\n                \"address_zip_check\": null,\n                \"brand\": \"Visa\",\n                \"country\": \"US\",\n                \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n                \"cvc_check\": null,\n                \"dynamic_last4\": null,\n                \"exp_month\": 6,\n                \"exp_year\": 2021,\n                \"fingerprint\": \"88PuXw9tEmvYe69o\",\n                \"funding\": \"credit\",\n                \"last4\": \"4242\",\n                \"metadata\": {\n                    \"djstripe_test_fake_id\": \"card_fakefakefakefakefake0003\"\n                },\n                \"name\": null,\n                \"tokenization_method\": null\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 2,\n        \"url\": \"/v1/customers/cus_6lsBvm5rJ0zyHc/sources\"\n    },\n    \"subscriptions\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"sub_fakefakefakefakefake0002\",\n                \"object\": \"subscription\",\n                \"application_fee_percent\": null,\n                \"billing_cycle_anchor\": 1558230766,\n                \"billing_thresholds\": null,\n                \"cancel_at\": null,\n                \"cancel_at_period_end\": false,\n                \"canceled_at\": null,\n                \"collection_method\": \"charge_automatically\",\n                \"created\": 1558230766,\n                \"current_period_end\": 1560909166,\n                \"current_period_start\": 1558230766,\n                \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n                \"days_until_due\": null,\n                \"default_payment_method\": null,\n                \"default_source\": null,\n                \"default_tax_rates\": [],\n                \"discount\": null,\n                \"ended_at\": null,\n                \"items\": {\n                    \"object\": \"list\",\n                    \"data\": [\n                        {\n                            \"id\": \"si_F5ukq6eM2QV9g5\",\n                            \"object\": \"subscription_item\",\n                            \"billing_thresholds\": null,\n                            \"created\": 1558230767,\n                            \"metadata\": {},\n                            \"plan\": {\n                                \"id\": \"silver41294\",\n                                \"object\": \"plan\",\n                                \"active\": true,\n                                \"aggregate_usage\": null,\n                                \"amount\": 4000,\n                                \"amount_decimal\": \"4000\",\n                                \"billing_scheme\": \"per_unit\",\n                                \"created\": 1558230763,\n                                \"currency\": \"usd\",\n                                \"interval\": \"month\",\n                                \"interval_count\": 1,\n                                \"livemode\": false,\n                                \"metadata\": {},\n                                \"nickname\": \"New plan name\",\n                                \"product\": \"prod_fake1\",\n                                \"tiers\": null,\n                                \"tiers_mode\": null,\n                                \"transform_usage\": null,\n                                \"trial_period_days\": 12,\n                                \"usage_type\": \"licensed\"\n                            },\n                            \"price\": {\n                                \"id\": \"silver41294\",\n                                \"object\": \"price\",\n                                \"active\": true,\n                                \"billing_scheme\": \"per_unit\",\n                                \"created\": 1593225979,\n                                \"currency\": \"usd\",\n                                \"livemode\": false,\n                                \"lookup_key\": null,\n                                \"metadata\": {},\n                                \"nickname\": \"New plan name\",\n                                \"product\": \"prod_fake1\",\n                                \"recurring\": {\n                                    \"aggregate_usage\": null,\n                                    \"interval\": \"month\",\n                                    \"interval_count\": 1,\n                                    \"trial_period_days\": 12,\n                                    \"usage_type\": \"licensed\"\n                                },\n                                \"tiers_mode\": null,\n                                \"transform_quantity\": null,\n                                \"type\": \"recurring\",\n                                \"unit_amount\": 4000,\n                                \"unit_amount_decimal\": \"4000\"\n                            },\n                            \"quantity\": 1,\n                            \"subscription\": \"sub_fakefakefakefakefake0002\",\n                            \"tax_rates\": [\n                                {\n                                    \"id\": \"txr_fakefakefakefakefake0001\",\n                                    \"object\": \"tax_rate\",\n                                    \"active\": true,\n                                    \"created\": 1593225980,\n                                    \"description\": null,\n                                    \"display_name\": \"VAT\",\n                                    \"inclusive\": true,\n                                    \"jurisdiction\": \"Example1\",\n                                    \"livemode\": false,\n                                    \"metadata\": {\n                                        \"djstripe_test_fake_id\": \"txr_fakefakefakefakefake0001\"\n                                    },\n                                    \"percentage\": 15.0\n                                }\n                            ]\n                        }\n                    ],\n                    \"has_more\": false,\n                    \"total_count\": 1,\n                    \"url\": \"/v1/subscription_items?subscription=sub_fakefakefakefakefake0002\"\n                },\n                \"latest_invoice\": \"in_fakefakefakefakefake0004\",\n                \"livemode\": false,\n                \"metadata\": {\n                    \"djstripe_test_fake_id\": \"sub_fakefakefakefakefake0002\"\n                },\n                \"next_pending_invoice_item_invoice\": null,\n                \"pause_collection\": null,\n                \"pending_invoice_item_interval\": null,\n                \"pending_setup_intent\": null,\n                \"pending_update\": null,\n                \"plan\": {\n                    \"id\": \"silver41294\",\n                    \"object\": \"plan\",\n                    \"active\": true,\n                    \"aggregate_usage\": null,\n                    \"amount\": 4000,\n                    \"amount_decimal\": \"4000\",\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1558230763,\n                    \"currency\": \"usd\",\n                    \"interval\": \"month\",\n                    \"interval_count\": 1,\n                    \"livemode\": false,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"tiers\": null,\n                    \"tiers_mode\": null,\n                    \"transform_usage\": null,\n                    \"trial_period_days\": 12,\n                    \"usage_type\": \"licensed\"\n                },\n                \"quantity\": 1,\n                \"schedule\": null,\n                \"start_date\": 1559476702,\n                \"status\": \"active\",\n                \"tax_percent\": null,\n                \"transfer_data\": null,\n                \"trial_end\": null,\n                \"trial_start\": null\n            },\n            {\n                \"id\": \"sub_fakefakefakefakefake0001\",\n                \"object\": \"subscription\",\n                \"application_fee_percent\": null,\n                \"billing_cycle_anchor\": 1558230764,\n                \"billing_thresholds\": null,\n                \"cancel_at\": null,\n                \"cancel_at_period_end\": false,\n                \"canceled_at\": null,\n                \"collection_method\": \"charge_automatically\",\n                \"created\": 1558230764,\n                \"current_period_end\": 1560909164,\n                \"current_period_start\": 1558230764,\n                \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n                \"days_until_due\": null,\n                \"default_payment_method\": null,\n                \"default_source\": null,\n                \"default_tax_rates\": [\n                    {\n                        \"id\": \"txr_fakefakefakefakefake0001\",\n                        \"object\": \"tax_rate\",\n                        \"active\": true,\n                        \"created\": 1593225980,\n                        \"description\": null,\n                        \"display_name\": \"VAT\",\n                        \"inclusive\": true,\n                        \"jurisdiction\": \"Example1\",\n                        \"livemode\": false,\n                        \"metadata\": {\n                            \"djstripe_test_fake_id\": \"txr_fakefakefakefakefake0001\"\n                        },\n                        \"percentage\": 15.0\n                    }\n                ],\n                \"discount\": null,\n                \"ended_at\": null,\n                \"items\": {\n                    \"object\": \"list\",\n                    \"data\": [\n                        {\n                            \"id\": \"si_F5ukmkS6Bxi90Y\",\n                            \"object\": \"subscription_item\",\n                            \"billing_thresholds\": null,\n                            \"created\": 1558230764,\n                            \"metadata\": {},\n                            \"plan\": {\n                                \"id\": \"gold21323\",\n                                \"object\": \"plan\",\n                                \"active\": true,\n                                \"aggregate_usage\": null,\n                                \"amount\": 2000,\n                                \"amount_decimal\": \"2000\",\n                                \"billing_scheme\": \"per_unit\",\n                                \"created\": 1558230763,\n                                \"currency\": \"usd\",\n                                \"interval\": \"month\",\n                                \"interval_count\": 1,\n                                \"livemode\": false,\n                                \"metadata\": {},\n                                \"nickname\": \"New plan name\",\n                                \"product\": \"prod_fake1\",\n                                \"tiers\": null,\n                                \"tiers_mode\": null,\n                                \"transform_usage\": null,\n                                \"trial_period_days\": null,\n                                \"usage_type\": \"licensed\"\n                            },\n                            \"price\": {\n                                \"id\": \"gold21323\",\n                                \"object\": \"price\",\n                                \"active\": true,\n                                \"billing_scheme\": \"per_unit\",\n                                \"created\": 1593225979,\n                                \"currency\": \"usd\",\n                                \"livemode\": false,\n                                \"lookup_key\": null,\n                                \"metadata\": {},\n                                \"nickname\": \"New plan name\",\n                                \"product\": \"prod_fake1\",\n                                \"recurring\": {\n                                    \"aggregate_usage\": null,\n                                    \"interval\": \"month\",\n                                    \"interval_count\": 1,\n                                    \"trial_period_days\": null,\n                                    \"usage_type\": \"licensed\"\n                                },\n                                \"tiers_mode\": null,\n                                \"transform_quantity\": null,\n                                \"type\": \"recurring\",\n                                \"unit_amount\": 2000,\n                                \"unit_amount_decimal\": \"2000\"\n                            },\n                            \"quantity\": 1,\n                            \"subscription\": \"sub_fakefakefakefakefake0001\",\n                            \"tax_rates\": []\n                        }\n                    ],\n                    \"has_more\": false,\n                    \"total_count\": 1,\n                    \"url\": \"/v1/subscription_items?subscription=sub_fakefakefakefakefake0001\"\n                },\n                \"latest_invoice\": \"in_fakefakefakefakefake0001\",\n                \"livemode\": false,\n                \"metadata\": {\n                    \"djstripe_test_fake_id\": \"sub_fakefakefakefakefake0001\"\n                },\n                \"next_pending_invoice_item_invoice\": null,\n                \"pause_collection\": null,\n                \"pending_invoice_item_interval\": null,\n                \"pending_setup_intent\": null,\n                \"pending_update\": null,\n                \"plan\": {\n                    \"id\": \"gold21323\",\n                    \"object\": \"plan\",\n                    \"active\": true,\n                    \"aggregate_usage\": null,\n                    \"amount\": 2000,\n                    \"amount_decimal\": \"2000\",\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1558230763,\n                    \"currency\": \"usd\",\n                    \"interval\": \"month\",\n                    \"interval_count\": 1,\n                    \"livemode\": false,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"tiers\": null,\n                    \"tiers_mode\": null,\n                    \"transform_usage\": null,\n                    \"trial_period_days\": null,\n                    \"usage_type\": \"licensed\"\n                },\n                \"quantity\": 1,\n                \"schedule\": null,\n                \"start_date\": 1559476700,\n                \"status\": \"active\",\n                \"tax_percent\": null,\n                \"transfer_data\": null,\n                \"trial_end\": null,\n                \"trial_start\": null\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 2,\n        \"url\": \"/v1/customers/cus_6lsBvm5rJ0zyHc/subscriptions\"\n    },\n    \"tax_exempt\": \"none\",\n    \"tax_ids\": {}\n}\n"
  },
  {
    "path": "tests/fixtures/customer_cus_example_with_bank_account.json",
    "content": "{\n    \"id\": \"cus_example_with_bank_account\",\n    \"object\": \"customer\",\n    \"address\": null,\n    \"balance\": 0,\n    \"created\": 1566463428,\n    \"currency\": null,\n    \"default_source\": {\n        \"id\": \"ba_fakefakefakefakefake0003\",\n        \"object\": \"bank_account\",\n        \"account_holder_name\": \"Jane Austen\",\n        \"account_holder_type\": \"individual\",\n        \"bank_name\": \"STRIPE TEST BANK\",\n        \"country\": \"US\",\n        \"currency\": \"usd\",\n        \"customer\": \"cus_example_with_bank_account\",\n        \"fingerprint\": \"PzC4f6ki9HTDR1cc\",\n        \"last4\": \"6789\",\n        \"metadata\": {\n            \"djstripe_test_fake_id\": \"ba_fakefakefakefakefake0003\",\n            \"djstripe_test_fixture_account_number\": \"000123456789\"\n        },\n        \"routing_number\": \"110000000\",\n        \"status\": \"new\"\n    },\n    \"delinquent\": false,\n    \"description\": null,\n    \"discount\": null,\n    \"email\": \"jane.austen@example.com\",\n    \"invoice_prefix\": \"905A7617\",\n    \"invoice_settings\": {\n        \"custom_fields\": null,\n        \"default_payment_method\": null,\n        \"footer\": null\n    },\n    \"livemode\": false,\n    \"metadata\": {},\n    \"name\": \"Jane Austen\",\n    \"next_invoice_sequence\": 1,\n    \"phone\": null,\n    \"preferred_locales\": [],\n    \"shipping\": null,\n    \"sources\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"ba_fakefakefakefakefake0003\",\n                \"object\": \"bank_account\",\n                \"account_holder_name\": \"Jane Austen\",\n                \"account_holder_type\": \"individual\",\n                \"bank_name\": \"STRIPE TEST BANK\",\n                \"country\": \"US\",\n                \"currency\": \"usd\",\n                \"customer\": \"cus_example_with_bank_account\",\n                \"fingerprint\": \"PzC4f6ki9HTDR1cc\",\n                \"last4\": \"6789\",\n                \"metadata\": {\n                    \"djstripe_test_fake_id\": \"ba_fakefakefakefakefake0003\",\n                    \"djstripe_test_fixture_account_number\": \"000123456789\"\n                },\n                \"routing_number\": \"110000000\",\n                \"status\": \"new\"\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 1,\n        \"url\": \"/v1/customers/cus_example_with_bank_account/sources\"\n    },\n    \"subscriptions\": {},\n    \"tax_exempt\": \"none\",\n    \"tax_ids\": {}\n}\n"
  },
  {
    "path": "tests/fixtures/dispute_ch_fakefakefakefake01.json",
    "content": "{\n    \"id\": \"ch_1JHRCWJSZQVUcJYgOCavbxio\",\n    \"object\": \"charge\",\n    \"livemode\": false,\n    \"created\": 1557995177,\n    \"metadata\": {},\n    \"description\": \"\",\n    \"amount\": \"10.00\",\n    \"amount_captured\": \"10.00\",\n    \"amount_refunded\": \"0.00\",\n    \"application\": \"\",\n    \"application_fee\": null,\n    \"application_fee_amount\": null,\n    \"balance_transaction\": \"txn_1JHRCWJSZQVUcJYgYwW8XpF4\",\n    \"billing_details\": {\n        \"address\": {\n            \"city\": null,\n            \"country\": null,\n            \"line1\": null,\n            \"line2\": null,\n            \"postal_code\": null,\n            \"state\": null\n        },\n        \"email\": null,\n        \"name\": null,\n        \"phone\": null\n    },\n    \"calculated_statement_descriptor\": \"CONSULTING\",\n    \"captured\": true,\n    \"currency\": \"usd\",\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"dispute\": \"dp_1JAyTwJSZQVUcJYgPqasUEn1\",\n    \"disputed\": true,\n    \"failure_code\": \"\",\n    \"failure_message\": \"\",\n    \"fraud_details\": {},\n    \"invoice\": null,\n    \"on_behalf_of\": null,\n    \"outcome\": {\n        \"network_status\": \"approved_by_network\",\n        \"reason\": null,\n        \"risk_level\": \"normal\",\n        \"risk_score\": 8,\n        \"seller_message\": \"Payment complete.\",\n        \"type\": \"authorized\"\n    },\n    \"paid\": true,\n    \"payment_intent\": \"pi_1JAyTwJSZQVUcJYgBbsz0NuH\",\n    \"payment_method\": \"card_fakefakefakefakefake0001\",\n    \"payment_method_details\": {\n        \"card\": {\n            \"brand\": \"visa\",\n            \"checks\": {\n                \"address_line1_check\": null,\n                \"address_postal_code_check\": null,\n                \"cvc_check\": \"pass\"\n            },\n            \"country\": \"US\",\n            \"exp_month\": 2,\n            \"exp_year\": 2022,\n            \"fingerprint\": \"Js2Tq8SMwjNsGwEr\",\n            \"funding\": \"credit\",\n            \"installments\": null,\n            \"last4\": \"0259\",\n            \"network\": \"visa\",\n            \"three_d_secure\": null,\n            \"wallet\": null\n        },\n        \"type\": \"card\"\n    },\n    \"receipt_email\": \"\",\n    \"receipt_number\": \"\",\n    \"receipt_url\": \"https://pay.stripe.com/receipts/acct_1ItQ7cJSZQVUcJYg/ch_1JHRCWJSZQVUcJYgOCavbxio/rcpt_JvHmTnLVQmraFXrLuQq5RT9oF14tFOL\",\n    \"refunded\": false,\n    \"shipping\": \"\",\n    \"source_transfer\": null,\n    \"statement_descriptor\": \"consulting\",\n    \"statement_descriptor_suffix\": \"\",\n    \"status\": \"succeeded\",\n    \"transfer\": null,\n    \"transfer_data\": \"\",\n    \"transfer_group\": \"\"\n}\n"
  },
  {
    "path": "tests/fixtures/dispute_dp_fakefakefakefake01.json",
    "content": "{\n    \"id\": \"dp_1JAyTwJSZQVUcJYgPqasUEn1\",\n    \"object\": \"dispute\",\n    \"amount\": 1000,\n    \"balance_transaction\": \"txn_16g5h62eZvKYlo2CQ2AHA89s\",\n    \"balance_transactions\": [\n        {\n            \"id\": \"txn_16g5h62eZvKYlo2CQ2AHA89s\",\n            \"object\": \"balance_transaction\",\n            \"amount\": -1000,\n            \"available_on\": 1626307200,\n            \"created\": 1625755540,\n            \"currency\": \"usd\",\n            \"description\": \"Chargeback withdrawal for ch_fakefakefakefakefake0001\",\n            \"exchange_rate\": null,\n            \"fee\": 1500,\n            \"fee_details\": [\n                {\n                    \"amount\": 1500,\n                    \"application\": null,\n                    \"currency\": \"usd\",\n                    \"description\": \"Dispute fee\",\n                    \"type\": \"stripe_fee\"\n                }\n            ],\n            \"net\": -2500,\n            \"reporting_category\": \"dispute\",\n            \"source\": \"dp_1JAyTwJSZQVUcJYgPqasUEn1\",\n            \"status\": \"pending\",\n            \"type\": \"adjustment\"\n        }\n    ],\n    \"charge\": \"ch_fakefakefakefakefake0001\",\n    \"created\": 1625755540,\n    \"currency\": \"usd\",\n    \"evidence\": {\n        \"access_activity_log\": null,\n        \"billing_address\": null,\n        \"cancellation_policy\": null,\n        \"cancellation_policy_disclosure\": null,\n        \"cancellation_rebuttal\": null,\n        \"customer_communication\": null,\n        \"customer_email_address\": \"test1@user.com\",\n        \"customer_name\": \"Jane Doe Test\",\n        \"customer_purchase_ip\": \"223.182.253.19\",\n        \"customer_signature\": null,\n        \"duplicate_charge_documentation\": null,\n        \"duplicate_charge_explanation\": null,\n        \"duplicate_charge_id\": null,\n        \"product_description\": null,\n        \"receipt\": null,\n        \"refund_policy\": null,\n        \"refund_policy_disclosure\": null,\n        \"refund_refusal_explanation\": null,\n        \"service_date\": null,\n        \"service_documentation\": null,\n        \"shipping_address\": null,\n        \"shipping_carrier\": null,\n        \"shipping_date\": null,\n        \"shipping_documentation\": null,\n        \"shipping_tracking_number\": null,\n        \"uncategorized_file\": null,\n        \"uncategorized_text\": null\n    },\n    \"evidence_details\": {\n        \"due_by\": 1626566399,\n        \"has_evidence\": false,\n        \"past_due\": false,\n        \"submission_count\": 0\n    },\n    \"is_charge_refundable\": false,\n    \"livemode\": false,\n    \"metadata\": {},\n    \"payment_intent\": \"pi_1JAyTwJSZQVUcJYgBbsz0NuH\",\n    \"reason\": \"fraudulent\",\n    \"status\": \"needs_response\"\n}\n"
  },
  {
    "path": "tests/fixtures/dispute_dp_fakefakefakefake02.json",
    "content": "{\n    \"id\": \"dp_1JAyTwJSZQVUcJYgPqasUEn1\",\n    \"object\": \"dispute\",\n    \"amount\": 1000,\n    \"balance_transaction\": \"txn_1JHRCWJSZQVUcJYgYwW8XpF4\",\n    \"balance_transactions\": [\n        {\n            \"id\": \"txn_1JHRCWJSZQVUcJYgYwW8XpF4\",\n            \"object\": \"balance_transaction\",\n            \"amount\": -1000,\n            \"available_on\": 1626307200,\n            \"created\": 1625755540,\n            \"currency\": \"usd\",\n            \"description\": \"Chargeback withdrawal for ch_1JHRCWJSZQVUcJYgOCavbxio\",\n            \"exchange_rate\": null,\n            \"fee\": 1500,\n            \"fee_details\": [\n                {\n                    \"amount\": 1500,\n                    \"application\": null,\n                    \"currency\": \"usd\",\n                    \"description\": \"Dispute fee\",\n                    \"type\": \"stripe_fee\"\n                }\n            ],\n            \"net\": -2500,\n            \"reporting_category\": \"dispute\",\n            \"source\": \"dp_1JAyTwJSZQVUcJYgPqasUEn1\",\n            \"status\": \"pending\",\n            \"type\": \"adjustment\"\n        }\n    ],\n    \"charge\": \"ch_1JHRCWJSZQVUcJYgOCavbxio\",\n    \"created\": 1625755540,\n    \"currency\": \"usd\",\n    \"evidence\": {\n        \"access_activity_log\": null,\n        \"billing_address\": null,\n        \"cancellation_policy\": null,\n        \"cancellation_policy_disclosure\": null,\n        \"cancellation_rebuttal\": null,\n        \"customer_communication\": null,\n        \"customer_email_address\": \"test1@user.com\",\n        \"customer_name\": \"Jane Doe Test\",\n        \"customer_purchase_ip\": \"223.182.253.19\",\n        \"customer_signature\": null,\n        \"duplicate_charge_documentation\": null,\n        \"duplicate_charge_explanation\": null,\n        \"duplicate_charge_id\": null,\n        \"product_description\": null,\n        \"receipt\": null,\n        \"refund_policy\": null,\n        \"refund_policy_disclosure\": null,\n        \"refund_refusal_explanation\": null,\n        \"service_date\": null,\n        \"service_documentation\": null,\n        \"shipping_address\": null,\n        \"shipping_carrier\": null,\n        \"shipping_date\": null,\n        \"shipping_documentation\": null,\n        \"shipping_tracking_number\": null,\n        \"uncategorized_file\": null,\n        \"uncategorized_text\": null\n    },\n    \"evidence_details\": {\n        \"due_by\": 1626566399,\n        \"has_evidence\": false,\n        \"past_due\": false,\n        \"submission_count\": 0\n    },\n    \"is_charge_refundable\": false,\n    \"livemode\": false,\n    \"metadata\": {},\n    \"payment_intent\": \"pi_1JAyTwJSZQVUcJYgBbsz0NuH\",\n    \"reason\": \"fraudulent\",\n    \"status\": \"needs_response\"\n}\n"
  },
  {
    "path": "tests/fixtures/dispute_dp_funds_reinstated_full.json",
    "content": "{\n    \"id\": \"dp_1JAyTwJSZQVUcJYgPqasUEn1\",\n    \"object\": \"dispute\",\n    \"amount\": 1000,\n    \"balance_transaction\": \"txn_16g5h62eZvKYlo2CQ2AHA89s\",\n    \"balance_transactions\": [\n        {\n            \"id\": \"txn_16g5h62eZvKYlo2CQ2AHA89s\",\n            \"object\": \"balance_transaction\",\n            \"amount\": -1000,\n            \"available_on\": 1626307200,\n            \"created\": 1625755540,\n            \"currency\": \"usd\",\n            \"description\": \"Chargeback withdrawal for ch_fakefakefakefakefake0001\",\n            \"exchange_rate\": null,\n            \"fee\": 1500,\n            \"fee_details\": [\n                {\n                    \"amount\": 1500,\n                    \"application\": null,\n                    \"currency\": \"usd\",\n                    \"description\": \"Dispute fee\",\n                    \"type\": \"stripe_fee\"\n                }\n            ],\n            \"net\": -2500,\n            \"reporting_category\": \"dispute\",\n            \"source\": \"dp_1JAyTwJSZQVUcJYgPqasUEn1\",\n            \"status\": \"pending\",\n            \"type\": \"adjustment\"\n        },\n        {\n            \"id\": \"txn_1JAyV2JSZQVUcJYgETKXUN9k\",\n            \"object\": \"balance_transaction\",\n            \"amount\": 1000,\n            \"available_on\": 1626307200,\n            \"created\": 1625755608,\n            \"currency\": \"usd\",\n            \"description\": \"Chargeback reversal for ch_fakefakefakefakefake0001\",\n            \"exchange_rate\": null,\n            \"fee\": -1500,\n            \"fee_details\": [\n                {\n                    \"amount\": -1500,\n                    \"application\": null,\n                    \"currency\": \"usd\",\n                    \"description\": \"Dispute fee refund\",\n                    \"type\": \"stripe_fee\"\n                }\n            ],\n            \"net\": 2500,\n            \"reporting_category\": \"dispute_reversal\",\n            \"source\": \"dp_1JAyTwJSZQVUcJYgPqasUEn1\",\n            \"status\": \"pending\",\n            \"type\": \"adjustment\"\n        }\n    ],\n    \"charge\": \"ch_fakefakefakefakefake0001\",\n    \"created\": 1625755540,\n    \"currency\": \"usd\",\n    \"evidence\": {\n        \"access_activity_log\": null,\n        \"billing_address\": null,\n        \"cancellation_policy\": null,\n        \"cancellation_policy_disclosure\": null,\n        \"cancellation_rebuttal\": null,\n        \"customer_communication\": null,\n        \"customer_email_address\": \"test1@user.com\",\n        \"customer_name\": \"Jane Doe Test\",\n        \"customer_purchase_ip\": \"223.182.253.19\",\n        \"customer_signature\": null,\n        \"duplicate_charge_documentation\": null,\n        \"duplicate_charge_explanation\": null,\n        \"duplicate_charge_id\": null,\n        \"product_description\": null,\n        \"receipt\": null,\n        \"refund_policy\": null,\n        \"refund_policy_disclosure\": null,\n        \"refund_refusal_explanation\": null,\n        \"service_date\": null,\n        \"service_documentation\": null,\n        \"shipping_address\": null,\n        \"shipping_carrier\": null,\n        \"shipping_date\": null,\n        \"shipping_documentation\": null,\n        \"shipping_tracking_number\": null,\n        \"uncategorized_file\": null,\n        \"uncategorized_text\": \"winning_evidence\"\n    },\n    \"evidence_details\": {\n        \"due_by\": 1626566399,\n        \"has_evidence\": true,\n        \"past_due\": false,\n        \"submission_count\": 1\n    },\n    \"is_charge_refundable\": false,\n    \"livemode\": false,\n    \"metadata\": {},\n    \"payment_intent\": \"pi_1JAyTwJSZQVUcJYgBbsz0NuH\",\n    \"reason\": \"fraudulent\",\n    \"status\": \"under_review\"\n}\n"
  },
  {
    "path": "tests/fixtures/dispute_pi_fakefakefakefake01.json",
    "content": "{\n    \"id\": \"pi_1JAyTwJSZQVUcJYgBbsz0NuH\",\n    \"object\": \"payment_intent\",\n    \"livemode\": false,\n    \"created\": 1557995177,\n    \"metadata\": {},\n    \"amount\": 1000,\n    \"amount_capturable\": 0,\n    \"amount_received\": 1000,\n    \"canceled_at\": null,\n    \"cancellation_reason\": \"\",\n    \"capture_method\": \"automatic\",\n    \"client_secret\": \"pi_1JAyTwJSZQVUcJYgBbsz0NuH_secret_IooFCSw3PeIIqRmJ7SK4yEFVr\",\n    \"confirmation_method\": \"automatic\",\n    \"currency\": \"usd\",\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"description\": \"\",\n    \"last_payment_error\": \"\",\n    \"next_action\": \"\",\n    \"on_behalf_of\": null,\n    \"payment_method\": null,\n    \"payment_method_types\": [\n        \"card\"\n    ],\n    \"receipt_email\": \"\",\n    \"setup_future_usage\": \"off_session\",\n    \"shipping\": \"\",\n    \"statement_descriptor\": \"consulting\",\n    \"status\": \"succeeded\",\n    \"transfer_data\": \"\",\n    \"transfer_group\": \"\"\n}\n"
  },
  {
    "path": "tests/fixtures/dispute_pm_fakefakefakefake01.json",
    "content": "{\n    \"id\": \"card_fakefakefakefakefake0001\",\n    \"object\": \"payment_method\",\n    \"billing_details\": {\n        \"address\": {\n            \"city\": null,\n            \"country\": null,\n            \"line1\": null,\n            \"line2\": null,\n            \"postal_code\": null,\n            \"state\": null\n        },\n        \"email\": null,\n        \"name\": \"alex-nesnes@hotmail.fr\",\n        \"phone\": null\n    },\n    \"card\": {\n        \"brand\": \"visa\",\n        \"checks\": {\n            \"address_line1_check\": null,\n            \"address_postal_code_check\": null,\n            \"cvc_check\": \"pass\"\n        },\n        \"country\": \"US\",\n        \"exp_month\": 2,\n        \"exp_year\": 2022,\n        \"fingerprint\": \"Js2Tq8SMwjNsGwEr\",\n        \"funding\": \"credit\",\n        \"installments\": null,\n        \"last4\": \"0259\",\n        \"network\": \"visa\",\n        \"three_d_secure\": null,\n        \"wallet\": null\n    },\n    \"created\": 1567571746,\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"livemode\": false,\n    \"metadata\": {},\n    \"type\": \"card\"\n}\n"
  },
  {
    "path": "tests/fixtures/dispute_txn_fakefakefakefake01.json",
    "content": "{\n    \"id\": \"txn_1JHRCWJSZQVUcJYgYwW8XpF4\",\n    \"object\": \"balance_transaction\",\n    \"livemode\": null,\n    \"created\": 1557995177,\n    \"metadata\": \"\",\n    \"description\": \"Chargeback withdrawal for ch_1JHRCWJSZQVUcJYgOCavbxio\",\n    \"amount\": -1000,\n    \"available_on\": 1558569600,\n    \"currency\": \"usd\",\n    \"exchange_rate\": null,\n    \"fee\": 1500,\n    \"fee_details\": [\n        {\n            \"amount\": 1500,\n            \"application\": null,\n            \"currency\": \"usd\",\n            \"description\": \"Dispute fee\",\n            \"type\": \"stripe_fee\"\n        }\n    ],\n    \"net\": -2500,\n    \"source\": \"dp_1JHRCWJSZQVUcJYgEHzUiuvb\",\n    \"reporting_category\": \"dispute\",\n    \"status\": \"pending\",\n    \"type\": \"adjustment\"\n}\n"
  },
  {
    "path": "tests/fixtures/event_account_application_authorized.json",
    "content": "{\n  \"id\": \"evt_1Iu8ZfA3kq9o1aTcf3b7EknK\",\n  \"object\": \"event\",\n  \"account\": \"acct_1Fg9jUA3kq9o1aTc\",\n  \"api_version\": \"2020-08-27\",\n  \"created\": 1621742759,\n  \"data\": {\n    \"object\": {\n      \"id\": \"ca_JWVN6vOJJs3RKsff0VUwIUfmUmN0VYqe\",\n      \"object\": \"application\",\n      \"name\": null\n    }\n  },\n  \"livemode\": false,\n  \"pending_webhooks\": 2,\n  \"request\": {\n    \"id\": null,\n    \"idempotency_key\": null\n  },\n  \"type\": \"account.application.deauthorized\"\n}\n"
  },
  {
    "path": "tests/fixtures/event_account_application_deauthorized.json",
    "content": "{\n  \"id\": \"evt_1Iu8ZfA3kq9o1aTcf3b7EknK\",\n  \"object\": \"event\",\n  \"account\": \"acct_1Fg9jUA3kq9o1aTc\",\n  \"api_version\": \"2020-08-27\",\n  \"created\": 1621742759,\n  \"data\": {\n    \"object\": {\n      \"id\": \"ca_JWVN6vOJJs3RKsff0VUwIUfmUmN0VYqe\",\n      \"object\": \"application\",\n      \"name\": null\n    }\n  },\n  \"livemode\": false,\n  \"pending_webhooks\": 2,\n  \"request\": {\n    \"id\": null,\n    \"idempotency_key\": null\n  },\n  \"type\": \"account.application.deauthorized\"\n}\n"
  },
  {
    "path": "tests/fixtures/event_account_updated_custom.json",
    "content": "{\n    \"id\": \"evt_1Itt6eB9wPxT0ovY3LLhi5bw\",\n    \"object\": \"event\",\n    \"account\": \"acct_1IuHosQveW0ONQsd\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1621683300,\n    \"data\": {\n        \"object\": {\n            \"business_profile\": {\n                \"mcc\": null,\n                \"name\": \"Express Account\",\n                \"support_address\": null,\n                \"support_email\": null,\n                \"support_phone\": null,\n                \"support_url\": null,\n                \"url\": \"https://djstripe.com/\"\n            },\n            \"capabilities\": {\n                \"transfers\": \"active\"\n            },\n            \"charges_enabled\": true,\n            \"country\": \"US\",\n            \"created\": 1622034893,\n            \"default_currency\": \"usd\",\n            \"details_submitted\": true,\n            \"email\": \"djstripe@example.com\",\n            \"external_accounts\": {\n                \"data\": [\n                    {\n                        \"account\": \"acct_1IuHosQveW0ONQsd\",\n                        \"account_holder_name\": null,\n                        \"account_holder_type\": null,\n                        \"available_payout_methods\": [\n                            \"standard\"\n                        ],\n                        \"bank_name\": \"STRIPE TEST BANK\",\n                        \"country\": \"US\",\n                        \"currency\": \"usd\",\n                        \"default_for_currency\": true,\n                        \"fingerprint\": \"E4D818AgpkNPGkvl\",\n                        \"id\": \"ba_1IuVozQveW0ONQsd3dWG85e2\",\n                        \"last4\": \"6789\",\n                        \"metadata\": {},\n                        \"object\": \"bank_account\",\n                        \"routing_number\": \"110000000\",\n                        \"status\": \"new\"\n                    },\n                    {\n                        \"id\": \"card_1IuVlSQveW0ONQsdkXBUUHyE\",\n                        \"object\": \"card\",\n                        \"account\": \"acct_1IuHosQveW0ONQsd\",\n                        \"address_city\": null,\n                        \"address_country\": null,\n                        \"address_line1\": null,\n                        \"address_line1_check\": null,\n                        \"address_line2\": null,\n                        \"address_state\": null,\n                        \"address_zip\": null,\n                        \"address_zip_check\": null,\n                        \"available_payout_methods\": [\n                            \"standard\",\n                            \"instant\"\n                        ],\n                        \"brand\": \"MasterCard\",\n                        \"country\": \"US\",\n                        \"currency\": \"usd\",\n                        \"cvc_check\": null,\n                        \"default_for_currency\": true,\n                        \"dynamic_last4\": null,\n                        \"exp_month\": 5,\n                        \"exp_year\": 2022,\n                        \"fingerprint\": \"SgIrwXiq2B8M5ab2\",\n                        \"funding\": \"debit\",\n                        \"last4\": \"8210\",\n                        \"metadata\": {},\n                        \"name\": null,\n                        \"tokenization_method\": null\n                    }\n                ],\n                \"has_more\": false,\n                \"object\": \"list\",\n                \"total_count\": 1,\n                \"url\": \"/v1/accounts/acct_1IuHosQveW0ONQsd/external_accounts\"\n            },\n            \"id\": \"acct_1IuHosQveW0ONQsd\",\n            \"login_links\": {\n                \"data\": [],\n                \"has_more\": false,\n                \"object\": \"list\",\n                \"total_count\": 0,\n                \"url\": \"/v1/accounts/acct_1IuHosQveW0ONQsd/login_links\"\n            },\n            \"metadata\": {\n                \"foo\": \"bar\"\n            },\n            \"object\": \"account\",\n            \"payouts_enabled\": true,\n            \"requirements\": {\n                \"current_deadline\": null,\n                \"currently_due\": [],\n                \"disabled_reason\": null,\n                \"errors\": [],\n                \"eventually_due\": [\n                    \"individual.verification.document\"\n                ],\n                \"past_due\": [],\n                \"pending_verification\": []\n            },\n            \"settings\": {\n                \"bacs_debit_payments\": {},\n                \"branding\": {\n                    \"icon\": null,\n                    \"logo\": null,\n                    \"primary_color\": null,\n                    \"secondary_color\": null\n                },\n                \"card_issuing\": {\n                    \"tos_acceptance\": {\n                        \"date\": null,\n                        \"ip\": null\n                    }\n                },\n                \"card_payments\": {\n                    \"decline_on\": {\n                        \"avs_failure\": false,\n                        \"cvc_failure\": false\n                    },\n                    \"statement_descriptor_prefix\": null\n                },\n                \"dashboard\": {\n                    \"display_name\": \"Djstripe\",\n                    \"timezone\": \"Etc/UTC\"\n                },\n                \"payments\": {\n                    \"statement_descriptor\": \"DJSTRIPE.COM\",\n                    \"statement_descriptor_kana\": null,\n                    \"statement_descriptor_kanji\": null\n                },\n                \"payouts\": {\n                    \"debit_negative_balances\": true,\n                    \"schedule\": {\n                        \"delay_days\": 2,\n                        \"interval\": \"daily\"\n                    },\n                    \"statement_descriptor\": null\n                },\n                \"sepa_debit_payments\": {}\n            },\n            \"tos_acceptance\": {\n                \"date\": 1622034889\n            },\n            \"type\": \"express\"\n        },\n        \"previous_attributes\": {\n            \"metadata\": {}\n        }\n    },\n    \"livemode\": false,\n    \"pending_webhooks\": 2,\n    \"request\": {\n        \"id\": \"req_A60l4iFvAHopqU\",\n        \"idempotency_key\": null\n    },\n    \"type\": \"account.updated\"\n}\n"
  },
  {
    "path": "tests/fixtures/event_account_updated_express.json",
    "content": "{\n    \"id\": \"evt_1Itt6eB9wPxT0ovY3LLhi5bw\",\n    \"object\": \"event\",\n    \"account\": \"acct_1IuHosQveW0ONQsd\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1621683300,\n    \"data\": {\n        \"object\": {\n            \"business_profile\": {\n                \"mcc\": null,\n                \"name\": \"Express Account\",\n                \"support_address\": null,\n                \"support_email\": null,\n                \"support_phone\": null,\n                \"support_url\": null,\n                \"url\": \"https://djstripe.com/\"\n            },\n            \"capabilities\": {\n                \"transfers\": \"active\"\n            },\n            \"charges_enabled\": true,\n            \"country\": \"US\",\n            \"created\": 1622034893,\n            \"default_currency\": \"usd\",\n            \"details_submitted\": true,\n            \"email\": \"djstripe@example.com\",\n            \"external_accounts\": {\n                \"data\": [\n                    {\n                        \"account\": \"acct_1IuHosQveW0ONQsd\",\n                        \"account_holder_name\": null,\n                        \"account_holder_type\": null,\n                        \"available_payout_methods\": [\n                            \"standard\"\n                        ],\n                        \"bank_name\": \"STRIPE TEST BANK\",\n                        \"country\": \"US\",\n                        \"currency\": \"usd\",\n                        \"default_for_currency\": true,\n                        \"fingerprint\": \"E4D818AgpkNPGkvl\",\n                        \"id\": \"ba_1IuVozQveW0ONQsd3dWG85e2\",\n                        \"last4\": \"6789\",\n                        \"metadata\": {},\n                        \"object\": \"bank_account\",\n                        \"routing_number\": \"110000000\",\n                        \"status\": \"new\"\n                    },\n                    {\n                        \"id\": \"card_1IuVlSQveW0ONQsdkXBUUHyE\",\n                        \"object\": \"card\",\n                        \"account\": \"acct_1IuHosQveW0ONQsd\",\n                        \"address_city\": null,\n                        \"address_country\": null,\n                        \"address_line1\": null,\n                        \"address_line1_check\": null,\n                        \"address_line2\": null,\n                        \"address_state\": null,\n                        \"address_zip\": null,\n                        \"address_zip_check\": null,\n                        \"available_payout_methods\": [\n                            \"standard\",\n                            \"instant\"\n                        ],\n                        \"brand\": \"MasterCard\",\n                        \"country\": \"US\",\n                        \"currency\": \"usd\",\n                        \"cvc_check\": null,\n                        \"default_for_currency\": true,\n                        \"dynamic_last4\": null,\n                        \"exp_month\": 5,\n                        \"exp_year\": 2022,\n                        \"fingerprint\": \"SgIrwXiq2B8M5ab2\",\n                        \"funding\": \"debit\",\n                        \"last4\": \"8210\",\n                        \"metadata\": {},\n                        \"name\": null,\n                        \"tokenization_method\": null\n                    }\n                ],\n                \"has_more\": false,\n                \"object\": \"list\",\n                \"total_count\": 1,\n                \"url\": \"/v1/accounts/acct_1IuHosQveW0ONQsd/external_accounts\"\n            },\n            \"id\": \"acct_1IuHosQveW0ONQsd\",\n            \"login_links\": {\n                \"data\": [],\n                \"has_more\": false,\n                \"object\": \"list\",\n                \"total_count\": 0,\n                \"url\": \"/v1/accounts/acct_1IuHosQveW0ONQsd/login_links\"\n            },\n            \"metadata\": {\n                \"foo\": \"bar\"\n            },\n            \"object\": \"account\",\n            \"payouts_enabled\": true,\n            \"requirements\": {\n                \"current_deadline\": null,\n                \"currently_due\": [],\n                \"disabled_reason\": null,\n                \"errors\": [],\n                \"eventually_due\": [\n                    \"individual.verification.document\"\n                ],\n                \"past_due\": [],\n                \"pending_verification\": []\n            },\n            \"settings\": {\n                \"bacs_debit_payments\": {},\n                \"branding\": {\n                    \"icon\": null,\n                    \"logo\": null,\n                    \"primary_color\": null,\n                    \"secondary_color\": null\n                },\n                \"card_issuing\": {\n                    \"tos_acceptance\": {\n                        \"date\": null,\n                        \"ip\": null\n                    }\n                },\n                \"card_payments\": {\n                    \"decline_on\": {\n                        \"avs_failure\": false,\n                        \"cvc_failure\": false\n                    },\n                    \"statement_descriptor_prefix\": null\n                },\n                \"dashboard\": {\n                    \"display_name\": \"Djstripe\",\n                    \"timezone\": \"Etc/UTC\"\n                },\n                \"payments\": {\n                    \"statement_descriptor\": \"DJSTRIPE.COM\",\n                    \"statement_descriptor_kana\": null,\n                    \"statement_descriptor_kanji\": null\n                },\n                \"payouts\": {\n                    \"debit_negative_balances\": true,\n                    \"schedule\": {\n                        \"delay_days\": 2,\n                        \"interval\": \"daily\"\n                    },\n                    \"statement_descriptor\": null\n                },\n                \"sepa_debit_payments\": {}\n            },\n            \"tos_acceptance\": {\n                \"date\": 1622034889\n            },\n            \"type\": \"express\"\n        },\n        \"previous_attributes\": {\n            \"metadata\": {}\n        }\n    },\n    \"livemode\": false,\n    \"pending_webhooks\": 2,\n    \"request\": {\n        \"id\": \"req_A60l4iFvAHopqU\",\n        \"idempotency_key\": null\n    },\n    \"type\": \"account.updated\"\n}\n"
  },
  {
    "path": "tests/fixtures/event_account_updated_standard.json",
    "content": "{\n  \"id\": \"evt_1Itt6eB9wPxT0ovY3LLhi5bw\",\n  \"object\": \"event\",\n  \"account\": \"acct_1Fg9jUA3kq9o1aTc\",\n  \"api_version\": \"2020-08-27\",\n  \"created\": 1621683300,\n  \"data\": {\n    \"object\": {\n      \"id\": \"acct_1Fg9jUA3kq9o1aTc\",\n      \"object\": \"account\",\n      \"business_profile\": {\n        \"mcc\": null,\n        \"name\": null,\n        \"support_address\": null,\n        \"support_email\": null,\n        \"support_phone\": null,\n        \"support_url\": null,\n        \"url\": null,\n        \"product_description\": null\n      },\n      \"capabilities\": {},\n      \"charges_enabled\": false,\n      \"country\": \"US\",\n      \"default_currency\": \"usd\",\n      \"details_submitted\": false,\n      \"email\": null,\n      \"payouts_enabled\": false,\n      \"settings\": {\n        \"bacs_debit_payments\": {},\n        \"branding\": {\n          \"icon\": null,\n          \"logo\": null,\n          \"primary_color\": null,\n          \"secondary_color\": null\n        },\n        \"card_issuing\": {\n          \"tos_acceptance\": {\n            \"date\": null,\n            \"ip\": null\n          }\n        },\n        \"card_payments\": {\n          \"statement_descriptor_prefix\": null,\n          \"decline_on\": {\n            \"avs_failure\": false,\n            \"cvc_failure\": true\n          }\n        },\n        \"dashboard\": {\n          \"display_name\": null,\n          \"timezone\": \"Etc/UTC\"\n        },\n        \"payments\": {\n          \"statement_descriptor\": null,\n          \"statement_descriptor_kana\": null,\n          \"statement_descriptor_kanji\": null\n        },\n        \"sepa_debit_payments\": {},\n        \"payouts\": {\n          \"debit_negative_balances\": true,\n          \"schedule\": {\n            \"delay_days\": 2,\n            \"interval\": \"daily\"\n          },\n          \"statement_descriptor\": null\n        }\n      },\n      \"type\": \"standard\",\n      \"business_type\": null,\n      \"created\": 1621683297,\n      \"external_accounts\": {\n        \"object\": \"list\",\n        \"data\": [],\n        \"has_more\": false,\n        \"total_count\": 0,\n        \"url\": \"/v1/accounts/acct_1Fg9jUA3kq9o1aTc/external_accounts\"\n      },\n      \"metadata\": {\n        \"foo\": \"bar\"\n      },\n      \"requirements\": {\n        \"current_deadline\": null,\n        \"currently_due\": [\n          \"business_profile.product_description\",\n          \"business_profile.support_phone\",\n          \"business_profile.url\",\n          \"email\",\n          \"external_account\",\n          \"tos_acceptance.date\",\n          \"tos_acceptance.ip\"\n        ],\n        \"disabled_reason\": \"requirements.past_due\",\n        \"errors\": [],\n        \"eventually_due\": [\n          \"business_profile.product_description\",\n          \"business_profile.support_phone\",\n          \"business_profile.url\",\n          \"email\",\n          \"external_account\",\n          \"tos_acceptance.date\",\n          \"tos_acceptance.ip\"\n        ],\n        \"past_due\": [\n          \"external_account\",\n          \"tos_acceptance.date\",\n          \"tos_acceptance.ip\"\n        ],\n        \"pending_verification\": []\n      },\n      \"tos_acceptance\": {\n        \"date\": null,\n        \"ip\": null,\n        \"user_agent\": null\n      }\n    },\n    \"previous_attributes\": {\n      \"metadata\": {}\n    }\n  },\n  \"livemode\": false,\n  \"pending_webhooks\": 2,\n  \"request\": {\n    \"id\": \"req_A60l4iFvAHopqU\",\n    \"idempotency_key\": null\n  },\n  \"type\": \"account.updated\"\n}\n"
  },
  {
    "path": "tests/fixtures/event_external_account_bank_account_created.json",
    "content": "{\n    \"id\": \"evt_1IuKmFQveW0ONQsdEAB1O64Y\",\n    \"object\": \"event\",\n    \"account\": \"acct_1IuHosQveW0ONQsd\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1621789667,\n    \"data\": {\n        \"object\": {\n            \"id\": \"ba_1IuVozQveW0ONQsd3dWG85e2\",\n            \"object\": \"bank_account\",\n            \"account\": \"acct_1IuHosQveW0ONQsd\",\n            \"account_holder_name\": \"Jenny Rosen\",\n            \"account_holder_type\": \"individual\",\n            \"available_payout_methods\": [\n                \"standard\"\n            ],\n            \"bank_name\": \"STRIPE TEST BANK\",\n            \"country\": \"US\",\n            \"currency\": \"usd\",\n            \"default_for_currency\": false,\n            \"fingerprint\": \"OAI2ZEI2iIJVbF1o\",\n            \"last4\": \"1116\",\n            \"metadata\": {},\n            \"routing_number\": \"110000000\",\n            \"status\": \"verified\"\n        }\n    },\n    \"livemode\": false,\n    \"pending_webhooks\": 2,\n    \"request\": {\n        \"id\": \"req_mIvRpJ6b3Y2pOW\",\n        \"idempotency_key\": null\n    },\n    \"type\": \"account.external_account.created\"\n}\n"
  },
  {
    "path": "tests/fixtures/event_external_account_bank_account_deleted.json",
    "content": "{\n  \"id\": \"evt_1IuKmFQveW0ONQsdEAB1O64Y\",\n  \"object\": \"event\",\n  \"account\": \"acct_1IuHosQveW0ONQsd\",\n  \"api_version\": \"2020-08-27\",\n  \"created\": 1621789667,\n  \"data\": {\n      \"object\": {\n          \"id\": \"ba_1IuVozQveW0ONQsd3dWG85e2\",\n          \"object\": \"bank_account\",\n          \"account\": \"acct_1IuHosQveW0ONQsd\",\n          \"account_holder_name\": \"Jenny Rosen\",\n          \"account_holder_type\": \"individual\",\n          \"available_payout_methods\": [\n              \"standard\"\n          ],\n          \"bank_name\": \"STRIPE TEST BANK\",\n          \"country\": \"US\",\n          \"currency\": \"usd\",\n          \"default_for_currency\": false,\n          \"fingerprint\": \"OAI2ZEI2iIJVbF1o\",\n          \"last4\": \"1116\",\n          \"metadata\": {},\n          \"routing_number\": \"110000000\",\n          \"status\": \"verified\"\n      }\n  },\n  \"livemode\": false,\n  \"pending_webhooks\": 2,\n  \"request\": {\n      \"id\": \"req_mIvRpJ6b3Y2pOW\",\n      \"idempotency_key\": null\n  },\n  \"type\": \"account.external_account.deleted\"\n}\n"
  },
  {
    "path": "tests/fixtures/event_external_account_bank_account_updated.json",
    "content": "{\n  \"id\": \"evt_1IuKmFQveW0ONQsdEAB1O64Y\",\n  \"object\": \"event\",\n  \"account\": \"acct_1IuHosQveW0ONQsd\",\n  \"api_version\": \"2020-08-27\",\n  \"created\": 1621789667,\n  \"data\": {\n      \"object\": {\n          \"id\": \"ba_1IuVozQveW0ONQsd3dWG85e2\",\n          \"object\": \"bank_account\",\n          \"account\": \"acct_1IuHosQveW0ONQsd\",\n          \"account_holder_name\": \"Jenny Rosen-Updated\",\n          \"account_holder_type\": \"individual\",\n          \"available_payout_methods\": [\n              \"standard\"\n          ],\n          \"bank_name\": \"STRIPE TEST BANK\",\n          \"country\": \"US\",\n          \"currency\": \"usd\",\n          \"default_for_currency\": false,\n          \"fingerprint\": \"OAI2ZEI2iIJVbF1o\",\n          \"last4\": \"1116\",\n          \"metadata\": {},\n          \"routing_number\": \"110000000\",\n          \"status\": \"verified\"\n      }\n  },\n  \"livemode\": false,\n  \"pending_webhooks\": 2,\n  \"request\": {\n      \"id\": \"req_mIvRpJ6b3Y2pOW\",\n      \"idempotency_key\": null\n  },\n  \"type\": \"account.external_account.updated\"\n}\n"
  },
  {
    "path": "tests/fixtures/event_external_account_card_created.json",
    "content": "{\n  \"id\": \"evt_1IuIg0QveW0ONQsdDLp7otQC\",\n  \"object\": \"event\",\n  \"account\": \"acct_1IuHosQveW0ONQsd\",\n  \"api_version\": \"2020-08-27\",\n  \"created\": 1621781592,\n  \"data\": {\n    \"object\": {\n      \"id\": \"card_1IuVlSQveW0ONQsdkXBUUHyE\",\n      \"object\": \"card\",\n      \"account\": \"acct_1IuHosQveW0ONQsd\",\n      \"address_city\": null,\n      \"address_country\": null,\n      \"address_line1\": null,\n      \"address_line1_check\": null,\n      \"address_line2\": null,\n      \"address_state\": null,\n      \"address_zip\": null,\n      \"address_zip_check\": null,\n      \"available_payout_methods\": [\n        \"standard\",\n        \"instant\"\n      ],\n      \"brand\": \"MasterCard\",\n      \"country\": \"US\",\n      \"currency\": \"usd\",\n      \"cvc_check\": null,\n      \"default_for_currency\": true,\n      \"dynamic_last4\": null,\n      \"exp_month\": 5,\n      \"exp_year\": 2022,\n      \"fingerprint\": \"SgIrwXiq2B8M5ab2\",\n      \"funding\": \"debit\",\n      \"last4\": \"8210\",\n      \"metadata\": {},\n      \"name\": null,\n      \"tokenization_method\": null\n    }\n  },\n  \"livemode\": false,\n  \"pending_webhooks\": 2,\n  \"request\": {\n    \"id\": \"req_IQUywOh6PF8AyM\",\n    \"idempotency_key\": null\n  },\n  \"type\": \"account.external_account.created\"\n}\n"
  },
  {
    "path": "tests/fixtures/event_external_account_card_deleted.json",
    "content": "{\n    \"id\": \"evt_1IuIg0QveW0ONQsdDLp7otQC\",\n    \"object\": \"event\",\n    \"account\": \"acct_1IuHosQveW0ONQsd\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1621781592,\n    \"data\": {\n        \"object\": {\n            \"id\": \"card_1IuVlSQveW0ONQsdkXBUUHyE\",\n            \"object\": \"card\",\n            \"account\": \"acct_1IuHosQveW0ONQsd\",\n            \"address_city\": null,\n            \"address_country\": null,\n            \"address_line1\": null,\n            \"address_line1_check\": null,\n            \"address_line2\": null,\n            \"address_state\": null,\n            \"address_zip\": null,\n            \"address_zip_check\": null,\n            \"available_payout_methods\": [\n                \"standard\",\n                \"instant\"\n            ],\n            \"brand\": \"MasterCard\",\n            \"country\": \"US\",\n            \"currency\": \"usd\",\n            \"cvc_check\": null,\n            \"default_for_currency\": true,\n            \"dynamic_last4\": null,\n            \"exp_month\": 5,\n            \"exp_year\": 2022,\n            \"fingerprint\": \"SgIrwXiq2B8M5ab2\",\n            \"funding\": \"debit\",\n            \"last4\": \"8210\",\n            \"metadata\": {},\n            \"name\": null,\n            \"tokenization_method\": null\n        }\n    },\n    \"livemode\": false,\n    \"pending_webhooks\": 2,\n    \"request\": {\n        \"id\": \"req_IQUywOh6PF8AyM\",\n        \"idempotency_key\": null\n    },\n    \"type\": \"account.external_account.deleted\"\n}\n"
  },
  {
    "path": "tests/fixtures/event_external_account_card_updated.json",
    "content": "{\n    \"id\": \"evt_1IuIg0QveW0ONQsdDLp7otQC\",\n    \"object\": \"event\",\n    \"account\": \"acct_1IuHosQveW0ONQsd\",\n    \"api_version\": \"2020-08-27\",\n    \"created\": 1621781592,\n    \"data\": {\n      \"object\": {\n        \"id\": \"card_1IuVlSQveW0ONQsdkXBUUHyE\",\n        \"object\": \"card\",\n        \"account\": \"acct_1IuHosQveW0ONQsd\",\n        \"address_city\": null,\n        \"address_country\": null,\n        \"address_line1\": null,\n        \"address_line1_check\": null,\n        \"address_line2\": null,\n        \"address_state\": null,\n        \"address_zip\": null,\n        \"address_zip_check\": null,\n        \"available_payout_methods\": [\n          \"standard\",\n          \"instant\"\n        ],\n        \"brand\": \"MasterCard\",\n        \"country\": \"US\",\n        \"currency\": \"usd\",\n        \"cvc_check\": null,\n        \"default_for_currency\": true,\n        \"dynamic_last4\": null,\n        \"exp_month\": 5,\n        \"exp_year\": 2022,\n        \"fingerprint\": \"SgIrwXiq2B8M5ab2\",\n        \"funding\": \"debit\",\n        \"last4\": \"8210\",\n        \"metadata\": {},\n        \"name\": \"Test Card\",\n        \"tokenization_method\": null\n      }\n    },\n    \"livemode\": false,\n    \"pending_webhooks\": 2,\n    \"request\": {\n      \"id\": \"req_IQUywOh6PF8AyM\",\n      \"idempotency_key\": null\n    },\n    \"type\": \"account.external_account.updated\"\n  }\n"
  },
  {
    "path": "tests/fixtures/invoice_in_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"in_fakefakefakefakefake0001\",\n    \"object\": \"invoice\",\n    \"account_country\": \"US\",\n    \"account_name\": \"dj-stripe scratch\",\n    \"amount_due\": 2000,\n    \"amount_paid\": 2000,\n    \"amount_remaining\": 0,\n    \"application_fee_amount\": null,\n    \"attempt_count\": 1,\n    \"attempted\": true,\n    \"auto_advance\": false,\n    \"billing_reason\": \"subscription_create\",\n    \"charge\": \"ch_fakefakefakefakefake0001\",\n    \"collection_method\": \"charge_automatically\",\n    \"created\": 1557995176,\n    \"currency\": \"usd\",\n    \"custom_fields\": null,\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"customer_address\": null,\n    \"customer_email\": \"michael.smith@example.com\",\n    \"customer_name\": null,\n    \"customer_phone\": null,\n    \"customer_shipping\": null,\n    \"customer_tax_exempt\": \"none\",\n    \"customer_tax_ids\": [],\n    \"default_payment_method\": null,\n    \"default_source\": null,\n    \"default_tax_rates\": [{\n        \"id\": \"txr_fakefakefakefakefake0001\",\n        \"object\": \"tax_rate\",\n        \"active\": true,\n        \"created\": 1593225980,\n        \"description\": null,\n        \"display_name\": \"VAT\",\n        \"inclusive\": true,\n        \"jurisdiction\": \"Example1\",\n        \"livemode\": false,\n        \"metadata\": {\n            \"djstripe_test_fake_id\": \"txr_fakefakefakefakefake0001\"\n        },\n        \"percentage\": 15.0\n    }],\n    \"description\": null,\n    \"discount\": null,\n    \"discounts\": [],\n    \"due_date\": null,\n    \"ending_balance\": 0,\n    \"footer\": null,\n    \"hosted_invoice_url\": \"https://pay.stripe.com/invoice/invst_5Z1RsP0atfAS4t9CCnnEDTDyUG\",\n    \"invoice_pdf\": \"https://pay.stripe.com/invoice/invst_5Z1RsP0atfAS4t9CCnnEDTDyUG/pdf\",\n    \"lines\": {},\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"in_fakefakefakefakefake0001\"\n    },\n    \"next_payment_attempt\": null,\n    \"number\": \"E5B23224-0001\",\n    \"paid\": true,\n    \"payment_intent\": \"pi_fakefakefakefakefake0001\",\n    \"period_end\": 1557995176,\n    \"period_start\": 1557995176,\n    \"post_payment_credit_notes_amount\": 0,\n    \"pre_payment_credit_notes_amount\": 0,\n    \"receipt_number\": null,\n    \"starting_balance\": 0,\n    \"statement_descriptor\": null,\n    \"status\": \"paid\",\n    \"status_transitions\": {\n        \"finalized_at\": 1593225981,\n        \"marked_uncollectible_at\": null,\n        \"paid_at\": 1593225983,\n        \"voided_at\": null\n    },\n    \"subscription\": \"sub_fakefakefakefakefake0001\",\n    \"subtotal\": 2000,\n    \"tax\": 261,\n    \"tax_percent\": null,\n    \"total\": 2000,\n    \"total_tax_amounts\": [{\n        \"amount\": 261,\n        \"inclusive\": true,\n        \"tax_rate\": \"txr_fakefakefakefakefake0001\"\n    }],\n    \"transfer_data\": null,\n    \"webhooks_delivered_at\": 1557995178\n}\n"
  },
  {
    "path": "tests/fixtures/invoice_in_fakefakefakefakefake0004.json",
    "content": "{\n    \"id\": \"in_fakefakefakefakefake0004\",\n    \"object\": \"invoice\",\n    \"account_country\": \"US\",\n    \"account_name\": \"dj-stripe scratch\",\n    \"amount_due\": 4000,\n    \"amount_paid\": 4000,\n    \"amount_remaining\": 0,\n    \"application_fee_amount\": null,\n    \"attempt_count\": 1,\n    \"attempted\": true,\n    \"auto_advance\": false,\n    \"billing_reason\": \"subscription_create\",\n    \"charge\": \"ch_1GyU3gCOCguPTL2BnyYlJe2x\",\n    \"collection_method\": \"charge_automatically\",\n    \"created\": 1570941590,\n    \"currency\": \"usd\",\n    \"custom_fields\": null,\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"customer_address\": null,\n    \"customer_email\": \"michael.smith@example.com\",\n    \"customer_name\": null,\n    \"customer_phone\": null,\n    \"customer_shipping\": null,\n    \"customer_tax_exempt\": \"none\",\n    \"customer_tax_ids\": [],\n    \"default_payment_method\": null,\n    \"default_source\": null,\n    \"default_tax_rates\": [],\n    \"description\": null,\n    \"discount\": null,\n    \"discounts\": [],\n    \"due_date\": null,\n    \"ending_balance\": 0,\n    \"footer\": null,\n    \"hosted_invoice_url\": \"https://pay.stripe.com/invoice/invst_ttp3K5exxywlAI3cGqCDxEWfsM\",\n    \"invoice_pdf\": \"https://pay.stripe.com/invoice/invst_ttp3K5exxywlAI3cGqCDxEWfsM/pdf\",\n    \"lines\": {\n        \"object\": \"list\",\n        \"data\": [{\n            \"id\": \"il_1GyU3gCOCguPTL2B69cIweEY\",\n            \"object\": \"line_item\",\n            \"amount\": 4000,\n            \"currency\": \"usd\",\n            \"description\": \"1 \\u00d7 Fake Product (at $40.00 / month)\",\n            \"discountable\": true,\n            \"livemode\": false,\n            \"metadata\": {\n                \"djstripe_test_fake_id\": \"sub_fakefakefakefakefake0002\"\n            },\n            \"period\": {\n                \"end\": 1595817984,\n                \"start\": 1593225984\n            },\n            \"plan\": {\n                \"id\": \"silver41294\",\n                \"object\": \"plan\",\n                \"active\": true,\n                \"aggregate_usage\": null,\n                \"amount\": 4000,\n                \"amount_decimal\": \"4000\",\n                \"billing_scheme\": \"per_unit\",\n                \"created\": 1570941587,\n                \"currency\": \"usd\",\n                \"interval\": \"month\",\n                \"interval_count\": 1,\n                \"livemode\": false,\n                \"metadata\": {},\n                \"nickname\": \"New plan name\",\n                \"product\": \"prod_fake1\",\n                \"tiers\": null,\n                \"tiers_mode\": null,\n                \"transform_usage\": null,\n                \"trial_period_days\": 12,\n                \"usage_type\": \"licensed\"\n            },\n            \"price\": {\n                \"id\": \"silver41294\",\n                \"object\": \"price\",\n                \"active\": true,\n                \"billing_scheme\": \"per_unit\",\n                \"created\": 1593225979,\n                \"currency\": \"usd\",\n                \"livemode\": false,\n                \"lookup_key\": null,\n                \"metadata\": {},\n                \"nickname\": \"New plan name\",\n                \"product\": \"prod_fake1\",\n                \"recurring\": {\n                    \"aggregate_usage\": null,\n                    \"interval\": \"month\",\n                    \"interval_count\": 1,\n                    \"trial_period_days\": 12,\n                    \"usage_type\": \"licensed\"\n                },\n                \"tiers_mode\": null,\n                \"transform_quantity\": null,\n                \"type\": \"recurring\",\n                \"unit_amount\": 4000,\n                \"unit_amount_decimal\": \"4000\"\n            },\n            \"proration\": false,\n            \"quantity\": 1,\n            \"subscription\": \"sub_fakefakefakefakefake0002\",\n            \"subscription_item\": \"si_HXZCuxDh3W7EQm\",\n            \"tax_amounts\": [{\n                \"amount\": 522,\n                \"inclusive\": true,\n                \"tax_rate\": \"txr_fakefakefakefakefake0001\"\n            }],\n            \"tax_rates\": [{\n                \"id\": \"txr_fakefakefakefakefake0001\",\n                \"object\": \"tax_rate\",\n                \"active\": true,\n                \"created\": 1593225980,\n                \"description\": null,\n                \"display_name\": \"VAT\",\n                \"inclusive\": true,\n                \"jurisdiction\": \"Example1\",\n                \"livemode\": false,\n                \"metadata\": {\n                    \"djstripe_test_fake_id\": \"txr_fakefakefakefakefake0001\"\n                },\n                \"percentage\": 15.0\n            }],\n            \"type\": \"subscription\"\n        }],\n        \"has_more\": false,\n        \"total_count\": 1,\n        \"url\": \"/v1/invoices/in_fakefakefakefakefake0004/lines\"\n    },\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"in_fakefakefakefakefake0004\"\n    },\n    \"next_payment_attempt\": null,\n    \"number\": \"E5B23224-0002\",\n    \"paid\": true,\n    \"payment_intent\": \"pi_1GyU3gCOCguPTL2BVH2OIzjf\",\n    \"period_end\": 1570941590,\n    \"period_start\": 1570941590,\n    \"post_payment_credit_notes_amount\": 0,\n    \"pre_payment_credit_notes_amount\": 0,\n    \"receipt_number\": null,\n    \"starting_balance\": 0,\n    \"statement_descriptor\": null,\n    \"status\": \"paid\",\n    \"status_transitions\": {\n        \"finalized_at\": 1593225984,\n        \"marked_uncollectible_at\": null,\n        \"paid_at\": 1593225985,\n        \"voided_at\": null\n    },\n    \"subscription\": \"sub_fakefakefakefakefake0002\",\n    \"subtotal\": 4000,\n    \"tax\": 522,\n    \"tax_percent\": null,\n    \"total\": 4000,\n    \"total_tax_amounts\": [{\n        \"amount\": 522,\n        \"inclusive\": true,\n        \"tax_rate\": \"txr_fakefakefakefakefake0001\"\n    }],\n    \"transfer_data\": null,\n    \"webhooks_delivered_at\": 1570941591\n}\n"
  },
  {
    "path": "tests/fixtures/line_item_il_invoice_item_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"il_fakefakefakefakefake0001\",\n    \"object\": \"line_item\",\n    \"livemode\": false,\n    \"created\": null,\n    \"metadata\": {},\n    \"description\": \"LINE ITEM 1\",\n    \"amount\": 27560,\n    \"amount_excluding_tax\": 27560,\n    \"currency\": \"usd\",\n    \"discount_amounts\": [{\n        \"amount\": 7028,\n        \"discount\": \"di_fakefakefakefakefake0001\"\n    }],\n    \"discountable\": true,\n    \"discounts\": [\n        \"di_fakefakefakefakefake0001\"\n    ],\n    \"invoice_item\": \"ii_fakefakefakefakefake0001\",\n    \"period\": {\n        \"end\": 1666876260,\n        \"start\": 1666876260\n    },\n    \"period_end\": 123456789,\n    \"period_start\": 12111,\n    \"price\": {\n        \"id\": \"price_1LKNRmJSZQVUcJYgRkQDOYBL\",\n        \"type\": \"one_time\",\n        \"active\": false,\n        \"object\": \"price\",\n        \"created\": 1657549130,\n        \"product\": \"prod_M2SMqVbyQ81ihY\",\n        \"currency\": \"usd\",\n        \"livemode\": false,\n        \"metadata\": {},\n        \"nickname\": null,\n        \"recurring\": null,\n        \"lookup_key\": null,\n        \"tiers_mode\": null,\n        \"unit_amount\": 5512,\n        \"tax_behavior\": \"exclusive\",\n        \"billing_scheme\": \"per_unit\",\n        \"custom_unit_amount\": null,\n        \"transform_quantity\": null,\n        \"unit_amount_decimal\": \"5512\"\n    },\n    \"proration\": false,\n    \"proration_details\": {\n        \"credited_items\": null\n    },\n    \"subscription\": \"sub_fakefakefakefakefake0001\",\n    \"subscription_item\": \"si_JiphMAMFxZKW8s\",\n    \"tax_amounts\": [],\n    \"tax_rates\": [],\n    \"type\": \"invoiceitem\",\n    \"unit_amount_excluding_tax\": \"5512.00\",\n    \"quantity\": 5\n}\n"
  },
  {
    "path": "tests/fixtures/line_item_il_invoice_item_fakefakefakefakefake0002.json",
    "content": "{\n    \"id\": \"il_fakefakefakefakefake0002\",\n    \"object\": \"line_item\",\n    \"livemode\": false,\n    \"created\": null,\n    \"metadata\": {},\n    \"description\": \"LINE ITEM 1\",\n    \"amount\": 2000,\n    \"amount_excluding_tax\": 2000,\n    \"currency\": \"usd\",\n    \"discount_amounts\": [{\n        \"amount\": 7028,\n        \"discount\": \"di_fakefakefakefakefake0001\"\n    }],\n    \"discountable\": true,\n    \"discounts\": [\n        \"di_fakefakefakefakefake0001\"\n    ],\n    \"invoice_item\": \"ii_fakefakefakefakefake0001\",\n    \"period\": {\n        \"end\": 1442111228,\n        \"start\": 1444703228\n    },\n    \"period_end\": 123456789,\n    \"period_start\": 12111,\n    \"price\": {\n        \"id\": \"price_1LKNRmJSZQVUcJYgRkQDOYBL\",\n        \"type\": \"one_time\",\n        \"active\": false,\n        \"object\": \"price\",\n        \"created\": 1657549130,\n        \"product\": \"prod_M2SMqVbyQ81ihY\",\n        \"currency\": \"usd\",\n        \"livemode\": false,\n        \"metadata\": {},\n        \"nickname\": null,\n        \"recurring\": null,\n        \"lookup_key\": null,\n        \"tiers_mode\": null,\n        \"unit_amount\": 5512,\n        \"tax_behavior\": \"exclusive\",\n        \"billing_scheme\": \"per_unit\",\n        \"custom_unit_amount\": null,\n        \"transform_quantity\": null,\n        \"unit_amount_decimal\": \"5512\"\n    },\n    \"proration\": false,\n    \"proration_details\": {\n        \"credited_items\": null\n    },\n    \"subscription\": \"sub_1rn1dp7WgjMtx9\",\n    \"subscription_item\": \"si_JiphMAMFxZKW8s\",\n    \"tax_amounts\": [],\n    \"tax_rates\": [],\n    \"type\": \"subscription\",\n    \"unit_amount_excluding_tax\": \"2000.00\",\n    \"quantity\": 1\n}\n"
  },
  {
    "path": "tests/fixtures/order_order_fakefakefakefake0001.json",
    "content": "{\n    \"id\": \"order_fakefakefakefakefake0001\",\n    \"object\": \"order\",\n    \"livemode\": false,\n    \"created\": 1562801159,\n    \"metadata\": {},\n    \"description\": \"\",\n    \"amount_subtotal\": 500,\n    \"amount_total\": 500,\n    \"application\": \"\",\n    \"automatic_tax\": {\n        \"status\": null,\n        \"enabled\": false\n    },\n    \"billing_details\": {\n        \"name\": \"Jane Doe\",\n        \"email\": null,\n        \"phone\": null,\n        \"address\": null\n    },\n    \"client_secret\": \"order_fakefakefakefakefake0001_secret_CQ2wCUrrDiDjp5YyocdjHonYz00s2YdRrKT\",\n    \"currency\": \"usd\",\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"discounts\": [],\n    \"ip_address\": null,\n    \"line_items\": {\n        \"url\": \"/v1/orders/order_fakefakefakefakefake0001/line_items\",\n        \"data\": [{\n            \"id\": \"li_1L3Hf7JSZQVUcJYghrqBjGnv\",\n            \"price\": {\n                \"id\": \"price_1J5O0hJSZQVUcJYgYpVPGEb9\",\n                \"type\": \"one_time\",\n                \"active\": true,\n                \"object\": \"price\",\n                \"created\": 1624423702,\n                \"product\": \"prod_JipgBUhNfhFYlt\",\n                \"currency\": \"usd\",\n                \"livemode\": false,\n                \"metadata\": {},\n                \"nickname\": \"One Time Updated\",\n                \"recurring\": null,\n                \"lookup_key\": null,\n                \"tiers_mode\": null,\n                \"unit_amount\": 500,\n                \"tax_behavior\": \"unspecified\",\n                \"billing_scheme\": \"per_unit\",\n                \"transform_quantity\": null,\n                \"unit_amount_decimal\": \"500\"\n            },\n            \"object\": \"item\",\n            \"product\": \"prod_JipgBUhNfhFYlt\",\n            \"currency\": \"usd\",\n            \"quantity\": 1,\n            \"amount_tax\": 0,\n            \"description\": \"Test Product 1\",\n            \"amount_total\": 500,\n            \"amount_discount\": 0,\n            \"amount_subtotal\": 500\n        }],\n        \"object\": \"list\",\n        \"has_more\": false\n    },\n    \"payment\": {\n        \"status\": \"complete\",\n        \"settings\": null,\n        \"payment_intent\": \"pi_fakefakefakefakefake0001\"\n    },\n    \"payment_intent\": \"pi_fakefakefakefakefake0001\",\n    \"shipping_cost\": null,\n    \"shipping_details\": null,\n    \"status\": \"complete\",\n    \"tax_details\": {\n        \"tax_ids\": [],\n        \"tax_exempt\": \"none\"\n    },\n    \"total_details\": {\n        \"amount_tax\": 0,\n        \"amount_discount\": 0,\n        \"amount_shipping\": 0\n    }\n}\n"
  },
  {
    "path": "tests/fixtures/payment_intent_pi_destination_charge.json",
    "content": "\n{\n    \"id\": \"pi_1FG742B7kbjcJ8QqGKF6qIM0\",\n    \"object\": \"payment_intent\",\n    \"amount\": 190200,\n    \"amount_capturable\": 0,\n    \"amount_received\": 190200,\n    \"application\": null,\n    \"application_fee_amount\": null,\n    \"canceled_at\": null,\n    \"cancellation_reason\": null,\n    \"capture_method\": \"automatic\",\n    \"charges\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"ch_fakefakefakefakefake0001\",\n                \"object\": \"charge\",\n                \"amount\": 190200,\n                \"amount_refunded\": 0,\n                \"application\": null,\n                \"application_fee\": null,\n                \"application_fee_amount\": null,\n                \"balance_transaction\": \"txn_fake_ch_fakefakefakefakefake0001\",\n                \"billing_details\": {\n                    \"address\": {\n                        \"city\": null,\n                        \"country\": \"US\",\n                        \"line1\": null,\n                        \"line2\": null,\n                        \"postal_code\": \"92082\",\n                        \"state\": null\n                    },\n                    \"email\": \"kyoung@hotmail.com\",\n                    \"name\": \"John Foo\",\n                    \"phone\": null\n                },\n                \"captured\": true,\n                \"created\": 1567874856,\n                \"currency\": \"usd\",\n                \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n                \"description\": \"Online payment for FOO\",\n                \"destination\": \"acct_1032D82eZvKYlo2C\",\n                \"dispute\": null,\n                \"failure_code\": null,\n                \"failure_message\": null,\n                \"fraud_details\": {},\n                \"invoice\": null,\n                \"livemode\": false,\n                \"metadata\": {\"foo\": \"bar\"},\n                \"on_behalf_of\": \"acct_1032D82eZvKYlo2C\",\n                \"order\": null,\n                \"outcome\": {\n                    \"network_status\": \"approved_by_network\",\n                    \"reason\": null,\n                    \"risk_level\": \"normal\",\n                    \"risk_score\": 47,\n                    \"seller_message\": \"Payment complete.\",\n                    \"type\": \"authorized\"\n                },\n                \"paid\": true,\n                \"payment_intent\": \"pi_1FG742B7kbjcJ8QqGKF6qIM0\",\n                \"payment_method\": \"pm_1FG74VB7kbjcJ8QqXqULdSAV\",\n                \"payment_method_details\": {\n                    \"card\": {\n                        \"brand\": \"visa\",\n                        \"checks\": {\n                            \"address_line1_check\": null,\n                            \"address_postal_code_check\": \"pass\",\n                            \"cvc_check\": \"pass\"\n                        },\n                        \"country\": \"US\",\n                        \"exp_month\": 10,\n                        \"exp_year\": 2020,\n                        \"fingerprint\": \"sb2OAOijRKy8wYHu\",\n                        \"funding\": \"credit\",\n                        \"last4\": \"4242\",\n                        \"three_d_secure\": null,\n                        \"wallet\": null\n                    },\n                    \"type\": \"card\"\n                },\n                \"receipt_email\": \"kyoung@hotmail.com\",\n                \"receipt_number\": null,\n                \"receipt_url\": \"https://pay.stripe.com/receipts/acct_1DrGYIB7kbjcJ8Qq/ch_1FG74WB7kbjcJ8Qqx1oIdqfG/rcpt_FleN33oToRTXKCy6sxd5Stnh0ttnxYT\",\n                \"refunded\": false,\n                \"refunds\": {\n                    \"object\": \"list\",\n                    \"data\": [],\n                    \"has_more\": false,\n                    \"total_count\": 0,\n                    \"url\": \"/v1/charges/ch_1FG74WB7kbjcJ8Qqx1oIdqfG/refunds\"\n                },\n                \"review\": null,\n                \"shipping\": null,\n                \"source\": null,\n                \"source_transfer\": null,\n                \"statement_descriptor\": \"FOO DESCRIPTOR\",\n                \"statement_descriptor_suffix\": null,\n                \"status\": \"succeeded\",\n                \"transfer\": \"tr_16Y9BK2eZvKYlo2CR0ySu1BA\",\n                \"transfer_data\": {\n                    \"amount\": null,\n                    \"destination\": \"acct_1032D82eZvKYlo2C\"\n                },\n                \"transfer_group\": \"group_pi_1FG742B7kbjcJ8QqGKF6qIM0\"\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 1,\n        \"url\": \"/v1/charges?payment_intent=pi_1FG742B7kbjcJ8QqGKF6qIM0\"\n    },\n    \"client_secret\": \"pi_1FG742B7kbjcJ8QqGKF6qIM0_secret_yeRoAechksXUy2HdUydIKlGbw\",\n    \"confirmation_method\": \"automatic\",\n    \"created\": 1567874826,\n    \"currency\": \"usd\",\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"description\": \"Online payment for FOO\",\n    \"invoice\": null,\n    \"last_payment_error\": null,\n    \"livemode\": false,\n    \"metadata\": {\"foo\": \"bar\"},\n    \"next_action\": null,\n    \"on_behalf_of\": \"acct_1032D82eZvKYlo2C\",\n    \"payment_method\": \"pm_fakefakefakefake0001\",\n    \"payment_method_options\": {\"card\": {\"request_three_d_secure\": \"automatic\"}},\n    \"payment_method_types\": [\"card\"],\n    \"receipt_email\": \"kyoung@hotmail.com\",\n    \"review\": null,\n    \"setup_future_usage\": null,\n    \"shipping\": null,\n    \"source\": null,\n    \"statement_descriptor\": \"FOO DESCRIPTOR\",\n    \"statement_descriptor_suffix\": null,\n    \"status\": \"succeeded\",\n    \"transfer_data\": {\"destination\": \"acct_1032D82eZvKYlo2C\"},\n    \"transfer_group\": \"group_pi_1FG742B7kbjcJ8QqGKF6qIM0\"\n}\n"
  },
  {
    "path": "tests/fixtures/payment_intent_pi_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"pi_fakefakefakefakefake0001\",\n    \"object\": \"payment_intent\",\n    \"amount\": 2000,\n    \"amount_capturable\": 0,\n    \"amount_received\": 2000,\n    \"application\": null,\n    \"application_fee_amount\": null,\n    \"canceled_at\": null,\n    \"cancellation_reason\": null,\n    \"capture_method\": \"automatic\",\n    \"charges\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"ch_fakefakefakefakefake0001\",\n                \"object\": \"charge\",\n                \"amount\": 2000,\n                \"amount_refunded\": 0,\n                \"application\": null,\n                \"application_fee\": null,\n                \"application_fee_amount\": null,\n                \"balance_transaction\": \"txn_fake_ch_fakefakefakefakefake0001\",\n                \"billing_details\": {\n                    \"address\": {\n                        \"city\": null,\n                        \"country\": null,\n                        \"line1\": null,\n                        \"line2\": null,\n                        \"postal_code\": null,\n                        \"state\": null\n                    },\n                    \"email\": null,\n                    \"name\": \"alex-nesnes@hotmail.fr\",\n                    \"phone\": null\n                },\n                \"calculated_statement_descriptor\": \"Stripe\",\n                \"captured\": true,\n                \"created\": 1562801159,\n                \"currency\": \"usd\",\n                \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n                \"description\": \"Subscription creation\",\n                \"destination\": null,\n                \"dispute\": null,\n                \"disputed\": false,\n                \"failure_code\": null,\n                \"failure_message\": null,\n                \"fraud_details\": {},\n                \"invoice\": \"in_fakefakefakefakefake0001\",\n                \"livemode\": false,\n                \"metadata\": {\n                    \"djstripe_test_fake_id\": \"ch_fakefakefakefakefake0001\"\n                },\n                \"on_behalf_of\": null,\n                \"order\": null,\n                \"outcome\": {\n                    \"network_status\": \"approved_by_network\",\n                    \"reason\": null,\n                    \"risk_level\": \"normal\",\n                    \"risk_score\": 2,\n                    \"seller_message\": \"Payment complete.\",\n                    \"type\": \"authorized\"\n                },\n                \"paid\": true,\n                \"payment_intent\": \"pi_fakefakefakefakefake0001\",\n                \"payment_method\": \"card_fakefakefakefakefake0001\",\n                \"payment_method_details\": {\n                    \"card\": {\n                        \"brand\": \"visa\",\n                        \"checks\": {\n                            \"address_line1_check\": null,\n                            \"address_postal_code_check\": null,\n                            \"cvc_check\": null\n                        },\n                        \"country\": \"US\",\n                        \"exp_month\": 6,\n                        \"exp_year\": 2021,\n                        \"fingerprint\": \"88PuXw9tEmvYe69o\",\n                        \"funding\": \"credit\",\n                        \"installments\": null,\n                        \"last4\": \"4242\",\n                        \"network\": \"visa\",\n                        \"three_d_secure\": null,\n                        \"wallet\": null\n                    },\n                    \"type\": \"card\"\n                },\n                \"receipt_email\": null,\n                \"receipt_number\": null,\n                \"receipt_url\": \"https://pay.stripe.com/receipts/acct_1DeuAXKatMEEd998/ch_fakefakefakefakefake0001/rcpt_FPeTB5h3PS9fE4GLTncDw2zC1bRRmmY\",\n                \"refunded\": false,\n                \"refunds\": {},\n                \"review\": null,\n                \"shipping\": null,\n                \"source\": {\n                    \"id\": \"card_fakefakefakefakefake0001\",\n                    \"object\": \"card\",\n                    \"address_city\": null,\n                    \"address_country\": null,\n                    \"address_line1\": null,\n                    \"address_line1_check\": null,\n                    \"address_line2\": null,\n                    \"address_state\": null,\n                    \"address_zip\": null,\n                    \"address_zip_check\": null,\n                    \"brand\": \"Visa\",\n                    \"country\": \"US\",\n                    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n                    \"cvc_check\": null,\n                    \"dynamic_last4\": null,\n                    \"exp_month\": 6,\n                    \"exp_year\": 2021,\n                    \"fingerprint\": \"88PuXw9tEmvYe69o\",\n                    \"funding\": \"credit\",\n                    \"last4\": \"4242\",\n                    \"metadata\": {\n                        \"djstripe_test_fake_id\": \"card_fakefakefakefakefake0001\"\n                    },\n                    \"name\": \"alex-nesnes@hotmail.fr\",\n                    \"tokenization_method\": null\n                },\n                \"source_transfer\": null,\n                \"statement_descriptor\": null,\n                \"statement_descriptor_suffix\": null,\n                \"status\": \"succeeded\",\n                \"transfer_data\": null,\n                \"transfer_group\": null\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 1,\n        \"url\": \"/v1/charges?payment_intent=pi_fakefakefakefakefake0001\"\n    },\n    \"client_secret\": \"pi_fakefakefakefakefake0001_secret_fu8Bf1uEyWIbQwB7f4xCQ7iTX\",\n    \"confirmation_method\": \"automatic\",\n    \"created\": 1562801159,\n    \"currency\": \"usd\",\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"description\": \"Subscription creation\",\n    \"invoice\": \"in_fakefakefakefakefake0001\",\n    \"last_payment_error\": null,\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"pi_fakefakefakefakefake0001\"\n    },\n    \"next_action\": null,\n    \"on_behalf_of\": null,\n    \"payment_method\": null,\n    \"payment_method_options\": {\n        \"card\": {\n            \"installments\": null,\n            \"network\": null,\n            \"request_three_d_secure\": \"automatic\"\n        }\n    },\n    \"payment_method_types\": [\n        \"card\"\n    ],\n    \"receipt_email\": null,\n    \"review\": null,\n    \"setup_future_usage\": \"off_session\",\n    \"shipping\": null,\n    \"source\": \"card_fakefakefakefakefake0001\",\n    \"statement_descriptor\": null,\n    \"statement_descriptor_suffix\": null,\n    \"status\": \"succeeded\",\n    \"transfer_data\": null,\n    \"transfer_group\": null\n}\n"
  },
  {
    "path": "tests/fixtures/payment_method_card_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"card_fakefakefakefakefake0001\",\n    \"object\": \"payment_method\",\n    \"billing_details\": {\n        \"address\": {\n            \"city\": null,\n            \"country\": null,\n            \"line1\": null,\n            \"line2\": null,\n            \"postal_code\": null,\n            \"state\": null\n        },\n        \"email\": null,\n        \"name\": \"alex-nesnes@hotmail.fr\",\n        \"phone\": null\n    },\n    \"card\": {\n        \"brand\": \"visa\",\n        \"checks\": {\n            \"address_line1_check\": null,\n            \"address_postal_code_check\": null,\n            \"cvc_check\": null\n        },\n        \"country\": \"US\",\n        \"exp_month\": 6,\n        \"exp_year\": 2021,\n        \"fingerprint\": \"88PuXw9tEmvYe69o\",\n        \"funding\": \"credit\",\n        \"generated_from\": null,\n        \"last4\": \"4242\",\n        \"networks\": {\n            \"available\": [\n                \"visa\"\n            ],\n            \"preferred\": null\n        },\n        \"three_d_secure_usage\": {\n            \"supported\": true\n        },\n        \"wallet\": null\n    },\n    \"created\": 1567571746,\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"card_fakefakefakefakefake0001\"\n    },\n    \"type\": \"card\"\n}\n"
  },
  {
    "path": "tests/fixtures/payment_method_pm_fakefakefakefake0001.json",
    "content": "{\n    \"id\": \"pm_fakefakefakefake0001\",\n    \"object\": \"payment_method\",\n    \"billing_details\": {\n        \"address\": {\n            \"city\": null,\n            \"country\": null,\n            \"line1\": null,\n            \"line2\": null,\n            \"postal_code\": null,\n            \"state\": null\n        },\n        \"email\": null,\n        \"name\": null,\n        \"phone\": null\n    },\n    \"card\": {\n        \"brand\": \"visa\",\n        \"checks\": {\n            \"address_line1_check\": null,\n            \"address_postal_code_check\": null,\n            \"cvc_check\": null\n        },\n        \"country\": \"US\",\n        \"exp_month\": 6,\n        \"exp_year\": 2021,\n        \"fingerprint\": \"88PuXw9tEmvYe69o\",\n        \"funding\": \"credit\",\n        \"generated_from\": null,\n        \"last4\": \"4242\",\n        \"networks\": {\n            \"available\": [\n                \"visa\"\n            ],\n            \"preferred\": null\n        },\n        \"three_d_secure_usage\": {\n            \"supported\": true\n        },\n        \"wallet\": null\n    },\n    \"created\": 123456789,\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"livemode\": false,\n    \"metadata\": {\n        \"order_id\": \"123456789\",\n        \"djstripe_test_fake_id\": \"pm_fakefakefakefake0001\"\n    },\n    \"type\": \"card\"\n}\n"
  },
  {
    "path": "tests/fixtures/payout_custom_bank_account.json",
    "content": "{\n    \"id\": \"po_1JR569QuFmP1Mw5uxasKoRx5\",\n    \"object\": \"payout\",\n    \"djstripe_owner_account\": \"acct_1IuHosQveW0ONQsd\",\n    \"livemode\": false,\n    \"created\": 1629594221,\n    \"metadata\": {},\n    \"description\": \"STRIPE PAYOUT\",\n    \"amount\": \"10.00\",\n    \"arrival_date\": 1629676800,\n    \"automatic\": true,\n    \"balance_transaction\": \"txn_fake_ch_fakefakefakefakefake0001\",\n    \"currency\": \"usd\",\n    \"destination\": \"ba_16hTzo2eZvKYlo2CeSjfb0tS\",\n    \"failure_balance_transaction\": null,\n    \"failure_code\": \"\",\n    \"failure_message\": \"\",\n    \"method\": \"standard\",\n    \"source_type\": \"card\",\n    \"statement_descriptor\": \"\",\n    \"status\": \"paid\",\n    \"type\": \"bank_account\"\n}\n"
  },
  {
    "path": "tests/fixtures/payout_custom_card.json",
    "content": "{\n    \"id\": \"po_1JR569QuFmP1Mw5uxasKoRx5\",\n    \"object\": \"payout\",\n    \"djstripe_owner_account\": \"acct_1IuHosQveW0ONQsd\",\n    \"livemode\": false,\n    \"created\": 1629594221,\n    \"metadata\": {},\n    \"description\": \"STRIPE PAYOUT\",\n    \"amount\": \"10.00\",\n    \"arrival_date\": 1629676800,\n    \"automatic\": true,\n    \"balance_transaction\": \"txn_fake_ch_fakefakefakefakefake0001\",\n    \"currency\": \"usd\",\n    \"destination\": \"card_fakefakefakefakefake0001\",\n    \"failure_balance_transaction\": null,\n    \"failure_code\": \"\",\n    \"failure_message\": \"\",\n    \"method\": \"standard\",\n    \"source_type\": \"card\",\n    \"statement_descriptor\": \"\",\n    \"status\": \"paid\",\n    \"type\": \"card\"\n}\n"
  },
  {
    "path": "tests/fixtures/plan_gold21323.json",
    "content": "{\n    \"id\": \"gold21323\",\n    \"object\": \"plan\",\n    \"active\": true,\n    \"aggregate_usage\": null,\n    \"amount\": 2000,\n    \"amount_decimal\": \"2000\",\n    \"billing_scheme\": \"per_unit\",\n    \"created\": 1557995175,\n    \"currency\": \"usd\",\n    \"interval\": \"month\",\n    \"interval_count\": 1,\n    \"livemode\": false,\n    \"metadata\": {},\n    \"nickname\": \"New plan name\",\n    \"product\": \"prod_fake1\",\n    \"tiers\": null,\n    \"tiers_mode\": null,\n    \"transform_usage\": null,\n    \"trial_period_days\": null,\n    \"usage_type\": \"licensed\"\n}\n"
  },
  {
    "path": "tests/fixtures/plan_silver41294.json",
    "content": "{\n    \"id\": \"silver41294\",\n    \"object\": \"plan\",\n    \"active\": true,\n    \"aggregate_usage\": null,\n    \"amount\": 4000,\n    \"amount_decimal\": \"4000\",\n    \"billing_scheme\": \"per_unit\",\n    \"created\": 1557995176,\n    \"currency\": \"usd\",\n    \"interval\": \"month\",\n    \"interval_count\": 1,\n    \"livemode\": false,\n    \"metadata\": {},\n    \"nickname\": \"New plan name\",\n    \"product\": \"prod_fake1\",\n    \"tiers\": null,\n    \"tiers_mode\": null,\n    \"transform_usage\": null,\n    \"trial_period_days\": 12,\n    \"usage_type\": \"licensed\"\n}\n"
  },
  {
    "path": "tests/fixtures/price_gold21323.json",
    "content": "{\n    \"active\": true,\n    \"billing_scheme\": \"per_unit\",\n    \"created\": 1557995175,\n    \"currency\": \"usd\",\n    \"id\": \"gold21323\",\n    \"livemode\": false,\n    \"lookup_key\": null,\n    \"metadata\": {},\n    \"nickname\": \"New price name\",\n    \"object\": \"price\",\n    \"product\": \"prod_fake1\",\n    \"recurring\": {\n      \"aggregate_usage\": null,\n      \"interval\": \"month\",\n      \"interval_count\": 1,\n      \"trial_period_days\": null,\n      \"usage_type\": \"licensed\"\n    },\n    \"tiers_mode\": null,\n    \"transform_quantity\": null,\n    \"type\": \"recurring\",\n    \"unit_amount\": 2000,\n    \"unit_amount_decimal\": \"2000\"\n}\n"
  },
  {
    "path": "tests/fixtures/price_silver41294.json",
    "content": "{\n    \"active\": true,\n    \"billing_scheme\": \"per_unit\",\n    \"created\": 1557995176,\n    \"currency\": \"usd\",\n    \"id\": \"silver41294\",\n    \"livemode\": false,\n    \"lookup_key\": null,\n    \"metadata\": {},\n    \"nickname\": \"New price name\",\n    \"object\": \"price\",\n    \"product\": \"prod_fake1\",\n    \"recurring\": {\n      \"aggregate_usage\": null,\n      \"interval\": \"month\",\n      \"interval_count\": 1,\n      \"trial_period_days\": 12,\n      \"usage_type\": \"licensed\"\n    },\n    \"tiers_mode\": null,\n    \"transform_quantity\": null,\n    \"type\": \"recurring\",\n    \"unit_amount\": 4000,\n    \"unit_amount_decimal\": \"4000\"\n}\n"
  },
  {
    "path": "tests/fixtures/product_prod_fake1.json",
    "content": "{\n    \"id\": \"prod_fake1\",\n    \"object\": \"product\",\n    \"active\": true,\n    \"attributes\": [],\n    \"created\": 1557995174,\n    \"description\": null,\n    \"images\": [],\n    \"livemode\": false,\n    \"default_price\": null,\n    \"metadata\": {},\n    \"name\": \"Fake Product\",\n    \"statement_descriptor\": null,\n    \"type\": \"service\",\n    \"unit_label\": null,\n    \"updated\": 1557995176\n}\n"
  },
  {
    "path": "tests/fixtures/setup_intent_pi_destination_charge.json",
    "content": "\n{\n\n\n    \"application\": null,\n    \"cancellation_reason\": null,\n    \"client_secret\": \"seti_1J0g0WJSZQVUcJYgWE2XSi1K_secret_Jdxw2mOaIEHBdE6eTsfJ2IfmamgNJaF\",\n    \"created\": 1567874826,\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"description\": null,\n    \"id\": \"seti_1J0g0WJSZQVUcJYgWE2XSi1K\",\n    \"last_setup_error\": null,\n    \"latest_attempt\": \"setatt_1J0g0WJSZQVUcJYgsrFgwxVh\",\n    \"livemode\": false,\n    \"mandate\": null,\n    \"metadata\": {},\n    \"next_action\": null,\n    \"object\": \"setup_intent\",\n    \"payment_method\": \"pm_fakefakefakefake0001\",\n    \"payment_method_options\": {\n      \"card\": {\n        \"request_three_d_secure\": \"automatic\"\n      }\n    },\n    \"payment_method_types\": [\n      \"card\"\n    ],\n\n    \"single_use_mandate\": null,\n    \"status\": \"succeeded\",\n    \"usage\": \"off_session\",\n    \"on_behalf_of\": \"acct_1Fg9jUA3kq9o1aTc\"\n}\n"
  },
  {
    "path": "tests/fixtures/shipping_rate_shr_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"shr_1L0co1JSZQVUcJYgcYfX1tZj\",\n    \"object\": \"shipping_rate\",\n    \"livemode\": false,\n    \"created\": 1570435136,\n    \"metadata\": {},\n    \"active\": true,\n    \"display_name\": \"Test Shipping Code with no Tax Code\",\n    \"fixed_amount\": {\n        \"amount\": 125,\n        \"currency\": \"usd\"\n    },\n    \"type\": \"fixed_amount\",\n    \"delivery_estimate\": null,\n    \"tax_behavior\": \"exclusive\",\n    \"tax_code\": null\n}\n"
  },
  {
    "path": "tests/fixtures/shipping_rate_shr_fakefakefakefakefake0002.json",
    "content": "{\n    \"id\": \"shr_1L0co1JSZQVUcJYgcYfX1tZj\",\n    \"object\": \"shipping_rate\",\n    \"livemode\": false,\n    \"created\": 1570435136,\n    \"metadata\": {},\n    \"active\": true,\n    \"display_name\": \"Test Shipping Code with no Tax Code\",\n    \"fixed_amount\": {\n        \"amount\": 125,\n        \"currency\": \"usd\"\n    },\n    \"type\": \"fixed_amount\",\n    \"delivery_estimate\": null,\n    \"tax_behavior\": \"exclusive\",\n    \"tax_code\": \"txcd_99999999\"\n}\n"
  },
  {
    "path": "tests/fixtures/source_src_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"src_fakefakefakefakefake0001\",\n    \"object\": \"source\",\n    \"amount\": null,\n    \"card\": {\n        \"exp_month\": 6,\n        \"exp_year\": 2021,\n        \"last4\": \"4242\",\n        \"country\": \"US\",\n        \"brand\": \"Visa\",\n        \"funding\": \"credit\",\n        \"fingerprint\": \"88PuXw9tEmvYe69o\",\n        \"three_d_secure\": \"optional\",\n        \"name\": null,\n        \"address_line1_check\": null,\n        \"address_zip_check\": null,\n        \"cvc_check\": null,\n        \"tokenization_method\": null,\n        \"dynamic_last4\": null\n    },\n    \"client_secret\": \"src_client_secret_F4oXYTWIpC9GV8qeLoVS9Wqk\",\n    \"created\": 1557995173,\n    \"currency\": null,\n    \"customer\": \"cus_4QWKsZuuTHcs7X\",\n    \"flow\": \"none\",\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"src_fakefakefakefakefake0001\"\n    },\n    \"owner\": {\n        \"address\": null,\n        \"email\": null,\n        \"name\": null,\n        \"phone\": null,\n        \"verified_address\": null,\n        \"verified_email\": null,\n        \"verified_name\": null,\n        \"verified_phone\": null\n    },\n    \"statement_descriptor\": null,\n    \"status\": \"chargeable\",\n    \"type\": \"card\",\n    \"usage\": \"reusable\"\n}\n"
  },
  {
    "path": "tests/fixtures/subscription_sub_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"sub_fakefakefakefakefake0001\",\n    \"object\": \"subscription\",\n    \"application_fee_percent\": null,\n    \"billing_cycle_anchor\": 1557995176,\n    \"billing_thresholds\": null,\n    \"cancel_at\": null,\n    \"cancel_at_period_end\": false,\n    \"canceled_at\": null,\n    \"collection_method\": \"charge_automatically\",\n    \"created\": 1557995176,\n    \"current_period_end\": 1560673576,\n    \"current_period_start\": 1557995176,\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"days_until_due\": null,\n    \"default_payment_method\": null,\n    \"default_source\": null,\n    \"default_tax_rates\": [\n        {\n            \"id\": \"txr_fakefakefakefakefake0001\",\n            \"object\": \"tax_rate\",\n            \"active\": true,\n            \"created\": 1593225980,\n            \"description\": null,\n            \"display_name\": \"VAT\",\n            \"inclusive\": true,\n            \"jurisdiction\": \"Example1\",\n            \"livemode\": false,\n            \"metadata\": {\n                \"djstripe_test_fake_id\": \"txr_fakefakefakefakefake0001\"\n            },\n            \"percentage\": 15.0\n        }\n    ],\n    \"discount\": null,\n    \"ended_at\": null,\n    \"items\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"si_F5ukmkS6Bxi90Y\",\n                \"object\": \"subscription_item\",\n                \"billing_thresholds\": null,\n                \"created\": 1558230764,\n                \"metadata\": {},\n                \"plan\": {\n                    \"id\": \"gold21323\",\n                    \"object\": \"plan\",\n                    \"active\": true,\n                    \"aggregate_usage\": null,\n                    \"amount\": 2000,\n                    \"amount_decimal\": \"2000\",\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1558230763,\n                    \"currency\": \"usd\",\n                    \"interval\": \"month\",\n                    \"interval_count\": 1,\n                    \"livemode\": false,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"tiers\": null,\n                    \"tiers_mode\": null,\n                    \"transform_usage\": null,\n                    \"trial_period_days\": null,\n                    \"usage_type\": \"licensed\"\n                },\n                \"price\": {\n                    \"id\": \"gold21323\",\n                    \"object\": \"price\",\n                    \"active\": true,\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1593225979,\n                    \"currency\": \"usd\",\n                    \"livemode\": false,\n                    \"lookup_key\": null,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"recurring\": {\n                        \"aggregate_usage\": null,\n                        \"interval\": \"month\",\n                        \"interval_count\": 1,\n                        \"trial_period_days\": null,\n                        \"usage_type\": \"licensed\"\n                    },\n                    \"tiers_mode\": null,\n                    \"transform_quantity\": null,\n                    \"type\": \"recurring\",\n                    \"unit_amount\": 2000,\n                    \"unit_amount_decimal\": \"2000\"\n                },\n                \"quantity\": 1,\n                \"subscription\": \"sub_fakefakefakefakefake0001\",\n                \"tax_rates\": []\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 1,\n        \"url\": \"/v1/subscription_items?subscription=sub_fakefakefakefakefake0001\"\n    },\n    \"latest_invoice\": \"in_fakefakefakefakefake0001\",\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"sub_fakefakefakefakefake0001\"\n    },\n    \"next_pending_invoice_item_invoice\": null,\n    \"pause_collection\": null,\n    \"pending_invoice_item_interval\": null,\n    \"pending_setup_intent\": null,\n    \"pending_update\": null,\n    \"plan\": {\n        \"id\": \"gold21323\",\n        \"object\": \"plan\",\n        \"active\": true,\n        \"aggregate_usage\": null,\n        \"amount\": 2000,\n        \"amount_decimal\": \"2000\",\n        \"billing_scheme\": \"per_unit\",\n        \"created\": 1558230763,\n        \"currency\": \"usd\",\n        \"interval\": \"month\",\n        \"interval_count\": 1,\n        \"livemode\": false,\n        \"metadata\": {},\n        \"nickname\": \"New plan name\",\n        \"product\": \"prod_fake1\",\n        \"tiers\": null,\n        \"tiers_mode\": null,\n        \"transform_usage\": null,\n        \"trial_period_days\": null,\n        \"usage_type\": \"licensed\"\n    },\n    \"quantity\": 1,\n    \"schedule\": null,\n    \"start_date\": 1559476700,\n    \"status\": \"active\",\n    \"tax_percent\": null,\n    \"transfer_data\": null,\n    \"trial_end\": null,\n    \"trial_start\": null\n}\n"
  },
  {
    "path": "tests/fixtures/subscription_sub_fakefakefakefakefake0002.json",
    "content": "{\n    \"id\": \"sub_fakefakefakefakefake0002\",\n    \"object\": \"subscription\",\n    \"application_fee_percent\": null,\n    \"billing_cycle_anchor\": 1557995178,\n    \"billing_thresholds\": null,\n    \"cancel_at\": null,\n    \"cancel_at_period_end\": false,\n    \"canceled_at\": null,\n    \"collection_method\": \"charge_automatically\",\n    \"created\": 1557995178,\n    \"current_period_end\": 1560673578,\n    \"current_period_start\": 1557995178,\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"days_until_due\": null,\n    \"default_payment_method\": null,\n    \"default_source\": null,\n    \"default_tax_rates\": [],\n    \"discount\": null,\n    \"ended_at\": null,\n    \"items\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"si_F5ukq6eM2QV9g5\",\n                \"object\": \"subscription_item\",\n                \"billing_thresholds\": null,\n                \"created\": 1558230767,\n                \"metadata\": {},\n                \"plan\": {\n                    \"id\": \"silver41294\",\n                    \"object\": \"plan\",\n                    \"active\": true,\n                    \"aggregate_usage\": null,\n                    \"amount\": 4000,\n                    \"amount_decimal\": \"4000\",\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1558230763,\n                    \"currency\": \"usd\",\n                    \"interval\": \"month\",\n                    \"interval_count\": 1,\n                    \"livemode\": false,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"tiers\": null,\n                    \"tiers_mode\": null,\n                    \"transform_usage\": null,\n                    \"trial_period_days\": 12,\n                    \"usage_type\": \"licensed\"\n                },\n                \"price\": {\n                    \"id\": \"silver41294\",\n                    \"object\": \"price\",\n                    \"active\": true,\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1593225979,\n                    \"currency\": \"usd\",\n                    \"livemode\": false,\n                    \"lookup_key\": null,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"recurring\": {\n                        \"aggregate_usage\": null,\n                        \"interval\": \"month\",\n                        \"interval_count\": 1,\n                        \"trial_period_days\": 12,\n                        \"usage_type\": \"licensed\"\n                    },\n                    \"tiers_mode\": null,\n                    \"transform_quantity\": null,\n                    \"type\": \"recurring\",\n                    \"unit_amount\": 4000,\n                    \"unit_amount_decimal\": \"4000\"\n                },\n                \"quantity\": 1,\n                \"subscription\": \"sub_fakefakefakefakefake0002\",\n                \"tax_rates\": [\n                    {\n                        \"id\": \"txr_fakefakefakefakefake0001\",\n                        \"object\": \"tax_rate\",\n                        \"active\": true,\n                        \"created\": 1593225980,\n                        \"description\": null,\n                        \"display_name\": \"VAT\",\n                        \"inclusive\": true,\n                        \"jurisdiction\": \"Example1\",\n                        \"livemode\": false,\n                        \"metadata\": {\n                            \"djstripe_test_fake_id\": \"txr_fakefakefakefakefake0001\"\n                        },\n                        \"percentage\": 15.0\n                    }\n                ]\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 1,\n        \"url\": \"/v1/subscription_items?subscription=sub_fakefakefakefakefake0002\"\n    },\n    \"latest_invoice\": \"in_fakefakefakefakefake0004\",\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"sub_fakefakefakefakefake0002\"\n    },\n    \"next_pending_invoice_item_invoice\": null,\n    \"pause_collection\": null,\n    \"pending_invoice_item_interval\": null,\n    \"pending_setup_intent\": null,\n    \"pending_update\": null,\n    \"plan\": {\n        \"id\": \"silver41294\",\n        \"object\": \"plan\",\n        \"active\": true,\n        \"aggregate_usage\": null,\n        \"amount\": 4000,\n        \"amount_decimal\": \"4000\",\n        \"billing_scheme\": \"per_unit\",\n        \"created\": 1558230763,\n        \"currency\": \"usd\",\n        \"interval\": \"month\",\n        \"interval_count\": 1,\n        \"livemode\": false,\n        \"metadata\": {},\n        \"nickname\": \"New plan name\",\n        \"product\": \"prod_fake1\",\n        \"tiers\": null,\n        \"tiers_mode\": null,\n        \"transform_usage\": null,\n        \"trial_period_days\": 12,\n        \"usage_type\": \"licensed\"\n    },\n    \"quantity\": 1,\n    \"schedule\": null,\n    \"start_date\": 1559476702,\n    \"status\": \"active\",\n    \"tax_percent\": null,\n    \"transfer_data\": null,\n    \"trial_end\": null,\n    \"trial_start\": null\n}\n"
  },
  {
    "path": "tests/fixtures/subscription_sub_fakefakefakefakefake0003.json",
    "content": "{\n    \"id\": \"sub_fakefakefakefakefake0003\",\n    \"object\": \"subscription\",\n    \"application_fee_percent\": null,\n    \"billing_cycle_anchor\": 1557995180,\n    \"billing_thresholds\": null,\n    \"cancel_at\": null,\n    \"cancel_at_period_end\": false,\n    \"canceled_at\": null,\n    \"collection_method\": \"charge_automatically\",\n    \"created\": 1557995180,\n    \"current_period_end\": 1560673580,\n    \"current_period_start\": 1557995180,\n    \"customer\": \"cus_4UbFSo9tl62jqj\",\n    \"days_until_due\": null,\n    \"default_payment_method\": null,\n    \"default_source\": null,\n    \"default_tax_rates\": [],\n    \"discount\": null,\n    \"ended_at\": null,\n    \"items\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"si_F5ukGdpR4EejF9\",\n                \"object\": \"subscription_item\",\n                \"billing_thresholds\": null,\n                \"created\": 1558230770,\n                \"metadata\": {},\n                \"plan\": {\n                    \"id\": \"gold21323\",\n                    \"object\": \"plan\",\n                    \"active\": true,\n                    \"aggregate_usage\": null,\n                    \"amount\": 2000,\n                    \"amount_decimal\": \"2000\",\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1558230763,\n                    \"currency\": \"usd\",\n                    \"interval\": \"month\",\n                    \"interval_count\": 1,\n                    \"livemode\": false,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"tiers\": null,\n                    \"tiers_mode\": null,\n                    \"transform_usage\": null,\n                    \"trial_period_days\": null,\n                    \"usage_type\": \"licensed\"\n                },\n                \"price\": {\n                    \"id\": \"gold21323\",\n                    \"object\": \"price\",\n                    \"active\": true,\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1593225979,\n                    \"currency\": \"usd\",\n                    \"livemode\": false,\n                    \"lookup_key\": null,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"recurring\": {\n                        \"aggregate_usage\": null,\n                        \"interval\": \"month\",\n                        \"interval_count\": 1,\n                        \"trial_period_days\": null,\n                        \"usage_type\": \"licensed\"\n                    },\n                    \"tiers_mode\": null,\n                    \"transform_quantity\": null,\n                    \"type\": \"recurring\",\n                    \"unit_amount\": 2000,\n                    \"unit_amount_decimal\": \"2000\"\n                },\n                \"quantity\": 1,\n                \"subscription\": \"sub_fakefakefakefakefake0003\",\n                \"tax_rates\": []\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 1,\n        \"url\": \"/v1/subscription_items?subscription=sub_fakefakefakefakefake0003\"\n    },\n    \"latest_invoice\": \"in_1GyU3iCOCguPTL2BDgqo4dj7\",\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"sub_fakefakefakefakefake0003\"\n    },\n    \"next_pending_invoice_item_invoice\": null,\n    \"pause_collection\": null,\n    \"pending_invoice_item_interval\": null,\n    \"pending_setup_intent\": null,\n    \"pending_update\": null,\n    \"plan\": {\n        \"id\": \"gold21323\",\n        \"object\": \"plan\",\n        \"active\": true,\n        \"aggregate_usage\": null,\n        \"amount\": 2000,\n        \"amount_decimal\": \"2000\",\n        \"billing_scheme\": \"per_unit\",\n        \"created\": 1558230763,\n        \"currency\": \"usd\",\n        \"interval\": \"month\",\n        \"interval_count\": 1,\n        \"livemode\": false,\n        \"metadata\": {},\n        \"nickname\": \"New plan name\",\n        \"product\": \"prod_fake1\",\n        \"tiers\": null,\n        \"tiers_mode\": null,\n        \"transform_usage\": null,\n        \"trial_period_days\": null,\n        \"usage_type\": \"licensed\"\n    },\n    \"quantity\": 1,\n    \"schedule\": null,\n    \"start_date\": 1559476704,\n    \"status\": \"active\",\n    \"tax_percent\": null,\n    \"transfer_data\": null,\n    \"trial_end\": null,\n    \"trial_start\": null\n}\n"
  },
  {
    "path": "tests/fixtures/subscription_sub_fakefakefakefakefake0004.json",
    "content": "{\n    \"id\": \"sub_fakefakefakefakefake0004\",\n    \"object\": \"subscription\",\n    \"application_fee_percent\": null,\n    \"billing_cycle_anchor\": 1557995182,\n    \"billing_thresholds\": null,\n    \"cancel_at\": null,\n    \"cancel_at_period_end\": false,\n    \"canceled_at\": null,\n    \"collection_method\": \"charge_automatically\",\n    \"created\": 1557995182,\n    \"current_period_end\": 1560673582,\n    \"current_period_start\": 1557995182,\n    \"customer\": \"cus_4UbFSo9tl62jqj\",\n    \"days_until_due\": null,\n    \"default_payment_method\": null,\n    \"default_source\": null,\n    \"default_tax_rates\": [],\n    \"discount\": null,\n    \"ended_at\": null,\n    \"items\": {\n        \"object\": \"list\",\n        \"data\": [\n            {\n                \"id\": \"si_F5uk9HMrUwrmUJ\",\n                \"object\": \"subscription_item\",\n                \"billing_thresholds\": null,\n                \"created\": 1558230772,\n                \"metadata\": {},\n                \"plan\": {\n                    \"id\": \"gold21323\",\n                    \"object\": \"plan\",\n                    \"active\": true,\n                    \"aggregate_usage\": null,\n                    \"amount\": 2000,\n                    \"amount_decimal\": \"2000\",\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1558230763,\n                    \"currency\": \"usd\",\n                    \"interval\": \"month\",\n                    \"interval_count\": 1,\n                    \"livemode\": false,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"tiers\": null,\n                    \"tiers_mode\": null,\n                    \"transform_usage\": null,\n                    \"trial_period_days\": null,\n                    \"usage_type\": \"licensed\"\n                },\n                \"price\": {\n                    \"id\": \"gold21323\",\n                    \"object\": \"price\",\n                    \"active\": true,\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1593225979,\n                    \"currency\": \"usd\",\n                    \"livemode\": false,\n                    \"lookup_key\": null,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"recurring\": {\n                        \"aggregate_usage\": null,\n                        \"interval\": \"month\",\n                        \"interval_count\": 1,\n                        \"trial_period_days\": null,\n                        \"usage_type\": \"licensed\"\n                    },\n                    \"tiers_mode\": null,\n                    \"transform_quantity\": null,\n                    \"type\": \"recurring\",\n                    \"unit_amount\": 2000,\n                    \"unit_amount_decimal\": \"2000\"\n                },\n                \"quantity\": 1,\n                \"subscription\": \"sub_fakefakefakefakefake0004\",\n                \"tax_rates\": []\n            },\n            {\n                \"id\": \"si_F5uk81B1xGi3Vr\",\n                \"object\": \"subscription_item\",\n                \"billing_thresholds\": null,\n                \"created\": 1558230772,\n                \"metadata\": {},\n                \"plan\": {\n                    \"id\": \"silver41294\",\n                    \"object\": \"plan\",\n                    \"active\": true,\n                    \"aggregate_usage\": null,\n                    \"amount\": 4000,\n                    \"amount_decimal\": \"4000\",\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1558230763,\n                    \"currency\": \"usd\",\n                    \"interval\": \"month\",\n                    \"interval_count\": 1,\n                    \"livemode\": false,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"tiers\": null,\n                    \"tiers_mode\": null,\n                    \"transform_usage\": null,\n                    \"trial_period_days\": 12,\n                    \"usage_type\": \"licensed\"\n                },\n                \"price\": {\n                    \"id\": \"silver41294\",\n                    \"object\": \"price\",\n                    \"active\": true,\n                    \"billing_scheme\": \"per_unit\",\n                    \"created\": 1593225979,\n                    \"currency\": \"usd\",\n                    \"livemode\": false,\n                    \"lookup_key\": null,\n                    \"metadata\": {},\n                    \"nickname\": \"New plan name\",\n                    \"product\": \"prod_fake1\",\n                    \"recurring\": {\n                        \"aggregate_usage\": null,\n                        \"interval\": \"month\",\n                        \"interval_count\": 1,\n                        \"trial_period_days\": 12,\n                        \"usage_type\": \"licensed\"\n                    },\n                    \"tiers_mode\": null,\n                    \"transform_quantity\": null,\n                    \"type\": \"recurring\",\n                    \"unit_amount\": 4000,\n                    \"unit_amount_decimal\": \"4000\"\n                },\n                \"quantity\": 1,\n                \"subscription\": \"sub_fakefakefakefakefake0004\",\n                \"tax_rates\": []\n            }\n        ],\n        \"has_more\": false,\n        \"total_count\": 2,\n        \"url\": \"/v1/subscription_items?subscription=sub_fakefakefakefakefake0004\"\n    },\n    \"latest_invoice\": \"in_1GyU3kCOCguPTL2BO0EVwuzj\",\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"sub_fakefakefakefakefake0004\"\n    },\n    \"next_pending_invoice_item_invoice\": null,\n    \"pause_collection\": null,\n    \"pending_invoice_item_interval\": null,\n    \"pending_setup_intent\": null,\n    \"pending_update\": null,\n    \"plan\": null,\n    \"quantity\": null,\n    \"schedule\": null,\n    \"start_date\": 1559476706,\n    \"status\": \"active\",\n    \"tax_percent\": null,\n    \"transfer_data\": null,\n    \"trial_end\": null,\n    \"trial_start\": null\n}\n"
  },
  {
    "path": "tests/fixtures/tax_code_txcd_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"txcd_99999999\",\n    \"object\": \"tax_code\",\n    \"livemode\": null,\n    \"created\": null,\n    \"description\": \"Any tangible or physical good. For jurisdictions that impose a tax, the standard rate is applied.\",\n    \"name\": \"General - Tangible Goods\"\n}\n"
  },
  {
    "path": "tests/fixtures/tax_id_txi_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"txi_1J5NznJSZQVUcJYgGWkyTcvb\",\n    \"object\": \"tax_id\",\n    \"livemode\": false,\n    \"created\": 1570435136,\n    \"country\": \"US\",\n    \"customer\": \"cus_6lsBvm5rJ0zyHc\",\n    \"type\": \"us_ein\",\n    \"value\": \"12-0982347\",\n    \"verification\": {\n        \"status\": \"unavailable\",\n        \"verified_address\": null,\n        \"verified_name\": null\n    }\n}\n"
  },
  {
    "path": "tests/fixtures/tax_rate_txr_fakefakefakefakefake0001.json",
    "content": "{\n    \"id\": \"txr_fakefakefakefakefake0001\",\n    \"object\": \"tax_rate\",\n    \"active\": true,\n    \"created\": 1570435136,\n    \"description\": null,\n    \"display_name\": \"VAT\",\n    \"inclusive\": true,\n    \"jurisdiction\": \"Example1\",\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"txr_fakefakefakefakefake0001\"\n    },\n    \"percentage\": 15.0\n}\n"
  },
  {
    "path": "tests/fixtures/tax_rate_txr_fakefakefakefakefake0002.json",
    "content": "{\n    \"id\": \"txr_fakefakefakefakefake0002\",\n    \"object\": \"tax_rate\",\n    \"active\": true,\n    \"created\": 1570436740,\n    \"description\": \"Example2 Sales Tax\",\n    \"display_name\": \"Sales tax\",\n    \"inclusive\": false,\n    \"jurisdiction\": \"Example2\",\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_test_fake_id\": \"txr_fakefakefakefakefake0002\"\n    },\n    \"percentage\": 4.25\n}\n"
  },
  {
    "path": "tests/fixtures/usage_record_summary_sis_fakefakefakefakefake0001.json",
    "content": "{\n    \"data\": [\n      {\n        \"id\": \"sis_1JPOIXJSZQVUcJYgoaYmbznL\",\n        \"invoice\": null,\n        \"livemode\": false,\n        \"object\": \"usage_record_summary\",\n        \"period\": {\n          \"end\": null,\n          \"start\": 1627015586\n        },\n        \"subscription_item\": \"si_JiphMAMFxZKW8s\",\n        \"total_usage\": 700\n      },\n      {\n        \"id\": \"sis_1JGGM6JSZQVUcJYgCljH2Mkl\",\n        \"invoice\": \"in_1JGGM6JSZQVUcJYgpWqfBOIl\",\n        \"livemode\": false,\n        \"object\": \"usage_record_summary\",\n        \"period\": {\n          \"end\": 1627015586,\n          \"start\": 1624423771\n        },\n        \"subscription_item\": \"si_JiphMAMFxZKW8s\",\n        \"total_usage\": 0\n      }\n    ],\n    \"has_more\": false,\n    \"object\": \"list\",\n    \"url\": \"/v1/subscription_items/si_JiphMAMFxZKW8s/usage_record_summaries\"\n  }\n"
  },
  {
    "path": "tests/fixtures/webhook_endpoint_fake0001.json",
    "content": "{\n    \"id\": \"we_1Jto4CJSZQVUcJYgJKJPEBll\",\n    \"livemode\": false,\n    \"metadata\": {\n        \"djstripe_uuid\": \"f6f9aa0e-cb6c-4e0f-b5ee-5e2b9e0716d8\"\n    },\n    \"created\": 1570435136,\n    \"description\": \"This is the first webhook of a different website example.com\",\n    \"api_version\": \"\",\n    \"application\": \"ca_JWVN6vOJJs3RKsff0VUwIUfmUmN0VYqe\",\n    \"enabled_events\": [\"*\"],\n    \"secret\": \"\",\n    \"status\": \"enabled\",\n    \"url\": \"https://dev.example.com/stripe/webhook/f6f9aa0e-cb6c-4e0f-b5ee-5e2b9e0716d8\",\n    \"djstripe_uuid\": \"f6f9aa0e-cb6c-4e0f-b5ee-5e2b9e0716d8\",\n    \"djstripe_tolerance_ms\": 300.0,\n    \"djstripe_validation_type\": \"verify_signature\",\n    \"object\": \"webhook_endpoint\"\n}\n"
  },
  {
    "path": "tests/settings.py",
    "content": "import json\nimport os\n\ntest_db_vendor = os.environ.get(\"DJSTRIPE_TEST_DB_VENDOR\", \"postgres\")\ntest_db_name = os.environ.get(\"DJSTRIPE_TEST_DB_NAME\", \"djstripe\")\ntest_db_user = os.environ.get(\"DJSTRIPE_TEST_DB_USER\", test_db_vendor)\ntest_db_pass = os.environ.get(\"DJSTRIPE_TEST_DB_PASS\", \"djstripe\")\ntest_db_port = os.environ.get(\"DJSTRIPE_TEST_DB_PORT\", \"\")\n\nDEBUG = True\nSECRET_KEY = os.environ.get(\"DJSTRIPE_TEST_DJANGO_SECRET_KEY\", \"djstripe\")\nSITE_ID = 1\nTIME_ZONE = \"UTC\"\nUSE_TZ = True\nPROJECT_DIR = os.path.dirname(os.path.abspath(__file__))\nBASE_DIR = os.path.dirname(PROJECT_DIR)\n\nALLOWED_HOSTS = json.loads(os.environ.get(\"DJSTRIPE_TEST_ALLOWED_HOSTS_JSON\", '[\"*\"]'))\n\nif test_db_vendor == \"postgres\":\n    DATABASES = {\n        \"default\": {\n            \"ENGINE\": \"django.db.backends.postgresql\",\n            \"NAME\": test_db_name,\n            \"USER\": test_db_user,\n            \"PASSWORD\": test_db_pass,\n            \"HOST\": os.environ.get(\"DJSTRIPE_TEST_DB_HOST\", \"localhost\"),\n            \"PORT\": test_db_port,\n        }\n    }\nelif test_db_vendor == \"mysql\":\n    DATABASES = {\n        \"default\": {\n            \"ENGINE\": \"django.db.backends.mysql\",\n            \"NAME\": test_db_name,\n            \"USER\": os.environ.get(\"DJSTRIPE_TEST_DB_USER\", \"root\"),\n            \"PASSWORD\": test_db_pass,\n            \"HOST\": os.environ.get(\"DJSTRIPE_TEST_DB_HOST\", \"127.0.0.1\"),\n            \"PORT\": test_db_port,\n        }\n    }\nelif test_db_vendor == \"sqlite\":\n    # sqlite is not officially supported, but useful for quick testing.\n    # may be dropped if we can't maintain compatibility.\n    DATABASES = {\n        \"default\": {\n            \"ENGINE\": \"django.db.backends.sqlite3\",\n            \"NAME\": os.path.join(BASE_DIR, \"db.sqlite3\"),\n            # use a on-disk db for test so --reuse-db can be used\n            \"TEST\": {\"NAME\": os.path.join(BASE_DIR, \"test_db.sqlite3\")},\n        }\n    }\nelse:\n    raise NotImplementedError(f\"DJSTRIPE_TEST_DB_VENDOR = {test_db_vendor}\")\n\n\nTEMPLATES = [\n    {\n        \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n        \"DIRS\": [],\n        \"APP_DIRS\": True,\n        \"OPTIONS\": {\n            \"context_processors\": [\n                \"django.contrib.auth.context_processors.auth\",\n                \"django.contrib.messages.context_processors.messages\",\n                \"django.template.context_processors.request\",\n            ]\n        },\n    }\n]\n\nROOT_URLCONF = \"tests.urls\"\nINSTALLED_APPS = [\n    \"django.contrib.admin\",\n    \"django.contrib.auth\",\n    \"django.contrib.contenttypes\",\n    \"django.contrib.sessions\",\n    \"django.contrib.messages\",\n    \"django.contrib.sites\",\n    \"django.contrib.staticfiles\",\n    \"djstripe\",\n    \"tests\",\n    # to load custom models defined to test fields.py\n    \"tests.fields\",\n    \"tests.apps.testapp\",\n    \"tests.apps.example\",\n]\n\nMIDDLEWARE = (\n    \"django.middleware.security.SecurityMiddleware\",\n    \"django.contrib.sessions.middleware.SessionMiddleware\",\n    \"django.middleware.common.CommonMiddleware\",\n    \"django.middleware.csrf.CsrfViewMiddleware\",\n    \"django.contrib.auth.middleware.AuthenticationMiddleware\",\n    \"django.contrib.messages.middleware.MessageMiddleware\",\n    \"django.middleware.clickjacking.XFrameOptionsMiddleware\",\n)\n\nSTRIPE_LIVE_PUBLIC_KEY = os.environ.get(\n    \"STRIPE_PUBLIC_KEY\", \"pk_live_XXXXXXXXXXXXXXXXXXXXXXXXX\"\n)\nSTRIPE_LIVE_SECRET_KEY = os.environ.get(\n    \"STRIPE_SECRET_KEY\", \"sk_live_XXXXXXXXXXXXXXXXXXXXXXXXX\"\n)\nSTRIPE_TEST_PUBLIC_KEY = os.environ.get(\n    \"STRIPE_PUBLIC_KEY\",\n    \"pk_test_XXXXXXXXXXXXXXXXXXXXXXXXX\",\n)\nSTRIPE_TEST_SECRET_KEY = os.environ.get(\n    \"STRIPE_SECRET_KEY\",\n    \"sk_test_XXXXXXXXXXXXXXXXXXXXXXXXX\",\n)\n\nDJSTRIPE_FOREIGN_KEY_TO_FIELD = (\n    \"id\" if os.environ.get(\"USE_NATIVE_STRIPE_ID\", \"\") == \"1\" else \"djstripe_id\"\n)\n\nDJSTRIPE_WEBHOOK_VALIDATION = \"verify_signature\"\nDJSTRIPE_WEBHOOK_SECRET = os.environ.get(\"DJSTRIPE_TEST_WEBHOOK_SECRET\", \"whsec_XXXXX\")\n\nSTATIC_URL = \"/static/\"\n\nDEFAULT_AUTO_FIELD = \"django.db.models.BigAutoField\"\n"
  },
  {
    "path": "tests/templates/base.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>{% block head_title %}{% endblock %}</title>\n\n    {% block header_js %}{% endblock %}\n    {% block header_css %}{% endblock %}\n</head>\n<body>\n\n{% block content %}\n{% endblock %}\n\n</body>\n</html>\n"
  },
  {
    "path": "tests/test_account.py",
    "content": "\"\"\"\ndj-stripe Account Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nfrom django.test.testcases import TestCase\nfrom django.test.utils import override_settings\n\nfrom djstripe.models import Account\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_ACCOUNT,\n    FAKE_CUSTOM_ACCOUNT,\n    FAKE_EXPRESS_ACCOUNT,\n    FAKE_FILEUPLOAD_ICON,\n    FAKE_FILEUPLOAD_LOGO,\n    FAKE_PLATFORM_ACCOUNT,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestAccount(AssertStripeFksMixin, TestCase):\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.File.retrieve\",\n        side_effect=[deepcopy(FAKE_FILEUPLOAD_ICON), deepcopy(FAKE_FILEUPLOAD_LOGO)],\n        autospec=True,\n    )\n    def test_get_default_account(self, file_retrieve_mock, account_retrieve_mock):\n        account_retrieve_mock.return_value = deepcopy(FAKE_ACCOUNT)\n\n        account = Account.get_default_account()\n\n        account_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        self.assertGreater(len(account.business_profile), 0)\n        self.assertGreater(len(account.settings), 0)\n\n        self.assertEqual(account.branding_icon.id, FAKE_FILEUPLOAD_ICON[\"id\"])\n        self.assertEqual(account.branding_logo.id, FAKE_FILEUPLOAD_LOGO[\"id\"])\n\n        self.assertEqual(account.settings[\"branding\"][\"icon\"], account.branding_icon.id)\n        self.assertEqual(account.settings[\"branding\"][\"logo\"], account.branding_logo.id)\n\n        self.assertNotEqual(account.branding_logo.id, account.branding_icon.id)\n\n        self.assert_fks(account, expected_blank_fks={})\n\n        self.assertEqual(account.business_url, \"https://djstripe.com\")\n        account.business_profile = None\n        self.assertEqual(account.business_url, \"\")\n\n    @patch(\n        \"stripe.Account.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_ACCOUNT),\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        side_effect=[deepcopy(FAKE_FILEUPLOAD_ICON), deepcopy(FAKE_FILEUPLOAD_LOGO)],\n        autospec=True,\n    )\n    def test_sync_from_stripe_data(\n        self, fileupload_retrieve_mock, account_retrieve_mock\n    ):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        account = Account.sync_from_stripe_data(fake_account)\n\n        self.assertGreater(len(account.business_profile), 0)\n        self.assertGreater(len(account.settings), 0)\n\n        self.assertEqual(account.branding_icon.id, FAKE_FILEUPLOAD_ICON[\"id\"])\n        self.assertEqual(account.branding_logo.id, FAKE_FILEUPLOAD_LOGO[\"id\"])\n\n        self.assertEqual(account.settings[\"branding\"][\"icon\"], account.branding_icon.id)\n        self.assertEqual(account.settings[\"branding\"][\"logo\"], account.branding_logo.id)\n\n        self.assertNotEqual(account.branding_logo.id, account.branding_icon.id)\n\n        self.assert_fks(account, expected_blank_fks={})\n\n        self.assertEqual(account.business_url, \"https://djstripe.com\")\n\n    @patch(\n        \"stripe.Account.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_ACCOUNT),\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        side_effect=[deepcopy(FAKE_FILEUPLOAD_ICON), deepcopy(FAKE_FILEUPLOAD_LOGO)],\n        autospec=True,\n    )\n    def test__find_owner_account(self, fileupload_retrieve_mock, account_retrieve_mock):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        account = Account.sync_from_stripe_data(fake_account)\n        self.assertEqual(account.djstripe_owner_account.id, FAKE_PLATFORM_ACCOUNT[\"id\"])\n\n    @patch(\n        \"stripe.Account.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_ACCOUNT),\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        side_effect=[deepcopy(FAKE_FILEUPLOAD_ICON), deepcopy(FAKE_FILEUPLOAD_LOGO)],\n        autospec=True,\n    )\n    def test_business_url(self, fileupload_retrieve_mock, account_retrieve_mock):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        account = Account.sync_from_stripe_data(fake_account)\n        self.assertEqual(fake_account[\"business_profile\"][\"url\"], account.business_url)\n\n    @patch(\n        \"stripe.Account.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_ACCOUNT),\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        side_effect=[deepcopy(FAKE_FILEUPLOAD_ICON), deepcopy(FAKE_FILEUPLOAD_LOGO)],\n        autospec=True,\n    )\n    def test_branding_logo(self, fileupload_retrieve_mock, account_retrieve_mock):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        account = Account.sync_from_stripe_data(fake_account)\n        self.assertEqual(\n            fake_account[\"settings\"][\"branding\"][\"logo\"], account.branding_logo.id\n        )\n\n    @patch(\n        \"stripe.Account.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_ACCOUNT),\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        side_effect=[deepcopy(FAKE_FILEUPLOAD_ICON), deepcopy(FAKE_FILEUPLOAD_LOGO)],\n        autospec=True,\n    )\n    def test_branding_icon(self, fileupload_retrieve_mock, account_retrieve_mock):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        account = Account.sync_from_stripe_data(fake_account)\n        self.assertEqual(\n            fake_account[\"settings\"][\"branding\"][\"icon\"], account.branding_icon.id\n        )\n\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_LOGO),\n        autospec=True,\n    )\n    def test__attach_objects_post_save_hook(\n        self, fileupload_retrieve_mock, account_retrieve_mock\n    ):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        fake_account[\"settings\"][\"branding\"][\"icon\"] = None\n        account_retrieve_mock.return_value = fake_account\n\n        account = Account.sync_from_stripe_data(fake_account)\n        assert account.livemode is False\n\n        fileupload_retrieve_mock.assert_called_with(\n            id=fake_account[\"settings\"][\"branding\"][\"logo\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[],\n            stripe_account=fake_account[\"id\"],\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_LOGO),\n        autospec=True,\n    )\n    def test_get_default_account_null_logo(\n        self, fileupload_retrieve_mock, account_retrieve_mock\n    ):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        fake_account[\"settings\"][\"branding\"][\"icon\"] = None\n        fake_account[\"settings\"][\"branding\"][\"logo\"] = None\n        account_retrieve_mock.return_value = fake_account\n\n        account = Account.get_default_account()\n\n        account_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        self.assert_fks(\n            account,\n            expected_blank_fks={\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n            },\n        )\n\n    @patch(\n        \"stripe.Account.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_ACCOUNT),\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        side_effect=[deepcopy(FAKE_FILEUPLOAD_ICON), deepcopy(FAKE_FILEUPLOAD_LOGO)],\n        autospec=True,\n    )\n    def test_get_stripe_dashboard_url(\n        self, fileupload_retrieve_mock, account_retrieve_mock\n    ):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        account = Account.sync_from_stripe_data(fake_account)\n\n        self.assertEqual(\n            account.get_stripe_dashboard_url(),\n            f\"https://dashboard.stripe.com/{account.id}/\"\n            f\"{'test/' if not account.livemode else ''}dashboard\",\n        )\n\n\nclass TestAccountMethods:\n    @pytest.mark.parametrize(\n        (\n            \"business_profile_update\",\n            \"settings_dashboard_update\",\n            \"expected_account_str\",\n        ),\n        (\n            ({}, {}, \"dj-stripe\"),\n            ({}, {\"display_name\": \"some display name\"}, \"some display name\"),\n            (\n                {\"name\": \"some business name\"},\n                {\"display_name\": \"\"},\n                \"some business name\",\n            ),\n            ({\"name\": \"\"}, {\"display_name\": \"\"}, \"<id=acct_1032D82eZvKYlo2C>\"),\n        ),\n    )\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_LOGO),\n        autospec=True,\n    )\n    def test_account_str(\n        self,\n        fileupload_retrieve_mock,\n        account_retrieve_mock,\n        business_profile_update,\n        settings_dashboard_update,\n        expected_account_str,\n    ):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        fake_account[\"business_profile\"].update(business_profile_update)\n        fake_account[\"settings\"][\"dashboard\"].update(settings_dashboard_update)\n        account_retrieve_mock.return_value = fake_account\n        account = Account.get_default_account()\n\n        assert str(account) == expected_account_str\n\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_LOGO),\n        autospec=True,\n    )\n    def test__str__null_settings_null_business_profile(\n        self,\n        fileupload_retrieve_mock,\n        account_retrieve_mock,\n    ):\n        \"\"\"Test that __str__ doesn't crash when settings and business_profile are NULL.\"\"\"\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        fake_account[\"settings\"] = None\n        fake_account[\"business_profile\"] = None\n        account_retrieve_mock.return_value = fake_account\n\n        account = Account.sync_from_stripe_data(fake_account)\n        assert str(account) == \"<id=acct_1032D82eZvKYlo2C>\"\n\n    @override_settings(\n        STRIPE_SECRET_KEY=\"sk_live_XXXXXXXXXXXXXXXXXXXX5678\",\n        STRIPE_LIVE_MODE=True,\n    )\n    @pytest.mark.parametrize(\n        \"_account,is_platform\",\n        [\n            (deepcopy(FAKE_ACCOUNT), False),\n            (deepcopy(FAKE_CUSTOM_ACCOUNT), False),\n            (deepcopy(FAKE_EXPRESS_ACCOUNT), False),\n            (deepcopy(FAKE_PLATFORM_ACCOUNT), True),\n        ],\n    )\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_LOGO),\n        autospec=True,\n    )\n    def test_livemode_populates_correctly_for_livemode(\n        self,\n        fileupload_retrieve_mock,\n        account_retrieve_mock,\n        _account,\n        is_platform,\n    ):\n        fake_account = _account\n        fake_account[\"settings\"][\"branding\"][\"icon\"] = None\n        account_retrieve_mock.return_value = fake_account\n\n        platform_account = FAKE_PLATFORM_ACCOUNT.create()\n\n        # Account.get_or_retrieve_for_api_key is called and since the passed in api_key doesn't have an owner acount,\n        # key is refreshed and the current mocked _account is assigned as the owner account.\n        # This essentially turns all these cases into Platform Account cases.\n        # And that is why Account.get_or_retrieve_for_api_key is patched\n        with patch.object(\n            Account, \"get_or_retrieve_for_api_key\", return_value=platform_account\n        ):\n            account = Account.sync_from_stripe_data(\n                fake_account, api_key=djstripe_settings.STRIPE_SECRET_KEY\n            )\n            assert account.djstripe_owner_account == platform_account\n\n            if is_platform is True:\n                assert account.livemode is None\n            else:\n                assert account.livemode is True\n\n    @override_settings(\n        STRIPE_SECRET_KEY=\"sk_test_XXXXXXXXXXXXXXXXXXXX5678\",\n        STRIPE_LIVE_MODE=False,\n    )\n    @pytest.mark.parametrize(\n        \"_account,is_platform\",\n        [\n            (deepcopy(FAKE_ACCOUNT), False),\n            (deepcopy(FAKE_CUSTOM_ACCOUNT), False),\n            (deepcopy(FAKE_EXPRESS_ACCOUNT), False),\n            (deepcopy(FAKE_PLATFORM_ACCOUNT), True),\n        ],\n    )\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_LOGO),\n        autospec=True,\n    )\n    def test_livemode_populates_correctly_for_testmode(\n        self, fileupload_retrieve_mock, account_retrieve_mock, _account, is_platform\n    ):\n        fake_account = _account\n        fake_account[\"settings\"][\"branding\"][\"icon\"] = None\n        account_retrieve_mock.return_value = fake_account\n\n        platform_account = FAKE_PLATFORM_ACCOUNT.create()\n\n        # Account.get_or_retrieve_for_api_key is called and since the passed in api_key doesn't have an owner acount,\n        # key is refreshed and the current mocked _account is assigned as the owner account.\n        # This essentially turns all these cases into Platform Account cases.\n        # And that is why Account.get_or_retrieve_for_api_key is patched\n        with patch.object(\n            Account, \"get_or_retrieve_for_api_key\", return_value=platform_account\n        ):\n            account = Account.sync_from_stripe_data(\n                fake_account, api_key=djstripe_settings.STRIPE_SECRET_KEY\n            )\n            assert account.djstripe_owner_account == platform_account\n\n            if is_platform is True:\n                assert account.livemode is None\n            else:\n                assert account.livemode is False\n\n\nclass TestAccountRestrictedKeys(TestCase):\n    @override_settings(\n        STRIPE_TEST_SECRET_KEY=\"rk_test_blah\",\n        STRIPE_TEST_PUBLIC_KEY=\"pk_test_foo\",\n        STRIPE_LIVE_MODE=False,\n    )\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    def test_account_str_restricted_key(self, account_retrieve_mock):\n        \"\"\"\n        Test that we do not attempt to retrieve account ID with restricted keys.\n        \"\"\"\n        assert djstripe_settings.STRIPE_SECRET_KEY == \"rk_test_blah\"\n\n        account = Account.get_default_account()\n\n        assert account is None\n        account_retrieve_mock.assert_not_called()\n\n\n@pytest.mark.parametrize(\n    \"mock_account_id, other_mock_account_id, expected_stripe_account\",\n    (\n        (\"acct_fakefakefakefake001\", None, \"acct_fakefakefakefake001\"),\n        (\n            \"acct_fakefakefakefake001\",\n            \"acct_fakefakefakefake002\",\n            \"acct_fakefakefakefake002\",\n        ),\n    ),\n)\n@patch(\n    target=\"djstripe.models.connect.StripeModel._create_from_stripe_object\",\n    autospec=True,\n)\ndef test_account__create_from_stripe_object(\n    mock_super__create_from_stripe_object,\n    mock_account_id,\n    other_mock_account_id,\n    expected_stripe_account,\n):\n    \"\"\"Ensure that we are setting the ID value correctly.\"\"\"\n    mock_data = {\"id\": mock_account_id}\n    Account._create_from_stripe_object(\n        data=mock_data, stripe_account=other_mock_account_id\n    )\n\n    mock_super__create_from_stripe_object.assert_called_once_with(\n        data=mock_data,\n        current_ids=None,\n        pending_relations=None,\n        save=True,\n        stripe_account=expected_stripe_account,\n        api_key=djstripe_settings.STRIPE_SECRET_KEY,\n    )\n\n\n@pytest.mark.parametrize(\"stripe_account\", (None, \"acct_fakefakefakefake001\"))\n@pytest.mark.parametrize(\n    \"api_key, expected_api_key\",\n    (\n        (None, djstripe_settings.STRIPE_SECRET_KEY),\n        (\"sk_fakefakefake01\", \"sk_fakefakefake01\"),\n    ),\n)\n@pytest.mark.parametrize(\"extra_kwargs\", ({\"reason\": \"fraud\"}, {\"reason\": \"other\"}))\n@patch(\n    \"stripe.Account.retrieve\",\n    autospec=True,\n    return_value=deepcopy(FAKE_ACCOUNT),\n)\n@patch(\n    \"stripe.Account.reject\",\n)\n@patch(\n    \"stripe.File.retrieve\",\n    side_effect=[deepcopy(FAKE_FILEUPLOAD_ICON), deepcopy(FAKE_FILEUPLOAD_LOGO)],\n    autospec=True,\n)\ndef test_api_reject(\n    fileupload_retrieve_mock,\n    account_reject_mock,\n    account_retrieve_mock,\n    extra_kwargs,\n    api_key,\n    expected_api_key,\n    stripe_account,\n):\n    \"\"\"Test that API reject properly uses the passed in parameters.\"\"\"\n\n    fake_account = deepcopy(FAKE_ACCOUNT)\n    fake_account_rejected = deepcopy(FAKE_ACCOUNT)\n    fake_account_rejected[\"charges_enabled\"] = False\n    fake_account_rejected[\"payouts_enabled\"] = False\n    account_reject_mock.return_value = fake_account_rejected\n\n    account = Account.sync_from_stripe_data(fake_account)\n\n    # invoke api_reject()\n    account_rejected = account.api_reject(\n        api_key=api_key, stripe_account=stripe_account, **extra_kwargs\n    )\n\n    assert account_rejected[\"charges_enabled\"] is False\n    assert account_rejected[\"payouts_enabled\"] is False\n\n    Account.stripe_class.reject.assert_called_once_with(\n        account.id,\n        api_key=expected_api_key,\n        stripe_account=stripe_account or FAKE_PLATFORM_ACCOUNT[\"id\"],\n        stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        **extra_kwargs,\n    )\n"
  },
  {
    "path": "tests/test_admin.py",
    "content": "\"\"\"\ndj-stripe Admin Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom typing import Sequence\n\nimport pytest\nimport stripe\nfrom django.apps import apps\nfrom django.contrib.admin import helpers, site\nfrom django.contrib.auth import get_user_model\nfrom django.core.exceptions import FieldError\nfrom django.test import TestCase\nfrom django.test.client import RequestFactory\nfrom django.urls import reverse\nfrom pytest_django.asserts import assertQuerysetEqual\n\nfrom djstripe import models, utils\nfrom djstripe.admin import admin as djstripe_admin\nfrom djstripe.admin.forms import CustomActionForm\nfrom djstripe.models.account import Account\nfrom tests import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PLAN,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_SUBSCRIPTION_SCHEDULE,\n)\n\nfrom .fields.models import CustomActionModel\n\npytestmark = pytest.mark.django_db\n\n\n@pytest.mark.parametrize(\n    \"output,input\",\n    [\n        (\n            [\"event\", \"stripe_trigger_account\", \"webhook_endpoint\"],\n            models.WebhookEventTrigger,\n        ),\n        (\n            [\n                \"djstripe_owner_account\",\n                \"customer\",\n                \"default_payment_method\",\n                \"default_source\",\n                \"latest_invoice\",\n                \"pending_setup_intent\",\n                \"plan\",\n                \"schedule\",\n                \"default_tax_rates\",\n            ],\n            models.Subscription,\n        ),\n        (\n            [\n                \"djstripe_owner_account\",\n                \"default_source\",\n                \"coupon\",\n                \"default_payment_method\",\n                \"subscriber\",\n            ],\n            models.Customer,\n        ),\n    ],\n)\ndef test_get_forward_relation_fields_for_model(output, input):\n    assert output == djstripe_admin.get_forward_relation_fields_for_model(input)\n\n\nclass TestAdminRegisteredModelsChildrenOfStripeModel(TestCase):\n    def setUp(self):\n        self.admin = get_user_model().objects.create_superuser(\n            username=\"admin\", email=\"admin@djstripe.com\", password=\"xxx\"\n        )\n        self.factory = RequestFactory()\n        # the 4 models that do not inherit from StripeModel and hence\n        # do not inherit from StripeModelAdmin\n        self.ignore_models = [\n            \"WebhookEventTrigger\",\n            \"WebhookEndpoint\",\n            \"IdempotencyKey\",\n            \"APIKey\",\n        ]\n\n    def test_get_list_display_links(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n                list_display = model_admin.get_changelist_instance(request).list_display\n\n                # get_changelist_instance to get an instance of the ChangelistView for logged in admin user\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    list(response.context_data[\"cl\"].list_display_links),\n                    list(\n                        model_admin.get_changelist_instance(request).list_display_links\n                    ),\n                )\n\n                # ensure all the fields in list_display_links are valid\n                for field in model_admin.get_list_display_links(request, list_display):\n                    model_admin.get_changelist_instance(request).get_ordering_field(\n                        field\n                    )\n\n    def test_get_list_display(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n\n                # get_changelist_instance to get an instance of the ChangelistView for logged in admin user\n                list_display = model_admin.get_changelist_instance(request).list_display\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    list(response.context_data[\"cl\"].list_display),\n                    list(list_display),\n                )\n\n                # ensure all the fields in list_display are valid\n                for field in model_admin.get_list_display(request):\n                    model_admin.get_changelist_instance(request).get_ordering_field(\n                        field\n                    )\n\n                # for models inheriting from StripeModelAdmin verify:\n                if model.__name__ not in self.ignore_models:\n                    self.assertTrue(\n                        all(\n                            [\n                                1\n                                for i in [\n                                    \"__str__\",\n                                    \"id\",\n                                    \"djstripe_owner_account\",\n                                    \"created\",\n                                    \"livemode\",\n                                ]\n                                if i in list_display\n                            ]\n                        )\n                    )\n\n    def test_get_list_filter(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n\n                # get_changelist_instance to get an instance of the ChangelistView for logged in admin user\n                list_filter = model_admin.get_changelist_instance(request).list_filter\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    list(response.context_data[\"cl\"].list_filter),\n                    list(list_filter),\n                )\n\n                # ensure all the filters get formed correctly\n                chl = model_admin.get_changelist_instance(request)\n                chl.get_filters(request)\n                chl.get_queryset(request)\n\n                # for models inheriting from StripeModelAdmin verify:\n                if model.__name__ not in self.ignore_models:\n                    self.assertTrue(\n                        all([1 for i in [\"created\", \"livemode\"] if i in list_filter])\n                    )\n\n    def test_get_readonly_fields(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n\n                # get_changelist_instance to get an instance of the ChangelistView for logged in admin user\n                readonly_fields = model_admin.get_changelist_instance(\n                    request\n                ).model_admin.readonly_fields\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    response.context_data[\"cl\"].model_admin.readonly_fields,\n                    readonly_fields,\n                )\n\n                # ensure all the fields in readonly_fields are valid\n                for field in model_admin.get_readonly_fields(request):\n                    # ensure the given field is on model, or model_admin or modelform\n                    model_admin.get_changelist_instance(request).get_ordering_field(\n                        field\n                    )\n\n                # for models inheriting from StripeModelAdmin verify:\n                if model.__name__ not in self.ignore_models:\n                    self.assertTrue(\n                        all(\n                            [\n                                1\n                                for i in [\"created\", \"djstripe_owner_account\", \"id\"]\n                                if i in readonly_fields\n                            ]\n                        )\n                    )\n\n    def test_get_list_select_related(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n\n                # get_changelist_instance to get an instance of the ChangelistView for logged in admin user\n                list_select_related = model_admin.get_changelist_instance(\n                    request\n                ).list_select_related\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    response.context_data[\"cl\"].list_select_related,\n                    list_select_related,\n                )\n\n                # ensure all the fields in list_select_related are valid\n                list_select_related_fields = model_admin.get_list_select_related(\n                    request\n                )\n                if isinstance(list_select_related_fields, Sequence):\n                    # need to force the returned queryset to get evaluated\n                    list(model.objects.select_related(*list_select_related_fields))\n\n    # todo complete after djstripe has integrated ModelFactory\n    # def test_get_fieldsets_change(self):\n    #     pass\n\n    def test_get_fieldsets_add(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard add url\n                add_url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_add\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(add_url)\n                request.user = self.admin\n\n                # skip model if model doesn't have \"has_add_permission\"\n                if not model_admin.has_add_permission(request):\n                    continue\n\n                response = model_admin.add_view(request)\n\n                fieldsets = model_admin.get_fieldsets(request)\n\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    response.context_data[\"adminform\"].fieldsets,\n                    [*fieldsets],\n                )\n\n                # for models inheriting from StripeModelAdmin verify:\n                if model.__name__ not in self.ignore_models:\n                    self.assertTrue(\n                        all(\n                            [\n                                1\n                                for i in [\n                                    \"created\",\n                                    \"livemode\",\n                                    \"djstripe_owner_account\",\n                                    \"id\",\n                                ]\n                                if i in fieldsets\n                            ]\n                        )\n                    )\n\n    # todo complete after djstripe has integrated ModelFactory\n    # def test_get_fields_change(self):\n    #     pass\n\n    def test_get_fields_add(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard add url\n                add_url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_add\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(add_url)\n                request.user = self.admin\n\n                # skip model if model doesn't have \"has_add_permission\"\n                if not model_admin.has_add_permission(request):\n                    continue\n\n                response = model_admin.add_view(request)\n\n                fields = model_admin.get_fields(request)\n\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    response.context_data[\"adminform\"].model_admin.get_fields(request),\n                    list(fields),\n                )\n\n                # ensure all the fields in model_admin are valid\n                for field in model_admin.get_fields(request):\n                    # as these fields are form field and not modelform fields\n                    if model_admin.model is models.WebhookEndpoint and field in (\n                        \"base_url\",\n                        \"enabled\",\n                        \"connect\",\n                    ):\n                        continue\n                    model_admin.get_changelist_instance(request).get_ordering_field(\n                        field\n                    )\n\n    def test_get_search_fields(self):\n        \"\"\"\n        Ensure all fields in model_admin.get_search_fields exist on the model or the related model\n        \"\"\"\n\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url and make a sample query to trigger search\n                url = (\n                    reverse(\n                        f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                    )\n                    + \"?q=bar\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n\n                search_fields = model_admin.get_changelist_instance(\n                    request\n                ).search_fields\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    response.context_data[\"cl\"].search_fields,\n                    search_fields,\n                )\n\n                try:\n                    # ensure all the fields in search_fields are valid\n                    # need to force the returned queryset to get evaluated\n                    list(model.objects.select_related(*search_fields))\n                except FieldError as error:\n                    if \"Non-relational field given in select_related\" not in str(error):\n                        self.fail(error)\n\n                # for models inheriting from StripeModelAdmin verify:\n                if model.__name__ not in self.ignore_models:\n                    self.assertTrue(\"id\" in search_fields)\n\n    def test_get_actions(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                actions = model_admin.get_actions(request)\n\n                # sub-classes of StripeModel\n                if model.__name__ not in self.ignore_models:\n                    if model.__name__ in (\"UsageRecordSummary\", \"LineItem\"):\n                        assert \"_resync_instances\" not in actions\n                        assert \"_sync_all_instances\" in actions\n                    elif model.__name__ == \"Subscription\":\n                        assert \"_resync_instances\" in actions\n                        assert \"_sync_all_instances\" in actions\n                        assert \"_cancel\" in actions\n                    elif model.__name__ in (\"Mandate\", \"UsageRecord\"):\n                        assert \"_resync_instances\" in actions\n                        assert \"_sync_all_instances\" not in actions\n                    elif model.__name__ == \"Discount\":\n                        assert \"_resync_instances\" not in actions\n                        assert \"_sync_all_instances\" not in actions\n                    else:\n                        assert \"_resync_instances\" in actions\n                        assert \"_sync_all_instances\" in actions\n\n                # not sub-classes of StripeModel\n                else:\n                    if model.__name__ == \"WebhookEndpoint\":\n                        assert \"delete_selected\" not in actions\n                        assert \"_resync_instances\" in actions\n                        assert \"_sync_all_instances\" in actions\n                    else:\n                        assert \"_resync_instances\" not in actions\n                        assert \"_sync_all_instances\" not in actions\n\n\nclass TestAdminRegisteredModelsNotChildrenOfStripeModel(TestCase):\n    def setUp(self):\n        self.admin = get_user_model().objects.create_superuser(\n            username=\"admin\", email=\"admin@djstripe.com\", password=\"xxx\"\n        )\n        self.factory = RequestFactory()\n\n    def test_get_list_display_links(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n                list_display = model_admin.get_changelist_instance(request).list_display\n\n                # get_changelist_instance to get an instance of the ChangelistView for logged in admin user\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    list(response.context_data[\"cl\"].list_display_links),\n                    list(\n                        model_admin.get_changelist_instance(request).list_display_links\n                    ),\n                )\n\n                # ensure all the fields in list_display_links are valid\n                for field in model_admin.get_list_display_links(request, list_display):\n                    model_admin.get_changelist_instance(request).get_ordering_field(\n                        field\n                    )\n\n    def test_get_list_display(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n\n                # get_changelist_instance to get an instance of the ChangelistView for logged in admin user\n                list_display = model_admin.get_changelist_instance(request).list_display\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    list(response.context_data[\"cl\"].list_display),\n                    list(list_display),\n                )\n\n                # ensure all the fields in list_display are valid\n                for field in model_admin.get_list_display(request):\n                    model_admin.get_changelist_instance(request).get_ordering_field(\n                        field\n                    )\n\n                self.assertTrue(\n                    all(\n                        [\n                            1\n                            for i in [\n                                \"__str__\",\n                                \"id\",\n                                \"djstripe_owner_account\",\n                                \"created\",\n                                \"livemode\",\n                            ]\n                            if i in list_display\n                        ]\n                    )\n                )\n\n    def test_get_list_filter(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n\n                # get_changelist_instance to get an instance of the ChangelistView for logged in admin user\n                list_filter = model_admin.get_changelist_instance(request).list_filter\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    list(response.context_data[\"cl\"].list_filter),\n                    list(list_filter),\n                )\n\n                # ensure all the filters get formed correctly\n                chl = model_admin.get_changelist_instance(request)\n                chl.get_filters(request)\n                chl.get_queryset(request)\n\n                self.assertTrue(\n                    all([1 for i in [\"created\", \"livemode\"] if i in list_filter])\n                )\n\n    def test_get_readonly_fields(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n\n                # get_changelist_instance to get an instance of the ChangelistView for logged in admin user\n                readonly_fields = model_admin.get_changelist_instance(\n                    request\n                ).model_admin.readonly_fields\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    response.context_data[\"cl\"].model_admin.readonly_fields,\n                    readonly_fields,\n                )\n\n                # ensure all the fields in readonly_fields are valid\n                for field in model_admin.get_readonly_fields(request):\n                    # ensure the given field is on model, or model_admin or modelform\n                    model_admin.get_changelist_instance(request).get_ordering_field(\n                        field\n                    )\n\n                self.assertTrue(\n                    all(\n                        [\n                            1\n                            for i in [\"created\", \"djstripe_owner_account\", \"id\"]\n                            if i in readonly_fields\n                        ]\n                    )\n                )\n\n    def test_get_list_select_related(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n\n                # get_changelist_instance to get an instance of the ChangelistView for logged in admin user\n                list_select_related = model_admin.get_changelist_instance(\n                    request\n                ).list_select_related\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    response.context_data[\"cl\"].list_select_related,\n                    list_select_related,\n                )\n\n                # ensure all the fields in list_select_related are valid\n                list_select_related_fields = model_admin.get_list_select_related(\n                    request\n                )\n                if isinstance(list_select_related_fields, Sequence):\n                    # need to force the returned queryset to get evaluated\n                    list(model.objects.select_related(*list_select_related_fields))\n\n    # todo complete after djstripe has integrated ModelFactory\n    # def test_get_fieldsets_change(self):\n    #     pass\n\n    def test_get_fieldsets_add(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard add url\n                add_url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_add\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(add_url)\n                request.user = self.admin\n\n                # skip model if model doesn't have \"has_add_permission\"\n                if not model_admin.has_add_permission(request):\n                    continue\n\n                response = model_admin.add_view(request)\n\n                fieldsets = model_admin.get_fieldsets(request)\n\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    response.context_data[\"adminform\"].fieldsets,\n                    [*fieldsets],\n                )\n\n                self.assertTrue(\n                    all(\n                        [\n                            1\n                            for i in [\n                                \"created\",\n                                \"livemode\",\n                                \"djstripe_owner_account\",\n                                \"id\",\n                            ]\n                            if i in fieldsets\n                        ]\n                    )\n                )\n\n    # todo complete after djstripe has integrated ModelFactory\n    # def test_get_fields_change(self):\n    #     pass\n\n    def test_get_fields_add(self):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard add url\n                add_url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_add\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(add_url)\n                request.user = self.admin\n\n                # skip model if model doesn't have \"has_add_permission\"\n                if not model_admin.has_add_permission(request):\n                    continue\n\n                response = model_admin.add_view(request)\n\n                fields = model_admin.get_fields(request)\n\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    response.context_data[\"adminform\"].model_admin.get_fields(request),\n                    list(fields),\n                )\n\n                # ensure all the fields in model_admin are valid\n                for field in model_admin.get_fields(request):\n                    # as these fields are form field and not modelform fields\n                    if model_admin.model is models.WebhookEndpoint and field in (\n                        \"base_url\",\n                        \"enabled\",\n                        \"connect\",\n                    ):\n                        continue\n                    model_admin.get_changelist_instance(request).get_ordering_field(\n                        field\n                    )\n\n    def test_get_search_fields(self):\n        \"\"\"\n        Ensure all fields in model_admin.get_search_fields exist on the model or the related model\n        \"\"\"\n\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n                # get the standard changelist_view url and make a sample query to trigger search\n                url = (\n                    reverse(\n                        f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                    )\n                    + \"?q=bar\"\n                )\n\n                # add the admin user to the mocked request\n                request = self.factory.get(url)\n                request.user = self.admin\n\n                response = model_admin.changelist_view(request)\n\n                search_fields = model_admin.get_changelist_instance(\n                    request\n                ).search_fields\n                self.assertEqual(response.status_code, 200)\n                self.assertEqual(\n                    response.context_data[\"cl\"].search_fields,\n                    search_fields,\n                )\n\n                try:\n                    # ensure all the fields in search_fields are valid\n                    # need to force the returned queryset to get evaluated\n                    list(model.objects.select_related(*search_fields))\n                except FieldError as error:\n                    if \"Non-relational field given in select_related\" not in str(error):\n                        self.fail(error)\n\n\nclass TestAdminInlineModels(TestCase):\n    def test_readonly_fields_exist(self):\n        \"\"\"\n        Ensure all fields in BaseModelAdmin.readonly_fields exist on the model\n        \"\"\"\n\n        for model, model_admin in site._registry.items():\n            for inline_admin in model_admin.inlines:\n                fields = getattr(inline_admin, \"readonly_fields\", [])\n                try:\n                    # need to force the returned queryset to get evaluated\n                    list(inline_admin.model.objects.select_related(*fields))\n                except FieldError as error:\n                    if \"Non-relational field given in select_related\" not in str(error):\n                        self.fail(error)\n\n\nclass TestAdminSite(TestCase):\n    def setUp(self):\n        self.empty_value = \"-empty-\"\n\n    def test_search_fields(self):\n        \"\"\"\n        Search for errors like this:\n        Bad search field <customer__user__username> for Customer model.\n        \"\"\"\n\n        for _model, model_admin in site._registry.items():\n            model_name = model_admin.model.__name__\n            table_name = model_name.lower()\n            for search_field in getattr(model_admin, \"search_fields\", []):\n                self.assertFalse(\n                    search_field.startswith(f\"{table_name}__\"),\n                    f\"Bad search field <{search_field}> for {model_name} model.\",\n                )\n\n    def test_search_fields_exist(self):\n        \"\"\"\n        Ensure all fields in model_admin.search_fields exist on the model or the related model\n        \"\"\"\n\n        for model, model_admin in site._registry.items():\n            fields = getattr(model_admin, \"search_fields\", [])\n            try:\n                # need to force the returned queryset to get evaluated\n                list(model.objects.select_related(*fields))\n            except FieldError as error:\n                if \"Non-relational field given in select_related\" not in str(error):\n                    self.fail(error)\n\n    def test_list_select_related_fields_exist(self):\n        \"\"\"\n        Ensure all fields in model_admin.list_select_related exist on the model or the related model\n        \"\"\"\n\n        for model, model_admin in site._registry.items():\n            fields = getattr(model_admin, \"list_select_related\", False)\n            if isinstance(fields, Sequence):\n                try:\n                    # need to force the returned queryset to get evaluated\n                    list(model.objects.select_related(*fields))\n                except FieldError as error:\n                    self.fail(error)\n\n\nclass TestCustomActionMixin:\n    # the 4 models that do not inherit from StripeModel and hence\n    # do not inherit from StripeModelAdmin\n    ignore_models = [\n        \"WebhookEventTrigger\",\n        \"WebhookEndpoint\",\n        \"IdempotencyKey\",\n        \"APIKey\",\n    ]\n\n    @pytest.mark.parametrize(\n        \"action_name\", [\"_sync_all_instances\", \"_resync_instances\"]\n    )\n    @pytest.mark.parametrize(\"djstripe_owner_account_exists\", [False, True])\n    def test_get_admin_action_context(\n        self, djstripe_owner_account_exists, action_name, monkeypatch\n    ):\n        # monkeypatch utils.get_model\n        def mock_get_model(*args, **kwargs):\n            return model\n\n        monkeypatch.setattr(utils, \"get_model\", mock_get_model)\n\n        model = CustomActionModel\n\n        # create instance to be used in the Django Admin Action\n        instance = model.objects.create(id=\"test\")\n\n        if djstripe_owner_account_exists:\n            account_instance = Account.objects.first()\n            instance.djstripe_owner_account = account_instance\n            instance.save()\n\n        queryset = model.objects.all()\n        model_admin = site._registry.get(model)\n\n        context = model_admin.get_admin_action_context(\n            queryset, action_name, CustomActionForm\n        )\n\n        assert context.get(\"queryset\") == queryset\n        assert context.get(\"action_name\") == action_name\n        assert context.get(\"model_name\") == \"customactionmodel\"\n        assert context.get(\"changelist_url\") == \"/admin/fields/customactionmodel/\"\n        assert context.get(\"ACTION_CHECKBOX_NAME\") == helpers.ACTION_CHECKBOX_NAME\n\n        if action_name == \"_sync_all_instances\":\n            assert context.get(\"info\") == []\n            assertQuerysetEqual(\n                context.get(\"form\").initial.get(helpers.ACTION_CHECKBOX_NAME),\n                [\"_sync_all_instances\"],\n            )\n            assert context.get(\"form\").fields.get(\n                helpers.ACTION_CHECKBOX_NAME\n            ).choices == [(\"_sync_all_instances\", \"_sync_all_instances\")]\n        else:\n            assert context.get(\"info\") == [\n                f'Custom action model: <a href=\"/admin/fields/customactionmodel/{instance.pk}/change/\">&lt;id=test&gt;</a>'\n            ]\n\n            assertQuerysetEqual(\n                context.get(\"form\").initial.get(helpers.ACTION_CHECKBOX_NAME),\n                queryset.values_list(\"pk\", flat=True),\n            )\n            assert context.get(\"form\").fields.get(\n                helpers.ACTION_CHECKBOX_NAME\n            ).choices == list(\n                zip(\n                    queryset.values_list(\"pk\", flat=True),\n                    queryset.values_list(\"pk\", flat=True),\n                )\n            )\n\n    def test_get_actions(self, admin_user):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys():\n                model_admin = site._registry.get(model)\n\n                # get the standard changelist_view url\n                url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                # add the admin user to the mocked request\n                request = RequestFactory().get(url)\n                request.user = admin_user\n\n                actions = model_admin.get_actions(request)\n\n                # sub-classes of StripeModel\n                if model.__name__ not in self.ignore_models:\n                    if getattr(model.stripe_class, \"retrieve\", None):\n                        # assert \"_resync_instances\" action is present\n                        assert \"_resync_instances\" in actions\n                    else:\n                        # assert \"_resync_instances\" action is not present\n                        assert \"_resync_instances\" not in actions\n\n    @pytest.mark.parametrize(\"fake_selected_pks\", [None, [1, 2]])\n    def test_changelist_view(self, admin_client, fake_selected_pks):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys() and (\n                model.__name__ == \"WebhookEndpoint\"\n                or model.__name__ not in self.ignore_models\n            ):\n                # get the standard changelist_view url\n                change_url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                data = {\"action\": \"_sync_all_instances\"}\n\n                if fake_selected_pks is not None:\n                    # add key helpers.ACTION_CHECKBOX_NAME when it is not None\n                    data[helpers.ACTION_CHECKBOX_NAME] = fake_selected_pks\n\n                # get the response. This will invoke the changelist_view\n                response = admin_client.post(change_url, data=data, follow=True)\n\n                assert response.status_code == 200\n\n    @pytest.mark.parametrize(\"djstripe_owner_account_exists\", [False, True])\n    def test__resync_instances(\n        self, djstripe_owner_account_exists, admin_client, monkeypatch\n    ):\n        model = CustomActionModel\n        model_admin = site._registry.get(model)\n\n        # monkeypatch utils.get_model\n        def mock_get_model(*args, **kwargs):\n            return model\n\n        # monkeypatch modeladmin.get_admin_action_context\n        def mock_get_admin_action_context(*args, **kwargs):\n            return {\n                \"action_name\": \"_resync_instances\",\n                \"model_name\": \"customactionmodel\",\n            }\n\n        monkeypatch.setattr(\n            model_admin, \"get_admin_action_context\", mock_get_admin_action_context\n        )\n        monkeypatch.setattr(utils, \"get_model\", mock_get_model)\n\n        # create instance to be used in the Django Admin Action\n        instance = model.objects.create(id=\"test\")\n\n        if djstripe_owner_account_exists:\n            account_instance = Account.objects.first()\n            instance.djstripe_owner_account = account_instance\n            instance.save()\n\n        data = {\n            \"action\": \"_resync_instances\",\n            helpers.ACTION_CHECKBOX_NAME: [instance.pk],\n        }\n\n        # get the standard changelist_view url\n        change_url = reverse(\n            f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n        )\n\n        response = admin_client.post(change_url, data)\n\n        # assert user got 200 status code\n        assert response.status_code == 200\n\n    @pytest.mark.parametrize(\"fake_selected_pks\", [None, [1, 2]])\n    def test__sync_all_instances(self, admin_client, fake_selected_pks):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if (\n                model in site._registry.keys()\n                and model.__name__ not in (\"Mandate\", \"UsageRecord\", \"Discount\")\n                and (\n                    model.__name__ == \"WebhookEndpoint\"\n                    or model.__name__ not in self.ignore_models\n                )\n            ):\n                # get the standard changelist_view url\n                change_url = reverse(\n                    f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n                )\n\n                data = {\"action\": \"_sync_all_instances\"}\n\n                if fake_selected_pks is not None:\n                    data[helpers.ACTION_CHECKBOX_NAME] = fake_selected_pks\n\n                response = admin_client.post(change_url, data)\n\n                # assert user got 200 status code\n                assert response.status_code == 200\n\n\nclass TestSubscriptionAdminCustomAction:\n    def test__cancel_subscription_instances(  # noqa: C901\n        self,\n        admin_client,\n        monkeypatch,\n    ):\n        def mock_invoice_get(*args, **kwargs):\n            return FAKE_INVOICE\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_customer_get(*args, **kwargs):\n            return FAKE_CUSTOMER\n\n        def mock_charge_get(*args, **kwargs):\n            return FAKE_CHARGE\n\n        def mock_payment_method_get(*args, **kwargs):\n            return FAKE_CARD_AS_PAYMENT_METHOD\n\n        def mock_payment_intent_get(*args, **kwargs):\n            return FAKE_PAYMENT_INTENT_I\n\n        def mock_subscription_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_balance_transaction_get(*args, **kwargs):\n            return FAKE_BALANCE_TRANSACTION\n\n        def mock_product_get(*args, **kwargs):\n            return FAKE_PRODUCT\n\n        def mock_plan_get(*args, **kwargs):\n            return FAKE_PLAN\n\n        # monkeypatch stripe retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n        monkeypatch.setattr(stripe.Plan, \"retrieve\", mock_plan_get)\n\n        # Create Latest Invoice\n        models.Invoice.sync_from_stripe_data(FAKE_INVOICE)\n\n        model = models.Subscription\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        instance = model.sync_from_stripe_data(subscription_fake)\n\n        # get the standard changelist_view url\n        change_url = reverse(\n            f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n        )\n\n        data = {\"action\": \"_cancel\", helpers.ACTION_CHECKBOX_NAME: [instance.pk]}\n\n        response = admin_client.post(change_url, data)\n\n        # assert user got 200 status code\n        assert response.status_code == 200\n\n\nclass TestSubscriptionScheduleAdminCustomAction:\n    def test__release_subscription_schedule(  # noqa: C901\n        self,\n        admin_client,\n        monkeypatch,\n    ):\n        def mock_balance_transaction_get(*args, **kwargs):\n            return FAKE_BALANCE_TRANSACTION\n\n        def mock_subscription_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_charge_get(*args, **kwargs):\n            return FAKE_CHARGE\n\n        def mock_payment_method_get(*args, **kwargs):\n            return FAKE_CARD_AS_PAYMENT_METHOD\n\n        def mock_payment_intent_get(*args, **kwargs):\n            return FAKE_PAYMENT_INTENT_I\n\n        def mock_product_get(*args, **kwargs):\n            return FAKE_PRODUCT\n\n        def mock_invoice_get(*args, **kwargs):\n            return FAKE_INVOICE\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_customer_get(*args, **kwargs):\n            return FAKE_CUSTOMER\n\n        def mock_plan_get(*args, **kwargs):\n            return FAKE_PLAN\n\n        # monkeypatch stripe retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n\n        monkeypatch.setattr(stripe.Plan, \"retrieve\", mock_plan_get)\n\n        # create latest invoice\n        models.Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        model = models.SubscriptionSchedule\n        subscription_schedule_fake = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        instance = model.sync_from_stripe_data(subscription_schedule_fake)\n\n        # get the standard changelist_view url\n        change_url = reverse(\n            f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n        )\n\n        data = {\n            \"action\": \"_release_subscription_schedule\",\n            helpers.ACTION_CHECKBOX_NAME: [instance.pk],\n        }\n\n        response = admin_client.post(change_url, data)\n\n        # assert user got 200 status code\n        assert response.status_code == 200\n\n    def test__cancel_subscription_schedule(  # noqa: C901\n        self,\n        admin_client,\n        monkeypatch,\n    ):\n        def mock_balance_transaction_get(*args, **kwargs):\n            return FAKE_BALANCE_TRANSACTION\n\n        def mock_subscription_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_charge_get(*args, **kwargs):\n            return FAKE_CHARGE\n\n        def mock_payment_method_get(*args, **kwargs):\n            return FAKE_CARD_AS_PAYMENT_METHOD\n\n        def mock_payment_intent_get(*args, **kwargs):\n            return FAKE_PAYMENT_INTENT_I\n\n        def mock_product_get(*args, **kwargs):\n            return FAKE_PRODUCT\n\n        def mock_invoice_get(*args, **kwargs):\n            return FAKE_INVOICE\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_customer_get(*args, **kwargs):\n            return FAKE_CUSTOMER\n\n        def mock_plan_get(*args, **kwargs):\n            return FAKE_PLAN\n\n        # monkeypatch stripe retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n\n        monkeypatch.setattr(stripe.Plan, \"retrieve\", mock_plan_get)\n\n        # create latest invoice\n        models.Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        model = models.SubscriptionSchedule\n        subscription_schedule_fake = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        instance = model.sync_from_stripe_data(subscription_schedule_fake)\n\n        # get the standard changelist_view url\n        change_url = reverse(\n            f\"admin:{model._meta.app_label}_{model.__name__.lower()}_changelist\"\n        )\n\n        data = {\n            \"action\": \"_cancel_subscription_schedule\",\n            helpers.ACTION_CHECKBOX_NAME: [instance.pk],\n        }\n\n        response = admin_client.post(change_url, data)\n\n        # assert user got 200 status code\n        assert response.status_code == 200\n"
  },
  {
    "path": "tests/test_api_keys.py",
    "content": "from copy import deepcopy\nfrom unittest.mock import patch\n\nfrom django.conf import settings\nfrom django.test import TestCase\nfrom django.test.utils import override_settings\n\nfrom djstripe import models\nfrom djstripe.enums import APIKeyType\nfrom djstripe.settings import djstripe_settings\n\nfrom . import FAKE_ACCOUNT, FAKE_FILEUPLOAD_LOGO\n\n\nclass TestCheckApiKeySettings(TestCase):\n    @override_settings(\n        STRIPE_LIVE_SECRET_KEY=\"sk_live_foo\",\n        STRIPE_LIVE_PUBLIC_KEY=\"sk_live_foo\",\n        STRIPE_LIVE_MODE=True,\n    )\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_LOGO),\n        autospec=True,\n    )\n    def test_global_api_keys_live_mode(\n        self,\n        fileupload_retrieve_mock,\n        account_retrieve_mock,\n    ):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        fake_account[\"settings\"][\"branding\"][\"icon\"] = None\n        account_retrieve_mock.return_value = fake_account\n\n        with patch.object(\n            models.api,\n            \"get_api_key_details_by_prefix\",\n            return_value=(APIKeyType.secret, True),\n        ):\n            account = models.Account.sync_from_stripe_data(\n                fake_account, api_key=djstripe_settings.STRIPE_SECRET_KEY\n            )\n            self.assertEqual(account.default_api_key, \"sk_live_foo\")\n\n        self.assertEqual(djstripe_settings.STRIPE_LIVE_MODE, True)\n        self.assertEqual(djstripe_settings.STRIPE_SECRET_KEY, \"sk_live_foo\")\n        self.assertEqual(djstripe_settings.LIVE_API_KEY, \"sk_live_foo\")\n\n    @override_settings(\n        STRIPE_TEST_SECRET_KEY=\"sk_test_foo\",\n        STRIPE_TEST_PUBLIC_KEY=\"pk_test_foo\",\n        STRIPE_LIVE_MODE=False,\n    )\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_LOGO),\n        autospec=True,\n    )\n    def test_global_api_keys_test_mode(\n        self,\n        fileupload_retrieve_mock,\n        account_retrieve_mock,\n    ):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        fake_account[\"settings\"][\"branding\"][\"icon\"] = None\n        account_retrieve_mock.return_value = fake_account\n\n        with patch.object(\n            models.api,\n            \"get_api_key_details_by_prefix\",\n            return_value=(APIKeyType.secret, False),\n        ):\n            account = models.Account.sync_from_stripe_data(\n                fake_account, api_key=djstripe_settings.STRIPE_SECRET_KEY\n            )\n            self.assertEqual(account.default_api_key, \"sk_test_foo\")\n\n        self.assertEqual(djstripe_settings.STRIPE_LIVE_MODE, False)\n        self.assertEqual(djstripe_settings.STRIPE_SECRET_KEY, \"sk_test_foo\")\n        self.assertEqual(djstripe_settings.TEST_API_KEY, \"sk_test_foo\")\n\n    @override_settings(\n        STRIPE_TEST_SECRET_KEY=\"sk_test_foo\",\n        STRIPE_LIVE_SECRET_KEY=\"sk_live_foo\",\n        STRIPE_TEST_PUBLIC_KEY=\"pk_test_foo\",\n        STRIPE_LIVE_PUBLIC_KEY=\"pk_live_foo\",\n        STRIPE_LIVE_MODE=True,\n    )\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_LOGO),\n        autospec=True,\n    )\n    def test_api_key_live_mode(\n        self,\n        fileupload_retrieve_mock,\n        account_retrieve_mock,\n    ):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        fake_account[\"settings\"][\"branding\"][\"icon\"] = None\n        account_retrieve_mock.return_value = fake_account\n\n        with patch.object(\n            models.api,\n            \"get_api_key_details_by_prefix\",\n            return_value=(APIKeyType.secret, True),\n        ):\n            account = models.Account.sync_from_stripe_data(\n                fake_account, api_key=djstripe_settings.STRIPE_SECRET_KEY\n            )\n            self.assertEqual(account.default_api_key, \"sk_live_foo\")\n\n        del settings.STRIPE_SECRET_KEY, settings.STRIPE_TEST_SECRET_KEY\n        del settings.STRIPE_PUBLIC_KEY, settings.STRIPE_TEST_PUBLIC_KEY\n        self.assertEqual(djstripe_settings.STRIPE_LIVE_MODE, True)\n        self.assertEqual(djstripe_settings.STRIPE_SECRET_KEY, \"sk_live_foo\")\n        self.assertEqual(djstripe_settings.STRIPE_PUBLIC_KEY, \"pk_live_foo\")\n\n    @override_settings(\n        STRIPE_TEST_SECRET_KEY=\"sk_test_foo\",\n        STRIPE_LIVE_SECRET_KEY=\"sk_live_foo\",\n        STRIPE_TEST_PUBLIC_KEY=\"pk_test_foo\",\n        STRIPE_LIVE_PUBLIC_KEY=\"pk_live_foo\",\n        STRIPE_LIVE_MODE=False,\n    )\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_LOGO),\n        autospec=True,\n    )\n    def test_secret_key_test_mode(\n        self,\n        fileupload_retrieve_mock,\n        account_retrieve_mock,\n    ):\n        fake_account = deepcopy(FAKE_ACCOUNT)\n        fake_account[\"settings\"][\"branding\"][\"icon\"] = None\n        account_retrieve_mock.return_value = fake_account\n\n        with patch.object(\n            models.api,\n            \"get_api_key_details_by_prefix\",\n            return_value=(APIKeyType.secret, False),\n        ):\n            account = models.Account.sync_from_stripe_data(\n                fake_account, api_key=djstripe_settings.STRIPE_SECRET_KEY\n            )\n            self.assertEqual(account.default_api_key, \"sk_test_foo\")\n\n        del settings.STRIPE_SECRET_KEY\n        del settings.STRIPE_PUBLIC_KEY\n        self.assertEqual(djstripe_settings.STRIPE_LIVE_MODE, False)\n        self.assertEqual(djstripe_settings.STRIPE_SECRET_KEY, \"sk_test_foo\")\n        self.assertEqual(djstripe_settings.STRIPE_PUBLIC_KEY, \"pk_test_foo\")\n        self.assertEqual(djstripe_settings.TEST_API_KEY, \"sk_test_foo\")\n"
  },
  {
    "path": "tests/test_apikey.py",
    "content": "\"\"\"\ndj-stripe APIKey model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nfrom django.test import TestCase\n\nfrom djstripe.admin.admin import APIKeyAdminCreateForm\nfrom djstripe.enums import APIKeyType\nfrom djstripe.exceptions import InvalidStripeAPIKey\nfrom djstripe.models import Account, APIKey\nfrom djstripe.models.api import get_api_key_details_by_prefix\n\nfrom . import FAKE_FILEUPLOAD_ICON, FAKE_FILEUPLOAD_LOGO, FAKE_PLATFORM_ACCOUNT\n\n# avoid literal api keys to prevent git secret scanners false-positives\nSK_TEST = \"sk_test_\" + \"XXXXXXXXXXXXXXXXXXXX1234\"\nSK_LIVE = \"sk_live_\" + \"XXXXXXXXXXXXXXXXXXXX5678\"\nRK_TEST = \"rk_test_\" + \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX9876\"\nRK_LIVE = \"rk_live_\" + \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX5432\"\nPK_TEST = \"pk_test_\" + \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXAAAA\"\nPK_LIVE = \"pk_live_\" + \"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXBBBB\"\n\npytestmark = pytest.mark.django_db\n\n\ndef test_get_api_key_details_by_prefix():\n    assert get_api_key_details_by_prefix(SK_TEST) == (APIKeyType.secret, False)\n    assert get_api_key_details_by_prefix(SK_LIVE) == (APIKeyType.secret, True)\n    assert get_api_key_details_by_prefix(RK_TEST) == (APIKeyType.restricted, False)\n    assert get_api_key_details_by_prefix(RK_LIVE) == (APIKeyType.restricted, True)\n    assert get_api_key_details_by_prefix(PK_TEST) == (APIKeyType.publishable, False)\n    assert get_api_key_details_by_prefix(PK_LIVE) == (APIKeyType.publishable, True)\n\n\ndef test_get_api_key_details_by_prefix_bad_values():\n    with pytest.raises(InvalidStripeAPIKey):\n        get_api_key_details_by_prefix(\"pk_a\")\n    with pytest.raises(InvalidStripeAPIKey):\n        get_api_key_details_by_prefix(\"sk_a\")\n    with pytest.raises(InvalidStripeAPIKey):\n        get_api_key_details_by_prefix(\"rk_nope_1234\")\n\n\ndef test_clean_public_apikey():\n    key = APIKey(type=APIKeyType.publishable, livemode=False, secret=PK_TEST)\n    assert not key.djstripe_owner_account\n    key.clean()\n    assert not key.djstripe_owner_account\n\n\n@patch(\"stripe.Account.retrieve\", return_value=deepcopy(FAKE_PLATFORM_ACCOUNT))\n@patch(\"stripe.File.retrieve\", return_value=deepcopy(FAKE_FILEUPLOAD_ICON))\ndef test_apikey_detect_livemode_and_type(\n    fileupload_retrieve_mock, account_retrieve_mock\n):\n    keys_and_values = (\n        (PK_TEST, False, APIKeyType.publishable),\n        (RK_TEST, False, APIKeyType.restricted),\n        (SK_TEST, False, APIKeyType.secret),\n        (PK_LIVE, True, APIKeyType.publishable),\n        (RK_LIVE, True, APIKeyType.restricted),\n        (SK_LIVE, True, APIKeyType.secret),\n    )\n    for secret, livemode, type in keys_and_values:\n        # need to use ModelAdmin Form to create the APIKey instance\n        form = APIKeyAdminCreateForm(\n            data={\"secret\": secret},\n        )\n        form.save()\n\n        key = form.instance\n\n        assert key.livemode is livemode\n        assert key.type is type\n\n\nclass APIKeyTest(TestCase):\n    def setUp(self):\n        # create a Stripe Platform Account\n        self.account = FAKE_PLATFORM_ACCOUNT.create()\n\n        self.apikey_test = APIKey.objects.create(\n            type=APIKeyType.secret,\n            name=\"Test Secret Key\",\n            secret=SK_TEST,\n            livemode=False,\n            djstripe_owner_account=self.account,\n        )\n        self.apikey_live = APIKey.objects.create(\n            type=APIKeyType.secret,\n            name=\"Live Secret Key\",\n            secret=SK_LIVE,\n            livemode=True,\n            djstripe_owner_account=self.account,\n        )\n\n    def test_get_stripe_dashboard_url(self):\n        self.assertEqual(\n            self.apikey_test.get_stripe_dashboard_url(),\n            \"https://dashboard.stripe.com/acct_1Fg9jUA3kq9o1aTc/test/apikeys\",\n        )\n        self.assertEqual(\n            self.apikey_live.get_stripe_dashboard_url(),\n            \"https://dashboard.stripe.com/acct_1Fg9jUA3kq9o1aTc/apikeys\",\n        )\n\n    def test___str__(self):\n        assert str(self.apikey_live) == \"Live Secret Key\"\n        assert str(self.apikey_test) == \"Test Secret Key\"\n\n        # update name of apikey_live to \"\"\n        self.apikey_live.name = \"\"\n        self.apikey_live.save()\n\n        assert str(self.apikey_live) == \"sk_live_...5678\"\n\n    def test_secret_redacted(self):\n        self.assertEqual(self.apikey_test.secret_redacted, \"sk_test_...1234\")\n        self.assertEqual(self.apikey_live.secret_redacted, \"sk_live_...5678\")\n\n    def test_secret_not_in_str(self):\n        assert self.apikey_test.secret not in str(self.apikey_test)\n        assert self.apikey_live.secret not in str(self.apikey_live)\n\n    def test_get_account_by_api_key(self):\n        account = Account.get_or_retrieve_for_api_key(self.apikey_test.secret)\n        assert account == self.account\n\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        side_effect=[deepcopy(FAKE_FILEUPLOAD_ICON), deepcopy(FAKE_FILEUPLOAD_LOGO)],\n        autospec=True,\n    )\n    def test_refresh_account(self, fileupload_retrieve_mock, account_retrieve_mock):\n        # remove djstripe_owner_account field\n        self.apikey_test.djstripe_owner_account = None\n        self.apikey_test.save()\n\n        # invoke refresh_Account()\n        self.apikey_test.refresh_account()\n        assert self.apikey_test.djstripe_owner_account.id == FAKE_PLATFORM_ACCOUNT[\"id\"]\n"
  },
  {
    "path": "tests/test_balance_transaction.py",
    "content": "\"\"\"\ndj-stripe BalanceTransaction model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nfrom django.test.testcases import TestCase\n\nfrom djstripe import models\nfrom djstripe.enums import BalanceTransactionStatus\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PLAN,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestBalanceTransactionStr:\n    @pytest.mark.parametrize(\"transaction_status\", BalanceTransactionStatus.__members__)\n    def test___str__(self, transaction_status):\n        modified_balance_transaction = deepcopy(FAKE_BALANCE_TRANSACTION)\n        modified_balance_transaction[\"status\"] = transaction_status\n\n        balance_transaction = models.BalanceTransaction.sync_from_stripe_data(\n            modified_balance_transaction\n        )\n        assert (\n            str(balance_transaction)\n            == f\"$20.00 USD ({BalanceTransactionStatus.humanize(modified_balance_transaction['status'])})\"\n        )\n\n\nclass TestBalanceTransactionSourceClass:\n    @pytest.mark.parametrize(\"transaction_type\", [\"card\", \"payout\", \"refund\"])\n    def test_get_source_class_success(self, transaction_type):\n        modified_balance_transaction = deepcopy(FAKE_BALANCE_TRANSACTION)\n        modified_balance_transaction[\"type\"] = transaction_type\n\n        balance_transaction = models.BalanceTransaction.sync_from_stripe_data(\n            modified_balance_transaction\n        )\n        assert balance_transaction.get_source_class() is getattr(\n            models, transaction_type.capitalize(), None\n        )\n\n    @pytest.mark.parametrize(\"transaction_type\", [\"network_cost\", \"payment_refund\"])\n    def test_get_source_class_failure(self, transaction_type):\n        modified_balance_transaction = deepcopy(FAKE_BALANCE_TRANSACTION)\n        modified_balance_transaction[\"type\"] = transaction_type\n\n        balance_transaction = models.BalanceTransaction.sync_from_stripe_data(\n            modified_balance_transaction\n        )\n        with pytest.raises(LookupError):\n            balance_transaction.get_source_class()\n\n\nclass TestBalanceTransaction(TestCase):\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_CHARGE),\n    )\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data(\n        self,\n        subscription_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        charge_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        customer_retrieve_mock,\n        invoice_retrieve_mock,\n    ):\n        balance_transaction = models.BalanceTransaction.sync_from_stripe_data(\n            deepcopy(FAKE_BALANCE_TRANSACTION)\n        )\n\n        balance_transaction_retrieve_mock.assert_not_called()\n\n        assert balance_transaction.type == FAKE_BALANCE_TRANSACTION[\"type\"]\n        assert balance_transaction.amount == FAKE_BALANCE_TRANSACTION[\"amount\"]\n        assert balance_transaction.status == FAKE_BALANCE_TRANSACTION[\"status\"]\n\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_CHARGE),\n    )\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test_get_source_instance(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        charge_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        customer_retrieve_mock,\n        invoice_retrieve_mock,\n        invoiceitem_retrieve_mock,\n    ):\n        balance_transaction = models.BalanceTransaction.sync_from_stripe_data(\n            deepcopy(FAKE_BALANCE_TRANSACTION)\n        )\n        charge = models.Charge.sync_from_stripe_data(deepcopy(FAKE_CHARGE))\n        assert balance_transaction.get_source_instance() == charge\n\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_CHARGE),\n    )\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test_get_stripe_dashboard_url(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        charge_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        customer_retrieve_mock,\n        invoice_retrieve_mock,\n        invoiceitem_retrieve_mock,\n    ):\n        balance_transaction = models.BalanceTransaction.sync_from_stripe_data(\n            deepcopy(FAKE_BALANCE_TRANSACTION)\n        )\n        charge = models.Charge.sync_from_stripe_data(deepcopy(FAKE_CHARGE))\n        assert (\n            balance_transaction.get_stripe_dashboard_url()\n            == charge.get_stripe_dashboard_url()\n        )\n"
  },
  {
    "path": "tests/test_bank_account.py",
    "content": "\"\"\"\ndj-stripe Bank Account Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nimport stripe\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase\n\nfrom djstripe import enums\nfrom djstripe.exceptions import StripeObjectManipulationException\nfrom djstripe.models import BankAccount, Customer\n\nfrom . import (\n    FAKE_BANK_ACCOUNT_IV,\n    FAKE_BANK_ACCOUNT_SOURCE,\n    FAKE_CUSTOM_ACCOUNT,\n    FAKE_CUSTOMER_IV,\n    FAKE_STANDARD_ACCOUNT,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestStrBankAccount:\n    @pytest.mark.parametrize(\n        \"fake_stripe_data, has_account, has_customer\",\n        [\n            (deepcopy(FAKE_BANK_ACCOUNT_IV), True, False),\n            (deepcopy(FAKE_BANK_ACCOUNT_SOURCE), False, True),\n            (deepcopy(FAKE_BANK_ACCOUNT_IV), False, False),\n        ],\n    )\n    def test__str__(self, fake_stripe_data, has_account, has_customer, monkeypatch):\n        def mock_customer_get(*args, **kwargs):\n            data = deepcopy(FAKE_CUSTOMER_IV)\n            data[\"default_source\"] = None\n            data[\"sources\"] = []\n            return data\n\n        def mock_account_get(*args, **kwargs):\n            return deepcopy(FAKE_CUSTOM_ACCOUNT)\n\n        # monkeypatch stripe.Account.retrieve and stripe.Customer.retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Account, \"retrieve\", mock_account_get)\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n\n        bankaccount = BankAccount.sync_from_stripe_data(fake_stripe_data)\n        default = False\n\n        if has_account:\n            default = fake_stripe_data[\"default_for_currency\"]\n            assert (\n                f\"{fake_stripe_data['bank_name']} {fake_stripe_data['currency']} {'Default' if default else ''} {fake_stripe_data['routing_number']} {fake_stripe_data['last4']}\"\n                == str(bankaccount)\n            )\n        if has_customer:\n            customer = Customer.objects.filter(id=fake_stripe_data[\"customer\"]).first()\n\n            default_source = customer.default_source\n            default_payment_method = customer.default_payment_method\n\n            if (\n                default_payment_method\n                and fake_stripe_data[\"id\"] == default_payment_method.id\n            ) or (default_source and fake_stripe_data[\"id\"] == default_source.id):\n                # current bankaccount is the default payment method or source\n                default = True\n\n            assert (\n                f\"{fake_stripe_data['bank_name']} {fake_stripe_data['routing_number']} ({bankaccount.human_readable_status}) {'Default' if default else ''} {fake_stripe_data['currency']}\"\n                == str(bankaccount)\n            )\n        if not has_account and not has_customer:\n            # ensure account and customer do not exist\n            fake_stripe_data_2 = deepcopy(fake_stripe_data)\n            fake_stripe_data_2[\"account\"] = None\n            fake_stripe_data_2[\"customer\"] = None\n\n            bankaccount = BankAccount.sync_from_stripe_data(fake_stripe_data_2)\n            default = fake_stripe_data_2[\"default_for_currency\"]\n            assert (\n                f\"{fake_stripe_data_2['bank_name']} {fake_stripe_data_2['currency']} {'Default' if default else ''} {fake_stripe_data_2['routing_number']} {fake_stripe_data_2['last4']}\"\n                == str(bankaccount)\n            )\n\n    @pytest.mark.parametrize(\n        \"fake_stripe_data\",\n        [\n            deepcopy(FAKE_BANK_ACCOUNT_IV),\n            deepcopy(FAKE_BANK_ACCOUNT_SOURCE),\n        ],\n    )\n    def test_human_readable_status(self, fake_stripe_data, monkeypatch):\n        def mock_customer_get(*args, **kwargs):\n            data = deepcopy(FAKE_CUSTOMER_IV)\n            data[\"default_source\"] = None\n            data[\"sources\"] = []\n            return data\n\n        def mock_account_get(*args, **kwargs):\n            return deepcopy(FAKE_CUSTOM_ACCOUNT)\n\n        # monkeypatch stripe.Account.retrieve and stripe.Customer.retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Account, \"retrieve\", mock_account_get)\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n\n        bankaccount = BankAccount.sync_from_stripe_data(fake_stripe_data)\n\n        if fake_stripe_data[\"status\"] == \"new\":\n            assert bankaccount.human_readable_status == \"Pending Verification\"\n        else:\n            assert (\n                bankaccount.human_readable_status\n                == enums.BankAccountStatus.humanize(fake_stripe_data[\"status\"])\n            )\n\n\nclass BankAccountTest(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        # create a Standard Stripe Account\n        self.standard_account = FAKE_STANDARD_ACCOUNT.create()\n\n        # create a Custom Stripe Account\n        self.custom_account = FAKE_CUSTOM_ACCOUNT.create()\n\n        user = get_user_model().objects.create_user(\n            username=\"testuser\", email=\"djstripe@example.com\"\n        )\n        fake_empty_customer = deepcopy(FAKE_CUSTOMER_IV)\n        fake_empty_customer[\"default_source\"] = None\n        fake_empty_customer[\"sources\"] = []\n\n        self.customer = fake_empty_customer.create_for_user(user)\n\n    def test_attach_objects_hook_without_customer(self):\n        FAKE_BANK_ACCOUNT_DICT = deepcopy(FAKE_BANK_ACCOUNT_SOURCE)\n        FAKE_BANK_ACCOUNT_DICT[\"customer\"] = None\n\n        bank_account = BankAccount.sync_from_stripe_data(FAKE_BANK_ACCOUNT_DICT)\n        self.assertEqual(bank_account.customer, None)\n\n    def test_attach_objects_hook_without_account(self):\n        bank_account = BankAccount.sync_from_stripe_data(FAKE_BANK_ACCOUNT_SOURCE)\n        self.assertEqual(bank_account.account, None)\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_IV),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_BANK_ACCOUNT_IV),\n        autospec=True,\n    )\n    def test_api_retrieve_by_customer_equals_retrieval_by_account(\n        self, account_retrieve_external_account_mock, customer_retrieve_mock\n    ):\n        # deepcopy the BankAccount object\n        FAKE_BANK_ACCOUNT_DICT = deepcopy(FAKE_BANK_ACCOUNT_IV)\n\n        bankaccount = BankAccount.sync_from_stripe_data(FAKE_BANK_ACCOUNT_DICT)\n        bankaccount_by_customer = bankaccount.api_retrieve()\n\n        # Add account\n        FAKE_BANK_ACCOUNT_DICT[\"account\"] = FAKE_CUSTOM_ACCOUNT[\"id\"]\n        FAKE_BANK_ACCOUNT_DICT[\"customer\"] = None\n\n        bankaccount = BankAccount.sync_from_stripe_data(FAKE_BANK_ACCOUNT_DICT)\n        bankaccount_by_account = bankaccount.api_retrieve()\n\n        # assert the same bankaccount object gets retrieved\n        self.assertCountEqual(bankaccount_by_customer, bankaccount_by_account)\n\n    def test_create_bank_account_finds_customer_with_account_absent(self):\n        bank_account = BankAccount.sync_from_stripe_data(FAKE_BANK_ACCOUNT_SOURCE)\n\n        self.assertEqual(self.customer, bank_account.customer)\n        self.assertEqual(\n            bank_account.get_stripe_dashboard_url(),\n            self.customer.get_stripe_dashboard_url(),\n        )\n\n        self.assert_fks(\n            bank_account,\n            expected_blank_fks={\n                \"djstripe.BankAccount.account\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.default_source\",\n                \"djstripe.Customer.coupon\",\n            },\n        )\n\n    def test_create_bank_account_finds_customer_with_account_present(self):\n        FAKE_BANK_ACCOUNT_DICT = deepcopy(FAKE_BANK_ACCOUNT_SOURCE)\n        FAKE_BANK_ACCOUNT_DICT[\"account\"] = self.standard_account.id\n\n        bank_account = BankAccount.sync_from_stripe_data(FAKE_BANK_ACCOUNT_DICT)\n\n        self.assertEqual(self.customer, bank_account.customer)\n        self.assertEqual(self.standard_account, bank_account.account)\n        self.assertEqual(\n            bank_account.get_stripe_dashboard_url(),\n            self.customer.get_stripe_dashboard_url(),\n        )\n\n        self.assert_fks(\n            bank_account,\n            expected_blank_fks={\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.default_source\",\n                \"djstripe.Customer.coupon\",\n            },\n        )\n\n    def test_create_bank_account_finds_account_with_customer_absent(self):\n        FAKE_BANK_ACCOUNT_DICT = deepcopy(FAKE_BANK_ACCOUNT_SOURCE)\n        FAKE_BANK_ACCOUNT_DICT[\"account\"] = self.standard_account.id\n        FAKE_BANK_ACCOUNT_DICT[\"customer\"] = None\n\n        bank_account = BankAccount.sync_from_stripe_data(FAKE_BANK_ACCOUNT_DICT)\n\n        self.assertEqual(self.standard_account, bank_account.account)\n        self.assertEqual(\n            bank_account.get_stripe_dashboard_url(),\n            f\"https://dashboard.stripe.com/{bank_account.account.id}/settings/payouts\",\n        )\n\n        self.assert_fks(\n            bank_account,\n            expected_blank_fks={\n                \"djstripe.BankAccount.customer\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.default_source\",\n                \"djstripe.Customer.coupon\",\n            },\n        )\n\n    def test_api_call_no_customer_and_no_account(self):\n        exception_message = (\n            \"BankAccount objects must be manipulated through either a Stripe Connected Account or a customer. \"\n            \"Pass a Customer or an Account object into this call.\"\n        )\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            BankAccount._api_create()\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            BankAccount.api_list()\n\n    def test_api_call_bad_customer(self):\n        exception_message = (\n            \"BankAccount objects must be manipulated through a Customer. \"\n            \"Pass a Customer object into this call.\"\n        )\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            BankAccount._api_create(customer=\"fish\")\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            BankAccount.api_list(customer=\"fish\")\n\n    def test_api_call_bad_account(self):\n        exception_message = (\n            \"BankAccount objects must be manipulated through a Stripe Connected Account. \"\n            \"Pass an Account object into this call.\"\n        )\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            BankAccount._api_create(account=\"fish\")\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            BankAccount.api_list(account=\"fish\")\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_IV),\n        autospec=True,\n    )\n    def test__api_create_with_account_absent(self, customer_retrieve_mock):\n        stripe_bank_account = BankAccount._api_create(\n            customer=self.customer, source=FAKE_BANK_ACCOUNT_SOURCE[\"id\"]\n        )\n\n        self.assertEqual(FAKE_BANK_ACCOUNT_SOURCE, stripe_bank_account)\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_IV),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOM_ACCOUNT),\n        autospec=True,\n    )\n    def test__api_create_with_customer_and_account(\n        self, account_retrieve_mock, customer_retrieve_mock\n    ):\n        FAKE_BANK_ACCOUNT_DICT = deepcopy(FAKE_BANK_ACCOUNT_SOURCE)\n        FAKE_BANK_ACCOUNT_DICT[\"account\"] = FAKE_CUSTOM_ACCOUNT[\"id\"]\n\n        stripe_bank_account = BankAccount._api_create(\n            account=self.custom_account,\n            customer=self.customer,\n            source=FAKE_BANK_ACCOUNT_DICT[\"id\"],\n        )\n\n        self.assertEqual(FAKE_BANK_ACCOUNT_SOURCE, stripe_bank_account)\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_IV),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOM_ACCOUNT),\n        autospec=True,\n    )\n    def test__api_create_with_customer_absent(\n        self, account_retrieve_mock, customer_retrieve_mock\n    ):\n        stripe_bank_account = BankAccount._api_create(\n            account=self.custom_account, source=FAKE_BANK_ACCOUNT_IV[\"id\"]\n        )\n\n        self.assertEqual(FAKE_BANK_ACCOUNT_IV, stripe_bank_account)\n\n    @patch(\n        \"stripe.Customer.delete_source\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BankAccount.retrieve\",\n        return_value=deepcopy(FAKE_BANK_ACCOUNT_SOURCE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_IV),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve_source\",\n        return_value=deepcopy(FAKE_BANK_ACCOUNT_SOURCE),\n        autospec=True,\n    )\n    def test_remove_bankaccount_by_customer(\n        self,\n        customer_retrieve_source_mock,\n        customer_retrieve_mock,\n        bank_account_retrieve_mock,\n        bank_account_delete_mock,\n    ):\n        stripe_bank_account = BankAccount._api_create(\n            customer=self.customer, source=FAKE_BANK_ACCOUNT_SOURCE[\"id\"]\n        )\n        BankAccount.sync_from_stripe_data(stripe_bank_account)\n\n        self.assertEqual(\n            1, BankAccount.objects.filter(id=stripe_bank_account[\"id\"]).count()\n        )\n\n        bank_account = self.customer.bank_account.all()[0]\n        bank_account.remove()\n\n        self.assertEqual(\n            0, BankAccount.objects.filter(id=stripe_bank_account[\"id\"]).count()\n        )\n\n        api_key = bank_account.default_api_key\n        stripe_account = bank_account._get_stripe_account_id(api_key)\n\n        bank_account_delete_mock.assert_called_once_with(\n            self.customer.id,\n            bank_account.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n        )\n\n    @patch(\n        \"stripe.Account.delete_external_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOM_ACCOUNT),\n        autospec=True,\n    )\n    def test_remove_bankaccount_by_account(\n        self,\n        account_retrieve_mock,\n        bank_account_delete_mock,\n    ):\n        stripe_bank_account = BankAccount._api_create(\n            account=self.custom_account, source=FAKE_BANK_ACCOUNT_IV[\"id\"]\n        )\n        bank_account = BankAccount.sync_from_stripe_data(stripe_bank_account)\n        self.assertEqual(\n            1, BankAccount.objects.filter(id=stripe_bank_account[\"id\"]).count()\n        )\n\n        api_key = bank_account.default_api_key\n        stripe_account = bank_account._get_stripe_account_id(api_key)\n\n        assert bank_account.customer is None\n        assert bank_account.account is not None\n\n        # remove BankAccount\n        bank_account.remove()\n\n        bank_account_delete_mock.assert_called_once_with(\n            self.custom_account.id,\n            bank_account.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n        )\n\n        self.assertEqual(\n            0, BankAccount.objects.filter(id=stripe_bank_account[\"id\"]).count()\n        )\n\n    @patch(\n        \"stripe.Account.delete_external_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOM_ACCOUNT),\n        autospec=True,\n    )\n    def test_remove_already_deleted_bankaccount_by_account(\n        self,\n        account_retrieve_mock,\n        bank_account_delete_mock,\n    ):\n        stripe_bank_account = BankAccount._api_create(\n            account=self.custom_account, source=FAKE_BANK_ACCOUNT_IV[\"id\"]\n        )\n        bank_account = BankAccount.sync_from_stripe_data(stripe_bank_account)\n        self.assertEqual(\n            1, BankAccount.objects.filter(id=stripe_bank_account[\"id\"]).count()\n        )\n\n        api_key = bank_account.default_api_key\n        stripe_account = bank_account._get_stripe_account_id(api_key)\n\n        assert bank_account.customer is None\n        assert bank_account.account is not None\n\n        # remove BankAccount\n        bank_account.remove()\n        self.assertEqual(\n            0, BankAccount.objects.filter(id=stripe_bank_account[\"id\"]).count()\n        )\n        bank_account_delete_mock.assert_called_once_with(\n            self.custom_account.id,\n            bank_account.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n        )\n\n        # remove BankAccount again\n        count, _ = BankAccount.objects.filter(id=stripe_bank_account[\"id\"]).delete()\n        self.assertEqual(0, count)\n\n    @patch(\n        \"stripe.Customer.delete_source\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_IV),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve_source\",\n        return_value=deepcopy(FAKE_BANK_ACCOUNT_SOURCE),\n        autospec=True,\n    )\n    def test_remove_already_deleted_bank_account(\n        self,\n        customer_retrieve_source_mock,\n        customer_retrieve_mock,\n        bank_account_delete_mock,\n    ):\n        stripe_bank_account = BankAccount._api_create(\n            customer=self.customer, source=FAKE_BANK_ACCOUNT_SOURCE[\"id\"]\n        )\n        BankAccount.sync_from_stripe_data(stripe_bank_account)\n\n        self.assertEqual(self.customer.bank_account.count(), 1)\n        bank_account_object = self.customer.bank_account.first()\n        BankAccount.objects.filter(id=stripe_bank_account[\"id\"]).delete()\n        self.assertEqual(self.customer.bank_account.count(), 0)\n        bank_account_object.remove()\n        self.assertEqual(self.customer.bank_account.count(), 0)\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_IV),\n        autospec=True,\n    )\n    def test_api_list(self, customer_retrieve_mock):\n        bank_account_list = BankAccount.api_list(customer=self.customer)\n\n        self.assertCountEqual(\n            [FAKE_BANK_ACCOUNT_SOURCE], [i for i in bank_account_list]\n        )\n"
  },
  {
    "path": "tests/test_card.py",
    "content": "\"\"\"\ndj-stripe Card Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import ANY, patch\n\nimport pytest\nimport stripe\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase\nfrom stripe.error import InvalidRequestError\n\nfrom djstripe import enums\nfrom djstripe.exceptions import StripeObjectManipulationException\nfrom djstripe.models import Account, Card, Customer\n\nfrom . import (\n    FAKE_CARD,\n    FAKE_CARD_III,\n    FAKE_CARD_IV,\n    FAKE_CUSTOM_ACCOUNT,\n    FAKE_CUSTOMER,\n    FAKE_STANDARD_ACCOUNT,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestStrCard:\n    @pytest.mark.parametrize(\n        \"fake_stripe_data, has_account, has_customer\",\n        [\n            (deepcopy(FAKE_CARD), False, True),\n            (deepcopy(FAKE_CARD_IV), True, False),\n        ],\n    )\n    def test__str__(self, fake_stripe_data, has_account, has_customer, monkeypatch):\n        def mock_customer_get(*args, **kwargs):\n            data = deepcopy(FAKE_CUSTOMER)\n            data[\"default_source\"] = None\n            data[\"sources\"] = []\n            return data\n\n        def mock_account_get(*args, **kwargs):\n            return deepcopy(FAKE_CUSTOM_ACCOUNT)\n\n        # monkeypatch stripe.Account.retrieve and stripe.Customer.retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Account, \"retrieve\", mock_account_get)\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n\n        card = Card.sync_from_stripe_data(fake_stripe_data)\n        default = False\n\n        if has_account:\n            account = Account.objects.filter(id=fake_stripe_data[\"account\"]).first()\n\n            default = fake_stripe_data[\"default_for_currency\"]\n            assert (\n                f\"{enums.CardBrand.humanize(fake_stripe_data['brand'])} {account.default_currency} {'Default' if default else ''} {fake_stripe_data['last4']}\"\n                == str(card)\n            )\n        if has_customer:\n            customer = Customer.objects.filter(id=fake_stripe_data[\"customer\"]).first()\n\n            default_source = customer.default_source\n            default_payment_method = customer.default_payment_method\n\n            if (\n                default_payment_method\n                and fake_stripe_data[\"id\"] == default_payment_method.id\n            ) or (default_source and fake_stripe_data[\"id\"] == default_source.id):\n                # current card is the default payment method or source\n                default = True\n\n            assert (\n                f\"{enums.CardBrand.humanize(fake_stripe_data['brand'])} {fake_stripe_data['last4']} {'Default' if default else ''} Expires {fake_stripe_data['exp_month']} {fake_stripe_data['exp_year']}\"\n                == str(card)\n            )\n\n\nclass CardTest(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        # create a Standard Stripe Account\n        self.standard_account = FAKE_STANDARD_ACCOUNT.create()\n\n        # create a Custom Stripe Account\n        self.custom_account = FAKE_CUSTOM_ACCOUNT.create()\n\n        user = get_user_model().objects.create_user(\n            username=\"testuser\", email=\"djstripe@example.com\"\n        )\n        fake_empty_customer = deepcopy(FAKE_CUSTOMER)\n        fake_empty_customer[\"default_source\"] = None\n        fake_empty_customer[\"sources\"] = []\n\n        self.customer = fake_empty_customer.create_for_user(user)\n\n    def test_attach_objects_hook_without_customer(self):\n        FAKE_CARD_DICT = deepcopy(FAKE_CARD)\n        FAKE_CARD_DICT[\"customer\"] = None\n\n        card = Card.sync_from_stripe_data(FAKE_CARD_DICT)\n        self.assertEqual(card.customer, None)\n\n    def test_attach_objects_hook_without_account(self):\n        card = Card.sync_from_stripe_data(FAKE_CARD)\n        self.assertEqual(card.account, None)\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_CARD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve_source\",\n        return_value=deepcopy(FAKE_CARD),\n        autospec=True,\n    )\n    def test_api_retrieve_by_customer_equals_retrieval_by_account(\n        self,\n        customer_retrieve_source_mock,\n        account_retrieve_external_account_mock,\n        customer_retrieve_mock,\n    ):\n        # deepcopy the CardDict object\n        FAKE_CARD_DICT = deepcopy(FAKE_CARD)\n\n        card = Card.sync_from_stripe_data(deepcopy(FAKE_CARD_DICT))\n        card_by_customer = card.api_retrieve()\n\n        # Add account\n        FAKE_CARD_DICT[\"account\"] = FAKE_CUSTOM_ACCOUNT[\"id\"]\n        FAKE_CARD_DICT[\"customer\"] = None\n\n        card = Card.sync_from_stripe_data(FAKE_CARD_DICT)\n        card_by_account = card.api_retrieve()\n\n        # assert the same card object gets retrieved\n        self.assertCountEqual(card_by_customer, card_by_account)\n\n    def test_create_card_finds_customer_with_account_absent(self):\n        card = Card.sync_from_stripe_data(FAKE_CARD)\n\n        self.assertEqual(self.customer, card.customer)\n        self.assertEqual(\n            card.get_stripe_dashboard_url(), self.customer.get_stripe_dashboard_url()\n        )\n\n        self.assert_fks(\n            card,\n            expected_blank_fks={\n                \"djstripe.Card.account\",\n                \"djstripe.BankAccount.account\",\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.default_source\",\n            },\n        )\n\n    def test_create_card_finds_customer_with_account_present(self):\n        # deepcopy the CardDict object\n        FAKE_CARD_DICT = deepcopy(FAKE_CARD)\n        # Add account\n        FAKE_CARD_DICT[\"account\"] = self.standard_account.id\n\n        card = Card.sync_from_stripe_data(FAKE_CARD_DICT)\n\n        self.assertEqual(self.customer, card.customer)\n        self.assertEqual(self.standard_account, card.account)\n        self.assertEqual(\n            card.get_stripe_dashboard_url(),\n            self.customer.get_stripe_dashboard_url(),\n        )\n\n        self.assert_fks(\n            card,\n            expected_blank_fks={\n                \"djstripe.BankAccount.account\",\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.default_source\",\n            },\n        )\n\n    def test_create_card_finds_account_with_customer_absent(self):\n        # deepcopy the CardDict object\n        FAKE_CARD_DICT = deepcopy(FAKE_CARD)\n        # Add account and remove customer\n        FAKE_CARD_DICT[\"account\"] = self.standard_account.id\n        FAKE_CARD_DICT[\"customer\"] = None\n\n        card = Card.sync_from_stripe_data(FAKE_CARD_DICT)\n\n        self.assertEqual(self.standard_account, card.account)\n        self.assertEqual(\n            card.get_stripe_dashboard_url(),\n            f\"https://dashboard.stripe.com/{card.account.id}/settings/payouts\",\n        )\n\n        self.assert_fks(\n            card,\n            expected_blank_fks={\n                \"djstripe.Card.customer\",\n                \"djstripe.BankAccount.account\",\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.default_source\",\n            },\n        )\n\n    @patch(\"stripe.Token.create\", autospec=True)\n    def test_card_create_token(self, token_create_mock):\n        card = {\"number\": \"4242\", \"exp_month\": 5, \"exp_year\": 2012, \"cvc\": 445}\n        Card.create_token(**card)\n\n        token_create_mock.assert_called_with(api_key=ANY, card=card)\n\n    def test_api_call_no_customer_and_no_account(self):\n        exception_message = (\n            \"Card objects must be manipulated through either a Stripe Connected Account or a customer. \"\n            \"Pass a Customer or an Account object into this call.\"\n        )\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            Card._api_create()\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            Card.api_list()\n\n    def test_api_call_bad_customer(self):\n        exception_message = (\n            \"Card objects must be manipulated through a Customer. \"\n            \"Pass a Customer object into this call.\"\n        )\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            Card._api_create(customer=\"fish\")\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            Card.api_list(customer=\"fish\")\n\n    def test_api_call_bad_account(self):\n        exception_message = (\n            \"Card objects must be manipulated through a Stripe Connected Account. \"\n            \"Pass an Account object into this call.\"\n        )\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            Card._api_create(account=\"fish\")\n\n        with self.assertRaisesMessage(\n            StripeObjectManipulationException, exception_message\n        ):\n            Card.api_list(account=\"fish\")\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test__api_create_with_account_absent(self, customer_retrieve_mock):\n        stripe_card = Card._api_create(customer=self.customer, source=FAKE_CARD[\"id\"])\n\n        self.assertEqual(FAKE_CARD, stripe_card)\n\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOM_ACCOUNT),\n        autospec=True,\n    )\n    def test__api_create_with_customer_absent(self, account_retrieve_mock):\n        stripe_card = Card._api_create(\n            account=self.custom_account, source=FAKE_CARD_IV[\"id\"]\n        )\n\n        self.assertEqual(FAKE_CARD_IV, stripe_card)\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOM_ACCOUNT),\n        autospec=True,\n    )\n    def test__api_create_with_customer_and_account(\n        self, account_retrieve_mock, customer_retrieve_mock\n    ):\n        FAKE_CARD_DICT = deepcopy(FAKE_CARD)\n        FAKE_CARD_DICT[\"account\"] = FAKE_CUSTOM_ACCOUNT[\"id\"]\n\n        stripe_card = Card._api_create(\n            account=self.custom_account,\n            customer=self.customer,\n            source=FAKE_CARD_DICT[\"id\"],\n        )\n\n        self.assertEqual(FAKE_CARD, stripe_card)\n\n    @patch(\n        \"stripe.Customer.delete_source\",\n        autospec=True,\n    )\n    @patch(\"stripe.Card.retrieve\", return_value=deepcopy(FAKE_CARD), autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve_source\",\n        return_value=deepcopy(FAKE_CARD),\n        autospec=True,\n    )\n    def test_remove_card_by_customer(\n        self,\n        customer_retrieve_source_mock,\n        customer_retrieve_mock,\n        card_retrieve_mock,\n        card_delete_mock,\n    ):\n        stripe_card = Card._api_create(customer=self.customer, source=FAKE_CARD[\"id\"])\n        Card.sync_from_stripe_data(stripe_card)\n\n        self.assertEqual(1, self.customer.legacy_cards.count())\n\n        # remove card\n        card = self.customer.legacy_cards.all()[0]\n        card.remove()\n\n        self.assertEqual(0, self.customer.legacy_cards.count())\n        api_key = card.default_api_key\n        stripe_account = card._get_stripe_account_id(api_key)\n\n        card_delete_mock.assert_called_once_with(\n            self.customer.id, card.id, api_key=api_key, stripe_account=stripe_account\n        )\n\n    @patch(\n        \"stripe.Account.delete_external_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOM_ACCOUNT),\n        autospec=True,\n    )\n    def test_remove_card_by_account(self, account_retrieve_mock, card_delete_mock):\n        stripe_card = Card._api_create(\n            account=self.custom_account, source=FAKE_CARD_IV[\"id\"]\n        )\n        card = Card.sync_from_stripe_data(stripe_card)\n        self.assertEqual(1, Card.objects.filter(id=stripe_card[\"id\"]).count())\n\n        # remove card\n        card.remove()\n\n        self.assertEqual(0, Card.objects.filter(id=stripe_card[\"id\"]).count())\n\n        api_key = card.default_api_key\n        stripe_account = card._get_stripe_account_id(api_key)\n\n        card_delete_mock.assert_called_once_with(\n            self.custom_account.id,\n            card.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n        )\n\n    @patch(\n        \"stripe.Account.delete_external_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOM_ACCOUNT),\n        autospec=True,\n    )\n    def test_remove_already_deleted_card_by_account(\n        self, account_retrieve_mock, card_delete_mock\n    ):\n        stripe_card = Card._api_create(\n            account=self.custom_account, source=FAKE_CARD_IV[\"id\"]\n        )\n        card = Card.sync_from_stripe_data(stripe_card)\n        self.assertEqual(1, Card.objects.filter(id=stripe_card[\"id\"]).count())\n\n        # remove card\n        card.remove()\n        self.assertEqual(0, Card.objects.filter(id=stripe_card[\"id\"]).count())\n\n        # remove card again\n        count, _ = Card.objects.filter(id=stripe_card[\"id\"]).delete()\n        self.assertEqual(0, count)\n\n        api_key = card.default_api_key\n        stripe_account = card._get_stripe_account_id(api_key)\n\n        card_delete_mock.assert_called_once_with(\n            self.custom_account.id,\n            card.id,\n            api_key=api_key,\n            stripe_account=stripe_account,\n        )\n\n    @patch(\n        \"stripe.Customer.delete_source\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve_source\",\n        return_value=deepcopy(FAKE_CARD),\n        autospec=True,\n    )\n    def test_remove_already_deleted_card(\n        self,\n        customer_retrieve_source_mock,\n        customer_retrieve_mock,\n        card_delete_mock,\n    ):\n        stripe_card = Card._api_create(customer=self.customer, source=FAKE_CARD[\"id\"])\n        Card.sync_from_stripe_data(stripe_card)\n\n        self.assertEqual(self.customer.legacy_cards.count(), 1)\n        card_object = self.customer.legacy_cards.first()\n        Card.objects.filter(id=stripe_card[\"id\"]).delete()\n        self.assertEqual(self.customer.legacy_cards.count(), 0)\n        card_object.remove()\n        self.assertEqual(self.customer.legacy_cards.count(), 0)\n\n    @patch(\"djstripe.models.Card._api_delete\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_remove_no_such_source(self, customer_retrieve_mock, card_delete_mock):\n        stripe_card = Card._api_create(customer=self.customer, source=FAKE_CARD[\"id\"])\n        Card.sync_from_stripe_data(stripe_card)\n\n        card_delete_mock.side_effect = InvalidRequestError(\"No such source:\", \"blah\")\n\n        self.assertEqual(1, self.customer.legacy_cards.count())\n\n        card = self.customer.legacy_cards.all()[0]\n        card.remove()\n\n        self.assertEqual(0, self.customer.legacy_cards.count())\n        self.assertTrue(card_delete_mock.called)\n\n    @patch(\"djstripe.models.Card._api_delete\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_remove_no_such_customer(self, customer_retrieve_mock, card_delete_mock):\n        stripe_card = Card._api_create(customer=self.customer, source=FAKE_CARD[\"id\"])\n        Card.sync_from_stripe_data(stripe_card)\n\n        card_delete_mock.side_effect = InvalidRequestError(\"No such customer:\", \"blah\")\n\n        self.assertEqual(1, self.customer.legacy_cards.count())\n\n        card = self.customer.legacy_cards.all()[0]\n        card.remove()\n\n        self.assertEqual(0, self.customer.legacy_cards.count())\n        self.assertTrue(card_delete_mock.called)\n\n    @patch(\"djstripe.models.Card._api_delete\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_remove_unexpected_exception(\n        self, customer_retrieve_mock, card_delete_mock\n    ):\n        stripe_card = Card._api_create(customer=self.customer, source=FAKE_CARD[\"id\"])\n        Card.sync_from_stripe_data(stripe_card)\n\n        card_delete_mock.side_effect = InvalidRequestError(\n            \"Unexpected Exception\", \"blah\"\n        )\n\n        self.assertEqual(1, self.customer.legacy_cards.count())\n\n        card = self.customer.legacy_cards.all()[0]\n\n        with self.assertRaisesMessage(InvalidRequestError, \"Unexpected Exception\"):\n            card.remove()\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_api_list(self, customer_retrieve_mock):\n        card_list = Card.api_list(customer=self.customer)\n\n        self.assertCountEqual([FAKE_CARD, FAKE_CARD_III], [i for i in card_list])\n"
  },
  {
    "path": "tests/test_charge.py",
    "content": "\"\"\"\ndj-stripe Charge Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom decimal import Decimal\nfrom unittest.mock import call, create_autospec, patch\n\nfrom django.contrib.auth import get_user_model\nfrom django.test.testcases import TestCase\n\nfrom djstripe.enums import ChargeStatus, LegacySourceType\nfrom djstripe.models import Charge, DjstripePaymentMethod, Transfer\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_BALANCE_TRANSACTION_REFUND,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CHARGE,\n    FAKE_CHARGE_REFUNDED,\n    FAKE_CUSTOMER,\n    FAKE_FILEUPLOAD_ICON,\n    FAKE_FILEUPLOAD_LOGO,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PLAN,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_PRODUCT,\n    FAKE_REFUND,\n    FAKE_STANDARD_ACCOUNT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_TRANSFER,\n    AssertStripeFksMixin,\n)\n\n\nclass ChargeTest(AssertStripeFksMixin, TestCase):\n    @classmethod\n    def setUp(self):\n        # create a Stripe Platform Account\n        self.account = FAKE_PLATFORM_ACCOUNT.create()\n\n        user = get_user_model().objects.create_user(\n            username=\"testuser\", email=\"djstripe@example.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(user)\n\n        self.default_expected_blank_fks = {\n            \"djstripe.Charge.application_fee\",\n            \"djstripe.Charge.dispute\",\n            \"djstripe.Charge.latest_upcominginvoice (related name)\",\n            \"djstripe.Charge.on_behalf_of\",\n            \"djstripe.Charge.source_transfer\",\n            \"djstripe.Charge.transfer\",\n            \"djstripe.Customer.coupon\",\n            \"djstripe.Customer.default_payment_method\",\n            \"djstripe.Invoice.default_payment_method\",\n            \"djstripe.Invoice.default_source\",\n            \"djstripe.PaymentIntent.on_behalf_of\",\n            \"djstripe.PaymentIntent.payment_method\",\n            \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            \"djstripe.Product.default_price\",\n            \"djstripe.Subscription.default_payment_method\",\n            \"djstripe.Subscription.default_source\",\n            \"djstripe.Subscription.pending_setup_intent\",\n            \"djstripe.Subscription.schedule\",\n        }\n\n    def test___str__(self):\n        charge = Charge(\n            amount=50,\n            currency=\"usd\",\n            id=\"ch_test\",\n            status=ChargeStatus.failed,\n            captured=False,\n            paid=False,\n        )\n        self.assertEqual(str(charge), \"$50.00 USD (Uncaptured)\")\n\n        charge.captured = True\n        self.assertEqual(str(charge), \"$50.00 USD (Failed)\")\n        charge.status = ChargeStatus.succeeded\n\n        charge.disputed = True\n        self.assertEqual(str(charge), \"$50.00 USD (Disputed)\")\n\n        charge.disputed = False\n        charge.refunded = True\n        charge.amount_refunded = 50\n        self.assertEqual(str(charge), \"$50.00 USD (Refunded)\")\n\n        charge.refunded = False\n        charge.amount_refunded = 0\n        self.assertEqual(str(charge), \"$50.00 USD (Succeeded)\")\n\n        charge.status = ChargeStatus.pending\n        self.assertEqual(str(charge), \"$50.00 USD (Pending)\")\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.PaymentIntent.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    def test_capture_charge(\n        self,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        charge_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_no_invoice = deepcopy(FAKE_CHARGE)\n        fake_charge_no_invoice.update({\"invoice\": None})\n\n        charge_retrieve_mock.return_value = fake_charge_no_invoice\n\n        # TODO - I think this is needed in line with above?\n        fake_payment_intent_no_invoice = deepcopy(FAKE_PAYMENT_INTENT_I)\n        fake_payment_intent_no_invoice.update({\"invoice\": None})\n\n        payment_intent_retrieve_mock.return_value = fake_payment_intent_no_invoice\n\n        charge, created = Charge._get_or_create_from_stripe_object(\n            fake_charge_no_invoice\n        )\n        self.assertTrue(created)\n\n        captured_charge = charge.capture()\n        self.assertTrue(captured_charge.captured)\n\n        self.assertFalse(captured_charge.fraudulent)\n\n        self.assert_fks(\n            charge,\n            expected_blank_fks=self.default_expected_blank_fks\n            | {\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n                \"djstripe.Charge.invoice\",\n                \"djstripe.Charge.latest_invoice (related name)\",\n                \"djstripe.Invoice.charge\",\n                \"djstripe.PaymentIntent.invoice (related name)\",\n                \"djstripe.Plan.product\",\n            },\n        )\n\n    @patch(\"djstripe.models.Account.get_default_account\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        customer_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n\n        charge = Charge.sync_from_stripe_data(fake_charge_copy)\n\n        self.assertEqual(Decimal(\"20\"), charge.amount)\n        self.assertEqual(True, charge.paid)\n        self.assertEqual(False, charge.refunded)\n        self.assertEqual(True, charge.captured)\n        self.assertEqual(False, charge.disputed)\n        self.assertEqual(\"Subscription creation\", charge.description)\n        self.assertEqual(0, charge.amount_refunded)\n\n        self.assertEqual(self.customer.default_source.id, charge.source_id)\n        self.assertEqual(charge.source.type, LegacySourceType.card)\n\n        self.assertGreater(len(charge.receipt_url), 1)\n        self.assertTrue(charge.payment_method_details[\"type\"])\n\n        charge_retrieve_mock.assert_not_called()\n        balance_transaction_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[],\n            id=FAKE_BALANCE_TRANSACTION[\"id\"],\n            stripe_account=None,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        self.assert_fks(\n            charge,\n            expected_blank_fks=self.default_expected_blank_fks\n            | {\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n            },\n        )\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data_refunded_on_update(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        charge_retrieve_mock,\n        default_account_mock,\n        customer_retrieve_mock,\n    ):\n        # first sync charge (as per test_sync_from_stripe_data)\n        # then sync refunded version, to hit the update code-path instead of insert\n\n        default_account_mock.return_value = self.account\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n        # charge_retrieve_mock.return_value = fake_charge_copy\n\n        with patch(\n            \"stripe.BalanceTransaction.retrieve\",\n            return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        ):\n            charge = Charge.sync_from_stripe_data(fake_charge_copy)\n\n        self.assertEqual(Decimal(\"20\"), charge.amount)\n        self.assertEqual(True, charge.paid)\n        self.assertEqual(False, charge.refunded)\n        self.assertEqual(True, charge.captured)\n        self.assertEqual(False, charge.disputed)\n\n        self.assertEqual(len(charge.refunds.all()), 0)\n\n        fake_charge_refunded_copy = deepcopy(FAKE_CHARGE_REFUNDED)\n\n        with patch(\n            \"stripe.BalanceTransaction.retrieve\",\n            return_value=deepcopy(FAKE_BALANCE_TRANSACTION_REFUND),\n        ) as balance_transaction_retrieve_mock:\n            charge_refunded = Charge.sync_from_stripe_data(fake_charge_refunded_copy)\n\n        self.assertEqual(charge.id, charge_refunded.id)\n\n        self.assertEqual(Decimal(\"20\"), charge_refunded.amount)\n        self.assertEqual(True, charge_refunded.paid)\n        self.assertEqual(True, charge_refunded.refunded)\n        self.assertEqual(True, charge_refunded.captured)\n        self.assertEqual(False, charge_refunded.disputed)\n        self.assertEqual(\"Subscription creation\", charge_refunded.description)\n        self.assertEqual(charge_refunded.amount, charge_refunded.amount_refunded)\n\n        charge_retrieve_mock.assert_not_called()\n        balance_transaction_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[],\n            id=FAKE_BALANCE_TRANSACTION_REFUND[\"id\"],\n            stripe_account=None,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        refunds = list(charge_refunded.refunds.all())\n        self.assertEqual(len(refunds), 1)\n\n        refund = refunds[0]\n\n        self.assertEqual(refund.id, FAKE_REFUND[\"id\"])\n\n        self.assertNotEqual(\n            charge_refunded.balance_transaction.id, refund.balance_transaction.id\n        )\n        self.assertEqual(\n            charge_refunded.balance_transaction.id, FAKE_BALANCE_TRANSACTION[\"id\"]\n        )\n        self.assertEqual(\n            refund.balance_transaction.id, FAKE_BALANCE_TRANSACTION_REFUND[\"id\"]\n        )\n\n        self.assert_fks(\n            charge_refunded,\n            expected_blank_fks=self.default_expected_blank_fks\n            | {\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n            },\n        )\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        side_effect=[\n            deepcopy(FAKE_BALANCE_TRANSACTION),\n            deepcopy(FAKE_BALANCE_TRANSACTION_REFUND),\n        ],\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data_refunded(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n        customer_retrieve_mock,\n    ):\n        default_account_mock.return_value = self.account\n        fake_charge_copy = deepcopy(FAKE_CHARGE_REFUNDED)\n\n        charge = Charge.sync_from_stripe_data(fake_charge_copy)\n\n        self.assertEqual(Decimal(\"20\"), charge.amount)\n        self.assertEqual(True, charge.paid)\n        self.assertEqual(True, charge.refunded)\n        self.assertEqual(True, charge.captured)\n        self.assertEqual(False, charge.disputed)\n        self.assertEqual(\"Subscription creation\", charge.description)\n        self.assertEqual(charge.amount, charge.amount_refunded)\n\n        charge_retrieve_mock.assert_not_called()\n\n        # We expect two calls - for charge and then for charge.refunds\n        balance_transaction_retrieve_mock.assert_has_calls(\n            [\n                call(\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[],\n                    id=FAKE_BALANCE_TRANSACTION[\"id\"],\n                    stripe_account=None,\n                    stripe_version=djstripe_settings.STRIPE_API_VERSION,\n                ),\n                call(\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[],\n                    id=FAKE_BALANCE_TRANSACTION_REFUND[\"id\"],\n                    stripe_account=None,\n                    stripe_version=djstripe_settings.STRIPE_API_VERSION,\n                ),\n            ]\n        )\n\n        refunds = list(charge.refunds.all())\n        self.assertEqual(len(refunds), 1)\n\n        refund = refunds[0]\n\n        self.assertEqual(refund.id, FAKE_REFUND[\"id\"])\n\n        self.assertNotEqual(\n            charge.balance_transaction.id, refund.balance_transaction.id\n        )\n        self.assertEqual(charge.balance_transaction.id, FAKE_BALANCE_TRANSACTION[\"id\"])\n        self.assertEqual(\n            refund.balance_transaction.id, FAKE_BALANCE_TRANSACTION_REFUND[\"id\"]\n        )\n\n        self.assert_fks(\n            charge,\n            expected_blank_fks=self.default_expected_blank_fks\n            | {\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n            },\n        )\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    def test_sync_from_stripe_data_max_amount(\n        self,\n        default_account_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        customer_retrieve_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n        # https://support.stripe.com/questions/what-is-the-maximum-amount-i-can-charge-with-stripe\n        fake_charge_copy.update({\"amount\": 99999999})\n\n        charge = Charge.sync_from_stripe_data(fake_charge_copy)\n\n        self.assertEqual(Decimal(\"999999.99\"), charge.amount)\n        self.assertEqual(True, charge.paid)\n        self.assertEqual(False, charge.refunded)\n        self.assertEqual(True, charge.captured)\n        self.assertEqual(False, charge.disputed)\n        self.assertEqual(0, charge.amount_refunded)\n\n        charge_retrieve_mock.assert_not_called()\n\n        self.assert_fks(\n            charge,\n            expected_blank_fks=self.default_expected_blank_fks\n            | {\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n            },\n        )\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data_unsupported_source(\n        self,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n        customer_retrieve_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n        fake_charge_copy.update({\"source\": {\"id\": \"test_id\", \"object\": \"unsupported\"}})\n\n        charge = Charge.sync_from_stripe_data(fake_charge_copy)\n        self.assertEqual(\"test_id\", charge.source_id)\n        self.assertEqual(\"UNSUPPORTED_test_id\", charge.source.type)\n        self.assertEqual(charge.source, DjstripePaymentMethod.objects.get(id=\"test_id\"))\n\n        charge_retrieve_mock.assert_not_called()\n\n        balance_transaction_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[],\n            id=FAKE_BALANCE_TRANSACTION[\"id\"],\n            stripe_account=None,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        self.assert_fks(\n            charge,\n            expected_blank_fks=self.default_expected_blank_fks\n            | {\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n            },\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\"stripe.PaymentIntent.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data_no_customer(\n        self,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n\n        fake_charge_copy.pop(\"customer\", None)\n        # remove invoice since it requires a customer\n        fake_charge_copy.pop(\"invoice\", None)\n\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_I)\n        fake_payment_intent[\"invoice\"] = None\n\n        payment_intent_retrieve_mock.return_value = fake_payment_intent\n\n        Charge.sync_from_stripe_data(fake_charge_copy)\n        assert Charge.objects.count() == 1\n        charge = Charge.objects.get()\n        assert charge.customer is None\n\n        charge_retrieve_mock.assert_not_called()\n        balance_transaction_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[],\n            id=FAKE_BALANCE_TRANSACTION[\"id\"],\n            stripe_account=None,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        self.assert_fks(\n            charge,\n            expected_blank_fks=self.default_expected_blank_fks\n            | {\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n                \"djstripe.Charge.customer\",\n                \"djstripe.Charge.invoice\",\n                \"djstripe.Charge.latest_invoice (related name)\",\n                \"djstripe.Invoice.charge\",\n                \"djstripe.PaymentIntent.invoice (related name)\",\n                \"djstripe.Plan.product\",\n            },\n        )\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\"stripe.Transfer.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    def test_sync_from_stripe_data_with_transfer(\n        self,\n        default_account_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        transfer_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n        customer_retrieve_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_transfer = deepcopy(FAKE_TRANSFER)\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n        fake_charge_copy.update({\"transfer\": fake_transfer[\"id\"]})\n\n        transfer_retrieve_mock.return_value = fake_transfer\n        charge_retrieve_mock.return_value = fake_charge_copy\n\n        charge, created = Charge._get_or_create_from_stripe_object(\n            fake_charge_copy, current_ids={fake_charge_copy[\"id\"]}\n        )\n        self.assertTrue(created)\n\n        self.assertNotEqual(None, charge.transfer)\n        self.assertEqual(fake_transfer[\"id\"], charge.transfer.id)\n\n        charge_retrieve_mock.assert_not_called()\n        balance_transaction_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[],\n            id=FAKE_BALANCE_TRANSACTION[\"id\"],\n            stripe_account=None,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        self.assert_fks(\n            charge,\n            expected_blank_fks=(\n                self.default_expected_blank_fks\n                | {\n                    \"djstripe.Account.branding_logo\",\n                    \"djstripe.Account.branding_icon\",\n                }\n            )\n            - {\"djstripe.Charge.transfer\"},\n        )\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\"stripe.Account.retrieve\", autospec=True)\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        side_effect=[deepcopy(FAKE_FILEUPLOAD_ICON), deepcopy(FAKE_FILEUPLOAD_LOGO)],\n        autospec=True,\n    )\n    def test_sync_from_stripe_data_with_destination(\n        self,\n        file_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        account_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n    ):\n        account_retrieve_mock.return_value = FAKE_STANDARD_ACCOUNT\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n        fake_charge_copy.update({\"destination\": FAKE_STANDARD_ACCOUNT[\"id\"]})\n\n        charge, created = Charge._get_or_create_from_stripe_object(\n            fake_charge_copy, current_ids={fake_charge_copy[\"id\"]}\n        )\n        self.assertTrue(created)\n\n        charge_retrieve_mock.assert_not_called()\n        balance_transaction_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[],\n            id=FAKE_BALANCE_TRANSACTION[\"id\"],\n            stripe_account=None,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        self.assert_fks(charge, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch.object(target=Charge, attribute=\"source\", autospec=True)\n    @patch(\n        target=\"djstripe.models.payment_methods.DjstripePaymentMethod\", autospec=True\n    )\n    @patch(target=\"djstripe.models.account.Account\", autospec=True)\n    def test__attach_objects_hook_missing_source_data(\n        self, mock_account, mock_payment_method, mock_charge_source\n    ):\n        \"\"\"\n        Make sure we handle the case where the source data is empty or insufficient.\n        \"\"\"\n        charge = Charge(\n            amount=50,\n            currency=\"usd\",\n            id=\"ch_test\",\n            status=ChargeStatus.failed,\n            captured=False,\n            paid=False,\n        )\n        mock_cls = create_autospec(spec=Charge, spec_set=True)\n        # Empty data dict works for this test since we only look up the source key and\n        # everything else is mocked.\n        mock_data = {}\n        starting_source = charge.source\n\n        charge._attach_objects_hook(cls=mock_cls, data=mock_data)\n\n        # source shouldn't be touched\n        self.assertEqual(starting_source, charge.source)\n        mock_payment_method._get_or_create_source.assert_not_called()\n\n        # try again with a source key, but no object sub key.\n        mock_data = {\"source\": {\"foo\": \"bar\"}}\n\n        charge._attach_objects_hook(cls=mock_cls, data=mock_data)\n\n        # source shouldn't be touched\n        self.assertEqual(starting_source, charge.source)\n        mock_payment_method._get_or_create_source.assert_not_called()\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\"djstripe.models.Account.get_default_account\", autospec=True)\n    @patch(\"stripe.BalanceTransaction.retrieve\", autospec=True)\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test_max_size_large_charge_on_decimal_amount(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n        customer_retrieve_mock,\n    ):\n        \"\"\"\n        By contacting stripe support, some accounts will have their limit raised to 11\n        digits\n        \"\"\"\n        amount = 99999999999\n        assert len(str(amount)) == 11\n\n        fake_transaction = deepcopy(FAKE_BALANCE_TRANSACTION)\n        fake_transaction.update({\"amount\": amount})\n\n        default_account_mock.return_value = self.account\n        balance_transaction_retrieve_mock.return_value = fake_transaction\n\n        fake_charge = deepcopy(FAKE_CHARGE)\n        fake_charge.update({\"amount\": amount})\n\n        charge = Charge.sync_from_stripe_data(fake_charge)\n\n        charge_retrieve_mock.assert_not_called()\n        self.assertTrue(bool(charge.pk))\n        self.assertEqual(charge.amount, Decimal(\"999999999.99\"))\n        self.assertEqual(charge.balance_transaction.amount, 99999999999)\n"
  },
  {
    "path": "tests/test_checks.py",
    "content": "# import pytest\n# from django.core.checks import Error\n# from django.test import TestCase, override_settings\n\n# # errors = checked_object.check()\n# # expected_errors = [\n# #     Error(\n# #         'an error',\n# #         hint='A hint.',\n# #         obj=checked_object,\n# #         id='myapp.E001',\n# #     )\n# # ]\n# # self.assertEqual(errors, expected_errors)\n\n\n# class TestChecks(TestCase):\n#     def foo_1(a, b, *args, **kwargs):\n#         pass\n\n#     def foo_2(a, *args):\n#         pass\n\n#     def foo_3(a, **kwargs):\n#         pass\n\n#     def foo_4(a):\n#         pass\n\n#     @override_settings(DJSTRIPE_WEBHOOK_EVENT_CALLBACK=foo_4)\n#     def test_check_webhook_event_callback_accepts_api_key(self):\n#         a = 5\n#         breakpoint()\n"
  },
  {
    "path": "tests/test_coupon.py",
    "content": "from copy import deepcopy\nfrom decimal import Decimal\n\nimport pytest\nfrom django.test.testcases import TestCase\n\nfrom djstripe.models import Coupon\n\nfrom . import FAKE_COUPON\n\npytestmark = pytest.mark.django_db\n\n\nclass TransferTest(TestCase):\n    def test_retrieve_coupon(self):\n        coupon_data = deepcopy(FAKE_COUPON)\n        coupon = Coupon.sync_from_stripe_data(coupon_data)\n        self.assertEqual(coupon.id, FAKE_COUPON[\"id\"])\n\n\nclass CouponTest(TestCase):\n    def test_blank_coupon_str(self):\n        coupon = Coupon()\n        self.assertEqual(str(coupon).strip(), \"(invalid amount) off once\")\n\n    def test___str__(self):\n        coupon = Coupon.objects.create(\n            id=\"coupon-test-amount-off-forever\",\n            amount_off=10,\n            currency=\"usd\",\n            duration=\"forever\",\n            name=\"Test coupon\",\n        )\n        self.assertEqual(str(coupon), \"Test coupon\")\n\n    def test_human_readable_usd_off_forever(self):\n        coupon = Coupon.objects.create(\n            id=\"coupon-test-amount-off-forever\",\n            amount_off=10,\n            currency=\"usd\",\n            duration=\"forever\",\n        )\n        self.assertEqual(coupon.human_readable, \"$10.00 USD off forever\")\n        self.assertEqual(str(coupon), coupon.human_readable)\n\n    def test_human_readable_eur_off_forever(self):\n        coupon = Coupon.objects.create(\n            id=\"coupon-test-amount-off-forever\",\n            amount_off=10,\n            currency=\"eur\",\n            duration=\"forever\",\n        )\n        self.assertEqual(coupon.human_readable, \"€10.00 EUR off forever\")\n        self.assertEqual(str(coupon), coupon.human_readable)\n\n    def test_human_readable_percent_off_forever(self):\n        coupon = Coupon.objects.create(\n            id=\"coupon-test-percent-off-forever\",\n            percent_off=10.25,\n            currency=\"usd\",\n            duration=\"forever\",\n        )\n        self.assertEqual(coupon.human_readable, \"10.25% off forever\")\n        self.assertEqual(str(coupon), coupon.human_readable)\n\n    def test_human_readable_percent_off_once(self):\n        coupon = Coupon.objects.create(\n            id=\"coupon-test-percent-off-once\",\n            percent_off=10.25,\n            currency=\"usd\",\n            duration=\"once\",\n        )\n        self.assertEqual(coupon.human_readable, \"10.25% off once\")\n        self.assertEqual(str(coupon), coupon.human_readable)\n\n    def test_human_readable_percent_off_one_month(self):\n        coupon = Coupon.objects.create(\n            id=\"coupon-test-percent-off-1month\",\n            percent_off=10.25,\n            currency=\"usd\",\n            duration=\"repeating\",\n            duration_in_months=1,\n        )\n        self.assertEqual(coupon.human_readable, \"10.25% off for 1 month\")\n        self.assertEqual(str(coupon), coupon.human_readable)\n\n    def test_human_readable_percent_off_three_months(self):\n        coupon = Coupon.objects.create(\n            id=\"coupon-test-percent-off-3month\",\n            percent_off=10.25,\n            currency=\"usd\",\n            duration=\"repeating\",\n            duration_in_months=3,\n        )\n        self.assertEqual(coupon.human_readable, \"10.25% off for 3 months\")\n        self.assertEqual(str(coupon), coupon.human_readable)\n\n    def test_human_readable_integer_percent_off_forever(self):\n        coupon = Coupon.objects.create(\n            id=\"coupon-test-percent-off-forever\",\n            percent_off=10,\n            currency=\"usd\",\n            duration=\"forever\",\n        )\n        self.assertEqual(coupon.human_readable, \"10% off forever\")\n        self.assertEqual(str(coupon), coupon.human_readable)\n\n\nclass TestCouponDecimal:\n    @pytest.mark.parametrize(\n        \"inputted,expected\",\n        [\n            (Decimal(\"1\"), Decimal(\"1.00\")),\n            (Decimal(\"1.5234567\"), Decimal(\"1.52\")),\n            (Decimal(\"0\"), Decimal(\"0.00\")),\n            (Decimal(\"23.2345678\"), Decimal(\"23.23\")),\n            (\"1\", Decimal(\"1.00\")),\n            (\"1.5234567\", Decimal(\"1.52\")),\n            (\"0\", Decimal(\"0.00\")),\n            (\"23.2345678\", Decimal(\"23.23\")),\n            (1, Decimal(\"1.00\")),\n            (1.5234567, Decimal(\"1.52\")),\n            (0, Decimal(\"0.00\")),\n            (23.2345678, Decimal(\"23.24\")),\n        ],\n    )\n    def test_decimal_percent_off_coupon(self, inputted, expected):\n        fake_coupon = deepcopy(FAKE_COUPON)\n        fake_coupon[\"percent_off\"] = inputted\n\n        coupon = Coupon.sync_from_stripe_data(fake_coupon)\n        field_data = coupon.percent_off\n\n        assert isinstance(field_data, Decimal)\n        assert field_data == expected\n"
  },
  {
    "path": "tests/test_customer.py",
    "content": "\"\"\"\nCustomer Model Tests.\n\"\"\"\nimport decimal\nfrom copy import deepcopy\nfrom unittest.mock import ANY, call, patch\n\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase, override_settings\nfrom django.utils import timezone\nfrom stripe.error import InvalidRequestError\n\nfrom djstripe.exceptions import MultipleSubscriptionException\nfrom djstripe.models import (\n    Card,\n    Charge,\n    Coupon,\n    Customer,\n    DjstripePaymentMethod,\n    IdempotencyKey,\n    Invoice,\n    PaymentMethod,\n    Plan,\n    Price,\n    Product,\n    Source,\n    Subscription,\n)\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CARD,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CARD_III,\n    FAKE_CHARGE,\n    FAKE_COUPON,\n    FAKE_CUSTOMER,\n    FAKE_CUSTOMER_II,\n    FAKE_CUSTOMER_III,\n    FAKE_CUSTOMER_IV,\n    FAKE_DISCOUNT_CUSTOMER,\n    FAKE_INVOICE,\n    FAKE_INVOICE_III,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PAYMENT_METHOD_I,\n    FAKE_PLAN,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_PRICE,\n    FAKE_PRODUCT,\n    FAKE_SOURCE,\n    FAKE_SOURCE_II,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_II,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_UPCOMING_INVOICE,\n    AssertStripeFksMixin,\n    StripeList,\n    datetime_to_unix,\n)\n\n\nclass TestCustomer(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        # create a Stripe Platform Account\n        self.account = FAKE_PLATFORM_ACCOUNT.create()\n\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n        self.payment_method, _ = DjstripePaymentMethod._get_or_create_source(\n            FAKE_CARD, \"card\"\n        )\n        self.card = self.payment_method.resolve()\n\n        self.customer.default_source = self.payment_method\n        self.customer.save()\n\n    def test___str__(self):\n        self.assertEqual(str(self.customer), str(self.user))\n        self.customer.subscriber = None\n        self.assertEqual(str(self.customer), self.customer.description)\n\n    def test_balance(self):\n        self.assertEqual(self.customer.balance, 0)\n        self.assertEqual(self.customer.credits, 0)\n\n        self.customer.balance = 1000\n        self.assertEqual(self.customer.balance, 1000)\n        self.assertEqual(self.customer.credits, 0)\n        self.assertEqual(self.customer.pending_charges, 1000)\n\n        self.customer.balance = -1000\n        self.assertEqual(self.customer.balance, -1000)\n        self.assertEqual(self.customer.credits, 1000)\n        self.assertEqual(self.customer.pending_charges, 0)\n\n    def test_customer_dashboard_url(self):\n        expected_url = f\"https://dashboard.stripe.com/{self.customer.djstripe_owner_account.id}/test/customers/{self.customer.id}\"\n        self.assertEqual(self.customer.get_stripe_dashboard_url(), expected_url)\n\n        self.customer.livemode = True\n        expected_url = f\"https://dashboard.stripe.com/{self.customer.djstripe_owner_account.id}/customers/{self.customer.id}\"\n        self.assertEqual(self.customer.get_stripe_dashboard_url(), expected_url)\n\n        unsaved_customer = Customer()\n        self.assertEqual(unsaved_customer.get_stripe_dashboard_url(), \"\")\n\n    def test_customer_sync_unsupported_source(self):\n        fake_customer = deepcopy(FAKE_CUSTOMER_II)\n        fake_customer[\"default_source\"][\"object\"] = fake_customer[\"sources\"][\"data\"][0][\n            \"object\"\n        ] = \"fish\"\n\n        user = get_user_model().objects.create_user(\n            username=\"test_user_sync_unsupported_source\"\n        )\n        self.assertRaisesRegex(\n            ValueError,\n            \"Trying to fit a 'fish' into 'Card'. Aborting.\",\n            fake_customer.create_for_user,\n            user,\n        )\n\n    def test_customer_sync_has_subscriber_metadata(self):\n        user = get_user_model().objects.create(username=\"test_metadata\", id=12345)\n\n        fake_customer = deepcopy(FAKE_CUSTOMER)\n        fake_customer[\"id\"] = \"cus_sync_has_subscriber_metadata\"\n        fake_customer[\"metadata\"] = {\"djstripe_subscriber\": \"12345\"}\n        customer = Customer.sync_from_stripe_data(fake_customer)\n\n        self.assertEqual(customer.subscriber, user)\n        self.assertEqual(customer.metadata, {\"djstripe_subscriber\": \"12345\"})\n\n    @override_settings(DJSTRIPE_SUBSCRIBER_CUSTOMER_KEY=\"\")\n    def test_customer_sync_has_subscriber_metadata_disabled(self):\n        user = get_user_model().objects.create(\n            username=\"test_metadata_disabled\", id=98765\n        )\n\n        fake_customer = deepcopy(FAKE_CUSTOMER)\n        fake_customer[\"id\"] = \"cus_test_metadata_disabled\"\n        fake_customer[\"metadata\"] = {\"djstripe_subscriber\": \"98765\"}\n\n        customer = Customer.sync_from_stripe_data(fake_customer)\n\n        self.assertNotEqual(customer.subscriber, user)\n        self.assertNotEqual(customer.subscriber_id, 98765)\n\n        self.assert_fks(\n            customer,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.subscriber\",\n            },\n        )\n\n    def test_customer_sync_has_bad_subscriber_metadata(self):\n        fake_customer = deepcopy(FAKE_CUSTOMER)\n        fake_customer[\"id\"] = \"cus_sync_has_bad_subscriber_metadata\"\n        fake_customer[\"metadata\"] = {\"djstripe_subscriber\": \"does_not_exist\"}\n        customer = Customer.sync_from_stripe_data(fake_customer)\n\n        self.assertEqual(customer.subscriber, None)\n        self.assertEqual(customer.metadata, {\"djstripe_subscriber\": \"does_not_exist\"})\n\n        self.assert_fks(\n            customer,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.subscriber\",\n            },\n        )\n\n    @override_settings(DJSTRIPE_SUBSCRIBER_CUSTOMER_KEY=\"\")\n    @patch(\"stripe.Customer.create\", autospec=True)\n    def test_customer_create_metadata_disabled(self, customer_mock):\n        user = get_user_model().objects.create_user(\n            username=\"test_user_create_metadata_disabled\"\n        )\n\n        fake_customer = deepcopy(FAKE_CUSTOMER)\n        fake_customer[\"id\"] = \"cus_test_create_metadata_disabled\"\n        customer_mock.return_value = fake_customer\n\n        customer = Customer.create(user)\n\n        customer_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            email=\"\",\n            idempotency_key=None,\n            metadata={},\n            stripe_account=None,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        self.assertEqual(customer.metadata, None)\n\n        self.assert_fks(\n            customer,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.default_source\",\n            },\n        )\n\n    @patch.object(Card, \"_get_or_create_from_stripe_object\")\n    @patch(\"stripe.Customer.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Card.retrieve\",\n        autospec=True,\n    )\n    def test_customer_sync_non_local_card(\n        self, card_retrieve_mock, customer_retrieve_mock, card_get_or_create_mock\n    ):\n        fake_customer = deepcopy(FAKE_CUSTOMER_II)\n        fake_customer[\"id\"] = fake_customer[\"sources\"][\"data\"][0][\n            \"customer\"\n        ] = \"cus_test_sync_non_local_card\"\n        fake_customer[\"default_source\"][\"id\"] = fake_customer[\"sources\"][\"data\"][0][\n            \"id\"\n        ] = \"card_cus_test_sync_non_local_card\"\n\n        customer_retrieve_mock.return_value = fake_customer\n\n        fake_card = deepcopy(fake_customer[\"default_source\"])\n        fake_card[\"customer\"] = \"cus_test_sync_non_local_card\"\n        card_retrieve_mock.return_value = fake_card\n        card_get_or_create_mock.return_value = fake_card\n\n        user = get_user_model().objects.create_user(\n            username=\"test_user_sync_non_local_card\"\n        )\n\n        # create a source object so that FAKE_CUSTOMER_III with a default source\n        # can be created correctly.\n        fake_source_data = deepcopy(FAKE_SOURCE_II)\n        fake_source_data[\"card\"] = deepcopy(fake_card)\n        fake_source_data[\"customer\"] = fake_customer\n\n        Source.sync_from_stripe_data(fake_source_data)\n\n        customer = fake_customer.create_for_user(user)\n\n        self.assertEqual(customer.sources.count(), 1)\n        self.assertEqual(customer.legacy_cards.count(), 0)\n        self.assertEqual(\n            customer.default_source.id, fake_customer[\"default_source\"][\"id\"]\n        )\n\n    @patch(\n        \"stripe.BankAccount.retrieve\",\n        return_value=FAKE_CUSTOMER_IV[\"default_source\"],\n        autospec=True,\n    )\n    def test_customer_sync_bank_account_source(self, bank_account_retrieve_mock):\n        fake_customer = deepcopy(FAKE_CUSTOMER_IV)\n        user = get_user_model().objects.create_user(\n            username=\"test_user_sync_bank_account_source\"\n        )\n        customer = fake_customer.create_for_user(user)\n\n        self.assertEqual(customer.deleted, False)\n        self.assertEqual(customer.sources.count(), 0)\n        self.assertEqual(customer.legacy_cards.count(), 0)\n        self.assertEqual(customer.bank_account.count(), 1)\n        self.assertEqual(\n            customer.default_source.id, fake_customer[\"default_source\"][\"id\"]\n        )\n\n        self.assert_fks(\n            customer,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n            },\n        )\n\n    @patch(\"stripe.Customer.create\", autospec=True)\n    def test_customer_sync_no_sources(self, customer_mock):\n        fake_customer = deepcopy(FAKE_CUSTOMER)\n        fake_customer[\"id\"] = \"cus_test_sync_no_sources\"\n        fake_customer[\"default_source\"] = None\n        fake_customer[\"sources\"] = None\n        customer_mock.return_value = fake_customer\n\n        user = get_user_model().objects.create_user(\n            username=\"test_user_sync_non_local_card\"\n        )\n        customer = Customer.create(user)\n        self.assertEqual(\n            customer_mock.call_args_list[0][1].get(\"metadata\"),\n            {\"djstripe_subscriber\": user.pk},\n        )\n\n        self.assertEqual(customer.sources.count(), 0)\n        self.assertEqual(customer.legacy_cards.count(), 0)\n        self.assertEqual(customer.default_source, None)\n\n        self.assert_fks(\n            customer,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.default_source\",\n            },\n        )\n\n    def test_customer_sync_default_source_string(self):\n        Customer.objects.all().delete()\n        Card.objects.all().delete()\n\n        customer_fake = deepcopy(FAKE_CUSTOMER)\n\n        customer = Customer.sync_from_stripe_data(customer_fake)\n        self.assertEqual(\n            customer.default_source.id, customer_fake[\"default_source\"][\"id\"]\n        )\n        self.assertEqual(customer.legacy_cards.count(), 2)\n        self.assertEqual(len(list(customer.customer_payment_methods)), 2)\n\n        self.assert_fks(\n            customer,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.subscriber\",\n            },\n        )\n\n    @patch(\"stripe.Customer.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\", return_value=deepcopy(FAKE_PAYMENT_METHOD_I)\n    )\n    def test_customer_sync_default_payment_method_string(\n        self, attach_mock, customer_retrieve_mock\n    ):\n        Customer.objects.all().delete()\n        PaymentMethod.objects.all().delete()\n        customer_fake = deepcopy(FAKE_CUSTOMER)\n        customer_fake[\"invoice_settings\"][\n            \"default_payment_method\"\n        ] = FAKE_PAYMENT_METHOD_I[\"id\"]\n        customer_retrieve_mock.return_value = customer_fake\n\n        customer = Customer.sync_from_stripe_data(customer_fake)\n        self.assertEqual(\n            customer.default_payment_method.id,\n            customer_fake[\"invoice_settings\"][\"default_payment_method\"],\n        )\n        self.assertEqual(customer.payment_methods.count(), 1)\n\n        self.assert_fks(\n            customer,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.subscriber\",\n            },\n        )\n\n    @patch(\"stripe.Customer.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\", return_value=deepcopy(FAKE_PAYMENT_METHOD_I)\n    )\n    def test_customer_sync_null_default_payment_method(\n        self, attach_mock, customer_retrieve_mock\n    ):\n        \"\"\"Test to make sure a custom'er default_payment_method gets updated to None\n        if they remove their only attached payment method\"\"\"\n        Customer.objects.all().delete()\n        PaymentMethod.objects.all().delete()\n\n        customer_fake = deepcopy(FAKE_CUSTOMER)\n        customer_fake[\"invoice_settings\"][\n            \"default_payment_method\"\n        ] = FAKE_PAYMENT_METHOD_I[\"id\"]\n        customer_retrieve_mock.return_value = customer_fake\n\n        customer = Customer.sync_from_stripe_data(customer_fake)\n        self.assertEqual(\n            customer.default_payment_method.id,\n            customer_fake[\"invoice_settings\"][\"default_payment_method\"],\n        )\n        self.assertEqual(customer.payment_methods.count(), 1)\n\n        self.assert_fks(\n            customer,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.subscriber\",\n            },\n        )\n\n        # update customer_retrieve_mock return value\n        customer_fake = deepcopy(FAKE_CUSTOMER)\n        customer_fake[\"invoice_settings\"][\"default_payment_method\"] = None\n        customer_retrieve_mock.return_value = customer_fake\n\n        # now detach the payment method from customer\n        is_detached = customer.default_payment_method.detach()\n        assert is_detached is True\n\n        # refresh customer from db\n        customer.refresh_from_db()\n\n        self.assertEqual(\n            customer.default_payment_method,\n            None,\n        )\n        self.assertEqual(customer.payment_methods.count(), 0)\n\n        self.assert_fks(\n            customer,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.subscriber\",\n                \"djstripe.Customer.default_payment_method\",\n            },\n        )\n\n    @patch(\n        \"stripe.Customer.delete_source\",\n        autospec=True,\n    )\n    @patch(\"stripe.Customer.delete\", autospec=True)\n    @patch(\"stripe.Customer.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve_source\",\n        side_effect=[deepcopy(FAKE_CARD), deepcopy(FAKE_CARD_III)],\n        autospec=True,\n    )\n    def test_customer_purge_leaves_customer_record(\n        self,\n        customer_retrieve_source_mock,\n        customer_retrieve_fake,\n        customer_delete_mock,\n        customer_source_delete_mock,\n    ):\n        self.customer.purge()\n        customer = Customer.objects.get(id=self.customer.id)\n\n        self.assertTrue(customer.subscriber is None)\n        self.assertTrue(customer.default_source is None)\n        self.assertTrue(customer.deleted is True)\n        self.assertTrue(not customer.legacy_cards.all())\n        self.assertTrue(not customer.sources.all())\n        self.assertTrue(get_user_model().objects.filter(pk=self.user.pk).exists())\n\n    @patch(\"stripe.Customer.create\", autospec=True)\n    def test_customer_purge_detaches_sources(\n        self,\n        customer_api_create_fake,\n    ):\n        fake_customer = deepcopy(FAKE_CUSTOMER_III)\n        customer_api_create_fake.return_value = fake_customer\n\n        user = get_user_model().objects.create_user(\n            username=\"blah\", email=FAKE_CUSTOMER_III[\"email\"]\n        )\n\n        Customer.get_or_create(user)\n        customer = Customer.sync_from_stripe_data(deepcopy(FAKE_CUSTOMER_III))\n\n        self.assertIsNotNone(customer.default_source)\n        self.assertNotEqual(customer.sources.count(), 0)\n\n        with patch(\"stripe.Customer.delete\", autospec=True), patch(\n            \"stripe.Source.retrieve\", return_value=deepcopy(FAKE_SOURCE), autospec=True\n        ):\n            customer.purge()\n\n        self.assertIsNone(customer.default_source)\n        self.assertEqual(customer.sources.count(), 0)\n\n    @patch(\n        \"stripe.Customer.create\", return_value=deepcopy(FAKE_CUSTOMER_II), autospec=True\n    )\n    def test_customer_purge_deletes_idempotency_key(self, customer_api_create_fake):\n        # We need to call Customer.get_or_create (which setUp doesn't)\n        # to get an idempotency key\n        user = get_user_model().objects.create_user(\n            username=\"blah\", email=FAKE_CUSTOMER_II[\"email\"]\n        )\n        idempotency_key_action = f\"customer:create:{user.pk}\"\n        self.assertFalse(\n            IdempotencyKey.objects.filter(action=idempotency_key_action).exists()\n        )\n\n        customer, created = Customer.get_or_create(user)\n        self.assertTrue(\n            IdempotencyKey.objects.filter(action=idempotency_key_action).exists()\n        )\n\n        with patch(\"stripe.Customer.delete\", autospec=True):\n            customer.purge()\n\n        self.assertFalse(\n            IdempotencyKey.objects.filter(action=idempotency_key_action).exists()\n        )\n\n    @patch(\n        \"stripe.Customer.delete_source\",\n        autospec=True,\n    )\n    @patch(\"stripe.Customer.delete\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\",\n        side_effect=InvalidRequestError(\"No such customer:\", \"blah\"),\n        autospec=True,\n    )\n    def test_customer_purge_raises_customer_exception(\n        self, customer_retrieve_mock, customer_delete_mock, customer_source_delete_mock\n    ):\n        self.customer.purge()\n        customer = Customer.objects.get(id=self.customer.id)\n        self.assertTrue(customer.subscriber is None)\n        self.assertTrue(customer.default_source is None)\n        self.assertTrue(not customer.legacy_cards.all())\n        self.assertTrue(not customer.sources.all())\n        self.assertTrue(get_user_model().objects.filter(pk=self.user.pk).exists())\n\n        customer_delete_mock.assert_called_once_with(\n            self.customer.id,\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_account=self.customer.djstripe_owner_account.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        self.assertEqual(0, customer_retrieve_mock.call_count)\n\n        self.assertEqual(2, customer_source_delete_mock.call_count)\n\n    @patch(\"stripe.Customer.delete_source\", autospec=True)\n    @patch(\"stripe.Customer.delete\", autospec=True)\n    @patch(\"stripe.Customer.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve_source\",\n        return_value=deepcopy(FAKE_CARD),\n        autospec=True,\n    )\n    def test_customer_delete_raises_unexpected_exception(\n        self,\n        customer_retrieve_source_mock,\n        customer_retrieve_mock,\n        customer_delete_mock,\n        customer_source_delete_mock,\n    ):\n        customer_delete_mock.side_effect = InvalidRequestError(\n            \"Unexpected Exception\", \"blah\"\n        )\n\n        with self.assertRaisesMessage(InvalidRequestError, \"Unexpected Exception\"):\n            self.customer.purge()\n\n        customer_delete_mock.assert_called_once_with(\n            self.customer.id,\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_account=self.customer.djstripe_owner_account.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_add_card_set_default_true(self, customer_retrieve_mock):\n        self.customer.add_card(FAKE_CARD[\"id\"])\n        self.customer.add_card(FAKE_CARD_III[\"id\"])\n\n        self.assertEqual(2, Card.objects.count())\n        self.assertEqual(FAKE_CARD_III[\"id\"], self.customer.default_source.id)\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_add_card_set_default_false(self, customer_retrieve_mock):\n        # self.customer already has FAKE_CARD as its default payment method\n        self.customer.add_card(FAKE_CARD_III[\"id\"], set_default=False)\n\n        self.assertEqual(2, Card.objects.count())\n        self.assertEqual(FAKE_CARD[\"id\"], self.customer.default_source.id)\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_add_card_set_default_false_with_single_card_still_becomes_default(\n        self, customer_retrieve_mock\n    ):\n        # delete all already added cards to self.customer\n        Card.objects.all().delete()\n\n        # assert self.customer has no cards\n        self.assertEqual(0, self.customer.legacy_cards.count())\n        self.assertEqual(0, self.customer.sources.count())\n\n        self.customer.add_card(FAKE_CARD[\"id\"], set_default=False)\n\n        # assert new card got added to self.customer\n        self.assertEqual(1, Card.objects.count())\n\n        # self.customer already has FAKE_CARD as its default payment method\n        self.assertEqual(FAKE_CARD[\"id\"], self.customer.default_source.id)\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\"stripe.PaymentMethod.attach\", return_value=deepcopy(FAKE_PAYMENT_METHOD_I))\n    def test_add_payment_method_obj(self, attach_mock, customer_retrieve_mock):\n        self.assertEqual(\n            self.customer.payment_methods.filter(\n                id=FAKE_PAYMENT_METHOD_I[\"id\"]\n            ).count(),\n            0,\n        )\n\n        payment_method = PaymentMethod.sync_from_stripe_data(FAKE_PAYMENT_METHOD_I)\n        payment_method = self.customer.add_payment_method(payment_method)\n\n        self.assertEqual(payment_method.customer.id, self.customer.id)\n\n        self.assertEqual(\n            self.customer.payment_methods.filter(\n                id=FAKE_PAYMENT_METHOD_I[\"id\"]\n            ).count(),\n            1,\n        )\n\n        self.assertEqual(\n            self.customer.payment_methods.filter(\n                id=FAKE_PAYMENT_METHOD_I[\"id\"]\n            ).first(),\n            self.customer.default_payment_method,\n        )\n\n        self.assertEqual(\n            self.customer.default_payment_method.id,\n            self.customer.invoice_settings[\"default_payment_method\"],\n        )\n\n        self.assert_fks(self.customer, expected_blank_fks={\"djstripe.Customer.coupon\"})\n\n    @patch(\"stripe.Customer.retrieve\", autospec=True)\n    @patch(\"stripe.PaymentMethod.attach\", return_value=deepcopy(FAKE_PAYMENT_METHOD_I))\n    def test_add_payment_method_set_default_true(\n        self, attach_mock, customer_retrieve_mock\n    ):\n        fake_customer = deepcopy(FAKE_CUSTOMER)\n        fake_customer[\"default_source\"] = None\n        customer_retrieve_mock.return_value = fake_customer\n\n        self.customer.default_source = None\n        self.customer.save()\n\n        self.assertEqual(\n            self.customer.payment_methods.filter(\n                id=FAKE_PAYMENT_METHOD_I[\"id\"]\n            ).count(),\n            0,\n        )\n\n        payment_method = self.customer.add_payment_method(FAKE_PAYMENT_METHOD_I[\"id\"])\n\n        self.assertEqual(payment_method.customer.id, self.customer.id)\n\n        self.assertEqual(\n            self.customer.payment_methods.filter(\n                id=FAKE_PAYMENT_METHOD_I[\"id\"]\n            ).count(),\n            1,\n        )\n\n        self.assertEqual(\n            self.customer.payment_methods.filter(\n                id=FAKE_PAYMENT_METHOD_I[\"id\"]\n            ).first(),\n            self.customer.default_payment_method,\n        )\n\n        self.assertEqual(\n            self.customer.default_payment_method.id,\n            self.customer.invoice_settings[\"default_payment_method\"],\n        )\n\n        self.assert_fks(\n            self.customer,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_source\",\n            },\n        )\n\n    @patch(\"stripe.Customer.retrieve\", autospec=True)\n    @patch(\"stripe.PaymentMethod.attach\", return_value=deepcopy(FAKE_PAYMENT_METHOD_I))\n    def test_add_payment_method_set_default_false(\n        self, attach_mock, customer_retrieve_mock\n    ):\n        fake_customer = deepcopy(FAKE_CUSTOMER)\n        fake_customer[\"default_source\"] = None\n        customer_retrieve_mock.return_value = fake_customer\n\n        self.customer.default_source = None\n        self.customer.save()\n\n        self.assertEqual(\n            self.customer.payment_methods.filter(\n                id=FAKE_PAYMENT_METHOD_I[\"id\"]\n            ).count(),\n            0,\n        )\n\n        payment_method = self.customer.add_payment_method(\n            FAKE_PAYMENT_METHOD_I[\"id\"], set_default=False\n        )\n\n        self.assertEqual(payment_method.customer.id, self.customer.id)\n\n        self.assertEqual(\n            self.customer.payment_methods.filter(\n                id=FAKE_PAYMENT_METHOD_I[\"id\"]\n            ).count(),\n            1,\n        )\n\n        self.assert_fks(\n            self.customer,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.default_source\",\n            },\n        )\n\n    def test_charge_accepts_only_decimals(self):\n        with self.assertRaises(ValueError):\n            self.customer.charge(10)\n\n    @patch(\"stripe.Coupon.retrieve\", return_value=deepcopy(FAKE_COUPON), autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_add_coupon_by_id(self, customer_retrieve_mock, coupon_retrieve_mock):\n        self.assertEqual(self.customer.coupon, None)\n        self.customer.add_coupon(FAKE_COUPON[\"id\"])\n        customer_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=ANY,\n            id=FAKE_CUSTOMER[\"id\"],\n            stripe_account=self.customer.djstripe_owner_account.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @patch(\"stripe.Coupon.retrieve\", return_value=deepcopy(FAKE_COUPON), autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_add_coupon_by_object(self, customer_retrieve_mock, coupon_retrieve_mock):\n        self.assertEqual(self.customer.coupon, None)\n        coupon = Coupon.sync_from_stripe_data(FAKE_COUPON)\n        fake_discount = deepcopy(FAKE_DISCOUNT_CUSTOMER)\n\n        def fake_customer_save(self, *args, **kwargs):\n            # fake the api coupon update behaviour\n            coupon = self.pop(\"coupon\", None)\n            if coupon:\n                self[\"discount\"] = fake_discount\n            else:\n                self[\"discount\"] = None\n\n            return self\n\n        with patch(\"tests.CustomerDict.save\", new=fake_customer_save):\n            self.customer.add_coupon(coupon)\n\n        customer_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=ANY,\n            id=FAKE_CUSTOMER[\"id\"],\n            stripe_account=self.customer.djstripe_owner_account.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        self.customer.refresh_from_db()\n\n        self.assert_fks(\n            self.customer,\n            expected_blank_fks={\"djstripe.Customer.default_payment_method\"},\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\"stripe.PaymentIntent.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    def test_refund_charge(\n        self,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_no_invoice = deepcopy(FAKE_CHARGE)\n        fake_charge_no_invoice.update({\"invoice\": None})\n\n        charge_retrieve_mock.return_value = fake_charge_no_invoice\n\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_I)\n        fake_payment_intent.update({\"invoice\": None})\n\n        payment_intent_retrieve_mock.return_value = fake_payment_intent\n\n        charge, created = Charge._get_or_create_from_stripe_object(\n            fake_charge_no_invoice\n        )\n        self.assertTrue(created)\n\n        self.assert_fks(\n            charge,\n            expected_blank_fks={\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n                \"djstripe.Charge.application_fee\",\n                \"djstripe.Charge.dispute\",\n                \"djstripe.Charge.latest_invoice (related name)\",\n                \"djstripe.Charge.latest_upcominginvoice (related name)\",\n                \"djstripe.Charge.invoice\",\n                \"djstripe.Charge.on_behalf_of\",\n                \"djstripe.Charge.source_transfer\",\n                \"djstripe.Charge.transfer\",\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.PaymentIntent.invoice (related name)\",\n                \"djstripe.PaymentIntent.on_behalf_of\",\n                \"djstripe.PaymentIntent.payment_method\",\n                \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            },\n        )\n\n        charge.refund()\n\n        refunded_charge, created2 = Charge._get_or_create_from_stripe_object(\n            fake_charge_no_invoice\n        )\n        self.assertFalse(created2)\n\n        self.assertEqual(refunded_charge.refunded, True)\n        self.assertEqual(refunded_charge.amount_refunded, decimal.Decimal(\"20.00\"))\n\n        self.assert_fks(\n            refunded_charge,\n            expected_blank_fks={\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n                \"djstripe.Charge.application_fee\",\n                \"djstripe.Charge.dispute\",\n                \"djstripe.Charge.latest_invoice (related name)\",\n                \"djstripe.Charge.latest_upcominginvoice (related name)\",\n                \"djstripe.Charge.invoice\",\n                \"djstripe.Charge.on_behalf_of\",\n                \"djstripe.Charge.source_transfer\",\n                \"djstripe.Charge.transfer\",\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.PaymentIntent.invoice (related name)\",\n                \"djstripe.PaymentIntent.on_behalf_of\",\n                \"djstripe.PaymentIntent.payment_method\",\n                \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            },\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\"stripe.PaymentIntent.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    def test_refund_charge_object_returned(\n        self,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_no_invoice = deepcopy(FAKE_CHARGE)\n        fake_charge_no_invoice.update({\"invoice\": None})\n\n        charge_retrieve_mock.return_value = fake_charge_no_invoice\n\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_I)\n        fake_payment_intent.update({\"invoice\": None})\n\n        payment_intent_retrieve_mock.return_value = fake_payment_intent\n\n        charge, created = Charge._get_or_create_from_stripe_object(\n            fake_charge_no_invoice\n        )\n        self.assertTrue(created)\n\n        self.assert_fks(\n            charge,\n            expected_blank_fks={\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n                \"djstripe.Charge.application_fee\",\n                \"djstripe.Charge.dispute\",\n                \"djstripe.Charge.latest_invoice (related name)\",\n                \"djstripe.Charge.latest_upcominginvoice (related name)\",\n                \"djstripe.Charge.invoice\",\n                \"djstripe.Charge.on_behalf_of\",\n                \"djstripe.Charge.source_transfer\",\n                \"djstripe.Charge.transfer\",\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.PaymentIntent.invoice (related name)\",\n                \"djstripe.PaymentIntent.on_behalf_of\",\n                \"djstripe.PaymentIntent.payment_method\",\n                \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            },\n        )\n\n        refunded_charge = charge.refund()\n        self.assertEqual(refunded_charge.refunded, True)\n        self.assertEqual(refunded_charge.amount_refunded, decimal.Decimal(\"20.00\"))\n\n        self.assert_fks(\n            refunded_charge,\n            expected_blank_fks={\n                \"djstripe.Account.branding_logo\",\n                \"djstripe.Account.branding_icon\",\n                \"djstripe.Charge.application_fee\",\n                \"djstripe.Charge.dispute\",\n                \"djstripe.Charge.latest_invoice (related name)\",\n                \"djstripe.Charge.latest_upcominginvoice (related name)\",\n                \"djstripe.Charge.invoice\",\n                \"djstripe.Charge.on_behalf_of\",\n                \"djstripe.Charge.source_transfer\",\n                \"djstripe.Charge.transfer\",\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.PaymentIntent.invoice (related name)\",\n                \"djstripe.PaymentIntent.on_behalf_of\",\n                \"djstripe.PaymentIntent.payment_method\",\n                \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            },\n        )\n\n    def test_calculate_refund_amount_partial_refund(self):\n        charge = Charge(\n            id=\"ch_111111\", customer=self.customer, amount=decimal.Decimal(\"500.00\")\n        )\n        self.assertEqual(\n            charge._calculate_refund_amount(amount=decimal.Decimal(\"300.00\")), 30000\n        )\n\n    def test_calculate_refund_above_max_refund(self):\n        charge = Charge(\n            id=\"ch_111111\", customer=self.customer, amount=decimal.Decimal(\"500.00\")\n        )\n        self.assertEqual(\n            charge._calculate_refund_amount(amount=decimal.Decimal(\"600.00\")), 50000\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\"stripe.Charge.create\", autospec=True)\n    @patch(\"stripe.PaymentIntent.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    def test_charge_converts_dollars_into_cents(\n        self,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_create_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n        fake_charge_copy.update({\"invoice\": None, \"amount\": 1000})\n\n        charge_create_mock.return_value = fake_charge_copy\n        charge_retrieve_mock.return_value = fake_charge_copy\n\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_I)\n        fake_payment_intent.update({\"invoice\": None})\n\n        payment_intent_retrieve_mock.return_value = fake_payment_intent\n\n        self.customer.charge(amount=decimal.Decimal(\"10.00\"))\n\n        _, kwargs = charge_create_mock.call_args\n        self.assertEqual(kwargs[\"amount\"], 1000)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\"stripe.Charge.create\", autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\"stripe.Invoice.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test_charge_doesnt_require_invoice(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_create_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        customer_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n        fake_charge_copy.update(\n            {\"invoice\": FAKE_INVOICE[\"id\"], \"amount\": FAKE_INVOICE[\"amount_due\"]}\n        )\n        fake_invoice_copy = deepcopy(FAKE_INVOICE)\n\n        charge_create_mock.return_value = fake_charge_copy\n        charge_retrieve_mock.return_value = fake_charge_copy\n        invoice_retrieve_mock.return_value = fake_invoice_copy\n\n        try:\n            self.customer.charge(amount=decimal.Decimal(\"20.00\"))\n        except Invoice.DoesNotExist:\n            self.fail(msg=\"Stripe Charge shouldn't throw Invoice DoesNotExist.\")\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\"stripe.Charge.create\", autospec=True)\n    @patch(\"stripe.PaymentIntent.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    def test_charge_passes_extra_arguments(\n        self,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_create_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n        fake_charge_copy.update({\"invoice\": None})\n\n        charge_create_mock.return_value = fake_charge_copy\n        charge_retrieve_mock.return_value = fake_charge_copy\n\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_I)\n        fake_payment_intent.update({\"invoice\": None})\n\n        payment_intent_retrieve_mock.return_value = fake_payment_intent\n\n        self.customer.charge(\n            amount=decimal.Decimal(\"10.00\"),\n            capture=True,\n            destination=FAKE_PLATFORM_ACCOUNT[\"id\"],\n        )\n\n        _, kwargs = charge_create_mock.call_args\n        self.assertEqual(kwargs[\"capture\"], True)\n        self.assertEqual(kwargs[\"destination\"], FAKE_PLATFORM_ACCOUNT[\"id\"])\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\"stripe.Charge.create\", autospec=True)\n    @patch(\"stripe.PaymentIntent.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    def test_charge_string_source(\n        self,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_create_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n        fake_charge_copy.update({\"invoice\": None})\n\n        charge_create_mock.return_value = fake_charge_copy\n        charge_retrieve_mock.return_value = fake_charge_copy\n\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_I)\n        fake_payment_intent.update({\"invoice\": None})\n\n        payment_intent_retrieve_mock.return_value = fake_payment_intent\n\n        self.customer.charge(amount=decimal.Decimal(\"10.00\"), source=self.card.id)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\"stripe.Charge.create\", autospec=True)\n    @patch(\"stripe.PaymentIntent.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    def test_charge_card_source(\n        self,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_create_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_charge_copy = deepcopy(FAKE_CHARGE)\n        fake_charge_copy.update({\"invoice\": None})\n\n        charge_create_mock.return_value = fake_charge_copy\n        charge_retrieve_mock.return_value = fake_charge_copy\n\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_I)\n        fake_payment_intent.update({\"invoice\": None})\n\n        payment_intent_retrieve_mock.return_value = fake_payment_intent\n\n        self.customer.charge(amount=decimal.Decimal(\"10.00\"), source=self.card)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_INVOICE_III),\n    )\n    @patch(\n        \"stripe.Invoice.list\",\n        return_value=StripeList(\n            data=[deepcopy(FAKE_INVOICE), deepcopy(FAKE_INVOICE_III)]\n        ),\n        autospec=True,\n    )\n    @patch(\"djstripe.models.Invoice.retry\", autospec=True)\n    def test_retry_unpaid_invoices(\n        self,\n        invoice_retry_mock,\n        invoice_list_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        self.customer.retry_unpaid_invoices()\n\n        invoice = Invoice.objects.get(id=FAKE_INVOICE_III[\"id\"])\n        invoice_retry_mock.assert_called_once_with(invoice)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    @patch(\n        \"stripe.Invoice.list\",\n        return_value=StripeList(data=[deepcopy(FAKE_INVOICE)]),\n        autospec=True,\n    )\n    @patch(\"djstripe.models.Invoice.retry\", autospec=True)\n    def test_retry_unpaid_invoices_none_unpaid(\n        self,\n        invoice_retry_mock,\n        invoice_list_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        self.customer.retry_unpaid_invoices()\n\n        self.assertFalse(invoice_retry_mock.called)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_INVOICE_III),\n    )\n    @patch(\n        \"stripe.Invoice.list\",\n        return_value=StripeList(data=[deepcopy(FAKE_INVOICE_III)]),\n    )\n    @patch(\"djstripe.models.Invoice.retry\", autospec=True)\n    def test_retry_unpaid_invoices_expected_exception(\n        self,\n        invoice_retry_mock,\n        invoice_list_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # latest invoice should be the unpaid one\n        fake_subscription[\"latest_invoice\"] = FAKE_INVOICE_III\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        invoice_retry_mock.side_effect = InvalidRequestError(\n            \"Invoice is already paid\", \"blah\"\n        )\n\n        try:\n            self.customer.retry_unpaid_invoices()\n        except Exception:\n            self.fail(\"Exception was unexpectedly raised.\")\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_INVOICE_III),\n    )\n    @patch(\n        \"stripe.Invoice.list\",\n        return_value=StripeList(data=[deepcopy(FAKE_INVOICE_III)]),\n    )\n    @patch(\"djstripe.models.Invoice.retry\", autospec=True)\n    def test_retry_unpaid_invoices_unexpected_exception(\n        self,\n        invoice_retry_mock,\n        invoice_list_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # latest invoice should be the unpaid one\n        fake_subscription[\"latest_invoice\"] = FAKE_INVOICE_III\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        invoice_retry_mock.side_effect = InvalidRequestError(\n            \"This should fail!\", \"blah\"\n        )\n\n        with self.assertRaisesMessage(InvalidRequestError, \"This should fail!\"):\n            self.customer.retry_unpaid_invoices()\n\n    @patch(\"stripe.Invoice.create\", autospec=True)\n    def test_send_invoice_success(self, invoice_create_mock):\n        return_status = self.customer.send_invoice()\n        self.assertTrue(return_status)\n\n        invoice_create_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            customer=self.customer.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @patch(\"stripe.Invoice.create\", autospec=True)\n    def test_send_invoice_failure(self, invoice_create_mock):\n        invoice_create_mock.side_effect = InvalidRequestError(\n            \"Invoice creation failed.\", \"blah\"\n        )\n\n        return_status = self.customer.send_invoice()\n        self.assertFalse(return_status)\n\n        invoice_create_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            customer=self.customer.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @patch(\"stripe.Coupon.retrieve\", return_value=deepcopy(FAKE_COUPON), autospec=True)\n    def test_sync_customer_with_discount(self, coupon_retrieve_mock):\n        self.assertIsNone(self.customer.coupon)\n        fake_customer = deepcopy(FAKE_CUSTOMER)\n        fake_customer[\"discount\"] = deepcopy(FAKE_DISCOUNT_CUSTOMER)\n        customer = Customer.sync_from_stripe_data(fake_customer)\n        self.assertEqual(customer.coupon.id, FAKE_COUPON[\"id\"])\n        self.assertIsNotNone(customer.coupon_start)\n        self.assertIsNone(customer.coupon_end)\n\n    @patch(\"stripe.Coupon.retrieve\", return_value=deepcopy(FAKE_COUPON), autospec=True)\n    def test_sync_customer_discount_already_present(self, coupon_retrieve_mock):\n        fake_customer = deepcopy(FAKE_CUSTOMER)\n        fake_customer[\"discount\"] = deepcopy(FAKE_DISCOUNT_CUSTOMER)\n\n        # Set the customer's coupon to be what we'll sync\n        customer = Customer.objects.get(id=FAKE_CUSTOMER[\"id\"])\n        customer.coupon = Coupon.sync_from_stripe_data(FAKE_COUPON)\n        customer.save()\n\n        customer = Customer.sync_from_stripe_data(fake_customer)\n        self.assertEqual(customer.coupon.id, FAKE_COUPON[\"id\"])\n\n    def test_sync_customer_delete_discount(self):\n        test_coupon = Coupon.sync_from_stripe_data(FAKE_COUPON)\n        self.customer.coupon = test_coupon\n        self.customer.save()\n        self.assertEqual(self.customer.coupon.id, FAKE_COUPON[\"id\"])\n\n        customer = Customer.sync_from_stripe_data(FAKE_CUSTOMER)\n        self.assertEqual(customer.coupon, None)\n\n    @patch(\n        \"djstripe.models.Invoice.sync_from_stripe_data\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.list\",\n        return_value=StripeList(\n            data=[deepcopy(FAKE_INVOICE), deepcopy(FAKE_INVOICE_III)]\n        ),\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_invoices(\n        self, customer_retrieve_mock, invoice_list_mock, invoice_sync_mock\n    ):\n        self.customer._sync_invoices()\n        self.assertEqual(2, invoice_sync_mock.call_count)\n\n    @patch(\n        \"djstripe.models.Invoice.sync_from_stripe_data\",\n        autospec=True,\n    )\n    @patch(\"stripe.Invoice.list\", return_value=StripeList(data=[]), autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_invoices_none(\n        self, customer_retrieve_mock, invoice_list_mock, invoice_sync_mock\n    ):\n        self.customer._sync_invoices()\n        self.assertEqual(0, invoice_sync_mock.call_count)\n\n    @patch(\n        \"djstripe.models.Charge.sync_from_stripe_data\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.list\",\n        return_value=StripeList(data=[deepcopy(FAKE_CHARGE)]),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_charges(\n        self, customer_retrieve_mock, charge_list_mock, charge_sync_mock\n    ):\n        self.customer._sync_charges()\n        self.assertEqual(1, charge_sync_mock.call_count)\n\n    @patch(\n        \"djstripe.models.Charge.sync_from_stripe_data\",\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.list\", return_value=StripeList(data=[]), autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_charges_none(\n        self, customer_retrieve_mock, charge_list_mock, charge_sync_mock\n    ):\n        self.customer._sync_charges()\n        self.assertEqual(0, charge_sync_mock.call_count)\n\n    @patch(\n        \"djstripe.models.Subscription.sync_from_stripe_data\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.list\",\n        return_value=StripeList(\n            data=[deepcopy(FAKE_SUBSCRIPTION), deepcopy(FAKE_SUBSCRIPTION_II)]\n        ),\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_subscriptions(\n        self, customer_retrieve_mock, subscription_list_mock, subscription_sync_mock\n    ):\n        self.customer._sync_subscriptions()\n        self.assertEqual(2, subscription_sync_mock.call_count)\n\n    @patch(\n        \"djstripe.models.Subscription.sync_from_stripe_data\",\n        autospec=True,\n    )\n    @patch(\"stripe.Subscription.list\", return_value=StripeList(data=[]), autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_subscriptions_none(\n        self, customer_retrieve_mock, subscription_list_mock, subscription_sync_mock\n    ):\n        self.customer._sync_subscriptions()\n        self.assertEqual(0, subscription_sync_mock.call_count)\n\n    @patch(\"stripe.Subscription.create\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_subscribe_price_string_new_style(\n        self,\n        product_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_create_mock,\n    ):\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        fake_subscription[\"latest_invoice\"] = None\n        subscription_create_mock.return_value = fake_subscription\n\n        current_subscriptions = self.customer.subscriptions.count()\n\n        price = Price.sync_from_stripe_data(deepcopy(FAKE_PRICE))\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n        self.customer.subscribe(items=[{\"price\": price.id}])\n\n        updated_subscriptions = self.customer.subscriptions.count()\n\n        # assert 1 new subscription got created\n        assert updated_subscriptions == current_subscriptions + 1\n\n    @patch(\"stripe.Subscription.create\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_subscribe_price_string_old_style(\n        self,\n        product_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_create_mock,\n    ):\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        fake_subscription[\"latest_invoice\"] = None\n        subscription_create_mock.return_value = fake_subscription\n\n        current_subscriptions = self.customer.subscriptions.count()\n        price = Price.sync_from_stripe_data(deepcopy(FAKE_PRICE))\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n        self.customer.subscribe(price=price.id)\n\n        updated_subscriptions = self.customer.subscriptions.count()\n\n        # assert 1 new subscription got created\n        assert updated_subscriptions == current_subscriptions + 1\n\n    @patch(\"stripe.Subscription.create\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_subscription_shortcut_with_multiple_subscriptions_old_style(\n        self, product_retrieve_mock, customer_retrieve_mock, subscription_create_mock\n    ):\n        price = Price.sync_from_stripe_data(deepcopy(FAKE_PRICE))\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n        subscription_fake_duplicate = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_fake_duplicate[\"id\"] = \"sub_6lsC8pt7IcF8jd\"\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        subscription_fake_duplicate[\"latest_invoice\"] = None\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        fake_subscription[\"latest_invoice\"] = None\n\n        subscription_create_mock.side_effect = [\n            fake_subscription,\n            subscription_fake_duplicate,\n        ]\n\n        self.customer.subscribe(price=price)\n        self.customer.subscribe(price=price)\n\n        self.assertEqual(2, self.customer.subscriptions.count())\n        self.assertEqual(2, len(self.customer.valid_subscriptions))\n\n        with self.assertRaises(MultipleSubscriptionException):\n            self.customer.subscription\n\n    @patch.object(Subscription, \"_api_create\", autospec=True)\n    @patch(\"stripe.Subscription.create\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_subscription_shortcut_with_multiple_subscriptions_new_style_by_price(\n        self,\n        product_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_create_mock,\n        subscription_api_create_mock,\n    ):\n        price = Price.sync_from_stripe_data(deepcopy(FAKE_PRICE))\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n        subscription_fake_duplicate = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_fake_duplicate[\"id\"] = \"sub_6lsC8pt7IcF8jd\"\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        subscription_fake_duplicate[\"latest_invoice\"] = None\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        fake_subscription[\"latest_invoice\"] = None\n\n        subscription_create_mock.side_effect = [\n            fake_subscription,\n            subscription_fake_duplicate,\n        ]\n\n        subscription_api_create_mock.side_effect = [\n            fake_subscription,\n            subscription_fake_duplicate,\n        ]\n\n        self.customer.subscribe(items=[{\"price\": price}, {\"price\": price}])\n\n        self.assertEqual(1, self.customer.subscriptions.count())\n        self.assertEqual(1, len(self.customer.valid_subscriptions))\n\n        subscription_api_create_mock.assert_called_once_with(\n            items=[{\"price\": price.id}, {\"price\": price.id}], customer=self.customer.id\n        )\n\n    @patch.object(Subscription, \"_api_create\", autospec=True)\n    @patch(\"stripe.Subscription.create\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_subscription_shortcut_with_multiple_subscriptions_new_style_by_price_and_plan(\n        self,\n        product_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_create_mock,\n        subscription_api_create_mock,\n    ):\n        price = Price.sync_from_stripe_data(deepcopy(FAKE_PRICE))\n        plan = Plan.sync_from_stripe_data(deepcopy(FAKE_PLAN))\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n        subscription_fake_duplicate = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_fake_duplicate[\"id\"] = \"sub_6lsC8pt7IcF8jd\"\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        subscription_fake_duplicate[\"latest_invoice\"] = None\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        fake_subscription[\"latest_invoice\"] = None\n\n        subscription_create_mock.side_effect = [\n            fake_subscription,\n            subscription_fake_duplicate,\n        ]\n\n        subscription_api_create_mock.side_effect = [\n            fake_subscription,\n            subscription_fake_duplicate,\n        ]\n\n        self.customer.subscribe(items=[{\"price\": price}, {\"plan\": plan}])\n\n        self.assertEqual(1, self.customer.subscriptions.count())\n        self.assertEqual(1, len(self.customer.valid_subscriptions))\n\n        subscription_api_create_mock.assert_called_once_with(\n            items=[{\"price\": price.id}, {\"plan\": plan.id}], customer=self.customer.id\n        )\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_subscription_shortcut_with_invalid_subscriptions(\n        self, product_retrieve_mock, customer_retrieve_mock\n    ):\n        price = Price.sync_from_stripe_data(deepcopy(FAKE_PRICE))\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n        fake_subscription_upd = deepcopy(FAKE_SUBSCRIPTION)\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        fake_subscription_upd[\"latest_invoice\"] = None\n\n        fake_subscriptions = [\n            deepcopy(fake_subscription_upd),\n            deepcopy(fake_subscription_upd),\n            deepcopy(fake_subscription_upd),\n        ]\n\n        # update the status of all but one to be invalid,\n        # we need to also change the id for sync to work\n        fake_subscriptions[1][\"status\"] = \"canceled\"\n        fake_subscriptions[1][\"id\"] = fake_subscriptions[1][\"id\"] + \"foo1\"\n        fake_subscriptions[2][\"status\"] = \"incomplete_expired\"\n        fake_subscriptions[2][\"id\"] = fake_subscriptions[2][\"id\"] + \"foo2\"\n\n        for _fake_subscription in fake_subscriptions:\n            with patch(\n                \"stripe.Subscription.create\",\n                autospec=True,\n                side_effect=[_fake_subscription],\n            ):\n                self.customer.subscribe(items=[{\"price\": price}])\n\n        self.assertEqual(3, self.customer.subscriptions.count())\n        self.assertEqual(1, len(self.customer.valid_subscriptions))\n        self.assertEqual(\n            self.customer.valid_subscriptions[0], self.customer.subscription\n        )\n\n        self.assertEqual(fake_subscriptions[0][\"id\"], self.customer.subscription.id)\n\n    @patch(\n        \"djstripe.models.InvoiceItem.sync_from_stripe_data\",\n        return_value=\"pancakes\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.create\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_add_invoice_item(self, invoiceitem_create_mock, invoiceitem_sync_mock):\n        invoiceitem = self.customer.add_invoice_item(\n            amount=decimal.Decimal(\"50.00\"),\n            currency=\"eur\",\n            description=\"test\",\n            invoice=77,\n            subscription=25,\n        )\n        self.assertEqual(\"pancakes\", invoiceitem)\n\n        invoiceitem_create_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            amount=5000,\n            customer=self.customer.id,\n            currency=\"eur\",\n            description=\"test\",\n            discountable=None,\n            invoice=77,\n            metadata=None,\n            subscription=25,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @patch(\n        \"djstripe.models.InvoiceItem.sync_from_stripe_data\",\n        return_value=\"pancakes\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.create\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_add_invoice_item_djstripe_objects(\n        self, invoiceitem_create_mock, invoiceitem_sync_mock\n    ):\n        invoiceitem = self.customer.add_invoice_item(\n            amount=decimal.Decimal(\"50.00\"),\n            currency=\"eur\",\n            description=\"test\",\n            invoice=Invoice(id=77),\n            subscription=Subscription(id=25),\n        )\n        self.assertEqual(\"pancakes\", invoiceitem)\n\n        invoiceitem_create_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            amount=5000,\n            customer=self.customer.id,\n            currency=\"eur\",\n            description=\"test\",\n            discountable=None,\n            invoice=77,\n            metadata=None,\n            subscription=25,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    def test_add_invoice_item_bad_decimal(self):\n        with self.assertRaisesMessage(\n            ValueError, \"You must supply a decimal value representing dollars.\"\n        ):\n            self.customer.add_invoice_item(amount=5000, currency=\"usd\")\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Plan.retrieve\",\n        return_value=deepcopy(FAKE_PLAN),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\",\n        return_value=deepcopy(FAKE_PRODUCT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    @patch(\n        \"stripe.Invoice.upcoming\",\n        autospec=True,\n    )\n    def test_upcoming_invoice_plan(\n        self,\n        invoice_upcoming_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_upcoming_invoice_data = deepcopy(FAKE_UPCOMING_INVOICE)\n        fake_upcoming_invoice_data[\"lines\"][\"data\"][0][\n            \"subscription\"\n        ] = FAKE_SUBSCRIPTION[\"id\"]\n        invoice_upcoming_mock.return_value = fake_upcoming_invoice_data\n\n        fake_subscription_item_data = deepcopy(FAKE_SUBSCRIPTION_ITEM)\n        fake_subscription_item_data[\"plan\"] = deepcopy(FAKE_PLAN)\n        fake_subscription_item_data[\"subscription\"] = deepcopy(FAKE_SUBSCRIPTION)[\"id\"]\n        subscription_item_retrieve_mock.return_value = fake_subscription_item_data\n\n        invoice = self.customer.upcoming_invoice()\n        self.assertIsNotNone(invoice)\n        self.assertIsNone(invoice.id)\n        self.assertIsNone(invoice.save())\n\n        # one more because of creating the associated line item\n        subscription_retrieve_mock.assert_has_calls(\n            [\n                call(\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[],\n                    id=FAKE_SUBSCRIPTION[\"id\"],\n                    stripe_account=None,\n                    stripe_version=\"2020-08-27\",\n                ),\n                call(\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[],\n                    id=FAKE_SUBSCRIPTION[\"id\"],\n                    stripe_account=None,\n                    stripe_version=\"2020-08-27\",\n                ),\n            ]\n        )\n\n        plan_retrieve_mock.assert_not_called()\n\n        items = invoice.lineitems.all()\n\n        self.assertEqual(1, len(items))\n        self.assertEqual(\"il_fakefakefakefakefake0002\", items[0].id)\n        self.assertEqual(0, invoice.invoiceitems.count())\n\n        self.assertIsNotNone(invoice.plan)\n        self.assertEqual(FAKE_PLAN[\"id\"], invoice.plan.id)\n\n        invoice._lineitems = []\n        items = invoice.lineitems.all()\n        self.assertEqual(0, len(items))\n        self.assertIsNotNone(invoice.plan)\n\n    @patch(\"stripe.Subscription.create\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_is_subscribed_to_with_product_old_style(\n        self,\n        product_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_create_mock,\n    ):\n        price = Price.sync_from_stripe_data(deepcopy(FAKE_PRICE))\n        product = Product.sync_from_stripe_data(deepcopy(FAKE_PRODUCT))\n\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_fake[\"current_period_end\"] = datetime_to_unix(\n            timezone.now() + timezone.timedelta(days=7)\n        )\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        subscription_fake[\"latest_invoice\"] = None\n\n        subscription_create_mock.return_value = subscription_fake\n\n        self.customer.subscribe(items=[{\"price\": price}])\n\n        assert self.customer.is_subscribed_to(product)\n\n    @patch(\"stripe.Subscription.create\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_is_subscribed_to_with_product_new_style(\n        self, product_retrieve_mock, customer_retrieve_mock, subscription_create_mock\n    ):\n        price = Price.sync_from_stripe_data(deepcopy(FAKE_PRICE))\n        product = Product.sync_from_stripe_data(deepcopy(FAKE_PRODUCT))\n\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_fake[\"current_period_end\"] = datetime_to_unix(\n            timezone.now() + timezone.timedelta(days=7)\n        )\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        subscription_fake[\"latest_invoice\"] = None\n\n        subscription_create_mock.return_value = subscription_fake\n\n        self.customer.subscribe(items=[{\"price\": price}])\n\n        assert self.customer.is_subscribed_to(product)\n\n    @patch(\"stripe.Subscription.create\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_is_subscribed_to_with_product_string_new_style(\n        self, product_retrieve_mock, customer_retrieve_mock, subscription_create_mock\n    ):\n        price = Price.sync_from_stripe_data(deepcopy(FAKE_PRICE))\n        product = Product.sync_from_stripe_data(deepcopy(FAKE_PRODUCT))\n\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_fake[\"current_period_end\"] = datetime_to_unix(\n            timezone.now() + timezone.timedelta(days=7)\n        )\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        subscription_fake[\"latest_invoice\"] = None\n\n        subscription_create_mock.return_value = subscription_fake\n\n        self.customer.subscribe(items=[{\"price\": price}])\n\n        assert self.customer.is_subscribed_to(product.id)\n\n    @patch(\"stripe.Subscription.create\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_is_subscribed_to_with_product_string_by_price(\n        self, product_retrieve_mock, customer_retrieve_mock, subscription_create_mock\n    ):\n        price = Price.sync_from_stripe_data(deepcopy(FAKE_PRICE))\n        product = Product.sync_from_stripe_data(deepcopy(FAKE_PRODUCT))\n\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_fake[\"current_period_end\"] = datetime_to_unix(\n            timezone.now() + timezone.timedelta(days=7)\n        )\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        subscription_fake[\"latest_invoice\"] = None\n\n        subscription_create_mock.return_value = subscription_fake\n\n        self.customer.subscribe(price=price)\n\n        assert self.customer.is_subscribed_to(product.id)\n\n\n# These tests use Plan which is deprecated in favor of Price\nclass TestCustomerLegacy(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        # create a Stripe Platform Account\n        self.account = FAKE_PLATFORM_ACCOUNT.create()\n\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n        self.payment_method, _ = DjstripePaymentMethod._get_or_create_source(\n            FAKE_CARD, \"card\"\n        )\n        self.card = self.payment_method.resolve()\n\n        self.customer.default_source = self.payment_method\n        self.customer.save()\n\n    @patch(\n        \"stripe.Subscription.create\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_subscribe_plan_string_new_style(\n        self,\n        product_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_create_mock,\n    ):\n        plan = Plan.sync_from_stripe_data(deepcopy(FAKE_PLAN))\n\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        fake_subscription[\"latest_invoice\"] = None\n        subscription_create_mock.return_value = fake_subscription\n\n        current_subscriptions = self.customer.subscriptions.count()\n        self.customer.subscribe(items=[{\"plan\": plan.id}])\n\n        updated_subscriptions = self.customer.subscriptions.count()\n\n        # assert 1 new subscription got created\n        assert updated_subscriptions == current_subscriptions + 1\n\n    @patch(\n        \"stripe.Subscription.create\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_subscribe_plan_string_old_style(\n        self,\n        product_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_create_mock,\n    ):\n        plan = Plan.sync_from_stripe_data(deepcopy(FAKE_PLAN))\n\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        fake_subscription[\"latest_invoice\"] = None\n        subscription_create_mock.return_value = fake_subscription\n\n        current_subscriptions = self.customer.subscriptions.count()\n        self.customer.subscribe(plan=plan.id)\n\n        updated_subscriptions = self.customer.subscriptions.count()\n\n        # assert 1 new subscription got created\n        assert updated_subscriptions == current_subscriptions + 1\n\n    @patch.object(Subscription, \"_api_create\", autospec=True)\n    @patch(\"stripe.Subscription.create\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_subscription_shortcut_with_multiple_subscriptions_new_style_by_plan(\n        self,\n        product_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_create_mock,\n        subscription_api_create_mock,\n    ):\n        plan = Plan.sync_from_stripe_data(deepcopy(FAKE_PLAN))\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n        subscription_fake_duplicate = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_fake_duplicate[\"id\"] = \"sub_6lsC8pt7IcF8jd\"\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        subscription_fake_duplicate[\"latest_invoice\"] = None\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        fake_subscription[\"latest_invoice\"] = None\n\n        subscription_create_mock.side_effect = [\n            fake_subscription,\n            subscription_fake_duplicate,\n        ]\n\n        subscription_api_create_mock.side_effect = [\n            fake_subscription,\n            subscription_fake_duplicate,\n        ]\n\n        self.customer.subscribe(items=[{\"plan\": plan}, {\"plan\": plan}])\n\n        self.assertEqual(1, self.customer.subscriptions.count())\n        self.assertEqual(1, len(self.customer.valid_subscriptions))\n\n        subscription_api_create_mock.assert_called_once_with(\n            items=[{\"plan\": plan.id}, {\"plan\": plan.id}], customer=self.customer.id\n        )\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_subscription_shortcut_with_invalid_subscriptions(\n        self, product_retrieve_mock, customer_retrieve_mock\n    ):\n        plan = Plan.sync_from_stripe_data(deepcopy(FAKE_PLAN))\n\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n        fake_subscription_upd = deepcopy(FAKE_SUBSCRIPTION)\n        # latest_invoice has to be None for an invoice that doesn't exist yet\n        # and hence cannot have been billed yet\n        fake_subscription_upd[\"latest_invoice\"] = None\n\n        fake_subscriptions = [\n            deepcopy(fake_subscription_upd),\n            deepcopy(fake_subscription_upd),\n            deepcopy(fake_subscription_upd),\n        ]\n\n        # update the status of all but one to be invalid,\n        # we need to also change the id for sync to work\n        fake_subscriptions[1][\"status\"] = \"canceled\"\n        fake_subscriptions[1][\"id\"] = fake_subscriptions[1][\"id\"] + \"foo1\"\n        fake_subscriptions[2][\"status\"] = \"incomplete_expired\"\n        fake_subscriptions[2][\"id\"] = fake_subscriptions[2][\"id\"] + \"foo2\"\n\n        for _fake_subscription in fake_subscriptions:\n            with patch(\n                \"stripe.Subscription.create\",\n                autospec=True,\n                side_effect=[_fake_subscription],\n            ):\n                self.customer.subscribe(items=[{\"plan\": plan}])\n\n        self.assertEqual(3, self.customer.subscriptions.count())\n        self.assertEqual(1, len(self.customer.valid_subscriptions))\n        self.assertEqual(\n            self.customer.valid_subscriptions[0], self.customer.subscription\n        )\n\n        self.assertEqual(fake_subscriptions[0][\"id\"], self.customer.subscription.id)\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Plan.retrieve\",\n        return_value=deepcopy(FAKE_PLAN),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\",\n        return_value=deepcopy(FAKE_PRODUCT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    @patch(\n        \"stripe.Invoice.upcoming\",\n        autospec=True,\n    )\n    def test_upcoming_invoice(\n        self,\n        invoice_upcoming_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_upcoming_invoice_data = deepcopy(FAKE_UPCOMING_INVOICE)\n        fake_upcoming_invoice_data[\"lines\"][\"data\"][0][\n            \"subscription\"\n        ] = FAKE_SUBSCRIPTION[\"id\"]\n        invoice_upcoming_mock.return_value = fake_upcoming_invoice_data\n\n        fake_subscription_item_data = deepcopy(FAKE_SUBSCRIPTION_ITEM)\n        fake_subscription_item_data[\"plan\"] = deepcopy(FAKE_PLAN)\n        fake_subscription_item_data[\"subscription\"] = deepcopy(FAKE_SUBSCRIPTION)[\"id\"]\n        subscription_item_retrieve_mock.return_value = fake_subscription_item_data\n\n        invoice = self.customer.upcoming_invoice()\n        self.assertIsNotNone(invoice)\n        self.assertIsNone(invoice.id)\n        self.assertIsNone(invoice.save())\n\n        # one more because of creating the associated line item\n        subscription_retrieve_mock.assert_has_calls(\n            [\n                call(\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[],\n                    id=FAKE_SUBSCRIPTION[\"id\"],\n                    stripe_account=None,\n                    stripe_version=\"2020-08-27\",\n                ),\n                call(\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[],\n                    id=FAKE_SUBSCRIPTION[\"id\"],\n                    stripe_account=None,\n                    stripe_version=\"2020-08-27\",\n                ),\n            ]\n        )\n\n        plan_retrieve_mock.assert_not_called()\n\n        items = invoice.lineitems.all()\n\n        self.assertEqual(1, len(items))\n        self.assertEqual(\"il_fakefakefakefakefake0002\", items[0].id)\n        self.assertEqual(0, invoice.invoiceitems.count())\n\n        self.assertIsNotNone(invoice.plan)\n        self.assertEqual(FAKE_PLAN[\"id\"], invoice.plan.id)\n\n        invoice._lineitems = []\n        items = invoice.lineitems.all()\n        self.assertEqual(0, len(items))\n        self.assertIsNotNone(invoice.plan)\n"
  },
  {
    "path": "tests/test_discount.py",
    "content": "\"\"\"\ndj-stripe Discount model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nfrom django.test.testcases import TestCase\n\nfrom djstripe.models.billing import Discount\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_DISCOUNT,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestDiscount(AssertStripeFksMixin, TestCase):\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    def test_sync_from_stripe_data(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_discount_data = deepcopy(FAKE_DISCOUNT)\n\n        discount = Discount.sync_from_stripe_data(fake_discount_data)\n\n        self.assertEqual(discount.id, fake_discount_data[\"id\"])\n        self.assertEqual(discount.customer.id, fake_discount_data[\"customer\"][\"id\"])\n        self.assertEqual(discount.subscription.id, fake_discount_data[\"subscription\"])\n        self.assert_fks(\n            discount,\n            expected_blank_fks={\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.subscriber\",\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.discount\",\n                \"djstripe.Charge.application_fee\",\n                \"djstripe.Charge.dispute\",\n                \"djstripe.Charge.latest_upcominginvoice (related name)\",\n                \"djstripe.Charge.on_behalf_of\",\n                \"djstripe.Charge.source_transfer\",\n                \"djstripe.Charge.transfer\",\n                \"djstripe.Discount.checkout_session\",\n                \"djstripe.Discount.invoice\",\n                \"djstripe.Discount.invoice_item\",\n                \"djstripe.PaymentIntent.on_behalf_of\",\n                \"djstripe.PaymentIntent.payment_method\",\n                \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n                \"djstripe.Product.default_price\",\n                \"djstripe.Invoice.default_payment_method\",\n                \"djstripe.Invoice.default_source\",\n                \"djstripe.Invoice.subscription\",\n                \"djstripe.Subscription.default_payment_method\",\n                \"djstripe.Subscription.default_source\",\n                \"djstripe.Subscription.pending_setup_intent\",\n                \"djstripe.Subscription.schedule\",\n            },\n        )\n"
  },
  {
    "path": "tests/test_dispute.py",
    "content": "\"\"\"\ndj-stripe Dispute model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nfrom django.contrib.auth import get_user_model\nfrom django.test.testcases import TestCase\n\nfrom djstripe.models import Dispute\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CUSTOMER,\n    FAKE_DISPUTE_BALANCE_TRANSACTION,\n    FAKE_DISPUTE_CHARGE,\n    FAKE_DISPUTE_I,\n    FAKE_DISPUTE_III,\n    FAKE_DISPUTE_PAYMENT_INTENT,\n    FAKE_FILEUPLOAD_ICON,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestDispute(TestCase):\n    def setUp(self):\n        self.user = get_user_model().objects.create_user(\n            username=\"fake_customer_1\", email=FAKE_CUSTOMER[\"email\"]\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_PAYMENT_INTENT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Dispute.retrieve\", return_value=deepcopy(FAKE_DISPUTE_I), autospec=True\n    )\n    def test___str__(\n        self,\n        dispute_retrieve_mock,\n        file_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        charge_retrieve_mock,\n        payment_intent_retrieve_mock,\n        payment_method_retrieve_mock,\n    ):\n        dispute = Dispute.sync_from_stripe_data(FAKE_DISPUTE_I)\n        self.assertEqual(str(dispute), \"$10.00 USD (Needs response) \")\n\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_PAYMENT_INTENT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Dispute.retrieve\", return_value=deepcopy(FAKE_DISPUTE_I), autospec=True\n    )\n    def test_sync_from_stripe_data(\n        self,\n        dispute_retrieve_mock,\n        file_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        charge_retrieve_mock,\n        payment_intent_retrieve_mock,\n        payment_method_retrieve_mock,\n    ):\n        dispute = Dispute.sync_from_stripe_data(FAKE_DISPUTE_I)\n        assert dispute.id == FAKE_DISPUTE_I[\"id\"]\n\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_PAYMENT_INTENT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Dispute.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_III),\n        autospec=True,\n    )\n    def test__attach_objects_post_save_hook(\n        self,\n        dispute_retrieve_mock,\n        file_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        charge_retrieve_mock,\n        payment_intent_retrieve_mock,\n        payment_method_retrieve_mock,\n    ):\n        dispute = Dispute.sync_from_stripe_data(FAKE_DISPUTE_III)\n        assert dispute.id == FAKE_DISPUTE_III[\"id\"]\n\n        # assert File was retrieved correctly\n        file_retrieve_mock.assert_called_once_with(\n            id=FAKE_DISPUTE_III[\"evidence\"][\"receipt\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[],\n            stripe_account=None,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        # assert Balance Transactions were retrieved correctly\n        balance_transaction_retrieve_mock.assert_called_once_with(\n            id=FAKE_DISPUTE_BALANCE_TRANSACTION[\"id\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            expand=[],\n            stripe_account=None,\n        )\n\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_PAYMENT_INTENT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Dispute.retrieve\", return_value=deepcopy(FAKE_DISPUTE_I), autospec=True\n    )\n    def test_get_stripe_dashboard_url(\n        self,\n        dispute_retrieve_mock,\n        file_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        charge_retrieve_mock,\n        payment_intent_retrieve_mock,\n        payment_method_retrieve_mock,\n    ):\n        dispute = Dispute.sync_from_stripe_data(FAKE_DISPUTE_I)\n        self.assertEqual(\n            dispute.get_stripe_dashboard_url(),\n            f\"{dispute._get_base_stripe_dashboard_url()}\"\n            f\"{dispute.stripe_dashboard_item_name}/{dispute.payment_intent.id}\",\n        )\n"
  },
  {
    "path": "tests/test_django.py",
    "content": "from django.core.management import call_command\nfrom django.test import TestCase\nfrom django.test.utils import override_settings\n\n\nclass TestRunManagePyCheck(TestCase):\n    @override_settings(\n        STRIPE_TEST_SECRET_KEY=\"sk_test_foo\",\n        STRIPE_LIVE_SECRET_KEY=\"sk_live_foo\",\n        STRIPE_TEST_PUBLIC_KEY=\"pk_test_foo\",\n        STRIPE_LIVE_PUBLIC_KEY=\"pk_live_foo\",\n        STRIPE_LIVE_MODE=True,\n    )\n    def test_manage_py_check(self):\n        call_command(\"check\")\n"
  },
  {
    "path": "tests/test_enums.py",
    "content": "from collections import OrderedDict\n\nfrom django.test import TestCase\nfrom django.utils.translation import gettext_lazy as _\n\nfrom djstripe.enums import Enum, EnumMetaClass\n\n\nclass TestEnumMetaClass(TestCase):\n    def test_python2_prepare(self):\n        # Python 2 hack to ensure __prepare__ is called...\n        self.assertEqual(EnumMetaClass.__prepare__(None, None), OrderedDict())\n\n\nclass TestEnumHumanize(TestCase):\n    def test_humanize(self):\n        class TestEnum(Enum):\n            red = _(\"Red\")\n            blue = _(\"Blue\")\n\n        self.assertEqual(TestEnum.humanize(\"red\"), _(\"Red\"))\n"
  },
  {
    "path": "tests/test_event.py",
    "content": "\"\"\"\ndj-stripe Event Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase\nfrom stripe.error import StripeError\n\nfrom djstripe import webhooks\nfrom djstripe.models import Event, Transfer\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_CUSTOMER,\n    FAKE_EVENT_TRANSFER_CREATED,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_TRANSFER,\n)\n\n\nclass EventTest(TestCase):\n    def setUp(self):\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n        patcher = patch.object(webhooks, \"call_handlers\")\n        self.addCleanup(patcher.stop)\n        self.call_handlers = patcher.start()\n\n    def test___str__(self):\n        event = self._create_event(FAKE_EVENT_TRANSFER_CREATED)\n\n        self.assertEqual(\n            f\"type={FAKE_EVENT_TRANSFER_CREATED['type']}, id={FAKE_EVENT_TRANSFER_CREATED['id']}\",\n            str(event),\n        )\n\n    def test_invoke_webhook_handlers_event_with_log_stripe_error(self):\n        event = self._create_event(FAKE_EVENT_TRANSFER_CREATED)\n        self.call_handlers.side_effect = StripeError(\"Boom!\")\n        with self.assertRaises(StripeError):\n            event.invoke_webhook_handlers()\n\n    def test_invoke_webhook_handlers_event_with_raise_stripe_error(self):\n        event = self._create_event(FAKE_EVENT_TRANSFER_CREATED)\n        self.call_handlers.side_effect = StripeError(\"Boom!\")\n        with self.assertRaises(StripeError):\n            event.invoke_webhook_handlers()\n\n    def test_invoke_webhook_handlers_event_when_invalid(self):\n        event = self._create_event(FAKE_EVENT_TRANSFER_CREATED)\n        event.valid = False\n        event.invoke_webhook_handlers()\n\n    @patch(target=\"djstripe.models.core.transaction.atomic\", autospec=True)\n    @patch.object(target=Event, attribute=\"_create_from_stripe_object\", autospec=True)\n    @patch.object(target=Event, attribute=\"objects\", autospec=True)\n    def test_process_event(\n        self, mock_objects, mock__create_from_stripe_object, mock_atomic\n    ):\n        \"\"\"Test that process event creates a new event and invokes webhooks\n        when the event doesn't already exist.\n        \"\"\"\n        # Set up mocks\n        mock_objects.filter.return_value.exists.return_value = False\n        mock_data = {\"id\": \"foo_id\", \"other_stuff\": \"more_things\"}\n\n        result = Event.process(data=mock_data)\n\n        # Check that all the expected work was performed\n        mock_objects.filter.assert_called_once_with(id=mock_data[\"id\"])\n        mock_objects.filter.return_value.exists.assert_called_once_with()\n        mock_atomic.return_value.__enter__.assert_called_once_with()\n        mock__create_from_stripe_object.assert_called_once_with(\n            mock_data, api_key=djstripe_settings.STRIPE_SECRET_KEY\n        )\n        (\n            mock__create_from_stripe_object.return_value.invoke_webhook_handlers\n        ).assert_called_once_with()\n        # Make sure the event was returned.\n        self.assertEqual(mock__create_from_stripe_object.return_value, result)\n\n    @patch(target=\"djstripe.models.core.transaction.atomic\", autospec=True)\n    @patch.object(target=Event, attribute=\"_create_from_stripe_object\", autospec=True)\n    @patch.object(target=Event, attribute=\"objects\", autospec=True)\n    def test_process_event_exists(\n        self, mock_objects, mock__create_from_stripe_object, mock_atomic\n    ):\n        \"\"\"\n        Test that process event returns the existing event and skips webhook processing\n        when the event already exists.\n        \"\"\"\n        # Set up mocks\n        mock_objects.filter.return_value.exists.return_value = True\n        mock_data = {\"id\": \"foo_id\", \"other_stuff\": \"more_things\"}\n\n        result = Event.process(data=mock_data)\n\n        # Make sure that the db was queried and the existing results used.\n        mock_objects.filter.assert_called_once_with(id=mock_data[\"id\"])\n        mock_objects.filter.return_value.exists.assert_called_once_with()\n        mock_objects.filter.return_value.first.assert_called_once_with()\n        # Make sure the webhook actions and event object creation were not performed.\n        mock_atomic.return_value.__enter__.assert_not_called()\n        mock__create_from_stripe_object.assert_not_called()\n        (\n            mock__create_from_stripe_object.return_value.invoke_webhook_handlers\n        ).assert_not_called()\n        # Make sure the existing event was returned.\n        self.assertEqual(mock_objects.filter.return_value.first.return_value, result)\n\n    @patch(\"djstripe.models.Event.invoke_webhook_handlers\", autospec=True)\n    def test_process_event_failure_rolls_back(self, invoke_webhook_handlers_mock):\n        \"\"\"Test that process event rolls back event creation on error\"\"\"\n\n        class HandlerException(Exception):\n            pass\n\n        invoke_webhook_handlers_mock.side_effect = HandlerException\n        real_create_from_stripe_object = Event._create_from_stripe_object\n\n        def side_effect(*args, **kwargs):\n            return real_create_from_stripe_object(*args, **kwargs)\n\n        event_data = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n\n        self.assertFalse(\n            Event.objects.filter(id=FAKE_EVENT_TRANSFER_CREATED[\"id\"]).exists()\n        )\n\n        with self.assertRaises(HandlerException), patch(\n            \"djstripe.models.Event._create_from_stripe_object\",\n            side_effect=side_effect,\n            autospec=True,\n        ) as create_from_stripe_object_mock:\n            Event.process(data=event_data)\n\n        create_from_stripe_object_mock.assert_called_once_with(\n            event_data, api_key=djstripe_settings.STRIPE_SECRET_KEY\n        )\n        self.assertFalse(\n            Event.objects.filter(id=FAKE_EVENT_TRANSFER_CREATED[\"id\"]).exists()\n        )\n\n    #\n    # Helpers\n    #\n\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def _create_event(self, event_data, event_retrieve_mock):\n        event_data = deepcopy(event_data)\n        event_retrieve_mock.return_value = event_data\n        event = Event.sync_from_stripe_data(event_data)\n        return event\n\n\nclass EventRaceConditionTest(TestCase):\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    def test_process_event_race_condition(\n        self,\n        transfer_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        transfer = Transfer.sync_from_stripe_data(deepcopy(FAKE_TRANSFER))\n        transfer_retrieve_mock.reset_mock()\n        event_data = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n\n        # emulate the race condition in _get_or_create_from_stripe_object where\n        # an object is created by a different request during the call\n        #\n        # Sequence of events:\n        # 1) first Transfer.stripe_objects.get fails with DoesNotExist\n        #    (due to it not existing in reality, but due to our side_effect in the test)\n        # 2) object is really created by a different request in reality\n        # 3) Transfer._create_from_stripe_object fails with IntegrityError due to\n        #    duplicate id\n        # 4) second Transfer.stripe_objects.get succeeds\n        #    (due to being created by step 2 in reality, due to side effect in the test)\n        side_effect = [Transfer.DoesNotExist(), transfer]\n\n        with patch(\n            \"djstripe.models.Transfer.stripe_objects.get\",\n            side_effect=side_effect,\n            autospec=True,\n        ) as transfer_objects_get_mock:\n            Event.process(event_data)\n\n        self.assertEqual(transfer_objects_get_mock.call_count, 2)\n        self.assertEqual(transfer_retrieve_mock.call_count, 1)\n"
  },
  {
    "path": "tests/test_event_handlers.py",
    "content": "\"\"\"\ndj-stripe Event Handler tests\n\"\"\"\nfrom copy import deepcopy\nfrom decimal import Decimal\nfrom unittest.mock import ANY, call, patch\n\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase\nfrom stripe.error import InvalidRequestError\n\nfrom djstripe.enums import SubscriptionStatus\nfrom djstripe.models import (\n    Card,\n    Charge,\n    Coupon,\n    Customer,\n    Dispute,\n    DjstripePaymentMethod,\n    Event,\n    Invoice,\n    InvoiceItem,\n    PaymentMethod,\n    Plan,\n    Price,\n    Subscription,\n    SubscriptionSchedule,\n    Transfer,\n)\nfrom djstripe.models.account import Account\nfrom djstripe.models.billing import TaxId\nfrom djstripe.models.checkout import Session\nfrom djstripe.models.core import File\nfrom djstripe.models.orders import Order\nfrom djstripe.models.payment_methods import BankAccount\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_ACCOUNT,\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_BANK_ACCOUNT_IV,\n    FAKE_CARD,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CARD_II,\n    FAKE_CARD_III,\n    FAKE_CARD_IV,\n    FAKE_CHARGE,\n    FAKE_CHARGE_II,\n    FAKE_COUPON,\n    FAKE_CUSTOM_ACCOUNT,\n    FAKE_CUSTOMER,\n    FAKE_CUSTOMER_II,\n    FAKE_DISPUTE_BALANCE_TRANSACTION,\n    FAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_FULL,\n    FAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_PARTIAL,\n    FAKE_DISPUTE_CHARGE,\n    FAKE_DISPUTE_I,\n    FAKE_DISPUTE_II,\n    FAKE_DISPUTE_III,\n    FAKE_DISPUTE_PAYMENT_INTENT,\n    FAKE_DISPUTE_PAYMENT_METHOD,\n    FAKE_DISPUTE_V_FULL,\n    FAKE_DISPUTE_V_PARTIAL,\n    FAKE_EVENT_ACCOUNT_APPLICATION_AUTHORIZED,\n    FAKE_EVENT_ACCOUNT_APPLICATION_DEAUTHORIZED,\n    FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_CREATED,\n    FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_DELETED,\n    FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_UPDATED,\n    FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_CREATED,\n    FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_DELETED,\n    FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_UPDATED,\n    FAKE_EVENT_CARD_PAYMENT_METHOD_ATTACHED,\n    FAKE_EVENT_CARD_PAYMENT_METHOD_DETACHED,\n    FAKE_EVENT_CHARGE_SUCCEEDED,\n    FAKE_EVENT_CUSTOM_ACCOUNT_UPDATED,\n    FAKE_EVENT_CUSTOMER_CREATED,\n    FAKE_EVENT_CUSTOMER_DELETED,\n    FAKE_EVENT_CUSTOMER_DISCOUNT_CREATED,\n    FAKE_EVENT_CUSTOMER_DISCOUNT_DELETED,\n    FAKE_EVENT_CUSTOMER_SOURCE_CREATED,\n    FAKE_EVENT_CUSTOMER_SOURCE_DELETED,\n    FAKE_EVENT_CUSTOMER_SOURCE_DELETED_DUPE,\n    FAKE_EVENT_CUSTOMER_SUBSCRIPTION_CREATED,\n    FAKE_EVENT_CUSTOMER_SUBSCRIPTION_DELETED,\n    FAKE_EVENT_CUSTOMER_UPDATED,\n    FAKE_EVENT_DISPUTE_CLOSED,\n    FAKE_EVENT_DISPUTE_CREATED,\n    FAKE_EVENT_DISPUTE_FUNDS_REINSTATED_FULL,\n    FAKE_EVENT_DISPUTE_FUNDS_REINSTATED_PARTIAL,\n    FAKE_EVENT_DISPUTE_FUNDS_WITHDRAWN,\n    FAKE_EVENT_DISPUTE_UPDATED,\n    FAKE_EVENT_EXPRESS_ACCOUNT_UPDATED,\n    FAKE_EVENT_FILE_CREATED,\n    FAKE_EVENT_INVOICE_CREATED,\n    FAKE_EVENT_INVOICE_DELETED,\n    FAKE_EVENT_INVOICE_UPCOMING,\n    FAKE_EVENT_INVOICEITEM_CREATED,\n    FAKE_EVENT_INVOICEITEM_DELETED,\n    FAKE_EVENT_ORDER_CANCELLED,\n    FAKE_EVENT_ORDER_COMPLETED,\n    FAKE_EVENT_ORDER_CREATED,\n    FAKE_EVENT_ORDER_PROCESSING,\n    FAKE_EVENT_ORDER_SUBMITTED,\n    FAKE_EVENT_ORDER_UPDATED,\n    FAKE_EVENT_PAYMENT_INTENT_SUCCEEDED_DESTINATION_CHARGE,\n    FAKE_EVENT_PAYMENT_METHOD_ATTACHED,\n    FAKE_EVENT_PAYMENT_METHOD_DETACHED,\n    FAKE_EVENT_PLAN_CREATED,\n    FAKE_EVENT_PLAN_DELETED,\n    FAKE_EVENT_PLAN_REQUEST_IS_OBJECT,\n    FAKE_EVENT_PRICE_CREATED,\n    FAKE_EVENT_PRICE_DELETED,\n    FAKE_EVENT_PRICE_UPDATED,\n    FAKE_EVENT_SESSION_COMPLETED,\n    FAKE_EVENT_STANDARD_ACCOUNT_UPDATED,\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_ABORTED,\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CANCELED,\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_COMPLETED,\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED,\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_EXPIRING,\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_RELEASED,\n    FAKE_EVENT_SUBSCRIPTION_SCHEDULE_UPDATED,\n    FAKE_EVENT_TAX_ID_CREATED,\n    FAKE_EVENT_TAX_ID_DELETED,\n    FAKE_EVENT_TAX_ID_UPDATED,\n    FAKE_EVENT_TRANSFER_CREATED,\n    FAKE_EVENT_TRANSFER_DELETED,\n    FAKE_EXPRESS_ACCOUNT,\n    FAKE_FILEUPLOAD_ICON,\n    FAKE_FILEUPLOAD_LOGO,\n    FAKE_INVOICE,\n    FAKE_INVOICE_II,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_DESTINATION_CHARGE,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PAYMENT_INTENT_II,\n    FAKE_PAYMENT_METHOD_I,\n    FAKE_PAYMENT_METHOD_II,\n    FAKE_PLAN,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_PRICE,\n    FAKE_PRODUCT,\n    FAKE_SESSION_I,\n    FAKE_STANDARD_ACCOUNT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_CANCELED,\n    FAKE_SUBSCRIPTION_III,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_SUBSCRIPTION_SCHEDULE,\n    FAKE_TAX_ID,\n    FAKE_TAX_ID_UPDATED,\n    FAKE_TRANSFER,\n    AssertStripeFksMixin,\n)\n\n\nclass EventTestCase(TestCase):\n    #\n    # Helpers\n    #\n\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def _create_event(self, event_data, event_retrieve_mock, patch_data=None):\n        event_data = deepcopy(event_data)\n\n        if patch_data:\n            event_data.update(patch_data)\n\n        event_retrieve_mock.return_value = event_data\n        event = Event.sync_from_stripe_data(event_data)\n\n        return event\n\n\nclass TestAccountEvents(EventTestCase):\n    def setUp(self):\n        # create a Custom Stripe Account\n        self.custom_account = FAKE_CUSTOM_ACCOUNT.create()\n\n        # create a Standard Stripe Account\n        self.standard_account = FAKE_STANDARD_ACCOUNT.create()\n\n        # create an Express Stripe Account\n        self.express_account = FAKE_EXPRESS_ACCOUNT.create()\n\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_account_deauthorized_event(self, event_retrieve_mock):\n        fake_stripe_event = deepcopy(FAKE_EVENT_ACCOUNT_APPLICATION_DEAUTHORIZED)\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_account_authorized_event(self, event_retrieve_mock):\n        fake_stripe_event = deepcopy(FAKE_EVENT_ACCOUNT_APPLICATION_AUTHORIZED)\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n    # account.external_account.* events are fired for Custom and Express Accounts\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_BANK_ACCOUNT_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_custom_account_external_account_created_bank_account_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_CREATED\n        )\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        # fetch the newly created BankAccount object\n        bankaccount = BankAccount.objects.get(account=self.custom_account)\n\n        # assert the ids of the Bank Account and the Accounts were synced correctly.\n        self.assertEqual(\n            bankaccount.id,\n            fake_stripe_event[\"data\"][\"object\"][\"id\"],\n        )\n        self.assertEqual(\n            self.custom_account.id,\n            fake_stripe_event[\"data\"][\"object\"][\"account\"],\n        )\n\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_BANK_ACCOUNT_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_custom_account_external_account_deleted_bank_account_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_create_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_CREATED\n        )\n\n        event = Event.sync_from_stripe_data(fake_stripe_create_event)\n        event.invoke_webhook_handlers()\n\n        fake_stripe_delete_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_DELETED\n        )\n        event = Event.sync_from_stripe_data(fake_stripe_delete_event)\n        event.invoke_webhook_handlers()\n\n        # assert the BankAccount object no longer exists\n        self.assertFalse(\n            BankAccount.objects.filter(\n                id=fake_stripe_create_event[\"data\"][\"object\"][\"id\"]\n            ).exists()\n        )\n\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_BANK_ACCOUNT_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_custom_account_external_account_updated_bank_account_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_create_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_CREATED\n        )\n\n        event = Event.sync_from_stripe_data(fake_stripe_create_event)\n        event.invoke_webhook_handlers()\n\n        fake_stripe_update_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_UPDATED\n        )\n        event = Event.sync_from_stripe_data(fake_stripe_update_event)\n        event.invoke_webhook_handlers()\n\n        # fetch the updated BankAccount object\n        bankaccount = BankAccount.objects.get(account=self.custom_account)\n\n        # assert we are updating the account_holder_name\n        self.assertNotEqual(\n            fake_stripe_update_event[\"data\"][\"object\"][\"account_holder_name\"],\n            fake_stripe_create_event[\"data\"][\"object\"][\"account_holder_name\"],\n        )\n\n        # assert the account_holder_name got updated\n        self.assertNotEqual(\n            bankaccount.account_holder_name,\n            fake_stripe_update_event[\"data\"][\"object\"][\"account_holder_name\"],\n        )\n\n        # assert the expected BankAccount object got updated\n        self.assertEqual(\n            bankaccount.id, fake_stripe_create_event[\"data\"][\"object\"][\"id\"]\n        )\n\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_CARD_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_custom_account_external_account_created_card_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_CREATED)\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        # fetch the newly created Card object\n        card = Card.objects.get(account=self.custom_account)\n\n        # assert the ids of the Card and the Accounts were synced correctly.\n        self.assertEqual(\n            card.id,\n            fake_stripe_event[\"data\"][\"object\"][\"id\"],\n        )\n        self.assertEqual(\n            self.custom_account.id,\n            fake_stripe_event[\"data\"][\"object\"][\"account\"],\n        )\n\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_CARD_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_custom_account_external_account_deleted_card_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_create_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_CREATED\n        )\n\n        event = Event.sync_from_stripe_data(fake_stripe_create_event)\n        event.invoke_webhook_handlers()\n\n        fake_stripe_delete_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_DELETED\n        )\n        event = Event.sync_from_stripe_data(fake_stripe_delete_event)\n        event.invoke_webhook_handlers()\n\n        # assert Card Object no longer exists\n        self.assertFalse(\n            Card.objects.filter(\n                id=fake_stripe_create_event[\"data\"][\"object\"][\"id\"]\n            ).exists()\n        )\n\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_CARD_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_custom_account_external_account_updated_card_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_create_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_CREATED\n        )\n\n        event = Event.sync_from_stripe_data(fake_stripe_create_event)\n        event.invoke_webhook_handlers()\n\n        fake_stripe_update_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_UPDATED\n        )\n        event = Event.sync_from_stripe_data(fake_stripe_update_event)\n        event.invoke_webhook_handlers()\n\n        # fetch the updated Card object\n        card = Card.objects.get(account=self.custom_account)\n\n        # assert we are updating the name\n        self.assertNotEqual(\n            fake_stripe_update_event[\"data\"][\"object\"][\"name\"],\n            fake_stripe_create_event[\"data\"][\"object\"][\"name\"],\n        )\n\n        # assert the name got updated\n        self.assertNotEqual(\n            card.name, fake_stripe_update_event[\"data\"][\"object\"][\"name\"]\n        )\n\n        # assert the expected Card object got updated\n        self.assertEqual(card.id, fake_stripe_create_event[\"data\"][\"object\"][\"id\"])\n\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_BANK_ACCOUNT_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_express_account_external_account_created_bank_account_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_CREATED\n        )\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        # fetch the newly created BankAccount object\n        bankaccount = BankAccount.objects.get(account=self.express_account)\n\n        # assert the ids of the Bank Account and the Accounts were synced correctly.\n        self.assertEqual(\n            bankaccount.id,\n            fake_stripe_event[\"data\"][\"object\"][\"id\"],\n        )\n        self.assertEqual(\n            self.express_account.id,\n            fake_stripe_event[\"data\"][\"object\"][\"account\"],\n        )\n\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_BANK_ACCOUNT_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_express_account_external_account_deleted_bank_account_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_create_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_CREATED\n        )\n\n        event = Event.sync_from_stripe_data(fake_stripe_create_event)\n        event.invoke_webhook_handlers()\n\n        fake_stripe_delete_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_DELETED\n        )\n        event = Event.sync_from_stripe_data(fake_stripe_delete_event)\n        event.invoke_webhook_handlers()\n\n        # assert the BankAccount object no longer exists\n        self.assertFalse(\n            BankAccount.objects.filter(\n                id=fake_stripe_create_event[\"data\"][\"object\"][\"id\"]\n            ).exists()\n        )\n\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_BANK_ACCOUNT_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_express_account_external_account_updated_bank_account_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_create_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_CREATED\n        )\n\n        event = Event.sync_from_stripe_data(fake_stripe_create_event)\n        event.invoke_webhook_handlers()\n\n        fake_stripe_update_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_BANK_ACCOUNT_UPDATED\n        )\n        event = Event.sync_from_stripe_data(fake_stripe_update_event)\n        event.invoke_webhook_handlers()\n\n        # fetch the updated BankAccount object\n        bankaccount = BankAccount.objects.get(account=self.express_account)\n\n        # assert we are updating the account_holder_name\n        self.assertNotEqual(\n            fake_stripe_update_event[\"data\"][\"object\"][\"account_holder_name\"],\n            fake_stripe_create_event[\"data\"][\"object\"][\"account_holder_name\"],\n        )\n\n        # assert the account_holder_name got updated\n        self.assertNotEqual(\n            bankaccount.account_holder_name,\n            fake_stripe_update_event[\"data\"][\"object\"][\"account_holder_name\"],\n        )\n\n        # assert the expected BankAccount object got updated\n        self.assertEqual(\n            bankaccount.id, fake_stripe_create_event[\"data\"][\"object\"][\"id\"]\n        )\n\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_CARD_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_express_account_external_account_created_card_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_CREATED)\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        # fetch the newly created Card object\n        card = Card.objects.get(account=self.express_account)\n\n        # assert the ids of the Card and the Accounts were synced correctly.\n        self.assertEqual(\n            card.id,\n            fake_stripe_event[\"data\"][\"object\"][\"id\"],\n        )\n        self.assertEqual(\n            self.express_account.id,\n            fake_stripe_event[\"data\"][\"object\"][\"account\"],\n        )\n\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_CARD_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_express_account_external_account_deleted_card_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_create_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_CREATED\n        )\n\n        event = Event.sync_from_stripe_data(fake_stripe_create_event)\n        event.invoke_webhook_handlers()\n\n        fake_stripe_delete_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_DELETED\n        )\n        event = Event.sync_from_stripe_data(fake_stripe_delete_event)\n        event.invoke_webhook_handlers()\n\n        # assert Card Object no longer exists\n        self.assertFalse(\n            Card.objects.filter(\n                id=fake_stripe_create_event[\"data\"][\"object\"][\"id\"]\n            ).exists()\n        )\n\n    @patch(\n        \"stripe.Account.retrieve_external_account\",\n        return_value=deepcopy(FAKE_CARD_IV),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_express_account_external_account_updated_card_event(\n        self, event_retrieve_mock, account_retrieve_external_account_mock\n    ):\n        fake_stripe_create_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_CREATED\n        )\n\n        event = Event.sync_from_stripe_data(fake_stripe_create_event)\n        event.invoke_webhook_handlers()\n\n        fake_stripe_update_event = deepcopy(\n            FAKE_EVENT_ACCOUNT_EXTERNAL_ACCOUNT_CARD_UPDATED\n        )\n        event = Event.sync_from_stripe_data(fake_stripe_update_event)\n        event.invoke_webhook_handlers()\n\n        # fetch the updated Card object\n        card = Card.objects.get(account=self.express_account)\n\n        # assert we are updating the name\n        self.assertNotEqual(\n            fake_stripe_update_event[\"data\"][\"object\"][\"name\"],\n            fake_stripe_create_event[\"data\"][\"object\"][\"name\"],\n        )\n\n        # assert the name got updated\n        self.assertNotEqual(\n            card.name, fake_stripe_update_event[\"data\"][\"object\"][\"name\"]\n        )\n\n        # assert the expected Card object got updated\n        self.assertEqual(card.id, fake_stripe_create_event[\"data\"][\"object\"][\"id\"])\n\n    # account.updated events\n\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_STANDARD_ACCOUNT_UPDATED[\"data\"][\"object\"]),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_standard_account_updated_event(\n        self, event_retrieve_mock, account_retrieve_mock\n    ):\n        # fetch the Stripe Account\n        standard_account = self.standard_account\n\n        # assert metadata is empty\n        self.assertEqual(standard_account.metadata, {})\n\n        fake_stripe_update_event = deepcopy(FAKE_EVENT_STANDARD_ACCOUNT_UPDATED)\n\n        event = Event.sync_from_stripe_data(fake_stripe_update_event)\n        event.invoke_webhook_handlers()\n\n        # fetch the updated Account object\n        updated_standard_account = Account.objects.get(id=standard_account.id)\n\n        # assert we are updating the metadata\n        self.assertNotEqual(\n            updated_standard_account.metadata,\n            standard_account.metadata,\n        )\n\n        # assert the meta got updated\n        self.assertEqual(\n            updated_standard_account.metadata,\n            fake_stripe_update_event[\"data\"][\"object\"][\"metadata\"],\n        )\n\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_EXPRESS_ACCOUNT_UPDATED[\"data\"][\"object\"]),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_express_account_updated_event(\n        self, event_retrieve_mock, account_retrieve_mock\n    ):\n        # fetch the Stripe Account\n        express_account = self.express_account\n\n        # assert metadata is empty\n        self.assertEqual(express_account.metadata, {})\n\n        fake_stripe_update_event = deepcopy(FAKE_EVENT_EXPRESS_ACCOUNT_UPDATED)\n\n        event = Event.sync_from_stripe_data(fake_stripe_update_event)\n        event.invoke_webhook_handlers()\n\n        # fetch the updated Account object\n        updated_express_account = Account.objects.get(id=express_account.id)\n\n        # assert we are updating the metadata\n        self.assertNotEqual(\n            updated_express_account.metadata,\n            express_account.metadata,\n        )\n\n        # assert the meta got updated\n        self.assertEqual(\n            updated_express_account.metadata,\n            fake_stripe_update_event[\"data\"][\"object\"][\"metadata\"],\n        )\n\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_CUSTOM_ACCOUNT_UPDATED[\"data\"][\"object\"]),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_custom_account_updated_event(\n        self, event_retrieve_mock, account_retrieve_mock\n    ):\n        # fetch the Stripe Account\n        custom_account = self.custom_account\n\n        # assert metadata is empty\n        self.assertEqual(custom_account.metadata, {})\n\n        fake_stripe_update_event = deepcopy(FAKE_EVENT_CUSTOM_ACCOUNT_UPDATED)\n\n        event = Event.sync_from_stripe_data(fake_stripe_update_event)\n        event.invoke_webhook_handlers()\n\n        # fetch the updated Account object\n        updated_custom_account = Account.objects.get(id=custom_account.id)\n\n        # assert we are updating the metadata\n        self.assertNotEqual(\n            updated_custom_account.metadata,\n            custom_account.metadata,\n        )\n\n        # assert the meta got updated\n        self.assertEqual(\n            updated_custom_account.metadata,\n            fake_stripe_update_event[\"data\"][\"object\"][\"metadata\"],\n        )\n\n\nclass TestChargeEvents(EventTestCase):\n    def setUp(self):\n        # create a Stripe Platform Account\n        self.account = FAKE_PLATFORM_ACCOUNT.create()\n\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n    )\n    @patch(\"stripe.Charge.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test_charge_created(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        event_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        customer_retrieve_mock,\n        account_mock,\n    ):\n        FAKE_CUSTOMER.create_for_user(self.user)\n        fake_stripe_event = deepcopy(FAKE_EVENT_CHARGE_SUCCEEDED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        charge_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n        account_mock.return_value = self.account\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        charge = Charge.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(\n            charge.amount,\n            fake_stripe_event[\"data\"][\"object\"][\"amount\"] / Decimal(\"100\"),\n        )\n        self.assertEqual(charge.status, fake_stripe_event[\"data\"][\"object\"][\"status\"])\n\n\nclass TestCheckoutEvents(EventTestCase):\n    def setUp(self):\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n    @patch(\n        \"stripe.checkout.Session.retrieve\", return_value=FAKE_SESSION_I, autospec=True\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_checkout_session_completed(\n        self,\n        event_retrieve_mock,\n        payment_intent_retrieve_mock,\n        customer_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        session_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_SESSION_COMPLETED)\n        event_retrieve_mock.return_value = fake_stripe_event\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        session = Session.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(session.customer.id, self.customer.id)\n\n    @patch(\n        \"stripe.checkout.Session.retrieve\", return_value=FAKE_SESSION_I, autospec=True\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_checkout_session_async_payment_succeeded(\n        self,\n        event_retrieve_mock,\n        payment_intent_retrieve_mock,\n        customer_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        session_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_SESSION_COMPLETED)\n        fake_stripe_event[\"type\"] = \"checkout.session.async_payment_succeeded\"\n\n        event_retrieve_mock.return_value = fake_stripe_event\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        session = Session.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(session.customer.id, self.customer.id)\n\n    @patch(\n        \"stripe.checkout.Session.retrieve\", return_value=FAKE_SESSION_I, autospec=True\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_checkout_session_async_payment_failed(\n        self,\n        event_retrieve_mock,\n        payment_intent_retrieve_mock,\n        customer_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        session_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_SESSION_COMPLETED)\n        fake_stripe_event[\"type\"] = \"checkout.session.async_payment_failed\"\n\n        event_retrieve_mock.return_value = fake_stripe_event\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        session = Session.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(session.customer.id, self.customer.id)\n\n    @patch(\"stripe.checkout.Session.retrieve\", autospec=True)\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.modify\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=FAKE_PAYMENT_INTENT_I,\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_checkout_session_completed_customer_subscriber_added(\n        self,\n        event_retrieve_mock,\n        payment_intent_retrieve_mock,\n        customer_modify_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        session_retrieve_mock,\n    ):\n        # because create_for_user method adds subscriber\n        self.customer.subcriber = None\n        self.customer.save()\n\n        # update metadata in deepcopied FAKE_SEESION_1 Object\n        fake_stripe_event = deepcopy(FAKE_EVENT_SESSION_COMPLETED)\n        fake_stripe_event[\"data\"][\"object\"][\"metadata\"] = {\n            \"djstripe_subscriber\": self.user.id\n        }\n        event_retrieve_mock.return_value = fake_stripe_event\n\n        # update metadata in FAKE_SEESION_1 Object\n        fake_stripe_session = deepcopy(FAKE_SESSION_I)\n        fake_stripe_session[\"metadata\"] = {\"djstripe_subscriber\": self.user.id}\n        session_retrieve_mock.return_value = fake_stripe_session\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        # refresh self.customer from db\n        self.customer.refresh_from_db()\n\n        session = Session.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(session.customer.id, self.customer.id)\n        self.assertEqual(self.customer.subscriber, self.user)\n        self.assertEqual(self.customer.metadata, {\"djstripe_subscriber\": self.user.id})\n\n\nclass TestCustomerEvents(EventTestCase):\n    def setUp(self):\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n    @patch(\"stripe.Customer.retrieve\", return_value=FAKE_CUSTOMER, autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_customer_created(self, event_retrieve_mock, customer_retrieve_mock):\n        fake_stripe_event = deepcopy(FAKE_EVENT_CUSTOMER_CREATED)\n        event_retrieve_mock.return_value = fake_stripe_event\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        customer = Customer.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(\n            customer.balance, fake_stripe_event[\"data\"][\"object\"][\"balance\"]\n        )\n        self.assertEqual(\n            customer.currency, fake_stripe_event[\"data\"][\"object\"][\"currency\"]\n        )\n\n    @patch(\"stripe.Customer.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_customer_metadata_created(\n        self, event_retrieve_mock, customer_retrieve_mock\n    ):\n        fake_customer = deepcopy(FAKE_CUSTOMER)\n        fake_customer[\"metadata\"] = {\"djstripe_subscriber\": self.user.id}\n\n        fake_stripe_event = deepcopy(FAKE_EVENT_CUSTOMER_CREATED)\n\n        fake_stripe_event[\"data\"][\"object\"] = fake_customer\n\n        event_retrieve_mock.return_value = fake_stripe_event\n        customer_retrieve_mock.return_value = fake_customer\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        customer = Customer.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(\n            customer.balance, fake_stripe_event[\"data\"][\"object\"][\"balance\"]\n        )\n        self.assertEqual(\n            customer.currency, fake_stripe_event[\"data\"][\"object\"][\"currency\"]\n        )\n        self.assertEqual(customer.subscriber, self.user)\n        self.assertEqual(customer.metadata, {\"djstripe_subscriber\": self.user.id})\n\n    @patch(\"stripe.Customer.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_customer_metadata_updated(\n        self, event_retrieve_mock, customer_retrieve_mock\n    ):\n        fake_customer = deepcopy(FAKE_CUSTOMER)\n        fake_customer[\"metadata\"] = {\"djstripe_subscriber\": self.user.id}\n\n        fake_stripe_event = deepcopy(FAKE_EVENT_CUSTOMER_UPDATED)\n\n        fake_stripe_event[\"data\"][\"object\"] = fake_customer\n\n        event_retrieve_mock.return_value = fake_stripe_event\n        customer_retrieve_mock.return_value = fake_customer\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        customer = Customer.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(\n            customer.balance, fake_stripe_event[\"data\"][\"object\"][\"balance\"]\n        )\n        self.assertEqual(\n            customer.currency, fake_stripe_event[\"data\"][\"object\"][\"currency\"]\n        )\n        self.assertEqual(customer.subscriber, self.user)\n        self.assertEqual(customer.metadata, {\"djstripe_subscriber\": self.user.id})\n\n    @patch(\n        \"stripe.Customer.delete_source\",\n        autospec=True,\n    )\n    @patch(\"stripe.Customer.delete\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve_source\",\n        side_effect=[deepcopy(FAKE_CARD), deepcopy(FAKE_CARD_III)],\n        autospec=True,\n    )\n    @patch(\"stripe.Customer.retrieve\", return_value=FAKE_CUSTOMER, autospec=True)\n    def test_customer_deleted(\n        self,\n        customer_retrieve_mock,\n        customer_retrieve_source_mock,\n        customer_delete_mock,\n        customer_source_delete_mock,\n    ):\n        FAKE_CUSTOMER.create_for_user(self.user)\n        event = self._create_event(FAKE_EVENT_CUSTOMER_CREATED)\n        event.invoke_webhook_handlers()\n\n        event = self._create_event(FAKE_EVENT_CUSTOMER_DELETED)\n        event.invoke_webhook_handlers()\n        customer = Customer.objects.get(id=FAKE_CUSTOMER[\"id\"])\n        self.assertIsNotNone(customer.date_purged)\n\n    @patch(\"stripe.Coupon.retrieve\", return_value=FAKE_COUPON, autospec=True)\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=FAKE_EVENT_CUSTOMER_DISCOUNT_CREATED,\n        autospec=True,\n    )\n    def test_customer_discount_created(self, event_retrieve_mock, coupon_retrieve_mock):\n        fake_stripe_event = deepcopy(FAKE_EVENT_CUSTOMER_DISCOUNT_CREATED)\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        self.assertIsNotNone(event.customer)\n        self.assertEqual(event.customer.id, FAKE_CUSTOMER[\"id\"])\n        self.assertIsNotNone(event.customer.coupon)\n\n    @patch(\"stripe.Coupon.retrieve\", return_value=FAKE_COUPON, autospec=True)\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=FAKE_EVENT_CUSTOMER_DISCOUNT_DELETED,\n        autospec=True,\n    )\n    def test_customer_discount_deleted(self, event_retrieve_mock, coupon_retrieve_mock):\n        coupon = Coupon.sync_from_stripe_data(FAKE_COUPON)\n        self.customer.coupon = coupon\n\n        fake_stripe_event = deepcopy(FAKE_EVENT_CUSTOMER_DISCOUNT_DELETED)\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        self.assertIsNotNone(event.customer)\n        self.assertEqual(event.customer.id, FAKE_CUSTOMER[\"id\"])\n        self.assertIsNone(event.customer.coupon)\n\n    @patch(\"stripe.Customer.retrieve\", return_value=FAKE_CUSTOMER, autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve_source\",\n        return_value=deepcopy(FAKE_CARD),\n        autospec=True,\n    )\n    def test_customer_card_created(\n        self, customer_retrieve_source_mock, event_retrieve_mock, customer_retrieve_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_CUSTOMER_SOURCE_CREATED)\n        event_retrieve_mock.return_value = fake_stripe_event\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        card = Card.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertIn(card, self.customer.legacy_cards.all())\n        self.assertEqual(card.brand, fake_stripe_event[\"data\"][\"object\"][\"brand\"])\n        self.assertEqual(card.last4, fake_stripe_event[\"data\"][\"object\"][\"last4\"])\n\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    @patch(\"stripe.Customer.retrieve\", return_value=FAKE_CUSTOMER, autospec=True)\n    def test_customer_unknown_source_created(\n        self, customer_retrieve_mock, event_retrieve_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_CUSTOMER_SOURCE_CREATED)\n        fake_stripe_event[\"data\"][\"object\"][\"object\"] = \"unknown\"\n        fake_stripe_event[\"data\"][\"object\"][\n            \"id\"\n        ] = \"card_xxx_test_customer_unk_source_created\"\n        event_retrieve_mock.return_value = fake_stripe_event\n\n        FAKE_CUSTOMER.create_for_user(self.user)\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        self.assertFalse(\n            Card.objects.filter(id=fake_stripe_event[\"data\"][\"object\"][\"id\"]).exists()\n        )\n\n    @patch(\"stripe.Customer.retrieve\", return_value=FAKE_CUSTOMER, autospec=True)\n    def test_customer_default_source_deleted(self, customer_retrieve_mock):\n        self.customer.default_source = DjstripePaymentMethod.objects.get(\n            id=FAKE_CARD[\"id\"]\n        )\n        self.customer.save()\n        self.assertIsNotNone(self.customer.default_source)\n\n        event = self._create_event(FAKE_EVENT_CUSTOMER_SOURCE_DELETED)\n        event.invoke_webhook_handlers()\n\n        # fetch the customer. Doubles up as a check that the customer didn't get\n        # deleted\n        customer = Customer.objects.get(id=FAKE_CUSTOMER[\"id\"])\n        self.assertIsNone(customer.default_source)\n\n    @patch(\"stripe.Customer.retrieve\", return_value=FAKE_CUSTOMER, autospec=True)\n    def test_customer_source_double_delete(self, customer_retrieve_mock):\n        event = self._create_event(FAKE_EVENT_CUSTOMER_SOURCE_DELETED)\n        event.invoke_webhook_handlers()\n\n        event = self._create_event(FAKE_EVENT_CUSTOMER_SOURCE_DELETED_DUPE)\n        event.invoke_webhook_handlers()\n\n        # fetch the customer. Doubles up as a check that the customer didn't get\n        # deleted\n        customer = Customer.objects.get(id=FAKE_CUSTOMER[\"id\"])\n        self.assertIsNone(customer.default_source)\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\"stripe.Subscription.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    @patch(\"stripe.Customer.retrieve\", return_value=FAKE_CUSTOMER, autospec=True)\n    def test_customer_subscription_created(\n        self,\n        customer_retrieve_mock,\n        event_retrieve_mock,\n        product_retrieve_mock,\n        subscription_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_CUSTOMER_SUBSCRIPTION_CREATED)\n        event_retrieve_mock.return_value = fake_stripe_event\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # latest_invoice has to be None for a Subscription that has not been created yet.\n        fake_subscription[\"latest_invoice\"] = None\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        subscription = Subscription.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n        self.assertIn(subscription, self.customer.subscriptions.all())\n        self.assertEqual(\n            subscription.status, fake_stripe_event[\"data\"][\"object\"][\"status\"]\n        )\n        self.assertEqual(\n            subscription.quantity, fake_stripe_event[\"data\"][\"object\"][\"quantity\"]\n        )\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_customer_subscription_deleted(\n        self,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        plan_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        # A just created Subscription cannot have latest_invoice\n        fake_subscription[\"latest_invoice\"] = None\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        fake_event = deepcopy(FAKE_EVENT_CUSTOMER_SUBSCRIPTION_CREATED)\n        fake_event[\"data\"][\"object\"] = fake_subscription\n\n        event = self._create_event(fake_event)\n        event.invoke_webhook_handlers()\n\n        sub = Subscription.objects.get(id=fake_subscription[\"id\"])\n        self.assertEqual(sub.status, SubscriptionStatus.active)\n\n        # create invoice for latest_invoice in subscription to work.\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        subscription_retrieve_mock.return_value = deepcopy(FAKE_SUBSCRIPTION_CANCELED)\n\n        event = self._create_event(FAKE_EVENT_CUSTOMER_SUBSCRIPTION_DELETED)\n        event.invoke_webhook_handlers()\n\n        sub = Subscription.objects.get(id=FAKE_SUBSCRIPTION[\"id\"])\n        # Check that Subscription is canceled and not deleted\n        self.assertEqual(sub.status, SubscriptionStatus.canceled)\n        self.assertIsNotNone(sub.canceled_at)\n\n    @patch(\"stripe.Customer.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_customer_bogus_event_type(\n        self, event_retrieve_mock, customer_retrieve_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_CUSTOMER_CREATED)\n        fake_stripe_event[\"data\"][\"object\"][\"customer\"] = fake_stripe_event[\"data\"][\n            \"object\"\n        ][\"id\"]\n        fake_stripe_event[\"type\"] = \"customer.praised\"\n\n        event_retrieve_mock.return_value = fake_stripe_event\n        customer_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n\nclass TestDisputeEvents(EventTestCase):\n    def setUp(self):\n        self.user = get_user_model().objects.create_user(\n            username=\"fake_customer_1\", email=FAKE_CUSTOMER[\"email\"]\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_PAYMENT_INTENT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_BALANCE_TRANSACTION),\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Dispute.retrieve\", return_value=deepcopy(FAKE_DISPUTE_I), autospec=True\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_DISPUTE_CREATED),\n        autospec=True,\n    )\n    def test_dispute_created(\n        self,\n        event_retrieve_mock,\n        dispute_retrieve_mock,\n        file_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        charge_retrieve_mock,\n        payment_intent_retrieve_mock,\n        payment_method_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_DISPUTE_CREATED)\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n        dispute = Dispute.objects.get()\n        self.assertEqual(dispute.id, FAKE_DISPUTE_I[\"id\"])\n\n    # funds get withdrawn from the account as soon as a charge is\n    # disputed so practically there is no difference between\n    # charge.dispute.created and charge.dispute.funds_withdrawn\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_PAYMENT_INTENT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_BALANCE_TRANSACTION),\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Dispute.retrieve\", return_value=deepcopy(FAKE_DISPUTE_II), autospec=True\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_DISPUTE_FUNDS_WITHDRAWN),\n        autospec=True,\n    )\n    def test_dispute_funds_withdrawn(\n        self,\n        event_retrieve_mock,\n        dispute_retrieve_mock,\n        file_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        charge_retrieve_mock,\n        payment_intent_retrieve_mock,\n        payment_method_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_DISPUTE_FUNDS_WITHDRAWN)\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n        dispute = Dispute.objects.get()\n        self.assertEqual(dispute.id, FAKE_DISPUTE_II[\"id\"])\n\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_PAYMENT_INTENT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_BALANCE_TRANSACTION),\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Dispute.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_III),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_DISPUTE_UPDATED),\n        autospec=True,\n    )\n    def test_dispute_updated(\n        self,\n        event_retrieve_mock,\n        dispute_retrieve_mock,\n        file_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        charge_retrieve_mock,\n        payment_intent_retrieve_mock,\n        payment_method_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_DISPUTE_UPDATED)\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n        dispute = Dispute.objects.get()\n        self.assertEqual(dispute.id, FAKE_DISPUTE_III[\"id\"])\n\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_PAYMENT_INTENT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_BALANCE_TRANSACTION),\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Dispute.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_III),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_DISPUTE_CLOSED),\n        autospec=True,\n    )\n    def test_dispute_closed(\n        self,\n        event_retrieve_mock,\n        dispute_retrieve_mock,\n        file_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        charge_retrieve_mock,\n        payment_intent_retrieve_mock,\n        payment_method_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_DISPUTE_CLOSED)\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n        dispute = Dispute.objects.get()\n        self.assertEqual(dispute.id, FAKE_DISPUTE_III[\"id\"])\n\n    # funds get reinstated after the dispute is closed\n    # includes full fund reinstatements as well as partial refunds\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_PAYMENT_INTENT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        side_effect=[\n            FAKE_DISPUTE_BALANCE_TRANSACTION,\n            FAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_FULL,\n        ],\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Dispute.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_V_FULL),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_DISPUTE_FUNDS_REINSTATED_FULL),\n        autospec=True,\n    )\n    def test_dispute_funds_reinstated_full(\n        self,\n        event_retrieve_mock,\n        dispute_retrieve_mock,\n        file_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        charge_retrieve_mock,\n        payment_intent_retrieve_mock,\n        payment_method_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_DISPUTE_FUNDS_REINSTATED_FULL)\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n        dispute = Dispute.objects.get()\n        self.assertEqual(dispute.id, FAKE_DISPUTE_V_FULL[\"id\"])\n\n    # funds get reinstated after the dispute is closed\n    # includes full fund reinstatements as well as partial refunds\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_PAYMENT_INTENT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        side_effect=[\n            FAKE_DISPUTE_BALANCE_TRANSACTION,\n            FAKE_DISPUTE_BALANCE_TRANSACTION_REFUND_PARTIAL,\n        ],\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Dispute.retrieve\",\n        return_value=deepcopy(FAKE_DISPUTE_V_PARTIAL),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_DISPUTE_FUNDS_REINSTATED_PARTIAL),\n        autospec=True,\n    )\n    def test_dispute_funds_reinstated_partial(\n        self,\n        event_retrieve_mock,\n        dispute_retrieve_mock,\n        file_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        charge_retrieve_mock,\n        payment_intent_retrieve_mock,\n        payment_method_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_DISPUTE_FUNDS_REINSTATED_PARTIAL)\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n        dispute = Dispute.objects.get()\n        self.assertGreaterEqual(len(dispute.balance_transactions), 2)\n        self.assertEqual(dispute.id, FAKE_DISPUTE_V_PARTIAL[\"id\"])\n\n\nclass TestFileEvents(EventTestCase):\n    def setUp(self):\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n    @patch(\n        \"stripe.File.retrieve\",\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_FILE_CREATED),\n        autospec=True,\n    )\n    def test_file_created(self, event_retrieve_mock, file_retrieve_mock):\n        fake_stripe_event = deepcopy(FAKE_EVENT_FILE_CREATED)\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n        file = File.objects.get()\n        self.assertEqual(file.id, FAKE_FILEUPLOAD_ICON[\"id\"])\n\n\nclass TestInvoiceEvents(EventTestCase):\n    def setUp(self):\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_invoice_created_no_existing_customer(\n        self,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        event_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_INVOICE_CREATED)\n        event_retrieve_mock.return_value = fake_stripe_event\n\n        invoice_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        self.assertEqual(Customer.objects.count(), 1)\n        customer = Customer.objects.get()\n        self.assertEqual(customer.subscriber, None)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\"stripe.Invoice.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_invoice_created(\n        self,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        event_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        FAKE_CUSTOMER.create_for_user(self.user)\n\n        fake_stripe_event = deepcopy(FAKE_EVENT_INVOICE_CREATED)\n        event_retrieve_mock.return_value = fake_stripe_event\n\n        invoice_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        invoice = Invoice.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(\n            invoice.amount_due,\n            fake_stripe_event[\"data\"][\"object\"][\"amount_due\"] / Decimal(\"100\"),\n        )\n        self.assertEqual(invoice.paid, fake_stripe_event[\"data\"][\"object\"][\"paid\"])\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_invoice_deleted(\n        self,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        FAKE_CUSTOMER.create_for_user(self.user)\n\n        event = self._create_event(FAKE_EVENT_INVOICE_CREATED)\n        event.invoke_webhook_handlers()\n\n        Invoice.objects.get(id=FAKE_INVOICE[\"id\"])\n\n        event = self._create_event(FAKE_EVENT_INVOICE_DELETED)\n        event.invoke_webhook_handlers()\n\n        with self.assertRaises(Invoice.DoesNotExist):\n            Invoice.objects.get(id=FAKE_INVOICE[\"id\"])\n\n    def test_invoice_upcoming(self):\n        # Ensure that invoice upcoming events are processed - No actual\n        # process occurs so the operation is an effective no-op.\n        event = self._create_event(FAKE_EVENT_INVOICE_UPCOMING)\n        event.invoke_webhook_handlers()\n\n\nclass TestInvoiceItemEvents(EventTestCase):\n    def setUp(self):\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE_II), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE_II), autospec=True\n    )\n    @patch(\"stripe.InvoiceItem.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    def test_invoiceitem_created(\n        self,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        event_retrieve_mock,\n        invoiceitem_retrieve_mock,\n        invoice_retrieve_mock,\n        paymentintent_retrieve_mock,\n        paymentmethod_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_II)\n        fake_payment_intent[\"invoice\"] = FAKE_INVOICE_II[\"id\"]\n        paymentintent_retrieve_mock.return_value = fake_payment_intent\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION_III)\n        fake_subscription[\"latest_invoice\"] = FAKE_INVOICE_II[\"id\"]\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        fake_stripe_event = deepcopy(FAKE_EVENT_INVOICEITEM_CREATED)\n        event_retrieve_mock.return_value = fake_stripe_event\n\n        invoiceitem_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        fake_card = deepcopy(FAKE_CARD_II)\n        fake_card[\"customer\"] = None\n        # create Card for FAKE_CUSTOMER_III\n        Card.sync_from_stripe_data(fake_card)\n\n        # create invoice\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE_II))\n\n        FAKE_CUSTOMER_II.create_for_user(self.user)\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        invoiceitem = InvoiceItem.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n        self.assertEqual(\n            invoiceitem.amount,\n            fake_stripe_event[\"data\"][\"object\"][\"amount\"] / Decimal(\"100\"),\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE_II), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE_II), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    def test_invoiceitem_deleted(\n        self,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        invoiceitem_retrieve_mock,\n        invoice_retrieve_mock,\n        paymentintent_retrieve_mock,\n        paymentmethod_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_II)\n        fake_payment_intent[\"invoice\"] = FAKE_INVOICE_II[\"id\"]\n        paymentintent_retrieve_mock.return_value = fake_payment_intent\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION_III)\n        fake_subscription[\"latest_invoice\"] = FAKE_INVOICE_II[\"id\"]\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        fake_card = deepcopy(FAKE_CARD_II)\n        fake_card[\"customer\"] = None\n        # create Card for FAKE_CUSTOMER_III\n        Card.sync_from_stripe_data(fake_card)\n\n        # create invoice\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE_II))\n\n        FAKE_CUSTOMER_II.create_for_user(self.user)\n\n        event = self._create_event(FAKE_EVENT_INVOICEITEM_CREATED)\n        event.invoke_webhook_handlers()\n\n        InvoiceItem.objects.get(id=FAKE_INVOICEITEM[\"id\"])\n\n        event = self._create_event(FAKE_EVENT_INVOICEITEM_DELETED)\n        event.invoke_webhook_handlers()\n\n        with self.assertRaises(InvoiceItem.DoesNotExist):\n            InvoiceItem.objects.get(id=FAKE_INVOICEITEM[\"id\"])\n\n\nclass TestPlanEvents(EventTestCase):\n    @patch(\"stripe.Plan.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_plan_created(\n        self, product_retrieve_mock, event_retrieve_mock, plan_retrieve_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_PLAN_CREATED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        plan_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        plan = Plan.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(plan.nickname, fake_stripe_event[\"data\"][\"object\"][\"nickname\"])\n\n    @patch(\"stripe.Plan.retrieve\", return_value=FAKE_PLAN, autospec=True)\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=FAKE_EVENT_PLAN_REQUEST_IS_OBJECT,\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_plan_updated_request_object(\n        self, product_retrieve_mock, event_retrieve_mock, plan_retrieve_mock\n    ):\n        plan_retrieve_mock.return_value = FAKE_EVENT_PLAN_REQUEST_IS_OBJECT[\"data\"][\n            \"object\"\n        ]\n\n        event = Event.sync_from_stripe_data(FAKE_EVENT_PLAN_REQUEST_IS_OBJECT)\n        event.invoke_webhook_handlers()\n\n        plan = Plan.objects.get(\n            id=FAKE_EVENT_PLAN_REQUEST_IS_OBJECT[\"data\"][\"object\"][\"id\"]\n        )\n        self.assertEqual(\n            plan.nickname,\n            FAKE_EVENT_PLAN_REQUEST_IS_OBJECT[\"data\"][\"object\"][\"nickname\"],\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=FAKE_PLAN, autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_plan_deleted(self, product_retrieve_mock, plan_retrieve_mock):\n        event = self._create_event(FAKE_EVENT_PLAN_CREATED)\n        event.invoke_webhook_handlers()\n\n        Plan.objects.get(id=FAKE_PLAN[\"id\"])\n\n        event = self._create_event(FAKE_EVENT_PLAN_DELETED)\n        event.invoke_webhook_handlers()\n\n        with self.assertRaises(Plan.DoesNotExist):\n            Plan.objects.get(id=FAKE_PLAN[\"id\"])\n\n\nclass TestPriceEvents(EventTestCase):\n    @patch(\"stripe.Price.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_price_created(\n        self, product_retrieve_mock, event_retrieve_mock, price_retrieve_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_PRICE_CREATED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        price_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        price = Price.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(\n            price.nickname, fake_stripe_event[\"data\"][\"object\"][\"nickname\"]\n        )\n\n    @patch(\"stripe.Price.retrieve\", return_value=FAKE_PRICE, autospec=True)\n    @patch(\n        \"stripe.Event.retrieve\", return_value=FAKE_EVENT_PRICE_UPDATED, autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_price_updated(\n        self, product_retrieve_mock, event_retrieve_mock, price_retrieve_mock\n    ):\n        price_retrieve_mock.return_value = FAKE_EVENT_PRICE_UPDATED[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(FAKE_EVENT_PRICE_UPDATED)\n        event.invoke_webhook_handlers()\n\n        price = Price.objects.get(id=FAKE_EVENT_PRICE_UPDATED[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(\n            price.unit_amount,\n            FAKE_EVENT_PRICE_UPDATED[\"data\"][\"object\"][\"unit_amount\"],\n        )\n        self.assertEqual(\n            price.unit_amount_decimal,\n            Decimal(FAKE_EVENT_PRICE_UPDATED[\"data\"][\"object\"][\"unit_amount_decimal\"]),\n        )\n\n    @patch(\"stripe.Price.retrieve\", return_value=FAKE_PRICE, autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_price_deleted(self, product_retrieve_mock, price_retrieve_mock):\n        event = self._create_event(FAKE_EVENT_PRICE_CREATED)\n        event.invoke_webhook_handlers()\n\n        Price.objects.get(id=FAKE_PRICE[\"id\"])\n\n        event = self._create_event(FAKE_EVENT_PRICE_DELETED)\n        event.invoke_webhook_handlers()\n\n        with self.assertRaises(Price.DoesNotExist):\n            Price.objects.get(id=FAKE_PRICE[\"id\"])\n\n\nclass TestPaymentMethodEvents(AssertStripeFksMixin, EventTestCase):\n    def setUp(self):\n        self.user = get_user_model().objects.create_user(\n            username=\"fake_customer_1\", email=FAKE_CUSTOMER[\"email\"]\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n    @patch(\"stripe.PaymentMethod.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_payment_method_attached(\n        self, event_retrieve_mock, payment_method_retrieve_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_PAYMENT_METHOD_ATTACHED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        payment_method_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        payment_method = PaymentMethod.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n\n        self.assert_fks(\n            payment_method,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n            },\n        )\n\n    @patch(\"stripe.PaymentMethod.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_card_payment_method_attached(\n        self, event_retrieve_mock, payment_method_retrieve_mock\n    ):\n        # Attach of a legacy id=\"card_xxx\" payment method should behave exactly\n        # as per a normal \"native\" id=\"pm_yyy\" payment_method.\n        fake_stripe_event = deepcopy(FAKE_EVENT_CARD_PAYMENT_METHOD_ATTACHED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        payment_method_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        payment_method = PaymentMethod.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n\n        self.assert_fks(\n            payment_method,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n            },\n        )\n\n    @patch(\"stripe.PaymentMethod.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_payment_method_detached(\n        self, event_retrieve_mock, payment_method_retrieve_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_PAYMENT_METHOD_DETACHED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        payment_method_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        payment_method = PaymentMethod.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n\n        self.assertIsNone(\n            payment_method.customer,\n            \"Detach of a payment_method should set customer to null\",\n        )\n\n        self.assert_fks(\n            payment_method, expected_blank_fks={\"djstripe.PaymentMethod.customer\"}\n        )\n\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        side_effect=InvalidRequestError(\n            message=\"No such payment_method: card_xxxx\",\n            param=\"payment_method\",\n            code=\"resource_missing\",\n        ),\n        autospec=True,\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_card_payment_method_detached(\n        self, event_retrieve_mock, payment_method_retrieve_mock\n    ):\n        # Detach of a legacy id=\"card_xxx\" payment method is handled specially,\n        # since the card is deleted by Stripe and therefore PaymetMethod.retrieve fails\n\n        fake_stripe_event = deepcopy(FAKE_EVENT_CARD_PAYMENT_METHOD_DETACHED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        payment_method_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        self.assertEqual(\n            PaymentMethod.objects.filter(\n                id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n            ).count(),\n            0,\n            \"Detach of a 'card_' payment_method should delete it\",\n        )\n\n\nclass TestPaymentIntentEvents(EventTestCase):\n    \"\"\"Test case for payment intent event handling.\"\"\"\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.File.retrieve\",\n        side_effect=(deepcopy(FAKE_FILEUPLOAD_ICON), deepcopy(FAKE_FILEUPLOAD_LOGO)),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_DESTINATION_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    def test_payment_intent_succeeded_with_destination_charge(\n        self,\n        customer_retrieve_mock,\n        account_retrieve_mock,\n        file_upload_retrieve_mock,\n        payment_intent_retrieve_mock,\n        payment_method_retrieve_mock,\n    ):\n        \"\"\"Test that the payment intent succeeded event can create all related objects.\n\n        This should exercise the machinery to set `stripe_account` when recursing into\n        objects related to a connect `Account`.\n        \"\"\"\n        event = self._create_event(\n            FAKE_EVENT_PAYMENT_INTENT_SUCCEEDED_DESTINATION_CHARGE\n        )\n        event.invoke_webhook_handlers()\n\n        # Make sure the file uploads were retrieved using the account ID.\n        file_upload_retrieve_mock.assert_has_calls(\n            (\n                call(\n                    id=FAKE_FILEUPLOAD_ICON[\"id\"],\n                    api_key=ANY,\n                    expand=ANY,\n                    stripe_account=FAKE_ACCOUNT[\"id\"],\n                    stripe_version=djstripe_settings.STRIPE_API_VERSION,\n                ),\n                call(\n                    id=FAKE_FILEUPLOAD_LOGO[\"id\"],\n                    api_key=ANY,\n                    expand=ANY,\n                    stripe_account=FAKE_ACCOUNT[\"id\"],\n                    stripe_version=djstripe_settings.STRIPE_API_VERSION,\n                ),\n            )\n        )\n\n\nclass TestSubscriptionScheduleEvents(EventTestCase):\n    @patch(\n        \"stripe.SubscriptionSchedule.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    def test_subscription_schedule_created(\n        self,\n        customer_retrieve_mock,\n        schedule_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED)\n        fake_stripe_event[\"data\"][\"object\"][\"subscription\"] = None\n\n        fake_subscription_schedule = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        fake_subscription_schedule[\"subscription\"] = None\n        schedule_retrieve_mock.return_value = fake_subscription_schedule\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        schedule = SubscriptionSchedule.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n\n        assert schedule.id == fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        assert schedule.status == \"not_started\"\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    @patch(\n        \"stripe.SubscriptionSchedule.retrieve\",\n        return_value=FAKE_SUBSCRIPTION_SCHEDULE,\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    def test_subscription_schedule_and_subscription_created(\n        self,\n        customer_retrieve_mock,\n        schedule_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        # create latest invoice\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        event = Event.sync_from_stripe_data(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED)\n        event.invoke_webhook_handlers()\n\n        schedule = SubscriptionSchedule.objects.get(\n            id=FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED[\"data\"][\"object\"][\"id\"]\n        )\n\n        assert (\n            schedule.id\n            == FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED[\"data\"][\"object\"][\"id\"]\n        )\n        assert schedule.status == \"not_started\"\n\n    @patch(\"stripe.SubscriptionSchedule.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    def test_subscription_schedule_canceled(\n        self, customer_retrieve_mock, schedule_retrieve_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_UPDATED)\n        fake_stripe_event[\"data\"][\"previous_attributes\"] = {\n            \"canceled_at\": None,\n            \"status\": \"not_started\",\n        }\n        fake_stripe_event[\"data\"][\"object\"][\"subscription\"] = None\n\n        fake_subscription_schedule = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        fake_subscription_schedule[\"subscription\"] = None\n        fake_subscription_schedule[\"canceled_at\"] = 1605058030\n        fake_subscription_schedule[\"status\"] = \"canceled\"\n        schedule_retrieve_mock.return_value = fake_subscription_schedule\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        schedule = SubscriptionSchedule.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n\n        assert schedule.status == \"canceled\"\n        assert schedule.canceled_at is not None\n\n        fake_stripe_event_2 = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CANCELED)\n        fake_stripe_event_2[\"data\"][\"object\"][\"subscription\"] = None\n\n        schedule_retrieve_mock.return_value = fake_stripe_event_2[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event_2)\n        event.invoke_webhook_handlers()\n\n        schedule.refresh_from_db()\n\n        assert schedule.status == \"canceled\"\n        assert schedule.canceled_at is not None\n\n    @patch(\"stripe.SubscriptionSchedule.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    def test_subscription_schedule_completed(\n        self, customer_retrieve_mock, schedule_retrieve_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_UPDATED)\n        fake_stripe_event[\"data\"][\"object\"][\"subscription\"] = None\n\n        fake_subscription_schedule = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        fake_subscription_schedule[\"subscription\"] = None\n        fake_subscription_schedule[\"completed_at\"] = 1605058030\n        fake_subscription_schedule[\"status\"] = \"completed\"\n        schedule_retrieve_mock.return_value = fake_subscription_schedule\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        schedule = SubscriptionSchedule.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n\n        assert schedule.status == \"completed\"\n        assert schedule.completed_at is not None\n\n        fake_stripe_event_2 = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_COMPLETED)\n        fake_stripe_event_2[\"data\"][\"object\"][\"subscription\"] = None\n\n        schedule_retrieve_mock.return_value = fake_stripe_event_2[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event_2)\n        event.invoke_webhook_handlers()\n\n        schedule.refresh_from_db()\n\n        assert schedule.status == \"completed\"\n        assert schedule.completed_at is not None\n\n    @patch(\"stripe.SubscriptionSchedule.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    def test_subscription_schedule_expiring(\n        self, customer_retrieve_mock, schedule_retrieve_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_UPDATED)\n        fake_stripe_event[\"data\"][\"object\"][\"subscription\"] = None\n\n        fake_subscription_schedule = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        fake_subscription_schedule[\"subscription\"] = None\n        fake_subscription_schedule[\"status\"] = \"active\"\n        schedule_retrieve_mock.return_value = fake_subscription_schedule\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        schedule = SubscriptionSchedule.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n\n        assert schedule.status == \"active\"\n        assert schedule.completed_at is None\n\n        fake_stripe_event_2 = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_EXPIRING)\n        fake_stripe_event_2[\"data\"][\"object\"][\"subscription\"] = None\n\n        schedule_retrieve_mock.return_value = fake_stripe_event_2[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event_2)\n        event.invoke_webhook_handlers()\n\n        schedule.refresh_from_db()\n\n        assert schedule.status == \"active\"\n        assert schedule.completed_at is None\n\n    @patch(\"stripe.SubscriptionSchedule.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    def test_subscription_schedule_released(\n        self, customer_retrieve_mock, schedule_retrieve_mock\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_UPDATED)\n        fake_stripe_event[\"data\"][\"previous_attributes\"] = {\n            \"released_at\": None,\n            \"status\": \"not_started\",\n        }\n        fake_stripe_event[\"data\"][\"object\"][\"subscription\"] = None\n\n        fake_subscription_schedule = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        fake_subscription_schedule[\"subscription\"] = None\n        fake_subscription_schedule[\"released_at\"] = 1605058030\n        fake_subscription_schedule[\"status\"] = \"released\"\n        schedule_retrieve_mock.return_value = fake_subscription_schedule\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        schedule = SubscriptionSchedule.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n\n        assert schedule.status == \"released\"\n        assert schedule.released_at is not None\n\n        fake_stripe_event_2 = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_RELEASED)\n        fake_stripe_event_2[\"data\"][\"object\"][\"subscription\"] = None\n\n        schedule_retrieve_mock.return_value = fake_stripe_event_2[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event_2)\n        event.invoke_webhook_handlers()\n\n        schedule.refresh_from_db()\n\n        assert schedule.status == \"released\"\n        assert schedule.released_at is not None\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    @patch(\"stripe.SubscriptionSchedule.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    def test_subscription_schedule_updated(\n        self,\n        customer_retrieve_mock,\n        schedule_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED)\n        fake_stripe_event[\"data\"][\"object\"][\"subscription\"] = None\n\n        fake_subscription_schedule = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        schedule_retrieve_mock.return_value = fake_subscription_schedule\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        schedule = SubscriptionSchedule.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n\n        assert schedule.released_at is None\n\n        fake_stripe_event = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_UPDATED)\n        fake_stripe_event[\"data\"][\"object\"][\"released_at\"] = 1605058030\n        fake_stripe_event[\"data\"][\"object\"][\"status\"] = \"released\"\n        fake_stripe_event[\"data\"][\"previous_attributes\"] = {\n            \"released_at\": None,\n            \"status\": \"not_started\",\n        }\n\n        schedule_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        schedule = SubscriptionSchedule.objects.get(\n            id=fake_stripe_event[\"data\"][\"object\"][\"id\"]\n        )\n\n        assert schedule.status == \"released\"\n        assert schedule.released_at is not None\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    @patch(\n        \"stripe.SubscriptionSchedule.retrieve\",\n        return_value=FAKE_SUBSCRIPTION_SCHEDULE,\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    def test_subscription_schedule_aborted(\n        self,\n        customer_retrieve_mock,\n        schedule_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        # create latest invoice (and the associated subscription)\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        event = Event.sync_from_stripe_data(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED)\n        event.invoke_webhook_handlers()\n\n        schedule = SubscriptionSchedule.objects.get(\n            id=FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED[\"data\"][\"object\"][\"id\"]\n        )\n\n        assert (\n            schedule.id\n            == FAKE_EVENT_SUBSCRIPTION_SCHEDULE_CREATED[\"data\"][\"object\"][\"id\"]\n        )\n\n        assert schedule.subscription.id == FAKE_SUBSCRIPTION[\"id\"]\n        assert schedule.subscription.status == \"active\"\n\n        # cancel the subscription\n        fake_subscription[\"status\"] = \"canceled\"\n        Subscription.sync_from_stripe_data(fake_subscription)\n\n        fake_stripe_event_2 = deepcopy(FAKE_EVENT_SUBSCRIPTION_SCHEDULE_ABORTED)\n        schedule_retrieve_mock.return_value = fake_stripe_event_2[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event_2)\n        event.invoke_webhook_handlers()\n\n        schedule.refresh_from_db()\n\n        assert schedule.status == \"canceled\"\n        assert schedule.subscription.status == \"canceled\"\n        assert schedule.canceled_at is not None\n\n\nclass TestTaxIdEvents(EventTestCase):\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve_tax_id\",\n        return_value=deepcopy(FAKE_TAX_ID),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_TAX_ID_CREATED),\n        autospec=True,\n    )\n    def test_tax_id_created(\n        self, event_retrieve_mock, tax_id_retrieve_mock, customer_retrieve_mock\n    ):\n        event = Event.sync_from_stripe_data(FAKE_EVENT_TAX_ID_CREATED)\n        event.invoke_webhook_handlers()\n        tax_id = TaxId.objects.get()\n        self.assertEqual(tax_id.id, FAKE_TAX_ID[\"id\"])\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve_tax_id\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        autospec=True,\n    )\n    def test_tax_id_updated(\n        self, event_retrieve_mock, tax_id_retrieve_mock, customer_retrieve_mock\n    ):\n        tax_id_retrieve_mock.return_value = FAKE_TAX_ID\n\n        fake_stripe_create_event = deepcopy(FAKE_EVENT_TAX_ID_CREATED)\n        event = Event.sync_from_stripe_data(fake_stripe_create_event)\n        event.invoke_webhook_handlers()\n\n        tax_id_retrieve_mock.return_value = FAKE_TAX_ID_UPDATED\n        fake_stripe_update_event = deepcopy(FAKE_EVENT_TAX_ID_UPDATED)\n        event = Event.sync_from_stripe_data(fake_stripe_update_event)\n        event.invoke_webhook_handlers()\n\n        tax_id = TaxId.objects.get()\n        self.assertEqual(tax_id.id, FAKE_TAX_ID[\"id\"])\n        self.assertEqual(tax_id.verification.get(\"status\"), \"verified\")\n        self.assertEqual(tax_id.verification.get(\"verified_name\"), \"Test\")\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve_tax_id\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        autospec=True,\n    )\n    def test_tax_id_deleted(\n        self, event_retrieve_mock, tax_id_retrieve_mock, customer_retrieve_mock\n    ):\n        tax_id_retrieve_mock.return_value = FAKE_TAX_ID\n\n        fake_stripe_create_event = deepcopy(FAKE_EVENT_TAX_ID_CREATED)\n        event = Event.sync_from_stripe_data(fake_stripe_create_event)\n        event.invoke_webhook_handlers()\n\n        tax_id_retrieve_mock.return_value = FAKE_EVENT_TAX_ID_DELETED\n        fake_stripe_delete_event = deepcopy(FAKE_EVENT_TAX_ID_DELETED)\n        event = Event.sync_from_stripe_data(fake_stripe_delete_event)\n        event.invoke_webhook_handlers()\n\n        self.assertFalse(TaxId.objects.filter(id=FAKE_TAX_ID[\"id\"]).exists())\n\n\nclass TestTransferEvents(EventTestCase):\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\"stripe.Transfer.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_transfer_created(\n        self,\n        event_retrieve_mock,\n        transfer_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        transfer_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        transfer = Transfer.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n        self.assertEqual(\n            transfer.amount,\n            fake_stripe_event[\"data\"][\"object\"][\"amount\"] / Decimal(\"100\"),\n        )\n\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\"stripe.Transfer.retrieve\", return_value=FAKE_TRANSFER, autospec=True)\n    def test_transfer_deleted(\n        self,\n        transfer_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        event = self._create_event(FAKE_EVENT_TRANSFER_CREATED)\n        event.invoke_webhook_handlers()\n\n        Transfer.objects.get(id=FAKE_TRANSFER[\"id\"])\n\n        event = self._create_event(FAKE_EVENT_TRANSFER_DELETED)\n        event.invoke_webhook_handlers()\n\n        with self.assertRaises(Transfer.DoesNotExist):\n            Transfer.objects.get(id=FAKE_TRANSFER[\"id\"])\n\n        event = self._create_event(FAKE_EVENT_TRANSFER_DELETED)\n        event.invoke_webhook_handlers()\n\n\nclass TestOrderEvents(EventTestCase):\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\"stripe.Order.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_order_created(\n        self,\n        event_retrieve_mock,\n        order_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_ORDER_CREATED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        order_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        order = Order.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n\n        self.assertEqual(order.status, \"open\")\n        self.assertEqual(order.payment_intent, None)\n        self.assertEqual(order.customer.id, FAKE_CUSTOMER[\"id\"])\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\"stripe.Order.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_order_updated(\n        self,\n        event_retrieve_mock,\n        order_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_ORDER_UPDATED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        order_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        order = Order.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n\n        # assert email got updated\n        self.assertEqual(order.billing_details[\"email\"], \"testuser@example.com\")\n        self.assertEqual(order.payment_intent.id, FAKE_PAYMENT_INTENT_I[\"id\"])\n        self.assertEqual(order.customer.id, FAKE_CUSTOMER[\"id\"])\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\"stripe.Order.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_order_submitted(\n        self,\n        event_retrieve_mock,\n        order_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_ORDER_SUBMITTED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        order_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        order = Order.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n\n        self.assertEqual(order.status, \"submitted\")\n        self.assertEqual(order.billing_details[\"email\"], \"testuser@example.com\")\n        self.assertEqual(order.payment_intent.id, FAKE_PAYMENT_INTENT_I[\"id\"])\n        self.assertEqual(order.customer.id, FAKE_CUSTOMER[\"id\"])\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\"stripe.Order.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_order_processing(\n        self,\n        event_retrieve_mock,\n        order_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_ORDER_PROCESSING)\n        event_retrieve_mock.return_value = fake_stripe_event\n        order_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        order = Order.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n\n        self.assertEqual(order.status, \"processing\")\n        self.assertEqual(order.billing_details[\"email\"], \"testuser@example.com\")\n        self.assertEqual(order.payment_intent.id, FAKE_PAYMENT_INTENT_I[\"id\"])\n        self.assertEqual(order.customer.id, FAKE_CUSTOMER[\"id\"])\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\"stripe.Order.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_order_cancelled(\n        self,\n        event_retrieve_mock,\n        order_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_ORDER_CANCELLED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        order_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        order = Order.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n\n        self.assertEqual(order.status, \"canceled\")\n        self.assertEqual(order.billing_details[\"email\"], \"testuser@example.com\")\n        self.assertEqual(order.payment_intent.id, FAKE_PAYMENT_INTENT_I[\"id\"])\n        self.assertEqual(order.customer.id, FAKE_CUSTOMER[\"id\"])\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\"stripe.Order.retrieve\", autospec=True)\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_order_complet(\n        self,\n        event_retrieve_mock,\n        order_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_stripe_event = deepcopy(FAKE_EVENT_ORDER_COMPLETED)\n        event_retrieve_mock.return_value = fake_stripe_event\n        order_retrieve_mock.return_value = fake_stripe_event[\"data\"][\"object\"]\n\n        event = Event.sync_from_stripe_data(fake_stripe_event)\n        event.invoke_webhook_handlers()\n\n        order = Order.objects.get(id=fake_stripe_event[\"data\"][\"object\"][\"id\"])\n\n        self.assertEqual(order.status, \"complete\")\n        self.assertEqual(order.billing_details[\"email\"], \"testuser@example.com\")\n        self.assertEqual(order.payment_intent.id, FAKE_PAYMENT_INTENT_I[\"id\"])\n        self.assertEqual(order.customer.id, FAKE_CUSTOMER[\"id\"])\n"
  },
  {
    "path": "tests/test_fields.py",
    "content": "\"\"\"\ndj-stripe Custom Field Tests.\n\"\"\"\nfrom datetime import datetime\nfrom decimal import Decimal\n\nimport pytest\nfrom django.test.testcases import TestCase\nfrom django.test.utils import override_settings\n\nfrom djstripe.fields import StripeDateTimeField, StripeDecimalCurrencyAmountField\nfrom djstripe.utils import get_timezone_utc\nfrom tests.fields.models import ExampleDecimalModel\n\npytestmark = pytest.mark.django_db\n\n\nclass TestStripeDecimalCurrencyAmountField:\n    noval = StripeDecimalCurrencyAmountField(name=\"noval\")\n\n    def test_stripe_to_db_none_val(self):\n        assert self.noval.stripe_to_db({\"noval\": None}) is None\n\n    @pytest.mark.parametrize(\n        \"expected,inputted\",\n        [\n            (Decimal(\"1\"), Decimal(\"100\")),\n            (Decimal(\"1.5\"), Decimal(\"150\")),\n            (Decimal(\"0\"), Decimal(\"0\")),\n        ],\n    )\n    def test_stripe_to_db_decimal_val(self, expected, inputted):\n        assert expected == self.noval.stripe_to_db({\"noval\": inputted})\n\n\n@override_settings(USE_TZ=get_timezone_utc())\nclass TestStripeDateTimeField(TestCase):\n    noval = StripeDateTimeField(name=\"noval\")\n\n    def test_stripe_to_db_none_val(self):\n        self.assertEqual(None, self.noval.stripe_to_db({\"noval\": None}))\n\n    def test_stripe_to_db_datetime_val(self):\n        self.assertEqual(\n            datetime(1997, 9, 18, 7, 48, 35, tzinfo=get_timezone_utc()),\n            self.noval.stripe_to_db({\"noval\": 874568915}),\n        )\n\n\nclass TestStripePercentField:\n    @pytest.mark.parametrize(\n        \"inputted,expected\",\n        [\n            (Decimal(\"1\"), Decimal(\"1.00\")),\n            (Decimal(\"1.5234567\"), Decimal(\"1.52\")),\n            (Decimal(\"0\"), Decimal(\"0.00\")),\n            (Decimal(\"23.2345678\"), Decimal(\"23.23\")),\n            (\"1\", Decimal(\"1.00\")),\n            (\"1.5234567\", Decimal(\"1.52\")),\n            (\"0\", Decimal(\"0.00\")),\n            (\"23.2345678\", Decimal(\"23.23\")),\n            (1, Decimal(\"1.00\")),\n            (1.5234567, Decimal(\"1.52\")),\n            (0, Decimal(\"0.00\")),\n            (23.2345678, Decimal(\"23.24\")),\n        ],\n    )\n    def test_stripe_percent_field(self, inputted, expected):\n        # create a model with the StripePercentField\n        model_field = ExampleDecimalModel(noval=inputted)\n        model_field.save()\n\n        # get the field data\n        field_data = ExampleDecimalModel.objects.get(pk=model_field.pk).noval\n\n        assert isinstance(field_data, Decimal)\n        assert field_data == expected\n"
  },
  {
    "path": "tests/test_file_link.py",
    "content": "\"\"\"\ndj-stripe FileLink model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nfrom django.test import TestCase\n\nfrom djstripe.models import File, FileLink\nfrom djstripe.settings import djstripe_settings\n\nfrom . import FAKE_FILEUPLOAD_ICON\n\npytestmark = pytest.mark.django_db\n\n\nclass TestFileLink(TestCase):\n    @patch(\n        target=\"stripe.File.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n    )\n    @patch(\n        target=\"stripe.FileLink.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON[\"links\"][\"data\"][0]),\n    )\n    def test___str__(self, mock_file_link_retrieve, mock_file_upload_retrieve):\n        file_link_data = deepcopy(FAKE_FILEUPLOAD_ICON[\"links\"][\"data\"][0])\n        file_link = FileLink.sync_from_stripe_data(file_link_data)\n        assert (f\"{FAKE_FILEUPLOAD_ICON['filename']}, {file_link_data['url']}\") == str(\n            file_link\n        )\n\n    @patch(\n        target=\"stripe.File.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n    )\n    @patch(\n        target=\"stripe.FileLink.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON[\"links\"][\"data\"][0]),\n    )\n    def test_sync_from_stripe_data(\n        self, mock_file_link_retrieve, mock_file_upload_retrieve\n    ):\n        file_link_data = deepcopy(FAKE_FILEUPLOAD_ICON[\"links\"][\"data\"][0])\n        file_link = FileLink.sync_from_stripe_data(file_link_data)\n\n        mock_file_link_retrieve.assert_not_called()\n        mock_file_upload_retrieve.assert_called_once_with(\n            id=file_link_data[\"file\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[],\n            stripe_account=None,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n        assert file_link.file == File.objects.get(id=file_link_data[\"file\"])\n        assert file_link.url == file_link_data[\"url\"]\n"
  },
  {
    "path": "tests/test_file_upload.py",
    "content": "\"\"\"\ndj-stripe File model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import ANY, call, patch\n\nimport pytest\nfrom django.test import TestCase\n\nfrom djstripe.enums import FilePurpose\nfrom djstripe.models import Account, File\nfrom djstripe.settings import djstripe_settings\n\nfrom . import FAKE_ACCOUNT, FAKE_FILEUPLOAD_ICON, FAKE_FILEUPLOAD_LOGO\n\npytestmark = pytest.mark.django_db\n\n\nclass TestFileLink(TestCase):\n    @patch(\n        target=\"stripe.File.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n    )\n    def test_file_upload_api_retrieve(self, mock_file_upload_retrieve):\n        \"\"\"Expect file_upload to use the ID of the account referring\n        to it to retrieve itself.\n        \"\"\"\n        # Create files\n        icon_file = File._get_or_create_from_stripe_object(data=FAKE_FILEUPLOAD_ICON)[0]\n        logo_file = File._get_or_create_from_stripe_object(data=FAKE_FILEUPLOAD_LOGO)[0]\n        # Create account to associate the files to it\n        account = Account._get_or_create_from_stripe_object(data=FAKE_ACCOUNT)[0]\n\n        # Call the API retrieve methods.\n        icon_file.api_retrieve()\n        logo_file.api_retrieve()\n\n        # Ensure the correct Account ID was used in retrieval\n        mock_file_upload_retrieve.assert_has_calls(\n            (\n                call(\n                    id=icon_file.id,\n                    api_key=ANY,\n                    expand=ANY,\n                    stripe_account=account.id,\n                    stripe_version=djstripe_settings.STRIPE_API_VERSION,\n                ),\n                call(\n                    id=logo_file.id,\n                    api_key=ANY,\n                    expand=ANY,\n                    stripe_account=account.id,\n                    stripe_version=djstripe_settings.STRIPE_API_VERSION,\n                ),\n            )\n        )\n\n    @patch(\n        target=\"stripe.File.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_FILEUPLOAD_ICON),\n    )\n    def test_sync_from_stripe_data(self, mock_file_upload_retrieve):\n        file = File.sync_from_stripe_data(deepcopy(FAKE_FILEUPLOAD_ICON))\n\n        mock_file_upload_retrieve.assert_not_called()\n\n        assert file.id == FAKE_FILEUPLOAD_ICON[\"id\"]\n        assert file.purpose == FAKE_FILEUPLOAD_ICON[\"purpose\"]\n        assert file.type == FAKE_FILEUPLOAD_ICON[\"type\"]\n\n\nclass TestFileUploadStr:\n    @pytest.mark.parametrize(\"file_purpose\", FilePurpose.__members__)\n    def test___str__(self, file_purpose):\n        modified_file_data = deepcopy(FAKE_FILEUPLOAD_ICON)\n        modified_file_data[\"purpose\"] = file_purpose\n\n        file = File.sync_from_stripe_data(modified_file_data)\n        assert (\n            f\"{modified_file_data['filename']}, {FilePurpose.humanize(modified_file_data['purpose'])}\"\n        ) == str(file)\n"
  },
  {
    "path": "tests/test_forms.py",
    "content": "\"\"\"\ndj-stripe form tests\n\"\"\"\nimport pytest\nfrom django import forms\nfrom django.contrib.admin import helpers\nfrom django.forms.utils import ErrorDict\n\nfrom djstripe import enums, utils\nfrom djstripe.admin.forms import APIKeyAdminCreateForm, CustomActionForm\nfrom tests import FAKE_PLATFORM_ACCOUNT\n\nfrom .fields.models import CustomActionModel\nfrom .test_apikey import RK_LIVE, RK_TEST, SK_LIVE, SK_TEST\n\npytestmark = pytest.mark.django_db\n\n\nclass TestCustomActionForm:\n    @pytest.mark.parametrize(\n        \"action_name\", [\"_sync_all_instances\", \"_resync_instances\"]\n    )\n    def test___init__(self, action_name, monkeypatch):\n        # monkeypatch utils.get_model\n        def mock_get_model(*args, **kwargs):\n            return model\n\n        monkeypatch.setattr(utils, \"get_model\", mock_get_model)\n\n        model = CustomActionModel\n\n        # create instances to be used in the Django Admin Action\n        inst_1 = model.objects.create(id=\"test\")\n        inst_2 = model.objects.create(id=\"test-2\")\n        pk_values = [inst_1.pk, inst_2.pk]\n\n        form = CustomActionForm(\n            model_name=CustomActionModel._meta.model_name,\n            action_name=action_name,\n        )\n\n        # assert _selected_action_field has been added to the form\n        _selected_action_field = form.fields[helpers.ACTION_CHECKBOX_NAME]\n        assert _selected_action_field is not None\n\n        # assert _selected_action_field is an instance of MultipleHiddenInput\n        assert isinstance(_selected_action_field.widget, forms.MultipleHiddenInput)\n\n        if action_name == \"_sync_all_instances\":\n            assert _selected_action_field.choices == [\n                (\"_sync_all_instances\", \"_sync_all_instances\")\n            ]\n        else:\n            assert _selected_action_field.choices == list(zip(pk_values, pk_values))\n\n\nclass TestAPIKeyAdminCreateForm:\n    @pytest.mark.parametrize(\"secret\", [SK_TEST, SK_LIVE, RK_TEST, RK_LIVE])\n    def test__post_clean(self, secret, monkeypatch):\n        form = APIKeyAdminCreateForm(data={\"name\": \"Test Secret Key\", \"secret\": secret})\n\n        # Manually invoking internals of Form.full_clean() to isolate\n        # Form._post_clean\n        form._errors = ErrorDict()\n        form.cleaned_data = {}\n        form._clean_fields()\n        form._clean_form()\n\n        # assert form is valid but instance is not yet saved in the db.\n        assert form.instance.pk is None\n        assert form.is_valid() is True\n\n        # assert that the instance does not have the owner account populated\n        assert form.instance.djstripe_owner_account is None\n\n        # Invoke _post_clean()\n        form._post_clean()\n\n        if secret.startswith(\"sk_\"):\n            assert form.instance.type == enums.APIKeyType.secret\n            assert (\n                form.instance.djstripe_owner_account.id == FAKE_PLATFORM_ACCOUNT[\"id\"]\n            )\n        elif secret.startswith(\"rk_\"):\n            assert form.instance.type == enums.APIKeyType.restricted\n            assert form.instance.djstripe_owner_account is None\n"
  },
  {
    "path": "tests/test_idempotency_keys.py",
    "content": "from datetime import timedelta\n\nfrom django.test import TestCase\nfrom django.utils.timezone import now\n\nfrom djstripe.models import IdempotencyKey\nfrom djstripe.settings import djstripe_settings\nfrom djstripe.utils import clear_expired_idempotency_keys\n\n\nclass IdempotencyKeyTest(TestCase):\n    def test_generate_idempotency_key(self):\n        key1 = djstripe_settings.get_idempotency_key(\"customer\", \"create:1\", False)\n        key2 = djstripe_settings.get_idempotency_key(\"customer\", \"create:1\", False)\n        self.assertTrue(key1 == key2)\n\n        key3 = djstripe_settings.get_idempotency_key(\"customer\", \"create:2\", False)\n        self.assertTrue(key1 != key3)\n\n        key4 = djstripe_settings.get_idempotency_key(\"charge\", \"create:1\", False)\n        self.assertTrue(key1 != key4)\n\n        self.assertEqual(IdempotencyKey.objects.count(), 3)\n        key1_obj = IdempotencyKey.objects.get(\n            action=\"customer:create:1\", livemode=False\n        )\n        self.assertFalse(key1_obj.is_expired)\n        self.assertEqual(str(key1_obj), str(key1_obj.uuid))\n\n    def test_clear_expired_idempotency_keys(self):\n        expired_key = djstripe_settings.get_idempotency_key(\n            \"customer\", \"create:1\", False\n        )\n        expired_key_obj = IdempotencyKey.objects.get(uuid=expired_key)\n        expired_key_obj.created = now() - timedelta(hours=25)\n        expired_key_obj.save()\n\n        valid_key = djstripe_settings.get_idempotency_key(\"customer\", \"create:2\", False)\n\n        self.assertEqual(IdempotencyKey.objects.count(), 2)\n\n        clear_expired_idempotency_keys()\n\n        self.assertEqual(IdempotencyKey.objects.count(), 1)\n        self.assertEqual(str(IdempotencyKey.objects.get().uuid), valid_key)\n"
  },
  {
    "path": "tests/test_integrations/README.md",
    "content": "Integration Tests\n=================\n\nAll the tests in this directory interact with the stripe API. In order\nto make them fire, you need to set the STRIPE\\_PUBLIC\\_KEY and\nSTRIPE\\_PRIVATE\\_KEY in your environment. Otherwise these tests will\nfail.\n"
  },
  {
    "path": "tests/test_integrations/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_invoice.py",
    "content": "\"\"\"\ndj-stripe Invoice Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom decimal import Decimal\nfrom unittest.mock import call, patch\n\nimport pytest\nimport stripe\nfrom django.contrib.auth import get_user_model\nfrom django.test.testcases import TestCase\nfrom stripe.error import InvalidRequestError\n\nfrom djstripe.enums import InvoiceStatus\nfrom djstripe.models import Invoice, Plan, Subscription, UpcomingInvoice\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_INVOICE,\n    FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE,\n    FAKE_INVOICEITEM,\n    FAKE_LINE_ITEM_SUBSCRIPTION,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PLAN,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_TAX_RATE_EXAMPLE_1_VAT,\n    FAKE_TAX_RATE_EXAMPLE_2_SALES,\n    FAKE_UPCOMING_INVOICE,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass InvoiceTest(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        # create a Stripe Platform Account\n        self.account = FAKE_PLATFORM_ACCOUNT.create()\n\n        user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(user)\n\n        self.default_expected_blank_fks = {\n            \"djstripe.Account.branding_logo\",\n            \"djstripe.Account.branding_icon\",\n            \"djstripe.Charge.application_fee\",\n            \"djstripe.Charge.dispute\",\n            \"djstripe.Charge.latest_upcominginvoice (related name)\",\n            \"djstripe.Charge.on_behalf_of\",\n            \"djstripe.Charge.source_transfer\",\n            \"djstripe.Charge.transfer\",\n            \"djstripe.Customer.coupon\",\n            \"djstripe.Customer.default_payment_method\",\n            \"djstripe.Invoice.default_payment_method\",\n            \"djstripe.Invoice.default_source\",\n            \"djstripe.PaymentIntent.on_behalf_of\",\n            \"djstripe.PaymentIntent.payment_method\",\n            \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            \"djstripe.Product.default_price\",\n            \"djstripe.Subscription.default_payment_method\",\n            \"djstripe.Subscription.default_source\",\n            \"djstripe.Subscription.pending_setup_intent\",\n            \"djstripe.Subscription.schedule\",\n        }\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    def test_sync_from_stripe_data(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n        customer_retrieve_mock,\n    ):\n        default_account_mock.return_value = self.account\n        invoice = Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n        assert invoice\n        assert (\n            str(invoice) == f\"Invoice #{FAKE_INVOICE['number']} for $20.00 USD (paid)\"\n        )\n        self.assertGreater(len(invoice.status_transitions.keys()), 1)\n        self.assertTrue(bool(invoice.account_country))\n        self.assertTrue(bool(invoice.account_name))\n        self.assertTrue(bool(invoice.collection_method))\n\n        self.assertEqual(invoice.default_tax_rates.count(), 1)\n        self.assertEqual(\n            invoice.default_tax_rates.first().id, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"id\"]\n        )\n\n        self.assertEqual(invoice.total_tax_amounts.count(), 1)\n\n        first_tax_amount = invoice.total_tax_amounts.first()\n        self.assertEqual(\n            first_tax_amount.tax_rate.id, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"id\"]\n        )\n        self.assertEqual(\n            first_tax_amount.inclusive, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"inclusive\"]\n        )\n        self.assertEqual(first_tax_amount.amount, 261)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    def test_sync_from_stripe_data_update_total_tax_amounts(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n        customer_retrieve_mock,\n    ):\n        default_account_mock.return_value = self.account\n        invoice = Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        # as per basic sync test\n        self.assertEqual(invoice.default_tax_rates.count(), 1)\n        self.assertEqual(\n            invoice.default_tax_rates.first().id, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"id\"]\n        )\n\n        self.assertEqual(invoice.total_tax_amounts.count(), 1)\n\n        first_tax_amount = invoice.total_tax_amounts.first()\n        self.assertEqual(\n            first_tax_amount.tax_rate.id, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"id\"]\n        )\n        self.assertEqual(\n            first_tax_amount.inclusive, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"inclusive\"]\n        )\n        self.assertEqual(first_tax_amount.amount, 261)\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n        # Now update with a different tax rate\n        # TODO - should update tax rate in invoice items etc as well,\n        #  but here we're mainly testing that invoice.total_tax_rates is\n        #  correctly updated\n        fake_updated_invoice = deepcopy(FAKE_INVOICE)\n        fake_tax_rate_2 = deepcopy(FAKE_TAX_RATE_EXAMPLE_2_SALES)\n\n        new_tax_amount = int(\n            fake_updated_invoice[\"total\"] * fake_tax_rate_2[\"percentage\"] / 100\n        )\n\n        fake_updated_invoice.update(\n            {\n                \"default_tax_rates\": [fake_tax_rate_2],\n                \"tax\": new_tax_amount,\n                \"total\": fake_updated_invoice[\"total\"] + new_tax_amount,\n                \"total_tax_amounts\": [\n                    {\n                        \"amount\": new_tax_amount,\n                        \"inclusive\": False,\n                        \"tax_rate\": fake_tax_rate_2[\"id\"],\n                    }\n                ],\n            }\n        )\n\n        invoice_updated = Invoice.sync_from_stripe_data(fake_updated_invoice)\n\n        self.assertEqual(invoice_updated.default_tax_rates.count(), 1)\n        self.assertEqual(\n            invoice_updated.default_tax_rates.first().id, fake_tax_rate_2[\"id\"]\n        )\n\n        self.assertEqual(invoice_updated.total_tax_amounts.count(), 1)\n\n        first_tax_amount = invoice_updated.total_tax_amounts.first()\n        self.assertEqual(first_tax_amount.tax_rate.id, fake_tax_rate_2[\"id\"])\n        self.assertEqual(first_tax_amount.inclusive, fake_tax_rate_2[\"inclusive\"])\n        self.assertEqual(first_tax_amount.amount, new_tax_amount)\n        self.assert_fks(\n            invoice_updated, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        autospec=True,\n    )\n    def test_sync_from_stripe_data_default_payment_method(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n        customer_retrieve_mock,\n    ):\n        default_account_mock.return_value = self.account\n        fake_invoice = deepcopy(FAKE_INVOICE)\n        fake_invoice[\"default_payment_method\"] = deepcopy(FAKE_CARD_AS_PAYMENT_METHOD)\n        invoice_retrieve_mock.return_value = fake_invoice\n\n        invoice = Invoice.sync_from_stripe_data(fake_invoice)\n\n        self.assertEqual(\n            invoice.default_payment_method.id, FAKE_CARD_AS_PAYMENT_METHOD[\"id\"]\n        )\n\n        self.assert_fks(\n            invoice,\n            expected_blank_fks=self.default_expected_blank_fks\n            - {\"djstripe.Invoice.default_payment_method\"},\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_billing_reason_enum(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n        fake_invoice = deepcopy(FAKE_INVOICE)\n\n        for billing_reason in (\n            \"subscription_cycle\",\n            \"subscription_create\",\n            \"subscription_update\",\n            \"subscription\",\n            \"manual\",\n            \"upcoming\",\n            \"subscription_threshold\",\n        ):\n            fake_invoice[\"billing_reason\"] = billing_reason\n\n            invoice = Invoice.sync_from_stripe_data(fake_invoice)\n            self.assertEqual(invoice.billing_reason, billing_reason)\n\n            # trigger model field validation (including enum value choices check)\n            invoice.full_clean()\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_invoice_status_enum(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n        fake_invoice = deepcopy(FAKE_INVOICE)\n\n        for status in (\n            \"draft\",\n            \"open\",\n            \"paid\",\n            \"uncollectible\",\n            \"void\",\n        ):\n            fake_invoice[\"status\"] = status\n\n            invoice = Invoice.sync_from_stripe_data(fake_invoice)\n            self.assertEqual(invoice.status, status)\n\n            # trigger model field validation (including enum value choices check)\n            invoice.full_clean()\n\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\"stripe.Invoice.retrieve\", autospec=True)\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_retry_true(\n        self,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_invoice = deepcopy(FAKE_INVOICE)\n        fake_invoice.update({\"paid\": False, \"status\": \"open\"})\n        fake_invoice.update({\"auto_advance\": True})\n        invoice_retrieve_mock.return_value = fake_invoice\n\n        invoice = Invoice.sync_from_stripe_data(fake_invoice)\n        return_value = invoice.retry()\n\n        invoice_retrieve_mock.assert_called_once_with(\n            id=invoice.id,\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[\"discounts\"],\n            stripe_account=invoice.djstripe_owner_account.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n        self.assertTrue(return_value)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\"stripe.Invoice.retrieve\", autospec=True)\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_retry_false(\n        self,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_invoice = deepcopy(FAKE_INVOICE)\n        invoice_retrieve_mock.return_value = fake_invoice\n\n        invoice = Invoice.sync_from_stripe_data(fake_invoice)\n        return_value = invoice.retry()\n\n        self.assertFalse(invoice_retrieve_mock.called)\n        self.assertFalse(return_value)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_status_draft(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice_data = deepcopy(FAKE_INVOICE)\n        invoice_data.update({\"paid\": False, \"status\": \"draft\"})\n        invoice = Invoice.sync_from_stripe_data(invoice_data)\n\n        self.assertEqual(InvoiceStatus.draft, invoice.status)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_status_open(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice_data = deepcopy(FAKE_INVOICE)\n        invoice_data.update({\"paid\": False, \"status\": \"open\"})\n        invoice = Invoice.sync_from_stripe_data(invoice_data)\n\n        self.assertEqual(InvoiceStatus.open, invoice.status)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_status_paid(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice = Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        self.assertEqual(InvoiceStatus.paid, invoice.status)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_status_uncollectible(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice_data = deepcopy(FAKE_INVOICE)\n        invoice_data.update({\"paid\": False, \"status\": \"uncollectible\"})\n        invoice = Invoice.sync_from_stripe_data(invoice_data)\n\n        self.assertEqual(InvoiceStatus.uncollectible, invoice.status)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_status_void(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice_data = deepcopy(FAKE_INVOICE)\n        invoice_data.update({\"paid\": False, \"status\": \"void\"})\n        invoice = Invoice.sync_from_stripe_data(invoice_data)\n\n        self.assertEqual(InvoiceStatus.void, invoice.status)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Plan.retrieve\",\n        return_value=deepcopy(FAKE_PLAN),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_sync_no_subscription(\n        self,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        subscription_item_retrieve_mock,\n        plan_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n        customer_retrieve_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice_data = deepcopy(FAKE_INVOICE)\n        invoice_data.update({\"subscription\": None})\n        invoice_data[\"lines\"][\"data\"][0][\"subscription\"] = None\n\n        invoice_retrieve_mock.return_value = invoice_data\n        invoice = Invoice.sync_from_stripe_data(invoice_data)\n\n        self.assertEqual(None, invoice.subscription)\n\n        self.assertEqual(FAKE_CHARGE[\"id\"], invoice.charge.id)\n        plan_retrieve_mock.assert_not_called()\n\n        self.assert_fks(\n            invoice,\n            expected_blank_fks=self.default_expected_blank_fks\n            | {\"djstripe.Invoice.subscription\"},\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_invoice_with_subscription_invoice_items(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice_data = deepcopy(FAKE_INVOICE)\n        invoice = Invoice.sync_from_stripe_data(invoice_data)\n\n        items = invoice.invoiceitems.all()\n        self.assertEqual(1, len(items))\n\n        self.assertEqual(items[0].id, \"ii_fakefakefakefakefake0001\")\n        self.assertIsNone(items[0].subscription)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_invoice_with_no_invoice_items(\n        self,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice_data = deepcopy(FAKE_INVOICE)\n        invoice_data[\"lines\"] = []\n        invoice = Invoice.sync_from_stripe_data(invoice_data)\n\n        self.assertIsNotNone(invoice.plan)  # retrieved from invoice item\n        self.assertEqual(FAKE_PLAN[\"id\"], invoice.plan.id)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_invoice_with_non_subscription_invoice_items(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice_data = deepcopy(FAKE_INVOICE)\n        invoice_data[\"lines\"][\"data\"].append(deepcopy(FAKE_LINE_ITEM_SUBSCRIPTION))\n        invoice_data[\"lines\"][\"total_count\"] += 1\n        invoice = Invoice.sync_from_stripe_data(invoice_data)\n\n        self.assertIsNotNone(invoice)\n        # assert only 1 line item of type=\"invoice_item\"\n        self.assertEqual(1, len(invoice.invoiceitems.all()))\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_invoice_plan_from_invoice_items(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice_data = deepcopy(FAKE_INVOICE)\n        invoice = Invoice.sync_from_stripe_data(invoice_data)\n\n        self.assertIsNotNone(invoice.plan)  # retrieved from invoice item\n        self.assertEqual(FAKE_PLAN[\"id\"], invoice.plan.id)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    def test_invoice_plan_from_subscription(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice_data = deepcopy(FAKE_INVOICE)\n        invoice_data[\"lines\"][\"data\"][0][\"plan\"] = None\n\n        invoice = Invoice.sync_from_stripe_data(invoice_data)\n        self.assertIsNotNone(invoice.plan)  # retrieved from subscription\n        self.assertEqual(FAKE_PLAN[\"id\"], invoice.plan.id)\n\n        self.assert_fks(invoice, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\"stripe.Subscription.retrieve\", autospec=True)\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        autospec=True,\n    )\n    def test_invoice_without_plan(\n        self,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        charge_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        payment_intent_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoice_data = deepcopy(FAKE_INVOICE)\n        invoice_data[\"lines\"][\"data\"][0][\"plan\"] = None\n        invoice_data[\"lines\"][\"data\"][0][\"subscription\"] = None\n        invoice_data[\"subscription\"] = None\n\n        fake_invoice_item = deepcopy(FAKE_INVOICEITEM)\n        fake_invoice_item[\"subscription\"] = None\n        invoice_item_retrieve_mock.return_value = fake_invoice_item\n\n        subscription_retrieve_mock.return_value = deepcopy(FAKE_SUBSCRIPTION)\n\n        invoice = Invoice.sync_from_stripe_data(invoice_data)\n        self.assertIsNone(invoice.plan)\n\n        self.assert_fks(\n            invoice,\n            expected_blank_fks=self.default_expected_blank_fks\n            | {\"djstripe.Invoice.subscription\"},\n        )\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    @patch(\n        \"stripe.Plan.retrieve\",\n        return_value=deepcopy(FAKE_PLAN),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.upcoming\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_upcoming_invoice(\n        self,\n        product_retrieve_mock,\n        invoice_upcoming_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        plan_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_upcoming_invoice_data = deepcopy(FAKE_UPCOMING_INVOICE)\n        fake_upcoming_invoice_data[\"lines\"][\"data\"][0][\n            \"subscription\"\n        ] = FAKE_SUBSCRIPTION[\"id\"]\n        invoice_upcoming_mock.return_value = fake_upcoming_invoice_data\n\n        fake_subscription_item_data = deepcopy(FAKE_SUBSCRIPTION_ITEM)\n        fake_subscription_item_data[\"plan\"] = deepcopy(FAKE_PLAN)\n        fake_subscription_item_data[\"subscription\"] = deepcopy(FAKE_SUBSCRIPTION)[\"id\"]\n        subscription_item_retrieve_mock.return_value = fake_subscription_item_data\n\n        invoice = UpcomingInvoice.upcoming()\n        self.assertIsNotNone(invoice)\n        self.assertIsNone(invoice.id)\n        self.assertIsNone(invoice.save())\n        self.assertEqual(invoice.get_stripe_dashboard_url(), \"\")\n\n        invoice.id = \"foo\"\n        self.assertIsNone(invoice.id)\n\n        # one more because of creating the associated line item\n        subscription_retrieve_mock.assert_has_calls(\n            [\n                call(\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[],\n                    id=FAKE_SUBSCRIPTION[\"id\"],\n                    stripe_account=None,\n                    stripe_version=\"2020-08-27\",\n                ),\n                call(\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[],\n                    id=FAKE_SUBSCRIPTION[\"id\"],\n                    stripe_account=None,\n                    stripe_version=\"2020-08-27\",\n                ),\n            ]\n        )\n\n        plan_retrieve_mock.assert_not_called()\n\n        items = invoice.lineitems.all()\n        self.assertEqual(1, len(items))\n        self.assertEqual(\"il_fakefakefakefakefake0002\", items[0].id)\n        self.assertEqual(0, invoice.invoiceitems.count())\n\n        # delete/update should do nothing\n        self.assertEqual(invoice.lineitems.update(), 0)\n        self.assertEqual(invoice.lineitems.delete(), 0)\n\n        self.assertIsNotNone(invoice.plan)\n        self.assertEqual(FAKE_PLAN[\"id\"], invoice.plan.id)\n\n        invoice._lineitems = []\n        items = invoice.lineitems.all()\n        self.assertEqual(0, len(items))\n        self.assertIsNotNone(invoice.plan)\n\n        self.assertEqual(invoice.default_tax_rates.count(), 1)\n        self.assertEqual(\n            invoice.default_tax_rates.first().id, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"id\"]\n        )\n\n        self.assertEqual(invoice.total_tax_amounts.count(), 1)\n\n        first_tax_amount = invoice.total_tax_amounts.first()\n        self.assertEqual(\n            first_tax_amount.tax_rate.id, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"id\"]\n        )\n        self.assertEqual(\n            first_tax_amount.inclusive, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"inclusive\"]\n        )\n        self.assertEqual(first_tax_amount.amount, 261)\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    @patch(\"stripe.Plan.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.upcoming\",\n        autospec=True,\n    )\n    def test_upcoming_invoice_with_subscription(\n        self,\n        invoice_upcoming_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_upcoming_invoice_data = deepcopy(FAKE_UPCOMING_INVOICE)\n        fake_upcoming_invoice_data[\"lines\"][\"data\"][0][\n            \"subscription\"\n        ] = FAKE_SUBSCRIPTION[\"id\"]\n        invoice_upcoming_mock.return_value = fake_upcoming_invoice_data\n\n        fake_subscription_item_data = deepcopy(FAKE_SUBSCRIPTION_ITEM)\n        fake_subscription_item_data[\"plan\"] = deepcopy(FAKE_PLAN)\n        fake_subscription_item_data[\"subscription\"] = deepcopy(FAKE_SUBSCRIPTION)[\"id\"]\n        subscription_item_retrieve_mock.return_value = fake_subscription_item_data\n\n        invoice = Invoice.upcoming(\n            subscription=Subscription(id=FAKE_SUBSCRIPTION[\"id\"])\n        )\n        self.assertIsNotNone(invoice)\n        self.assertIsNone(invoice.id)\n        self.assertIsNone(invoice.save())\n\n        # one more because of creating the associated line item\n        subscription_retrieve_mock.assert_has_calls(\n            [\n                call(\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[],\n                    id=FAKE_SUBSCRIPTION[\"id\"],\n                    stripe_account=None,\n                    stripe_version=\"2020-08-27\",\n                ),\n                call(\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[],\n                    id=FAKE_SUBSCRIPTION[\"id\"],\n                    stripe_account=None,\n                    stripe_version=\"2020-08-27\",\n                ),\n            ]\n        )\n\n        plan_retrieve_mock.assert_not_called()\n\n        self.assertIsNotNone(invoice.plan)\n        self.assertEqual(FAKE_PLAN[\"id\"], invoice.plan.id)\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\"stripe.Invoice.retrieve\", autospec=True)\n    @patch(\"stripe.Plan.retrieve\", autospec=True)\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.upcoming\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_upcoming_invoice_with_subscription_plan(\n        self,\n        product_retrieve_mock,\n        invoice_upcoming_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        plan_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        customer_retrieve_mock,\n    ):\n        fake_upcoming_invoice_data = deepcopy(FAKE_UPCOMING_INVOICE)\n        fake_upcoming_invoice_data[\n            \"subscription\"\n        ] = FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE[\"id\"]\n        invoice_upcoming_mock.return_value = fake_upcoming_invoice_data\n\n        fake_invoice_data = deepcopy(FAKE_INVOICE)\n        fake_invoice_data[\"subscription\"] = FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE[\n            \"id\"\n        ]\n        fake_invoice_data[\"lines\"][\"data\"][0][\n            \"subscription\"\n        ] = FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE[\"id\"]\n        fake_invoice_data[\"lines\"][\"data\"][0][\"discounts\"][0][\n            \"subscription\"\n        ] = FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE[\"id\"]\n        invoice_retrieve_mock.return_value = fake_invoice_data\n\n        fake_subscription_item_data = deepcopy(FAKE_SUBSCRIPTION_ITEM)\n        fake_subscription_item_data[\"plan\"] = deepcopy(FAKE_PLAN)\n        subscription_item_retrieve_mock.return_value = fake_subscription_item_data\n\n        fake_subscription_data = deepcopy(FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE)\n        fake_subscription_data[\"plan\"] = deepcopy(FAKE_PLAN)\n        subscription_retrieve_mock.return_value = fake_subscription_data\n\n        invoice = Invoice.upcoming(subscription_plan=Plan(id=FAKE_PLAN[\"id\"]))\n        self.assertIsNotNone(invoice)\n        self.assertIsNone(invoice.id)\n        self.assertIsNone(invoice.save())\n\n        subscription_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[],\n            id=FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE[\"id\"],\n            stripe_account=None,\n            stripe_version=\"2020-08-27\",\n        )\n        plan_retrieve_mock.assert_not_called()\n\n        self.assertIsNotNone(invoice.plan)\n        self.assertEqual(FAKE_PLAN[\"id\"], invoice.plan.id)\n\n    @patch(\n        \"stripe.Invoice.upcoming\",\n        side_effect=InvalidRequestError(\"Nothing to invoice for customer\", None),\n    )\n    def test_no_upcoming_invoices(self, invoice_upcoming_mock):\n        invoice = Invoice.upcoming()\n        self.assertIsNone(invoice)\n\n    @patch(\n        \"stripe.Invoice.upcoming\",\n        side_effect=InvalidRequestError(\"Some other error\", None),\n    )\n    def test_upcoming_invoice_error(self, invoice_upcoming_mock):\n        with self.assertRaises(InvalidRequestError):\n            Invoice.upcoming()\n\n\nclass TestInvoiceDecimal:\n    @pytest.mark.parametrize(\n        \"inputted,expected\",\n        [\n            (Decimal(\"1\"), Decimal(\"1.00\")),\n            (Decimal(\"1.5234567\"), Decimal(\"1.52\")),\n            (Decimal(\"0\"), Decimal(\"0.00\")),\n            (Decimal(\"23.2345678\"), Decimal(\"23.23\")),\n            (\"1\", Decimal(\"1.00\")),\n            (\"1.5234567\", Decimal(\"1.52\")),\n            (\"0\", Decimal(\"0.00\")),\n            (\"23.2345678\", Decimal(\"23.23\")),\n            (1, Decimal(\"1.00\")),\n            (1.5234567, Decimal(\"1.52\")),\n            (0, Decimal(\"0.00\")),\n            (23.2345678, Decimal(\"23.24\")),\n        ],\n    )\n    def test_decimal_tax_percent(self, inputted, expected, monkeypatch):  # noqa: C901\n        fake_invoice = deepcopy(FAKE_INVOICE)\n        fake_invoice[\"tax_percent\"] = inputted\n\n        def mock_invoice_get(*args, **kwargs):\n            return fake_invoice\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_customer_get(*args, **kwargs):\n            return FAKE_CUSTOMER\n\n        def mock_charge_get(*args, **kwargs):\n            return FAKE_CHARGE\n\n        def mock_payment_method_get(*args, **kwargs):\n            return FAKE_CARD_AS_PAYMENT_METHOD\n\n        def mock_payment_intent_get(*args, **kwargs):\n            return FAKE_PAYMENT_INTENT_I\n\n        def mock_subscription_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_balance_transaction_get(*args, **kwargs):\n            return FAKE_BALANCE_TRANSACTION\n\n        def mock_product_get(*args, **kwargs):\n            return FAKE_PRODUCT\n\n        # monkeypatch stripe retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n\n        invoice = Invoice.sync_from_stripe_data(fake_invoice)\n        field_data = invoice.tax_percent\n\n        assert isinstance(field_data, Decimal)\n        assert field_data == expected\n"
  },
  {
    "path": "tests/test_invoiceitem.py",
    "content": "\"\"\"\ndj-stripe InvoiceItem Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nfrom django.test.testcases import TestCase\n\nfrom djstripe.models import Invoice, InvoiceItem\nfrom djstripe.models.payment_methods import Card\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CARD_II,\n    FAKE_CHARGE,\n    FAKE_CHARGE_II,\n    FAKE_CUSTOMER,\n    FAKE_CUSTOMER_II,\n    FAKE_INVOICE,\n    FAKE_INVOICE_II,\n    FAKE_INVOICEITEM,\n    FAKE_INVOICEITEM_III,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PAYMENT_INTENT_II,\n    FAKE_PAYMENT_METHOD_II,\n    FAKE_PLAN,\n    FAKE_PLAN_II,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_PRICE_II,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_III,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_TAX_RATE_EXAMPLE_1_VAT,\n    AssertStripeFksMixin,\n)\n\n\nclass InvoiceItemTest(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        # create a Stripe Platform Account\n        self.account = FAKE_PLATFORM_ACCOUNT.create()\n\n        self.default_expected_blank_fks = {\n            \"djstripe.Account.branding_logo\",\n            \"djstripe.Account.branding_icon\",\n            \"djstripe.Charge.application_fee\",\n            \"djstripe.Charge.dispute\",\n            \"djstripe.Charge.latest_upcominginvoice (related name)\",\n            \"djstripe.Charge.on_behalf_of\",\n            \"djstripe.Charge.source_transfer\",\n            \"djstripe.Charge.transfer\",\n            \"djstripe.Customer.coupon\",\n            \"djstripe.Customer.default_payment_method\",\n            \"djstripe.Customer.subscriber\",\n            \"djstripe.Invoice.default_payment_method\",\n            \"djstripe.Invoice.default_source\",\n            \"djstripe.Invoice.payment_intent\",\n            \"djstripe.PaymentIntent.invoice (related name)\",\n            \"djstripe.PaymentIntent.on_behalf_of\",\n            \"djstripe.PaymentIntent.payment_method\",\n            \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            \"djstripe.Product.default_price\",\n            \"djstripe.Subscription.default_payment_method\",\n            \"djstripe.Subscription.default_source\",\n            \"djstripe.Subscription.pending_setup_intent\",\n            \"djstripe.Subscription.schedule\",\n        }\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE),\n        autospec=True,\n    )\n    def test___str__(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        paymentintent_retrieve_mock,\n        paymentmethod_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_card = deepcopy(FAKE_CARD_II)\n        fake_card[\"customer\"] = None\n\n        # create Card for FAKE_CUSTOMER_III\n        Card.sync_from_stripe_data(fake_card)\n\n        # create invoice for latest_invoice in subscription to work.\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        invoiceitem = InvoiceItem.sync_from_stripe_data(deepcopy(FAKE_INVOICEITEM))\n\n        self.assertEqual(\n            invoiceitem.get_stripe_dashboard_url(),\n            invoiceitem.invoice.get_stripe_dashboard_url(),\n        )\n\n        assert str(invoiceitem) == invoiceitem.description\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_CHARGE_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE_II),\n        autospec=True,\n    )\n    def test_sync_with_subscription(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        paymentintent_retrieve_mock,\n        paymentmethod_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_II)\n        fake_payment_intent[\"invoice\"] = FAKE_INVOICE_II[\"id\"]\n        paymentintent_retrieve_mock.return_value = fake_payment_intent\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION_III)\n        fake_subscription[\"latest_invoice\"] = FAKE_INVOICE_II[\"id\"]\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        fake_customer = deepcopy(FAKE_CUSTOMER_II)\n        customer_retrieve_mock.return_value = fake_customer\n\n        fake_card = deepcopy(FAKE_CARD_II)\n        fake_card[\"customer\"] = None\n\n        # create Card for FAKE_CUSTOMER_II\n        Card.sync_from_stripe_data(fake_card)\n\n        default_account_mock.return_value = self.account\n\n        invoiceitem_data = deepcopy(FAKE_INVOICEITEM)\n        invoiceitem_data.update({\"subscription\": fake_subscription[\"id\"]})\n        invoiceitem_data.update({\"invoice\": FAKE_INVOICE_II[\"id\"]})\n        invoice_item_retrieve_mock.return_value = invoiceitem_data\n\n        invoiceitem = InvoiceItem.sync_from_stripe_data(invoiceitem_data)\n        expected_blank_fks = self.default_expected_blank_fks | {\n            \"djstripe.InvoiceItem.plan\",\n            \"djstripe.InvoiceItem.price\",\n        }\n        expected_blank_fks.difference_update(\n            {\n                \"djstripe.PaymentIntent.invoice (related name)\",\n                \"djstripe.Invoice.payment_intent\",\n            }\n        )\n\n        self.assert_fks(invoiceitem, expected_blank_fks=expected_blank_fks)\n\n        # Coverage of sync of existing data\n        invoiceitem = InvoiceItem.sync_from_stripe_data(invoiceitem_data)\n\n        self.assert_fks(invoiceitem, expected_blank_fks=expected_blank_fks)\n\n        invoice_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            expand=[\"discounts\"],\n            id=FAKE_INVOICE_II[\"id\"],\n            stripe_account=None,\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE_II), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE_II), autospec=True\n    )\n    def test_sync_expanded_invoice_with_subscription(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        paymentintent_retrieve_mock,\n        paymentmethod_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_II)\n        fake_payment_intent[\"invoice\"] = FAKE_INVOICE_II[\"id\"]\n        paymentintent_retrieve_mock.return_value = fake_payment_intent\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION_III)\n        fake_subscription[\"latest_invoice\"] = FAKE_INVOICE_II[\"id\"]\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        fake_card = deepcopy(FAKE_CARD_II)\n        fake_card[\"customer\"] = None\n        # create Card for FAKE_CUSTOMER_III\n        Card.sync_from_stripe_data(fake_card)\n\n        # create invoice\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE_II))\n\n        default_account_mock.return_value = self.account\n\n        invoiceitem_data = deepcopy(FAKE_INVOICEITEM)\n        # Expand the Invoice data\n        invoiceitem_data.update(\n            {\n                \"subscription\": FAKE_SUBSCRIPTION_III[\"id\"],\n                \"invoice\": deepcopy(dict(FAKE_INVOICE_II)),\n            }\n        )\n        invoiceitem = InvoiceItem.sync_from_stripe_data(invoiceitem_data)\n\n        expected_blank_fks = self.default_expected_blank_fks | {\n            \"djstripe.InvoiceItem.plan\",\n            \"djstripe.InvoiceItem.price\",\n        }\n        expected_blank_fks.difference_update(\n            {\n                \"djstripe.PaymentIntent.invoice (related name)\",\n                \"djstripe.Invoice.payment_intent\",\n            }\n        )\n\n        self.assert_fks(invoiceitem, expected_blank_fks=expected_blank_fks)\n\n        # Coverage of sync of existing data\n        invoiceitem = InvoiceItem.sync_from_stripe_data(invoiceitem_data)\n\n        self.assert_fks(invoiceitem, expected_blank_fks=expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Price.retrieve\", return_value=deepcopy(FAKE_PRICE_II), autospec=True)\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_II), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE_II), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE_II), autospec=True\n    )\n    def test_sync_proration(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        paymentintent_retrieve_mock,\n        paymentmethod_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        price_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_II)\n        fake_payment_intent[\"invoice\"] = FAKE_INVOICE_II[\"id\"]\n        paymentintent_retrieve_mock.return_value = fake_payment_intent\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION_III)\n        fake_subscription[\"latest_invoice\"] = FAKE_INVOICE_II[\"id\"]\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        fake_card = deepcopy(FAKE_CARD_II)\n        fake_card[\"customer\"] = None\n        # create Card for FAKE_CUSTOMER_III\n        Card.sync_from_stripe_data(fake_card)\n\n        # create invoice\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE_II))\n\n        default_account_mock.return_value = self.account\n\n        invoiceitem_data = deepcopy(FAKE_INVOICEITEM)\n        invoiceitem_data.update(\n            {\n                \"proration\": True,\n                \"plan\": FAKE_PLAN_II[\"id\"],\n                \"price\": FAKE_PRICE_II[\"id\"],\n            }\n        )\n        invoiceitem = InvoiceItem.sync_from_stripe_data(invoiceitem_data)\n\n        self.assertEqual(FAKE_PLAN_II[\"id\"], invoiceitem.plan.id)\n        self.assertEqual(FAKE_PRICE_II[\"id\"], invoiceitem.price.id)\n\n        expected_blank_fks = self.default_expected_blank_fks | {\n            \"djstripe.InvoiceItem.subscription\"\n        }\n        expected_blank_fks.difference_update(\n            {\n                \"djstripe.PaymentIntent.invoice (related name)\",\n                \"djstripe.Invoice.payment_intent\",\n            }\n        )\n\n        self.assert_fks(\n            invoiceitem,\n            expected_blank_fks=expected_blank_fks,\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\"stripe.Price.retrieve\", return_value=deepcopy(FAKE_PRICE_II), autospec=True)\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_II), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_III),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE_II), autospec=True\n    )\n    @patch(\"stripe.Invoice.retrieve\", autospec=True)\n    def test_sync_null_invoice(\n        self,\n        invoice_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        price_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        invoiceitem_data = deepcopy(FAKE_INVOICEITEM)\n        invoiceitem_data.update(\n            {\n                \"proration\": True,\n                \"plan\": FAKE_PLAN_II[\"id\"],\n                \"price\": FAKE_PRICE_II[\"id\"],\n                \"invoice\": None,\n            }\n        )\n        invoiceitem = InvoiceItem.sync_from_stripe_data(invoiceitem_data)\n\n        self.assertEqual(FAKE_PLAN_II[\"id\"], invoiceitem.plan.id)\n        self.assertEqual(FAKE_PRICE_II[\"id\"], invoiceitem.price.id)\n\n        self.assert_fks(\n            invoiceitem,\n            expected_blank_fks=self.default_expected_blank_fks\n            | {\n                \"djstripe.InvoiceItem.invoice\",\n                \"djstripe.InvoiceItem.subscription\",\n                \"djstripe.Customer.default_source\",\n            },\n        )\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE_II), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE_II), autospec=True\n    )\n    def test_sync_with_taxes(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        paymentintent_retrieve_mock,\n        paymentmethod_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_II)\n        fake_payment_intent[\"invoice\"] = FAKE_INVOICE_II[\"id\"]\n        paymentintent_retrieve_mock.return_value = fake_payment_intent\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION_III)\n        fake_subscription[\"latest_invoice\"] = FAKE_INVOICE_II[\"id\"]\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        fake_card = deepcopy(FAKE_CARD_II)\n        fake_card[\"customer\"] = None\n        # create Card for FAKE_CUSTOMER_III\n        Card.sync_from_stripe_data(fake_card)\n\n        # create invoice\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE_II))\n\n        default_account_mock.return_value = self.account\n\n        invoiceitem_data = deepcopy(FAKE_INVOICEITEM_III)\n        invoiceitem_data[\"plan\"] = FAKE_PLAN_II\n        invoiceitem_data[\"price\"] = FAKE_PRICE_II\n        invoiceitem = InvoiceItem.sync_from_stripe_data(invoiceitem_data)\n\n        self.assertEqual(invoiceitem.tax_rates.count(), 1)\n        self.assertEqual(\n            invoiceitem.tax_rates.first().id, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"id\"]\n        )\n"
  },
  {
    "path": "tests/test_line_item.py",
    "content": "\"\"\"\ndj-stripe LineItem Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import PropertyMock, patch\n\nfrom django.test.testcases import TestCase\n\nfrom djstripe.models import Invoice\nfrom djstripe.models.billing import LineItem\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_LINE_ITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n    AssertStripeFksMixin,\n)\n\n\nclass LineItemTest(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        # create a Stripe Platform Account\n        self.account = FAKE_PLATFORM_ACCOUNT.create()\n\n        self.default_expected_blank_fks = {\n            \"djstripe.Account.branding_logo\",\n            \"djstripe.Account.branding_icon\",\n            \"djstripe.Charge.application_fee\",\n            \"djstripe.Charge.dispute\",\n            \"djstripe.Charge.latest_upcominginvoice (related name)\",\n            \"djstripe.Charge.on_behalf_of\",\n            \"djstripe.Charge.source_transfer\",\n            \"djstripe.Charge.transfer\",\n            \"djstripe.Customer.default_payment_method\",\n            \"djstripe.Customer.subscriber\",\n            \"djstripe.Customer.coupon\",\n            \"djstripe.Invoice.default_payment_method\",\n            \"djstripe.Invoice.default_source\",\n            \"djstripe.InvoiceItem.plan\",\n            \"djstripe.InvoiceItem.price\",\n            \"djstripe.InvoiceItem.subscription\",\n            \"djstripe.PaymentIntent.on_behalf_of\",\n            \"djstripe.PaymentIntent.payment_method\",\n            \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            \"djstripe.Product.default_price\",\n            \"djstripe.Subscription.default_payment_method\",\n            \"djstripe.Subscription.default_source\",\n            \"djstripe.Subscription.pending_setup_intent\",\n            \"djstripe.Subscription.schedule\",\n        }\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", autospec=True, return_value=deepcopy(FAKE_CUSTOMER)\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        return_value=deepcopy(FAKE_CHARGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data(\n        self,\n        invoice_retrieve_mock,\n        invoiceitem_retrieve_mock,\n        charge_retrieve_mock,\n        customer_retrieve_mock,\n        paymentintent_retrieve_mock,\n        paymentmethod_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        product_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        # create the latest invoice as Line Items (Invoice) need to exist on an Invoice\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        # Create the Line Item\n        il = LineItem.sync_from_stripe_data(deepcopy(FAKE_LINE_ITEM))\n\n        self.assertEqual(il.id, \"il_fakefakefakefakefake0001\")\n\n        self.assert_fks(\n            il,\n            expected_blank_fks=(self.default_expected_blank_fks),\n        )\n\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        autospec=True,\n    )\n    def test_api_list(\n        self,\n        invoice_retrieve_mock,\n    ):\n        p = PropertyMock(return_value=deepcopy(FAKE_LINE_ITEM))\n\n        with patch.object(\n            invoice_retrieve_mock.return_value.lines, \"list\"\n        ) as patched_obj:\n            type(patched_obj).auto_paging_iter = p\n\n            # Invoke LineItem.api_list(...)\n            LineItem.api_list(id=FAKE_INVOICE[\"id\"], expand=[\"data.discounts\"])\n\n            # assert invoice_retrieve_mock was called once\n            invoice_retrieve_mock.assert_called_once_with(\n                FAKE_INVOICE[\"id\"],\n                api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            )\n\n            # assert invoice.lines.list(...) was called once\n            patched_obj.assert_called_once_with(\n                api_key=djstripe_settings.STRIPE_SECRET_KEY, expand=[\"data.discounts\"]\n            )\n"
  },
  {
    "path": "tests/test_managers.py",
    "content": "\"\"\"\ndj-stripe Model Manager Tests.\n\"\"\"\nimport datetime\nimport decimal\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase\n\nfrom djstripe.models import Charge, Customer, Plan, Subscription, Transfer\nfrom djstripe.utils import get_timezone_utc\n\nfrom . import (\n    FAKE_PLAN,\n    FAKE_PLAN_II,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_PRODUCT,\n    FAKE_TRANSFER,\n)\n\n\nclass SubscriptionManagerTest(TestCase):\n    def setUp(self):\n        # create customers and current subscription records\n        period_start = datetime.datetime(2013, 4, 1, tzinfo=get_timezone_utc())\n        period_end = datetime.datetime(2013, 4, 30, tzinfo=get_timezone_utc())\n        start = datetime.datetime(\n            2013, 1, 1, 0, 0, 1, tzinfo=get_timezone_utc()\n        )  # more realistic start\n\n        with patch(\n            \"stripe.Product.retrieve\",\n            return_value=deepcopy(FAKE_PRODUCT),\n            autospec=True,\n        ):\n            self.plan = Plan.sync_from_stripe_data(FAKE_PLAN)\n            self.plan2 = Plan.sync_from_stripe_data(FAKE_PLAN_II)\n\n        for i in range(10):\n            user = get_user_model().objects.create_user(\n                username=f\"patrick{i}\",\n                email=f\"patrick{i}@example.com\",\n            )\n            customer = Customer.objects.create(\n                subscriber=user,\n                id=f\"cus_xxxxxxxxxxxxxx{i}\",\n                livemode=False,\n                balance=0,\n                delinquent=False,\n            )\n\n            Subscription.objects.create(\n                id=f\"sub_xxxxxxxxxxxxxx{i}\",\n                customer=customer,\n                plan=self.plan,\n                current_period_start=period_start,\n                current_period_end=period_end,\n                status=\"active\",\n                start_date=start,\n                quantity=1,\n            )\n\n        user = get_user_model().objects.create_user(\n            username=\"patrick11\", email=\"patrick11@example.com\"\n        )\n        customer = Customer.objects.create(\n            subscriber=user,\n            id=\"cus_xxxxxxxxxxxxxx11\",\n            livemode=False,\n            balance=0,\n            delinquent=False,\n        )\n        Subscription.objects.create(\n            id=\"sub_xxxxxxxxxxxxxx11\",\n            customer=customer,\n            plan=self.plan,\n            current_period_start=period_start,\n            current_period_end=period_end,\n            status=\"canceled\",\n            canceled_at=period_end,\n            start_date=start,\n            quantity=1,\n        )\n\n        user = get_user_model().objects.create_user(\n            username=\"patrick12\", email=\"patrick12@example.com\"\n        )\n        customer = Customer.objects.create(\n            subscriber=user,\n            id=\"cus_xxxxxxxxxxxxxx12\",\n            livemode=False,\n            balance=0,\n            delinquent=False,\n        )\n        Subscription.objects.create(\n            id=\"sub_xxxxxxxxxxxxxx12\",\n            customer=customer,\n            plan=self.plan2,\n            current_period_start=period_start,\n            current_period_end=period_end,\n            status=\"active\",\n            start_date=start,\n            quantity=1,\n        )\n\n    def test_started_during_no_records(self):\n        self.assertEqual(Subscription.objects.started_during(2013, 4).count(), 0)\n\n    def test_started_during_has_records(self):\n        self.assertEqual(Subscription.objects.started_during(2013, 1).count(), 12)\n\n    def test_canceled_during(self):\n        self.assertEqual(Subscription.objects.canceled_during(2013, 4).count(), 1)\n\n    def test_canceled_all(self):\n        self.assertEqual(Subscription.objects.canceled().count(), 1)\n\n    def test_active_all(self):\n        self.assertEqual(Subscription.objects.active().count(), 11)\n\n    def test_started_plan_summary(self):\n        for plan in Subscription.objects.started_plan_summary_for(2013, 1):\n            if plan[\"plan\"] == self.plan:\n                self.assertEqual(plan[\"count\"], 11)\n            if plan[\"plan\"] == self.plan2:\n                self.assertEqual(plan[\"count\"], 1)\n\n    def test_active_plan_summary(self):\n        for plan in Subscription.objects.active_plan_summary():\n            if plan[\"plan\"] == self.plan:\n                self.assertEqual(plan[\"count\"], 10)\n            if plan[\"plan\"] == self.plan2:\n                self.assertEqual(plan[\"count\"], 1)\n\n    def test_canceled_plan_summary(self):\n        for plan in Subscription.objects.canceled_plan_summary_for(2013, 1):\n            if plan[\"plan\"] == self.plan:\n                self.assertEqual(plan[\"count\"], 1)\n            if plan[\"plan\"] == self.plan2:\n                self.assertEqual(plan[\"count\"], 0)\n\n    def test_churn(self):\n        self.assertEqual(\n            Subscription.objects.churn(), decimal.Decimal(\"1\") / decimal.Decimal(\"11\")\n        )\n\n\nclass TransferManagerTest(TestCase):\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    def test_transfer_summary(\n        self, account_retrieve_mock, transfer__attach_object_post_save_hook_mock\n    ):\n        def FAKE_TRANSFER_III():\n            data = deepcopy(FAKE_TRANSFER)\n            data[\"id\"] = \"tr_17O4U52eZvKYlo2CmyYbDAEy\"\n            data[\"amount\"] = 19010\n            data[\"created\"] = 1451560845\n            return data\n\n        def FAKE_TRANSFER_II():\n            data = deepcopy(FAKE_TRANSFER)\n            data[\"id\"] = \"tr_16hTzv2eZvKYlo2CWuyMmuvV\"\n            data[\"amount\"] = 2000\n            data[\"created\"] = 1440420000\n            return data\n\n        Transfer.sync_from_stripe_data(deepcopy(FAKE_TRANSFER))\n        Transfer.sync_from_stripe_data(FAKE_TRANSFER_II())\n        Transfer.sync_from_stripe_data(FAKE_TRANSFER_III())\n\n        self.assertEqual(Transfer.objects.during(2015, 8).count(), 2)\n\n        totals = Transfer.objects.paid_totals_for(2015, 12)\n        self.assertEqual(totals[\"total_amount\"], decimal.Decimal(\"190.10\"))\n\n\nclass ChargeManagerTest(TestCase):\n    def setUp(self):\n        customer = Customer.objects.create(\n            id=\"cus_XXXXXXX\", livemode=False, balance=0, delinquent=False\n        )\n\n        self.march_charge = Charge.objects.create(\n            id=\"ch_XXXXMAR1\",\n            customer=customer,\n            created=datetime.datetime(2015, 3, 31, tzinfo=get_timezone_utc()),\n            amount=0,\n            amount_refunded=0,\n            currency=\"usd\",\n            status=\"pending\",\n        )\n\n        self.april_charge_1 = Charge.objects.create(\n            id=\"ch_XXXXAPR1\",\n            customer=customer,\n            created=datetime.datetime(2015, 4, 1, tzinfo=get_timezone_utc()),\n            amount=decimal.Decimal(\"20.15\"),\n            amount_refunded=0,\n            currency=\"usd\",\n            status=\"succeeded\",\n            paid=True,\n        )\n\n        self.april_charge_2 = Charge.objects.create(\n            id=\"ch_XXXXAPR2\",\n            customer=customer,\n            created=datetime.datetime(2015, 4, 18, tzinfo=get_timezone_utc()),\n            amount=decimal.Decimal(\"10.35\"),\n            amount_refunded=decimal.Decimal(\"5.35\"),\n            currency=\"usd\",\n            status=\"succeeded\",\n            paid=True,\n        )\n\n        self.april_charge_3 = Charge.objects.create(\n            id=\"ch_XXXXAPR3\",\n            customer=customer,\n            created=datetime.datetime(2015, 4, 30, tzinfo=get_timezone_utc()),\n            amount=decimal.Decimal(\"100.00\"),\n            amount_refunded=decimal.Decimal(\"80.00\"),\n            currency=\"usd\",\n            status=\"pending\",\n            paid=False,\n        )\n\n        self.may_charge = Charge.objects.create(\n            id=\"ch_XXXXMAY1\",\n            customer=customer,\n            created=datetime.datetime(2015, 5, 1, tzinfo=get_timezone_utc()),\n            amount=0,\n            amount_refunded=0,\n            currency=\"usd\",\n            status=\"pending\",\n        )\n\n        self.november_charge = Charge.objects.create(\n            id=\"ch_XXXXNOV1\",\n            customer=customer,\n            created=datetime.datetime(2015, 11, 16, tzinfo=get_timezone_utc()),\n            amount=0,\n            amount_refunded=0,\n            currency=\"usd\",\n            status=\"pending\",\n        )\n\n        self.charge_2014 = Charge.objects.create(\n            id=\"ch_XXXX20141\",\n            customer=customer,\n            created=datetime.datetime(2014, 12, 31, tzinfo=get_timezone_utc()),\n            amount=0,\n            amount_refunded=0,\n            currency=\"usd\",\n            status=\"pending\",\n        )\n\n        self.charge_2016 = Charge.objects.create(\n            id=\"ch_XXXX20161\",\n            customer=customer,\n            created=datetime.datetime(2016, 1, 1, tzinfo=get_timezone_utc()),\n            amount=0,\n            amount_refunded=0,\n            currency=\"usd\",\n            status=\"pending\",\n        )\n\n    def test_is_during_april_2015(self):\n        raw_charges = Charge.objects.during(year=2015, month=4)\n        charges = [charge.id for charge in raw_charges]\n\n        self.assertIn(self.april_charge_1.id, charges, \"April charge 1 not in charges.\")\n        self.assertIn(self.april_charge_2.id, charges, \"April charge 2 not in charges.\")\n        self.assertIn(self.april_charge_3.id, charges, \"April charge 3 not in charges.\")\n\n        self.assertNotIn(\n            self.march_charge.id, charges, \"March charge unexpectedly in charges.\"\n        )\n        self.assertNotIn(\n            self.may_charge.id, charges, \"May charge unexpectedly in charges.\"\n        )\n        self.assertNotIn(\n            self.november_charge.id, charges, \"November charge unexpectedly in charges.\"\n        )\n        self.assertNotIn(\n            self.charge_2014.id, charges, \"2014 charge unexpectedly in charges.\"\n        )\n        self.assertNotIn(\n            self.charge_2016.id, charges, \"2016 charge unexpectedly in charges.\"\n        )\n\n    def test_get_paid_totals_for_april_2015(self):\n        paid_totals = Charge.objects.paid_totals_for(year=2015, month=4)\n\n        self.assertEqual(\n            decimal.Decimal(\"30.50\"),\n            paid_totals[\"total_amount\"],\n            \"Total amount is not correct.\",\n        )\n        self.assertEqual(\n            decimal.Decimal(\"5.35\"),\n            paid_totals[\"total_refunded\"],\n            \"Total amount refunded is not correct.\",\n        )\n"
  },
  {
    "path": "tests/test_migrations.py",
    "content": "\"\"\"\ndj-stripe Migrations Tests\n\"\"\"\nimport pytest\nfrom django.conf import settings\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase, override_settings\n\nfrom djstripe.models.core import Customer\nfrom djstripe.settings import djstripe_settings\n\n\nclass TestCustomerSubscriberFK(TestCase):\n    @override_settings(\n        DJSTRIPE_SUBSCRIBER_MODEL=\"testapp.Organization\",\n        DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK=(lambda request: request.org),\n    )\n    def setUp(self):\n        return super().setUp()\n\n    def test_customer_subscriber_fk_to_subscriber_model(self):\n        \"\"\"\n        Test to ensure customer.subscriber fk points to the configured model\n        set by DJSTRIPE_SUBSCRIBER_MODEL\n        \"\"\"\n        field = Customer._meta.get_field(\"subscriber\")\n\n        self.assertEqual(field.related_model, djstripe_settings.get_subscriber_model())\n        self.assertNotEqual(field.related_model, settings.AUTH_USER_MODEL)\n\n    def test_customer_subscriber_fk_fallback_to_auth_user_model(self):\n        \"\"\"\n        Test to ensure customer.subscriber fk points to the fallback AUTH_USER_MODEL\n        when DJSTRIPE_SUBSCRIBER_MODEL is not set\n        \"\"\"\n        # assert DJSTRIPE_SUBSCRIBER_MODEL has not been set\n        with pytest.raises(AttributeError):\n            settings.DJSTRIPE_SUBSCRIBER_MODEL\n\n        field = Customer._meta.get_field(\"subscriber\")\n        self.assertEqual(field.related_model, get_user_model())\n"
  },
  {
    "path": "tests/test_mixins.py",
    "content": "\"\"\"\ndj-stripe Mixin Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nfrom django.contrib.auth import get_user_model\nfrom django.test.client import RequestFactory\nfrom django.test.testcases import TestCase\n\nfrom djstripe.mixins import PaymentsContextMixin, SubscriptionMixin\nfrom djstripe.models import Plan\nfrom djstripe.settings import djstripe_settings\n\nfrom . import FAKE_CUSTOMER, FAKE_PLAN, FAKE_PLAN_II, FAKE_PRODUCT\n\n\nclass TestPaymentsContextMixin(TestCase):\n    def test_get_context_data(self):\n        class TestSuperView(object):\n            def get_context_data(self):\n                return {}\n\n        class TestView(PaymentsContextMixin, TestSuperView):\n            pass\n\n        context = TestView().get_context_data()\n        self.assertIn(\n            \"STRIPE_PUBLIC_KEY\", context, \"STRIPE_PUBLIC_KEY missing from context.\"\n        )\n        self.assertEqual(\n            context[\"STRIPE_PUBLIC_KEY\"],\n            djstripe_settings.STRIPE_PUBLIC_KEY,\n            \"Incorrect STRIPE_PUBLIC_KEY.\",\n        )\n\n        self.assertIn(\"plans\", context, \"pans missing from context.\")\n        self.assertEqual(\n            list(Plan.objects.all()), list(context[\"plans\"]), \"Incorrect plans.\"\n        )\n\n\nclass TestSubscriptionMixin(TestCase):\n    def setUp(self):\n        with patch(\n            \"stripe.Product.retrieve\",\n            return_value=deepcopy(FAKE_PRODUCT),\n            autospec=True,\n        ):\n            Plan.sync_from_stripe_data(deepcopy(FAKE_PLAN))\n            Plan.sync_from_stripe_data(deepcopy(FAKE_PLAN_II))\n\n    @patch(\n        \"stripe.Customer.create\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_get_context_data(self, stripe_create_customer_mock):\n        class TestSuperView(object):\n            def get_context_data(self):\n                return {}\n\n        class TestView(SubscriptionMixin, TestSuperView):\n            pass\n\n        test_view = TestView()\n\n        test_view.request = RequestFactory()\n        test_view.request.user = get_user_model().objects.create(\n            username=\"x\", email=\"user@test.com\"\n        )\n\n        context = test_view.get_context_data()\n        self.assertIn(\n            \"is_plans_plural\", context, \"is_plans_plural missing from context.\"\n        )\n        self.assertTrue(context[\"is_plans_plural\"], \"Incorrect is_plans_plural.\")\n\n        self.assertIn(\"customer\", context, \"customer missing from context.\")\n"
  },
  {
    "path": "tests/test_order.py",
    "content": "\"\"\"\ndj-stripe Order model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nimport stripe\nfrom django.test import TestCase\n\nfrom djstripe.enums import OrderStatus\nfrom djstripe.models import Order\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_ACCOUNT,\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT,\n    FAKE_ORDER_WITH_CUSTOMER_WITHOUT_PAYMENT_INTENT,\n    FAKE_ORDER_WITHOUT_CUSTOMER_WITH_PAYMENT_INTENT,\n    FAKE_ORDER_WITHOUT_CUSTOMER_WITHOUT_PAYMENT_INTENT,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PAYMENT_METHOD_I,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestOrder(AssertStripeFksMixin, TestCase):\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    def test_sync_from_stripe_data(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        default_expected_blank_fks = {\n            \"djstripe.Customer.default_payment_method\",\n            \"djstripe.Customer.subscriber\",\n            \"djstripe.Customer.coupon\",\n            \"djstripe.Charge.latest_upcominginvoice (related name)\",\n            \"djstripe.Charge.application_fee\",\n            \"djstripe.Charge.dispute\",\n            \"djstripe.Charge.on_behalf_of\",\n            \"djstripe.Charge.source_transfer\",\n            \"djstripe.Charge.transfer\",\n            \"djstripe.Invoice.default_payment_method\",\n            \"djstripe.Invoice.default_source\",\n            \"djstripe.Subscription.default_payment_method\",\n            \"djstripe.Subscription.default_source\",\n            \"djstripe.Subscription.pending_setup_intent\",\n            \"djstripe.Subscription.schedule\",\n            \"djstripe.PaymentIntent.on_behalf_of\",\n            \"djstripe.PaymentIntent.payment_method\",\n            \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            \"djstripe.Product.default_price\",\n        }\n        # Ensure Order objects with Customer and PaymentIntent data sync correctly\n        order = Order.sync_from_stripe_data(\n            deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT)\n        )\n        assert order\n        self.assertEqual(order.payment_intent.id, FAKE_PAYMENT_INTENT_I[\"id\"])\n        self.assertEqual(order.customer.id, FAKE_CUSTOMER[\"id\"])\n\n        self.assert_fks(order, expected_blank_fks=default_expected_blank_fks)\n\n        # Ensure Order objects with Customer and NO PaymentIntent data sync correctly\n        order = Order.sync_from_stripe_data(\n            deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITHOUT_PAYMENT_INTENT)\n        )\n        assert order\n        assert order.payment_intent is None\n        self.assertEqual(order.customer.id, FAKE_CUSTOMER[\"id\"])\n\n        self.assert_fks(\n            order,\n            expected_blank_fks=default_expected_blank_fks\n            | {\n                \"djstripe.Order.payment_intent\",\n            },\n        )\n\n        # Ensure Order objects with NO Customer and PaymentIntent data sync correctly\n        order = Order.sync_from_stripe_data(\n            deepcopy(FAKE_ORDER_WITHOUT_CUSTOMER_WITH_PAYMENT_INTENT)\n        )\n        assert order\n        self.assertEqual(order.payment_intent.id, FAKE_PAYMENT_INTENT_I[\"id\"])\n        self.assertEqual(order.customer, None)\n\n        self.assert_fks(\n            order,\n            expected_blank_fks=default_expected_blank_fks\n            | {\n                \"djstripe.Order.customer\",\n            },\n        )\n\n        # Ensure Order objects without Customer and without PaymentIntent data sync correctly\n        order = Order.sync_from_stripe_data(\n            deepcopy(FAKE_ORDER_WITHOUT_CUSTOMER_WITHOUT_PAYMENT_INTENT)\n        )\n        assert order\n        self.assertEqual(order.payment_intent, None)\n        self.assertEqual(order.customer, None)\n\n        self.assert_fks(\n            order,\n            expected_blank_fks=default_expected_blank_fks\n            | {\n                \"djstripe.Order.customer\",\n                \"djstripe.Order.payment_intent\",\n            },\n        )\n\n    def test__manipulate_stripe_object_hook(self):\n        order_data = deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT)\n\n        # Remove \"payment_intent\" key from order_data dictionary\n        del order_data[\"payment_intent\"]\n\n        # assert \"payment_intent\" key is removed from the data dictionary\n        self.assertTrue(\"payment_intent\" not in order_data)\n\n        # Invoke _manipulate_stripe_object_hook\n        modified_order_data = Order._manipulate_stripe_object_hook(order_data)\n\n        # assert \"payment_intent\" key gets added to the data dictionary\n        self.assertTrue(\"payment_intent\" in modified_order_data)\n        self.assertEqual(\n            modified_order_data[\"payment_intent\"],\n            order_data[\"payment\"][\"payment_intent\"],\n        )\n\n\nclass TestOrderStr:\n    @pytest.mark.parametrize(\n        \"order_status\",\n        [\n            OrderStatus.open,\n            OrderStatus.canceled,\n            OrderStatus.submitted,\n            OrderStatus.complete,\n            OrderStatus.processing,\n        ],\n    )\n    def test___str__(self, order_status, monkeypatch):\n        def mock_customer_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Customer.retrieve\"\"\"\n            return deepcopy(FAKE_CUSTOMER)\n\n        def mock_account_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Account.retrieve\"\"\"\n            data = deepcopy(FAKE_ACCOUNT)\n            # Otherwise Account.api_retrieve will invoke File.api_retrieve...\n            data[\"settings\"][\"branding\"] = {}\n            return data\n\n        def mock_payment_intent_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.PaymentIntent.retrieve\"\"\"\n            return deepcopy(FAKE_PAYMENT_INTENT_I)\n\n        def mock_payment_method_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.PaymentMethod.retrieve\"\"\"\n            return deepcopy(FAKE_PAYMENT_METHOD_I)\n\n        def mock_invoice_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Invoice.retrieve\"\"\"\n            return deepcopy(FAKE_INVOICE)\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_subscription_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Subscription.retrieve\"\"\"\n            return deepcopy(FAKE_SUBSCRIPTION)\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_balance_transaction_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.BalanceTransaction.retrieve\"\"\"\n            return deepcopy(FAKE_BALANCE_TRANSACTION)\n\n        def mock_product_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Product.retrieve\"\"\"\n            return deepcopy(FAKE_PRODUCT)\n\n        def mock_charge_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Charge.retrieve\"\"\"\n            return deepcopy(FAKE_CHARGE)\n\n        # monkeypatch stripe.Product.retrieve, stripe.Price.retrieve, stripe.PaymentIntent.retrieve, stripe.PaymentMethod.retrieve, and stripe.PaymentIntent.retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Account, \"retrieve\", mock_account_get)\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n\n        # because of Reverse o2o field sync due to PaymentIntent.sync_from_stripe_data..\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n\n        order_data = deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT)\n        order_data[\"status\"] = order_status\n\n        order = Order.sync_from_stripe_data(order_data)\n\n        if order_status in (OrderStatus.open, OrderStatus.canceled):\n            assert str(order) == f\"Created on 07/10/2019 ({order_status})\"\n\n        elif order_status in (\n            OrderStatus.submitted,\n            OrderStatus.complete,\n            OrderStatus.processing,\n        ):\n            assert str(order) == f\"Placed on 07/10/2019 ({order_status})\"\n\n\nclass TestOrderMethods:\n    @pytest.mark.parametrize(\"stripe_account\", (None, \"acct_fakefakefakefake001\"))\n    @pytest.mark.parametrize(\n        \"api_key, expected_api_key\",\n        (\n            (None, djstripe_settings.STRIPE_SECRET_KEY),\n            (\"sk_fakefakefake01\", \"sk_fakefakefake01\"),\n        ),\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.Order.cancel\",\n    )\n    def test_cancel(\n        self,\n        order_cancel_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        api_key,\n        expected_api_key,\n        stripe_account,\n    ):\n        fake_order = deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT)\n\n        fake_order_cancel = deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT)\n        fake_order_cancel[\"status\"] = \"canceled\"\n        order_cancel_mock.return_value = fake_order_cancel\n\n        order = Order.sync_from_stripe_data(fake_order)\n        assert order\n\n        # Cancel the order\n        cancelled_order = order.cancel(\n            api_key=api_key,\n            stripe_account=stripe_account,\n        )\n\n        # assert Order got cancelled\n        assert cancelled_order[\"status\"] == \"canceled\"\n\n        # assert cancel called with the correct kwargs\n        Order.stripe_class.cancel.assert_called_once_with(\n            \"order_fakefakefakefakefake0001\",\n            api_key=expected_api_key,\n            stripe_account=stripe_account or FAKE_PLATFORM_ACCOUNT[\"id\"],\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @pytest.mark.parametrize(\"stripe_account\", (None, \"acct_fakefakefakefake001\"))\n    @pytest.mark.parametrize(\n        \"api_key, expected_api_key\",\n        (\n            (None, djstripe_settings.STRIPE_SECRET_KEY),\n            (\"sk_fakefakefake01\", \"sk_fakefakefake01\"),\n        ),\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.Order.reopen\",\n    )\n    def test_reopen(\n        self,\n        order_reopen_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        api_key,\n        expected_api_key,\n        stripe_account,\n    ):\n        fake_order = deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT)\n\n        fake_order_reopen = deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT)\n        fake_order_reopen[\"status\"] = \"open\"\n        order_reopen_mock.return_value = fake_order_reopen\n\n        order = Order.sync_from_stripe_data(fake_order)\n        assert order\n\n        # Reopen the order\n        reopened_order = order.reopen(\n            api_key=api_key,\n            stripe_account=stripe_account,\n        )\n\n        # assert Order got reopened\n        assert reopened_order[\"status\"] == \"open\"\n\n        # assert reopen called with the correct kwargs\n        Order.stripe_class.reopen.assert_called_once_with(\n            \"order_fakefakefakefakefake0001\",\n            api_key=expected_api_key,\n            stripe_account=stripe_account or FAKE_PLATFORM_ACCOUNT[\"id\"],\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @pytest.mark.parametrize(\"stripe_account\", (None, \"acct_fakefakefakefake001\"))\n    @pytest.mark.parametrize(\n        \"api_key, expected_api_key\",\n        (\n            (None, djstripe_settings.STRIPE_SECRET_KEY),\n            (\"sk_fakefakefake01\", \"sk_fakefakefake01\"),\n        ),\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.Order.submit\",\n    )\n    def test_submit(\n        self,\n        order_submit_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        api_key,\n        expected_api_key,\n        stripe_account,\n    ):\n        fake_order = deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT)\n\n        fake_order_submit = deepcopy(FAKE_ORDER_WITH_CUSTOMER_WITH_PAYMENT_INTENT)\n        fake_order_submit[\"status\"] = \"submitted\"\n        order_submit_mock.return_value = fake_order_submit\n\n        order = Order.sync_from_stripe_data(fake_order)\n        assert order\n\n        expected_total = fake_order[\"amount_total\"]\n\n        # Submit the order\n        submitted_order = order.submit(\n            api_key=api_key,\n            stripe_account=stripe_account,\n            expected_total=expected_total,\n        )\n\n        # assert Order got submitted\n        assert submitted_order[\"status\"] == \"submitted\"\n\n        # assert submit called with the correct kwargs\n        Order.stripe_class.submit.assert_called_once_with(\n            \"order_fakefakefakefakefake0001\",\n            api_key=expected_api_key,\n            stripe_account=stripe_account or FAKE_PLATFORM_ACCOUNT[\"id\"],\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            expected_total=expected_total,\n        )\n"
  },
  {
    "path": "tests/test_payment_intent.py",
    "content": "\"\"\"\ndj-stripe PaymentIntent Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nimport stripe\nfrom django.test import TestCase\n\nfrom djstripe.models import PaymentIntent\n\nfrom . import (\n    FAKE_ACCOUNT,\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_DESTINATION_CHARGE,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PAYMENT_METHOD_I,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\ndef _get_fake_payment_intent_destination_charge_no_customer():\n    FAKE_PAYMENT_INTENT_DESTINATION_CHARGE_NO_CUSTOMER = deepcopy(\n        FAKE_PAYMENT_INTENT_DESTINATION_CHARGE\n    )\n    FAKE_PAYMENT_INTENT_DESTINATION_CHARGE_NO_CUSTOMER[\"customer\"] = None\n    return FAKE_PAYMENT_INTENT_DESTINATION_CHARGE_NO_CUSTOMER\n\n\ndef _get_fake_payment_intent_i_no_customer():\n    FAKE_PAYMENT_INTENT_I_NO_CUSTOMER = deepcopy(FAKE_PAYMENT_INTENT_I)\n    FAKE_PAYMENT_INTENT_I_NO_CUSTOMER[\"customer\"] = None\n    return FAKE_PAYMENT_INTENT_I_NO_CUSTOMER\n\n\nclass TestStrPaymentIntent:\n    #\n    # Helpers\n    #\n\n    @pytest.mark.parametrize(\n        \"fake_intent_data, has_account, has_customer\",\n        [\n            (FAKE_PAYMENT_INTENT_I, False, True),\n            (FAKE_PAYMENT_INTENT_DESTINATION_CHARGE, True, True),\n            (_get_fake_payment_intent_destination_charge_no_customer(), True, False),\n            (_get_fake_payment_intent_i_no_customer(), False, False),\n        ],\n    )\n    def test___str__(self, fake_intent_data, has_account, has_customer, monkeypatch):\n        def mock_customer_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Customer.retrieve\"\"\"\n            return deepcopy(FAKE_CUSTOMER)\n\n        def mock_account_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Account.retrieve\"\"\"\n            data = deepcopy(FAKE_ACCOUNT)\n            # Otherwise Account.api_retrieve will invoke File.api_retrieve...\n            data[\"settings\"][\"branding\"] = {}\n            return data\n\n        def mock_payment_method_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.PaymentMethod.retrieve\"\"\"\n            return deepcopy(FAKE_PAYMENT_METHOD_I)\n\n        def mock_invoice_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Invoice.retrieve\"\"\"\n            return deepcopy(FAKE_INVOICE)\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_subscription_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Subscription.retrieve\"\"\"\n            return deepcopy(FAKE_SUBSCRIPTION)\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_balance_transaction_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.BalanceTransaction.retrieve\"\"\"\n            return deepcopy(FAKE_BALANCE_TRANSACTION)\n\n        def mock_product_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Product.retrieve\"\"\"\n            return deepcopy(FAKE_PRODUCT)\n\n        def mock_charge_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Charge.retrieve\"\"\"\n            return deepcopy(FAKE_CHARGE)\n\n        # monkeypatch stripe.Product.retrieve, stripe.Price.retrieve, stripe.PaymentMethod.retrieve, and stripe.PaymentIntent.retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Account, \"retrieve\", mock_account_get)\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n\n        # because of Reverse o2o field sync due to PaymentIntent.sync_from_stripe_data..\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n\n        pi = PaymentIntent.sync_from_stripe_data(fake_intent_data)\n        assert pi\n\n        # due to reverse o2o sync invoice should also get created\n        if fake_intent_data.get(\"invoice\"):\n            assert pi.invoice is not None\n\n        if has_account and has_customer:\n            assert (\n                str(pi)\n                == \"$1,902.00 USD (The funds are in your account.) for dj-stripe by Michael Smith\"\n            )\n\n        elif has_account and not has_customer:\n            assert (\n                str(pi)\n            ) == \"$1,902.00 USD for dj-stripe. The funds are in your account.\"\n\n        elif has_customer and not has_account:\n            assert (\n                str(pi)\n            ) == \"$20.00 USD by Michael Smith. The funds are in your account.\"\n        elif not has_customer and not has_account:\n            assert str(pi) == \"$20.00 USD (The funds are in your account.)\"\n\n\nclass PaymentIntentTest(AssertStripeFksMixin, TestCase):\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    def test_sync_from_stripe_data(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        paymentintent_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        payment_intent = PaymentIntent.sync_from_stripe_data(\n            deepcopy(FAKE_PAYMENT_INTENT_I)\n        )\n\n        self.assert_fks(\n            payment_intent,\n            expected_blank_fks={\n                \"djstripe.Charge.latest_upcominginvoice (related name)\",\n                \"djstripe.Charge.application_fee\",\n                \"djstripe.Charge.dispute\",\n                \"djstripe.Charge.on_behalf_of\",\n                \"djstripe.Charge.source_transfer\",\n                \"djstripe.Charge.transfer\",\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.subscriber\",\n                \"djstripe.Invoice.default_payment_method\",\n                \"djstripe.Invoice.default_source\",\n                \"djstripe.PaymentIntent.on_behalf_of\",\n                \"djstripe.PaymentIntent.payment_method\",\n                \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n                \"djstripe.Product.default_price\",\n                \"djstripe.Subscription.default_payment_method\",\n                \"djstripe.Subscription.default_source\",\n                \"djstripe.Subscription.pending_setup_intent\",\n                \"djstripe.Subscription.schedule\",\n            },\n        )\n\n        assert payment_intent\n        self.assertIsNotNone(payment_intent.invoice)\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    def test_status_enum(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        paymentintent_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_I)\n\n        for status in (\n            \"requires_payment_method\",\n            \"requires_confirmation\",\n            \"requires_action\",\n            \"processing\",\n            \"requires_capture\",\n            \"canceled\",\n            \"succeeded\",\n        ):\n            fake_payment_intent[\"status\"] = status\n            payment_intent = PaymentIntent.sync_from_stripe_data(fake_payment_intent)\n\n            # trigger model field validation (including enum value choices check)\n            assert payment_intent\n            payment_intent.full_clean()\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    def test_canceled_intent(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        paymentintent_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_I)\n\n        fake_payment_intent[\"status\"] = \"canceled\"\n        fake_payment_intent[\"canceled_at\"] = 1567524169\n\n        for reason in (\n            None,\n            \"duplicate\",\n            \"fraudulent\",\n            \"requested_by_customer\",\n            \"abandoned\",\n            \"failed_invoice\",\n            \"void_invoice\",\n            \"automatic\",\n        ):\n            fake_payment_intent[\"cancellation_reason\"] = reason\n            paymentintent_retrieve_mock.return_value = fake_payment_intent\n\n            payment_intent = PaymentIntent.sync_from_stripe_data(fake_payment_intent)\n            assert payment_intent\n\n            if reason is None:\n                # enums nulls are coerced to \"\" by StripeModel._stripe_object_to_record\n                self.assertEqual(payment_intent.cancellation_reason, \"\")\n            else:\n                self.assertEqual(payment_intent.cancellation_reason, reason)\n\n            # trigger model field validation (including enum value choices check)\n            payment_intent.full_clean()\n"
  },
  {
    "path": "tests/test_payment_method.py",
    "content": "\"\"\"\ndj-stripe PaymenthMethod Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nimport stripe\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase\nfrom stripe.error import InvalidRequestError\n\nfrom djstripe import enums, models\n\nfrom . import (\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CUSTOMER,\n    FAKE_PAYMENT_METHOD_I,\n    AssertStripeFksMixin,\n    PaymentMethodDict,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestPaymentMethod:\n    #\n    # Helper Methods for monkeypatching\n    #\n\n    def mock_customer_get(*args, **kwargs):\n        return deepcopy(FAKE_CUSTOMER)\n\n    @pytest.mark.parametrize(\"customer_exists\", [True, False])\n    def test___str__(self, monkeypatch, customer_exists):\n        # monkeypatch stripe.Customer.retrieve call to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", self.mock_customer_get)\n\n        fake_payment_method_data = deepcopy(FAKE_PAYMENT_METHOD_I)\n        if not customer_exists:\n            fake_payment_method_data[\"customer\"] = None\n            pm = models.PaymentMethod.sync_from_stripe_data(fake_payment_method_data)\n            customer = None\n            assert (\n                f\"{enums.PaymentMethodType.humanize(fake_payment_method_data['type'])} is not associated with any customer\"\n            ) == str(pm)\n\n        else:\n            pm = models.PaymentMethod.sync_from_stripe_data(fake_payment_method_data)\n            customer = models.Customer.objects.get(\n                id=fake_payment_method_data[\"customer\"]\n            )\n            assert (\n                f\"{enums.PaymentMethodType.humanize(fake_payment_method_data['type'])} for {customer}\"\n            ) == str(pm)\n\n    @pytest.mark.parametrize(\"customer_exists\", [True, False])\n    def test_get_stripe_dashboard_url(self, monkeypatch, customer_exists):\n        # monkeypatch stripe.Customer.retrieve call to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", self.mock_customer_get)\n\n        fake_payment_method_data = deepcopy(FAKE_PAYMENT_METHOD_I)\n        if not customer_exists:\n            fake_payment_method_data[\"customer\"] = None\n            pm = models.PaymentMethod.sync_from_stripe_data(fake_payment_method_data)\n            assert pm\n            assert pm.get_stripe_dashboard_url() == \"\"\n\n        else:\n            pm = models.PaymentMethod.sync_from_stripe_data(fake_payment_method_data)\n            assert pm\n            customer = models.Customer.objects.get(\n                id=fake_payment_method_data[\"customer\"]\n            )\n            assert pm.get_stripe_dashboard_url() == customer.get_stripe_dashboard_url()\n\n    @pytest.mark.parametrize(\"customer_exists\", [True, False])\n    def test_sync_from_stripe_data(self, monkeypatch, customer_exists):\n        # monkeypatch stripe.Customer.retrieve call to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", self.mock_customer_get)\n\n        fake_payment_method_data = deepcopy(FAKE_PAYMENT_METHOD_I)\n        if not customer_exists:\n            fake_payment_method_data[\"customer\"] = None\n\n        pm = models.PaymentMethod.sync_from_stripe_data(fake_payment_method_data)\n        assert pm\n        assert pm.id == fake_payment_method_data[\"id\"]\n\n\nclass PaymentMethodTest(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        user = get_user_model().objects.create_user(\n            username=\"testuser\", email=\"djstripe@example.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(user)\n\n    # stripe modifies attach() at compile time, which is why\n    # another stripe classmethod is decorated.\n    # See Here:\n    # https://github.com/stripe/stripe-python/blob/master/stripe/api_resources/payment_method.py#L10\n    # https://github.com/stripe/stripe-python/blob/master/stripe/api_resources/abstract/custom_method.py#L35\n    @patch(\n        \"stripe.PaymentMethod._cls_attach\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    def test_attach(self, attach_mock):\n        payment_method = models.PaymentMethod.attach(\n            FAKE_PAYMENT_METHOD_I[\"id\"], customer=FAKE_CUSTOMER[\"id\"]\n        )\n\n        self.assert_fks(\n            payment_method,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n            },\n        )\n\n    @patch(\n        \"stripe.PaymentMethod._cls_attach\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    def test_attach_obj(self, attach_mock):\n        pm = models.PaymentMethod.sync_from_stripe_data(FAKE_PAYMENT_METHOD_I)\n        assert pm\n\n        payment_method = models.PaymentMethod.attach(pm, customer=self.customer)\n\n        self.assert_fks(\n            payment_method,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n            },\n        )\n\n    @patch(\n        \"stripe.PaymentMethod._cls_attach\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    def test_attach_synced(self, attach_mock):\n        fake_payment_method = deepcopy(FAKE_PAYMENT_METHOD_I)\n        fake_payment_method[\"customer\"] = None\n\n        payment_method = models.PaymentMethod.sync_from_stripe_data(fake_payment_method)\n        assert payment_method\n\n        self.assert_fks(\n            payment_method, expected_blank_fks={\"djstripe.PaymentMethod.customer\"}\n        )\n\n        payment_method = models.PaymentMethod.attach(\n            payment_method.id, customer=FAKE_CUSTOMER[\"id\"]\n        )\n\n        self.assert_fks(\n            payment_method,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n            },\n        )\n\n    def test_detach(self):\n        original_detach = PaymentMethodDict.detach\n\n        def mocked_detach(*args, **kwargs):\n            return original_detach(*args, **kwargs)\n\n        with patch(\n            \"stripe.PaymentMethod.retrieve\",\n            return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n            autospec=True,\n        ):\n            models.PaymentMethod.sync_from_stripe_data(deepcopy(FAKE_PAYMENT_METHOD_I))\n\n        assert self.customer\n        self.assertEqual(1, self.customer.payment_methods.count())\n\n        payment_method = self.customer.payment_methods.first()\n\n        with patch(\n            \"tests.PaymentMethodDict.detach\", side_effect=mocked_detach, autospec=True\n        ) as mock_detach, patch(\n            \"stripe.PaymentMethod.retrieve\",\n            return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n            autospec=True,\n        ):\n            self.assertTrue(payment_method.detach())\n\n        self.assertEqual(0, self.customer.payment_methods.count())\n        self.assertIsNone(self.customer.default_payment_method)\n\n        self.assertIsNone(payment_method.customer)\n\n        mock_detach.assert_called()\n\n        self.assert_fks(\n            payment_method, expected_blank_fks={\"djstripe.PaymentMethod.customer\"}\n        )\n\n        with patch(\n            \"tests.PaymentMethodDict.detach\",\n            side_effect=InvalidRequestError(\n                message=\"A source must be attached to a customer to be used \"\n                \"as a `payment_method`\",\n                param=\"payment_method\",\n            ),\n            autospec=True,\n        ) as mock_detach, patch(\n            \"stripe.PaymentMethod.retrieve\",\n            return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n            autospec=True,\n        ) as payment_method_retrieve_mock:\n            payment_method_retrieve_mock.return_value[\"customer\"] = None\n            # Need to re-sync as the PaymentMethod object has been deleted\n            models.PaymentMethod.sync_from_stripe_data(\n                deepcopy(FAKE_CARD_AS_PAYMENT_METHOD)\n            )\n            self.assertFalse(\n                payment_method.detach(), \"Second call to detach should return false\"\n            )\n\n    def test_detach_card(self):\n        original_detach = PaymentMethodDict.detach\n\n        # \"card_\" payment methods are deleted after detach\n        deleted_card_exception = InvalidRequestError(\n            message=\"No such payment_method: card_xxxx\",\n            param=\"payment_method\",\n            code=\"resource_missing\",\n        )\n\n        def mocked_detach(*args, **kwargs):\n            return original_detach(*args, **kwargs)\n\n        with patch(\n            \"stripe.PaymentMethod.retrieve\",\n            return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n            autospec=True,\n        ):\n            models.PaymentMethod.sync_from_stripe_data(\n                deepcopy(FAKE_CARD_AS_PAYMENT_METHOD)\n            )\n\n        assert self.customer\n        assert self.customer.payment_methods.count() == 1\n\n        payment_method = self.customer.payment_methods.first()\n\n        self.assertTrue(\n            payment_method.id.startswith(\"card_\"), \"We expect this to be a 'card_'\"\n        )\n\n        with patch(\n            \"tests.PaymentMethodDict.detach\", side_effect=mocked_detach, autospec=True\n        ) as mock_detach, patch(\n            \"stripe.PaymentMethod.retrieve\",\n            return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n            autospec=True,\n        ):\n            self.assertTrue(payment_method.detach())\n\n        assert self.customer.payment_methods.count() == 0\n        assert self.customer.default_payment_method is None\n\n        self.assertEqual(\n            models.PaymentMethod.objects.filter(id=payment_method.id).count(),\n            0,\n            \"We expect PaymentMethod id = card_* to be deleted\",\n        )\n\n        mock_detach.assert_called()\n\n        with patch(\n            \"tests.PaymentMethodDict.detach\",\n            side_effect=InvalidRequestError(\n                message=\"A source must be attached to a customer to be used \"\n                \"as a `payment_method`\",\n                param=\"payment_method\",\n            ),\n            autospec=True,\n        ) as mock_detach, patch(\n            \"stripe.PaymentMethod.retrieve\",\n            side_effect=deleted_card_exception,\n            autospec=True,\n        ) as payment_method_retrieve_mock:\n            payment_method_retrieve_mock.return_value[\"customer\"] = None\n            # Need to re-sync as the PaymentMethod object has been deleted\n            models.PaymentMethod.sync_from_stripe_data(\n                deepcopy(FAKE_CARD_AS_PAYMENT_METHOD)\n            )\n            # Get the Payment Method from the DB\n            payment_method = models.PaymentMethod.objects.filter(\n                id=payment_method.id\n            ).first()\n            self.assertFalse(\n                payment_method.detach(), \"Second call to detach should return false\"\n            )\n\n    def test_sync_null_customer(self):\n        payment_method = models.PaymentMethod.sync_from_stripe_data(\n            deepcopy(FAKE_PAYMENT_METHOD_I)\n        )\n        assert payment_method\n\n        self.assertIsNotNone(payment_method.customer)\n\n        # simulate remote detach\n        fake_payment_method_no_customer = deepcopy(FAKE_PAYMENT_METHOD_I)\n        fake_payment_method_no_customer[\"customer\"] = None\n\n        payment_method = models.PaymentMethod.sync_from_stripe_data(\n            fake_payment_method_no_customer\n        )\n        assert payment_method\n\n        self.assertIsNone(payment_method.customer)\n\n        self.assert_fks(\n            payment_method, expected_blank_fks={\"djstripe.PaymentMethod.customer\"}\n        )\n"
  },
  {
    "path": "tests/test_payout.py",
    "content": "\"\"\"\ndj-stripe Payout Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase\n\nfrom djstripe.models import BankAccount, Card, Payout\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_BANK_ACCOUNT,\n    FAKE_CARD,\n    FAKE_CUSTOM_ACCOUNT,\n    FAKE_CUSTOMER,\n    FAKE_EXPRESS_ACCOUNT,\n    FAKE_PAYOUT_CUSTOM_BANK_ACCOUNT,\n    FAKE_PAYOUT_CUSTOM_CARD,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_STANDARD_ACCOUNT,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestPayout(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        # create a Standard Stripe Account\n        self.standard_account = FAKE_STANDARD_ACCOUNT.create()\n\n        # create a Custom Stripe Account\n        self.custom_account = FAKE_CUSTOM_ACCOUNT.create()\n\n        # create an Express Stripe Account\n        self.express_account = FAKE_EXPRESS_ACCOUNT.create()\n\n        user = get_user_model().objects.create_user(\n            username=\"arnav13\", email=\"arnav13@gmail.com\"\n        )\n        fake_empty_customer = deepcopy(FAKE_CUSTOMER)\n        fake_empty_customer[\"default_source\"] = None\n        fake_empty_customer[\"sources\"] = []\n\n        self.customer = fake_empty_customer.create_for_user(user)\n\n        self.card = Card.sync_from_stripe_data(FAKE_CARD)\n        self.bank_account = BankAccount.sync_from_stripe_data(FAKE_BANK_ACCOUNT)\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data(self, balance_transaction_retrieve_mock):\n        fake_payout_custom = deepcopy(FAKE_PAYOUT_CUSTOM_BANK_ACCOUNT)\n        payout = Payout.sync_from_stripe_data(fake_payout_custom)\n\n        self.assertEqual(payout.balance_transaction.id, FAKE_BALANCE_TRANSACTION[\"id\"])\n        self.assertEqual(payout.destination.id, fake_payout_custom[\"destination\"])\n        self.assertEqual(payout.djstripe_owner_account.id, FAKE_PLATFORM_ACCOUNT[\"id\"])\n        self.assert_fks(\n            payout, expected_blank_fks={\"djstripe.Payout.failure_balance_transaction\"}\n        )\n\n        fake_payout_express = deepcopy(FAKE_PAYOUT_CUSTOM_CARD)\n        payout = Payout.sync_from_stripe_data(fake_payout_express)\n\n        self.assertEqual(payout.balance_transaction.id, FAKE_BALANCE_TRANSACTION[\"id\"])\n        self.assertEqual(payout.destination.id, fake_payout_express[\"destination\"])\n        self.assertEqual(payout.djstripe_owner_account.id, FAKE_PLATFORM_ACCOUNT[\"id\"])\n        self.assert_fks(\n            payout, expected_blank_fks={\"djstripe.Payout.failure_balance_transaction\"}\n        )\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    def test___str__(self, balance_transaction_retrieve_mock):\n        fake_payout_custom = deepcopy(FAKE_PAYOUT_CUSTOM_BANK_ACCOUNT)\n        payout = Payout.sync_from_stripe_data(fake_payout_custom)\n\n        self.assertEqual(str(payout), \"10.00 (Paid)\")\n\n        fake_payout_express = deepcopy(FAKE_PAYOUT_CUSTOM_CARD)\n        payout = Payout.sync_from_stripe_data(fake_payout_express)\n\n        self.assertEqual(str(payout), \"10.00 (Paid)\")\n"
  },
  {
    "path": "tests/test_plan.py",
    "content": "\"\"\"\ndj-stripe Plan Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nimport stripe\nfrom django.test import TestCase\n\nfrom djstripe.enums import PriceUsageType\nfrom djstripe.models import Plan, Product\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_PLAN,\n    FAKE_PLAN_II,\n    FAKE_PLAN_METERED,\n    FAKE_PRODUCT,\n    FAKE_TIER_PLAN,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass PlanCreateTest(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        with patch(\n            \"stripe.Product.retrieve\",\n            return_value=deepcopy(FAKE_PRODUCT),\n            autospec=True,\n        ):\n            self.stripe_product = Product(id=FAKE_PRODUCT[\"id\"]).api_retrieve()\n\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\"stripe.Plan.create\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    def test_create_from_product_id(self, plan_create_mock, product_retrieve_mock):\n        fake_plan = deepcopy(FAKE_PLAN)\n        fake_plan[\"amount\"] = fake_plan[\"amount\"] / 100\n        self.assertIsInstance(fake_plan[\"product\"], str)\n\n        plan = Plan.create(**fake_plan)\n\n        expected_create_kwargs = deepcopy(FAKE_PLAN)\n        expected_create_kwargs[\"api_key\"] = djstripe_settings.STRIPE_SECRET_KEY\n        expected_create_kwargs[\"stripe_version\"] = djstripe_settings.STRIPE_API_VERSION\n\n        plan_create_mock.assert_called_once_with(**expected_create_kwargs)\n\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\"stripe.Plan.create\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    def test_create_from_stripe_product(self, plan_create_mock, product_retrieve_mock):\n        fake_plan = deepcopy(FAKE_PLAN)\n        fake_plan[\"product\"] = self.stripe_product\n        fake_plan[\"amount\"] = fake_plan[\"amount\"] / 100\n        self.assertIsInstance(fake_plan[\"product\"], dict)\n\n        plan = Plan.create(**fake_plan)\n\n        expected_create_kwargs = deepcopy(FAKE_PLAN)\n        expected_create_kwargs[\"product\"] = self.stripe_product\n\n        plan_create_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **expected_create_kwargs,\n        )\n\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\"stripe.Plan.create\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    def test_create_from_djstripe_product(\n        self, plan_create_mock, product_retrieve_mock\n    ):\n        fake_plan = deepcopy(FAKE_PLAN)\n        fake_plan[\"product\"] = Product.sync_from_stripe_data(self.stripe_product)\n        fake_plan[\"amount\"] = fake_plan[\"amount\"] / 100\n        self.assertIsInstance(fake_plan[\"product\"], Product)\n\n        plan = Plan.create(**fake_plan)\n\n        plan_create_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **FAKE_PLAN,\n        )\n\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\"stripe.Plan.create\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    def test_create_with_metadata(self, plan_create_mock, product_retrieve_mock):\n        metadata = {\"other_data\": \"more_data\"}\n        fake_plan = deepcopy(FAKE_PLAN)\n        fake_plan[\"amount\"] = fake_plan[\"amount\"] / 100\n        fake_plan[\"metadata\"] = metadata\n        self.assertIsInstance(fake_plan[\"product\"], str)\n\n        plan = Plan.create(**fake_plan)\n\n        expected_create_kwargs = deepcopy(FAKE_PLAN)\n        expected_create_kwargs[\"metadata\"] = metadata\n\n        plan_create_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **expected_create_kwargs,\n        )\n\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n\nclass PlanTest(AssertStripeFksMixin, TestCase):\n    plan: Plan\n\n    def setUp(self):\n        self.plan_data = deepcopy(FAKE_PLAN)\n        with patch(\n            \"stripe.Product.retrieve\",\n            return_value=deepcopy(FAKE_PRODUCT),\n            autospec=True,\n        ):\n            self.plan = Plan.sync_from_stripe_data(self.plan_data)\n\n    def test___str__(self):\n        assert (\n            str(self.plan)\n            == f\"{self.plan.human_readable_price} for {FAKE_PRODUCT['name']}\"\n        )\n\n    def test___str__null_product(self):\n        plan_data = deepcopy(FAKE_PLAN_II)\n        del plan_data[\"product\"]\n        plan: Plan = Plan.sync_from_stripe_data(plan_data)\n\n        self.assertIsNone(plan.product)\n\n        assert str(plan) == plan.human_readable_price\n\n    @patch(\"stripe.Plan.retrieve\", return_value=FAKE_PLAN, autospec=True)\n    def test_stripe_plan(self, plan_retrieve_mock):\n        stripe_plan = self.plan.api_retrieve()\n        plan_retrieve_mock.assert_called_once_with(\n            id=self.plan_data[\"id\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            expand=[\"product\", \"tiers\"],\n            stripe_account=self.plan.djstripe_owner_account.id,\n        )\n        plan = Plan.sync_from_stripe_data(stripe_plan)\n        assert plan.amount_in_cents == plan.amount * 100\n        assert isinstance(plan.amount_in_cents, int)\n\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n    def test_stripe_plan_null_product(self):\n        \"\"\"\n        assert that plan.Product can be null for backwards compatibility\n        though note that it is a Stripe required field\n        \"\"\"\n        plan_data = deepcopy(FAKE_PLAN_II)\n        del plan_data[\"product\"]\n        plan = Plan.sync_from_stripe_data(plan_data)\n\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\"djstripe.Customer.coupon\", \"djstripe.Plan.product\"},\n        )\n\n    def test_stripe_tier_plan(self):\n        tier_plan_data = deepcopy(FAKE_TIER_PLAN)\n        plan = Plan.sync_from_stripe_data(tier_plan_data)\n\n        self.assertEqual(plan.id, tier_plan_data[\"id\"])\n        self.assertIsNone(plan.amount)\n        self.assertIsNotNone(plan.tiers, plan.product)\n\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n    def test_stripe_metered_plan(self):\n        plan_data = deepcopy(FAKE_PLAN_METERED)\n        plan = Plan.sync_from_stripe_data(plan_data)\n        self.assertEqual(plan.id, plan_data[\"id\"])\n        self.assertEqual(plan.usage_type, PriceUsageType.metered)\n        self.assertIsNotNone(plan.amount, plan.product)\n\n        self.assert_fks(\n            plan,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n\nclass TestHumanReadablePlan:\n    #\n    # Helpers\n    #\n    def get_fake_price_NONE_flat_amount():\n        FAKE_PRICE_TIER_NONE_FLAT_AMOUNT = deepcopy(FAKE_TIER_PLAN)\n        FAKE_PRICE_TIER_NONE_FLAT_AMOUNT[\"tiers\"][0][\"flat_amount\"] = None\n        FAKE_PRICE_TIER_NONE_FLAT_AMOUNT[\"tiers\"][0][\"flat_amount_decimal\"] = None\n        return FAKE_PRICE_TIER_NONE_FLAT_AMOUNT\n\n    def get_fake_price_0_flat_amount():\n        FAKE_PRICE_TIER_0_FLAT_AMOUNT = deepcopy(FAKE_TIER_PLAN)\n        FAKE_PRICE_TIER_0_FLAT_AMOUNT[\"tiers\"][0][\"flat_amount\"] = 0\n        FAKE_PRICE_TIER_0_FLAT_AMOUNT[\"tiers\"][0][\"flat_amount_decimal\"] = 0\n        return FAKE_PRICE_TIER_0_FLAT_AMOUNT\n\n    def get_fake_price_0_amount():\n        FAKE_PRICE_TIER_0_AMOUNT = deepcopy(FAKE_PLAN)\n        FAKE_PRICE_TIER_0_AMOUNT[\"amount\"] = 0\n        FAKE_PRICE_TIER_0_AMOUNT[\"amount_decimal\"] = 0\n        return FAKE_PRICE_TIER_0_AMOUNT\n\n    @pytest.mark.parametrize(\n        \"fake_plan_data, expected_str\",\n        [\n            (deepcopy(FAKE_PLAN), \"$20.00 USD/month\"),\n            (get_fake_price_0_amount(), \"$0.00 USD/month\"),\n            (\n                deepcopy(FAKE_TIER_PLAN),\n                \"Starts at $10.00 USD per unit + $49.00 USD/month\",\n            ),\n            (\n                get_fake_price_0_flat_amount(),\n                \"Starts at $10.00 USD per unit + $0.00 USD/month\",\n            ),\n            (\n                get_fake_price_NONE_flat_amount(),\n                \"Starts at $10.00 USD per unit/month\",\n            ),\n            (deepcopy(FAKE_PLAN_METERED), \"$2.00 USD/month\"),\n        ],\n    )\n    def test_human_readable(self, fake_plan_data, expected_str, monkeypatch):\n        def mock_product_get(*args, **kwargs):\n            return deepcopy(FAKE_PRODUCT)\n\n        def mock_price_get(*args, **kwargs):\n            return fake_plan_data\n\n        # monkeypatch stripe.Product.retrieve and stripe.Plan.retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n        monkeypatch.setattr(stripe.Plan, \"retrieve\", mock_price_get)\n\n        plan = Plan.sync_from_stripe_data(fake_plan_data)\n\n        assert plan.human_readable_price == expected_str\n"
  },
  {
    "path": "tests/test_price.py",
    "content": "\"\"\"\ndj-stripe Price model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nimport stripe\nfrom django.test import TestCase\n\nfrom djstripe.enums import PriceType, PriceUsageType\nfrom djstripe.models import Price, Product\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_PRICE,\n    FAKE_PRICE_METERED,\n    FAKE_PRICE_ONETIME,\n    FAKE_PRICE_TIER,\n    FAKE_PRODUCT,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass PriceCreateTest(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        with patch(\n            \"stripe.Product.retrieve\",\n            return_value=deepcopy(FAKE_PRODUCT),\n            autospec=True,\n        ):\n            self.stripe_product = Product(id=FAKE_PRODUCT[\"id\"]).api_retrieve()\n\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\"stripe.Price.create\", return_value=deepcopy(FAKE_PRICE), autospec=True)\n    def test_create_from_product_id(self, price_create_mock, product_retrieve_mock):\n        fake_price = deepcopy(FAKE_PRICE)\n        fake_price[\"unit_amount\"] /= 100\n        assert isinstance(fake_price[\"product\"], str)\n\n        price = Price.create(**fake_price)\n\n        expected_create_kwargs = deepcopy(FAKE_PRICE)\n        expected_create_kwargs[\"api_key\"] = djstripe_settings.STRIPE_SECRET_KEY\n\n        price_create_mock.assert_called_once_with(\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **expected_create_kwargs,\n        )\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\"stripe.Price.create\", return_value=deepcopy(FAKE_PRICE), autospec=True)\n    def test_create_from_stripe_product(self, price_create_mock, product_retrieve_mock):\n        fake_price = deepcopy(FAKE_PRICE)\n        fake_price[\"product\"] = self.stripe_product\n        fake_price[\"unit_amount\"] /= 100\n        assert isinstance(fake_price[\"product\"], dict)\n\n        price = Price.create(**fake_price)\n\n        expected_create_kwargs = deepcopy(FAKE_PRICE)\n        expected_create_kwargs[\"product\"] = self.stripe_product\n\n        price_create_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **expected_create_kwargs,\n        )\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\"stripe.Price.create\", return_value=deepcopy(FAKE_PRICE), autospec=True)\n    def test_create_from_djstripe_product(\n        self, price_create_mock, product_retrieve_mock\n    ):\n        fake_price = deepcopy(FAKE_PRICE)\n        fake_price[\"product\"] = Product.sync_from_stripe_data(self.stripe_product)\n        fake_price[\"unit_amount\"] /= 100\n        assert isinstance(fake_price[\"product\"], Product)\n\n        price = Price.create(**fake_price)\n\n        price_create_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **FAKE_PRICE,\n        )\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\"stripe.Price.create\", return_value=deepcopy(FAKE_PRICE), autospec=True)\n    def test_create_with_metadata(self, price_create_mock, product_retrieve_mock):\n        metadata = {\"other_data\": \"more_data\"}\n        fake_price = deepcopy(FAKE_PRICE)\n        fake_price[\"unit_amount\"] /= 100\n        fake_price[\"metadata\"] = metadata\n        assert isinstance(fake_price[\"product\"], str)\n\n        price = Price.create(**fake_price)\n\n        expected_create_kwargs = deepcopy(FAKE_PRICE)\n        expected_create_kwargs[\"metadata\"] = metadata\n\n        price_create_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            **expected_create_kwargs,\n        )\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n\nclass PriceTest(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        self.price_data = deepcopy(FAKE_PRICE)\n        with patch(\n            \"stripe.Product.retrieve\",\n            return_value=deepcopy(FAKE_PRODUCT),\n            autospec=True,\n        ):\n            self.price = Price.sync_from_stripe_data(self.price_data)\n\n    @patch(\"stripe.Price.retrieve\", return_value=FAKE_PRICE, autospec=True)\n    def test_stripe_price(self, price_retrieve_mock):\n        stripe_price = self.price.api_retrieve()\n        price_retrieve_mock.assert_called_once_with(\n            id=self.price_data[\"id\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            expand=[\"product\", \"tiers\"],\n            stripe_account=self.price.djstripe_owner_account.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n        price = Price.sync_from_stripe_data(stripe_price)\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n        assert price.human_readable_price == \"$20.00 USD/month\"\n\n    @patch(\"stripe.Price.retrieve\", autospec=True)\n    def test_stripe_tier_price(self, price_retrieve_mock):\n        price_data = deepcopy(FAKE_PRICE_TIER)\n        price = Price.sync_from_stripe_data(price_data)\n        assert price.id == price_data[\"id\"]\n        assert price.unit_amount is None\n        assert price.tiers is not None\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n    @patch(\"stripe.Price.retrieve\", autospec=True)\n    def test_stripe_metered_price(self, price_retrieve_mock):\n        price_data = deepcopy(FAKE_PRICE_METERED)\n        price = Price.sync_from_stripe_data(price_data)\n        assert price.id == price_data[\"id\"]\n        assert price.recurring[\"usage_type\"] == PriceUsageType.metered\n        assert price.unit_amount is not None\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n    @patch(\"stripe.Price.retrieve\", autospec=True)\n    def test_stripe_onetime_price(self, price_retrieve_mock):\n        price_data = deepcopy(FAKE_PRICE_ONETIME)\n        price = Price.sync_from_stripe_data(price_data)\n        assert price.id == price_data[\"id\"]\n        assert price.unit_amount is not None\n        assert not price.recurring\n        assert price.type == PriceType.one_time\n\n        self.assert_fks(\n            price,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n\nclass TestStrPrice:\n    @pytest.mark.parametrize(\n        \"fake_price_data\",\n        [\n            deepcopy(FAKE_PRICE),\n            deepcopy(FAKE_PRICE_ONETIME),\n            deepcopy(FAKE_PRICE_TIER),\n            deepcopy(FAKE_PRICE_METERED),\n        ],\n    )\n    def test___str__(self, fake_price_data, monkeypatch):\n        def mock_product_get(*args, **kwargs):\n            return deepcopy(FAKE_PRODUCT)\n\n        def mock_price_get(*args, **kwargs):\n            return fake_price_data\n\n        # monkeypatch stripe.Product.retrieve and stripe.Price.retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n        monkeypatch.setattr(stripe.Price, \"retrieve\", mock_price_get)\n\n        if not fake_price_data[\"recurring\"]:\n            price = Price.sync_from_stripe_data(fake_price_data)\n            assert (f\"{price.human_readable_price} for {FAKE_PRODUCT['name']}\") == str(\n                price\n            )\n\n        else:\n            price = Price.sync_from_stripe_data(fake_price_data)\n            assert (\n                str(price) == f\"{price.human_readable_price} for {FAKE_PRODUCT['name']}\"\n            )\n\n\nclass TestHumanReadablePrice:\n    #\n    # Helpers\n    #\n    def get_fake_price_NONE_flat_amount():\n        FAKE_PRICE_TIER_NONE_FLAT_AMOUNT = deepcopy(FAKE_PRICE_TIER)\n        FAKE_PRICE_TIER_NONE_FLAT_AMOUNT[\"tiers\"][0][\"flat_amount\"] = None\n        FAKE_PRICE_TIER_NONE_FLAT_AMOUNT[\"tiers\"][0][\"flat_amount_decimal\"] = None\n        return FAKE_PRICE_TIER_NONE_FLAT_AMOUNT\n\n    def get_fake_price_0_flat_amount():\n        FAKE_PRICE_TIER_0_FLAT_AMOUNT = deepcopy(FAKE_PRICE_TIER)\n        FAKE_PRICE_TIER_0_FLAT_AMOUNT[\"tiers\"][0][\"flat_amount\"] = 0\n        FAKE_PRICE_TIER_0_FLAT_AMOUNT[\"tiers\"][0][\"flat_amount_decimal\"] = 0\n        return FAKE_PRICE_TIER_0_FLAT_AMOUNT\n\n    def get_fake_price_0_amount():\n        FAKE_PRICE_TIER_0_AMOUNT = deepcopy(FAKE_PRICE)\n        FAKE_PRICE_TIER_0_AMOUNT[\"unit_amount\"] = 0\n        FAKE_PRICE_TIER_0_AMOUNT[\"unit_amount_decimal\"] = 0\n        return FAKE_PRICE_TIER_0_AMOUNT\n\n    @pytest.mark.parametrize(\n        \"fake_price_data, expected_str\",\n        [\n            (deepcopy(FAKE_PRICE), \"$20.00 USD/month\"),\n            (get_fake_price_0_amount(), \"$0.00 USD/month\"),\n            (deepcopy(FAKE_PRICE_ONETIME), \"$20.00 USD (one time)\"),\n            (\n                deepcopy(FAKE_PRICE_TIER),\n                \"Starts at $10.00 USD per unit + $49.00 USD/month\",\n            ),\n            (\n                get_fake_price_0_flat_amount(),\n                \"Starts at $10.00 USD per unit + $0.00 USD/month\",\n            ),\n            (\n                get_fake_price_NONE_flat_amount(),\n                \"Starts at $10.00 USD per unit/month\",\n            ),\n            (deepcopy(FAKE_PRICE_METERED), \"$2.00 USD/month\"),\n        ],\n    )\n    def test_human_readable(self, fake_price_data, expected_str, monkeypatch):\n        def mock_product_get(*args, **kwargs):\n            return deepcopy(FAKE_PRODUCT)\n\n        def mock_price_get(*args, **kwargs):\n            return fake_price_data\n\n        # monkeypatch stripe.Product.retrieve and stripe.Price.retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n        monkeypatch.setattr(stripe.Price, \"retrieve\", mock_price_get)\n\n        price = Price.sync_from_stripe_data(fake_price_data)\n\n        assert price.human_readable_price == expected_str\n"
  },
  {
    "path": "tests/test_product.py",
    "content": "\"\"\"\ndj-stripe Product model tests\n\"\"\"\n\nfrom copy import deepcopy\n\nimport pytest\nimport stripe\n\nfrom djstripe.models import Product\nfrom djstripe.models.core import Price\n\nfrom . import (\n    FAKE_FILEUPLOAD_ICON,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_PRICE,\n    FAKE_PRICE_METERED,\n    FAKE_PRICE_ONETIME,\n    FAKE_PRICE_TIER,\n    FAKE_PRODUCT,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestProduct:\n    #\n    # Helper Methods for monkeypatching\n    #\n    def mock_file_retrieve(*args, **kwargs):\n        return deepcopy(FAKE_FILEUPLOAD_ICON)\n\n    def mock_account_retrieve(*args, **kwargs):\n        return deepcopy(FAKE_PLATFORM_ACCOUNT)\n\n    def mock_product_get(self, *args, **kwargs):\n        return deepcopy(FAKE_PRODUCT)\n\n    @pytest.mark.parametrize(\"count\", [1, 2, 3])\n    def test___str__(self, count, monkeypatch):\n        def mock_price_get(*args, **kwargs):\n            return random_price_data\n\n        # monkeypatch stripe.Product.retrieve and stripe.Price.retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Product, \"retrieve\", self.mock_product_get)\n        monkeypatch.setattr(stripe.Price, \"retrieve\", mock_price_get)\n\n        product = Product.sync_from_stripe_data(deepcopy(FAKE_PRODUCT))\n\n        PRICE_DATA_OPTIONS = [\n            deepcopy(FAKE_PRICE),\n            deepcopy(FAKE_PRICE_TIER),\n            deepcopy(FAKE_PRICE_METERED),\n            deepcopy(FAKE_PRICE_ONETIME),\n        ]\n        for _ in range(count):\n            random_price_data = PRICE_DATA_OPTIONS.pop()\n            price = Price.sync_from_stripe_data(random_price_data)\n\n        if count > 1:\n            assert f\"{FAKE_PRODUCT['name']} ({count} prices)\" == str(product)\n        else:\n            assert f\"{FAKE_PRODUCT['name']} ({price.human_readable_price})\" == str(\n                product\n            )\n\n    def test_sync_from_stripe_data(self, monkeypatch):\n        # monkeypatch stripe.Product.retrieve call to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Product, \"retrieve\", self.mock_product_get)\n        product = Product.sync_from_stripe_data(deepcopy(FAKE_PRODUCT))\n\n        assert product.id == FAKE_PRODUCT[\"id\"]\n        assert product.name == FAKE_PRODUCT[\"name\"]\n        assert product.type == FAKE_PRODUCT[\"type\"]\n"
  },
  {
    "path": "tests/test_refund.py",
    "content": "\"\"\"\ndj-stripe Charge Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nfrom django.contrib.auth import get_user_model\nfrom django.test.testcases import TestCase\n\nfrom djstripe import enums\nfrom djstripe.models import Refund\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION_REFUND,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_PRODUCT,\n    FAKE_REFUND,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n    AssertStripeFksMixin,\n)\n\n\nclass RefundTest(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        # create a Stripe Platform Account\n        self.account = FAKE_PLATFORM_ACCOUNT.create()\n\n        user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(user)\n\n        self.default_expected_blank_fks = {\n            \"djstripe.Account.branding_logo\",\n            \"djstripe.Account.branding_icon\",\n            \"djstripe.Charge.application_fee\",\n            \"djstripe.Charge.dispute\",\n            \"djstripe.Charge.latest_upcominginvoice (related name)\",\n            \"djstripe.Charge.on_behalf_of\",\n            \"djstripe.Charge.refund\",\n            \"djstripe.Charge.source_transfer\",\n            \"djstripe.Charge.transfer\",\n            \"djstripe.Customer.coupon\",\n            \"djstripe.Customer.default_payment_method\",\n            \"djstripe.Invoice.default_payment_method\",\n            \"djstripe.Invoice.default_source\",\n            \"djstripe.PaymentIntent.on_behalf_of\",\n            \"djstripe.PaymentIntent.payment_method\",\n            \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            \"djstripe.Product.default_price\",\n            \"djstripe.Subscription.default_payment_method\",\n            \"djstripe.Subscription.default_source\",\n            \"djstripe.Subscription.pending_setup_intent\",\n            \"djstripe.Subscription.schedule\",\n            \"djstripe.Refund.failure_balance_transaction\",\n        }\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    def test_sync_from_stripe_data(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_refund = deepcopy(FAKE_REFUND)\n\n        balance_transaction_retrieve_mock.return_value = deepcopy(\n            FAKE_BALANCE_TRANSACTION_REFUND\n        )\n\n        refund = Refund.sync_from_stripe_data(fake_refund)\n\n        self.assert_fks(refund, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    def test___str__(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        fake_refund = deepcopy(FAKE_REFUND)\n        fake_refund[\"reason\"] = enums.RefundReason.requested_by_customer\n\n        balance_transaction_retrieve_mock.return_value = deepcopy(\n            FAKE_BALANCE_TRANSACTION_REFUND\n        )\n\n        refund = Refund.sync_from_stripe_data(fake_refund)\n\n        self.assertEqual(str(refund), \"$20.00 USD (Succeeded)\")\n\n        self.assert_fks(refund, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    def test_reason_enum(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        balance_transaction_retrieve_mock.return_value = deepcopy(\n            FAKE_BALANCE_TRANSACTION_REFUND\n        )\n\n        fake_refund = deepcopy(FAKE_REFUND)\n\n        for reason in (\n            \"duplicate\",\n            \"fraudulent\",\n            \"requested_by_customer\",\n            \"expired_uncaptured_charge\",\n        ):\n            fake_refund[\"reason\"] = reason\n\n            refund = Refund.sync_from_stripe_data(fake_refund)\n\n            self.assertEqual(refund.reason, reason)\n\n            # trigger model field validation (including enum value choices check)\n            refund.full_clean()\n\n            self.assert_fks(refund, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\n        \"djstripe.models.Account.get_default_account\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    def test_status_enum(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        default_account_mock,\n    ):\n        default_account_mock.return_value = self.account\n\n        balance_transaction_retrieve_mock.return_value = deepcopy(\n            FAKE_BALANCE_TRANSACTION_REFUND\n        )\n\n        fake_refund = deepcopy(FAKE_REFUND)\n\n        for status in (\n            \"pending\",\n            \"succeeded\",\n            \"failed\",\n            \"canceled\",\n        ):\n            fake_refund[\"status\"] = status\n\n            refund = Refund.sync_from_stripe_data(fake_refund)\n\n            self.assertEqual(refund.status, status)\n\n            # trigger model field validation (including enum value choices check)\n            refund.full_clean()\n\n            self.assert_fks(refund, expected_blank_fks=self.default_expected_blank_fks)\n"
  },
  {
    "path": "tests/test_session.py",
    "content": "\"\"\"\ndj-stripe Session Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nimport stripe\nfrom django.test import TestCase\n\nfrom djstripe.models import Session\nfrom djstripe.settings import djstripe_settings\nfrom tests import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PAYMENT_METHOD_I,\n    FAKE_PRODUCT,\n    FAKE_SESSION_I,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass SessionTest(AssertStripeFksMixin, TestCase):\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data(\n        self,\n        payment_intent_retrieve_mock,\n        customer_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        session = Session.sync_from_stripe_data(deepcopy(FAKE_SESSION_I))\n\n        self.assert_fks(\n            session,\n            expected_blank_fks={\n                \"djstripe.Charge.latest_upcominginvoice (related name)\",\n                \"djstripe.Charge.application_fee\",\n                \"djstripe.Charge.dispute\",\n                \"djstripe.Charge.on_behalf_of\",\n                \"djstripe.Charge.source_transfer\",\n                \"djstripe.Charge.transfer\",\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.subscriber\",\n                \"djstripe.Invoice.default_payment_method\",\n                \"djstripe.Invoice.default_source\",\n                \"djstripe.PaymentIntent.on_behalf_of\",\n                \"djstripe.PaymentIntent.payment_method\",\n                \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n                \"djstripe.Product.default_price\",\n                \"djstripe.Subscription.default_payment_method\",\n                \"djstripe.Subscription.default_source\",\n                \"djstripe.Subscription.pending_setup_intent\",\n                \"djstripe.Subscription.schedule\",\n                \"djstripe.Session.subscription\",\n            },\n        )\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    def test___str__(\n        self,\n        payment_intent_retrieve_mock,\n        customer_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        session = Session.sync_from_stripe_data(deepcopy(FAKE_SESSION_I))\n\n        self.assertEqual(f\"<id={FAKE_SESSION_I['id']}>\", str(session))\n\n\nclass TestSession:\n    key = djstripe_settings.SUBSCRIBER_CUSTOMER_KEY\n\n    @pytest.mark.parametrize(\n        \"metadata\",\n        [\n            {},\n            {\"key1\": \"val1\", key: \"random\"},\n        ],\n    )\n    def test__attach_objects_post_save_hook(\n        self, monkeypatch, fake_user, fake_customer, metadata\n    ):\n        \"\"\"\n        Test for Checkout Session _attach_objects_post_save_hook\n        \"\"\"\n        user = fake_user\n        customer = fake_customer\n\n        # because create_for_user method adds subscriber\n        customer.subcriber = None\n        customer.save()\n\n        # update metadata\n        if metadata.get(self.key, \"\"):\n            metadata[self.key] = user.id\n\n        fake_stripe_session = deepcopy(FAKE_SESSION_I)\n        fake_stripe_session[\"metadata\"] = metadata\n\n        def mock_checkout_session_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Session.retrieve\"\"\"\n            return deepcopy(fake_stripe_session)\n\n        def mock_customer_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Customer.retrieve\"\"\"\n            return deepcopy(FAKE_CUSTOMER)\n\n        def mock_payment_intent_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.PaymentIntent.retrieve\"\"\"\n            return deepcopy(FAKE_PAYMENT_INTENT_I)\n\n        def mock_invoice_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Invoice.retrieve\"\"\"\n            return deepcopy(FAKE_INVOICE)\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return deepcopy(FAKE_INVOICEITEM)\n\n        def mock_payment_method_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.PaymentMethod.retrieve\"\"\"\n            return deepcopy(FAKE_PAYMENT_METHOD_I)\n\n        def mock_subscription_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Subscription.retrieve\"\"\"\n            return deepcopy(FAKE_SUBSCRIPTION)\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return deepcopy(FAKE_SUBSCRIPTION_ITEM)\n\n        def mock_balance_transaction_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.BalanceTransaction.retrieve\"\"\"\n            return deepcopy(FAKE_BALANCE_TRANSACTION)\n\n        def mock_product_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Product.retrieve\"\"\"\n            return deepcopy(FAKE_PRODUCT)\n\n        def mock_charge_get(*args, **kwargs):\n            \"\"\"Monkeypatched stripe.Charge.retrieve\"\"\"\n            return deepcopy(FAKE_CHARGE)\n\n        # monkeypatch stripe.checkout.Session.retrieve, stripe.Customer.retrieve, stripe.PaymentIntent.retrieve\n        monkeypatch.setattr(\n            stripe.checkout.Session, \"retrieve\", mock_checkout_session_get\n        )\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n        monkeypatch.setattr(stripe.Customer, \"modify\", mock_customer_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n\n        # because of Reverse o2o field sync due to PaymentIntent.sync_from_stripe_data..\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n\n        # Invoke the sync to invoke _attach_objects_post_save_hook()\n        session = Session.sync_from_stripe_data(fake_stripe_session)\n\n        # refresh self.customer from db\n        customer.refresh_from_db()\n\n        assert session\n        assert session.customer.id == customer.id\n        assert customer.subscriber == user\n        if metadata.get(self.key, \"\"):\n            assert customer.metadata == {self.key: metadata.get(self.key)}\n        else:\n            assert customer.metadata == {}\n"
  },
  {
    "path": "tests/test_settings.py",
    "content": "\"\"\"\ndj-stripe Settings Tests.\n\"\"\"\nfrom unittest.mock import patch\n\nimport stripe\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.db.models.base import ModelBase\nfrom django.test import TestCase\nfrom django.test.utils import override_settings\n\nfrom djstripe import settings\n\n\nclass TestSubscriberModelRetrievalMethod(TestCase):\n    def test_with_user(self):\n        user_model = settings.djstripe_settings.get_subscriber_model()\n        self.assertTrue(isinstance(user_model, ModelBase))\n\n    @override_settings(\n        DJSTRIPE_SUBSCRIBER_MODEL=\"testapp.Organization\",\n        DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK=(lambda request: request.org),\n    )\n    def test_with_org(self):\n        org_model = settings.djstripe_settings.get_subscriber_model()\n        self.assertTrue(isinstance(org_model, ModelBase))\n\n    @override_settings(\n        DJSTRIPE_SUBSCRIBER_MODEL=\"testapp.StaticEmailOrganization\",\n        DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK=(lambda request: request.org),\n    )\n    def test_with_org_static(self):\n        org_model = settings.djstripe_settings.get_subscriber_model()\n        self.assertTrue(isinstance(org_model, ModelBase))\n\n    @override_settings(\n        DJSTRIPE_SUBSCRIBER_MODEL=\"testappStaticEmailOrganization\",\n        DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK=(lambda request: request.org),\n    )\n    def test_bad_model_name(self):\n        self.assertRaisesMessage(\n            ImproperlyConfigured,\n            \"DJSTRIPE_SUBSCRIBER_MODEL must be of the form 'app_label.model_name'.\",\n            settings.djstripe_settings.get_subscriber_model,\n        )\n\n    @override_settings(\n        DJSTRIPE_SUBSCRIBER_MODEL=\"testapp.UnknownModel\",\n        DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK=(lambda request: request.org),\n    )\n    def test_unknown_model(self):\n        self.assertRaisesMessage(\n            ImproperlyConfigured,\n            \"DJSTRIPE_SUBSCRIBER_MODEL refers to model 'testapp.UnknownModel' \"\n            \"that has not been installed.\",\n            settings.djstripe_settings.get_subscriber_model,\n        )\n\n    @override_settings(\n        DJSTRIPE_SUBSCRIBER_MODEL=\"testapp.NoEmailOrganization\",\n        DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK=(lambda request: request.org),\n    )\n    def test_no_email_model(self):\n        self.assertRaisesMessage(\n            ImproperlyConfigured,\n            \"DJSTRIPE_SUBSCRIBER_MODEL must have an email attribute.\",\n            settings.djstripe_settings.get_subscriber_model,\n        )\n\n    @override_settings(DJSTRIPE_SUBSCRIBER_MODEL=\"testapp.Organization\")\n    def test_no_callback(self):\n        self.assertRaisesMessage(\n            ImproperlyConfigured,\n            \"DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK must be implemented \"\n            \"if a DJSTRIPE_SUBSCRIBER_MODEL is defined.\",\n            settings.djstripe_settings.get_subscriber_model,\n        )\n\n    @override_settings(\n        DJSTRIPE_SUBSCRIBER_MODEL=\"testapp.Organization\",\n        DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK=5,\n    )\n    def test_bad_callback(self):\n        self.assertRaisesMessage(\n            ImproperlyConfigured,\n            \"DJSTRIPE_SUBSCRIBER_MODEL_REQUEST_CALLBACK must be callable.\",\n            settings.djstripe_settings.get_subscriber_model,\n        )\n\n    @override_settings(DJSTRIPE_TEST_CALLBACK=(lambda: \"ok\"))\n    def test_get_callback_function_with_valid_func_callable(self):\n        func = settings.djstripe_settings.get_callback_function(\n            \"DJSTRIPE_TEST_CALLBACK\"\n        )\n        self.assertEqual(\"ok\", func())\n\n    @override_settings(DJSTRIPE_TEST_CALLBACK=\"foo.valid_callback\")\n    @patch.object(settings, \"import_string\", return_value=(lambda: \"ok\"))\n    def test_get_callback_function_with_valid_string_callable(self, import_string_mock):\n        func = settings.djstripe_settings.get_callback_function(\n            \"DJSTRIPE_TEST_CALLBACK\"\n        )\n        self.assertEqual(\"ok\", func())\n        import_string_mock.assert_called_with(\"foo.valid_callback\")\n\n    @override_settings(DJSTRIPE_TEST_CALLBACK=\"foo.non_existant_callback\")\n    def test_get_callback_function_import_error(self):\n        with self.assertRaises(ImportError):\n            settings.djstripe_settings.get_callback_function(\"DJSTRIPE_TEST_CALLBACK\")\n\n    @override_settings(DJSTRIPE_TEST_CALLBACK=\"foo.invalid_callback\")\n    @patch.object(settings, \"import_string\", return_value=\"not_callable\")\n    def test_get_callback_function_with_non_callable_string(self, import_string_mock):\n        with self.assertRaises(ImproperlyConfigured):\n            settings.djstripe_settings.get_callback_function(\"DJSTRIPE_TEST_CALLBACK\")\n        import_string_mock.assert_called_with(\"foo.invalid_callback\")\n\n    @override_settings(DJSTRIPE_TEST_CALLBACK=\"foo.non_existant_callback\")\n    def test_get_callback_function_(self):\n        with self.assertRaises(ImportError):\n            settings.djstripe_settings.get_callback_function(\"DJSTRIPE_TEST_CALLBACK\")\n\n\n@override_settings(STRIPE_API_VERSION=\"2016-03-07\")\nclass TestStripeApiVersion(TestCase):\n    def test_global_stripe_api_version(self):\n        \"\"\"Test that stripe.api_version is untouched.\n\n        See https://github.com/dj-stripe/dj-stripe/issues/1854\n        \"\"\"\n        assert stripe.api_version is None\n\n\n@override_settings(STRIPE_API_VERSION=None)\nclass TestGetStripeApiVersion(TestCase):\n    def test_with_default(self):\n        self.assertEqual(\n            settings.djstripe_settings.DEFAULT_STRIPE_API_VERSION,\n            settings.djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @override_settings(STRIPE_API_VERSION=\"2016-03-07\")\n    def test_with_override(self):\n        self.assertEqual(\n            \"2016-03-07\",\n            settings.djstripe_settings.STRIPE_API_VERSION,\n        )\n\n\nclass TestObjectPatching(TestCase):\n    @patch.object(\n        settings.djstripe_settings,\n        \"DJSTRIPE_WEBHOOK_URL\",\n        return_value=r\"^webhook/sample/$\",\n    )\n    def test_object_patching(self, mock):\n        webhook_url = settings.djstripe_settings.DJSTRIPE_WEBHOOK_URL\n        self.assertTrue(webhook_url, r\"^webhook/sample/$\")\n"
  },
  {
    "path": "tests/test_setup_intent.py",
    "content": "\"\"\"\ndj-stripe SetupIntent Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nimport stripe\nfrom django.test import TestCase\n\nfrom djstripe.enums import SetupIntentStatus\nfrom djstripe.models import Account, Customer, PaymentMethod, SetupIntent\nfrom tests import (\n    FAKE_CUSTOMER,\n    FAKE_PAYMENT_METHOD_I,\n    FAKE_SETUP_INTENT_DESTINATION_CHARGE,\n    FAKE_SETUP_INTENT_I,\n    FAKE_SETUP_INTENT_II,\n    FAKE_STANDARD_ACCOUNT,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestStrSetupIntent:\n    #\n    # Helpers\n    #\n    def get_fake_setup_intent_destination_charge_no_customer():\n        FAKE_SETUP_INTENT_DESTINATION_CHARGE_NO_CUSTOMER = deepcopy(\n            FAKE_SETUP_INTENT_DESTINATION_CHARGE\n        )\n        FAKE_SETUP_INTENT_DESTINATION_CHARGE_NO_CUSTOMER[\"customer\"] = None\n        return FAKE_SETUP_INTENT_DESTINATION_CHARGE_NO_CUSTOMER\n\n    @pytest.mark.parametrize(\n        \"fake_intent_data, has_account, has_customer\",\n        [\n            (FAKE_SETUP_INTENT_I, False, False),\n            (FAKE_SETUP_INTENT_DESTINATION_CHARGE, True, True),\n            (get_fake_setup_intent_destination_charge_no_customer(), True, False),\n            (FAKE_SETUP_INTENT_II, False, True),\n        ],\n    )\n    def test___str__(self, fake_intent_data, has_account, has_customer, monkeypatch):\n        def mock_customer_get(*args, **kwargs):\n            return deepcopy(FAKE_CUSTOMER)\n\n        def mock_account_get(*args, **kwargs):\n            return deepcopy(FAKE_STANDARD_ACCOUNT)\n\n        def mock_payment_method_get(*args, **kwargs):\n            return deepcopy(FAKE_PAYMENT_METHOD_I)\n\n        # monkeypatch stripe.Account.retrieve, stripe.Customer.retrieve, and  stripe.PaymentMethod.retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Account, \"retrieve\", mock_account_get)\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n\n        si = SetupIntent.sync_from_stripe_data(fake_intent_data)\n        pm = PaymentMethod.objects.filter(id=fake_intent_data[\"payment_method\"]).first()\n        account = Account.objects.filter(id=fake_intent_data[\"on_behalf_of\"]).first()\n        customer = Customer.objects.filter(id=fake_intent_data[\"customer\"]).first()\n\n        if has_account and has_customer:\n            assert (\n                f\"{pm} ({SetupIntentStatus.humanize(fake_intent_data['status'])}) \"\n                f\"for {account} \"\n                f\"by {customer}\"\n            ) == str(si)\n\n        elif has_account and not has_customer:\n            assert (\n                f\"{pm} for {account}. {SetupIntentStatus.humanize(fake_intent_data['status'])}\"\n            ) == str(si)\n\n        elif has_customer and not has_account:\n            assert (\n                f\"{pm} by {customer}. {SetupIntentStatus.humanize(fake_intent_data['status'])}\"\n            ) == str(si)\n\n        elif not has_customer and not has_account:\n            f\"{pm} ({SetupIntentStatus.humanize(fake_intent_data['status'])})\" == str(\n                si\n            )\n\n\nclass SetupIntentTest(AssertStripeFksMixin, TestCase):\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_from_stripe_data(self, customer_retrieve_mock):\n        fake_payment_intent = deepcopy(FAKE_SETUP_INTENT_I)\n\n        setup_intent = SetupIntent.sync_from_stripe_data(fake_payment_intent)\n\n        self.assertEqual(setup_intent.payment_method_types, [\"card\"])\n\n        self.assert_fks(\n            setup_intent,\n            expected_blank_fks={\n                \"djstripe.SetupIntent.customer\",\n                \"djstripe.SetupIntent.on_behalf_of\",\n                \"djstripe.SetupIntent.payment_method\",\n            },\n        )\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_status_enum(self, customer_retrieve_mock):\n        fake_setup_intent = deepcopy(FAKE_SETUP_INTENT_I)\n\n        for status in (\n            \"requires_payment_method\",\n            \"requires_confirmation\",\n            \"requires_action\",\n            \"processing\",\n            \"canceled\",\n            \"succeeded\",\n        ):\n            fake_setup_intent[\"status\"] = status\n\n            setup_intent = SetupIntent.sync_from_stripe_data(fake_setup_intent)\n\n            # trigger model field validation (including enum value choices check)\n            setup_intent.full_clean()\n\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_canceled_intent(self, customer_retrieve_mock):\n        fake_setup_intent = deepcopy(FAKE_SETUP_INTENT_I)\n\n        fake_setup_intent[\"status\"] = \"canceled\"\n        fake_setup_intent[\"canceled_at\"] = 1567524169\n\n        for reason in (None, \"abandoned\", \"requested_by_customer\", \"duplicate\"):\n            fake_setup_intent[\"cancellation_reason\"] = reason\n            setup_intent = SetupIntent.sync_from_stripe_data(fake_setup_intent)\n\n            if reason is None:\n                # enums nulls are coerced to \"\" by StripeModel._stripe_object_to_record\n                self.assertEqual(setup_intent.cancellation_reason, \"\")\n            else:\n                self.assertEqual(setup_intent.cancellation_reason, reason)\n\n            # trigger model field validation (including enum value choices check)\n            setup_intent.full_clean()\n"
  },
  {
    "path": "tests/test_shipping_rate.py",
    "content": "\"\"\"\ndj-stripe ShippingRate Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nfrom django.test import TestCase\n\nfrom djstripe.models import ShippingRate\nfrom tests import (\n    FAKE_SHIPPING_RATE,\n    FAKE_SHIPPING_RATE_WITH_TAX_CODE,\n    FAKE_TAX_CODE,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass ShippingRateTest(AssertStripeFksMixin, TestCase):\n    def test_sync_from_stripe_data(self):\n        shipping_rate = ShippingRate.sync_from_stripe_data(deepcopy(FAKE_SHIPPING_RATE))\n\n        self.assertEqual(\n            FAKE_SHIPPING_RATE[\"id\"],\n            shipping_rate.id,\n        )\n\n        self.assertEqual(\n            FAKE_SHIPPING_RATE[\"tax_code\"],\n            None,\n        )\n\n        self.assert_fks(\n            shipping_rate, expected_blank_fks={\"djstripe.ShippingRate.tax_code\"}\n        )\n\n    @patch(\n        \"stripe.TaxCode.retrieve\", autospec=True, return_value=deepcopy(FAKE_TAX_CODE)\n    )\n    def test_sync_from_stripe_data_with_tax_code(self, tax_code_retrieve_mock):\n        shipping_rate = ShippingRate.sync_from_stripe_data(\n            deepcopy(FAKE_SHIPPING_RATE_WITH_TAX_CODE)\n        )\n\n        self.assertEqual(\n            FAKE_SHIPPING_RATE[\"id\"],\n            shipping_rate.id,\n        )\n\n        self.assertEqual(\n            FAKE_SHIPPING_RATE[\"tax_code\"],\n            None,\n        )\n\n        self.assert_fks(shipping_rate, expected_blank_fks={})\n\n    def test___str__(self):\n        shipping_rate = ShippingRate.sync_from_stripe_data(deepcopy(FAKE_SHIPPING_RATE))\n\n        self.assertEqual(\n            \"Test Shipping Code with no Tax Code - $1.25 USD (Active)\",\n            str(shipping_rate),\n        )\n"
  },
  {
    "path": "tests/test_source.py",
    "content": "\"\"\"\ndj-stripe Source Model Tests.\n\"\"\"\nimport sys\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase\n\nfrom djstripe.models import Source\n\nfrom . import (\n    FAKE_CUSTOMER_III,\n    FAKE_SOURCE,\n    FAKE_SOURCE_II,\n    AssertStripeFksMixin,\n    SourceDict,\n)\n\n\nclass SourceTest(AssertStripeFksMixin, TestCase):\n    def setUp(self):\n        user = get_user_model().objects.create_user(\n            username=\"testuser\", email=\"djstripe@example.com\"\n        )\n\n        # create a source object so that FAKE_CUSTOMER_III with a default source\n        # can be created correctly.\n        fake_source_data = deepcopy(FAKE_SOURCE)\n        fake_source_data[\"customer\"] = None\n        self.source = Source.sync_from_stripe_data(fake_source_data)\n\n        self.customer = FAKE_CUSTOMER_III.create_for_user(user)\n        self.customer.sources.all().delete()\n        self.customer.legacy_cards.all().delete()\n\n    def test_attach_objects_hook_without_customer(self):\n        source = Source.sync_from_stripe_data(deepcopy(FAKE_SOURCE_II))\n        self.assertEqual(source.customer, None)\n\n        self.assert_fks(\n            source,\n            expected_blank_fks={\n                \"djstripe.Source.customer\",\n                \"djstripe.Customer.default_payment_method\",\n            },\n        )\n\n    def test_sync_from_stripe_data(self):\n        source = Source.sync_from_stripe_data(deepcopy(FAKE_SOURCE))\n\n        self.assertEqual(self.customer, source.customer)\n\n        self.assert_fks(\n            source,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n            },\n        )\n\n    def test___str__(self):\n        fake_source = deepcopy(FAKE_SOURCE)\n        source = Source.sync_from_stripe_data(fake_source)\n\n        self.assertEqual(\n            f\"{fake_source['type']} {fake_source['id']}\",\n            str(source),\n        )\n\n        self.assert_fks(\n            source,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n            },\n        )\n\n    @patch(\"stripe.Source.retrieve\", return_value=deepcopy(FAKE_SOURCE), autospec=True)\n    def test_detach(self, source_retrieve_mock):\n        original_detach = SourceDict.detach\n\n        def mocked_detach(self):\n            return original_detach(self)\n\n        Source.sync_from_stripe_data(deepcopy(FAKE_SOURCE))\n\n        self.assertEqual(0, self.customer.legacy_cards.count())\n        self.assertEqual(1, self.customer.sources.count())\n\n        source = self.customer.sources.first()\n\n        with patch(\n            \"tests.SourceDict.detach\", side_effect=mocked_detach, autospec=True\n        ) as mock_detach:\n            source.detach()\n\n        self.assertEqual(0, self.customer.sources.count())\n        # need to refresh_from_db since default_source was cleared with a query\n        self.customer.refresh_from_db()\n        self.assertIsNone(self.customer.default_source)\n\n        # need to refresh_from_db due to the implementation of Source.detach() -\n        # see TODO in method\n        source.refresh_from_db()\n        self.assertIsNone(source.customer)\n        self.assertEqual(source.status, \"consumed\")\n\n        if sys.version_info >= (3, 6):\n            # this mock isn't working on py34, py35, but it's not strictly necessary\n            # for the test\n            mock_detach.assert_called()\n\n        self.assert_fks(\n            source,\n            expected_blank_fks={\n                \"djstripe.Source.customer\",\n                \"djstripe.Customer.default_payment_method\",\n            },\n        )\n"
  },
  {
    "path": "tests/test_stripe_model.py",
    "content": "\"\"\"\ndj-stripe StripeModel Model Tests.\n\"\"\"\nfrom unittest.mock import MagicMock, patch\n\nimport pytest\nfrom django.test import TestCase\n\nfrom djstripe.models import Account, Customer, StripeModel\nfrom djstripe.settings import djstripe_settings\n\npytestmark = pytest.mark.django_db\n\n\nclass ExampleStripeModel(StripeModel):\n    # exists to avoid \"Abstract models cannot be instantiated.\" error\n    pass\n\n\nclass TestStripeModelExceptions(TestCase):\n    def test_no_object_value(self):\n        # Instantiate a stripeobject model class\n        class BasicModel(StripeModel):\n            pass\n\n        with self.assertRaises(ValueError):\n            # Errors because there's no object value\n            BasicModel._stripe_object_to_record(\n                {\"id\": \"test_XXXXXXXX\", \"livemode\": False}\n            )\n\n    def test_bad_object_value(self):\n        with self.assertRaises(ValueError):\n            # Errors because the object is not correct\n            Customer._stripe_object_to_record(\n                {\"id\": \"test_XXXXXXXX\", \"livemode\": False, \"object\": \"not_a_customer\"}\n            )\n\n\n@pytest.mark.parametrize(\"stripe_account\", (None, \"acct_fakefakefakefake001\"))\n@pytest.mark.parametrize(\n    \"api_key, expected_api_key\",\n    (\n        (None, djstripe_settings.STRIPE_SECRET_KEY),\n        (\"sk_fakefakefake01\", \"sk_fakefakefake01\"),\n    ),\n)\n@pytest.mark.parametrize(\"extra_kwargs\", ({}, {\"foo\": \"bar\"}))\n@patch.object(target=StripeModel, attribute=\"stripe_class\")\ndef test__api_delete(\n    mock_stripe_class, stripe_account, api_key, expected_api_key, extra_kwargs\n):\n    \"\"\"Test that API delete properly uses the passed in parameters.\"\"\"\n    test_model = ExampleStripeModel()\n    mock_id = \"id_fakefakefakefake01\"\n    test_model.id = mock_id\n\n    # invoke _api_delete()\n    test_model._api_delete(\n        api_key=api_key, stripe_account=stripe_account, **extra_kwargs\n    )\n\n    mock_stripe_class.delete.assert_called_once_with(\n        mock_id,\n        api_key=expected_api_key,\n        stripe_account=stripe_account,\n        stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        **extra_kwargs\n    )\n\n\n@pytest.mark.parametrize(\"stripe_account\", (None, \"acct_fakefakefakefake001\"))\n@pytest.mark.parametrize(\n    \"api_key, expected_api_key\",\n    (\n        (None, djstripe_settings.STRIPE_SECRET_KEY),\n        (\"sk_fakefakefake01\", \"sk_fakefakefake01\"),\n    ),\n)\n@pytest.mark.parametrize(\"expand_fields\", ([], [\"foo\", \"bar\"]))\n@patch.object(target=StripeModel, attribute=\"stripe_class\")\ndef test_api_retrieve(\n    mock_stripe_class, stripe_account, api_key, expected_api_key, expand_fields\n):\n    \"\"\"Test that API delete properly uses the passed in parameters.\"\"\"\n    test_model = ExampleStripeModel()\n    mock_id = \"id_fakefakefakefake01\"\n    test_model.id = mock_id\n    test_model.expand_fields = expand_fields\n    test_model.api_retrieve(api_key=api_key, stripe_account=stripe_account)\n\n    mock_stripe_class.retrieve.assert_called_once_with(\n        id=mock_id,\n        api_key=expected_api_key,\n        stripe_account=stripe_account,\n        stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        expand=expand_fields,\n    )\n\n\n@patch.object(target=StripeModel, attribute=\"stripe_class\")\ndef test_api_retrieve_reverse_foreign_key_lookup(mock_stripe_class):\n    \"\"\"Test that the reverse foreign key lookup finds the correct fields.\"\"\"\n    # Set up some mock fields that shouldn't be used for reverse lookups\n    mock_field_1 = MagicMock()\n    mock_field_1.is_relation = False\n    mock_field_2 = MagicMock()\n    mock_field_2.is_relation = True\n    mock_field_2.one_to_many = False\n    # Set up a mock reverse foreign key field\n    mock_reverse_foreign_key = MagicMock()\n    mock_reverse_foreign_key.is_relation = True\n    mock_reverse_foreign_key.one_to_many = True\n    mock_reverse_foreign_key.related_model = Account\n    mock_reverse_foreign_key.get_accessor_name.return_value = \"foo_account_reverse_attr\"\n\n    # Set up a mock account for the reverse foreign key query to return.\n    mock_account = MagicMock()\n    mock_account_reverse_manager = MagicMock()\n    # Make first return the mock account.\n    mock_account_reverse_manager.first.return_value = mock_account\n\n    test_model = ExampleStripeModel()\n    mock_id = \"id_fakefakefakefake01\"\n    test_model.id = mock_id\n    # Set mock reverse manager on the model.\n    test_model.foo_account_reverse_attr = mock_account_reverse_manager\n\n    # Set the mocked _meta.get_fields to return some mock fields, including the mock\n    # reverse foreign key above.\n    test_model._meta = MagicMock()\n    test_model._meta.get_fields.return_value = (\n        mock_field_1,\n        mock_field_2,\n        mock_reverse_foreign_key,\n    )\n\n    # Call the function with API key set because we mocked _meta\n    mock_api_key = \"sk_fakefakefakefake01\"\n    test_model.api_retrieve(api_key=mock_api_key)\n\n    # Expect the retrieve to be done with the reverse look up of the Account ID.\n    mock_stripe_class.retrieve.assert_called_once_with(\n        id=mock_id,\n        api_key=mock_api_key,\n        stripe_account=mock_account.id,\n        expand=[],\n        stripe_version=djstripe_settings.STRIPE_API_VERSION,\n    )\n    mock_reverse_foreign_key.get_accessor_name.assert_called_once_with()\n    mock_account_reverse_manager.first.assert_called_once_with()\n\n\n@pytest.mark.parametrize(\"api_key\", (None, \"sk_fakefakefake01\"))\n@patch.object(target=Account, attribute=\"get_or_retrieve_for_api_key\")\ndef test__find_owner_account_for_empty_data(\n    mock_get_or_retrieve_for_api_key,\n    api_key,\n):\n    \"\"\"\n    Test that the correct classmethod is invoked with the correct arguments\n    to get the owner account\n    \"\"\"\n\n    fake_data = {}\n\n    if api_key is None:\n        # invoke _find_owner_account without the api_key parameter\n        StripeModel._find_owner_account(fake_data)\n    else:\n        # invoke _find_owner_account with the api_key parameter\n        StripeModel._find_owner_account(fake_data, api_key=api_key)\n\n    if api_key:\n        mock_get_or_retrieve_for_api_key.assert_called_once_with(api_key)\n    else:\n        mock_get_or_retrieve_for_api_key.assert_called_once_with(\n            djstripe_settings.STRIPE_SECRET_KEY\n        )\n\n\n@pytest.mark.parametrize(\n    \"has_stripe_account_attr,stripe_account\",\n    ((False, None), (True, \"\"), (True, \"acct_fakefakefakefake001\")),\n)\n@pytest.mark.parametrize(\"api_key\", (None, \"sk_fakefakefake01\"))\n@patch.object(target=Account, attribute=\"get_or_retrieve_for_api_key\")\n@patch.object(target=Account, attribute=\"_get_or_retrieve\")\ndef test__find_owner_account(\n    mock__get_or_retrieve,\n    mock_get_or_retrieve_for_api_key,\n    api_key,\n    stripe_account,\n    has_stripe_account_attr,\n    monkeypatch,\n):\n    \"\"\"\n    Test that the correct classmethod is invoked with the correct arguments\n    to get the owner account\n    \"\"\"\n\n    # fake_data_class used to invoke _find_owner_account classmethod\n    class fake_data_class:\n        @property\n        def stripe_account(self):\n            return stripe_account\n\n        def get(*args, **kwargs):\n            return \"customer\"\n\n    fake_data = fake_data_class()\n\n    if api_key is None:\n        # invoke _find_owner_account without the api_key parameter\n        StripeModel._find_owner_account(fake_data)\n    else:\n        # invoke _find_owner_account with the api_key parameter\n        StripeModel._find_owner_account(fake_data, api_key=api_key)\n\n    if has_stripe_account_attr and stripe_account:\n        if api_key:\n            mock__get_or_retrieve.assert_called_once_with(\n                id=stripe_account, api_key=api_key\n            )\n        else:\n            mock__get_or_retrieve.assert_called_once_with(\n                id=stripe_account, api_key=djstripe_settings.STRIPE_SECRET_KEY\n            )\n\n    else:\n        if api_key:\n            mock_get_or_retrieve_for_api_key.assert_called_once_with(api_key)\n        else:\n            mock_get_or_retrieve_for_api_key.assert_called_once_with(\n                djstripe_settings.STRIPE_SECRET_KEY\n            )\n\n\n@pytest.mark.parametrize(\n    \"has_account_key,stripe_account\",\n    ((False, None), (True, \"\"), (True, \"acct_fakefakefakefake001\")),\n)\n@pytest.mark.parametrize(\"api_key\", (None, \"sk_fakefakefake01\"))\n@patch.object(target=Account, attribute=\"get_or_retrieve_for_api_key\")\n@patch.object(target=Account, attribute=\"_get_or_retrieve\")\ndef test__find_owner_account_for_webhook_event_trigger(\n    mock__get_or_retrieve,\n    mock_get_or_retrieve_for_api_key,\n    api_key,\n    stripe_account,\n    has_account_key,\n):\n    \"\"\"\n    Test that the correct classmethod is invoked with the correct arguments\n    to get the owner account\n    \"\"\"\n\n    # should fake_data have the account key\n    if has_account_key:\n        # fake_data used to invoke _find_owner_account classmethod\n        fake_data = {\n            \"id\": \"test_XXXXXXXX\",\n            \"livemode\": False,\n            \"object\": \"event\",\n            \"account\": stripe_account,\n        }\n    else:\n        # fake_data used to invoke _find_owner_account classmethod\n        fake_data = {\n            \"id\": \"test_XXXXXXXX\",\n            \"livemode\": False,\n            \"object\": \"event\",\n        }\n\n    if api_key is None:\n        # invoke _find_owner_account without the api_key parameter\n        StripeModel._find_owner_account(fake_data)\n    else:\n        # invoke _find_owner_account with the api_key parameter\n        StripeModel._find_owner_account(fake_data, api_key=api_key)\n\n    if has_account_key and stripe_account:\n        if api_key:\n            mock__get_or_retrieve.assert_called_once_with(\n                id=stripe_account, api_key=api_key\n            )\n        else:\n            mock__get_or_retrieve.assert_called_once_with(\n                id=stripe_account, api_key=djstripe_settings.STRIPE_SECRET_KEY\n            )\n\n    else:\n        if api_key:\n            mock_get_or_retrieve_for_api_key.assert_called_once_with(api_key)\n        else:\n            mock_get_or_retrieve_for_api_key.assert_called_once_with(\n                djstripe_settings.STRIPE_SECRET_KEY\n            )\n"
  },
  {
    "path": "tests/test_subscription.py",
    "content": "\"\"\"\ndj-stripe Subscription Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom decimal import Decimal\nfrom unittest.mock import PropertyMock, patch\n\nimport pytest\nimport stripe\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase\nfrom django.utils import timezone\nfrom stripe.error import InvalidRequestError\n\nfrom djstripe.enums import SubscriptionStatus\nfrom djstripe.models import Plan, Product, Subscription\nfrom djstripe.models.billing import Invoice\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CARD,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CHARGE,\n    FAKE_CHARGE_II,\n    FAKE_CUSTOMER,\n    FAKE_CUSTOMER_II,\n    FAKE_INVOICE,\n    FAKE_INVOICE_II,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PAYMENT_INTENT_II,\n    FAKE_PAYMENT_METHOD_II,\n    FAKE_PLAN,\n    FAKE_PLAN_II,\n    FAKE_PLAN_METERED,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_CANCELED,\n    FAKE_SUBSCRIPTION_II,\n    FAKE_SUBSCRIPTION_III,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_SUBSCRIPTION_METERED,\n    FAKE_SUBSCRIPTION_MULTI_PLAN,\n    FAKE_SUBSCRIPTION_NOT_PERIOD_CURRENT,\n    FAKE_TAX_RATE_EXAMPLE_1_VAT,\n    AssertStripeFksMixin,\n    datetime_to_unix,\n)\n\npytestmark = pytest.mark.django_db\n\n# TODO: test with Prices instead of Plans when creating Subscriptions\n# with Prices is fully supported\n\n\nclass SubscriptionStrTest(TestCase):\n    def setUp(self):\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER_II.create_for_user(self.user)\n\n    @patch(\n        \"stripe.Plan.retrieve\",\n        return_value=deepcopy(FAKE_PLAN),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    def test___str__(\n        self,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION_III)\n        subscription_fake[\"latest_invoice\"] = None\n\n        # sync subscriptions (to update the changes just made)\n        Subscription.sync_from_stripe_data(subscription_fake)\n\n        self.assertEqual(\n            str(Subscription.objects.get(id=subscription_fake[\"id\"])),\n            f'<id={subscription_fake[\"id\"]}>',\n        )\n\n\nclass SubscriptionTest(AssertStripeFksMixin, TestCase):\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    def setUp(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        customer_retrieve_mock,\n    ):\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n        self.default_expected_blank_fks = {\n            \"djstripe.Customer.coupon\",\n            \"djstripe.Customer.default_payment_method\",\n            \"djstripe.Charge.application_fee\",\n            \"djstripe.Charge.dispute\",\n            \"djstripe.Charge.latest_upcominginvoice (related name)\",\n            \"djstripe.Charge.on_behalf_of\",\n            \"djstripe.Charge.source_transfer\",\n            \"djstripe.Charge.transfer\",\n            \"djstripe.PaymentIntent.on_behalf_of\",\n            \"djstripe.PaymentIntent.payment_method\",\n            \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            \"djstripe.Product.default_price\",\n            \"djstripe.Invoice.default_payment_method\",\n            \"djstripe.Invoice.default_source\",\n            \"djstripe.Subscription.default_payment_method\",\n            \"djstripe.Subscription.default_source\",\n            \"djstripe.Subscription.pending_setup_intent\",\n            \"djstripe.Subscription.schedule\",\n        }\n\n        # create latest invoice\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_from_stripe_data(\n        self,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_fake[\"pause_collection\"] = {\n            \"behavior\": \"keep_as_draft\",\n            \"resumes_at\": 1624553615,\n        }\n        subscription_fake[\"cancel_at\"] = 1624553655\n\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        assert subscription\n\n        self.assertEqual(subscription.default_tax_rates.count(), 1)\n        self.assertEqual(\n            subscription.default_tax_rates.first().id, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"id\"]\n        )\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n        self.assertEqual(datetime_to_unix(subscription.cancel_at), 1624553655)\n        self.assertEqual(\n            subscription.pause_collection,\n            subscription_fake[\"pause_collection\"],\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_from_stripe_data_default_source_string(\n        self, customer_retrieve_mock, product_retrieve_mock, plan_retrieve_mock\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_fake[\"default_source\"] = FAKE_CARD[\"id\"]\n\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        assert subscription\n        self.assertEqual(subscription.default_source.id, FAKE_CARD[\"id\"])\n\n        # pop out \"djstripe.Subscription.default_source\" from self.assert_fks\n        expected_blank_fks = deepcopy(self.default_expected_blank_fks)\n        expected_blank_fks.remove(\"djstripe.Subscription.default_source\")\n        self.assert_fks(subscription, expected_blank_fks=expected_blank_fks)\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_II), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    def test_sync_items_with_tax_rates(\n        self,\n        subscription_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION_II)\n        subscription_fake[\"latest_invoice\"] = FAKE_INVOICE[\"id\"]\n        subscription_retrieve_mock.return_value = subscription_fake\n\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        assert subscription\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n        self.assertEqual(subscription.default_tax_rates.count(), 0)\n        first_item = subscription.items.first()\n\n        self.assertEqual(first_item.tax_rates.count(), 1)\n        self.assertEqual(\n            first_item.tax_rates.first().id, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"id\"]\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_is_status_temporarily_current(\n        self, customer_retrieve_mock, product_retrieve_mock, plan_retrieve_mock\n    ):\n        product = Product.sync_from_stripe_data(deepcopy(FAKE_PRODUCT))\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        assert subscription\n        subscription.canceled_at = timezone.now() + timezone.timedelta(days=7)\n        subscription.current_period_end = timezone.now() + timezone.timedelta(days=7)\n        subscription.cancel_at_period_end = True\n        subscription.save()\n\n        self.assertTrue(subscription.is_status_current())\n        self.assertTrue(subscription.is_status_temporarily_current())\n        self.assertTrue(subscription.is_valid())\n        self.assertTrue(subscription in self.customer.active_subscriptions)\n        self.assertTrue(self.customer.is_subscribed_to(product))\n        self.assertTrue(self.customer.has_any_active_subscription())\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_is_status_temporarily_current_false(\n        self, customer_retrieve_mock, product_retrieve_mock, plan_retrieve_mock\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        subscription.current_period_end = timezone.now() + timezone.timedelta(days=7)\n        subscription.save()\n\n        self.assertTrue(subscription.is_status_current())\n        self.assertFalse(subscription.is_status_temporarily_current())\n        self.assertTrue(subscription.is_valid())\n        self.assertTrue(subscription in self.customer.active_subscriptions)\n        self.assertTrue(self.customer.is_subscribed_to(FAKE_PRODUCT[\"id\"]))\n        self.assertTrue(self.customer.has_any_active_subscription())\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_is_status_temporarily_current_false_and_canceled(\n        self, customer_retrieve_mock, product_retrieve_mock, plan_retrieve_mock\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        assert subscription\n        subscription.status = SubscriptionStatus.canceled\n        subscription.current_period_end = timezone.now() + timezone.timedelta(days=7)\n        subscription.save()\n\n        self.assertFalse(subscription.is_status_current())\n        self.assertFalse(subscription.is_status_temporarily_current())\n        self.assertFalse(subscription.is_valid())\n        self.assertFalse(subscription in self.customer.active_subscriptions)\n        self.assertFalse(self.customer.is_subscribed_to(FAKE_PRODUCT[\"id\"]))\n        self.assertFalse(self.customer.has_any_active_subscription())\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.modify\",\n        autospec=True,\n    )\n    @patch(\"stripe.Subscription.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_extend(\n        self,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_modify_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        current_period_end = timezone.now() - timezone.timedelta(days=20)\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_fake[\"current_period_end\"] = int(current_period_end.timestamp())\n        subscription_retrieve_mock.return_value = subscription_fake\n\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        self.assertFalse(subscription in self.customer.active_subscriptions)\n        self.assertEqual(self.customer.active_subscriptions.count(), 0)\n\n        # Extend the Subscription by 30 days\n        delta = timezone.timedelta(days=30)\n        subscription_updated = deepcopy(subscription_fake)\n        subscription_updated[\"trial_end\"] = int(\n            (current_period_end + delta).timestamp()\n        )\n        subscription_modify_mock.return_value = subscription_updated\n\n        extended_subscription = subscription.extend(delta)\n        product = Product.sync_from_stripe_data(deepcopy(FAKE_PRODUCT))\n\n        self.assertNotEqual(None, extended_subscription.trial_end)\n        self.assertTrue(self.customer.is_subscribed_to(product))\n        self.assertTrue(self.customer.has_any_active_subscription())\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_extend_negative_delta(\n        self,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION_NOT_PERIOD_CURRENT)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n\n        with self.assertRaises(ValueError):\n            subscription.extend(timezone.timedelta(days=-30))\n\n        self.assertFalse(self.customer.is_subscribed_to(FAKE_PRODUCT[\"id\"]))\n        self.assertFalse(self.customer.has_any_active_subscription())\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.modify\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_extend_with_trial(\n        self,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_modify_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        trial_end = timezone.now() + timezone.timedelta(days=5)\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        subscription.trial_end = trial_end\n        subscription.save()\n\n        # Extend the Subscription by 30 days\n        delta = timezone.timedelta(days=30)\n        subscription_updated = deepcopy(subscription_fake)\n        subscription_updated[\"trial_end\"] = int((trial_end + delta).timestamp())\n        subscription_modify_mock.return_value = subscription_updated\n\n        extended_subscription = subscription.extend(delta)\n\n        new_trial_end = subscription.trial_end + delta\n        self.assertEqual(\n            new_trial_end.replace(microsecond=0), extended_subscription.trial_end\n        )\n        self.assertTrue(self.customer.is_subscribed_to(FAKE_PRODUCT[\"id\"]))\n        self.assertTrue(self.customer.has_any_active_subscription())\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.modify\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_update(\n        self,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_modify_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n\n        self.assertEqual(1, subscription.quantity)\n\n        # Update the quantity of the Subscription\n        subscription_updated = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_updated[\"quantity\"] = 4\n        subscription_modify_mock.return_value = subscription_updated\n\n        new_subscription = subscription.update(quantity=4)\n\n        self.assertEqual(4, new_subscription.quantity)\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.modify\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_update_deprecation_warnings_raised(\n        self,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_modify_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        assert subscription\n\n        self.assertEqual(1, subscription.quantity)\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.modify\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_update_with_plan_model(\n        self,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_modify_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        new_plan = Plan.sync_from_stripe_data(deepcopy(FAKE_PLAN_II))\n\n        self.assertEqual(FAKE_PLAN[\"id\"], subscription.plan.id)\n\n        # Update the Subscription's plan\n        subscription_updated = deepcopy(FAKE_SUBSCRIPTION)\n        subscription_updated[\"plan\"] = deepcopy(FAKE_PLAN_II)\n        subscription_modify_mock.return_value = subscription_updated\n\n        new_subscription = subscription.update(plan=new_plan)\n\n        self.assertEqual(FAKE_PLAN_II[\"id\"], new_subscription.plan.id)\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n        self.assert_fks(\n            new_plan,\n            expected_blank_fks={\n                \"djstripe.Product.default_price\",\n            },\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\"stripe.Subscription.delete\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_cancel_now(\n        self,\n        customer_retrieve_mock,\n        subscription_delete_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        subscription.current_period_end = timezone.now() + timezone.timedelta(days=7)\n        subscription.save()\n\n        cancel_timestamp = datetime_to_unix(timezone.now())\n        canceled_subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        canceled_subscription_fake[\"status\"] = SubscriptionStatus.canceled\n        canceled_subscription_fake[\"canceled_at\"] = cancel_timestamp\n        canceled_subscription_fake[\"ended_at\"] = cancel_timestamp\n\n        subscription_delete_mock.return_value = canceled_subscription_fake\n\n        self.assertTrue(self.customer.is_subscribed_to(FAKE_PRODUCT[\"id\"]))\n        self.assertEqual(self.customer.active_subscriptions.count(), 1)\n        self.assertTrue(self.customer.has_any_active_subscription())\n\n        new_subscription = subscription.cancel(at_period_end=False)\n\n        self.assertEqual(SubscriptionStatus.canceled, new_subscription.status)\n        self.assertEqual(False, new_subscription.cancel_at_period_end)\n        self.assertEqual(new_subscription.canceled_at, new_subscription.ended_at)\n        self.assertFalse(new_subscription.is_valid())\n        self.assertFalse(new_subscription.is_status_temporarily_current())\n        self.assertFalse(new_subscription in self.customer.active_subscriptions)\n        self.assertFalse(self.customer.is_subscribed_to(FAKE_PRODUCT[\"id\"]))\n        self.assertFalse(self.customer.has_any_active_subscription())\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.modify\",\n        autospec=True,\n    )\n    @patch(\"stripe.Subscription.delete\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_cancel_at_period_end(\n        self,\n        customer_retrieve_mock,\n        subscription_delete_mock,\n        subscription_modify_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        current_period_end = timezone.now() + timezone.timedelta(days=7)\n\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        subscription.current_period_end = current_period_end\n        subscription.save()\n\n        canceled_subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        canceled_subscription_fake[\"current_period_end\"] = datetime_to_unix(\n            current_period_end\n        )\n        canceled_subscription_fake[\"canceled_at\"] = datetime_to_unix(timezone.now())\n        subscription_delete_mock.return_value = (\n            canceled_subscription_fake  # retrieve().delete()\n        )\n\n        self.assertTrue(self.customer.is_subscribed_to(FAKE_PRODUCT[\"id\"]))\n        self.assertTrue(self.customer.has_any_active_subscription())\n        self.assertEqual(self.customer.active_subscriptions.count(), 1)\n        self.assertTrue(subscription in self.customer.active_subscriptions)\n\n        # Update the Subscription by cancelling it at the end of the period\n        subscription_updated = deepcopy(canceled_subscription_fake)\n        subscription_updated[\"cancel_at_period_end\"] = True\n        subscription_modify_mock.return_value = subscription_updated\n\n        new_subscription = subscription.cancel(at_period_end=True)\n\n        self.assertEqual(self.customer.active_subscriptions.count(), 1)\n        self.assertTrue(new_subscription in self.customer.active_subscriptions)\n\n        self.assertEqual(SubscriptionStatus.active, new_subscription.status)\n        self.assertEqual(True, new_subscription.cancel_at_period_end)\n        self.assertNotEqual(new_subscription.canceled_at, new_subscription.ended_at)\n        self.assertTrue(new_subscription.is_valid())\n        self.assertTrue(new_subscription.is_status_temporarily_current())\n        self.assertTrue(self.customer.is_subscribed_to(FAKE_PRODUCT[\"id\"]))\n        self.assertTrue(self.customer.has_any_active_subscription())\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\"stripe.Subscription.delete\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_cancel_during_trial_sets_at_period_end(\n        self,\n        customer_retrieve_mock,\n        subscription_delete_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        subscription.trial_end = timezone.now() + timezone.timedelta(days=7)\n        subscription.save()\n\n        cancel_timestamp = datetime_to_unix(timezone.now())\n        canceled_subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        canceled_subscription_fake[\"status\"] = SubscriptionStatus.canceled\n        canceled_subscription_fake[\"canceled_at\"] = cancel_timestamp\n        canceled_subscription_fake[\"ended_at\"] = cancel_timestamp\n        subscription_delete_mock.return_value = canceled_subscription_fake\n\n        self.assertTrue(self.customer.is_subscribed_to(FAKE_PRODUCT[\"id\"]))\n        self.assertTrue(self.customer.has_any_active_subscription())\n\n        new_subscription = subscription.cancel(at_period_end=False)\n\n        self.assertEqual(SubscriptionStatus.canceled, new_subscription.status)\n        self.assertEqual(False, new_subscription.cancel_at_period_end)\n        self.assertEqual(new_subscription.canceled_at, new_subscription.ended_at)\n        self.assertFalse(new_subscription.is_valid())\n        self.assertFalse(self.customer.is_subscribed_to(FAKE_PRODUCT[\"id\"]))\n        self.assertFalse(self.customer.has_any_active_subscription())\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.modify\",\n        autospec=True,\n    )\n    @patch(\"stripe.Subscription.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_cancel_and_reactivate(\n        self,\n        customer_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_modify_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        current_period_end = timezone.now() + timezone.timedelta(days=7)\n\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        assert subscription\n        subscription.current_period_end = current_period_end\n        subscription.save()\n\n        canceled_subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        canceled_subscription_fake[\"current_period_end\"] = datetime_to_unix(\n            current_period_end\n        )\n        canceled_subscription_fake[\"canceled_at\"] = datetime_to_unix(timezone.now())\n        subscription_retrieve_mock.return_value = canceled_subscription_fake\n\n        self.assertTrue(self.customer.is_subscribed_to(FAKE_PRODUCT[\"id\"]))\n        self.assertTrue(self.customer.has_any_active_subscription())\n\n        # Update the Subscription by cancelling it at the end of the period\n        subscription_updated = deepcopy(canceled_subscription_fake)\n        subscription_updated[\"cancel_at_period_end\"] = True\n        subscription_modify_mock.return_value = subscription_updated\n\n        new_subscription = subscription.cancel(at_period_end=True)\n        self.assertEqual(new_subscription.cancel_at_period_end, True)\n\n        new_subscription.reactivate()\n        subscription_reactivate_fake = deepcopy(FAKE_SUBSCRIPTION)\n        reactivated_subscription = Subscription.sync_from_stripe_data(\n            subscription_reactivate_fake\n        )\n        self.assertEqual(reactivated_subscription.cancel_at_period_end, False)\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"djstripe.models.Subscription._api_delete\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_CANCELED),\n    )\n    def test_cancel_already_canceled(\n        self,\n        subscription_retrieve_mock,\n        product_retrieve_mock,\n        subscription_delete_mock,\n    ):\n        subscription_delete_mock.side_effect = InvalidRequestError(\n            \"No such subscription: sub_xxxx\", \"blah\"\n        )\n\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n\n        self.assertEqual(Subscription.objects.filter(status=\"canceled\").count(), 0)\n        subscription.cancel(at_period_end=False)\n        self.assertEqual(Subscription.objects.filter(status=\"canceled\").count(), 1)\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"djstripe.models.Subscription._api_delete\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    def test_cancel_error_in_cancel(\n        self, product_retrieve_mock, subscription_delete_mock\n    ):\n        subscription_delete_mock.side_effect = InvalidRequestError(\n            \"Unexpected error\", \"blah\"\n        )\n\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n\n        with self.assertRaises(InvalidRequestError):\n            subscription.cancel(at_period_end=False)\n\n        self.assert_fks(\n            subscription, expected_blank_fks=self.default_expected_blank_fks\n        )\n\n    @patch(\"stripe.Plan.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    def test_sync_multi_plan(\n        self,\n        subscription_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION_MULTI_PLAN)\n        subscription_fake[\"latest_invoice\"] = FAKE_INVOICE[\"id\"]\n        subscription_retrieve_mock.return_value = subscription_fake\n\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n\n        self.assertIsNone(subscription.plan)\n        self.assertIsNone(subscription.quantity)\n\n        items = subscription.items.all()\n        self.assertEqual(2, len(items))\n\n        # delete pydanny customer as that causes issues with Invoice and Latest_invoice FKs\n        self.customer.delete()\n\n        self.assert_fks(\n            subscription,\n            expected_blank_fks=(\n                self.default_expected_blank_fks\n                | {\n                    \"djstripe.Customer.subscriber\",\n                    \"djstripe.Subscription.plan\",\n                    \"djstripe.Charge.latest_upcominginvoice (related name)\",\n                    \"djstripe.Charge.application_fee\",\n                    \"djstripe.Charge.dispute\",\n                    \"djstripe.Charge.on_behalf_of\",\n                    \"djstripe.Charge.source_transfer\",\n                    \"djstripe.Charge.transfer\",\n                    \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n                    \"djstripe.PaymentIntent.on_behalf_of\",\n                    \"djstripe.PaymentIntent.payment_method\",\n                    \"djstripe.Invoice.default_payment_method\",\n                    \"djstripe.Invoice.default_source\",\n                    \"djstripe.Invoice.charge\",\n                    \"djstripe.Invoice.customer\",\n                    \"djstripe.Invoice.payment_intent\",\n                    \"djstripe.Invoice.subscription\",\n                }\n            ),\n        )\n\n    @patch(\"stripe.Plan.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    def test_update_multi_plan(\n        self,\n        subscription_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION_MULTI_PLAN)\n        subscription_fake[\"latest_invoice\"] = FAKE_INVOICE[\"id\"]\n        subscription_retrieve_mock.return_value = subscription_fake\n\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n\n        self.assertIsNone(subscription.plan)\n        self.assertIsNone(subscription.quantity)\n\n        items = subscription.items.all()\n        self.assertEqual(2, len(items))\n\n        # Simulate a webhook received with one plan that has been removed\n        del subscription_fake[\"items\"][\"data\"][1]\n        subscription_fake[\"items\"][\"total_count\"] = 1\n\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        items = subscription.items.all()\n        self.assertEqual(1, len(items))\n\n        # delete pydanny customer as that causes issues with Invoice and Latest_invoice FKs\n        self.customer.delete()\n\n        self.assert_fks(\n            subscription,\n            expected_blank_fks=(\n                self.default_expected_blank_fks\n                | {\n                    \"djstripe.Customer.subscriber\",\n                    \"djstripe.Subscription.plan\",\n                    \"djstripe.Charge.latest_upcominginvoice (related name)\",\n                    \"djstripe.Charge.application_fee\",\n                    \"djstripe.Charge.dispute\",\n                    \"djstripe.Charge.on_behalf_of\",\n                    \"djstripe.Charge.source_transfer\",\n                    \"djstripe.Charge.transfer\",\n                    \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n                    \"djstripe.PaymentIntent.on_behalf_of\",\n                    \"djstripe.PaymentIntent.payment_method\",\n                    \"djstripe.Invoice.default_payment_method\",\n                    \"djstripe.Invoice.default_source\",\n                    \"djstripe.Invoice.charge\",\n                    \"djstripe.Invoice.customer\",\n                    \"djstripe.Invoice.payment_intent\",\n                    \"djstripe.Invoice.subscription\",\n                }\n            ),\n        )\n\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Charge.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_METHOD_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", return_value=deepcopy(FAKE_INVOICE_II), autospec=True\n    )\n    @patch(\"stripe.Plan.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    def test_remove_all_multi_plan(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        paymentintent_retrieve_mock,\n        paymentmethod_retrieve_mock,\n        charge_retrieve_mock,\n        balance_transaction_retrieve_mock,\n    ):\n        # delete pydanny customer as that causes issues with Invoice and Latest_invoice FKs\n        self.customer.delete()\n\n        fake_payment_intent = deepcopy(FAKE_PAYMENT_INTENT_II)\n        fake_payment_intent[\"invoice\"] = FAKE_INVOICE_II[\"id\"]\n        paymentintent_retrieve_mock.return_value = fake_payment_intent\n\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION_MULTI_PLAN)\n        fake_subscription[\"latest_invoice\"] = FAKE_INVOICE_II[\"id\"]\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        fake_charge = deepcopy(FAKE_CHARGE_II)\n        fake_charge[\"payment_method\"] = FAKE_PAYMENT_METHOD_II[\"id\"]\n        charge_retrieve_mock.return_value = fake_charge\n\n        # create invoice\n        fake_invoice = deepcopy(FAKE_INVOICE_II)\n        Invoice.sync_from_stripe_data(fake_invoice)\n\n        subscription = Subscription.sync_from_stripe_data(fake_subscription)\n\n        self.assertIsNone(subscription.plan)\n        self.assertIsNone(subscription.quantity)\n\n        items = subscription.items.all()\n        self.assertEqual(2, len(items))\n\n        # Simulate a webhook received with no more plan\n        del fake_subscription[\"items\"][\"data\"][1]\n        del fake_subscription[\"items\"][\"data\"][0]\n        fake_subscription[\"items\"][\"total_count\"] = 0\n\n        subscription = Subscription.sync_from_stripe_data(fake_subscription)\n        items = subscription.items.all()\n        self.assertEqual(0, len(items))\n\n        self.assert_fks(\n            subscription,\n            expected_blank_fks=self.default_expected_blank_fks\n            | {\n                \"djstripe.Customer.subscriber\",\n                \"djstripe.Subscription.plan\",\n            },\n        )\n\n    @patch(\"stripe.Plan.retrieve\", autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\", return_value=deepcopy(FAKE_SUBSCRIPTION_METERED)\n    )\n    def test_sync_metered_plan(\n        self,\n        subscription_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION_METERED)\n        self.assertNotIn(\n            \"quantity\",\n            subscription_fake[\"items\"][\"data\"],\n            \"Expect Metered plan SubscriptionItem to have no quantity\",\n        )\n\n        subscription = Subscription.sync_from_stripe_data(subscription_fake)\n        assert subscription\n\n        items = subscription.items.all()\n        self.assertEqual(1, len(items))\n\n        item = items[0]\n\n        self.assertEqual(subscription.quantity, 1)\n        # Note that subscription.quantity is 1,\n        # but item.quantity isn't set on metered plans\n        self.assertIsNone(item.quantity)\n        self.assertEqual(item.plan.id, FAKE_PLAN_METERED[\"id\"])\n\n        self.assert_fks(\n            subscription,\n            expected_blank_fks=(\n                self.default_expected_blank_fks\n                | {\"djstripe.Subscription.latest_invoice\"}\n            ),\n        )\n\n    @patch(\"stripe.Subscription.list\")\n    def test_api_list(self, subscription_list_mock):\n        p = PropertyMock(return_value=deepcopy(FAKE_SUBSCRIPTION))\n        type(subscription_list_mock).auto_paging_iter = p\n\n        # invoke Subscription.api_list with status enum populated\n        Subscription.api_list(status=SubscriptionStatus.canceled)\n\n        subscription_list_mock.assert_called_once_with(\n            status=SubscriptionStatus.canceled,\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @patch(\"stripe.Subscription.list\")\n    def test_api_list_with_no_status(self, subscription_list_mock):\n        p = PropertyMock(return_value=deepcopy(FAKE_SUBSCRIPTION))\n        type(subscription_list_mock).auto_paging_iter = p\n\n        # invoke Subscription.api_list without status enum populated\n        Subscription.api_list()\n\n        subscription_list_mock.assert_called_once_with(\n            status=\"all\",\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n\nclass TestSubscriptionDecimal:\n    @pytest.mark.parametrize(\n        \"inputted,expected\",\n        [\n            (Decimal(\"1\"), Decimal(\"1.00\")),\n            (Decimal(\"1.5234567\"), Decimal(\"1.52\")),\n            (Decimal(\"0\"), Decimal(\"0.00\")),\n            (Decimal(\"23.2345678\"), Decimal(\"23.23\")),\n            (\"1\", Decimal(\"1.00\")),\n            (\"1.5234567\", Decimal(\"1.52\")),\n            (\"0\", Decimal(\"0.00\")),\n            (\"23.2345678\", Decimal(\"23.23\")),\n            (1, Decimal(\"1.00\")),\n            (1.5234567, Decimal(\"1.52\")),\n            (0, Decimal(\"0.00\")),\n            (23.2345678, Decimal(\"23.24\")),\n        ],\n    )\n    def test_decimal_application_fee_percent(  # noqa: C901\n        self, inputted, expected, monkeypatch\n    ):\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION)\n        fake_subscription[\"application_fee_percent\"] = inputted\n\n        def mock_invoice_get(*args, **kwargs):\n            return FAKE_INVOICE\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_customer_get(*args, **kwargs):\n            return FAKE_CUSTOMER\n\n        def mock_charge_get(*args, **kwargs):\n            return FAKE_CHARGE\n\n        def mock_payment_method_get(*args, **kwargs):\n            return FAKE_CARD_AS_PAYMENT_METHOD\n\n        def mock_payment_intent_get(*args, **kwargs):\n            return FAKE_PAYMENT_INTENT_I\n\n        def mock_subscription_get(*args, **kwargs):\n            return fake_subscription\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_balance_transaction_get(*args, **kwargs):\n            return FAKE_BALANCE_TRANSACTION\n\n        def mock_product_get(*args, **kwargs):\n            return FAKE_PRODUCT\n\n        def mock_plan_get(*args, **kwargs):\n            return FAKE_PLAN\n\n        # monkeypatch stripe retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n        monkeypatch.setattr(stripe.Plan, \"retrieve\", mock_plan_get)\n\n        # Create Latest Invoice\n        Invoice.sync_from_stripe_data(FAKE_INVOICE)\n\n        subscription = Subscription.sync_from_stripe_data(fake_subscription)\n        field_data = subscription.application_fee_percent\n\n        assert isinstance(field_data, Decimal)\n        assert field_data == expected\n"
  },
  {
    "path": "tests/test_subscription_item.py",
    "content": "\"\"\"\ndj-stripe SubscriptionItem model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase\n\nfrom djstripe.models import SubscriptionItem\nfrom djstripe.models.billing import Invoice\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_CUSTOMER_II,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PLAN,\n    FAKE_PLAN_II,\n    FAKE_PLAN_METERED,\n    FAKE_PRICE,\n    FAKE_PRICE_II,\n    FAKE_PRICE_METERED,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_II,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_SUBSCRIPTION_ITEM_METERED,\n    FAKE_SUBSCRIPTION_ITEM_MULTI_PLAN,\n    FAKE_SUBSCRIPTION_ITEM_TAX_RATES,\n    FAKE_SUBSCRIPTION_METERED,\n    FAKE_SUBSCRIPTION_MULTI_PLAN,\n    FAKE_TAX_RATE_EXAMPLE_1_VAT,\n    AssertStripeFksMixin,\n)\n\n\nclass SubscriptionItemTest(AssertStripeFksMixin, TestCase):\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    def setUp(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        customer_retrieve_mock,\n    ):\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n        self.default_expected_blank_fks = {\n            \"djstripe.Customer.coupon\",\n            \"djstripe.Customer.default_payment_method\",\n            \"djstripe.Product.default_price\",\n            \"djstripe.Subscription.default_payment_method\",\n            \"djstripe.Subscription.default_source\",\n            \"djstripe.Subscription.pending_setup_intent\",\n            \"djstripe.Subscription.schedule\",\n        }\n        # create latest invoice\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n    @patch(\n        \"stripe.Price.retrieve\",\n        return_value=deepcopy(FAKE_PRICE_METERED),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_METERED), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_METERED),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data_metered_subscription(\n        self,\n        subscription_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        price_retrieve_mock,\n    ):\n        subscription_item_fake = deepcopy(FAKE_SUBSCRIPTION_ITEM_METERED)\n        subscription_item = SubscriptionItem.sync_from_stripe_data(\n            subscription_item_fake\n        )\n\n        self.assertEqual(subscription_item.id, FAKE_SUBSCRIPTION_ITEM_METERED[\"id\"])\n        self.assertEqual(\n            subscription_item.plan.id, FAKE_SUBSCRIPTION_ITEM_METERED[\"plan\"][\"id\"]\n        )\n        self.assertEqual(\n            subscription_item.price.id, FAKE_SUBSCRIPTION_ITEM_METERED[\"price\"][\"id\"]\n        )\n        self.assertEqual(\n            subscription_item.subscription.id,\n            FAKE_SUBSCRIPTION_ITEM_METERED[\"subscription\"],\n        )\n\n        self.assert_fks(\n            subscription_item,\n            expected_blank_fks=(\n                self.default_expected_blank_fks\n                | {\"djstripe.Subscription.latest_invoice\"}\n            ),\n        )\n\n    @patch(\n        \"stripe.Price.retrieve\",\n        return_value=deepcopy(FAKE_PRICE_II),\n        autospec=True,\n    )\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_II), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    def test_sync_items_with_tax_rates(\n        self,\n        subscription_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        price_retrieve_mock,\n    ):\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION_II)\n        fake_subscription[\"latest_invoice\"] = FAKE_INVOICE[\"id\"]\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        subscription_item_fake = deepcopy(FAKE_SUBSCRIPTION_ITEM_TAX_RATES)\n        subscription_item = SubscriptionItem.sync_from_stripe_data(\n            subscription_item_fake\n        )\n\n        self.assertEqual(subscription_item.id, FAKE_SUBSCRIPTION_ITEM_TAX_RATES[\"id\"])\n        self.assertEqual(\n            subscription_item.plan.id, FAKE_SUBSCRIPTION_ITEM_TAX_RATES[\"plan\"][\"id\"]\n        )\n        self.assertEqual(\n            subscription_item.price.id, FAKE_SUBSCRIPTION_ITEM_TAX_RATES[\"price\"][\"id\"]\n        )\n        self.assertEqual(\n            subscription_item.subscription.id,\n            FAKE_SUBSCRIPTION_ITEM_TAX_RATES[\"subscription\"],\n        )\n\n        self.assert_fks(\n            subscription_item,\n            expected_blank_fks=(\n                self.default_expected_blank_fks\n                | {\n                    \"djstripe.Charge.latest_upcominginvoice (related name)\",\n                    \"djstripe.Charge.application_fee\",\n                    \"djstripe.Charge.dispute\",\n                    \"djstripe.Charge.on_behalf_of\",\n                    \"djstripe.Charge.source_transfer\",\n                    \"djstripe.Charge.transfer\",\n                    \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n                    \"djstripe.PaymentIntent.on_behalf_of\",\n                    \"djstripe.PaymentIntent.payment_method\",\n                    \"djstripe.Invoice.default_payment_method\",\n                    \"djstripe.Invoice.default_source\",\n                }\n            ),\n        )\n\n        self.assertEqual(subscription_item.tax_rates.count(), 1)\n        self.assertEqual(\n            subscription_item.tax_rates.first().id, FAKE_TAX_RATE_EXAMPLE_1_VAT[\"id\"]\n        )\n\n    @patch(\n        \"stripe.Price.retrieve\",\n        side_effect=[deepcopy(FAKE_PRICE), deepcopy(FAKE_PRICE_II)],\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Plan.retrieve\",\n        side_effect=[deepcopy(FAKE_PLAN), deepcopy(FAKE_PLAN_II)],\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        autospec=True,\n    )\n    def test_sync_multi_plan_subscription(\n        self,\n        subscription_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        price_retrieve_mock,\n    ):\n        fake_subscription = deepcopy(FAKE_SUBSCRIPTION_MULTI_PLAN)\n        fake_subscription[\"latest_invoice\"] = FAKE_INVOICE[\"id\"]\n        subscription_retrieve_mock.return_value = fake_subscription\n\n        subscription_item_fake = deepcopy(FAKE_SUBSCRIPTION_ITEM_MULTI_PLAN)\n        subscription_item = SubscriptionItem.sync_from_stripe_data(\n            subscription_item_fake\n        )\n\n        self.assertEqual(subscription_item.id, FAKE_SUBSCRIPTION_ITEM_MULTI_PLAN[\"id\"])\n        self.assertEqual(\n            subscription_item.plan.id, FAKE_SUBSCRIPTION_ITEM_MULTI_PLAN[\"plan\"][\"id\"]\n        )\n        self.assertEqual(\n            subscription_item.price.id, FAKE_SUBSCRIPTION_ITEM_MULTI_PLAN[\"price\"][\"id\"]\n        )\n        self.assertEqual(\n            subscription_item.subscription.id,\n            FAKE_SUBSCRIPTION_ITEM_MULTI_PLAN[\"subscription\"],\n        )\n\n        # delete pydanny customer as that causes issues with Invoice and Latest_invoice FKs\n        self.customer.delete()\n\n        self.assert_fks(\n            subscription_item,\n            expected_blank_fks=(\n                self.default_expected_blank_fks\n                | {\n                    \"djstripe.Customer.subscriber\",\n                    \"djstripe.Subscription.plan\",\n                    \"djstripe.Charge.latest_upcominginvoice (related name)\",\n                    \"djstripe.Charge.application_fee\",\n                    \"djstripe.Charge.dispute\",\n                    \"djstripe.Charge.on_behalf_of\",\n                    \"djstripe.Charge.source_transfer\",\n                    \"djstripe.Charge.transfer\",\n                    \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n                    \"djstripe.PaymentIntent.on_behalf_of\",\n                    \"djstripe.PaymentIntent.payment_method\",\n                    \"djstripe.Invoice.default_payment_method\",\n                    \"djstripe.Invoice.default_source\",\n                    \"djstripe.Invoice.charge\",\n                    \"djstripe.Invoice.customer\",\n                    \"djstripe.Invoice.payment_intent\",\n                    \"djstripe.Invoice.subscription\",\n                }\n            ),\n        )\n"
  },
  {
    "path": "tests/test_subscription_schedule.py",
    "content": "\"\"\"\ndj-stripe SubscriptionSchedule model tests.\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport stripe\nfrom django.contrib.auth import get_user_model\nfrom django.test import TestCase\n\nfrom djstripe.enums import SubscriptionScheduleStatus\nfrom djstripe.models import Invoice, SubscriptionSchedule\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PLAN,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_SUBSCRIPTION_SCHEDULE,\n    AssertStripeFksMixin,\n    datetime_to_unix,\n)\n\n\nclass SubscriptionScheduleTest(AssertStripeFksMixin, TestCase):\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION),\n        autospec=True,\n    )\n    @patch(\"stripe.Charge.retrieve\", return_value=deepcopy(FAKE_CHARGE), autospec=True)\n    @patch(\n        \"stripe.PaymentMethod.retrieve\",\n        return_value=deepcopy(FAKE_CARD_AS_PAYMENT_METHOD),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.PaymentIntent.retrieve\",\n        return_value=deepcopy(FAKE_PAYMENT_INTENT_I),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\", autospec=True, return_value=deepcopy(FAKE_INVOICE)\n    )\n    def setUp(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        product_retrieve_mock,\n        payment_intent_retrieve_mock,\n        paymentmethod_card_retrieve_mock,\n        charge_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        customer_retrieve_mock,\n    ):\n        self.user = get_user_model().objects.create_user(\n            username=\"pydanny\", email=\"pydanny@gmail.com\"\n        )\n        self.customer = FAKE_CUSTOMER.create_for_user(self.user)\n\n        self.default_expected_blank_fks = {\n            \"djstripe.Customer.coupon\",\n            \"djstripe.Customer.default_payment_method\",\n            \"djstripe.Charge.application_fee\",\n            \"djstripe.Charge.dispute\",\n            \"djstripe.Charge.latest_upcominginvoice (related name)\",\n            \"djstripe.Charge.on_behalf_of\",\n            \"djstripe.Charge.source_transfer\",\n            \"djstripe.Charge.transfer\",\n            \"djstripe.PaymentIntent.on_behalf_of\",\n            \"djstripe.PaymentIntent.payment_method\",\n            \"djstripe.PaymentIntent.upcominginvoice (related name)\",\n            \"djstripe.Product.default_price\",\n            \"djstripe.Invoice.default_payment_method\",\n            \"djstripe.Invoice.default_source\",\n            \"djstripe.Subscription.default_payment_method\",\n            \"djstripe.Subscription.default_source\",\n            \"djstripe.Subscription.pending_setup_intent\",\n            \"djstripe.Subscription.schedule\",\n            \"djstripe.SubscriptionSchedule.released_subscription\",\n        }\n\n        # create latest invoice\n        Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_from_stripe_data(\n        self,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        canceled_schedule_fake = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        canceled_schedule_fake[\"canceled_at\"] = 1624553655\n        canceled_schedule_fake[\"status\"] = SubscriptionScheduleStatus.canceled\n\n        schedule = SubscriptionSchedule.sync_from_stripe_data(canceled_schedule_fake)\n\n        self.assert_fks(schedule, expected_blank_fks=self.default_expected_blank_fks)\n        self.assertEqual(datetime_to_unix(schedule.canceled_at), 1624553655)\n        self.assertEqual(schedule.subscription.id, FAKE_SUBSCRIPTION[\"id\"])\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test___str__(\n        self,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        schedule = SubscriptionSchedule.sync_from_stripe_data(\n            deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        )\n        self.assertEqual(f\"<id={FAKE_SUBSCRIPTION_SCHEDULE['id']}>\", str(schedule))\n\n        self.assert_fks(schedule, expected_blank_fks=self.default_expected_blank_fks)\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_release(\n        self,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        schedule = SubscriptionSchedule.sync_from_stripe_data(\n            deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        )\n        with patch.object(\n            stripe.SubscriptionSchedule,\n            \"release\",\n            return_value=FAKE_SUBSCRIPTION_SCHEDULE,\n        ) as patched__api_update:\n            schedule.release()\n\n        patched__api_update.assert_called_once_with(\n            FAKE_SUBSCRIPTION_SCHEDULE[\"id\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_account=schedule.djstripe_owner_account.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_cancel(\n        self,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        schedule = SubscriptionSchedule.sync_from_stripe_data(\n            deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        )\n        with patch.object(\n            stripe.SubscriptionSchedule,\n            \"cancel\",\n            return_value=FAKE_SUBSCRIPTION_SCHEDULE,\n        ) as patched__api_update:\n            schedule.cancel()\n\n        patched__api_update.assert_called_once_with(\n            FAKE_SUBSCRIPTION_SCHEDULE[\"id\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_account=schedule.djstripe_owner_account.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @patch(\"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN), autospec=True)\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_update(\n        self,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        schedule = SubscriptionSchedule.sync_from_stripe_data(\n            deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        )\n        with patch.object(\n            stripe.SubscriptionSchedule,\n            \"modify\",\n            return_value=FAKE_SUBSCRIPTION_SCHEDULE,\n        ) as patched__api_update:\n            schedule.update()\n\n        patched__api_update.assert_called_once_with(\n            FAKE_SUBSCRIPTION_SCHEDULE[\"id\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_account=schedule.djstripe_owner_account.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n"
  },
  {
    "path": "tests/test_sync.py",
    "content": "\"\"\"\ndj-stripe Sync Method Tests.\n\"\"\"\nimport contextlib\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nfrom django.contrib.auth import get_user_model\nfrom django.test.testcases import TestCase\nfrom stripe.error import InvalidRequestError\n\nfrom djstripe.models import Customer\nfrom djstripe.sync import sync_subscriber\n\nfrom . import FAKE_CUSTOMER\n\n\n@contextlib.contextmanager\ndef capture_stdout():\n    import sys\n    from io import StringIO\n\n    old_stdout = sys.stdout\n    sys.stdout = StringIO()\n\n    try:\n        yield sys.stdout\n    finally:\n        sys.stdout = old_stdout\n\n\nclass TestSyncSubscriber(TestCase):\n    def setUp(self):\n        self.user = get_user_model().objects.create_user(\n            username=\"testuser\", email=\"test@example.com\", password=\"123\"\n        )\n\n    @patch(\"djstripe.models.Customer._sync_charges\", autospec=True)\n    @patch(\"djstripe.models.Customer._sync_invoices\", autospec=True)\n    @patch(\"djstripe.models.Customer._sync_subscriptions\", autospec=True)\n    @patch(\n        \"stripe.Customer.retrieve\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.create\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_success(\n        self,\n        stripe_customer_create_mock,\n        api_retrieve_mock,\n        _sync_subscriptions_mock,\n        _sync_invoices_mock,\n        _sync_charges_mock,\n    ):\n        sync_subscriber(self.user)\n        self.assertEqual(1, Customer.objects.count())\n        self.assertEqual(\n            FAKE_CUSTOMER[\"id\"],\n            Customer.objects.get(subscriber=self.user).api_retrieve()[\"id\"],\n        )\n\n        _sync_subscriptions_mock.assert_called_once_with(Customer.objects.first())\n        _sync_invoices_mock.assert_called_once_with(Customer.objects.first())\n        _sync_charges_mock.assert_called_once_with(Customer.objects.first())\n\n    @patch(\n        \"djstripe.models.Customer.api_retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.create\", return_value=deepcopy(FAKE_CUSTOMER), autospec=True\n    )\n    def test_sync_fail(self, stripe_customer_create_mock, api_retrieve_mock):\n        api_retrieve_mock.side_effect = InvalidRequestError(\"No such customer:\", \"blah\")\n\n        with capture_stdout() as stdout:\n            sync_subscriber(self.user)\n\n        self.assertEqual(\"ERROR: No such customer:\", stdout.getvalue().strip())\n"
  },
  {
    "path": "tests/test_tax_code.py",
    "content": "\"\"\"\ndj-stripe TaxCode Model Tests.\n\"\"\"\nfrom copy import deepcopy\n\nimport pytest\nfrom django.test import TestCase\n\nfrom djstripe.models import TaxCode\nfrom tests import FAKE_TAX_CODE\n\npytestmark = pytest.mark.django_db\n\n\nclass TaxCodeTest(TestCase):\n    def test_sync_from_stripe_data(self):\n        tax_code = TaxCode.sync_from_stripe_data(deepcopy(FAKE_TAX_CODE))\n        assert tax_code\n        assert tax_code.id == FAKE_TAX_CODE[\"id\"]\n\n    def test___str__(self):\n        tax_code = TaxCode.sync_from_stripe_data(deepcopy(FAKE_TAX_CODE))\n        assert str(tax_code) == \"General - Tangible Goods: txcd_99999999\"\n"
  },
  {
    "path": "tests/test_tax_id.py",
    "content": "\"\"\"\ndj-stripe TaxId model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import PropertyMock, patch\n\nimport pytest\nfrom django.test.testcases import TestCase\n\nfrom djstripe import enums\nfrom djstripe.models import Customer, TaxId\nfrom djstripe.settings import djstripe_settings\n\nfrom . import FAKE_CUSTOMER, FAKE_TAX_ID, AssertStripeFksMixin\n\npytestmark = pytest.mark.django_db\n\n\nclass TestTaxIdStr(TestCase):\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve_tax_id\",\n        return_value=deepcopy(FAKE_TAX_ID),\n        autospec=True,\n    )\n    def test___str__(\n        self,\n        tax_id_retrieve_mock,\n        customer_retrieve_mock,\n    ):\n        tax_id = TaxId.sync_from_stripe_data(FAKE_TAX_ID)\n        self.assertEqual(\n            str(tax_id),\n            f\"{enums.TaxIdType.humanize(FAKE_TAX_ID['type'])} {FAKE_TAX_ID['value']} ({FAKE_TAX_ID['verification']['status']})\",\n        )\n\n\nclass TestTransfer(AssertStripeFksMixin, TestCase):\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve_tax_id\",\n        return_value=deepcopy(FAKE_TAX_ID),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data(\n        self,\n        tax_id_retrieve_mock,\n        customer_retrieve_mock,\n    ):\n        tax_id = TaxId.sync_from_stripe_data(FAKE_TAX_ID)\n        assert tax_id\n        assert tax_id.id == FAKE_TAX_ID[\"id\"]\n        assert tax_id.customer.id == FAKE_CUSTOMER[\"id\"]\n        self.assert_fks(\n            tax_id,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.subscriber\",\n            },\n        )\n\n    # we are returning any value for the Customer.objects.get as we only need to avoid the Customer.DoesNotExist error\n    @patch(\n        \"djstripe.models.core.Customer.objects.get\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.create_tax_id\",\n        return_value=deepcopy(FAKE_TAX_ID),\n        autospec=True,\n    )\n    def test__api_create(\n        self,\n        tax_id_create_mock,\n        customer_get_mock,\n    ):\n        STRIPE_DATA = TaxId._api_create(\n            id=FAKE_CUSTOMER[\"id\"], type=FAKE_TAX_ID[\"type\"], value=FAKE_TAX_ID[\"value\"]\n        )\n\n        assert STRIPE_DATA == FAKE_TAX_ID\n        tax_id_create_mock.assert_called_once_with(\n            id=FAKE_CUSTOMER[\"id\"],\n            type=FAKE_TAX_ID[\"type\"],\n            value=FAKE_TAX_ID[\"value\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        )\n\n    # we are returning any value for the Customer.objects.get as we only need to avoid the Customer.DoesNotExist error\n    @patch(\n        \"djstripe.models.core.Customer.objects.get\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.create_tax_id\",\n        return_value=deepcopy(FAKE_TAX_ID),\n        autospec=True,\n    )\n    def test__api_create_no_id_kwarg(\n        self,\n        tax_id_create_mock,\n        customer_get_mock,\n    ):\n        with pytest.raises(KeyError) as exc:\n            TaxId._api_create(\n                FAKE_CUSTOMER[\"id\"],\n                type=FAKE_TAX_ID[\"type\"],\n                value=FAKE_TAX_ID[\"value\"],\n            )\n        assert \"Customer Object ID is missing\" in str(exc.value)\n\n    @patch(\n        \"stripe.Customer.create_tax_id\",\n        return_value=deepcopy(FAKE_TAX_ID),\n        autospec=True,\n    )\n    def test__api_create_no_customer(\n        self,\n        tax_id_create_mock,\n    ):\n        with pytest.raises(Customer.DoesNotExist):\n            TaxId._api_create(\n                id=FAKE_CUSTOMER[\"id\"],\n                type=FAKE_TAX_ID[\"type\"],\n                value=FAKE_TAX_ID[\"value\"],\n            )\n\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Customer.retrieve_tax_id\",\n        return_value=deepcopy(FAKE_TAX_ID),\n        autospec=True,\n    )\n    def test_api_retrieve(\n        self,\n        tax_id_retrieve_mock,\n        customer_retrieve_mock,\n    ):\n        tax_id = TaxId.sync_from_stripe_data(FAKE_TAX_ID)\n        assert tax_id\n        tax_id.api_retrieve()\n        assert tax_id.djstripe_owner_account\n\n        tax_id_retrieve_mock.assert_called_once_with(\n            id=FAKE_CUSTOMER[\"id\"],\n            nested_id=FAKE_TAX_ID[\"id\"],\n            expand=[],\n            stripe_account=tax_id.djstripe_owner_account.id,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        )\n\n    @patch(\n        \"stripe.Customer.list_tax_ids\",\n        autospec=True,\n    )\n    def test_api_list(\n        self,\n        tax_id_list_mock,\n    ):\n        p = PropertyMock(return_value=deepcopy(FAKE_TAX_ID))\n        type(tax_id_list_mock).auto_paging_iter = p\n\n        TaxId.api_list(id=FAKE_CUSTOMER[\"id\"])\n\n        tax_id_list_mock.assert_called_once_with(\n            id=FAKE_CUSTOMER[\"id\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n"
  },
  {
    "path": "tests/test_tax_rates.py",
    "content": "\"\"\"\ndj-stripe TaxRate Model Tests.\n\"\"\"\nfrom copy import deepcopy\nfrom decimal import Decimal\n\nimport pytest\nfrom django.test import TestCase\n\nfrom djstripe.models import TaxRate\nfrom tests import FAKE_TAX_RATE_EXAMPLE_1_VAT\n\npytestmark = pytest.mark.django_db\n\n\nclass TaxRateTest(TestCase):\n    def test_sync_from_stripe_data(self):\n        tax_rate = TaxRate.sync_from_stripe_data(deepcopy(FAKE_TAX_RATE_EXAMPLE_1_VAT))\n\n        self.assertEqual(\n            FAKE_TAX_RATE_EXAMPLE_1_VAT[\"id\"],\n            tax_rate.id,\n        )\n\n    def test___str__(self):\n        tax_rate = TaxRate.sync_from_stripe_data(deepcopy(FAKE_TAX_RATE_EXAMPLE_1_VAT))\n\n        self.assertEqual(\n            f\"{FAKE_TAX_RATE_EXAMPLE_1_VAT['display_name']} at {FAKE_TAX_RATE_EXAMPLE_1_VAT['percentage']:.4f}%\",\n            str(tax_rate),\n        )\n\n\nclass TestTaxRateDecimal:\n    @pytest.mark.parametrize(\n        \"inputted,expected\",\n        [\n            (Decimal(\"1\"), Decimal(\"1.0000\")),\n            (Decimal(\"1.5234567\"), Decimal(\"1.5235\")),\n            (Decimal(\"0\"), Decimal(\"0.0000\")),\n            (Decimal(\"23.2345678\"), Decimal(\"23.2346\")),\n            (\"1\", Decimal(\"1.0000\")),\n            (\"1.5234567\", Decimal(\"1.5235\")),\n            (\"0\", Decimal(\"0.0000\")),\n            (\"23.2345678\", Decimal(\"23.2346\")),\n            (1, Decimal(\"1.0000\")),\n            (1.5234567, Decimal(\"1.5235\")),\n            (0, Decimal(\"0.0000\")),\n            (23.2345678, Decimal(\"23.2346\")),\n        ],\n    )\n    def test_decimal_tax_percent(self, inputted, expected):\n        fake_tax_rate = deepcopy(FAKE_TAX_RATE_EXAMPLE_1_VAT)\n        fake_tax_rate[\"percentage\"] = inputted\n\n        tax_rate = TaxRate.sync_from_stripe_data(fake_tax_rate)\n        field_data = tax_rate.percentage\n\n        assert isinstance(field_data, Decimal)\n        assert field_data == expected\n"
  },
  {
    "path": "tests/test_transfer.py",
    "content": "\"\"\"\ndj-stripe Transfer model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nfrom django.test.testcases import TestCase\n\nfrom djstripe.models import Transfer\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION_II,\n    FAKE_STANDARD_ACCOUNT,\n    FAKE_TRANSFER,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\ndef FAKE_TRANSFER_COMPLETE_REVERSAL():\n    data = deepcopy(FAKE_TRANSFER)\n    data[\"reversed\"] = True\n    data[\"amount_reversed\"] = data[\"amount\"]\n    return data\n\n\ndef FAKE_TRANSFER_PARTIAL_REVERSAL():\n    data = deepcopy(FAKE_TRANSFER)\n    assert data[\"amount\"] > 1\n    data[\"amount_reversed\"] = data[\"amount\"] - 1\n    return data\n\n\nclass TestTransferStr:\n    @pytest.mark.parametrize(\n        \"fake_transfer_data\",\n        [\n            deepcopy(FAKE_TRANSFER),\n            FAKE_TRANSFER_COMPLETE_REVERSAL(),\n            FAKE_TRANSFER_PARTIAL_REVERSAL(),\n        ],\n    )\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_STANDARD_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION_II),\n        autospec=True,\n    )\n    @patch(\"stripe.Transfer.retrieve\", autospec=True)\n    def test___str__(\n        self,\n        transfer_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n        fake_transfer_data,\n    ):\n        transfer_retrieve_mock.return_value = fake_transfer_data\n        transfer = Transfer.sync_from_stripe_data(fake_transfer_data)\n\n        if fake_transfer_data[\"reversed\"]:\n            assert \"$1.00 USD Reversed\" == str(transfer)\n\n        elif fake_transfer_data[\"amount_reversed\"]:\n            assert \"$1.00 USD Partially Reversed\" == str(transfer)\n\n        else:\n            assert \"$1.00 USD\" == str(transfer)\n\n\nclass TestTransfer(AssertStripeFksMixin, TestCase):\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_STANDARD_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    def test_sync_from_stripe_data(\n        self,\n        transfer_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        transfer = Transfer.sync_from_stripe_data(deepcopy(FAKE_TRANSFER))\n\n        balance_transaction_retrieve_mock.assert_not_called()\n        transfer_retrieve_mock.assert_not_called()\n\n        assert (\n            transfer.balance_transaction.id\n            == FAKE_TRANSFER[\"balance_transaction\"][\"id\"]\n        )\n        # assert transfer.destination.id == FAKE_TRANSFER[\"destination\"]\n        assert transfer.destination == FAKE_TRANSFER[\"destination\"]\n\n        self.assert_fks(transfer, expected_blank_fks=\"\")\n\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_STANDARD_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    def test_fee(\n        self,\n        transfer_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        transfer = Transfer.sync_from_stripe_data(deepcopy(FAKE_TRANSFER))\n        assert transfer.fee == FAKE_BALANCE_TRANSACTION_II[\"fee\"]\n        assert transfer.fee == transfer.balance_transaction.fee\n\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_STANDARD_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    def test_get_stripe_dashboard_url(\n        self,\n        transfer_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        transfer = Transfer.sync_from_stripe_data(deepcopy(FAKE_TRANSFER))\n\n        assert transfer.get_stripe_dashboard_url() == (\n            f\"{transfer._get_base_stripe_dashboard_url()}\"\n            f\"connect/{transfer.stripe_dashboard_item_name}/{transfer.id}\"\n        )\n"
  },
  {
    "path": "tests/test_transfer_reversal.py",
    "content": "\"\"\"\ndj-stripe TransferReversal model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import PropertyMock, patch\n\nimport pytest\nfrom django.test.testcases import TestCase\n\nfrom djstripe.models import Transfer, TransferReversal\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_BALANCE_TRANSACTION_II,\n    FAKE_PLATFORM_ACCOUNT,\n    FAKE_TRANSFER,\n    FAKE_TRANSFER_WITH_1_REVERSAL,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestTransferReversalStr(TestCase):\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve_reversal\",\n        autospec=True,\n        return_value=deepcopy(FAKE_TRANSFER_WITH_1_REVERSAL),\n    )\n    def test___str__(\n        self,\n        transfer_reversal_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        transfer_reversal = TransferReversal.sync_from_stripe_data(\n            deepcopy(FAKE_TRANSFER_WITH_1_REVERSAL[\"reversals\"][\"data\"][0])\n        )\n        self.assertEqual(str(f\"{transfer_reversal.transfer}\"), str(transfer_reversal))\n\n\nclass TestTransfer(AssertStripeFksMixin, TestCase):\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve_reversal\",\n        autospec=True,\n        return_value=deepcopy(FAKE_TRANSFER_WITH_1_REVERSAL[\"reversals\"][\"data\"][0]),\n    )\n    def test_sync_from_stripe_data(\n        self,\n        transfer_reversal_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        transfer_reversal = TransferReversal.sync_from_stripe_data(\n            deepcopy(FAKE_TRANSFER_WITH_1_REVERSAL[\"reversals\"][\"data\"][0])\n        )\n\n        balance_transaction_retrieve_mock.assert_not_called()\n        transfer_reversal_retrieve_mock.assert_not_called()\n\n        assert (\n            transfer_reversal.balance_transaction.id\n            == FAKE_TRANSFER[\"balance_transaction\"][\"id\"]\n        )\n        assert (\n            transfer_reversal.transfer.id\n            == FAKE_TRANSFER_WITH_1_REVERSAL[\"reversals\"][\"data\"][0][\"transfer\"][\"id\"]\n        )\n\n        self.assert_fks(transfer_reversal, expected_blank_fks=\"\")\n\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_PLATFORM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.BalanceTransaction.retrieve\",\n        return_value=deepcopy(FAKE_BALANCE_TRANSACTION_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve_reversal\",\n        autospec=True,\n        return_value=deepcopy(FAKE_TRANSFER_WITH_1_REVERSAL),\n    )\n    def test_api_retrieve(\n        self,\n        transfer_reversal_retrieve_mock,\n        balance_transaction_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        transfer_reversal = TransferReversal.sync_from_stripe_data(\n            deepcopy(FAKE_TRANSFER_WITH_1_REVERSAL[\"reversals\"][\"data\"][0])\n        )\n        transfer_reversal.api_retrieve()\n\n        transfer_reversal_retrieve_mock.assert_called_once_with(\n            id=FAKE_TRANSFER_WITH_1_REVERSAL[\"id\"],\n            nested_id=FAKE_TRANSFER_WITH_1_REVERSAL[\"reversals\"][\"data\"][0][\"id\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n            expand=[\"balance_transaction\", \"transfer\"],\n            stripe_account=transfer_reversal.djstripe_owner_account.id,\n        )\n\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    # we are returning any value for the Transfer.objects.get as we only need to avoid the Transfer.DoesNotExist error\n    @patch(\n        \"djstripe.models.connect.Transfer.objects.get\",\n        return_value=deepcopy(FAKE_TRANSFER),\n    )\n    @patch(\n        \"stripe.Transfer.create_reversal\",\n        autospec=True,\n        return_value=deepcopy(FAKE_TRANSFER_WITH_1_REVERSAL),\n    )\n    def test__api_create(\n        self,\n        transfer_reversal_create_mock,\n        transfer_get_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        TransferReversal._api_create(\n            id=FAKE_TRANSFER_WITH_1_REVERSAL[\"reversals\"][\"data\"][0][\"transfer\"][\"id\"]\n        )\n\n        transfer_reversal_create_mock.assert_called_once_with(\n            id=FAKE_TRANSFER_WITH_1_REVERSAL[\"reversals\"][\"data\"][0][\"transfer\"][\"id\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    @patch(\"stripe.Transfer.list_reversals\", autospec=True)\n    def test_api_list(self, transfer_reversal_list_mock):\n        p = PropertyMock(return_value=deepcopy(FAKE_TRANSFER_WITH_1_REVERSAL))\n        type(transfer_reversal_list_mock).auto_paging_iter = p\n\n        TransferReversal.api_list(\n            id=FAKE_TRANSFER_WITH_1_REVERSAL[\"reversals\"][\"data\"][0][\"transfer\"][\"id\"]\n        )\n\n        transfer_reversal_list_mock.assert_called_once_with(\n            id=FAKE_TRANSFER_WITH_1_REVERSAL[\"reversals\"][\"data\"][0][\"transfer\"][\"id\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n\n    def test_is_valid_object(self):\n        assert TransferReversal.is_valid_object(\n            deepcopy(FAKE_TRANSFER_WITH_1_REVERSAL[\"reversals\"][\"data\"][0])\n        )\n"
  },
  {
    "path": "tests/test_usage_record.py",
    "content": "\"\"\"\ndj-stripe UsageRecord model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import patch\n\nimport pytest\nfrom django.test.testcases import TestCase\n\nfrom djstripe.models.billing import UsageRecord\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_CUSTOMER_II,\n    FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE,\n    FAKE_PLAN_METERED,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_USAGE_RECORD,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestUsageRecord(AssertStripeFksMixin, TestCase):\n    @patch(\n        \"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_METERED), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        fake_usage_data = deepcopy(FAKE_USAGE_RECORD)\n\n        usage_record = UsageRecord.sync_from_stripe_data(fake_usage_data)\n        assert usage_record\n\n        self.assertEqual(usage_record.id, fake_usage_data[\"id\"])\n        self.assertEqual(\n            usage_record.subscription_item.id, fake_usage_data[\"subscription_item\"]\n        )\n\n        self.assert_fks(\n            usage_record,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.subscriber\",\n                \"djstripe.Product.default_price\",\n                \"djstripe.Subscription.default_payment_method\",\n                \"djstripe.Subscription.default_source\",\n                \"djstripe.Subscription.pending_setup_intent\",\n                \"djstripe.Subscription.schedule\",\n                \"djstripe.Subscription.latest_invoice\",\n            },\n        )\n\n    @patch(\n        \"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_METERED), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE),\n        autospec=True,\n    )\n    def test___str__(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        fake_usage_data = deepcopy(FAKE_USAGE_RECORD)\n\n        usage_record = UsageRecord.sync_from_stripe_data(fake_usage_data)\n        assert usage_record\n\n        self.assertEqual(\n            str(usage_record),\n            f\"Usage for {str(usage_record.subscription_item)} ({fake_usage_data['action']}) is {fake_usage_data['quantity']}\",\n        )\n\n    @patch(\n        \"stripe.SubscriptionItem.create_usage_record\",\n        autospec=True,\n        return_value=deepcopy(FAKE_USAGE_RECORD),\n    )\n    @patch(\n        \"djstripe.models.billing.UsageRecord.sync_from_stripe_data\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n    )\n    @patch(\n        \"djstripe.models.billing.SubscriptionItem.objects.get\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n    )\n    @patch(\n        \"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_METERED), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE),\n        autospec=True,\n    )\n    def test__api_create(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        subcription_item_get_mock,\n        sync_from_stripe_data_mock,\n        usage_record_creation_mock,\n    ):\n        fake_usage_data = deepcopy(FAKE_USAGE_RECORD)\n\n        UsageRecord._api_create(id=fake_usage_data[\"subscription_item\"])\n\n        # assert usage_record_creation_mock was called as expected\n        usage_record_creation_mock.assert_called_once_with(\n            id=fake_usage_data[\"subscription_item\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n        )\n\n        # assert usage_record_creation_mock was called as expected\n        sync_from_stripe_data_mock.assert_called_once_with(\n            fake_usage_data, api_key=djstripe_settings.STRIPE_SECRET_KEY\n        )\n"
  },
  {
    "path": "tests/test_usage_record_summary.py",
    "content": "\"\"\"\ndj-stripe UsageRecordSummary model tests\n\"\"\"\nfrom copy import deepcopy\nfrom unittest.mock import PropertyMock, call, patch\n\nimport pytest\nfrom django.test.testcases import TestCase\n\nfrom djstripe.models.billing import UsageRecordSummary\nfrom djstripe.settings import djstripe_settings\n\nfrom . import (\n    FAKE_CUSTOMER_II,\n    FAKE_INVOICE_METERED_SUBSCRIPTION,\n    FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE,\n    FAKE_INVOICEITEM,\n    FAKE_INVOICEITEM_II,\n    FAKE_PLAN_METERED,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_USAGE_RECORD_SUMMARY,\n    AssertStripeFksMixin,\n)\n\npytestmark = pytest.mark.django_db\n\n\nclass TestUsageRecordSummary(AssertStripeFksMixin, TestCase):\n    @patch(\n        \"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_METERED), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data_with_null_invoice(\n        self,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        fake_usage_data = deepcopy(FAKE_USAGE_RECORD_SUMMARY)\n\n        usage_record_summary = UsageRecordSummary.sync_from_stripe_data(\n            fake_usage_data[\"data\"][0]\n        )\n\n        self.assertEqual(usage_record_summary.id, fake_usage_data[\"data\"][0][\"id\"])\n        self.assertEqual(\n            usage_record_summary.subscription_item.id,\n            fake_usage_data[\"data\"][0][\"subscription_item\"],\n        )\n\n        self.assert_fks(\n            usage_record_summary,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.subscriber\",\n                \"djstripe.Product.default_price\",\n                \"djstripe.Subscription.default_payment_method\",\n                \"djstripe.Subscription.default_source\",\n                \"djstripe.Subscription.pending_setup_intent\",\n                \"djstripe.Subscription.schedule\",\n                \"djstripe.Subscription.latest_invoice\",\n                \"djstripe.UsageRecordSummary.invoice\",\n            },\n        )\n\n    @patch(\n        \"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_METERED), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE_METERED_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test_sync_from_stripe_data(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        fake_usage_data = deepcopy(FAKE_USAGE_RECORD_SUMMARY)\n\n        usage_record_summary = UsageRecordSummary.sync_from_stripe_data(\n            fake_usage_data[\"data\"][1]\n        )\n\n        self.assertEqual(usage_record_summary.id, fake_usage_data[\"data\"][1][\"id\"])\n        self.assertEqual(\n            usage_record_summary.subscription_item.id,\n            fake_usage_data[\"data\"][1][\"subscription_item\"],\n        )\n\n        self.assert_fks(\n            usage_record_summary,\n            expected_blank_fks={\n                \"djstripe.Customer.coupon\",\n                \"djstripe.Customer.default_payment_method\",\n                \"djstripe.Customer.subscriber\",\n                \"djstripe.Product.default_price\",\n                \"djstripe.Subscription.default_payment_method\",\n                \"djstripe.Subscription.default_source\",\n                \"djstripe.Subscription.pending_setup_intent\",\n                \"djstripe.Subscription.schedule\",\n                \"djstripe.Subscription.latest_invoice\",\n                \"djstripe.Invoice.default_payment_method\",\n                \"djstripe.Invoice.default_source\",\n                \"djstripe.Invoice.payment_intent\",\n                \"djstripe.Invoice.charge\",\n            },\n        )\n\n        # assert invoice_retrieve_mock was called like so:\n        invoice_retrieve_mock.assert_has_calls(\n            [\n                call(\n                    id=FAKE_INVOICE_METERED_SUBSCRIPTION[\"id\"],\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[\"discounts\"],\n                    stripe_account=None,\n                    stripe_version=\"2020-08-27\",\n                ),\n                call(\n                    id=\"in_16af5A2eZvKYlo2CJjANLL81\",\n                    api_key=djstripe_settings.STRIPE_SECRET_KEY,\n                    expand=[\"discounts\"],\n                    stripe_account=None,\n                    stripe_version=\"2020-08-27\",\n                ),\n            ]\n        )\n\n    @patch(\n        \"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_METERED), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.InvoiceItem.retrieve\",\n        return_value=deepcopy(FAKE_INVOICEITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE_METERED_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test___str__(\n        self,\n        invoice_retrieve_mock,\n        invoice_item_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n    ):\n        fake_usage_data = deepcopy(FAKE_USAGE_RECORD_SUMMARY)\n\n        usage_record_summary = UsageRecordSummary.sync_from_stripe_data(\n            fake_usage_data[\"data\"][1]\n        )\n\n        self.assertEqual(\n            str(usage_record_summary),\n            f\"Usage Summary for {str(usage_record_summary.subscription_item)} ({str(usage_record_summary.invoice)}) is {fake_usage_data['data'][1]['total_usage']}\",\n        )\n\n    @patch(\n        \"stripe.SubscriptionItem.list_usage_record_summaries\",\n        autospec=True,\n    )\n    @patch(\n        \"djstripe.models.billing.SubscriptionItem.objects.get\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n    )\n    @patch(\n        \"stripe.Plan.retrieve\", return_value=deepcopy(FAKE_PLAN_METERED), autospec=True\n    )\n    @patch(\n        \"stripe.Product.retrieve\", return_value=deepcopy(FAKE_PRODUCT), autospec=True\n    )\n    @patch(\n        \"stripe.Customer.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOMER_II),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.SubscriptionItem.retrieve\",\n        return_value=deepcopy(FAKE_SUBSCRIPTION_ITEM),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Subscription.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE_METERED_SUBSCRIPTION_USAGE),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Invoice.retrieve\",\n        return_value=deepcopy(FAKE_INVOICE_METERED_SUBSCRIPTION),\n        autospec=True,\n    )\n    def test_api_list(\n        self,\n        invoice_retrieve_mock,\n        subscription_retrieve_mock,\n        subscription_item_retrieve_mock,\n        customer_retrieve_mock,\n        product_retrieve_mock,\n        plan_retrieve_mock,\n        subcription_item_get_mock,\n        usage_record_list_mock,\n    ):\n        p = PropertyMock(return_value=deepcopy(FAKE_USAGE_RECORD_SUMMARY))\n        type(usage_record_list_mock).auto_paging_iter = p\n\n        fake_usage_data = deepcopy(FAKE_USAGE_RECORD_SUMMARY)\n\n        UsageRecordSummary.api_list(id=fake_usage_data[\"data\"][1][\"subscription_item\"])\n\n        # assert usage_record_list_mock was called as expected\n        usage_record_list_mock.assert_called_once_with(\n            id=fake_usage_data[\"data\"][1][\"subscription_item\"],\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=djstripe_settings.STRIPE_API_VERSION,\n        )\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "\"\"\"\ndj-stripe Utilities Tests.\n\"\"\"\nimport time\nfrom datetime import datetime\nfrom decimal import Decimal\nfrom unittest import skipIf\nfrom unittest.mock import patch\n\nfrom django.test import TestCase\nfrom django.test.utils import override_settings\n\nfrom djstripe.utils import (\n    convert_tstamp,\n    get_friendly_currency_amount,\n    get_supported_currency_choices,\n    get_timezone_utc,\n)\n\nTZ_IS_UTC = time.tzname == (\"UTC\", \"UTC\")\n\n\nclass TestTimestampConversion(TestCase):\n    def test_conversion(self):\n        stamp = convert_tstamp(1365567407)\n        self.assertEqual(\n            stamp, datetime(2013, 4, 10, 4, 16, 47, tzinfo=get_timezone_utc())\n        )\n\n    # NOTE: These next two tests will fail if your system clock is not in UTC\n    # Travis CI is, and coverage is good, so...\n\n    @skipIf(not TZ_IS_UTC, \"Skipped because timezone is not UTC.\")\n    @override_settings(USE_TZ=False)\n    def test_conversion_no_tz(self):\n        stamp = convert_tstamp(1365567407)\n        self.assertEqual(stamp, datetime(2013, 4, 10, 4, 16, 47))\n\n\nclass TestGetSupportedCurrencyChoices(TestCase):\n    @patch(\n        \"stripe.CountrySpec.retrieve\",\n        return_value={\"supported_payment_currencies\": [\"usd\", \"cad\", \"eur\"]},\n    )\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value={\"country\": \"US\"},\n        autospec=True,\n    )\n    def test_get_choices(\n        self, stripe_account_retrieve_mock, stripe_countryspec_retrieve_mock\n    ):\n        # Simple test to test sure that at least one currency choice tuple is returned.\n\n        currency_choices = get_supported_currency_choices(None)\n        stripe_account_retrieve_mock.assert_called_once_with(api_key=None)\n        stripe_countryspec_retrieve_mock.assert_called_once_with(\"US\", api_key=None)\n        self.assertGreaterEqual(\n            len(currency_choices), 1, \"Currency choices pull returned an empty list.\"\n        )\n        self.assertEqual(\n            tuple, type(currency_choices[0]), \"Currency choices are not tuples.\"\n        )\n        self.assertIn((\"usd\", \"USD\"), currency_choices, \"USD not in currency choices.\")\n\n\nclass TestUtils(TestCase):\n    def test_get_friendly_currency_amount(self):\n        self.assertEqual(\n            get_friendly_currency_amount(Decimal(\"1.001\"), \"usd\"), \"$1.00 USD\"\n        )\n        self.assertEqual(\n            get_friendly_currency_amount(Decimal(\"10\"), \"usd\"), \"$10.00 USD\"\n        )\n        self.assertEqual(\n            get_friendly_currency_amount(Decimal(\"10.50\"), \"usd\"), \"$10.50 USD\"\n        )\n        self.assertEqual(\n            get_friendly_currency_amount(Decimal(\"10.51\"), \"cad\"), \"$10.51 CAD\"\n        )\n        self.assertEqual(\n            get_friendly_currency_amount(Decimal(\"9.99\"), \"eur\"), \"€9.99 EUR\"\n        )\n"
  },
  {
    "path": "tests/test_views.py",
    "content": "\"\"\"\ndj-stripe Views Tests.\n\"\"\"\nfrom copy import deepcopy\n\nimport pytest\nimport stripe\nfrom django.apps import apps\nfrom django.contrib import messages\nfrom django.contrib.admin import helpers, site\nfrom django.contrib.messages.middleware import MessageMiddleware\nfrom django.contrib.sessions.middleware import SessionMiddleware\nfrom django.test.client import RequestFactory\nfrom django.urls import reverse\nfrom pytest_django.asserts import assertContains\n\nfrom djstripe import models, utils\nfrom djstripe.admin.views import ConfirmCustomAction\nfrom tests import (\n    FAKE_BALANCE_TRANSACTION,\n    FAKE_CARD_AS_PAYMENT_METHOD,\n    FAKE_CHARGE,\n    FAKE_CUSTOMER,\n    FAKE_INVOICE,\n    FAKE_INVOICEITEM,\n    FAKE_PAYMENT_INTENT_I,\n    FAKE_PLAN,\n    FAKE_PRODUCT,\n    FAKE_SUBSCRIPTION,\n    FAKE_SUBSCRIPTION_ITEM,\n    FAKE_SUBSCRIPTION_SCHEDULE,\n)\n\nfrom .fields.models import CustomActionModel\n\npytestmark = pytest.mark.django_db\n\n\nclass TestConfirmCustomActionView:\n    # the 4 models that do not inherit from StripeModel and hence\n    # do not inherit from StripeModelAdmin\n    ignore_models = [\n        \"WebhookEventTrigger\",\n        \"WebhookEndpoint\",\n        \"IdempotencyKey\",\n        \"APIKey\",\n    ]\n    kwargs_called_with = {}\n\n    # to get around Session/MessageMiddleware Deprecation Warnings\n    def dummy_get_response(self, request):\n        return None\n\n    @pytest.mark.parametrize(\n        \"action_name\",\n        [\n            \"_resync_instances\",\n            \"_sync_all_instances\",\n            \"_cancel\",\n            \"_release_subscription_schedule\",\n            \"_cancel_subscription_schedule\",\n        ],\n    )\n    def test_get_form_kwargs(self, action_name, admin_user, monkeypatch):\n        model = CustomActionModel\n\n        # monkeypatch utils.get_model\n        def mock_get_model(*args, **kwargs):\n            return model\n\n        monkeypatch.setattr(utils, \"get_model\", mock_get_model)\n\n        kwargs = {\n            \"action_name\": action_name,\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().get(change_url)\n        # add the admin user to the mocked request\n        request.user = admin_user\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        # Invoke the get_form_kwargs method\n        form_kwargs = view.get_form_kwargs()\n        assert form_kwargs.get(\"model_name\") == model.__name__.lower()\n        assert form_kwargs.get(\"action_name\") == action_name\n\n    @pytest.mark.parametrize(\n        \"action_name\",\n        [\n            \"_resync_instances\",\n            \"_sync_all_instances\",\n            \"_cancel\",\n            \"_release_subscription_schedule\",\n            \"_cancel_subscription_schedule\",\n        ],\n    )\n    @pytest.mark.parametrize(\"djstripe_owner_account_exists\", [False, True])\n    def test_form_valid(self, djstripe_owner_account_exists, action_name, monkeypatch):\n        model = CustomActionModel\n\n        # create instance to be used in the Django Admin Action\n        instance = model.objects.create(id=\"test\")\n\n        if djstripe_owner_account_exists:\n            account_instance = models.Account.objects.first()\n            instance.djstripe_owner_account = account_instance\n            instance.save()\n\n        data = {\n            \"action\": action_name,\n            helpers.ACTION_CHECKBOX_NAME: [instance.pk],\n        }\n\n        if action_name == \"_sync_all_instances\":\n            data[helpers.ACTION_CHECKBOX_NAME] = [\"_sync_all_instances\"]\n\n        # monkeypatch utils.get_model and\n        def mock_get_model(*args, **kwargs):\n            return model\n\n        monkeypatch.setattr(utils, \"get_model\", mock_get_model)\n\n        kwargs = {\n            \"action_name\": action_name,\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().post(change_url, data=data, follow=True)\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        # monkeypatch Request Handler\n        def mock_request_handler(*args, **kwargs):\n            return None\n\n        monkeypatch.setattr(view, action_name, mock_request_handler)\n\n        # get the form\n        form = view.get_form()\n\n        # Ensure form is valid\n        assert form.is_valid()\n\n        # Invoke form_valid()\n        response = view.form_valid(form)\n\n        # assert user redirected to the correct url\n        assert response.status_code == 302\n        assert response.url == \"/admin/fields/customactionmodel/\"\n\n    @pytest.mark.parametrize(\n        \"action_name\",\n        [\n            \"_resync_instances\",\n            \"_sync_all_instances\",\n            \"_cancel\",\n            \"_release_subscription_schedule\",\n            \"_cancel_subscription_schedule\",\n        ],\n    )\n    @pytest.mark.parametrize(\"djstripe_owner_account_exists\", [False, True])\n    def test_form_invalid(\n        self, djstripe_owner_account_exists, action_name, monkeypatch\n    ):\n        model = CustomActionModel\n\n        # create instance to be used in the Django Admin Action\n        instance = model.objects.create(id=\"test\")\n\n        if djstripe_owner_account_exists:\n            account_instance = models.Account.objects.first()\n            instance.djstripe_owner_account = account_instance\n            instance.save()\n\n        data = {\n            \"action\": action_name,\n        }\n\n        # monkeypatch utils.get_model and\n        def mock_get_model(*args, **kwargs):\n            return model\n\n        monkeypatch.setattr(utils, \"get_model\", mock_get_model)\n\n        kwargs = {\n            \"action_name\": action_name,\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().post(change_url, data=data, follow=True)\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        # get the form\n        form = view.get_form()\n\n        # Ensure form is not valid\n        assert not form.is_valid()\n\n        # Invoke form_invalid()\n        response = view.form_invalid(form)\n\n        # assert user got redirected to the action page with the error rendered\n        assertContains(\n            response,\n            '<ul class=\"messagelist\">\\n              <li class=\"error\">* This field is required.</li>\\n            </ul>',\n            html=True,\n        )\n\n    @pytest.mark.parametrize(\"fake_selected_pks\", [None, [1, 2]])\n    def test__sync_all_instances(self, fake_selected_pks, monkeypatch):\n        app_label = \"djstripe\"\n        app_config = apps.get_app_config(app_label)\n        all_models_lst = app_config.get_models()\n\n        for model in all_models_lst:\n            if model in site._registry.keys() and (\n                model.__name__ == \"WebhookEndpoint\"\n                or model.__name__ not in self.ignore_models\n            ):\n                # monkeypatch utils.get_model\n                def mock_get_model(*args, **kwargs):\n                    return model\n\n                monkeypatch.setattr(utils, \"get_model\", mock_get_model)\n\n                data = {\"action\": \"_sync_all_instances\"}\n\n                if fake_selected_pks is not None:\n                    data[helpers.ACTION_CHECKBOX_NAME] = fake_selected_pks\n\n                kwargs = {\n                    \"action_name\": \"_sync_all_instances\",\n                    \"model_name\": model.__name__.lower(),\n                }\n\n                # get the custom action POST url\n                change_url = reverse(\n                    \"admin:djstripe_custom_action\",\n                    kwargs=kwargs,\n                )\n\n                request = RequestFactory().post(change_url, data=data, follow=True)\n\n                # Add the session/message middleware to the request\n                SessionMiddleware(self.dummy_get_response).process_request(request)\n                MessageMiddleware(self.dummy_get_response).process_request(request)\n\n                view = ConfirmCustomAction()\n                view.setup(request, **kwargs)\n\n                # Invoke the Custom Actions\n                view._sync_all_instances(request, model.objects.none())\n\n                # assert correct Success messages are emitted\n                messages_sent_dictionary = {\n                    m.message: m.level_tag for m in messages.get_messages(request)\n                }\n\n                # assert correct success message was emitted\n                assert (\n                    messages_sent_dictionary.get(\"Successfully Synced All Instances\")\n                    == \"success\"\n                )\n\n    @pytest.mark.parametrize(\"djstripe_owner_account_exists\", [False, True])\n    def test__resync_instances(self, djstripe_owner_account_exists, monkeypatch):\n        model = CustomActionModel\n\n        # create instance to be used in the Django Admin Action\n        instance = model.objects.create(id=\"test\")\n\n        if djstripe_owner_account_exists:\n            account_instance = models.Account.objects.first()\n            instance.djstripe_owner_account = account_instance\n            instance.save()\n\n        data = {\n            \"action\": \"_resync_instances\",\n            helpers.ACTION_CHECKBOX_NAME: [instance.pk],\n        }\n\n        # monkeypatch instance.api_retrieve, instance.__class__.sync_from_stripe_data, and app_config.get_model\n        def mock_instance_api_retrieve(*args, **keywargs):\n            self.kwargs_called_with = keywargs\n\n        def mock_instance_sync_from_stripe_data(*args, **kwargs):\n            pass\n\n        def mock_get_model(*args, **kwargs):\n            return model\n\n        monkeypatch.setattr(model, \"api_retrieve\", mock_instance_api_retrieve)\n\n        monkeypatch.setattr(\n            model,\n            \"sync_from_stripe_data\",\n            mock_instance_sync_from_stripe_data,\n        )\n\n        monkeypatch.setattr(utils, \"get_model\", mock_get_model)\n\n        kwargs = {\n            \"action_name\": \"_resync_instances\",\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().post(change_url, data=data, follow=True)\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        # Invoke the Custom Actions\n        view._resync_instances(request, [instance])\n\n        # assert correct Success messages are emitted\n        messages_sent_dictionary = {\n            m.message: m.level_tag for m in messages.get_messages(request)\n        }\n\n        # assert correct success message was emitted\n        assert (\n            messages_sent_dictionary.get(f\"Successfully Synced: {instance}\")\n            == \"success\"\n        )\n\n        if djstripe_owner_account_exists:\n            # assert in case djstripe_owner_account exists that kwargs are not empty\n            assert self.kwargs_called_with == {\n                \"stripe_account\": instance.djstripe_owner_account.id,\n                \"api_key\": instance.default_api_key,\n            }\n        else:\n            # assert in case djstripe_owner_account does not exist that kwargs are empty\n            assert self.kwargs_called_with == {}\n\n    def test__resync_instances_stripe_permission_error(self, monkeypatch):\n        model = CustomActionModel\n\n        # create instance to be used in the Django Admin Action\n        instance = model.objects.create(id=\"test\")\n\n        data = {\n            \"action\": \"_resync_instances\",\n            helpers.ACTION_CHECKBOX_NAME: [instance.pk],\n        }\n\n        # monkeypatch instance.api_retrieve and app_config.get_model\n        def mock_instance_api_retrieve(*args, **kwargs):\n            raise stripe.error.PermissionError(\"some random error message\")\n\n        def mock_get_model(*args, **kwargs):\n            return model\n\n        monkeypatch.setattr(instance, \"api_retrieve\", mock_instance_api_retrieve)\n        monkeypatch.setattr(utils, \"get_model\", mock_get_model)\n\n        kwargs = {\n            \"action_name\": \"_resync_instances\",\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().post(change_url, data=data, follow=True)\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        # Invoke the Custom Actions\n        view._resync_instances(request, [instance])\n\n        # assert correct Success messages are emitted\n        messages_sent_dictionary = {\n            m.message.user_message: m.level_tag for m in messages.get_messages(request)\n        }\n\n        # assert correct success message was emitted\n        assert messages_sent_dictionary.get(\"some random error message\") == \"warning\"\n\n    def test__resync_instances_stripe_invalid_request_error(self, monkeypatch):\n        model = CustomActionModel\n\n        # create instance to be used in the Django Admin Action\n        instance = model.objects.create(id=\"test\")\n\n        data = {\n            \"action\": \"_resync_instances\",\n            helpers.ACTION_CHECKBOX_NAME: [instance.pk],\n        }\n\n        # monkeypatch instance.api_retrieve and app_config.get_model\n        def mock_instance_api_retrieve(*args, **kwargs):\n            raise stripe.error.InvalidRequestError({}, \"some random error message\")\n\n        def mock_get_model(*args, **kwargs):\n            return model\n\n        monkeypatch.setattr(instance, \"api_retrieve\", mock_instance_api_retrieve)\n        monkeypatch.setattr(utils, \"get_model\", mock_get_model)\n\n        kwargs = {\n            \"action_name\": \"_resync_instances\",\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().post(change_url, data=data, follow=True)\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        with pytest.raises(stripe.error.InvalidRequestError) as exc_info:\n            # Invoke the Custom Actions\n            view._resync_instances(request, [instance])\n\n        assert str(exc_info.value.param) == \"some random error message\"\n\n    def test__cancel_subscription_instances(\n        self,\n        monkeypatch,\n    ):\n        def mock_invoice_get(*args, **kwargs):\n            return FAKE_INVOICE\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_customer_get(*args, **kwargs):\n            return FAKE_CUSTOMER\n\n        def mock_charge_get(*args, **kwargs):\n            return FAKE_CHARGE\n\n        def mock_payment_method_get(*args, **kwargs):\n            return FAKE_CARD_AS_PAYMENT_METHOD\n\n        def mock_payment_intent_get(*args, **kwargs):\n            return FAKE_PAYMENT_INTENT_I\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_subscription_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION\n\n        def mock_balance_transaction_get(*args, **kwargs):\n            return FAKE_BALANCE_TRANSACTION\n\n        def mock_product_get(*args, **kwargs):\n            return FAKE_PRODUCT\n\n        def mock_plan_get(*args, **kwargs):\n            return FAKE_PLAN\n\n        # monkeypatch stripe retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        # si_HXZCDv9ixoUB5u\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n        monkeypatch.setattr(stripe.Plan, \"retrieve\", mock_plan_get)\n\n        model = models.Subscription\n\n        # Create Latest Invoice\n        models.Invoice.sync_from_stripe_data(FAKE_INVOICE)\n\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        instance = model.sync_from_stripe_data(subscription_fake)\n\n        # monkeypatch subscription.cancel()\n        def mock_subscription_cancel(*args, **keywargs):\n            return instance\n\n        monkeypatch.setattr(instance, \"cancel\", mock_subscription_cancel)\n\n        data = {\"action\": \"_cancel\", helpers.ACTION_CHECKBOX_NAME: [instance.pk]}\n\n        kwargs = {\n            \"action_name\": \"_cancel\",\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().post(change_url, data=data, follow=True)\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        # Invoke the Custom Actions\n        view._cancel(request, [instance])\n\n        # assert correct Success messages are emitted\n        messages_sent_dictionary = {\n            m.message: m.level_tag for m in messages.get_messages(request)\n        }\n\n        # assert correct success message was emitted\n        assert (\n            messages_sent_dictionary.get(f\"Successfully Canceled: {instance}\")\n            == \"success\"\n        )\n\n    def test__cancel_subscription_instances_stripe_invalid_request_error(\n        self,\n        monkeypatch,\n    ):\n        def mock_invoice_get(*args, **kwargs):\n            return FAKE_INVOICE\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_customer_get(*args, **kwargs):\n            return FAKE_CUSTOMER\n\n        def mock_charge_get(*args, **kwargs):\n            return FAKE_CHARGE\n\n        def mock_payment_method_get(*args, **kwargs):\n            return FAKE_CARD_AS_PAYMENT_METHOD\n\n        def mock_payment_intent_get(*args, **kwargs):\n            return FAKE_PAYMENT_INTENT_I\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_subscription_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION\n\n        def mock_balance_transaction_get(*args, **kwargs):\n            return FAKE_BALANCE_TRANSACTION\n\n        def mock_product_get(*args, **kwargs):\n            return FAKE_PRODUCT\n\n        def mock_plan_get(*args, **kwargs):\n            return FAKE_PLAN\n\n        # monkeypatch stripe retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n        monkeypatch.setattr(stripe.Plan, \"retrieve\", mock_plan_get)\n\n        model = models.Subscription\n\n        # Create Latest Invoice\n        models.Invoice.sync_from_stripe_data(FAKE_INVOICE)\n\n        subscription_fake = deepcopy(FAKE_SUBSCRIPTION)\n        instance = model.sync_from_stripe_data(subscription_fake)\n\n        # monkeypatch subscription.cancel()\n        def mock_subscription_cancel(*args, **keywargs):\n            raise stripe.error.InvalidRequestError({}, \"some random error message\")\n\n        monkeypatch.setattr(instance, \"cancel\", mock_subscription_cancel)\n\n        data = {\"action\": \"_cancel\", helpers.ACTION_CHECKBOX_NAME: [instance.pk]}\n\n        kwargs = {\n            \"action_name\": \"_cancel\",\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().post(change_url, data=data, follow=True)\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        with pytest.warns(None, match=r\"some random error message\"):\n            # Invoke the Custom Actions\n            view._cancel(request, [instance])\n\n    def test__release_subscription_schedule(\n        self,\n        monkeypatch,\n    ):\n        def mock_balance_transaction_get(*args, **kwargs):\n            return FAKE_BALANCE_TRANSACTION\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_subscription_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION\n\n        def mock_charge_get(*args, **kwargs):\n            return FAKE_CHARGE\n\n        def mock_payment_method_get(*args, **kwargs):\n            return FAKE_CARD_AS_PAYMENT_METHOD\n\n        def mock_payment_intent_get(*args, **kwargs):\n            return FAKE_PAYMENT_INTENT_I\n\n        def mock_product_get(*args, **kwargs):\n            return FAKE_PRODUCT\n\n        def mock_invoice_get(*args, **kwargs):\n            return FAKE_INVOICE\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_customer_get(*args, **kwargs):\n            return FAKE_CUSTOMER\n\n        def mock_plan_get(*args, **kwargs):\n            return FAKE_PLAN\n\n        # monkeypatch stripe retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n\n        monkeypatch.setattr(stripe.Plan, \"retrieve\", mock_plan_get)\n\n        # create latest invoice\n        models.Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        model = models.SubscriptionSchedule\n        subscription_schedule_fake = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        instance = model.sync_from_stripe_data(subscription_schedule_fake)\n\n        # monkeypatch subscription_schedule.release()\n        def mock_subscription_schedule_release(*args, **keywargs):\n            return instance\n\n        monkeypatch.setattr(instance, \"release\", mock_subscription_schedule_release)\n\n        data = {\n            \"action\": \"_release_subscription_schedule\",\n            helpers.ACTION_CHECKBOX_NAME: [instance.pk],\n        }\n\n        kwargs = {\n            \"action_name\": \"_release_subscription_schedule\",\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().post(change_url, data=data, follow=True)\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        # Invoke the Custom Actions\n        view._release_subscription_schedule(request, [instance])\n\n        # assert correct Success messages are emitted\n        messages_sent_dictionary = {\n            m.message: m.level_tag for m in messages.get_messages(request)\n        }\n\n        # assert correct success message was emitted\n        assert (\n            messages_sent_dictionary.get(f\"Successfully Released: {instance}\")\n            == \"success\"\n        )\n\n    def test__cancel_subscription_schedule(\n        self,\n        monkeypatch,\n    ):\n        def mock_balance_transaction_get(*args, **kwargs):\n            return FAKE_BALANCE_TRANSACTION\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_subscription_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION\n\n        def mock_charge_get(*args, **kwargs):\n            return FAKE_CHARGE\n\n        def mock_payment_method_get(*args, **kwargs):\n            return FAKE_CARD_AS_PAYMENT_METHOD\n\n        def mock_payment_intent_get(*args, **kwargs):\n            return FAKE_PAYMENT_INTENT_I\n\n        def mock_product_get(*args, **kwargs):\n            return FAKE_PRODUCT\n\n        def mock_invoice_get(*args, **kwargs):\n            return FAKE_INVOICE\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_customer_get(*args, **kwargs):\n            return FAKE_CUSTOMER\n\n        def mock_plan_get(*args, **kwargs):\n            return FAKE_PLAN\n\n        # monkeypatch stripe retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n\n        monkeypatch.setattr(stripe.Plan, \"retrieve\", mock_plan_get)\n\n        # create latest invoice\n        models.Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        model = models.SubscriptionSchedule\n        subscription_schedule_fake = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        instance = model.sync_from_stripe_data(subscription_schedule_fake)\n\n        # monkeypatch subscription_schedule.cancel()\n        def mock_subscription_schedule_cancel(*args, **keywargs):\n            return instance\n\n        monkeypatch.setattr(instance, \"cancel\", mock_subscription_schedule_cancel)\n\n        data = {\n            \"action\": \"_cancel_subscription_schedule\",\n            helpers.ACTION_CHECKBOX_NAME: [instance.pk],\n        }\n\n        kwargs = {\n            \"action_name\": \"_cancel_subscription_schedule\",\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().post(change_url, data=data, follow=True)\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        # Invoke the Custom Actions\n        view._cancel_subscription_schedule(request, [instance])\n\n        # assert correct Success messages are emitted\n        messages_sent_dictionary = {\n            m.message: m.level_tag for m in messages.get_messages(request)\n        }\n\n        # assert correct success message was emitted\n        assert (\n            messages_sent_dictionary.get(f\"Successfully Canceled: {instance}\")\n            == \"success\"\n        )\n\n    def test__release_subscription_schedule_stripe_invalid_request_error(\n        self,\n        monkeypatch,\n    ):\n        def mock_balance_transaction_get(*args, **kwargs):\n            return FAKE_BALANCE_TRANSACTION\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_subscription_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION\n\n        def mock_charge_get(*args, **kwargs):\n            return FAKE_CHARGE\n\n        def mock_payment_method_get(*args, **kwargs):\n            return FAKE_CARD_AS_PAYMENT_METHOD\n\n        def mock_payment_intent_get(*args, **kwargs):\n            return FAKE_PAYMENT_INTENT_I\n\n        def mock_product_get(*args, **kwargs):\n            return FAKE_PRODUCT\n\n        def mock_invoice_get(*args, **kwargs):\n            return FAKE_INVOICE\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_customer_get(*args, **kwargs):\n            return FAKE_CUSTOMER\n\n        def mock_plan_get(*args, **kwargs):\n            return FAKE_PLAN\n\n        # monkeypatch stripe retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n\n        monkeypatch.setattr(stripe.Plan, \"retrieve\", mock_plan_get)\n\n        # create latest invoice\n        models.Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        model = models.SubscriptionSchedule\n        subscription_schedule_fake = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        instance = model.sync_from_stripe_data(subscription_schedule_fake)\n\n        # monkeypatch subscription_schedule.release()\n        def mock_subscription_schedule_release(*args, **keywargs):\n            raise stripe.error.InvalidRequestError({}, \"some random error message\")\n\n        monkeypatch.setattr(instance, \"release\", mock_subscription_schedule_release)\n\n        data = {\n            \"action\": \"_release_subscription_schedule\",\n            helpers.ACTION_CHECKBOX_NAME: [instance.pk],\n        }\n\n        kwargs = {\n            \"action_name\": \"_release_subscription_schedule\",\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().post(change_url, data=data, follow=True)\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        with pytest.warns(None, match=r\"some random error message\"):\n            # Invoke the Custom Actions\n            view._release_subscription_schedule(request, [instance])\n\n    def test__cancel_subscription_schedule_stripe_invalid_request_error(\n        self,\n        monkeypatch,\n    ):\n        def mock_balance_transaction_get(*args, **kwargs):\n            return FAKE_BALANCE_TRANSACTION\n\n        def mock_subscriptionitem_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION_ITEM\n\n        def mock_subscription_get(*args, **kwargs):\n            return FAKE_SUBSCRIPTION\n\n        def mock_charge_get(*args, **kwargs):\n            return FAKE_CHARGE\n\n        def mock_payment_method_get(*args, **kwargs):\n            return FAKE_CARD_AS_PAYMENT_METHOD\n\n        def mock_payment_intent_get(*args, **kwargs):\n            return FAKE_PAYMENT_INTENT_I\n\n        def mock_product_get(*args, **kwargs):\n            return FAKE_PRODUCT\n\n        def mock_invoice_get(*args, **kwargs):\n            return FAKE_INVOICE\n\n        def mock_invoice_item_get(*args, **kwargs):\n            return FAKE_INVOICEITEM\n\n        def mock_customer_get(*args, **kwargs):\n            return FAKE_CUSTOMER\n\n        def mock_plan_get(*args, **kwargs):\n            return FAKE_PLAN\n\n        # monkeypatch stripe retrieve calls to return\n        # the desired json response.\n        monkeypatch.setattr(\n            stripe.BalanceTransaction, \"retrieve\", mock_balance_transaction_get\n        )\n        monkeypatch.setattr(stripe.Subscription, \"retrieve\", mock_subscription_get)\n        monkeypatch.setattr(\n            stripe.SubscriptionItem, \"retrieve\", mock_subscriptionitem_get\n        )\n        monkeypatch.setattr(stripe.Charge, \"retrieve\", mock_charge_get)\n\n        monkeypatch.setattr(stripe.PaymentMethod, \"retrieve\", mock_payment_method_get)\n        monkeypatch.setattr(stripe.PaymentIntent, \"retrieve\", mock_payment_intent_get)\n        monkeypatch.setattr(stripe.Product, \"retrieve\", mock_product_get)\n\n        monkeypatch.setattr(stripe.Invoice, \"retrieve\", mock_invoice_get)\n        monkeypatch.setattr(stripe.InvoiceItem, \"retrieve\", mock_invoice_item_get)\n\n        monkeypatch.setattr(stripe.Customer, \"retrieve\", mock_customer_get)\n\n        monkeypatch.setattr(stripe.Plan, \"retrieve\", mock_plan_get)\n\n        # create latest invoice\n        models.Invoice.sync_from_stripe_data(deepcopy(FAKE_INVOICE))\n\n        model = models.SubscriptionSchedule\n        subscription_schedule_fake = deepcopy(FAKE_SUBSCRIPTION_SCHEDULE)\n        instance = model.sync_from_stripe_data(subscription_schedule_fake)\n\n        # monkeypatch subscription_schedule.cancel()\n        def mock_subscription_schedule_cancel(*args, **keywargs):\n            raise stripe.error.InvalidRequestError({}, \"some random error message\")\n\n        monkeypatch.setattr(instance, \"cancel\", mock_subscription_schedule_cancel)\n\n        data = {\n            \"action\": \"_cancel_subscription_schedule\",\n            helpers.ACTION_CHECKBOX_NAME: [instance.pk],\n        }\n\n        kwargs = {\n            \"action_name\": \"_cancel_subscription_schedule\",\n            \"model_name\": model.__name__.lower(),\n        }\n\n        # get the custom action POST url\n        change_url = reverse(\"admin:djstripe_custom_action\", kwargs=kwargs)\n\n        request = RequestFactory().post(change_url, data=data, follow=True)\n\n        # Add the session/message middleware to the request\n        SessionMiddleware(self.dummy_get_response).process_request(request)\n        MessageMiddleware(self.dummy_get_response).process_request(request)\n\n        view = ConfirmCustomAction()\n        view.setup(request, **kwargs)\n\n        with pytest.warns(None, match=r\"some random error message\"):\n            # Invoke the Custom Actions\n            view._cancel_subscription_schedule(request, [instance])\n"
  },
  {
    "path": "tests/test_webhooks.py",
    "content": "\"\"\"\ndj-stripe Webhook Tests.\n\"\"\"\nimport json\nimport warnings\nfrom collections import defaultdict\nfrom copy import deepcopy\nfrom unittest.mock import Mock, PropertyMock, call, patch\nfrom uuid import UUID\n\nimport pytest\nfrom django.http.request import HttpHeaders\nfrom django.test import TestCase, override_settings\nfrom django.test.client import Client\nfrom django.urls import reverse\n\nfrom djstripe import webhooks\nfrom djstripe.models import Event, Transfer, WebhookEventTrigger\nfrom djstripe.models.webhooks import WebhookEndpoint, get_remote_ip\nfrom djstripe.settings import djstripe_settings\nfrom djstripe.webhooks import TEST_EVENT_ID, call_handlers, handler, handler_all\n\nfrom . import (\n    FAKE_CUSTOM_ACCOUNT,\n    FAKE_EVENT_TEST_CHARGE_SUCCEEDED,\n    FAKE_EVENT_TRANSFER_CREATED,\n    FAKE_STANDARD_ACCOUNT,\n    FAKE_TRANSFER,\n    FAKE_WEBHOOK_ENDPOINT_1,\n)\n\npytestmark = pytest.mark.django_db\n\n\ndef mock_webhook_handler(webhook_event_trigger):\n    webhook_event_trigger.process()\n\n\nclass TestWebhookEventTrigger(TestCase):\n    \"\"\"Test class to test WebhookEventTrigger model and its methods\"\"\"\n\n    def _send_event(self, event_data):\n        return Client().post(\n            reverse(\"djstripe:webhook\"),\n            json.dumps(event_data),\n            content_type=\"application/json\",\n            HTTP_STRIPE_SIGNATURE=\"PLACEHOLDER\",\n        )\n\n    def test_webhook_test_event(self):\n        self.assertEqual(WebhookEventTrigger.objects.count(), 0)\n        resp = self._send_event(FAKE_EVENT_TEST_CHARGE_SUCCEEDED)\n        self.assertEqual(resp.status_code, 200)\n        self.assertFalse(Event.objects.filter(id=TEST_EVENT_ID).exists())\n        self.assertEqual(WebhookEventTrigger.objects.count(), 1)\n        event_trigger = WebhookEventTrigger.objects.first()\n        self.assertTrue(event_trigger.is_test_event)\n\n    def test___str__(self):\n        self.assertEqual(WebhookEventTrigger.objects.count(), 0)\n        resp = self._send_event(FAKE_EVENT_TEST_CHARGE_SUCCEEDED)\n        self.assertEqual(resp.status_code, 200)\n        self.assertEqual(WebhookEventTrigger.objects.count(), 1)\n        webhookeventtrigger = WebhookEventTrigger.objects.first()\n\n        self.assertEqual(\n            f\"id={webhookeventtrigger.id}, valid={webhookeventtrigger.valid}, processed={webhookeventtrigger.processed}\",\n            str(webhookeventtrigger),\n        )\n\n    @override_settings(DJSTRIPE_WEBHOOK_VALIDATION=\"retrieve_event\")\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_TRANSFER_CREATED),\n        autospec=True,\n    )\n    def test_webhook_retrieve_event_fail(\n        self,\n        event_retrieve_mock,\n        transfer_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        invalid_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n        invalid_event[\"id\"] = \"evt_invalid\"\n        invalid_event[\"data\"][\"valid\"] = \"not really\"\n\n        resp = self._send_event(invalid_event)\n\n        self.assertEqual(resp.status_code, 400)\n        self.assertFalse(Event.objects.filter(id=\"evt_invalid\").exists())\n\n    @patch.object(\n        WebhookEventTrigger.validate,\n        \"__defaults__\",\n        (None, \"whsec_XXXXX\", 300, \"retrieve_event\"),\n    )\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_STANDARD_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_TRANSFER_CREATED),\n        autospec=True,\n    )\n    def test_webhook_retrieve_event_pass(\n        self,\n        event_retrieve_mock,\n        transfer_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        resp = self._send_event(FAKE_EVENT_TRANSFER_CREATED)\n\n        self.assertEqual(resp.status_code, 200)\n        event_retrieve_mock.assert_called_once_with(\n            api_key=djstripe_settings.STRIPE_SECRET_KEY,\n            stripe_version=FAKE_EVENT_TRANSFER_CREATED[\"api_version\"],\n            id=FAKE_EVENT_TRANSFER_CREATED[\"id\"],\n        )\n\n    @override_settings(\n        DJSTRIPE_WEBHOOK_VALIDATION=\"verify_signature\",\n        DJSTRIPE_WEBHOOK_SECRET=\"whsec_XXXXX\",\n    )\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_TRANSFER_CREATED),\n        autospec=True,\n    )\n    def test_webhook_invalid_verify_signature_fail(\n        self,\n        event_retrieve_mock,\n        transfer_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        invalid_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n        invalid_event[\"id\"] = \"evt_invalid\"\n        invalid_event[\"data\"][\"valid\"] = \"not really\"\n\n        resp = self._send_event(invalid_event)\n\n        self.assertEqual(resp.status_code, 400)\n        self.assertFalse(Event.objects.filter(id=\"evt_invalid\").exists())\n\n    @override_settings(\n        DJSTRIPE_WEBHOOK_VALIDATION=\"verify_signature\",\n        DJSTRIPE_WEBHOOK_SECRET=\"whsec_XXXXX\",\n    )\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.WebhookSignature.verify_header\",\n        return_value=True,\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_STANDARD_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_TRANSFER_CREATED),\n        autospec=True,\n    )\n    def test_webhook_verify_signature_pass(\n        self,\n        event_retrieve_mock,\n        transfer_retrieve_mock,\n        account_retrieve_mock,\n        verify_header_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        resp = self._send_event(FAKE_EVENT_TRANSFER_CREATED)\n\n        self.assertEqual(resp.status_code, 200)\n        self.assertFalse(Event.objects.filter(id=\"evt_invalid\").exists())\n        verify_header_mock.assert_called_once_with(\n            json.dumps(FAKE_EVENT_TRANSFER_CREATED),\n            \"PLACEHOLDER\",\n            djstripe_settings.WEBHOOK_SECRET,\n            djstripe_settings.WEBHOOK_TOLERANCE,\n        )\n        event_retrieve_mock.assert_not_called()\n\n    @patch.object(\n        WebhookEventTrigger.validate, \"__defaults__\", (None, \"whsec_XXXXX\", 300, None)\n    )\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\"stripe.WebhookSignature.verify_header\", autospec=True)\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_STANDARD_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    @patch(\n        \"stripe.Event.retrieve\",\n        return_value=deepcopy(FAKE_EVENT_TRANSFER_CREATED),\n        autospec=True,\n    )\n    def test_webhook_no_validation_pass(\n        self,\n        event_retrieve_mock,\n        transfer_retrieve_mock,\n        account_retrieve_mock,\n        verify_header_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        invalid_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n        invalid_event[\"id\"] = \"evt_invalid\"\n        invalid_event[\"data\"][\"valid\"] = \"not really\"\n\n        # ensure warning is raised\n        with pytest.warns(None, match=r\"WEBHOOK VALIDATION is disabled.\"):\n            resp = self._send_event(invalid_event)\n\n        self.assertEqual(resp.status_code, 200)\n        self.assertTrue(Event.objects.filter(id=\"evt_invalid\").exists())\n        event_retrieve_mock.assert_not_called()\n        verify_header_mock.assert_not_called()\n\n    def test_webhook_no_signature(self):\n        self.assertEqual(WebhookEventTrigger.objects.count(), 0)\n        resp = Client().post(\n            reverse(\"djstripe:webhook\"), \"{}\", content_type=\"application/json\"\n        )\n        self.assertEqual(resp.status_code, 400)\n        self.assertEqual(WebhookEventTrigger.objects.count(), 0)\n\n    def test_webhook_remote_addr_is_none(self):\n        self.assertEqual(WebhookEventTrigger.objects.count(), 0)\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"ignore\")\n            Client().post(\n                reverse(\"djstripe:webhook\"),\n                \"{}\",\n                content_type=\"application/json\",\n                HTTP_STRIPE_SIGNATURE=\"PLACEHOLDER\",\n                REMOTE_ADDR=None,\n            )\n\n        self.assertEqual(WebhookEventTrigger.objects.count(), 1)\n        event_trigger = WebhookEventTrigger.objects.first()\n        self.assertEqual(event_trigger.remote_ip, \"0.0.0.0\")\n\n    def test_webhook_remote_addr_is_empty_string(self):\n        self.assertEqual(WebhookEventTrigger.objects.count(), 0)\n        with warnings.catch_warnings():\n            warnings.simplefilter(\"ignore\")\n            Client().post(\n                reverse(\"djstripe:webhook\"),\n                \"{}\",\n                content_type=\"application/json\",\n                HTTP_STRIPE_SIGNATURE=\"PLACEHOLDER\",\n                REMOTE_ADDR=\"\",\n            )\n\n        self.assertEqual(WebhookEventTrigger.objects.count(), 1)\n        event_trigger = WebhookEventTrigger.objects.first()\n        self.assertEqual(event_trigger.remote_ip, \"0.0.0.0\")\n\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"djstripe.models.WebhookEventTrigger.validate\", return_value=True, autospec=True\n    )\n    @patch(\"djstripe.models.WebhookEventTrigger.process\", autospec=True)\n    def test_webhook_reraise_exception(\n        self,\n        webhook_event_process_mock,\n        webhook_event_validate_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        class ProcessException(Exception):\n            pass\n\n        exception_message = \"process fail\"\n\n        webhook_event_process_mock.side_effect = ProcessException(exception_message)\n\n        self.assertEqual(WebhookEventTrigger.objects.count(), 0)\n\n        fake_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n\n        with self.assertRaisesMessage(ProcessException, exception_message):\n            self._send_event(fake_event)\n\n        self.assertEqual(WebhookEventTrigger.objects.count(), 1)\n        event_trigger = WebhookEventTrigger.objects.first()\n        self.assertEqual(event_trigger.exception, exception_message)\n\n    @patch.object(\n        WebhookEventTrigger.validate, \"__defaults__\", (None, \"whsec_XXXXX\", 300, None)\n    )\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch.object(\n        djstripe_settings, \"WEBHOOK_EVENT_CALLBACK\", return_value=mock_webhook_handler\n    )\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_STANDARD_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_webhook_with_custom_callback(\n        self,\n        event_retrieve_mock,\n        transfer_retrieve_mock,\n        account_retrieve_mock,\n        webhook_event_callback_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        fake_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n        event_retrieve_mock.return_value = fake_event\n        with pytest.warns(None):\n            resp = self._send_event(fake_event)\n        self.assertEqual(resp.status_code, 200)\n        webhook_event_trigger = WebhookEventTrigger.objects.get()\n        webhook_event_callback_mock.called_once_with(webhook_event_trigger)\n\n    @patch.object(\n        WebhookEventTrigger.validate, \"__defaults__\", (None, \"whsec_XXXXX\", 300, None)\n    )\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_STANDARD_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_webhook_with_transfer_event_duplicate(\n        self,\n        event_retrieve_mock,\n        transfer_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        fake_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n        event_retrieve_mock.return_value = fake_event\n        with pytest.warns(None):\n            resp = self._send_event(fake_event)\n\n        self.assertEqual(resp.status_code, 200)\n        self.assertTrue(Event.objects.filter(type=\"transfer.created\").exists())\n        self.assertEqual(1, Event.objects.filter(type=\"transfer.created\").count())\n\n        # Duplication\n        with pytest.warns(None):\n            resp = self._send_event(fake_event)\n        self.assertEqual(resp.status_code, 200)\n        self.assertEqual(1, Event.objects.filter(type=\"transfer.created\").count())\n\n    @patch.object(\n        WebhookEventTrigger.validate, \"__defaults__\", (None, \"whsec_XXXXX\", 300, None)\n    )\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_STANDARD_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_webhook_good_platform_account(\n        self,\n        event_retrieve_mock,\n        transfer_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        fake_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n        event_retrieve_mock.return_value = fake_event\n        with pytest.warns(None):\n            resp = self._send_event(fake_event)\n\n        self.assertEqual(resp.status_code, 200)\n        self.assertEqual(Event.objects.count(), 1)\n        self.assertEqual(WebhookEventTrigger.objects.count(), 1)\n\n        event_trigger = WebhookEventTrigger.objects.first()\n        self.assertEqual(event_trigger.is_test_event, False)\n        self.assertEqual(\n            event_trigger.stripe_trigger_account.id, FAKE_STANDARD_ACCOUNT[\"id\"]\n        )\n\n    @patch.object(\n        WebhookEventTrigger.validate, \"__defaults__\", (None, \"whsec_XXXXX\", 300, None)\n    )\n    @patch.object(Transfer, \"_attach_objects_post_save_hook\")\n    @patch(\n        \"stripe.Account.retrieve\",\n        return_value=deepcopy(FAKE_CUSTOM_ACCOUNT),\n        autospec=True,\n    )\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_webhook_good_connect_account(\n        self,\n        event_retrieve_mock,\n        transfer_retrieve_mock,\n        account_retrieve_mock,\n        transfer__attach_object_post_save_hook_mock,\n    ):\n        fake_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n        fake_event[\"account\"] = FAKE_CUSTOM_ACCOUNT[\"id\"]\n        event_retrieve_mock.return_value = fake_event\n        with pytest.warns(None):\n            resp = self._send_event(fake_event)\n\n        self.assertEqual(resp.status_code, 200)\n        self.assertEqual(Event.objects.count(), 1)\n        self.assertEqual(WebhookEventTrigger.objects.count(), 1)\n\n        event_trigger = WebhookEventTrigger.objects.first()\n        self.assertEqual(event_trigger.is_test_event, False)\n        self.assertEqual(\n            event_trigger.stripe_trigger_account.id, FAKE_CUSTOM_ACCOUNT[\"id\"]\n        )\n\n    @patch.object(\n        WebhookEventTrigger.validate, \"__defaults__\", (None, \"whsec_XXXXX\", 300, None)\n    )\n    @patch.object(target=Event, attribute=\"invoke_webhook_handlers\", autospec=True)\n    @patch(\n        \"stripe.Transfer.retrieve\", return_value=deepcopy(FAKE_TRANSFER), autospec=True\n    )\n    @patch(\"stripe.Event.retrieve\", autospec=True)\n    def test_webhook_error(\n        self,\n        event_retrieve_mock,\n        transfer_retrieve_mock,\n        mock_invoke_webhook_handlers,\n    ):\n        \"\"\"Test the case where webhook processing fails to ensure we rollback\n        and do not commit the Event object to the database.\n        \"\"\"\n        mock_invoke_webhook_handlers.side_effect = KeyError(\"Test error\")\n\n        fake_event = deepcopy(FAKE_EVENT_TRANSFER_CREATED)\n        event_retrieve_mock.return_value = fake_event\n        with self.assertRaises(KeyError):\n            with pytest.warns(None):\n                self._send_event(fake_event)\n\n        self.assertEqual(Event.objects.count(), 0)\n        self.assertEqual(WebhookEventTrigger.objects.count(), 1)\n\n        event_trigger = WebhookEventTrigger.objects.first()\n        self.assertEqual(event_trigger.is_test_event, False)\n        self.assertEqual(event_trigger.exception, \"'Test error'\")\n\n\nclass TestWebhookHandlers(TestCase):\n    def setUp(self):\n        # Reset state of registrations per test\n        patcher = patch.object(\n            webhooks, \"registrations\", new_callable=(lambda: defaultdict(list))\n        )\n        self.addCleanup(patcher.stop)\n        self.registrations = patcher.start()\n\n        patcher = patch.object(webhooks, \"registrations_global\", new_callable=list)\n        self.addCleanup(patcher.stop)\n        self.registrations_global = patcher.start()\n\n    def test_global_handler_registration(self):\n        func_mock = Mock()\n        handler_all()(func_mock)\n        event = self._call_handlers(\"wib.ble\", {\"data\": \"foo\"})  # handled\n        self.assertEqual(1, func_mock.call_count)\n        func_mock.assert_called_with(event=event)\n\n    def test_event_handler_registration(self):\n        global_func_mock = Mock()\n        handler_all()(global_func_mock)\n        func_mock = Mock()\n        handler(\"foo\")(func_mock)\n        event = self._call_handlers(\"foo.bar\", {\"data\": \"foo\"})  # handled\n        self._call_handlers(\"bar.foo\", {\"data\": \"foo\"})  # not handled\n        self.assertEqual(2, global_func_mock.call_count)  # called each time\n        self.assertEqual(1, func_mock.call_count)\n        func_mock.assert_called_with(event=event)\n\n    def test_event_subtype_handler_registration(self):\n        global_func_mock = Mock()\n        handler_all()(global_func_mock)\n        func_mock = Mock()\n        handler(\"foo.bar\")(func_mock)\n        event1 = self._call_handlers(\"foo.bar\", {\"data\": \"foo\"})  # handled\n        event2 = self._call_handlers(\"foo.bar.wib\", {\"data\": \"foo\"})  # handled\n        self._call_handlers(\"foo.baz\", {\"data\": \"foo\"})  # not handled\n        self.assertEqual(3, global_func_mock.call_count)  # called each time\n        self.assertEqual(2, func_mock.call_count)\n        func_mock.assert_has_calls([call(event=event1), call(event=event2)])\n\n    def test_global_handler_registration_with_function(self):\n        func_mock = Mock()\n        handler_all(func_mock)\n        event = self._call_handlers(\"wib.ble\", {\"data\": \"foo\"})  # handled\n        self.assertEqual(1, func_mock.call_count)\n        func_mock.assert_called_with(event=event)\n\n    def test_event_handle_registation_with_string(self):\n        func_mock = Mock()\n        handler(\"foo\")(func_mock)\n        event = self._call_handlers(\"foo.bar\", {\"data\": \"foo\"})  # handled\n        self.assertEqual(1, func_mock.call_count)\n        func_mock.assert_called_with(event=event)\n\n    def test_event_handle_registation_with_list_of_strings(self):\n        func_mock = Mock()\n        handler(\"foo\", \"bar\")(func_mock)\n        event1 = self._call_handlers(\"foo.bar\", {\"data\": \"foo\"})  # handled\n        event2 = self._call_handlers(\"bar.foo\", {\"data\": \"bar\"})  # handled\n        self.assertEqual(2, func_mock.call_count)\n        func_mock.assert_has_calls([call(event=event1), call(event=event2)])\n\n    def test_webhook_event_trigger_invalid_body(self):\n        trigger = WebhookEventTrigger(remote_ip=\"127.0.0.1\", body=\"invalid json\")\n        assert not trigger.json_body\n\n    #\n    # Helpers\n    #\n\n    @staticmethod\n    def _call_handlers(event_spec, data):\n        event = Mock(spec=Event)\n        parts = event_spec.split(\".\")\n        category = parts[0]\n        verb = \".\".join(parts[1:])\n        type(event).parts = PropertyMock(return_value=parts)\n        type(event).category = PropertyMock(return_value=category)\n        type(event).verb = PropertyMock(return_value=verb)\n        call_handlers(event=event)\n        return event\n\n\nclass TestGetRemoteIp:\n    class RequestClass:\n        def __init__(self, data):\n            self.data = data\n\n        @property\n        def headers(self):\n            return HttpHeaders(self.META)\n\n        @property\n        def META(self):\n            return self.data\n\n    @pytest.mark.parametrize(\n        \"data\",\n        [\n            {\"HTTP_X_FORWARDED_FOR\": \"127.0.0.1,345.5.5.3,451.1.1.2\"},\n            {\n                \"REMOTE_ADDR\": \"422.0.0.1\",\n                \"HTTP_X_FORWARDED_FOR\": \"127.0.0.1,345.5.5.3,451.1.1.2\",\n            },\n            {\n                \"REMOTE_ADDR\": \"127.0.0.1\",\n            },\n        ],\n    )\n    def test_get_remote_ip(self, data):\n        request = self.RequestClass(data)\n        assert get_remote_ip(request) == \"127.0.0.1\"\n\n    @pytest.mark.parametrize(\n        \"data\",\n        [\n            {\n                \"REMOTE_ADDR\": \"\",\n            },\n            {\n                \"pqwwe\": \"127.0.0.1\",\n            },\n        ],\n    )\n    def test_get_remote_ip_remote_addr_is_none(self, data):\n        request = self.RequestClass(data)\n\n        # ensure warning is raised\n        with pytest.warns(\n            None, match=r\"Could not determine remote IP \\(missing REMOTE_ADDR\\)\\.\"\n        ):\n            assert get_remote_ip(request) == \"0.0.0.0\"\n\n\nclass TestWebhookEndpoint:\n    \"\"\"Test Class to test WebhookEndpoint and its methods\"\"\"\n\n    def test_sync_from_stripe_data_non_existent_webhook_endpoint(self):\n        fake_webhook = deepcopy(FAKE_WEBHOOK_ENDPOINT_1)\n        webhook_endpoint = WebhookEndpoint.sync_from_stripe_data(fake_webhook)\n\n        assert webhook_endpoint.id == fake_webhook[\"id\"]\n        assert isinstance(webhook_endpoint.djstripe_uuid, UUID)\n\n        # assert WebHookEndpoint's secret does not exist for a new sync\n        assert not webhook_endpoint.secret\n\n    def test_sync_from_stripe_data_existent_webhook_endpoint(self):\n        fake_webhook_1 = deepcopy(FAKE_WEBHOOK_ENDPOINT_1)\n        webhook_endpoint = WebhookEndpoint.sync_from_stripe_data(fake_webhook_1)\n        assert webhook_endpoint.id == fake_webhook_1[\"id\"]\n        djstripe_uuid = webhook_endpoint.djstripe_uuid\n\n        assert isinstance(djstripe_uuid, UUID)\n\n        # assert WebHookEndpoint's secret does not exist for a new sync\n        assert not webhook_endpoint.secret\n\n        # add a secret to the webhook_endpoint\n        fake_webhook_2 = deepcopy(FAKE_WEBHOOK_ENDPOINT_1)\n        fake_webhook_2[\"secret\"] = \"whsec_rguCE5LMINfRKjmIkxDJM1lOPXkAOQp3\"\n        webhook_endpoint.secret = fake_webhook_2[\"secret\"]\n        webhook_endpoint.save()\n\n        # re-sync the WebhookEndpoint instance\n        WebhookEndpoint.sync_from_stripe_data(fake_webhook_2)\n\n        webhook_endpoint.refresh_from_db()\n        assert webhook_endpoint.id == fake_webhook_2[\"id\"]\n        # assert secret got updated\n        assert webhook_endpoint.secret == fake_webhook_2[\"secret\"]\n\n        # assert UUID didn't get regenerated\n        assert webhook_endpoint.djstripe_uuid == djstripe_uuid\n\n    def test___str__(self):\n        fake_webhook = deepcopy(FAKE_WEBHOOK_ENDPOINT_1)\n        webhook_endpoint = WebhookEndpoint.sync_from_stripe_data(fake_webhook)\n        assert str(webhook_endpoint) == webhook_endpoint.url\n        assert (\n            str(webhook_endpoint)\n            == \"https://dev.example.com/stripe/webhook/f6f9aa0e-cb6c-4e0f-b5ee-5e2b9e0716d8\"\n        )\n"
  },
  {
    "path": "tests/urls.py",
    "content": "from django.contrib import admin\nfrom django.http.response import HttpResponse\nfrom django.urls import include, path\n\nadmin.autodiscover()\n\n\ndef empty_view(request):\n    return HttpResponse()\n\n\nurlpatterns = [\n    path(\"home/\", empty_view, name=\"home\"),\n    path(\"admin/\", admin.site.urls),\n    path(\"djstripe/\", include(\"djstripe.urls\", namespace=\"djstripe\")),\n    path(\"example/\", include(\"tests.apps.example.urls\")),\n    path(\"testapp/\", include(\"tests.apps.testapp.urls\")),\n    path(\n        \"testapp_namespaced/\",\n        include(\"tests.apps.testapp_namespaced.urls\", namespace=\"testapp_namespaced\"),\n    ),\n    # Represents protected content\n    path(\"testapp_content/\", include(\"tests.apps.testapp_content.urls\")),\n    # For testing fnmatches\n    path(\"test_fnmatch/extra_text/\", empty_view, name=\"test_fnmatch\"),\n]\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nisolated_build = True\nenvlist =\n    # Django 3.2 LTS. Limited support matrix.\n    django32-py{38,39,310}-{postgres,mysql}\n    # Django 4.0\n    django40-py{38,39,310}-{postgres,mysql,sqlite}\n    # Django 4.1\n    django41-py{38,39,310}-{postgres,mysql,sqlite}\n    # Django mainline - Only test on latest python / postgres\n    djangomain-py311-postgres\n\nskip_missing_interpreters = True\n\n[gh-actions]\npython =\n    3.8: py38\n    3.9: py39\n    3.10: py310\n    3.11: py311\n\n[testenv]\npassenv = DJSTRIPE_*\nsetenv =\n    postgres: DJSTRIPE_TEST_DB_VENDOR=postgres\n    mysql:    DJSTRIPE_TEST_DB_VENDOR=mysql\n    sqlite:   DJSTRIPE_TEST_DB_VENDOR=sqlite\n\n    PYTHONWARNINGS = all\n    PYTEST_ADDOPTS = --cov --cov-fail-under=90 --cov-report=html  --cov-report=term --no-cov-on-fail\ncommands = pytest {posargs}\ndeps =\n    postgres: psycopg2>=2.9\n    mysql: mysqlclient>=1.4.0\n\n    django32: Django==3.2,<3.3\n    django40: Django==4.0,<4.1\n    django41: Django==4.1,<4.2\n    djangomain: https://github.com/django/django/archive/main.tar.gz\n    pytest-django\n    pytest-cov\n\n[pytest]\nDJANGO_SETTINGS_MODULE = tests.settings\n\n[coverage:run]\nbranch = True\nsource = djstripe\nomit =\n    djstripe/migrations/*\n    djstripe/management/*\n    djstripe/admin.py\n    djstripe/checks.py\n\n[coverage:html]\ndirectory = cover\n\n[flake8]\nexclude = djstripe/migrations/, .tox/, build/lib/, .venv/\nignore = W191, W503, E203, E501\nmax-line-length = 88\n"
  }
]