From e98d5cbad5582103a4c1699255c7aa7ed39cb7fa Mon Sep 17 00:00:00 2001 From: Victor Fernandez Saborit Date: Tue, 7 May 2024 14:50:40 +0200 Subject: [PATCH] first commit --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/bug_report.yaml | 45 + .github/ISSUE_TEMPLATE/config.yml | 8 + .github/ISSUE_TEMPLATE/feature_request.yaml | 44 + .github/dependabot.yml | 22 + .github/workflows/check-semver.yml | 20 + .github/workflows/go.yml | 49 + .github/workflows/lint.yml | 27 + .github/workflows/release-on-push.yml | 17 + .gitignore | 5 + .golangci.yml | 136 ++ CHANGELOG.md | 13 + CONTRIBUTING.md | 5 + LICENSE | 190 ++ README.md | 253 +++ SECURITY.md | 14 + addresses.go | 144 ++ adjustments.go | 225 +++ businesses.go | 140 ++ collection.go | 192 ++ collection_test.go | 190 ++ context.go | 15 + currencies.go | 25 + customers.go | 164 ++ discounts.go | 180 ++ events.go | 51 + events_types.go | 21 + example_create_test.go | 69 + example_get_test.go | 39 + example_list_test.go | 91 + example_test.go | 61 + example_update_test.go | 53 + example_webhook_verifier_test.go | 123 ++ go.mod | 16 + go.sum | 18 + internal/client/client.go | 176 ++ internal/client/client_test.go | 123 ++ internal/client/directive_include.go | 57 + internal/client/directive_include_test.go | 81 + internal/client/directive_query.go | 24 + internal/client/transit_id.go | 20 + internal/client/transit_id_test.go | 19 + internal/client/version.txt | 1 + internal/response/response.go | 72 + .../response/response_api_error_handler.go | 58 + internal/response/response_error.go | 45 + ip_addresses.go | 26 + notification_logs.go | 49 + notification_setting_replays.go | 24 + notification_settings.go | 152 ++ notifications.go | 170 ++ options.go | 42 + paddle.go | 70 + patch_field.go | 54 + patch_field_test.go | 56 + pkg/paddleerr/paddleerr.go | 64 + prices.go | 226 +++ pricing_preview.go | 138 ++ products.go | 167 ++ ptr_to.go | 6 + reports.go | 287 +++ sdk.go | 48 + shared.go | 1585 +++++++++++++++++ subscriptions.go | 1205 +++++++++++++ testdata/price_created.json | 30 + testdata/transaction.json | 272 +++ testdata/transactions.json | 851 +++++++++ testdata/transactions_paginated_pg1.json | 851 +++++++++ testdata/transactions_paginated_pg2.json | 824 +++++++++ transactions.go | 1153 ++++++++++++ webhook_verifier.go | 96 + webhook_verifier_test.go | 163 ++ 72 files changed, 11951 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yaml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/check-semver.yml create mode 100644 .github/workflows/go.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/release-on-push.yml create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 addresses.go create mode 100644 adjustments.go create mode 100644 businesses.go create mode 100644 collection.go create mode 100644 collection_test.go create mode 100644 context.go create mode 100644 currencies.go create mode 100644 customers.go create mode 100644 discounts.go create mode 100644 events.go create mode 100644 events_types.go create mode 100644 example_create_test.go create mode 100644 example_get_test.go create mode 100644 example_list_test.go create mode 100644 example_test.go create mode 100644 example_update_test.go create mode 100644 example_webhook_verifier_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/client/client.go create mode 100644 internal/client/client_test.go create mode 100644 internal/client/directive_include.go create mode 100644 internal/client/directive_include_test.go create mode 100644 internal/client/directive_query.go create mode 100644 internal/client/transit_id.go create mode 100644 internal/client/transit_id_test.go create mode 100644 internal/client/version.txt create mode 100644 internal/response/response.go create mode 100644 internal/response/response_api_error_handler.go create mode 100644 internal/response/response_error.go create mode 100644 ip_addresses.go create mode 100644 notification_logs.go create mode 100644 notification_setting_replays.go create mode 100644 notification_settings.go create mode 100644 notifications.go create mode 100644 options.go create mode 100644 paddle.go create mode 100644 patch_field.go create mode 100644 patch_field_test.go create mode 100644 pkg/paddleerr/paddleerr.go create mode 100644 prices.go create mode 100644 pricing_preview.go create mode 100644 products.go create mode 100644 ptr_to.go create mode 100644 reports.go create mode 100644 sdk.go create mode 100644 shared.go create mode 100644 subscriptions.go create mode 100644 testdata/price_created.json create mode 100644 testdata/transaction.json create mode 100644 testdata/transactions.json create mode 100644 testdata/transactions_paginated_pg1.json create mode 100644 testdata/transactions_paginated_pg2.json create mode 100644 transactions.go create mode 100644 webhook_verifier.go create mode 100644 webhook_verifier_test.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b838b57 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @PaddleHQ/developer-experience diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 0000000..6bcecbe --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,45 @@ +name: Bug report +description: Report a problem. +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Use this form to report a bug or problem with the Paddle Go SDK. + + Remember to remove sensitive information from screenshots, videos, or code samples before submitting. + + **Do not create issues for potential security vulnerabilities.** Please see the [Paddle Vulnerability Disclosure Policy](https://www.paddle.com/vulnerability-disclosure-policy) and report any vulnerabilities [using our form](https://vdp.paddle.com/p/Report-a-Vulnerability). + + Thanks for helping to make the Paddle platform better for everyone! + - type: textarea + id: description + attributes: + label: What happened? + description: Describe the bug in a sentence or two. Feel free to add screenshots or a video to better explain! + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce + description: Explain how to reproduce this issue. We prefer a step-by-step walkthrough, where possible. + value: | + 1. + 2. + 3. + ... + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: What did you expect to happen? + description: Tell us what should happen when you encounter this bug. + - type: textarea + id: logs + attributes: + label: Logs + description: Copy and paste any relevant logs. This is automatically formatted into code, so no need for backticks. + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0ca9886 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Get help + url: https://developer.paddle.com/ + about: For help with the Paddle Go SDK or building your integration, contact our support team at [sellers@paddle.com](mailto:sellers@paddle.com). + - name: Report a vulnerability + url: https://vdp.paddle.com/p/Report-a-Vulnerability + about: Please see the [Paddle Vulnerability Disclosure Policy](https://www.paddle.com/vulnerability-disclosure-policy) and report any vulnerabilities using https://vdp.paddle.com/p/Report-a-Vulnerability. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 0000000..69d8c62 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,44 @@ +name: Feature request +description: Suggest an idea. +title: "[Feature]: " +labels: ["feature"] +body: + - type: markdown + attributes: + value: | + Use this form to send us suggestions for improvements to the Paddle Go SDK. + + For general feedback about the Paddle API or developer platform, contact our DX team directly + at [team-dx@paddle.com](mailto:team-dx@paddle.com). + + Thanks for helping to make the Paddle platform better for everyone! + - type: textarea + id: request + attributes: + label: Tell us about your feature request + description: Describe what you'd like to see added or improved. + validations: + required: true + - type: textarea + id: problem + attributes: + label: What problem are you looking to solve? + description: Tell us how and why would implementing your suggestion would help. + validations: + required: true + - type: textarea + id: additional-information + attributes: + label: Additional context + description: Add any other context, screenshots, or illustrations about your suggestion here. + - type: dropdown + id: priority + attributes: + label: How important is this suggestion to you? + options: + - Nice to have + - Important + - Critical + default: 0 + validations: + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7014c02 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: / + schedule: + interval: "weekly" + day: "wednesday" + time: "03:00" + labels: + - "release:patch" + groups: + go.mod: + patterns: + - "*" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "wednesday" + time: "03:00" + labels: + - "release:none" diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml new file mode 100644 index 0000000..259c1be --- /dev/null +++ b/.github/workflows/check-semver.yml @@ -0,0 +1,20 @@ +name: Label Checker + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - labeled + - unlabeled + +jobs: + check_labels: + name: Check labels + runs-on: ubuntu-latest + steps: + - uses: docker://agilepathway/pull-request-label-checker:v1.6.13 + with: + one_of: norelease,release:major,release:minor,release:patch + repo_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..f0c296f --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,49 @@ +name: go + +on: + - push + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + + - name: Install dependencies + run: go mod download + + - name: Verify dependencies + run: go mod verify + + - name: Build + run: go build -v ./... + + - name: Go Format + run: gofmt -s -w . && git diff --exit-code + + - name: Go Generate + run: go generate ./... && git diff --exit-code + + - name: Install `govulncheck` + run: go install golang.org/x/vuln/cmd/govulncheck@latest + + - name: Run `govulncheck` + run: govulncheck ./... + + - name: Vet + run: go vet ./... + + - name: Test + run: go test ./... -v -coverprofile=./cover.out -covermode atomic -coverpkg ./... -race + + - name: Check coverage + uses: vladopajic/go-test-coverage@v2 + with: + profile: cover.out + threshold-total: 30 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..0d315f3 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,27 @@ +name: Linting + +on: + pull_request: + push: + branches: + - master + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: golangci-lint + uses: reviewdog/action-golangci-lint@v2 + with: + reporter: github-pr-review + fail_on_error: true + filter_mode: nofilter + + - name: eclint + uses: reviewdog/action-eclint@v1 + with: + github_token: ${{ secrets.github_token }} diff --git a/.github/workflows/release-on-push.yml b/.github/workflows/release-on-push.yml new file mode 100644 index 0000000..cf943e0 --- /dev/null +++ b/.github/workflows/release-on-push.yml @@ -0,0 +1,17 @@ +name: Release on Push + +on: + push: + branches: + - main + +jobs: + release_on_push: + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: rymndhng/release-on-push-action@v0.28.0 + with: + bump_version_scheme: norelease + use_github_release_notes: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d120a34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +.env +*.test +*.coverprofile +/.idea diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..42dd94a --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,136 @@ +run: + tests: false +issues: + exclude-use-default: false + +# Override the defaults for each linter specified. +linters-settings: + gocritic: + # https://golangci-lint.run/usage/linters/#gocritic + enabled-tags: + - diagnostic + - opinionated + - performance + - style + disabled-checks: + # causes a crash with some libraries + - hugeParam + # causes a crash with some libraries + - paramTypeCombine + exhaustive: + default-signifies-exhaustive: true + lll: + line-length: 140 + # misspell is not enabled by default, but can be for specific projects with --enable "misspell" + misspell: + locale: US + ignore-words: + - localised + - catalogue + godot: + exclude: + - "@(Given|When|Then|And|But) (.*)" # go-relish uses comments to auto-generate code, these lines should not end with a `.` + gci: + custom-order: true + sections: + - standard + - prefix(github.com/PaddleHQ/paddle-go-sdk) + - prefix(github.com/PaddleHQ) + - default + wsl: + allow-assign-and-call: false + force-case-trailing-whitespace: 0 + force-err-cuddling: true + allow-cuddle-with-calls: ["Lock", "RLock"] + allow-cuddle-with-rhs: ["Unlock", "RUnlock"] + error-variable-names: ["err"] + +linters: + disable-all: true + enable: + # Checks for unclosed HTTP response body: https://github.com/timakin/bodyclose + - bodyclose + # Checks for struct contained context.Context field: https://github.com/sivchari/containedctx + - containedctx + # Checks function and package cyclomatic complexity: https://github.com/bkielbasa/cyclop + - cyclop + # Checks for assignments with too many blank identifiers (e.g. x, , , _, := f()): https://github.com/alexkohler/dogsled + - dogsled + # Checks for two durations multiplied together: https://github.com/charithe/durationcheck + - durationcheck + # Checks for unchecked errors: https://github.com/kisielk/errcheck + - errcheck + # Checks for pointers to enclosing loop variables: https://github.com/kyoh86/exportloopref + - exportloopref + # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`: https://github.com/Antonboom/errname + - errname + # Check exhaustiveness of enum switch statements: https://github.com/nishanths/exhaustive + - exhaustive + # Checks for forced type assertions: https://github.com/gostaticanalysis/forcetypeassert + - forcetypeassert + # Checks for long functions: https://github.com/ultraware/funlen + - funlen + # Check the errors handling expressions: https://github.com/Djarvur/go-err113 + - goerr113 + # Checks the cognitive complexity of functions: https://github.com/uudashr/gocognit + - gocognit + # Checks for repeated strings that could be replaced by a constant: https://github.com/jgautheron/goconst + - goconst + # Checks for bugs, performance and style issues: https://github.com/go-critic/go-critic + - gocritic + # Checks the cyclomatic complexity of functions: https://github.com/fzipp/gocyclo + - gocyclo + # Checks if comments end in a period: https://github.com/tetafro/godot + - godot + # Checks whether code was gofumpt-ed: https://github.com/mvdan/gofumpt + - gofumpt + # Checks for security problems: https://github.com/securego/gosec + - gosec + # Checks if code could be simplified: https://github.com/dominikh/go-tools/tree/master/simple + - gosimple + # Checks for suspicious constructs, such as Printf calls whose arguments do not align with the format string: https://golang.org/cmd/vet/ + - govet + # Checks for import ordering + - gci + # Checks for assignments to existing variables are unused: https://github.com/gordonklaus/ineffassign + - ineffassign + # Checks for long lines: https://golangci-lint.run/usage/linters/#lll + - lll + # Checks for slice declarations with non-zero initial length: https://github.com/ashanbrown/makezero + - makezero + # Checks for incorrect spellings: https://github.com/client9/misspell: + - misspell + # Checks for code that returns nil even if it checks that the error is not nil: https://github.com/gostaticanalysis/nilerr + - nilerr + # Checks for deeply nested if statements: https://github.com/nakabonne/nestif + - nestif + # Checks for sending http request without context.Context: https://github.com/sonatard/noctx + - noctx + # Checks for slice declarations that could potentially be pre-allocated: https://github.com/alexkohler/prealloc + - prealloc + # Checks for code that shadows one of Go's predeclared identifiers: https://github.com/nishanths/predeclared + - predeclared + # Fast, configurable, extensible, flexible, and beautiful linter for Go: https://github.com/mgechev/revive + - revive + # Checks for whether Err of rows is checked successfully: https://github.com/jingyugao/rowserrcheck + - rowserrcheck + # https://staticcheck.io/ + - staticcheck + # a replacement for golint: https://github.com/dominikh/go-tools/tree/master/stylecheck + - stylecheck + # Checks that sql.Rows and sql.Stmt are closed: https://github.com/ryanrolds/sqlclosecheck + - sqlclosecheck + # linter that makes you use a separate _test package: https://github.com/maratori/testpackage + - testpackage + # Checks for unnecessary type conversions: https://github.com/mdempsky/unconvert + - unconvert + # Checks for unused function parameters: https://github.com/mvdan/unparam + - unparam + # Checks for unused constants, variables, functions and types: https://github.com/dominikh/go-tools/tree/master/unused + - unused + # Checks for wasted assignment statements: https://github.com/sanposhiho/wastedassign + - wastedassign + # Checks for detection of leading and trailing whitespace: https://github.com/ultraware/whitespace + - whitespace + # Whitespace Linter - Forces you to use empty lines! + - wsl diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..aaff24d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx&utm_medium=paddle-go-sdk) for information about changes to the Paddle Billing platform, the Paddle API, and other developer tools. + +## 0.1.0 - 2024-05-07 + +### Added + +- Initial early access release. Added support for the most frequently used Paddle Billing entities and API operations. Check the [README](./README.md) for more information. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8975696 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,5 @@ +## Contributing + +If you've spotted a problem with this package or have a new feature request please open an issue. + +For help with the Paddle API or building your integration, contact our support team at [sellers@paddle.com](mailto:sellers@paddle.com). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8fab21c --- /dev/null +++ b/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2024 Paddle.com Market Limited + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e5255bf --- /dev/null +++ b/README.md @@ -0,0 +1,253 @@ +# Paddle GO SDK + +[Paddle Billing](https://www.paddle.com/billing?utm_source=dx&utm_medium=paddle-go-sdk) is a complete digital product sales and subscription management platform, designed for modern software businesses. It helps you increase your revenue, retain customers, and scale your operations. + +This is a [Go](https://go.dev/) SDK that you can use to integrate Paddle Billing with applications written in server-side Go. + +For working with Paddle in your frontend, use [Paddle.js](https://developer.paddle.com/paddlejs/overview?utm_source=dx&utm_medium=paddle-go-sdk). You can open checkouts, securely collect payment information, build pricing pages, and integrate with Paddle Retain. + +> **Important:** This package works with Paddle Billing. It does not support Paddle Classic. To work with Paddle Classic, see: [Paddle Classic API reference](https://developer.paddle.com/classic/api-reference/1384a288aca7a-api-reference?utm_source=dx&utm_medium=paddle-go-sdk) + +## Learn more + +- [Paddle API reference](https://developer.paddle.com/api-reference/overview?utm_source=dx&utm_medium=paddle-go-sdk) +- [Sign up for Paddle Billing](https://login.paddle.com/signup?utm_source=dx&utm_medium=paddle-go-sdk) + +## Requirements + +Go 1.21 or later + +## Before you begin + +If you've used this SDK, we'd love to hear how you found it! Email us at [team-dx@paddle.com](mailto:team-dx@paddle.com) with any thoughts. + +While in early access, we may introduce breaking changes. Where we can, we'll tag breaking changes and communicate ahead of time. + +## Installation + +Make sure your project is using Go Modules (it will have a go.mod file in its root if it already is): + +```bash +go mod init +``` + +To install the Paddle Go SDK, use the following command: + +```bash +go get github.com/PaddleHQ/paddle-go-sdk +``` + +Then, reference paddle-go-sdk in a Go program with import: + +```go +import ( + paddle "github.com/PaddleHQ/paddle-go-sdk" +) +``` + +## Usage + +To authenticate, you'll need an API key. You can create and manage API keys in **Paddle > Developer tools > Authentication**. + +Pass your API key while initializing a new Paddle client. + +``` go +import ( + paddle "github.com/PaddleHQ/paddle-go-sdk" +) + +client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(paddle.ProductionBaseURL), +) +``` + +You can now use the client to make requests to the Paddle API. + +## Examples + +### List all entities + +You can list supported entities with the `List*` function. It returns an iterator to help when working with multiple pages. +``` go +products, err := client.ListProducts(ctx, &paddle.ListProductsRequest{IncludePrices: true}) +if err != nil { + panic(err) +} + +err = products.Iter(ctx, func(p *paddle.ProductWithIncludes) (bool, error) { + // Do something with the product + fmt.Printf("%+v\n", p) + return true, nil +}) +if err != nil { + panic(err) +} +``` + +### Create an entity + +You can create a supported entity with the `Create*` function. It accepts the resource's corresponding `Create*Request` operation e.g. `CreateProductRequest`. The created entity is returned. + +``` go +product, err := client.CreateProduct(ctx, &paddle.CreateProductRequest{ + Name: "Test Product - GO SDK", + TaxCategory: paddle.TaxCategoryStandard, +}) +if err != nil { + panic(err) +} +fmt.Printf("%+v\n", product) +``` + +### Update an entity + +You can update a supported entity with the `Update*` function. It accepts the `ID` of the entity to update and the corresponding `Update*Request` operation e.g. `UpdateProductRequest`. The updated entity is returned. + +``` go +product, err := client.UpdateProduct(ctx, &paddle.UpdateProductRequest{ + ProductID: product.ID, + Name: paddle.NewPatchField("Test Product - GO SDK Updated"), +}) +if err != nil { + panic(err) +} +fmt.Printf("%+v\n", product) +``` + +### Get an entity + +You can get an entity with the `Get*` function. It accepts the `ID` of the entity to get and the corresponding `Get*Request` operation e.g. `GetProductRequest`. The entity is returned. + +``` go +product, err := client.GetProduct(ctx, &paddle.GetProductRequest{ + ProductID: productID, + IncludePrices: true +}) +if err != nil { + panic(err) +} +fmt.Printf("%+v\n", product) +``` + +## Resources + +### Webhook signature verification + +The SDK includes a couple of helper functions to verify webhook signatures sent by Notifications from Paddle. + +You could use a middleware to verify the signature of the incoming request before processing it. + +```go +verifier := paddle.NewWebhookVerifier(os.Getenv("WEBHOOK_SECRET_KEY")) +// Wrap your handler with the verifier.Middleware method +handler := verifier.Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // The request making it this far means the webhook was verified + // Best practice here is to check if you have processed this webhook already using the event id + // At this point you should store for async processing + // For example a local queue or db entry + + // Respond as soon as possible with a 200 OK + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"success": true}`)) +})) +``` + +Alternatively you can verify the signature of the incoming request manually. + +``` go +webhookVerifier := paddle.NewWebhookVerifier(os.Getenv("WEBHOOK_SECRET_KEY")) +// Note: the request (req *http.Request) should be pass exactly as it comes without altering it. +ok, err := webhookVerifier.Verify(req) +``` + +## Feature parity + +While in early access, not all operations in the Paddle API are available in our Go SDK. We're working on complete feature parity for our final release. + +This table shows which operations are available as of the latest release. + +| Paddle API operation | Support | +|----------------------------------------------|:-----------:| +| **Products** | **Full** | +| List products | ✅ | +| Create a product | ✅ | +| Get a product | ✅ | +| Update a product | ✅ | +| **Prices** | **Full** | +| List prices | ✅ | +| Create a price | ✅ | +| Get a price | ✅ | +| Update a price | ✅ | +| **Discounts** | **Full** | +| List discounts | ✅ | +| Create a discount | ✅ | +| Get a discount | ✅ | +| Update a discount | ✅ | +| **Customers** | **Full** | +| List customers | ✅ | +| Create a customer | ✅ | +| Get a customer | ✅ | +| Update a customer | ✅ | +| List credit balances for a customer | ✅ | +| **Addresses** | **Full** | +| List addresses for a customer | ✅ | +| Create an addresses for a customer | ✅ | +| Get an address for a customer | ✅ | +| Update an address for a customer | ✅ | +| **Businesses** | ✅ | +| List businesses for a customer | ✅ | +| Create a business for a customer | ✅ | +| Get a business for a customer | ✅ | +| Update a business for a customer | ✅ | +| **Transactions** | **Full** | +| List transactions | ✅ | +| Create a transaction | ✅ | +| Get a transaction | ✅ | +| Update a transaction | ✅ | +| Preview a transaction | ✅ | +| Get a PDF invoice for a transaction | ✅ | +| **Subscriptions** | **Full** | +| List subscriptions | ✅ | +| Get a subscription | ✅ | +| Preview an update to a subscription | ✅ | +| Update a subscription | ✅ | +| Get a transaction to update payment method | ✅ | +| Preview a one-time charge for a subscription | ✅ | +| Create a one-time charge for a subscription | ✅ | +| Activate a trialing subscription | ✅ | +| Pause a subscription | ✅ | +| Resume a paused subscription | ✅ | +| Cancel a subscription | ✅ | +| **Adjustments** | **Full** | +| List adjustments | ✅ | +| Create an adjustment | ✅ | +| **Pricing preview** | **Full** | +| Preview prices | ✅ | +| **Reports** | **Full** | +| List reports | ✅ | +| Create a report | ✅ | +| Get a report | ✅ | +| Get a CSV file for a report | ✅ | +| **Notification settings** | **Full** | +| List notification settings | ✅ | +| Create a notification setting | ✅ | +| Get a notification setting | ✅ | +| Update a notification setting | ✅ | +| Delete a notification setting | ✅ | +| **Event types** | **Full** | +| List event types | ✅ | +| **Events** | **-** | +| List events | - | +| **Notifications** | **Partial** | +| List notifications | - | +| Get a notification | - | +| Replay a notification | ✅ | +| **Notification logs** | **Full** | +| List logs for a notification | ✅ | + +## Learn more + +- [Paddle API reference](https://developer.paddle.com/api-reference/overview?utm_source=dx&utm_medium=paddle-go-sdk) +- [Sign up for Paddle Billing](https://login.paddle.com/signup?utm_source=dx&utm_medium=paddle-go-sdk) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7bd6d90 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,14 @@ +- [Security Policy](#security-policy) + - [Reporting a Vulnerability](#reporting-a-vulnerability) + +# Security policy + +## Reporting a vulnerability + +Please see the [Paddle Vulnerability Disclosure Policy](https://www.paddle.com/vulnerability-disclosure-policy) and +report any vulnerabilities using https://vdp.paddle.com/p/Report-a-Vulnerability. + +> [!WARNING] +> Do not create issues for potential security vulnerabilities. Issues are public and can be seen by potentially malicious actors. + +Thanks for helping to make the Paddle platform safe for everyone. \ No newline at end of file diff --git a/addresses.go b/addresses.go new file mode 100644 index 0000000..cdcb50d --- /dev/null +++ b/addresses.go @@ -0,0 +1,144 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import ( + "context" + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrAddressLocationNotAllowed represents a `address_location_not_allowed` error. +// See https://developer.paddle.com/errors/addresses/address_location_not_allowed for more information. +var ErrAddressLocationNotAllowed = &paddleerr.Error{ + Code: "address_location_not_allowed", + Type: paddleerr.ErrorTypeRequestError, +} + +// AddressesClient is a client for the Addresses resource. +type AddressesClient struct { + doer Doer +} + +// ListAddressesRequest is given as an input to ListAddresses. +type ListAddressesRequest struct { + // URL path parameters. + CustomerID string `in:"path=customer_id" json:"-"` + + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // ID is a query parameter. + // Return only the IDs specified. Use a comma-separated list to get multiple entities. + ID []string `in:"query=id,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `id`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `50`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // Search is a query parameter. + // Return entities that match a search query. Searches all fields except `status`, `created_at`, and `updated_at`. + Search *string `in:"query=search,omitempty" json:"-"` + // Status is a query parameter. + // Return entities that match the specified status. Use a comma-separated list to specify multiple status values. + Status []string `in:"query=status,omitempty" json:"-"` +} + +// ListAddresses performs the GET operation on a Addresses resource. +func (c *AddressesClient) ListAddresses(ctx context.Context, req *ListAddressesRequest) (res *Collection[*Address], err error) { + if err := c.doer.Do(ctx, "GET", "/customers/{customer_id}/addresses", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// CreateAddressRequest is given as an input to CreateAddress. +type CreateAddressRequest struct { + // URL path parameters. + CustomerID string `in:"path=customer_id" json:"-"` + + // CountryCode: Supported two-letter ISO 3166-1 alpha-2 country code for this address. + CountryCode string `json:"country_code,omitempty"` + // Description: Memorable description for this address. + Description *string `json:"description,omitempty"` + // FirstLine: First line of this address. + FirstLine *string `json:"first_line,omitempty"` + // SecondLine: Second line of this address. + SecondLine *string `json:"second_line,omitempty"` + // City: City of this address. + City *string `json:"city,omitempty"` + // PostalCode: ZIP or postal code of this address. Required for some countries. + PostalCode *string `json:"postal_code,omitempty"` + // Region: State, county, or region of this address. + Region *string `json:"region,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` +} + +// CreateAddress performs the POST operation on a Addresses resource. +func (c *AddressesClient) CreateAddress(ctx context.Context, req *CreateAddressRequest) (res *Address, err error) { + if err := c.doer.Do(ctx, "POST", "/customers/{customer_id}/addresses", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// GetAddressRequest is given as an input to GetAddress. +type GetAddressRequest struct { + // URL path parameters. + CustomerID string `in:"path=customer_id" json:"-"` + AddressID string `in:"path=address_id" json:"-"` +} + +// GetAddress performs the GET operation on a Addresses resource. +func (c *AddressesClient) GetAddress(ctx context.Context, req *GetAddressRequest) (res *Address, err error) { + if err := c.doer.Do(ctx, "GET", "/customers/{customer_id}/addresses/{address_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// UpdateAddressRequest is given as an input to UpdateAddress. +type UpdateAddressRequest struct { + // URL path parameters. + CustomerID string `in:"path=customer_id" json:"-"` + AddressID string `in:"path=address_id" json:"-"` + + // Description: Memorable description for this address. + Description *PatchField[*string] `json:"description,omitempty"` + // FirstLine: First line of this address. + FirstLine *PatchField[*string] `json:"first_line,omitempty"` + // SecondLine: Second line of this address. + SecondLine *PatchField[*string] `json:"second_line,omitempty"` + // City: City of this address. + City *PatchField[*string] `json:"city,omitempty"` + // PostalCode: ZIP or postal code of this address. Required for some countries. + PostalCode *PatchField[*string] `json:"postal_code,omitempty"` + // Region: State, county, or region of this address. + Region *PatchField[*string] `json:"region,omitempty"` + // CountryCode: Supported two-letter ISO 3166-1 alpha-2 country code for this address. + CountryCode *PatchField[string] `json:"country_code,omitempty"` + // CustomData: Your own structured key-value data. + CustomData *PatchField[CustomData] `json:"custom_data,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status *PatchField[string] `json:"status,omitempty"` +} + +// UpdateAddress performs the PATCH operation on a Addresses resource. +func (c *AddressesClient) UpdateAddress(ctx context.Context, req *UpdateAddressRequest) (res *Address, err error) { + if err := c.doer.Do(ctx, "PATCH", "/customers/{customer_id}/addresses/{address_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/adjustments.go b/adjustments.go new file mode 100644 index 0000000..e189e06 --- /dev/null +++ b/adjustments.go @@ -0,0 +1,225 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import ( + "context" + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrAdjustmentTransactionMissingCustomerID represents a `adjustment_transaction_missing_customer_id` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_transaction_missing_customer_id for more information. +var ErrAdjustmentTransactionMissingCustomerID = &paddleerr.Error{ + Code: "adjustment_transaction_missing_customer_id", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentTransactionCustomerMismatch represents a `adjustment_transaction_customer_mismatch` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_transaction_customer_mismatch for more information. +var ErrAdjustmentTransactionCustomerMismatch = &paddleerr.Error{ + Code: "adjustment_transaction_customer_mismatch", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentTransactionSubscriptionMismatch represents a `adjustment_transaction_subscription_mismatch` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_transaction_subscription_mismatch for more information. +var ErrAdjustmentTransactionSubscriptionMismatch = &paddleerr.Error{ + Code: "adjustment_transaction_subscription_mismatch", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentTransactionInvalidStatusForCredit represents a `adjustment_transaction_invalid_status_for_credit` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_transaction_invalid_status_for_credit for more information. +var ErrAdjustmentTransactionInvalidStatusForCredit = &paddleerr.Error{ + Code: "adjustment_transaction_invalid_status_for_credit", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentTransactionInvalidStatusForRefund represents a `adjustment_transaction_invalid_status_for_refund` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_transaction_invalid_status_for_refund for more information. +var ErrAdjustmentTransactionInvalidStatusForRefund = &paddleerr.Error{ + Code: "adjustment_transaction_invalid_status_for_refund", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentInvalidCreditAction represents a `adjustment_invalid_credit_action` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_invalid_credit_action for more information. +var ErrAdjustmentInvalidCreditAction = &paddleerr.Error{ + Code: "adjustment_invalid_credit_action", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentInvalidCombinationOfTypes represents a `adjustment_invalid_combination_of_types` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_invalid_combination_of_types for more information. +var ErrAdjustmentInvalidCombinationOfTypes = &paddleerr.Error{ + Code: "adjustment_invalid_combination_of_types", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentAmountAboveRemainingAllowed represents a `adjustment_amount_above_remaining_allowed` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_amount_above_remaining_allowed for more information. +var ErrAdjustmentAmountAboveRemainingAllowed = &paddleerr.Error{ + Code: "adjustment_amount_above_remaining_allowed", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentTotalAmountAboveRemainingAllowed represents a `adjustment_total_amount_above_remaining_allowed` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_total_amount_above_remaining_allowed for more information. +var ErrAdjustmentTotalAmountAboveRemainingAllowed = &paddleerr.Error{ + Code: "adjustment_total_amount_above_remaining_allowed", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentTransactionItemHasAlreadyBeenFullyAdjusted represents a `adjustment_transaction_item_has_already_been_fully_adjusted` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_transaction_item_has_already_been_fully_adjusted for more information. +var ErrAdjustmentTransactionItemHasAlreadyBeenFullyAdjusted = &paddleerr.Error{ + Code: "adjustment_transaction_item_has_already_been_fully_adjusted", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentNoTaxAvailableToAdjust represents a `adjustment_no_tax_available_to_adjust` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_no_tax_available_to_adjust for more information. +var ErrAdjustmentNoTaxAvailableToAdjust = &paddleerr.Error{ + Code: "adjustment_no_tax_available_to_adjust", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentAmountCannotBeZero represents a `adjustment_amount_cannot_be_zero` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_amount_cannot_be_zero for more information. +var ErrAdjustmentAmountCannotBeZero = &paddleerr.Error{ + Code: "adjustment_amount_cannot_be_zero", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentPendingRefundRequest represents a `adjustment_pending_refund_request` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_pending_refund_request for more information. +var ErrAdjustmentPendingRefundRequest = &paddleerr.Error{ + Code: "adjustment_pending_refund_request", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentTransactionItemOverAdjustment represents a `adjustment_transaction_item_over_adjustment` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_transaction_item_over_adjustment for more information. +var ErrAdjustmentTransactionItemOverAdjustment = &paddleerr.Error{ + Code: "adjustment_transaction_item_over_adjustment", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAdjustmentTransactionItemInvalid represents a `adjustment_transaction_item_invalid` error. +// See https://developer.paddle.com/errors/adjustments/adjustment_transaction_item_invalid for more information. +var ErrAdjustmentTransactionItemInvalid = &paddleerr.Error{ + Code: "adjustment_transaction_item_invalid", + Type: paddleerr.ErrorTypeRequestError, +} + +// CustomerBalance: Totals for this credit balance. Where a customer has more than one subscription in this currency with a credit balance, includes totals for all subscriptions. +type CustomerBalance struct { + // Available: Total amount of credit available to use. + Available string `json:"available,omitempty"` + // Reserved: Total amount of credit temporarily reserved for `billed` transactions. + Reserved string `json:"reserved,omitempty"` + // Used: Total amount of credit used. + Used string `json:"used,omitempty"` +} + +// CreditBalance: Represents a credit balance for a customer. +type CreditBalance struct { + // CustomerID: Paddle ID of the customer that this credit balance is for, prefixed with `ctm_`. + CustomerID string `json:"customer_id,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code for this credit balance. + CurrencyCode string `json:"currency_code,omitempty"` + // Balance: Totals for this credit balance. Where a customer has more than one subscription in this currency with a credit balance, includes totals for all subscriptions. + Balance CustomerBalance `json:"balance,omitempty"` +} + +// AdjustmentsClient is a client for the Adjustments resource. +type AdjustmentsClient struct { + doer Doer +} + +// ListAdjustmentsRequest is given as an input to ListAdjustments. +type ListAdjustmentsRequest struct { + // Action is a query parameter. + // Return entities for the specified action. + Action *string `in:"query=action,omitempty" json:"-"` + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // CustomerID is a query parameter. + // Return entities related to the specified customer. Use a comma-separated list to specify multiple customer IDs. + CustomerID []string `in:"query=customer_id,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `id`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `10`; Maximum: `50`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // Status is a query parameter. + // Return entities that match the specified status. Use a comma-separated list to specify multiple status values. + Status []string `in:"query=status,omitempty" json:"-"` + // SubscriptionID is a query parameter. + // Return entities related to the specified subscription. Use a comma-separated list to specify multiple subscription IDs. + SubscriptionID []string `in:"query=subscription_id,omitempty" json:"-"` + // TransactionID is a query parameter. + // Return entities related to the specified transaction. Use a comma-separated list to specify multiple transaction IDs. + TransactionID []string `in:"query=transaction_id,omitempty" json:"-"` + // ID is a query parameter. + // Return only the IDs specified. Use a comma-separated list to get multiple entities. + ID []string `in:"query=id,omitempty" json:"-"` +} + +// ListAdjustments performs the GET operation on a Adjustments resource. +func (c *AdjustmentsClient) ListAdjustments(ctx context.Context, req *ListAdjustmentsRequest) (res *Collection[*Adjustment], err error) { + if err := c.doer.Do(ctx, "GET", "/adjustments", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// CreateAdjustmentRequest is given as an input to CreateAdjustment. +type CreateAdjustmentRequest struct { + // Action: How this adjustment impacts the related transaction. `refund` adjustments must be approved by Paddle, and are created with the status `pending_approval`. + Action string `json:"action,omitempty"` + // Items: List of items on this adjustment. + Items []AdjustmentItem `json:"items,omitempty"` + // Reason: Why this adjustment was created. Appears in the Paddle Dashboard. Retained for record-keeping purposes. + Reason string `json:"reason,omitempty"` + /* + TransactionID: Paddle ID for the transaction related to this adjustment, prefixed with `txn_`. + Transactions must be `billed` or `completed`. You can't create an adjustment for a transaction + that has an adjustment that's `pending_approval`. + */ + TransactionID string `json:"transaction_id,omitempty"` +} + +// CreateAdjustment performs the POST operation on a Adjustments resource. +func (c *AdjustmentsClient) CreateAdjustment(ctx context.Context, req *CreateAdjustmentRequest) (res *Adjustment, err error) { + if err := c.doer.Do(ctx, "POST", "/adjustments", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// ListCreditBalancesRequest is given as an input to ListCreditBalances. +type ListCreditBalancesRequest struct { + // URL path parameters. + CustomerID string `in:"path=customer_id" json:"-"` +} + +// ListCreditBalances performs the GET operation on a Adjustments resource. +func (c *AdjustmentsClient) ListCreditBalances(ctx context.Context, req *ListCreditBalancesRequest) (res *Collection[*CreditBalance], err error) { + if err := c.doer.Do(ctx, "GET", "/customers/{customer_id}/credit-balances", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/businesses.go b/businesses.go new file mode 100644 index 0000000..f34926a --- /dev/null +++ b/businesses.go @@ -0,0 +1,140 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import ( + "context" + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrBusinessContactEmailDomainNotAllowed represents a `business_contact_email_domain_not_allowed` error. +// See https://developer.paddle.com/errors/businesses/business_contact_email_domain_not_allowed for more information. +var ErrBusinessContactEmailDomainNotAllowed = &paddleerr.Error{ + Code: "business_contact_email_domain_not_allowed", + Type: paddleerr.ErrorTypeRequestError, +} + +// BusinessesContacts: List of contacts related to this business, typically used for sending invoices. +type BusinessesContacts struct { + // Name: Full name of this contact. + Name string `json:"name,omitempty"` + // Email: Email address for this contact. + Email string `json:"email,omitempty"` +} + +// BusinessesClient is a client for the Businesses resource. +type BusinessesClient struct { + doer Doer +} + +// ListBusinessesRequest is given as an input to ListBusinesses. +type ListBusinessesRequest struct { + // URL path parameters. + CustomerID string `in:"path=customer_id" json:"-"` + + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // ID is a query parameter. + // Return only the IDs specified. Use a comma-separated list to get multiple entities. + ID []string `in:"query=id,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `id`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `50`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // Search is a query parameter. + // Return entities that match a search query. Searches all fields, including contacts, except `status`, `created_at`, and `updated_at`. + Search *string `in:"query=search,omitempty" json:"-"` + // Status is a query parameter. + // Return entities that match the specified status. Use a comma-separated list to specify multiple status values. + Status []string `in:"query=status,omitempty" json:"-"` +} + +// ListBusinesses performs the GET operation on a Businesses resource. +func (c *BusinessesClient) ListBusinesses(ctx context.Context, req *ListBusinessesRequest) (res *Collection[*Business], err error) { + if err := c.doer.Do(ctx, "GET", "/customers/{customer_id}/businesses", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// CreateBusinessRequest is given as an input to CreateBusiness. +type CreateBusinessRequest struct { + // URL path parameters. + CustomerID string `in:"path=customer_id" json:"-"` + + // Name: Name of this business. + Name string `json:"name,omitempty"` + // CompanyNumber: Company number for this business. + CompanyNumber *string `json:"company_number,omitempty"` + // TaxIdentifier: Tax or VAT Number for this business. + TaxIdentifier *string `json:"tax_identifier,omitempty"` + // Contacts: List of contacts related to this business, typically used for sending invoices. + Contacts []BusinessesContacts `json:"contacts,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` +} + +// CreateBusiness performs the POST operation on a Businesses resource. +func (c *BusinessesClient) CreateBusiness(ctx context.Context, req *CreateBusinessRequest) (res *Business, err error) { + if err := c.doer.Do(ctx, "POST", "/customers/{customer_id}/businesses", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// GetBusinessRequest is given as an input to GetBusiness. +type GetBusinessRequest struct { + // URL path parameters. + CustomerID string `in:"path=customer_id" json:"-"` + BusinessID string `in:"path=business_id" json:"-"` +} + +// GetBusiness performs the GET operation on a Businesses resource. +func (c *BusinessesClient) GetBusiness(ctx context.Context, req *GetBusinessRequest) (res *Business, err error) { + if err := c.doer.Do(ctx, "GET", "/customers/{customer_id}/businesses/{business_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// UpdateBusinessRequest is given as an input to UpdateBusiness. +type UpdateBusinessRequest struct { + // URL path parameters. + CustomerID string `in:"path=customer_id" json:"-"` + BusinessID string `in:"path=business_id" json:"-"` + + // Name: Name of this business. + Name *PatchField[string] `json:"name,omitempty"` + // CompanyNumber: Company number for this business. + CompanyNumber *PatchField[*string] `json:"company_number,omitempty"` + // TaxIdentifier: Tax or VAT Number for this business. + TaxIdentifier *PatchField[*string] `json:"tax_identifier,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status *PatchField[string] `json:"status,omitempty"` + // Contacts: List of contacts related to this business, typically used for sending invoices. + Contacts *PatchField[[]BusinessesContacts] `json:"contacts,omitempty"` + // CustomData: Your own structured key-value data. + CustomData *PatchField[CustomData] `json:"custom_data,omitempty"` +} + +// UpdateBusiness performs the PATCH operation on a Businesses resource. +func (c *BusinessesClient) UpdateBusiness(ctx context.Context, req *UpdateBusinessRequest) (res *Business, err error) { + if err := c.doer.Do(ctx, "PATCH", "/customers/{customer_id}/businesses/{business_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/collection.go b/collection.go new file mode 100644 index 0000000..80a515e --- /dev/null +++ b/collection.go @@ -0,0 +1,192 @@ +package paddle + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/url" + "sync" + + "github.com/PaddleHQ/paddle-go-sdk/internal/client" + "github.com/PaddleHQ/paddle-go-sdk/internal/response" +) + +// Collection is the response from a listing endpoint in the Paddle API. +// It is used as a container to iterate over many pages of results. +type Collection[T any] struct { + client Doer + + m sync.RWMutex + pos uint32 + results []*Res[T] + + nextURL *url.URL + itemsPerPage int + estimatedTotal int + hasMore bool +} + +// PerPage returns the number of items per page in the collection. +func (c *Collection[T]) PerPage() int { + return c.itemsPerPage +} + +// EstimatedTotal returns the estimated number of items in the collection. +func (c *Collection[T]) EstimatedTotal() int { + return c.estimatedTotal +} + +// HasMore returns true if there are more pages of results to be fetched. +func (c *Collection[T]) HasMore() bool { + return c.hasMore +} + +// Res is a single result returned from an iteration of a collection. +type Res[T any] struct { + value T + err error + end bool +} + +// Ok returns true if the result is not an error and not the end of the +// collection. +func (r *Res[T]) Ok() bool { + return !r.end +} + +// Err returns the error from the result, if it exists. This should be checked +// on each iteration. +func (r *Res[T]) Err() error { + return r.err +} + +// Value returns the value contained from the result. +func (r *Res[T]) Value() T { + return r.value +} + +// Next returns the next result from the collection. +// If there are no more results, the result will response to Ok() with false. +func (c *Collection[T]) Next(ctx context.Context) *Res[T] { + // We need to lock, as we mutate the underlying position and read values in + // sequence. It is unlikely that multiple consumers will be reading from the + // same collection as it provides no real performance benefit, however this + // prevents a possible data-race with misuse. + c.m.Lock() + defer c.m.Unlock() + + // If we're about to seek past the end of the results, we need to fetch the + // next page of results. + if c.pos >= uint32(len(c.results)) { + if !c.hasMore { + return &Res[T]{end: true} + } + + err := c.client.Do(ctx, http.MethodGet, c.nextURL.String(), nil, &c) + if err != nil { + return &Res[T]{end: true, err: err} + } + + if len(c.results) == 0 { + return &Res[T]{end: true} + } + } + + // Copy the result, as the next iteration may reset results. + ptrCopy := *c.results[c.pos] + c.pos++ + + return &ptrCopy +} + +// Ensure that Collection[T] implements the json.Unmarshaler and the +// client.Wanter interfaces. +var ( + _ json.Unmarshaler = &Collection[any]{} + _ client.Wanter = &Collection[any]{} +) + +// Wants sets the client to be used for making requests. +func (c *Collection[T]) Wants(d client.Doer) { + c.client = d +} + +// Iter iterates over the collection, calling the given function for each result. +// If the function returns false, the iteration will stop. +// You should check the error given to your callback function on each iteration. +func (c *Collection[T]) Iter(ctx context.Context, fn func(v T) (bool, error)) error { + for { + r := c.Next(ctx) + if !r.Ok() { + return r.Err() + } + + cont, err := fn(r.Value()) + if err != nil { + return err + } + + if !cont { + return nil + } + } +} + +// ErrStopIteration is used as a return from the iteration function to stop the +// iteration from proceeding. It will ensure that IterErr returns nil. +var ErrStopIteration = errors.New("stop iteration") + +// IterErr iterates over the collection, calling the given function for each +// result. +// If the function returns false, the iteration will stop. +// You should check the error given to the function on each iteration. +func (c *Collection[T]) IterErr(ctx context.Context, fn func(v T) error) error { + for { + r := c.Next(ctx) + if !r.Ok() { + return r.Err() + } + + err := fn(r.Value()) + if err != nil && errors.Is(err, ErrStopIteration) { + return nil + } else if err != nil { + return err + } + } +} + +// UnmarshalJSON unmarshals the collection from a JSON response. +func (c *Collection[T]) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return nil + } + + // Unmarshal into an intermediary struct that matches the API response. + var res response.Response[[]T] + + if err := json.Unmarshal(b, &res); err != nil { + return err + } + + // Reset results. + c.results = nil + c.pos = 0 + + for _, item := range res.Data { + c.results = append(c.results, &Res[T]{value: item}) + } + + nextURL, err := url.Parse(res.Meta.Pagination.Next) + if err != nil { + return err + } + + c.itemsPerPage = res.Meta.Pagination.PerPage + c.nextURL = nextURL + c.estimatedTotal = res.Meta.Pagination.EstimatedTotal + c.hasMore = res.Meta.Pagination.HasMore + + return nil +} diff --git a/collection_test.go b/collection_test.go new file mode 100644 index 0000000..d87b50f --- /dev/null +++ b/collection_test.go @@ -0,0 +1,190 @@ +package paddle_test + +import ( + "context" + "encoding/json" + "errors" + "testing" + + paddle "github.com/PaddleHQ/paddle-go-sdk" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type Request struct { + ID int `json:"id"` +} + +func TestCollection(t *testing.T) { + firstPageJSON := []byte(`{ + "data": [ + {"id": 1} + ], + "meta": { + "pagination": { + "next_url": "https://example.com/next", + "per_page": 1, + "has_more": true, + "estimated_total": 2 + } + } + }`) + + t.Run("successful pagination", func(t *testing.T) { + secondPageJSON := []byte(`{ + "data": [ + {"id": 2} + ], + "meta": { + "pagination": { + "next_url": "https://example.com/next", + "per_page": 1, + "has_more": true, + "estimated_total": 2 + } + } + }`) + + thirdPageJSON := []byte(`{ + "data": [], + "meta": { + "pagination": { + "next_url": "https://example.com/next", + "per_page": 1, + "has_more": false, + "estimated_total": 3 + } + } + }`) + + c := &paddle.Collection[*Request]{} + m := &MockDoer{t: t, pages: [][]byte{secondPageJSON, thirdPageJSON}} + c.Wants(m) + + err := json.Unmarshal(firstPageJSON, &c) + if err != nil { + t.Error(err) + } + + assert.Equal(t, 2, c.EstimatedTotal()) + assert.Equal(t, true, c.HasMore()) + assert.Equal(t, 1, c.PerPage()) + + called := false + i := 1 + + err = c.IterErr(context.Background(), func(r *Request) error { + called = true + assert.Equal(t, i, r.ID) + i++ + + return nil + }) + assert.NoError(t, err) + + assert.True(t, called) + }) + + t.Run("error on iteration", func(t *testing.T) { + wantErr := errors.New("something happened") + c := &paddle.Collection[*Request]{} + m := &MockDoer{t: t, err: wantErr} + c.Wants(m) + + err := json.Unmarshal(firstPageJSON, &c) + if err != nil { + t.Error(err) + } + + assert.Equal(t, 2, c.EstimatedTotal()) + assert.Equal(t, true, c.HasMore()) + assert.Equal(t, 1, c.PerPage()) + + called := false + i := 1 + + err = c.IterErr(context.Background(), func(r *Request) error { + called = true + assert.Equal(t, i, r.ID) + i++ + + return nil + }) + assert.Error(t, wantErr) + assert.True(t, called) + }) + + t.Run("result not mutate on next page", func(t *testing.T) { + secondPageJSON := []byte(`{ + "data": [ + {"id": 2} + ], + "meta": { + "pagination": { + "next_url": "https://example.com/next", + "per_page": 1, + "has_more": false, + "estimated_total": 2 + } + } + }`) + + c := &paddle.Collection[*Request]{} + m := &MockDoer{t: t, pages: [][]byte{secondPageJSON}} + c.Wants(m) + + err := json.Unmarshal(firstPageJSON, &c) + if err != nil { + t.Error(err) + } + + assert.Equal(t, 2, c.EstimatedTotal()) + assert.Equal(t, true, c.HasMore()) + assert.Equal(t, 1, c.PerPage()) + + called := false + var foundReq *Request + + err = c.IterErr(context.Background(), func(r *Request) error { + if called { + return nil + } + + foundReq = r + called = true + + return nil + }) + + assert.NoError(t, err) + assert.True(t, called) + assert.Equal(t, 1, foundReq.ID) + }) +} + +type MockDoer struct { + i int + t *testing.T + pages [][]byte + err error +} + +func (m *MockDoer) Do(_ context.Context, method, path string, src, dst any) error { + m.t.Helper() + + assert.Equal(m.t, "GET", method) + + typ := &paddle.Collection[*Request]{} + require.IsType(m.t, &typ, dst) + + if m.err != nil { + return m.err + } + + defer func() { + m.i++ + }() + + return json.Unmarshal(m.pages[m.i], dst) +} diff --git a/context.go b/context.go new file mode 100644 index 0000000..12c443b --- /dev/null +++ b/context.go @@ -0,0 +1,15 @@ +package paddle + +import ( + "context" + + "github.com/PaddleHQ/paddle-go-sdk/internal/client" +) + +// ContextWithTransitID returns a new context with the provided transitID. +// This transit ID will then be present in requests to the Paddle API where this +// context is used. These can be used by Paddle support to aid in debugging your +// integration. +func ContextWithTransitID(ctx context.Context, transitID string) context.Context { + return client.ContextWithTransitID(ctx, transitID) +} diff --git a/currencies.go b/currencies.go new file mode 100644 index 0000000..410a38b --- /dev/null +++ b/currencies.go @@ -0,0 +1,25 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" + +// ErrCurrencyPrimaryBalanceInvalid represents a `currency_primary_balance_invalid` error. +// See https://developer.paddle.com/errors/currencies/currency_primary_balance_invalid for more information. +var ErrCurrencyPrimaryBalanceInvalid = &paddleerr.Error{ + Code: "currency_primary_balance_invalid", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrCurrencyCodeInvalid represents a `currency_code_invalid` error. +// See https://developer.paddle.com/errors/currencies/currency_code_invalid for more information. +var ErrCurrencyCodeInvalid = &paddleerr.Error{ + Code: "currency_code_invalid", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrCurrencyCodesInvalid represents a `currency_codes_invalid` error. +// See https://developer.paddle.com/errors/currencies/currency_codes_invalid for more information. +var ErrCurrencyCodesInvalid = &paddleerr.Error{ + Code: "currency_codes_invalid", + Type: paddleerr.ErrorTypeRequestError, +} diff --git a/customers.go b/customers.go new file mode 100644 index 0000000..27225e6 --- /dev/null +++ b/customers.go @@ -0,0 +1,164 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import ( + "context" + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrCustomerAlreadyExists represents a `customer_already_exists` error. +// See https://developer.paddle.com/errors/customers/customer_already_exists for more information. +var ErrCustomerAlreadyExists = &paddleerr.Error{ + Code: "customer_already_exists", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrCustomerEmailDomainNotAllowed represents a `customer_email_domain_not_allowed` error. +// See https://developer.paddle.com/errors/customers/customer_email_domain_not_allowed for more information. +var ErrCustomerEmailDomainNotAllowed = &paddleerr.Error{ + Code: "customer_email_domain_not_allowed", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrCustomerEmailInvalid represents a `customer_email_invalid` error. +// See https://developer.paddle.com/errors/customers/customer_email_invalid for more information. +var ErrCustomerEmailInvalid = &paddleerr.Error{ + Code: "customer_email_invalid", + Type: paddleerr.ErrorTypeRequestError, +} + +// CustomerIncludes: Represents a customer entity with included entities. +type CustomerIncludes struct { + // ID: Unique Paddle ID for this customer entity, prefixed with `ctm_`. + ID string `json:"id,omitempty"` + // Name: Full name of this customer. Required when creating transactions where `collection_mode` is `manual` (invoices). + Name *string `json:"name,omitempty"` + // Email: Email address for this customer. + Email string `json:"email,omitempty"` + /* + MarketingConsent: Whether this customer opted into marketing from you. + Set to `true` by Paddle where a customer checks the marketing consent box when using Paddle Checkout; `false` otherwise. + */ + MarketingConsent bool `json:"marketing_consent,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status string `json:"status,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // Locale: Valid IETF BCP 47 short form locale tag. If omitted, defaults to `en`. + Locale string `json:"locale,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` +} + +// CustomersClient is a client for the Customers resource. +type CustomersClient struct { + doer Doer +} + +// ListCustomersRequest is given as an input to ListCustomers. +type ListCustomersRequest struct { + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // Email is a query parameter. + // Return entities that exactly match the specified email address. Use a comma-separated list to specify multiple email addresses. Recommended for precise matching of email addresses. + Email []string `in:"query=email,omitempty" json:"-"` + // ID is a query parameter. + // Return only the IDs specified. Use a comma-separated list to get multiple entities. + ID []string `in:"query=id,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `id`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `50`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // Search is a query parameter. + // Return entities that match a search query. Searches `id`, `name`, and `email` fields. Use the `email` query parameter for precise matching of email addresses. + Search *string `in:"query=search,omitempty" json:"-"` + // Status is a query parameter. + // Return entities that match the specified status. Use a comma-separated list to specify multiple status values. + Status []string `in:"query=status,omitempty" json:"-"` +} + +// ListCustomers performs the GET operation on a Customers resource. +func (c *CustomersClient) ListCustomers(ctx context.Context, req *ListCustomersRequest) (res *Collection[*Customer], err error) { + if err := c.doer.Do(ctx, "GET", "/customers", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// CreateCustomerRequest is given as an input to CreateCustomer. +type CreateCustomerRequest struct { + // Email: Email address for this customer. + Email string `json:"email,omitempty"` + // Name: Full name of this customer. Required when creating transactions where `collection_mode` is `manual` (invoices). + Name *string `json:"name,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // Locale: Valid IETF BCP 47 short form locale tag. If omitted, defaults to `en`. + Locale *string `json:"locale,omitempty"` +} + +// CreateCustomer performs the POST operation on a Customers resource. +func (c *CustomersClient) CreateCustomer(ctx context.Context, req *CreateCustomerRequest) (res *Customer, err error) { + if err := c.doer.Do(ctx, "POST", "/customers", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// GetCustomerRequest is given as an input to GetCustomer. +type GetCustomerRequest struct { + // URL path parameters. + CustomerID string `in:"path=customer_id" json:"-"` +} + +// GetCustomer performs the GET operation on a Customers resource. +func (c *CustomersClient) GetCustomer(ctx context.Context, req *GetCustomerRequest) (res *CustomerIncludes, err error) { + if err := c.doer.Do(ctx, "GET", "/customers/{customer_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// UpdateCustomerRequest is given as an input to UpdateCustomer. +type UpdateCustomerRequest struct { + // URL path parameters. + CustomerID string `in:"path=customer_id" json:"-"` + + // Name: Full name of this customer. Required when creating transactions where `collection_mode` is `manual` (invoices). + Name *PatchField[*string] `json:"name,omitempty"` + // Email: Email address for this customer. + Email *PatchField[string] `json:"email,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status *PatchField[string] `json:"status,omitempty"` + // CustomData: Your own structured key-value data. + CustomData *PatchField[CustomData] `json:"custom_data,omitempty"` + // Locale: Valid IETF BCP 47 short form locale tag. If omitted, defaults to `en`. + Locale *PatchField[string] `json:"locale,omitempty"` +} + +// UpdateCustomer performs the PATCH operation on a Customers resource. +func (c *CustomersClient) UpdateCustomer(ctx context.Context, req *UpdateCustomerRequest) (res *Customer, err error) { + if err := c.doer.Do(ctx, "PATCH", "/customers/{customer_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/discounts.go b/discounts.go new file mode 100644 index 0000000..aebd749 --- /dev/null +++ b/discounts.go @@ -0,0 +1,180 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import ( + "context" + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrDiscountExpired represents a `discount_expired` error. +// See https://developer.paddle.com/errors/discounts/discount_expired for more information. +var ErrDiscountExpired = &paddleerr.Error{ + Code: "discount_expired", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrDiscountUsageLimitExceeded represents a `discount_usage_limit_exceeded` error. +// See https://developer.paddle.com/errors/discounts/discount_usage_limit_exceeded for more information. +var ErrDiscountUsageLimitExceeded = &paddleerr.Error{ + Code: "discount_usage_limit_exceeded", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrDiscountCodeConflict represents a `discount_code_conflict` error. +// See https://developer.paddle.com/errors/discounts/discount_code_conflict for more information. +var ErrDiscountCodeConflict = &paddleerr.Error{ + Code: "discount_code_conflict", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrDiscountRestrictedProductNotActive represents a `discount_restricted_product_not_active` error. +// See https://developer.paddle.com/errors/discounts/discount_restricted_product_not_active for more information. +var ErrDiscountRestrictedProductNotActive = &paddleerr.Error{ + Code: "discount_restricted_product_not_active", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrDiscountRestrictedProductPriceNotActive represents a `discount_restricted_product_price_not_active` error. +// See https://developer.paddle.com/errors/discounts/discount_restricted_product_price_not_active for more information. +var ErrDiscountRestrictedProductPriceNotActive = &paddleerr.Error{ + Code: "discount_restricted_product_price_not_active", + Type: paddleerr.ErrorTypeRequestError, +} + +// DiscountsClient is a client for the Discounts resource. +type DiscountsClient struct { + doer Doer +} + +// ListDiscountsRequest is given as an input to ListDiscounts. +type ListDiscountsRequest struct { + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // Code is a query parameter. + // Return entities that match the discount code. Use a comma-separated list to specify multiple discount codes. + Code []string `in:"query=code,omitempty" json:"-"` + // ID is a query parameter. + // Return only the IDs specified. Use a comma-separated list to get multiple entities. + ID []string `in:"query=id,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `created_at` and `id`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `50`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // Status is a query parameter. + // Return entities that match the specified status. Use a comma-separated list to specify multiple status values. + Status []string `in:"query=status,omitempty" json:"-"` +} + +// ListDiscounts performs the GET operation on a Discounts resource. +func (c *DiscountsClient) ListDiscounts(ctx context.Context, req *ListDiscountsRequest) (res *Collection[*Discount], err error) { + if err := c.doer.Do(ctx, "GET", "/discounts", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// CreateDiscountRequest is given as an input to CreateDiscount. +type CreateDiscountRequest struct { + // Amount: Amount to discount by. For `percentage` discounts, must be an amount between `0.01` and `100`. For `flat` and `flat_per_seat` discounts, amount in the lowest denomination for a currency. + Amount string `json:"amount,omitempty"` + // Description: Short description for this discount for your reference. Not shown to customers. + Description string `json:"description,omitempty"` + // Type: Type of discount. + Type string `json:"type,omitempty"` + // EnabledForCheckout: Whether this discount can be applied by a customer at checkout. + EnabledForCheckout *bool `json:"enabled_for_checkout,omitempty"` + // Code: Unique code that customers can use to apply this discount at checkout. Use letters and numbers only, up to 16 characters. Paddle generates a random 10-character code if a code is not provided and `enabled_for_checkout` is `true`. + Code *string `json:"code,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Required where discount type is `flat` or `flat_per_seat`. + CurrencyCode *string `json:"currency_code,omitempty"` + // Recur: Whether this discount applies for multiple billing periods. + Recur *bool `json:"recur,omitempty"` + // MaximumRecurringIntervals: Amount of subscription billing periods that this discount recurs for. Requires `recur`. `null` if this discount recurs forever. + MaximumRecurringIntervals *int `json:"maximum_recurring_intervals,omitempty"` + // UsageLimit: Maximum amount of times this discount can be used. This is an overall limit, rather than a per-customer limit. `null` if this discount can be used an unlimited amount of times. + UsageLimit *int `json:"usage_limit,omitempty"` + // RestrictTo: Product or price IDs that this discount is for. When including a product ID, all prices for that product can be discounted. `null` if this discount applies to all products and prices. + RestrictTo []string `json:"restrict_to,omitempty"` + // ExpiresAt: RFC 3339 datetime string of when this discount expires. Discount can no longer be applied after this date has elapsed. `null` if this discount can be applied forever. + ExpiresAt *string `json:"expires_at,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` +} + +// CreateDiscount performs the POST operation on a Discounts resource. +func (c *DiscountsClient) CreateDiscount(ctx context.Context, req *CreateDiscountRequest) (res *Discount, err error) { + if err := c.doer.Do(ctx, "POST", "/discounts", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// GetDiscountRequest is given as an input to GetDiscount. +type GetDiscountRequest struct { + // URL path parameters. + DiscountID string `in:"path=discount_id" json:"-"` +} + +// GetDiscount performs the GET operation on a Discounts resource. +func (c *DiscountsClient) GetDiscount(ctx context.Context, req *GetDiscountRequest) (res *Discount, err error) { + if err := c.doer.Do(ctx, "GET", "/discounts/{discount_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// UpdateDiscountRequest is given as an input to UpdateDiscount. +type UpdateDiscountRequest struct { + // URL path parameters. + DiscountID string `in:"path=discount_id" json:"-"` + + // Status: Whether this entity can be used in Paddle. `expired` and `used` are set automatically by Paddle. + Status *PatchField[string] `json:"status,omitempty"` + // Description: Short description for this discount for your reference. Not shown to customers. + Description *PatchField[string] `json:"description,omitempty"` + // EnabledForCheckout: Whether this discount can be applied by a customer at checkout. + EnabledForCheckout *PatchField[bool] `json:"enabled_for_checkout,omitempty"` + // Code: Unique code that customers can use to apply this discount at checkout. Use letters and numbers only, up to 16 characters. Paddle generates a random 10-character code if a code is not provided and `enabled_for_checkout` is `true`. + Code *PatchField[*string] `json:"code,omitempty"` + // Type: Type of discount. + Type *PatchField[string] `json:"type,omitempty"` + // Amount: Amount to discount by. For `percentage` discounts, must be an amount between `0.01` and `100`. For `flat` and `flat_per_seat` discounts, amount in the lowest denomination for a currency. + Amount *PatchField[string] `json:"amount,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Required where discount type is `flat` or `flat_per_seat`. + CurrencyCode *PatchField[*string] `json:"currency_code,omitempty"` + // Recur: Whether this discount applies for multiple billing periods. + Recur *PatchField[bool] `json:"recur,omitempty"` + // MaximumRecurringIntervals: Amount of subscription billing periods that this discount recurs for. Requires `recur`. `null` if this discount recurs forever. + MaximumRecurringIntervals *PatchField[*int] `json:"maximum_recurring_intervals,omitempty"` + // UsageLimit: Maximum amount of times this discount can be used. This is an overall limit, rather than a per-customer limit. `null` if this discount can be used an unlimited amount of times. + UsageLimit *PatchField[*int] `json:"usage_limit,omitempty"` + // RestrictTo: Product or price IDs that this discount is for. When including a product ID, all prices for that product can be discounted. `null` if this discount applies to all products and prices. + RestrictTo *PatchField[[]string] `json:"restrict_to,omitempty"` + // ExpiresAt: RFC 3339 datetime string of when this discount expires. Discount can no longer be applied after this date has elapsed. `null` if this discount can be applied forever. + ExpiresAt *PatchField[*string] `json:"expires_at,omitempty"` + // CustomData: Your own structured key-value data. + CustomData *PatchField[CustomData] `json:"custom_data,omitempty"` +} + +// UpdateDiscount performs the PATCH operation on a Discounts resource. +func (c *DiscountsClient) UpdateDiscount(ctx context.Context, req *UpdateDiscountRequest) (res *Discount, err error) { + if err := c.doer.Do(ctx, "PATCH", "/discounts/{discount_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/events.go b/events.go new file mode 100644 index 0000000..3fdc701 --- /dev/null +++ b/events.go @@ -0,0 +1,51 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import "context" + +// Event: Represents an event entity. +type Event struct { + // EventID: Unique Paddle ID for this event, prefixed with `evt_`. + EventID string `json:"event_id,omitempty"` + // EventType: Type of event sent by Paddle, in the format `entity.event_type`. + EventType string `json:"event_type,omitempty"` + // OccurredAt: RFC 3339 datetime string of when this event occurred. + OccurredAt string `json:"occurred_at,omitempty"` + // Data: New or changed entity. + Data any `json:"data,omitempty"` +} + +// EventsClient is a client for the Events resource. +type EventsClient struct { + doer Doer +} + +// ListEventsRequest is given as an input to ListEvents. +type ListEventsRequest struct { + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `id` (for `event_id`). + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `50`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` +} + +// ListEvents performs the GET operation on a Events resource. +func (c *EventsClient) ListEvents(ctx context.Context, req *ListEventsRequest) (res *Collection[*Event], err error) { + if err := c.doer.Do(ctx, "GET", "/events", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/events_types.go b/events_types.go new file mode 100644 index 0000000..40baf6d --- /dev/null +++ b/events_types.go @@ -0,0 +1,21 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import "context" + +// EventsTypesClient is a client for the Events types resource. +type EventsTypesClient struct { + doer Doer +} + +// ListEventTypesRequest is given as an input to ListEventTypes. +type ListEventTypesRequest struct{} + +// ListEventTypes performs the GET operation on a Events types resource. +func (c *EventsTypesClient) ListEventTypes(ctx context.Context, req *ListEventTypesRequest) (res *Collection[*EventType], err error) { + if err := c.doer.Do(ctx, "GET", "/event-types", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/example_create_test.go b/example_create_test.go new file mode 100644 index 0000000..4f583a8 --- /dev/null +++ b/example_create_test.go @@ -0,0 +1,69 @@ +package paddle_test + +import ( + "context" + "fmt" + "os" + "time" + + paddle "github.com/PaddleHQ/paddle-go-sdk" +) + +func Example_create() { + // Create a mock HTTP server for this example - skip over this bit! + s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{transaction}}}) + + // Create a new Paddle client. + client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration. + ) + + if err != nil { + fmt.Println(err) + return + } + + ctx := context.Background() + + // Optionally set a transit ID on the context. This is useful to link your + // own request IDs to Paddle API requests. + ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1") + + // Create a transaction. + res, err := client.CreateTransaction(ctx, &paddle.CreateTransactionRequest{ + Items: []paddle.CreateTransactionItems{ + *paddle.NewCreateTransactionItemsCatalogItem(&paddle.CatalogItem{ + Quantity: 20, + PriceID: "pri_01gsz91wy9k1yn7kx82aafwvea", + }), + *paddle.NewCreateTransactionItemsCatalogItem(&paddle.CatalogItem{ + Quantity: 1, + PriceID: "pri_01gsz96z29d88jrmsf2ztbfgjg", + }), + *paddle.NewCreateTransactionItemsCatalogItem(&paddle.CatalogItem{ + Quantity: 1, + PriceID: "pri_01gsz98e27ak2tyhexptwc58yk", + }), + }, + CustomerID: paddle.PtrTo("ctm_01hv6y1jedq4p1n0yqn5ba3ky4"), + AddressID: paddle.PtrTo("add_01hv8gq3318ktkfengj2r75gfx"), + CurrencyCode: paddle.PtrTo(paddle.CurrencyCodeUSD), + CollectionMode: paddle.PtrTo(paddle.CollectionModeManual), + BillingDetails: &paddle.BillingDetails{ + EnableCheckout: false, + PurchaseOrderNumber: "PO-123", + PaymentTerms: paddle.Duration{ + Interval: paddle.IntervalDay, + Frequency: 14, + }, + }, + BillingPeriod: &paddle.TimePeriod{ + StartsAt: time.Date(2024, 4, 12, 0, 0, 0, 0, time.UTC).Format(time.RFC3339), + EndsAt: time.Date(2024, 4, 12, 0, 0, 0, 0, time.UTC).Format(time.RFC3339), + }, + }) + + fmt.Println(res.ID, err) + // Output: txn_01hv8m0mnx3sj85e7gxc6kga03 +} diff --git a/example_get_test.go b/example_get_test.go new file mode 100644 index 0000000..6d16f85 --- /dev/null +++ b/example_get_test.go @@ -0,0 +1,39 @@ +package paddle_test + +import ( + "context" + "fmt" + "os" + + paddle "github.com/PaddleHQ/paddle-go-sdk" +) + +func Example_get() { + // Create a mock HTTP server for this example - skip over this bit! + s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{transaction}}}) + + // Create a new Paddle client. + client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration. + ) + + if err != nil { + fmt.Println(err) + return + } + + ctx := context.Background() + + // Optionally set a transit ID on the context. This is useful to link your + // own request IDs to Paddle API requests. + ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1") + + // Get a transaction. + res, err := client.GetTransaction(ctx, &paddle.GetTransactionRequest{ + TransactionID: "txn_01hv8m0mnx3sj85e7gxc6kga03", + }) + + fmt.Println(res.ID, err) + // Output: txn_01hv8m0mnx3sj85e7gxc6kga03 +} diff --git a/example_list_test.go b/example_list_test.go new file mode 100644 index 0000000..da1662d --- /dev/null +++ b/example_list_test.go @@ -0,0 +1,91 @@ +package paddle_test + +import ( + "context" + "fmt" + "os" + + paddle "github.com/PaddleHQ/paddle-go-sdk" +) + +func Example_list() { + // Create a mock HTTP server for this example - skip over this bit! + s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{transactions}}}) + + // Create a new Paddle client. + client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration. + ) + + if err != nil { + fmt.Println(err) + return + } + + ctx := context.Background() + + // Optionally set a transit ID on the context. This is useful to link your + // own request IDs to Paddle API requests. + ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1") + + // Get a collection of transactions. + res, err := client.ListTransactions(ctx, &paddle.ListTransactionsRequest{}) + + // Iterate the transactions. + err = res.Iter(ctx, func(v *paddle.TransactionIncludes) (bool, error) { + fmt.Println(v.ID) + return true, nil + }) + fmt.Println(err) + + // Output: + //txn_01hv8xxw3etar07vaxsqbyqasy + //txn_01hv8xbtmb6zc7c264ycteehth + //txn_01hv8wptq8987qeep44cyrewp9 + // +} + +func Example_list_paginate() { + // Create a mock HTTP server for this example - skip over this bit! + s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{ + transactionsPaginatedPg1, + transactionsPaginatedPg2, + }}}) + + // Create a new Paddle client. + client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration. + ) + + if err != nil { + fmt.Println(err) + return + } + + ctx := context.Background() + + // Optionally set a transit ID on the context. This is useful to link your + // own request IDs to Paddle API requests. + ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1") + + // Get a collection of transactions. + res, err := client.ListTransactions(ctx, &paddle.ListTransactionsRequest{}) + + // Iterate the transactions which will internally paginate to the next page. + err = res.Iter(ctx, func(v *paddle.TransactionIncludes) (bool, error) { + fmt.Println(v.ID) + return true, nil + }) + fmt.Println(err) + + // Output: + //txn_01hv8xxw3etar07vaxsqbyqasy + //txn_01hv8xbtmb6zc7c264ycteehth + //txn_01hv8wptq8987qeep44cyrewp9 + //txn_01hv8wnvvtedwjrhfhpr9vkq9w + //txn_01hv8m0mnx3sj85e7gxc6kga03 + //txn_01hv8kxg3hxyxs9t471ms9kfsz + // +} diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..6e2eb56 --- /dev/null +++ b/example_test.go @@ -0,0 +1,61 @@ +package paddle_test + +import ( + "embed" + "net/http" + "net/http/httptest" +) + +type stubPath string + +const ( + transaction stubPath = "testdata/transaction.json" + transactions stubPath = "testdata/transactions.json" + transactionsPaginatedPg1 stubPath = "testdata/transactions_paginated_pg1.json" + transactionsPaginatedPg2 stubPath = "testdata/transactions_paginated_pg2.json" + priceCreatedEvent stubPath = "testdata/price_created.json" +) + +//go:embed testdata +var exampleData embed.FS + +type stub struct { + paths []stubPath +} + +type mockServerResponse struct { + stub *stub + body *[]byte +} + +func mockServerForExample(res mockServerResponse) *httptest.Server { + call := 0 + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + var body []byte + + if res.stub != nil && len(res.stub.paths) > 0 { + stub := res.stub.paths[0] + if len(res.stub.paths) > 1 { + stub = res.stub.paths[call] + } + c, err := exampleData.ReadFile(string(stub)) + if err != nil { + w.Write([]byte(err.Error())) + return + } + body = c + } + + if res.body != nil { + body = *res.body + } + + w.Write(body) + call++ + })) + + return s +} diff --git a/example_update_test.go b/example_update_test.go new file mode 100644 index 0000000..49b396f --- /dev/null +++ b/example_update_test.go @@ -0,0 +1,53 @@ +package paddle_test + +import ( + "context" + "fmt" + "os" + + paddle "github.com/PaddleHQ/paddle-go-sdk" +) + +func Example_update() { + // Create a mock HTTP server for this example - skip over this bit! + s := mockServerForExample(mockServerResponse{stub: &stub{paths: []stubPath{transaction}}}) + + // Create a new Paddle client. + client, err := paddle.New( + os.Getenv("PADDLE_API_KEY"), + paddle.WithBaseURL(s.URL), // Uses the mock server, you will not need this in your integration. + ) + + if err != nil { + fmt.Println(err) + return + } + + ctx := context.Background() + + // Optionally set a transit ID on the context. This is useful to link your + // own request IDs to Paddle API requests. + ctx = paddle.ContextWithTransitID(ctx, "sdk-testing-request-1") + + // Update a transaction. + res, err := client.UpdateTransaction(ctx, &paddle.UpdateTransactionRequest{ + DiscountID: paddle.NewPtrPatchField("dsc_01gtgztp8fpchantd5g1wrksa3"), + Items: paddle.NewPatchField([]paddle.UpdateTransactionItems{ + *paddle.NewUpdateTransactionItemsCatalogItem(&paddle.CatalogItem{ + Quantity: 50, + PriceID: "pri_01gsz91wy9k1yn7kx82aafwvea", + }), + *paddle.NewUpdateTransactionItemsCatalogItem(&paddle.CatalogItem{ + Quantity: 1, + PriceID: "pri_01gsz96z29d88jrmsf2ztbfgjg", + }), + *paddle.NewUpdateTransactionItemsCatalogItem(&paddle.CatalogItem{ + Quantity: 1, + PriceID: "pri_01gsz98e27ak2tyhexptwc58yk", + }), + }), + }) + + fmt.Println(res.ID, err) + // Output: txn_01hv8m0mnx3sj85e7gxc6kga03 +} diff --git a/example_webhook_verifier_test.go b/example_webhook_verifier_test.go new file mode 100644 index 0000000..0a44368 --- /dev/null +++ b/example_webhook_verifier_test.go @@ -0,0 +1,123 @@ +package paddle_test + +import ( + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + + paddle "github.com/PaddleHQ/paddle-go-sdk" +) + +const ( + exampleSignature = `ts=1710929255;h1=6c05ef8fa83c44d751be6d259ec955ce5638e2c54095bf128e408e2fce1589c8` + examplePayload = `{"data":{"id":"pri_01hsdn96k2hxjzsq5yerecdj9j","name":null,"status":"active","quantity":{"maximum":999999,"minimum":1},"tax_mode":"account_setting","product_id":"pro_01hsdn8qp7yydry3x1yeg6a9rv","unit_price":{"amount":"1000","currency_code":"USD"},"custom_data":null,"description":"testing","import_meta":null,"trial_period":null,"billing_cycle":{"interval":"month","frequency":1},"unit_price_overrides":[]},"event_id":"evt_01hsdn97563968dy0szkmgjwh3","event_type":"price.created","occurred_at":"2024-03-20T10:07:35.590857Z","notification_id":"ntf_01hsdn977e920kbgzt6r6c9rqc"}` + exampleSecretKey = `pdl_ntfset_01hsdn8d43dt7mezr1ef2jtbaw_hKkRiCGyyRhbFwIUuqiTBgI7gnWoV0Gr` +) + +func ExampleWebhookVerifier_Verify() { + // Create a WebhookVerifier with your secret key + // You should keep your secret outside the src, e.g. as an env variable + verifier := paddle.NewWebhookVerifier(exampleSecretKey) + + // Create a server to receive the webhook + // Note: you may have this in place already + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Verify the request with the verifier + ok, err := verifier.Verify(r) + + // Check no error occurred during verification and return an appropriate response + if err != nil && (errors.Is(err, paddle.ErrMissingSignature) || errors.Is(err, paddle.ErrInvalidSignatureFormat)) { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } else if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Check if verification was successful + if !ok { + // Return an appropriate response to let Paddle know + http.Error(w, "signature mismatch", http.StatusForbidden) + return + } + + // Best practice here is to check if you have processed this webhook already using the event id + // At this point you should store for async processing + // For example a local queue or db entry + + // Respond as soon as possible with a 200 OK + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"success": true}`)) + })) + + // We're simulating a call to the server, everything below can be skipped in your implementation + + req, err := http.NewRequest(http.MethodPost, s.URL, strings.NewReader(examplePayload)) + if err != nil { + fmt.Println(err) + return + } + req.Header.Set("Paddle-Signature", exampleSignature) + + client := http.Client{} + res, err := client.Do(req) + if err != nil { + fmt.Println(err) + return + } + + body, err := io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + return + } + + defer res.Body.Close() + + fmt.Println(string(body), err) + // Output: {"success": true} +} + +func ExampleWebhookVerifier_Middleware() { + // Create a WebhookVerifier with your secret key + // You should keep your secret outside the src, e.g. as an env variable + verifier := paddle.NewWebhookVerifier(exampleSecretKey) + + // Wrap your handler with the verifier.Middleware method + handler := verifier.Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // The request making it this far means the webhook was verified + // Best practice here is to check if you have processed this webhook already using the event id + // At this point you should store for async processing + // For example a local queue or db entry + + // Respond as soon as possible with a 200 OK + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"success": true}`)) + })) + + // We're simulating a call to the server, everything below can be skipped in your implementation + + req, err := http.NewRequest(http.MethodPost, "localhost:8081", strings.NewReader(examplePayload)) + if err != nil { + fmt.Println(err) + return + } + req.Header.Set("Paddle-Signature", exampleSignature) + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + body, err := io.ReadAll(rr.Body) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(string(body), err) + // Output: {"success": true} +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ca0d4b8 --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module github.com/PaddleHQ/paddle-go-sdk + +go 1.21.0 + +require ( + github.com/ggicci/httpin v0.16.0 + github.com/hashicorp/go-cleanhttp v0.5.2 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/ggicci/owl v0.7.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d65fd79 --- /dev/null +++ b/go.sum @@ -0,0 +1,18 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ggicci/httpin v0.16.0 h1:ZR6RXH1xNWg39xqM33V7iz7PP/GuR7vc3aHa2g5mWo4= +github.com/ggicci/httpin v0.16.0/go.mod h1:whE/5nx1jCp//UQ6rgNpq2WNxOr9FV0OpxMnQQC0Xvs= +github.com/ggicci/owl v0.7.0 h1:+AMlCR0AY7j72q7hjtN4pm8VJiikwpROtMgvPnXtuik= +github.com/ggicci/owl v0.7.0/go.mod h1:TRPWshRwYej6uES//YW5aNgLB370URwyta1Ytfs7KXs= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= +github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/client/client.go b/internal/client/client.go new file mode 100644 index 0000000..31ae136 --- /dev/null +++ b/internal/client/client.go @@ -0,0 +1,176 @@ +// Package client provides the base for all requests and responses to the +// Paddle API. +package client + +import ( + "bytes" + "context" + "embed" + "encoding/json" + "io" + "net/http" + "net/url" + "reflect" + "strings" + + "github.com/PaddleHQ/paddle-go-sdk/internal/response" + + "github.com/ggicci/httpin" + "github.com/ggicci/httpin/core" +) + +// Client provides the base for all requests and responses to the Paddle API. +type Client struct { + client HTTPDoer + + apiKey string + baseURL *url.URL + version string +} + +// HTTPDoer is an interface that expects the Do method for making HTTP requests. +type HTTPDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +//go:embed version.txt +var versionFs embed.FS + +func init() { + core.RegisterDirective("paddle_include", &DirectiveInclude{}) + core.RegisterDirective("query", &DirectiveQuery{}, true) +} + +// New returns a new Client with the provided http.Client, apiKey, and baseURL. +func New(httpClient HTTPDoer, apiKey string, baseURL *url.URL) (*Client, error) { + version, err := versionFs.ReadFile("version.txt") + if err != nil { + return nil, err + } + + return &Client{ + client: httpClient, + + apiKey: apiKey, + baseURL: baseURL, + version: strings.TrimSuffix(string(version), "\n"), + }, nil +} + +// Wanter is an interface that can be implemented by a response struct. +// It is used to inject the client into to the response struct after a request +// has been successfully made. +type Wanter interface { + Wants(Doer) +} + +// Doer is an interface that expects the Do method for making API requests. +type Doer interface { + Do(ctx context.Context, method, path string, src, dst any) (err error) +} + +// Do sends an API request and returns the API response. The src field is a +// request which contains `json` or `in` struct tags (see github.com/ggicci/httpin) +// and will be encoded as the request body. The dst field is a response which will +// be decoded from the response body. The dst field should be given as a pointer. +// If the src field is nil, no request body will be sent. If the dst field is nil, +// no response body will be decoded. +func (c *Client) Do(ctx context.Context, method, path string, src, dst any) (err error) { //nolint:cyclop // flat function + if ctx == nil { + ctx = context.Background() + } + + u, err := url.Parse(path) + if err != nil { + return err + } + + u.Host = c.baseURL.Host + u.Scheme = c.baseURL.Scheme + + req, err := encodeRequest(ctx, method, u, src) + if err != nil { + return err + } + + req.Header.Set("Authorization", "Bearer "+c.apiKey) + req.Header.Set("User-Agent", "PaddleSDK/go "+c.version) + + if transitID := TransitIDFromContext(ctx); transitID != "" { + req.Header.Set("X-Transaction-Id", transitID) // Deprecated. + req.Header.Set("X-Transit-Id", transitID) + } + + if err := attachRequestBody(req, method, src); err != nil { + return err + } + + res, requestErr := c.client.Do(req) + defer func() { + if requestErr != nil { + return + } + + if dErr := res.Body.Close(); dErr != nil { + err = dErr + } + }() + + if err := response.HandleError(req, res, requestErr); err != nil { + return err + } + + // We don't return the error from response.Handle here, as we want to return + // the error from the response body closing in the defer func above. + err = response.Handle(req, res, dst) + + if dst != nil { + v := reflect.ValueOf(dst).Elem().Interface() + if wanter, ok := v.(Wanter); ok { + wanter.Wants(c) + } + } + + return +} + +// encodeRequest encodes a request with the given method, URL, and source. +// If the source is nil, then we completely skip `httpin`'s encoder. This is +// because the `httpin` package does not support nil bodies, and empty request +// payloads will cause URL to be completely rewritten. +func encodeRequest(ctx context.Context, method string, u *url.URL, src any) (*http.Request, error) { + if src == nil { + return http.NewRequestWithContext(ctx, method, u.String(), http.NoBody) + } + + return httpin.NewRequestWithContext(ctx, method, u.String(), src) +} + +func attachRequestBody(req *http.Request, method string, src any) error { + if src == nil { + return nil + } + + body := bytes.NewBuffer([]byte{}) + + if err := json.NewEncoder(body).Encode(src); err != nil { + return err + } + + if method == http.MethodGet || method == http.MethodDelete { + return nil + } + + if strings.TrimSpace(body.String()) == "{}" { + return nil + } + + req.Body = io.NopCloser(body) + req.Header.Set("Content-Type", "application/json") + + req.GetBody = func() (io.ReadCloser, error) { + return io.NopCloser(body), nil + } + + return nil +} diff --git a/internal/client/client_test.go b/internal/client/client_test.go new file mode 100644 index 0000000..60b3db6 --- /dev/null +++ b/internal/client/client_test.go @@ -0,0 +1,123 @@ +package client_test + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/PaddleHQ/paddle-go-sdk/internal/client" + "github.com/PaddleHQ/paddle-go-sdk/internal/response" + "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +func ptr[V any](v V) *V { + return &v +} + +func TestClient(t *testing.T) { + type args struct { + name string + + req any + res any + path string + + expectedRes any + expectedPath string + expectedError *paddleerr.Error + } + + cases := []args{ + { + name: "simple request in/out", + + req: &Req{ID: "123", Optional: ptr("test")}, + res: &Res{}, + path: "/", + + expectedRes: &Res{ID: "123"}, + expectedPath: "/?id=123&optional=test", + }, + { + name: "path rewrite", + + req: &ReqPath{ID: "123"}, + res: &Res{}, + path: "/test/{id}", + + expectedRes: &Res{ID: "123"}, + expectedPath: "/test/123", + }, + { + name: "simple request in/out", + + req: &Req{ID: "123"}, + res: &Res{}, + path: "/", + + expectedRes: &Res{ID: "123"}, + expectedPath: "/?id=123", + }, + } + + apiKey := "my-example-api-key-here" + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tt.expectedPath, r.URL.String()) + assert.Equal(t, "Bearer "+apiKey, r.Header.Get("Authorization")) + + res := response.Response[any]{ + Data: tt.expectedRes, + } + + if tt.expectedError != nil { + res.Data, res.Error = nil, tt.expectedError + } + + j, err := json.Marshal(res) + require.NoError(t, err) + + w.WriteHeader(http.StatusOK) + + _, err = w.Write(j) + require.NoError(t, err) + })) + + parsedURL, err := url.Parse(s.URL) + require.NoError(t, err) + + c, err := client.New(http.DefaultClient, apiKey, parsedURL) + require.NoError(t, err) + + err = c.Do(context.Background(), "GET", tt.path, tt.req, &tt.res) + if tt.expectedError != nil { + assert.ErrorIs(t, err, tt.expectedError) + } else { + require.NoError(t, err) + } + + assert.Equal(t, tt.expectedRes, tt.res) + }) + } +} + +type Req struct { + ID string `in:"query=id"` + Optional *string `in:"query=optional,omitempty"` +} + +type ReqPath struct { + ID string `in:"path=id"` +} + +type Res struct { + ID string `json:"id"` +} diff --git a/internal/client/directive_include.go b/internal/client/directive_include.go new file mode 100644 index 0000000..3974e83 --- /dev/null +++ b/internal/client/directive_include.go @@ -0,0 +1,57 @@ +package client + +import ( + "errors" + "net/url" + "reflect" + + "github.com/ggicci/httpin/core" +) + +// DirectiveInclude is a directive that adds the custom 'include' query +// parameter to the request, based on the value of the directive's struct field. +type DirectiveInclude struct{} + +// Decode is not implemented. This is because we only deal with encoding +// concerns in the client. +func (*DirectiveInclude) Decode(_ *core.DirectiveRuntime) error { + return errors.New("not implemented") //nolint:goerr113 // typed error not needed here. +} + +// ErrDirectiveRequiresBool is returned when the directive's struct field is not +// a boolean. +var ErrDirectiveRequiresBool = errors.New("include directive requires a boolean value") + +// Encode adds/appends the custom 'include' query parameter to the request. +// The value of the directive's struct field is expected to be a boolean. +// If the value is true, the directive's value query parameter 'include'. +// e.g. +// +// type Request struct { +// IncludeCustomer bool `in:"paddle_include=customer"` +// } +// +// Req{IncludeCustomer: true} // ?paddle_include=customer +func (*DirectiveInclude) Encode(rtm *core.DirectiveRuntime) error { + if len(rtm.Directive.Argv) == 0 { + return nil + } + + if rtm.Value.Kind() != reflect.Bool { + return ErrDirectiveRequiresBool + } + + if !rtm.Value.Bool() { + return nil + } + + encoder := &core.FormEncoder{ + Setter: func(_ string, _ []string) { + rtm.GetRequestBuilder().Query["include"] = append( + rtm.GetRequestBuilder().Query["include"], url.QueryEscape(rtm.Directive.Argv[0]), + ) + }, + } + + return encoder.Execute(rtm) +} diff --git a/internal/client/directive_include_test.go b/internal/client/directive_include_test.go new file mode 100644 index 0000000..fa29435 --- /dev/null +++ b/internal/client/directive_include_test.go @@ -0,0 +1,81 @@ +package client_test + +import ( + "net/http" + "testing" + + "github.com/PaddleHQ/paddle-go-sdk/internal/client" + + "github.com/ggicci/httpin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDirectiveInclude(t *testing.T) { + type args struct { + name string + req any + + expectedURL string + expectedErr error + } + + cases := []args{ + { + name: "no include", + req: &Request{}, + + expectedURL: "/", + }, + { + name: "include one", + req: &Request{ + IncludeBusiness: true, + IncludeCustomer: false, + }, + + expectedURL: "/?include=business", + }, + { + name: "include both", + req: &Request{ + IncludeBusiness: true, + IncludeCustomer: true, + }, + + expectedURL: "/?include=business&include=customer", + }, + { + name: "wrong field type", + req: &NonBoolRequest{ + IncludeBusiness: "a", + }, + + expectedErr: client.ErrDirectiveRequiresBool, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + req, err := httpin.NewRequest(http.MethodGet, "/", c.req) + if c.expectedErr != nil { + require.Error(t, err) + assert.ErrorIs(t, err, c.expectedErr) + + return + } + + require.NoError(t, err) + assert.Equal(t, req.URL.String(), c.expectedURL) + }) + } +} + +type Request struct { + IncludeBusiness bool `in:"paddle_include=business"` + IncludeCustomer bool `in:"paddle_include=customer"` +} + +type NonBoolRequest struct { + IncludeBusiness string `in:"paddle_include=business"` +} diff --git a/internal/client/directive_query.go b/internal/client/directive_query.go new file mode 100644 index 0000000..4a880fd --- /dev/null +++ b/internal/client/directive_query.go @@ -0,0 +1,24 @@ +package client + +import ( + "strings" + + "github.com/ggicci/httpin/core" +) + +// DirectiveQuery extends the core.DirectiveQuery struct from the httpin package and adds support for the "omitempty" +// tag. +type DirectiveQuery struct { + core.DirectiveQuery +} + +// Encode adds the values of the input struct into the http.Request as query parameters. +// If the value is zero and the "omitempty" tag is present, the value will not be added to the query string. +func (d *DirectiveQuery) Encode(rtm *core.DirectiveRuntime) error { + tag := rtm.Resolver.Field.Tag.Get("in") + if rtm.Value.IsZero() && strings.Contains(tag, "omitempty") { + return nil + } + + return d.DirectiveQuery.Encode(rtm) +} diff --git a/internal/client/transit_id.go b/internal/client/transit_id.go new file mode 100644 index 0000000..66ffa0f --- /dev/null +++ b/internal/client/transit_id.go @@ -0,0 +1,20 @@ +package client + +import ( + "context" +) + +// transitIDContextKey is the context key for the transit ID. +type transitIDContextKey struct{} + +// ContextWithTransitID returns a new context with the provided transit ID. +func ContextWithTransitID(ctx context.Context, transitID string) context.Context { + return context.WithValue(ctx, transitIDContextKey{}, transitID) +} + +// TransitIDFromContext returns the transit ID from the provided context. +func TransitIDFromContext(ctx context.Context) string { + transitID, _ := ctx.Value(transitIDContextKey{}).(string) + + return transitID +} diff --git a/internal/client/transit_id_test.go b/internal/client/transit_id_test.go new file mode 100644 index 0000000..d042700 --- /dev/null +++ b/internal/client/transit_id_test.go @@ -0,0 +1,19 @@ +package client_test + +import ( + "context" + "testing" + + "github.com/PaddleHQ/paddle-go-sdk/internal/client" + + "github.com/stretchr/testify/assert" +) + +func TestTransitID(t *testing.T) { + ctx := context.Background() + ctx = client.ContextWithTransitID(ctx, "id") + + transitID := client.TransitIDFromContext(ctx) + + assert.Equal(t, "id", transitID) +} diff --git a/internal/client/version.txt b/internal/client/version.txt new file mode 100644 index 0000000..b82608c --- /dev/null +++ b/internal/client/version.txt @@ -0,0 +1 @@ +v0.1.0 diff --git a/internal/response/response.go b/internal/response/response.go new file mode 100644 index 0000000..57f433c --- /dev/null +++ b/internal/response/response.go @@ -0,0 +1,72 @@ +// Package response provides the response handling logic for responses and any +// errors returned by the Paddle API. +package response + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "net/http" + "reflect" + + "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// Response is the wrapper response type returned by the Paddle API. +type Response[T any] struct { + Data T `json:"data"` + Error *paddleerr.Error `json:"error"` + Meta Meta `json:"meta"` +} + +// Meta represents the metadata returned by the Paddle API. +type Meta struct { + RequestID string `json:"request_id"` + Pagination *Pagination `json:"pagination"` +} + +// Pagination represents the pagination information returned by the Paddle API. +type Pagination struct { + PerPage int `json:"per_page"` + Next string `json:"next"` + HasMore bool `json:"has_more"` + EstimatedTotal int `json:"estimated_total"` +} + +// ErrErrorHandlerCallExpected should never be returned, it indicates that the error handling logic has failed. +var ErrErrorHandlerCallExpected = errors.New("error handler should be called, this response should not be handled here") + +// Handle handles the response from the Paddle API. The dst field is a response +// which will be decoded from the response body. The dst field should be given as +// a pointer. If the dst field is nil, no response body will be decoded. +// It is expected that any error handling logic is done before calling this +// function, and that this is only for handling of successful responses. +func Handle(req *http.Request, res *http.Response, dst any) (err error) { + if res.StatusCode >= 400 { + return ErrErrorHandlerCallExpected + } + + if res.StatusCode == http.StatusNoContent { + return nil + } + + r := &Response[any]{ + Data: &dst, + } + + teedBytes := bytes.NewBuffer([]byte{}) + tee := io.TeeReader(res.Body, teedBytes) + + if dst != nil && reflect.TypeOf(dst).Elem().Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()) { + err = json.NewDecoder(tee).Decode(dst) + } else { + err = json.NewDecoder(tee).Decode(r) + } + + if err != nil { + return NewError(err, req.Method, req.URL.Path, res.StatusCode, teedBytes.Bytes()) + } + + return nil +} diff --git a/internal/response/response_api_error_handler.go b/internal/response/response_api_error_handler.go new file mode 100644 index 0000000..9bbed90 --- /dev/null +++ b/internal/response/response_api_error_handler.go @@ -0,0 +1,58 @@ +package response + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "net/http" + "strings" + + "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrUnexpectedResponse is returned when an paddle.Error was expected, but instead received nil. +var ErrUnexpectedResponse = errors.New("found nil paddle.Error when one was expected") + +// HandleError handles the error response from the Paddle API. This will handle +// cases where the response is the standard error response type from the API, +// and will return the decoded paddleerr.Error. If the response is not valid, +// a response.Error will be returned. +func HandleError(req *http.Request, res *http.Response, requestErr error) error { + if requestErr != nil { + return requestErr + } + + if res.StatusCode < 400 { + return nil + } + + contentType := res.Header.Get("Content-Type") + if !strings.Contains(contentType, "application/json") { + switch res.StatusCode { + case http.StatusBadGateway: + return &paddleerr.Error{ + Code: "bad_gateway", + Type: "request_error", + } + default: + return NewError(ErrUnexpectedResponse, req.Method, req.URL.Path, res.StatusCode, nil) + } + } + + var apiResponse Response[any] + + teedBytes := bytes.NewBuffer([]byte{}) + tee := io.TeeReader(res.Body, teedBytes) + + err := json.NewDecoder(tee).Decode(&apiResponse) + if err != nil { + return NewError(err, req.Method, req.URL.Path, res.StatusCode, teedBytes.Bytes()) + } + + if apiResponse.Error == nil { + return NewError(ErrUnexpectedResponse, req.Method, req.URL.Path, res.StatusCode, teedBytes.Bytes()) + } + + return apiResponse.Error +} diff --git a/internal/response/response_error.go b/internal/response/response_error.go new file mode 100644 index 0000000..3d495c9 --- /dev/null +++ b/internal/response/response_error.go @@ -0,0 +1,45 @@ +package response + +import ( + "fmt" +) + +// Error contains the details of a non-expected error when making a request to +// the Paddle API. This is not to be confused with the public `paddleerr.Error` +// type. These are returned in cases of non-expected errors, such as decoding, +// timeouts, malformed response bodies, etc. +// The cause of the error can be inspected using errors.Is. +type Error struct { + cause error + + Method string + Path string + + StatusCode int + ResponseBody []byte +} + +// NewError returns an instance of *Error, which conforms to the +// error interface and allows Unwrap. +func NewError(cause error, method, path string, statusCode int, body []byte) *Error { + return &Error{ + cause: cause, + + Method: method, + Path: path, + + StatusCode: statusCode, + ResponseBody: body, + } +} + +// Unwrap allows errors.Is on the underlying error to be performed. +func (de *Error) Unwrap() error { + return de.cause +} + +// Error conforms to the errors interface. +func (de *Error) Error() string { + return fmt.Sprintf("Decoding issue in call to %s %s (HTTP status %d): %s", + de.Method, de.Path, de.StatusCode, de.cause) +} diff --git a/ip_addresses.go b/ip_addresses.go new file mode 100644 index 0000000..d629092 --- /dev/null +++ b/ip_addresses.go @@ -0,0 +1,26 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import "context" + +type IPAddressesData struct { + // IPv4CIDRs: List of Paddle IPv4 CIDRs. + IPv4CIDRs []string `json:"ipv4_cidrs,omitempty"` +} + +// IPAddressesClient is a client for the IP addresses resource. +type IPAddressesClient struct { + doer Doer +} + +// GetIPAddressesRequest is given as an input to GetIPAddresses. +type GetIPAddressesRequest struct{} + +// GetIPAddresses performs the GET operation on a IP addresses resource. +func (c *IPAddressesClient) GetIPAddresses(ctx context.Context, req *GetIPAddressesRequest) (res *IPAddressesData, err error) { + if err := c.doer.Do(ctx, "GET", "/ips", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/notification_logs.go b/notification_logs.go new file mode 100644 index 0000000..2a1fb3d --- /dev/null +++ b/notification_logs.go @@ -0,0 +1,49 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import "context" + +// NotificationLog: Represents a notification log entity. +type NotificationLog struct { + // ID: Unique Paddle ID for this notification log, prefixed with `ntflog_`. + ID string `json:"id,omitempty"` + // ResponseCode: HTTP code sent by the responding server. + ResponseCode int `json:"response_code,omitempty"` + // ResponseContentType: Content-Type sent by the responding server. + ResponseContentType *string `json:"response_content_type,omitempty"` + // ResponseBody: Response body sent by the responding server. Typically empty for success responses. + ResponseBody string `json:"response_body,omitempty"` + // AttemptedAt: RFC 3339 datetime string of when Paddle attempted to deliver the related notification. + AttemptedAt string `json:"attempted_at,omitempty"` +} + +// NotificationLogsClient is a client for the Notification logs resource. +type NotificationLogsClient struct { + doer Doer +} + +// ListNotificationLogsRequest is given as an input to ListNotificationLogs. +type ListNotificationLogsRequest struct { + // URL path parameters. + NotificationID string `in:"path=notification_id" json:"-"` + + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `50`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` +} + +// ListNotificationLogs performs the GET operation on a Notification logs resource. +func (c *NotificationLogsClient) ListNotificationLogs(ctx context.Context, req *ListNotificationLogsRequest) (res *Collection[*NotificationLog], err error) { + if err := c.doer.Do(ctx, "GET", "/notifications/{notification_id}/logs", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/notification_setting_replays.go b/notification_setting_replays.go new file mode 100644 index 0000000..94a0c2f --- /dev/null +++ b/notification_setting_replays.go @@ -0,0 +1,24 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import "context" + +// NotificationSettingReplaysClient is a client for the Notification setting replays resource. +type NotificationSettingReplaysClient struct { + doer Doer +} + +// ReplayNotificationRequest is given as an input to ReplayNotification. +type ReplayNotificationRequest struct { + // URL path parameters. + NotificationID string `in:"path=notification_id" json:"-"` +} + +// ReplayNotification performs the POST operation on a Notification setting replays resource. +func (c *NotificationSettingReplaysClient) ReplayNotification(ctx context.Context, req *ReplayNotificationRequest) (err error) { + if err := c.doer.Do(ctx, "POST", "/notifications/{notification_id}/replay", req, nil); err != nil { + return err + } + + return nil +} diff --git a/notification_settings.go b/notification_settings.go new file mode 100644 index 0000000..85919bd --- /dev/null +++ b/notification_settings.go @@ -0,0 +1,152 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import "context" + +// NotificationSettingType: Where notifications should be sent for this destination.. +type NotificationSettingType string + +const ( + NotificationSettingTypeEmail = "email" + NotificationSettingTypeURL = "url" +) + +// NotificationSetting: Represents a notification destination. +type NotificationSetting struct { + // ID: Unique Paddle ID for this notification setting, prefixed with `ntfset_`. + ID string `json:"id,omitempty"` + // Description: Short description for this notification destination. Shown in the Paddle web app. + Description string `json:"description,omitempty"` + // Type: Where notifications should be sent for this destination. + Type string `json:"type,omitempty"` + // Destination: Webhook endpoint URL or email address. + Destination string `json:"destination,omitempty"` + // Active: Whether Paddle should try to deliver events to this notification destination. + Active bool `json:"active,omitempty"` + // APIVersion: API version that returned objects for events should conform to. Must be a valid version of the Paddle API. Cannot be a version older than your account default. Defaults to your account default if not included. + APIVersion int `json:"api_version,omitempty"` + // IncludeSensitiveFields: Whether potentially sensitive fields should be sent to this notification destination. + IncludeSensitiveFields bool `json:"include_sensitive_fields,omitempty"` + // SubscribedEvents: Represents an event type. + SubscribedEvents []EventType `json:"subscribed_events,omitempty"` + // EndpointSecretKey: Webhook destination secret key, prefixed with `pdl_ntfset_`. Used for signature verification. + EndpointSecretKey string `json:"endpoint_secret_key,omitempty"` +} + +// NotificationSettingsClient is a client for the Notification settings resource. +type NotificationSettingsClient struct { + doer Doer +} + +// ListNotificationSettingsRequest is given as an input to ListNotificationSettings. +type ListNotificationSettingsRequest struct { + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `200`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `id`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` +} + +// ListNotificationSettings performs the GET operation on a Notification settings resource. +func (c *NotificationSettingsClient) ListNotificationSettings(ctx context.Context, req *ListNotificationSettingsRequest) (res *Collection[*NotificationSetting], err error) { + if err := c.doer.Do(ctx, "GET", "/notification-settings", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// CreateNotificationSettingRequest is given as an input to CreateNotificationSetting. +type CreateNotificationSettingRequest struct { + // Description: Short description for this notification destination. Shown in the Paddle Dashboard. + Description string `json:"description,omitempty"` + // Destination: Webhook endpoint URL or email address. + Destination string `json:"destination,omitempty"` + // SubscribedEvents: Subscribed events for this notification destination. When creating or updating a notification destination, pass an array of event type names only. Paddle returns the complete event type object. + SubscribedEvents []Event `json:"subscribed_events,omitempty"` + // Type: Where notifications should be sent for this destination. + Type string `json:"type,omitempty"` + // APIVersion: API version that returned objects for events should conform to. Must be a valid version of the Paddle API. Cannot be a version older than your account default. Defaults to your account default if not included. + APIVersion *int `json:"api_version,omitempty"` + // IncludeSensitiveFields: Whether potentially sensitive fields should be sent to this notification destination. + IncludeSensitiveFields *bool `json:"include_sensitive_fields,omitempty"` +} + +// CreateNotificationSetting performs the POST operation on a Notification settings resource. +func (c *NotificationSettingsClient) CreateNotificationSetting(ctx context.Context, req *CreateNotificationSettingRequest) (res *NotificationSetting, err error) { + if err := c.doer.Do(ctx, "POST", "/notification-settings", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// GetNotificationSettingRequest is given as an input to GetNotificationSetting. +type GetNotificationSettingRequest struct { + // URL path parameters. + NotificationSettingID string `in:"path=notification_setting_id" json:"-"` +} + +// GetNotificationSetting performs the GET operation on a Notification settings resource. +func (c *NotificationSettingsClient) GetNotificationSetting(ctx context.Context, req *GetNotificationSettingRequest) (res *NotificationSetting, err error) { + if err := c.doer.Do(ctx, "GET", "/notification-settings/{notification_setting_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// UpdateNotificationSettingRequest is given as an input to UpdateNotificationSetting. +type UpdateNotificationSettingRequest struct { + // URL path parameters. + NotificationSettingID string `in:"path=notification_setting_id" json:"-"` + + // Description: Short description for this notification destination. Shown in the Paddle Dashboard. + Description *PatchField[string] `json:"description,omitempty"` + // Destination: Webhook endpoint URL or email address. + Destination *PatchField[string] `json:"destination,omitempty"` + // Active: Whether Paddle should try to deliver events to this notification destination. + Active *PatchField[bool] `json:"active,omitempty"` + // APIVersion: API version that returned objects for events should conform to. Must be a valid version of the Paddle API. Cannot be a version older than your account default. Defaults to your account default if omitted. + APIVersion *PatchField[int] `json:"api_version,omitempty"` + // IncludeSensitiveFields: Whether potentially sensitive fields should be sent to this notification destination. + IncludeSensitiveFields *PatchField[bool] `json:"include_sensitive_fields,omitempty"` + // SubscribedEvents: Subscribed events for this notification destination. When creating or updating a notification destination, pass an array of event type names only. Paddle returns the complete event type object. + SubscribedEvents *PatchField[[]Event] `json:"subscribed_events,omitempty"` +} + +// UpdateNotificationSetting performs the PATCH operation on a Notification settings resource. +func (c *NotificationSettingsClient) UpdateNotificationSetting(ctx context.Context, req *UpdateNotificationSettingRequest) (res *NotificationSetting, err error) { + if err := c.doer.Do(ctx, "PATCH", "/notification-settings/{notification_setting_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// DeleteNotificationSettingRequest is given as an input to DeleteNotificationSetting. +type DeleteNotificationSettingRequest struct { + // URL path parameters. + NotificationSettingID string `in:"path=notification_setting_id" json:"-"` +} + +// DeleteNotificationSetting performs the DELETE operation on a Notification settings resource. +func (c *NotificationSettingsClient) DeleteNotificationSetting(ctx context.Context, req *DeleteNotificationSettingRequest) (err error) { + if err := c.doer.Do(ctx, "DELETE", "/notification-settings/{notification_setting_id}", req, nil); err != nil { + return err + } + + return nil +} diff --git a/notifications.go b/notifications.go new file mode 100644 index 0000000..552d634 --- /dev/null +++ b/notifications.go @@ -0,0 +1,170 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import ( + "context" + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrNotificationMaximumActiveSettingsReached represents a `notification_maximum_active_settings_reached` error. +// See https://developer.paddle.com/errors/notifications/notification_maximum_active_settings_reached for more information. +var ErrNotificationMaximumActiveSettingsReached = &paddleerr.Error{ + Code: "notification_maximum_active_settings_reached", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrNotificationCannotReplay represents a `notification_cannot_replay` error. +// See https://developer.paddle.com/errors/notifications/notification_cannot_replay for more information. +var ErrNotificationCannotReplay = &paddleerr.Error{ + Code: "notification_cannot_replay", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrURLNotificationSettingIncorrect represents a `url_notification_setting_incorrect` error. +// See https://developer.paddle.com/errors/notifications/url_notification_setting_incorrect for more information. +var ErrURLNotificationSettingIncorrect = &paddleerr.Error{ + Code: "url_notification_setting_incorrect", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrEmailNotificationSettingIncorrect represents a `email_notification_setting_incorrect` error. +// See https://developer.paddle.com/errors/notifications/email_notification_setting_incorrect for more information. +var ErrEmailNotificationSettingIncorrect = &paddleerr.Error{ + Code: "email_notification_setting_incorrect", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrNotificationReplayInvalidOriginType represents a `notification_replay_invalid_origin_type` error. +// See https://developer.paddle.com/errors/notifications/notification_replay_invalid_origin_type for more information. +var ErrNotificationReplayInvalidOriginType = &paddleerr.Error{ + Code: "notification_replay_invalid_origin_type", + Type: paddleerr.ErrorTypeRequestError, +} + +// NotificationStatus: Status of this notification.. +type NotificationStatus string + +const ( + NotificationStatusNotAttempted = "not_attempted" + NotificationStatusNeedsRetry = "needs_retry" + NotificationStatusDelivered = "delivered" + NotificationStatusFailed = "failed" +) + +// NotificationsEvent: Notification payload. Includes the new or changed event. +type NotificationsEvent struct { + // EventID: Unique Paddle ID for this event, prefixed with `evt_`. + EventID string `json:"event_id,omitempty"` + // EventType: Type of event sent by Paddle, in the format `entity.event_type`. + EventType string `json:"event_type,omitempty"` + // OccurredAt: RFC 3339 datetime string of when this event occurred. + OccurredAt string `json:"occurred_at,omitempty"` + // Data: New or changed entity. + Data any `json:"data,omitempty"` + // NotificationID: Unique Paddle ID for this notification, prefixed with `ntf_`. + NotificationID string `json:"notification_id,omitempty"` +} + +// Origin: Describes how this notification was created.. +type Origin string + +const ( + OriginEvent = "event" + OriginReplay = "replay" +) + +// Notification: Represents a notification entity. +type Notification struct { + // ID: Unique Paddle ID for this notification, prefixed with `ntf_`. + ID string `json:"id,omitempty"` + // Type: Type of event sent by Paddle, in the format `entity.event_type`. + Type string `json:"type,omitempty"` + // Status: Status of this notification. + Status string `json:"status,omitempty"` + // Payload: Notification payload. Includes the new or changed event. + Payload NotificationsEvent `json:"payload,omitempty"` + // OccurredAt: RFC 3339 datetime string of when this notification occurred. + OccurredAt string `json:"occurred_at,omitempty"` + // DeliveredAt: RFC 3339 datetime string of when this notification was delivered. `null` if not yet delivered successfully. + DeliveredAt *string `json:"delivered_at,omitempty"` + // ReplayedAt: RFC 3339 datetime string of when this notification was replayed. `null` if not replayed. + ReplayedAt *string `json:"replayed_at,omitempty"` + // Origin: Describes how this notification was created. + Origin string `json:"origin,omitempty"` + // LastAttemptAt: RFC 3339 datetime string of when this notification was last attempted. + LastAttemptAt *string `json:"last_attempt_at,omitempty"` + // RetryAt: RFC 3339 datetime string of when this notification is scheduled to be retried. + RetryAt *string `json:"retry_at,omitempty"` + // TimesAttempted: How many times delivery of this notification has been attempted. Automatically incremented by Paddle after an attempt. + TimesAttempted int `json:"times_attempted,omitempty"` + // NotificationSettingID: Unique Paddle ID for this notification setting, prefixed with `ntfset_`. + NotificationSettingID string `json:"notification_setting_id,omitempty"` +} + +// NotificationsClient is a client for the Notifications resource. +type NotificationsClient struct { + doer Doer +} + +// ListNotificationsRequest is given as an input to ListNotifications. +type ListNotificationsRequest struct { + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // NotificationSettingID is a query parameter. + // Return entities related to the specified notification destination. Use a comma-separated list to specify multiple notification destination IDs. + NotificationSettingID []string `in:"query=notification_setting_id,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `id`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `50`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // Search is a query parameter. + // Return entities that match a search query. Searches `id` and `type` fields. + Search *string `in:"query=search,omitempty" json:"-"` + // Status is a query parameter. + // Return entities that match the specified status. Use a comma-separated list to specify multiple status values. + Status []string `in:"query=status,omitempty" json:"-"` + // Filter is a query parameter. + // Return entities that contain the Paddle ID specified. Pass a transaction, customer, or subscription ID. + Filter *string `in:"query=filter,omitempty" json:"-"` + // To is a query parameter. + // Return entities up to a specific time. + To *string `in:"query=to,omitempty" json:"-"` + // From is a query parameter. + // Return entities from a specific time. + From *string `in:"query=from,omitempty" json:"-"` +} + +// ListNotifications performs the GET operation on a Notifications resource. +func (c *NotificationsClient) ListNotifications(ctx context.Context, req *ListNotificationsRequest) (res *Collection[*Notification], err error) { + if err := c.doer.Do(ctx, "GET", "/notifications", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// GetNotificationRequest is given as an input to GetNotification. +type GetNotificationRequest struct { + // URL path parameters. + NotificationID string `in:"path=notification_id" json:"-"` +} + +// GetNotification performs the GET operation on a Notifications resource. +func (c *NotificationsClient) GetNotification(ctx context.Context, req *GetNotificationRequest) (res *Notification, err error) { + if err := c.doer.Do(ctx, "GET", "/notifications/{notification_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/options.go b/options.go new file mode 100644 index 0000000..ec23bb1 --- /dev/null +++ b/options.go @@ -0,0 +1,42 @@ +package paddle + +import ( + "github.com/PaddleHQ/paddle-go-sdk/internal/client" +) + +// Option is a function that configures the Paddle SDK. +type Option func(*options) + +// options contains the configuration for the Paddle SDK. +type options struct { + // APIKey is the Paddle API key. + APIKey string + + // BaseURL is the base URL for the Paddle API. + BaseURL string + + // Client is the HTTP client used to make requests to the Paddle API. + Client client.HTTPDoer +} + +// WithAPIKey returns an option that sets the Paddle API key. +func WithAPIKey(apiKey string) Option { + return func(o *options) { + o.APIKey = apiKey + } +} + +// WithBaseURL returns an option that sets the base URL for the Paddle API. +func WithBaseURL(baseURL string) Option { + return func(o *options) { + o.BaseURL = baseURL + } +} + +// WithClient returns an option that sets the HTTP client used to make requests +// to the Paddle API. +func WithClient(c client.HTTPDoer) Option { + return func(o *options) { + o.Client = c + } +} diff --git a/paddle.go b/paddle.go new file mode 100644 index 0000000..889a0b5 --- /dev/null +++ b/paddle.go @@ -0,0 +1,70 @@ +// Package paddle provides the official SDK for using the Paddle Billing API. +package paddle + +import ( + "context" + "net/url" + + "github.com/PaddleHQ/paddle-go-sdk/internal/client" + + "github.com/hashicorp/go-cleanhttp" +) + +// Doer is an interface that is used to make requests to the Paddle API. +// This is of the custom client used by the SDK, instead of the standard +// http.Client Do method. +type Doer interface { + Do(ctx context.Context, method, path string, src, dst any) error +} + +const ( + // ProductionBaseURL is the base URL for the production Paddle API. + ProductionBaseURL = "https://api.paddle.com" + + // SandboxBaseURL is the base URL for the sandbox Paddle API. + SandboxBaseURL = "https://sandbox-api.paddle.com" +) + +// New creates a new Paddle SDK with the given API key. +func New(apiKey string, opts ...Option) (*SDK, error) { + return bootstrapSDK( + append([]Option{ + WithAPIKey(apiKey), + WithBaseURL(ProductionBaseURL), + }, opts...)..., + ) +} + +// NewSandbox creates a new Paddle SDK with the given API key, using the sandbox +// environment. +func NewSandbox(apiKey string, opts ...Option) (*SDK, error) { + return bootstrapSDK( + append([]Option{ + WithAPIKey(apiKey), + WithBaseURL(SandboxBaseURL), + }, opts...)..., + ) +} + +func bootstrapSDK(opts ...Option) (*SDK, error) { + o := &options{ + Client: cleanhttp.DefaultPooledClient(), + } + + for _, fn := range opts { + fn(o) + } + + parsedBaseURL, err := url.Parse(o.BaseURL) + if err != nil { + return nil, err + } + + c, err := client.New(cleanhttp.DefaultPooledClient(), o.APIKey, parsedBaseURL) + if err != nil { + return nil, err + } + + // newSDK is generated by the SDK generator, do not modify. + return newSDK(c), nil +} diff --git a/patch_field.go b/patch_field.go new file mode 100644 index 0000000..ef9ef3a --- /dev/null +++ b/patch_field.go @@ -0,0 +1,54 @@ +package paddle + +import ( + "encoding/json" +) + +// PatchField is a type that represents a field in a patch request. +// It is used to differentiate between: +// * a field that should not be present in the request. +// * a field that should be present in the request, but with a null value. +// * a field that should be present in the request, with a non-null value. +// Using the standard json package, it is not possible to differentiate between +// a field that is not present and a field that is present with an explicit +// null value without the use of maps. +// This type is used to work around this limitation, and provides some type +// safety by allowing the user to specify the type of the field as a +// type parameter. +// To create a new PatchField value, use the NewPatchField function. +type PatchField[T any] struct { + value *T +} + +// NewPatchField creates a new PatchField from a given value. +// This is used for fields that accept either a value, or omitted completely. +func NewPatchField[T any](value T) *PatchField[T] { + return &PatchField[T]{value: &value} +} + +// NewPtrPatchField creates a new PatchField from a concrete value, where the +// expected PatchField value is a pointer (aka optional). +// This is an alias to doing NewPatchField(ptrTo(v)), where ptrTo is a function +// that returns a pointer to the given value (e.g. &v). +func NewPtrPatchField[T any](value T) *PatchField[*T] { + v := &value + return &PatchField[*T]{value: &v} +} + +// NewNullPatchField creates a new PatchField with a null value. +func NewNullPatchField[T any]() *PatchField[T] { + return &PatchField[T]{value: nil} +} + +// Value returns the value, if any. +func (f PatchField[T]) Value() *T { + return f.value +} + +// MarshalJSON implements the json.Marshaler interface. +// If the PatchField hasn't been set on the type, then the omitempty will handle +// it as an omitted field. If the PatchField has been set, then this will render +// the null value as a JSON null. +func (f PatchField[T]) MarshalJSON() ([]byte, error) { + return json.Marshal(f.value) +} diff --git a/patch_field_test.go b/patch_field_test.go new file mode 100644 index 0000000..149bfd1 --- /dev/null +++ b/patch_field_test.go @@ -0,0 +1,56 @@ +package paddle_test + +import ( + "encoding/json" + "testing" + + paddle "github.com/PaddleHQ/paddle-go-sdk" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type InnerStruct struct { + Test string `json:"test"` +} + +type TestStruct struct { + StringExistsAndIsSet *paddle.PatchField[string] `json:"string_exists_and_is_set,omitempty"` + IntExistsAndIsNull *paddle.PatchField[int] `json:"int_exists_and_is_null,omitempty"` + NullableString *paddle.PatchField[*string] `json:"nullable_string,omitempty"` + FieldOmitted *paddle.PatchField[*InnerStruct] `json:"field_omitted,omitempty"` + InnerStructPresent *paddle.PatchField[*InnerStruct] `json:"inner_struct_present,omitempty"` +} + +func TestPatchField(t *testing.T) { + s := TestStruct{ + StringExistsAndIsSet: paddle.NewPatchField("test"), + IntExistsAndIsNull: paddle.NewNullPatchField[int](), + NullableString: paddle.NewPtrPatchField("test"), + FieldOmitted: nil, + InnerStructPresent: paddle.NewPatchField(&InnerStruct{Test: "testing"}), + } + + require.NotNil(t, s.StringExistsAndIsSet.Value()) + assert.Equal(t, "test", *s.StringExistsAndIsSet.Value()) + + assert.Nil(t, s.IntExistsAndIsNull.Value()) + + assert.Nil(t, s.FieldOmitted) + + require.NotNil(t, s.InnerStructPresent.Value()) + innerVal := *s.InnerStructPresent.Value() + assert.Equal(t, "testing", innerVal.Test) + + jsonBytes, err := json.Marshal(s) + require.NoError(t, err) + + assert.JSONEq(t, `{ + "string_exists_and_is_set": "test", + "int_exists_and_is_null": null, + "nullable_string": "test", + "inner_struct_present": { + "test": "testing" + } + }`, string(jsonBytes)) +} diff --git a/pkg/paddleerr/paddleerr.go b/pkg/paddleerr/paddleerr.go new file mode 100644 index 0000000..88f90e7 --- /dev/null +++ b/pkg/paddleerr/paddleerr.go @@ -0,0 +1,64 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddleerr + +import ( + "fmt" + "strings" +) + +type ErrorType string + +type Error struct { + Status int `json:"-"` + Type ErrorType `json:"type"` + Code string `json:"code"` + Detail string `json:"detail"` + DocumentationURL string `json:"documentation_url"` + Extra map[string]any `json:"extra"` + Errors []ValidationError `json:"errors,omitempty"` +} + +type ValidationError struct { + Field string `json:"field"` + Message string `json:"message"` +} + +const ( + ErrorTypeRequestError ErrorType = "request_error" + ErrorTypeAPIError ErrorType = "api_error" +) + +// Error conforms to the errors interface. +// It returns a string representation of the error, which contains the type and +// code of the error, and the detail if it exists. +// If the documentation URL exists, it will be appended to the end of the string. +func (e *Error) Error() string { + var sb strings.Builder + + sb.WriteString(fmt.Sprintf("%s.%s", e.Type, e.Code)) + + if e.Detail != "" { + sb.WriteString(fmt.Sprintf(": %s", e.Detail)) + } + + if e.DocumentationURL != "" { + sb.WriteString(fmt.Sprintf(" (%s)", e.DocumentationURL)) + } + + for _, e := range e.Errors { + sb.WriteString(fmt.Sprintf("\n - %s: %s", e.Field, e.Message)) + } + + return sb.String() +} + +// Is returns true if the target error is a *Error and the error type and code match. +func (e *Error) Is(target error) bool { + targetAsserted, targetOk := target.(*Error) + + if !targetOk { + return false + } + + return (e.Code == targetAsserted.Code && e.Type == targetAsserted.Type) +} diff --git a/prices.go b/prices.go new file mode 100644 index 0000000..ca5ebde --- /dev/null +++ b/prices.go @@ -0,0 +1,226 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import ( + "context" + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrPriceTrialPeriodMissingFields represents a `price_trial_period_missing_fields` error. +// See https://developer.paddle.com/errors/prices/price_trial_period_missing_fields for more information. +var ErrPriceTrialPeriodMissingFields = &paddleerr.Error{ + Code: "price_trial_period_missing_fields", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrPriceTrialPeriodRequiresBillingCycle represents a `price_trial_period_requires_billing_cycle` error. +// See https://developer.paddle.com/errors/prices/price_trial_period_requires_billing_cycle for more information. +var ErrPriceTrialPeriodRequiresBillingCycle = &paddleerr.Error{ + Code: "price_trial_period_requires_billing_cycle", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrPriceBillingCycleFrequencyBelow1 represents a `price_billing_cycle_frequency_below_1` error. +// See https://developer.paddle.com/errors/prices/price_billing_cycle_frequency_below_1 for more information. +var ErrPriceBillingCycleFrequencyBelow1 = &paddleerr.Error{ + Code: "price_billing_cycle_frequency_below_1", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrPriceTrialPeriodFrequencyBelow1 represents a `price_trial_period_frequency_below_1` error. +// See https://developer.paddle.com/errors/prices/price_trial_period_frequency_below_1 for more information. +var ErrPriceTrialPeriodFrequencyBelow1 = &paddleerr.Error{ + Code: "price_trial_period_frequency_below_1", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrPriceDuplicateCurrencyOverrideForCountry represents a `price_duplicate_currency_override_for_country` error. +// See https://developer.paddle.com/errors/prices/price_duplicate_currency_override_for_country for more information. +var ErrPriceDuplicateCurrencyOverrideForCountry = &paddleerr.Error{ + Code: "price_duplicate_currency_override_for_country", + Type: paddleerr.ErrorTypeRequestError, +} + +// PriceIncludes: Represents a price entity with included entities. +type PriceIncludes struct { + // ID: Unique Paddle ID for this price, prefixed with `pri_`. + ID string `json:"id,omitempty"` + // ProductID: Paddle ID for the product that this price is for, prefixed with `pro_`. + ProductID string `json:"product_id,omitempty"` + // Description: Internal description for this price, not shown to customers. Typically notes for your team. + Description string `json:"description,omitempty"` + // Type: Type of item. Standard items are considered part of your catalog and are shown on the Paddle web app. + Type string `json:"type,omitempty"` + // Name: Name of this price, shown to customers at checkout and on invoices. Typically describes how often the related product bills. + Name *string `json:"name,omitempty"` + // BillingCycle: How often this price should be charged. `null` if price is non-recurring (one-time). + BillingCycle *Duration `json:"billing_cycle,omitempty"` + // TrialPeriod: Trial period for the product related to this price. The billing cycle begins once the trial period is over. `null` for no trial period. Requires `billing_cycle`. + TrialPeriod *Duration `json:"trial_period,omitempty"` + // TaxMode: How tax is calculated for this price. + TaxMode string `json:"tax_mode,omitempty"` + // UnitPrice: Base price. This price applies to all customers, except for customers located in countries where you have `unit_price_overrides`. + UnitPrice Money `json:"unit_price,omitempty"` + // UnitPriceOverrides: List of unit price overrides. Use to override the base price with a custom price and currency for a country or group of countries. + UnitPriceOverrides []UnitPriceOverride `json:"unit_price_overrides,omitempty"` + // Quantity: Limits on how many times the related product can be purchased at this price. Useful for discount campaigns. + Quantity PriceQuantity `json:"quantity,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status string `json:"status,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // Product: Related product for this price. Returned when the `include` parameter is used with the `product` value. + Product Product `json:"product,omitempty"` +} + +// PricesClient is a client for the Prices resource. +type PricesClient struct { + doer Doer +} + +// ListPricesRequest is given as an input to ListPrices. +type ListPricesRequest struct { + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // ID is a query parameter. + // Return only the IDs specified. Use a comma-separated list to get multiple entities. + ID []string `in:"query=id,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `billing_cycle.frequency`, `billing_cycle.interval`, `id`, `product_id`, `quantity.maximum`, `quantity.minimum`, `status`, `tax_mode`, `unit_price.amount`, and `unit_price.currency_code`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `50`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // ProductID is a query parameter. + // Return entities related to the specified product. Use a comma-separated list to specify multiple product IDs. + ProductID []string `in:"query=product_id,omitempty" json:"-"` + // Status is a query parameter. + // Return entities that match the specified status. Use a comma-separated list to specify multiple status values. + Status []string `in:"query=status,omitempty" json:"-"` + // Recurring is a query parameter. + // Determine whether returned entities are for recurring prices (`true`) or one-time prices (`false`). + Recurring *bool `in:"query=recurring,omitempty" json:"-"` + // Type is a query parameter. + // Return items that match the specified type. + Type *string `in:"query=type,omitempty" json:"-"` + + // IncludeProduct allows requesting the product sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeProduct bool `in:"paddle_include=product" json:"-"` +} + +// ListPrices performs the GET operation on a Prices resource. +func (c *PricesClient) ListPrices(ctx context.Context, req *ListPricesRequest) (res *Collection[*PriceIncludes], err error) { + if err := c.doer.Do(ctx, "GET", "/prices", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// CreatePriceRequest is given as an input to CreatePrice. +type CreatePriceRequest struct { + // Description: Internal description for this price, not shown to customers. Typically notes for your team. + Description string `json:"description,omitempty"` + // ProductID: Paddle ID for the product that this price is for, prefixed with `pro_`. + ProductID string `json:"product_id,omitempty"` + // UnitPrice: Base price. This price applies to all customers, except for customers located in countries where you have `unit_price_overrides`. + UnitPrice Money `json:"unit_price,omitempty"` + // Type: Type of item. Standard items are considered part of your catalog and are shown on the Paddle web app. + Type *string `json:"type,omitempty"` + // Name: Name of this price, shown to customers at checkout and on invoices. Typically describes how often the related product bills. + Name *string `json:"name,omitempty"` + // BillingCycle: How often this price should be charged. `null` if price is non-recurring (one-time). + BillingCycle *Duration `json:"billing_cycle,omitempty"` + // TrialPeriod: Trial period for the product related to this price. The billing cycle begins once the trial period is over. `null` for no trial period. Requires `billing_cycle`. + TrialPeriod *Duration `json:"trial_period,omitempty"` + // TaxMode: How tax is calculated for this price. + TaxMode *string `json:"tax_mode,omitempty"` + // UnitPriceOverrides: List of unit price overrides. Use to override the base price with a custom price and currency for a country or group of countries. + UnitPriceOverrides []UnitPriceOverride `json:"unit_price_overrides,omitempty"` + // Quantity: Limits on how many times the related product can be purchased at this price. Useful for discount campaigns. If omitted, defaults to 1-100. + Quantity *PriceQuantity `json:"quantity,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` +} + +// CreatePrice performs the POST operation on a Prices resource. +func (c *PricesClient) CreatePrice(ctx context.Context, req *CreatePriceRequest) (res *Price, err error) { + if err := c.doer.Do(ctx, "POST", "/prices", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// GetPriceRequest is given as an input to GetPrice. +type GetPriceRequest struct { + // URL path parameters. + PriceID string `in:"path=price_id" json:"-"` + + // IncludeProduct allows requesting the product sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeProduct bool `in:"paddle_include=product" json:"-"` +} + +// GetPrice performs the GET operation on a Prices resource. +func (c *PricesClient) GetPrice(ctx context.Context, req *GetPriceRequest) (res *PriceIncludes, err error) { + if err := c.doer.Do(ctx, "GET", "/prices/{price_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// UpdatePriceRequest is given as an input to UpdatePrice. +type UpdatePriceRequest struct { + // URL path parameters. + PriceID string `in:"path=price_id" json:"-"` + + // Description: Internal description for this price, not shown to customers. Typically notes for your team. + Description *PatchField[string] `json:"description,omitempty"` + // Type: Type of item. Standard items are considered part of your catalog and are shown on the Paddle web app. + Type *PatchField[string] `json:"type,omitempty"` + // Name: Name of this price, shown to customers at checkout and on invoices. Typically describes how often the related product bills. + Name *PatchField[*string] `json:"name,omitempty"` + // BillingCycle: How often this price should be charged. `null` if price is non-recurring (one-time). + BillingCycle *PatchField[*Duration] `json:"billing_cycle,omitempty"` + // TrialPeriod: Trial period for the product related to this price. The billing cycle begins once the trial period is over. `null` for no trial period. Requires `billing_cycle`. + TrialPeriod *PatchField[*Duration] `json:"trial_period,omitempty"` + // TaxMode: How tax is calculated for this price. + TaxMode *PatchField[string] `json:"tax_mode,omitempty"` + // UnitPrice: Base price. This price applies to all customers, except for customers located in countries where you have `unit_price_overrides`. + UnitPrice *PatchField[Money] `json:"unit_price,omitempty"` + // UnitPriceOverrides: List of unit price overrides. Use to override the base price with a custom price and currency for a country or group of countries. + UnitPriceOverrides *PatchField[[]UnitPriceOverride] `json:"unit_price_overrides,omitempty"` + // Quantity: Limits on how many times the related product can be purchased at this price. Useful for discount campaigns. + Quantity *PatchField[PriceQuantity] `json:"quantity,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status *PatchField[string] `json:"status,omitempty"` + // CustomData: Your own structured key-value data. + CustomData *PatchField[CustomData] `json:"custom_data,omitempty"` +} + +// UpdatePrice performs the PATCH operation on a Prices resource. +func (c *PricesClient) UpdatePrice(ctx context.Context, req *UpdatePriceRequest) (res *Price, err error) { + if err := c.doer.Do(ctx, "PATCH", "/prices/{price_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/pricing_preview.go b/pricing_preview.go new file mode 100644 index 0000000..cbddb2b --- /dev/null +++ b/pricing_preview.go @@ -0,0 +1,138 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import "context" + +// PricePreviewItem: List of items to preview price calculations for. +type PricePreviewItem struct { + // PriceID: Paddle ID for the price to add to this transaction, prefixed with `pri_`. + PriceID string `json:"price_id,omitempty"` + // Quantity: Quantity of the item to preview. + Quantity int `json:"quantity,omitempty"` +} + +// UnitTotalsFormatted: Breakdown of the charge for one unit in the format of a given currency. +type UnitTotalsFormatted struct { + // Subtotal: Unit price. + Subtotal string `json:"subtotal,omitempty"` + /* + Discount: Total discount as a result of any discounts applied. + Except for percentage discounts, Paddle applies tax to discounts based on the line item `price.tax_mode`. If `price.tax_mode` for a line item is `internal`, Paddle removes tax from the discount applied. + */ + Discount string `json:"discount,omitempty"` + // Tax: Total tax on the subtotal. + Tax string `json:"tax,omitempty"` + // Total: Total after discount and tax. + Total string `json:"total,omitempty"` +} + +// TotalsFormatted: The financial breakdown of a charge in the format of a given currency. +type TotalsFormatted struct { + // Subtotal: The amount times the quantity. + Subtotal string `json:"subtotal,omitempty"` + /* + Discount: The amount discounted due to a discount code or ID being applied. + + Except for percentage discounts, Paddle applies tax to discounts based on the line item `price.tax_mode`. If `price.tax_mode` for a line item is `internal`, Paddle removes tax from the discount applied. + */ + Discount string `json:"discount,omitempty"` + // Tax: The amount of tax due on the subtotal. + Tax string `json:"tax,omitempty"` + // Total: The subtotal - discount + tax. + Total string `json:"total,omitempty"` +} + +// PricePreviewDiscounts: Array of discounts applied to this preview line item. Empty if no discounts applied. +type PricePreviewDiscounts struct { + // Discount: Related discount entity for this preview line item. + Discount Discount `json:"discount,omitempty"` + // Total: Total amount discounted as a result of this discount. + Total string `json:"total,omitempty"` + // FormattedTotal: Total amount discounted as a result of this discount in the format of a given currency. ' + FormattedTotal string `json:"formatted_total,omitempty"` +} + +// PricePreviewLineItem: Information about line items for this preview. Includes totals calculated by Paddle. Considered the source of truth for line item totals. +type PricePreviewLineItem struct { + // Price: Related price entity for this preview line item. + Price Price `json:"price,omitempty"` + // Quantity: Quantity of this preview line item. + Quantity int `json:"quantity,omitempty"` + // TaxRate: Rate used to calculate tax for this preview line item. + TaxRate string `json:"tax_rate,omitempty"` + // UnitTotals: Breakdown of the charge for one unit in the lowest denomination of a currency (e.g. cents for USD). + UnitTotals UnitTotals `json:"unit_totals,omitempty"` + // FormattedUnitTotals: Breakdown of the charge for one unit in the format of a given currency. + FormattedUnitTotals UnitTotalsFormatted `json:"formatted_unit_totals,omitempty"` + // Totals: Breakdown of a charge in the lowest denomination of a currency (e.g. cents for USD). + Totals Totals `json:"totals,omitempty"` + // FormattedTotals: The financial breakdown of a charge in the format of a given currency. + FormattedTotals TotalsFormatted `json:"formatted_totals,omitempty"` + // Product: Related product entity for this preview line item price. + Product Product `json:"product,omitempty"` + // Discounts: Array of discounts applied to this preview line item. Empty if no discounts applied. + Discounts []PricePreviewDiscounts `json:"discounts,omitempty"` +} + +// PricePreviewDetails: Calculated totals for a price preview, including discounts, tax, and currency conversion. +type PricePreviewDetails struct { + // LineItems: Information about line items for this preview. Includes totals calculated by Paddle. Considered the source of truth for line item totals. + LineItems []PricePreviewLineItem `json:"line_items,omitempty"` +} + +type PricePreview struct { + // CustomerID: Paddle ID of the customer that this preview is for, prefixed with `ctm_`. + CustomerID *string `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this preview is for, prefixed with `add_`. Send one of `address_id`, `customer_ip_address`, or the `address` object when previewing. + AddressID *string `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this preview is for, prefixed with `biz_`. + BusinessID *string `json:"business_id,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. + CurrencyCode string `json:"currency_code,omitempty"` + // DiscountID: Paddle ID of the discount applied to this preview, prefixed with `dsc_`. + DiscountID *string `json:"discount_id,omitempty"` + // Address: Address for this preview. Send one of `address_id`, `customer_ip_address`, or the `address` object when previewing. + Address *AddressPreview `json:"address,omitempty"` + // CustomerIPAddress: IP address for this transaction preview. Send one of `address_id`, `customer_ip_address`, or the `address` object when previewing. + CustomerIPAddress *string `json:"customer_ip_address,omitempty"` + // Items: List of items to preview price calculations for. + Items []PricePreviewItem `json:"items,omitempty"` + // Details: Calculated totals for a price preview, including discounts, tax, and currency conversion. + Details PricePreviewDetails `json:"details,omitempty"` + // AvailablePaymentMethods: List of available payment methods for Paddle Checkout given the price and location information passed. + AvailablePaymentMethods []PaymentMethodType `json:"available_payment_methods,omitempty"` +} + +// PricingPreviewClient is a client for the Pricing preview resource. +type PricingPreviewClient struct { + doer Doer +} + +// PricePreviewRequest is given as an input to PricePreview. +type PricePreviewRequest struct { + // CustomerID: Paddle ID of the customer that this preview is for, prefixed with `ctm_`. + CustomerID *string `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this preview is for, prefixed with `add_`. Send one of `address_id`, `customer_ip_address`, or the `address` object when previewing. + AddressID *string `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this preview is for, prefixed with `biz_`. + BusinessID *string `json:"business_id,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. + CurrencyCode *string `json:"currency_code,omitempty"` + // DiscountID: Paddle ID of the discount applied to this preview, prefixed with `dsc_`. + DiscountID *string `json:"discount_id,omitempty"` + // Address: Address for this preview. Send one of `address_id`, `customer_ip_address`, or the `address` object when previewing. + Address *AddressPreview `json:"address,omitempty"` + // CustomerIPAddress: IP address for this transaction preview. Send one of `address_id`, `customer_ip_address`, or the `address` object when previewing. + CustomerIPAddress *string `json:"customer_ip_address,omitempty"` + // Items: List of items to preview price calculations for. + Items []PricePreviewItem `json:"items,omitempty"` +} + +// PricePreview performs the POST operation on a Pricing preview resource. +func (c *PricingPreviewClient) PricePreview(ctx context.Context, req *PricePreviewRequest) (res *PricePreview, err error) { + if err := c.doer.Do(ctx, "POST", "/pricing-preview", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/products.go b/products.go new file mode 100644 index 0000000..15f8555 --- /dev/null +++ b/products.go @@ -0,0 +1,167 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import ( + "context" + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrProductTaxCategoryNotApproved represents a `product_tax_category_not_approved` error. +// See https://developer.paddle.com/errors/products/product_tax_category_not_approved for more information. +var ErrProductTaxCategoryNotApproved = &paddleerr.Error{ + Code: "product_tax_category_not_approved", + Type: paddleerr.ErrorTypeRequestError, +} + +// ProductWithIncludes: Represents a product entity with included entities. +type ProductWithIncludes struct { + // ID: Unique Paddle ID for this product, prefixed with `pro_`. + ID string `json:"id,omitempty"` + // Name: Name of this product. + Name string `json:"name,omitempty"` + // Description: Short description for this product. + Description *string `json:"description,omitempty"` + // Type: Type of item. Standard items are considered part of your catalog and are shown on the Paddle web app. + Type string `json:"type,omitempty"` + // TaxCategory: Tax category for this product. Used for charging the correct rate of tax. Selected tax category must be enabled on your Paddle account. + TaxCategory string `json:"tax_category,omitempty"` + // ImageURL: Image for this product. Included in the checkout and on some customer documents. + ImageURL *string `json:"image_url,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status string `json:"status,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // Prices: Represents a price entity. + Prices []Price `json:"prices,omitempty"` +} + +// ProductsClient is a client for the Products resource. +type ProductsClient struct { + doer Doer +} + +// ListProductsRequest is given as an input to ListProducts. +type ListProductsRequest struct { + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // ID is a query parameter. + // Return only the IDs specified. Use a comma-separated list to get multiple entities. + ID []string `in:"query=id,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `created_at`, `custom_data`, `description`, `id`, `image_url`, `name`, `status`, `tax_category`, and `updated_at`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `50`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // Status is a query parameter. + // Return entities that match the specified status. Use a comma-separated list to specify multiple status values. + Status []string `in:"query=status,omitempty" json:"-"` + // TaxCategory is a query parameter. + // Return entities that match the specified tax category. Use a comma-separated list to specify multiple tax categories. + TaxCategory []string `in:"query=tax_category,omitempty" json:"-"` + // Type is a query parameter. + // Return items that match the specified type. + Type *string `in:"query=type,omitempty" json:"-"` + + // IncludePrices allows requesting the prices sub-resource as part of this request. + // If set to true, will be included on the response. + IncludePrices bool `in:"paddle_include=prices" json:"-"` +} + +// ListProducts performs the GET operation on a Products resource. +func (c *ProductsClient) ListProducts(ctx context.Context, req *ListProductsRequest) (res *Collection[*ProductWithIncludes], err error) { + if err := c.doer.Do(ctx, "GET", "/products", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// CreateProductRequest is given as an input to CreateProduct. +type CreateProductRequest struct { + // Name: Name of this product. + Name string `json:"name,omitempty"` + // TaxCategory: Tax category for this product. Used for charging the correct rate of tax. Selected tax category must be enabled on your Paddle account. + TaxCategory string `json:"tax_category,omitempty"` + // Description: Short description for this product. + Description *string `json:"description,omitempty"` + // Type: Type of item. Standard items are considered part of your catalog and are shown on the Paddle web app. + Type *string `json:"type,omitempty"` + // ImageURL: Image for this product. Included in the checkout and on some customer documents. + ImageURL *string `json:"image_url,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` +} + +// CreateProduct performs the POST operation on a Products resource. +func (c *ProductsClient) CreateProduct(ctx context.Context, req *CreateProductRequest) (res *Product, err error) { + if err := c.doer.Do(ctx, "POST", "/products", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// GetProductRequest is given as an input to GetProduct. +type GetProductRequest struct { + // URL path parameters. + ProductID string `in:"path=product_id" json:"-"` + + // IncludePrices allows requesting the prices sub-resource as part of this request. + // If set to true, will be included on the response. + IncludePrices bool `in:"paddle_include=prices" json:"-"` +} + +// GetProduct performs the GET operation on a Products resource. +func (c *ProductsClient) GetProduct(ctx context.Context, req *GetProductRequest) (res *ProductWithIncludes, err error) { + if err := c.doer.Do(ctx, "GET", "/products/{product_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// UpdateProductRequest is given as an input to UpdateProduct. +type UpdateProductRequest struct { + // URL path parameters. + ProductID string `in:"path=product_id" json:"-"` + + // Name: Name of this product. + Name *PatchField[string] `json:"name,omitempty"` + // Description: Short description for this product. + Description *PatchField[*string] `json:"description,omitempty"` + // Type: Type of item. Standard items are considered part of your catalog and are shown on the Paddle web app. + Type *PatchField[string] `json:"type,omitempty"` + // TaxCategory: Tax category for this product. Used for charging the correct rate of tax. Selected tax category must be enabled on your Paddle account. + TaxCategory *PatchField[string] `json:"tax_category,omitempty"` + // ImageURL: Image for this product. Included in the checkout and on some customer documents. + ImageURL *PatchField[*string] `json:"image_url,omitempty"` + // CustomData: Your own structured key-value data. + CustomData *PatchField[CustomData] `json:"custom_data,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status *PatchField[string] `json:"status,omitempty"` +} + +// UpdateProduct performs the PATCH operation on a Products resource. +func (c *ProductsClient) UpdateProduct(ctx context.Context, req *UpdateProductRequest) (res *Product, err error) { + if err := c.doer.Do(ctx, "PATCH", "/products/{product_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/ptr_to.go b/ptr_to.go new file mode 100644 index 0000000..82a0a96 --- /dev/null +++ b/ptr_to.go @@ -0,0 +1,6 @@ +package paddle + +// PtrTo creates a pointer from a given value. +func PtrTo[V any](v V) *V { + return &v +} diff --git a/reports.go b/reports.go new file mode 100644 index 0000000..f0a3ed0 --- /dev/null +++ b/reports.go @@ -0,0 +1,287 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import ( + "context" + "encoding/json" + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrReportCreationLimitExceeded represents a `report_creation_limit_exceeded` error. +// See https://developer.paddle.com/errors/reports/report_creation_limit_exceeded for more information. +var ErrReportCreationLimitExceeded = &paddleerr.Error{ + Code: "report_creation_limit_exceeded", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrReportNotReady represents a `report_not_ready` error. +// See https://developer.paddle.com/errors/reports/report_not_ready for more information. +var ErrReportNotReady = &paddleerr.Error{ + Code: "report_not_ready", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrReportExpired represents a `report_expired` error. +// See https://developer.paddle.com/errors/reports/report_expired for more information. +var ErrReportExpired = &paddleerr.Error{ + Code: "report_expired", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrReportFailed represents a `report_failed` error. +// See https://developer.paddle.com/errors/reports/report_failed for more information. +var ErrReportFailed = &paddleerr.Error{ + Code: "report_failed", + Type: paddleerr.ErrorTypeRequestError, +} + +// Report: Represents a report entity. +type Report struct { + // ID: Unique Paddle ID for this report, prefixed with `rep_` + ID string `json:"id,omitempty"` + /* + Status: Status of this report. Set automatically by Paddle. + + Reports are created as `pending` initially, then move to `ready` when they're available to download. + */ + Status string `json:"status,omitempty"` + // Rows: Number of records in this report. `null` if the report is `pending`. + Rows *int `json:"rows,omitempty"` + // Type: Type of report. + Type string `json:"type,omitempty"` + // Filters: List of filters applied to this report. + Filters []ReportFilters `json:"filters,omitempty"` + // ExpiresAt: RFC 3339 datetime string of when this report expires. The report is no longer available to download after this date. + ExpiresAt *string `json:"expires_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this report was last updated. + UpdatedAt string `json:"updated_at,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this report was created. + CreatedAt string `json:"created_at,omitempty"` +} + +// ReportTypeAdjustments: Type of report to create.. +type ReportTypeAdjustments string + +const ( + ReportTypeAdjustmentsAdjustments = "adjustments" + ReportTypeAdjustmentsAdjustmentLineItems = "adjustment_line_items" +) + +// ReportsReportFilters: Filter criteria for this report. If omitted, reports are filtered to include data updated in the last 30 days. This means `updated_at` is greater than or equal to (`gte`) the date 30 days ago from the time the report was generated. +type ReportsReportFilters struct { + // Name: Field name to filter by. + Name string `json:"name,omitempty"` + // Operator: Operator to use when filtering. Valid when filtering by `updated_at`, `null` otherwise. + Operator *string `json:"operator,omitempty"` + // Value: Value to filter by. Check the allowed values descriptions for the `name` field to see valid values for a field. + Value string `json:"value,omitempty"` +} + +// AdjustmentsReports: Request body when creating reports for adjustments or adjustment line items. +type AdjustmentsReports struct { + // Type: Type of report to create. + Type string `json:"type,omitempty"` + // Filters: Filter criteria for this report. If omitted, reports are filtered to include data updated in the last 30 days. This means `updated_at` is greater than or equal to (`gte`) the date 30 days ago from the time the report was generated. + Filters []ReportsReportFilters `json:"filters,omitempty"` +} + +// ReportsReportsReportFilters: Filter criteria for this report. If omitted, reports are filtered to include data updated in the last 30 days. This means `updated_at` is greater than or equal to (`gte`) the date 30 days ago from the time the report was generated. +type ReportsReportsReportFilters struct { + // Name: Field name to filter by. + Name string `json:"name,omitempty"` + // Operator: Operator to use when filtering. Valid when filtering by `updated_at`, `null` otherwise. + Operator *string `json:"operator,omitempty"` + // Value: Value to filter by. Check the allowed values descriptions for the `name` field to see valid values for a field. + Value string `json:"value,omitempty"` +} + +// TransactionsReports: Request body when creating reports for transaction or transaction line items. +type TransactionsReports struct { + // Type: Type of report to create. + Type string `json:"type,omitempty"` + // Filters: Filter criteria for this report. If omitted, reports are filtered to include data updated in the last 30 days. This means `updated_at` is greater than or equal to (`gte`) the date 30 days ago from the time the report was generated. + Filters []ReportsReportsReportFilters `json:"filters,omitempty"` +} + +// ReportsReportsReportsReportFilters: Filter criteria for this report. If omitted, reports are filtered to include data updated in the last 30 days. This means `product_updated_at` and `price_updated_at` are greater than or equal to (`gte`) the date 30 days ago from the time the report was generated. +type ReportsReportsReportsReportFilters struct { + // Name: Field name to filter by. + Name string `json:"name,omitempty"` + // Operator: Operator to use when filtering. Valid when filtering by `product_updated_at` or `price_updated_at`, `null` otherwise. + Operator *string `json:"operator,omitempty"` + // Value: Value to filter by. + Value string `json:"value,omitempty"` +} + +// ProductsAndPricesReport: Request body when creating a products and prices report. +type ProductsAndPricesReport struct { + // Type: Type of report to create. + Type string `json:"type,omitempty"` + // Filters: Filter criteria for this report. If omitted, reports are filtered to include data updated in the last 30 days. This means `product_updated_at` and `price_updated_at` are greater than or equal to (`gte`) the date 30 days ago from the time the report was generated. + Filters []ReportsReportsReportsReportFilters `json:"filters,omitempty"` +} + +// ReportsReportsReportsReportsReportFilters: Filter criteria for this report. If omitted, reports are filtered to include data updated in the last 30 days. This means `updated_at` is greater than or equal to (`gte`) the date 30 days ago from the time the report was generated. +type ReportsReportsReportsReportsReportFilters struct { + // Name: Field name to filter by. + Name string `json:"name,omitempty"` + // Operator: Operator to use when filtering. Valid when filtering by `updated_at`, `null` otherwise. + Operator *string `json:"operator,omitempty"` + // Value: Value to filter by. Check the allowed values descriptions for the `name` field to see valid values for a field. + Value string `json:"value,omitempty"` +} + +// DiscountsReport: Request body when creating a discounts report. +type DiscountsReport struct { + // Type: Type of report to create. + Type string `json:"type,omitempty"` + // Filters: Filter criteria for this report. If omitted, reports are filtered to include data updated in the last 30 days. This means `updated_at` is greater than or equal to (`gte`) the date 30 days ago from the time the report was generated. + Filters []ReportsReportsReportsReportsReportFilters `json:"filters,omitempty"` +} + +type ReportCSV struct { + // URL: URL of the requested resource. + URL string `json:"url,omitempty"` +} + +// ReportsClient is a client for the Reports resource. +type ReportsClient struct { + doer Doer +} + +// ListReportsRequest is given as an input to ListReports. +type ListReportsRequest struct { + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `id`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `50`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // Status is a query parameter. + // Return entities that match the specified status. Use a comma-separated list to specify multiple status values. + Status []string `in:"query=status,omitempty" json:"-"` +} + +// ListReports performs the GET operation on a Reports resource. +func (c *ReportsClient) ListReports(ctx context.Context, req *ListReportsRequest) (res *Collection[*Report], err error) { + if err := c.doer.Do(ctx, "GET", "/reports", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// NewCreateReportRequestAdjustmentsReports takes a AdjustmentsReports type +// and creates a CreateReportRequest for use in a request. +func NewCreateReportRequestAdjustmentsReports(r *AdjustmentsReports) *CreateReportRequest { + return &CreateReportRequest{AdjustmentsReports: r} +} + +// NewCreateReportRequestTransactionsReports takes a TransactionsReports type +// and creates a CreateReportRequest for use in a request. +func NewCreateReportRequestTransactionsReports(r *TransactionsReports) *CreateReportRequest { + return &CreateReportRequest{TransactionsReports: r} +} + +// NewCreateReportRequestProductsAndPricesReport takes a ProductsAndPricesReport type +// and creates a CreateReportRequest for use in a request. +func NewCreateReportRequestProductsAndPricesReport(r *ProductsAndPricesReport) *CreateReportRequest { + return &CreateReportRequest{ProductsAndPricesReport: r} +} + +// NewCreateReportRequestDiscountsReport takes a DiscountsReport type +// and creates a CreateReportRequest for use in a request. +func NewCreateReportRequestDiscountsReport(r *DiscountsReport) *CreateReportRequest { + return &CreateReportRequest{DiscountsReport: r} +} + +// CreateReportRequest represents a union request type of the following types: +// - `AdjustmentsReports` +// - `TransactionsReports` +// - `ProductsAndPricesReport` +// - `DiscountsReport` +// +// The following constructor functions can be used to create a new instance of this type. +// - `NewCreateReportRequestAdjustmentsReports()` +// - `NewCreateReportRequestTransactionsReports()` +// - `NewCreateReportRequestProductsAndPricesReport()` +// - `NewCreateReportRequestDiscountsReport()` +// +// Only one of the values can be set at a time, the first non-nil value will be used in the request. +type CreateReportRequest struct { + *AdjustmentsReports + *TransactionsReports + *ProductsAndPricesReport + *DiscountsReport +} + +// CreateReport performs the POST operation on a Reports resource. +func (c *ReportsClient) CreateReport(ctx context.Context, req *CreateReportRequest) (res *Report, err error) { + if err := c.doer.Do(ctx, "POST", "/reports", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (u CreateReportRequest) MarshalJSON() ([]byte, error) { + if u.AdjustmentsReports != nil { + return json.Marshal(u.AdjustmentsReports) + } + + if u.TransactionsReports != nil { + return json.Marshal(u.TransactionsReports) + } + + if u.ProductsAndPricesReport != nil { + return json.Marshal(u.ProductsAndPricesReport) + } + + if u.DiscountsReport != nil { + return json.Marshal(u.DiscountsReport) + } + + return nil, nil +} + +// GetReportCSVRequest is given as an input to GetReportCSV. +type GetReportCSVRequest struct { + // URL path parameters. + ReportID string `in:"path=report_id" json:"-"` +} + +// GetReportCSV performs the GET operation on a Reports resource. +func (c *ReportsClient) GetReportCSV(ctx context.Context, req *GetReportCSVRequest) (res *ReportCSV, err error) { + if err := c.doer.Do(ctx, "GET", "/reports/{report_id}/download-url", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// GetReportRequest is given as an input to GetReport. +type GetReportRequest struct { + // URL path parameters. + ReportID string `in:"path=report_id" json:"-"` +} + +// GetReport performs the GET operation on a Reports resource. +func (c *ReportsClient) GetReport(ctx context.Context, req *GetReportRequest) (res *Report, err error) { + if err := c.doer.Do(ctx, "GET", "/reports/{report_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/sdk.go b/sdk.go new file mode 100644 index 0000000..79c23bd --- /dev/null +++ b/sdk.go @@ -0,0 +1,48 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +// SDK contains all sub-clients for the Paddle API. +type SDK struct { + *ProductsClient + *PricesClient + *TransactionsClient + *PricingPreviewClient + *AdjustmentsClient + *CustomersClient + *AddressesClient + *BusinessesClient + *NotificationSettingsClient + *EventsTypesClient + *EventsClient + *NotificationsClient + *NotificationLogsClient + *NotificationSettingReplaysClient + *IPAddressesClient + *DiscountsClient + *SubscriptionsClient + *ReportsClient +} + +// newSDK creates a new SDK instance. This is auto-generated, modifications should be done in the generator. +func newSDK(d Doer) *SDK { + return &SDK{ + AddressesClient: &AddressesClient{doer: d}, + AdjustmentsClient: &AdjustmentsClient{doer: d}, + BusinessesClient: &BusinessesClient{doer: d}, + CustomersClient: &CustomersClient{doer: d}, + DiscountsClient: &DiscountsClient{doer: d}, + EventsClient: &EventsClient{doer: d}, + EventsTypesClient: &EventsTypesClient{doer: d}, + IPAddressesClient: &IPAddressesClient{doer: d}, + NotificationLogsClient: &NotificationLogsClient{doer: d}, + NotificationSettingReplaysClient: &NotificationSettingReplaysClient{doer: d}, + NotificationSettingsClient: &NotificationSettingsClient{doer: d}, + NotificationsClient: &NotificationsClient{doer: d}, + PricesClient: &PricesClient{doer: d}, + PricingPreviewClient: &PricingPreviewClient{doer: d}, + ProductsClient: &ProductsClient{doer: d}, + ReportsClient: &ReportsClient{doer: d}, + SubscriptionsClient: &SubscriptionsClient{doer: d}, + TransactionsClient: &TransactionsClient{doer: d}, + } +} diff --git a/shared.go b/shared.go new file mode 100644 index 0000000..1b4b8f2 --- /dev/null +++ b/shared.go @@ -0,0 +1,1585 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" + +// ErrNotFound represents a `not_found` error. +// See https://developer.paddle.com/errors/shared/not_found for more information. +var ErrNotFound = &paddleerr.Error{ + Code: "not_found", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrInvalidURL represents a `invalid_url` error. +// See https://developer.paddle.com/errors/shared/invalid_url for more information. +var ErrInvalidURL = &paddleerr.Error{ + Code: "invalid_url", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAuthenticationMissing represents a `authentication_missing` error. +// See https://developer.paddle.com/errors/shared/authentication_missing for more information. +var ErrAuthenticationMissing = &paddleerr.Error{ + Code: "authentication_missing", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrAuthenticationMalformed represents a `authentication_malformed` error. +// See https://developer.paddle.com/errors/shared/authentication_malformed for more information. +var ErrAuthenticationMalformed = &paddleerr.Error{ + Code: "authentication_malformed", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrInvalidToken represents a `invalid_token` error. +// See https://developer.paddle.com/errors/shared/invalid_token for more information. +var ErrInvalidToken = &paddleerr.Error{ + Code: "invalid_token", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrPaddleBillingNotEnabled represents a `paddle_billing_not_enabled` error. +// See https://developer.paddle.com/errors/shared/paddle_billing_not_enabled for more information. +var ErrPaddleBillingNotEnabled = &paddleerr.Error{ + Code: "paddle_billing_not_enabled", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrForbidden represents a `forbidden` error. +// See https://developer.paddle.com/errors/shared/forbidden for more information. +var ErrForbidden = &paddleerr.Error{ + Code: "forbidden", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrBadRequest represents a `bad_request` error. +// See https://developer.paddle.com/errors/shared/bad_request for more information. +var ErrBadRequest = &paddleerr.Error{ + Code: "bad_request", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrInternalError represents a `internal_error` error. +// See https://developer.paddle.com/errors/shared/internal_error for more information. +var ErrInternalError = &paddleerr.Error{ + Code: "internal_error", + Type: paddleerr.ErrorTypeAPIError, +} + +// ErrServiceUnavailable represents a `service_unavailable` error. +// See https://developer.paddle.com/errors/shared/service_unavailable for more information. +var ErrServiceUnavailable = &paddleerr.Error{ + Code: "service_unavailable", + Type: paddleerr.ErrorTypeAPIError, +} + +// ErrMethodNotAllowed represents a `method_not_allowed` error. +// See https://developer.paddle.com/errors/shared/method_not_allowed for more information. +var ErrMethodNotAllowed = &paddleerr.Error{ + Code: "method_not_allowed", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrNotImplemented represents a `not_implemented` error. +// See https://developer.paddle.com/errors/shared/not_implemented for more information. +var ErrNotImplemented = &paddleerr.Error{ + Code: "not_implemented", + Type: paddleerr.ErrorTypeAPIError, +} + +// ErrBadGateway represents a `bad_gateway` error. +// See https://developer.paddle.com/errors/shared/bad_gateway for more information. +var ErrBadGateway = &paddleerr.Error{ + Code: "bad_gateway", + Type: paddleerr.ErrorTypeAPIError, +} + +// ErrTooManyRequests represents a `too_many_requests` error. +// See https://developer.paddle.com/errors/shared/too_many_requests for more information. +var ErrTooManyRequests = &paddleerr.Error{ + Code: "too_many_requests", + Type: paddleerr.ErrorTypeAPIError, +} + +// ErrEntityArchived represents a `entity_archived` error. +// See https://developer.paddle.com/errors/shared/entity_archived for more information. +var ErrEntityArchived = &paddleerr.Error{ + Code: "entity_archived", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrInvalidField represents a `invalid_field` error. +// See https://developer.paddle.com/errors/shared/invalid_field for more information. +var ErrInvalidField = &paddleerr.Error{ + Code: "invalid_field", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrConcurrentModification represents a `concurrent_modification` error. +// See https://developer.paddle.com/errors/shared/concurrent_modification for more information. +var ErrConcurrentModification = &paddleerr.Error{ + Code: "concurrent_modification", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrConflict represents a `conflict` error. +// See https://developer.paddle.com/errors/shared/conflict for more information. +var ErrConflict = &paddleerr.Error{ + Code: "conflict", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrInvalidJson represents a `invalid_json` error. +// See https://developer.paddle.com/errors/shared/invalid_json for more information. +var ErrInvalidJson = &paddleerr.Error{ + Code: "invalid_json", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrInvalidTimeQueryParameter represents a `invalid_time_query_parameter` error. +// See https://developer.paddle.com/errors/shared/invalid_time_query_parameter for more information. +var ErrInvalidTimeQueryParameter = &paddleerr.Error{ + Code: "invalid_time_query_parameter", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrUnsupportedMediaType represents a `unsupported_media_type` error. +// See https://developer.paddle.com/errors/shared/unsupported_media_type for more information. +var ErrUnsupportedMediaType = &paddleerr.Error{ + Code: "unsupported_media_type", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrReceiptDataNotEnabled represents a `receipt_data_not_enabled` error. +// See https://developer.paddle.com/errors/shared/receipt_data_not_enabled for more information. +var ErrReceiptDataNotEnabled = &paddleerr.Error{ + Code: "receipt_data_not_enabled", + Type: paddleerr.ErrorTypeRequestError, +} + +// CatalogType: Return items that match the specified type.. +type CatalogType string + +const ( + CatalogTypeCustom = "custom" + CatalogTypeStandard = "standard" +) + +// TaxCategory: Tax category for this product. Used for charging the correct rate of tax. Selected tax category must be enabled on your Paddle account.. +type TaxCategory string + +const ( + TaxCategoryDigitalGoods = "digital-goods" + TaxCategoryEbooks = "ebooks" + TaxCategoryImplementationServices = "implementation-services" + TaxCategoryProfessionalServices = "professional-services" + TaxCategorySaas = "saas" + TaxCategorySoftwareProgrammingServices = "software-programming-services" + TaxCategoryStandard = "standard" + TaxCategoryTrainingServices = "training-services" + TaxCategoryWebsiteHosting = "website-hosting" +) + +// Status: Whether this entity can be used in Paddle.. +type Status string + +const ( + StatusActive = "active" + StatusArchived = "archived" +) + +// ImportMeta: Import information for this entity. `null` if this entity is not imported. +type ImportMeta struct { + // ExternalID: Reference or identifier for this entity from the solution where it was imported from. + ExternalID *string `json:"external_id,omitempty"` + // ImportedFrom: Name of the platform where this entity was imported from. + ImportedFrom string `json:"imported_from,omitempty"` +} + +// Interval: Unit of time.. +type Interval string + +const ( + IntervalDay = "day" + IntervalWeek = "week" + IntervalMonth = "month" + IntervalYear = "year" +) + +// Duration: How often this price should be charged. `null` if price is non-recurring (one-time). +type Duration struct { + // Interval: Unit of time. + Interval string `json:"interval,omitempty"` + // Frequency: Amount of time. + Frequency int `json:"frequency,omitempty"` +} + +// TaxMode: How tax is calculated for this price.. +type TaxMode string + +const ( + TaxModeAccountSetting = "account_setting" + TaxModeExternal = "external" + TaxModeInternal = "internal" +) + +// CurrencyCode: Supported three-letter ISO 4217 currency code.. +type CurrencyCode string + +const ( + CurrencyCodeUSD = "USD" + CurrencyCodeEUR = "EUR" + CurrencyCodeGBP = "GBP" + CurrencyCodeJPY = "JPY" + CurrencyCodeAUD = "AUD" + CurrencyCodeCAD = "CAD" + CurrencyCodeCHF = "CHF" + CurrencyCodeHKD = "HKD" + CurrencyCodeSGD = "SGD" + CurrencyCodeSEK = "SEK" + CurrencyCodeARS = "ARS" + CurrencyCodeBRL = "BRL" + CurrencyCodeCNY = "CNY" + CurrencyCodeCOP = "COP" + CurrencyCodeCZK = "CZK" + CurrencyCodeDKK = "DKK" + CurrencyCodeHUF = "HUF" + CurrencyCodeILS = "ILS" + CurrencyCodeINR = "INR" + CurrencyCodeKRW = "KRW" + CurrencyCodeMXN = "MXN" + CurrencyCodeNOK = "NOK" + CurrencyCodeNZD = "NZD" + CurrencyCodePLN = "PLN" + CurrencyCodeRUB = "RUB" + CurrencyCodeTHB = "THB" + CurrencyCodeTRY = "TRY" + CurrencyCodeTWD = "TWD" + CurrencyCodeUAH = "UAH" + CurrencyCodeZAR = "ZAR" +) + +// Money: Base price. This price applies to all customers, except for customers located in countries where you have `unit_price_overrides`. +type Money struct { + // Amount: Amount in the lowest denomination for the currency, e.g. 10 USD = 1000 (cents). + Amount string `json:"amount,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// CountryCode: Supported two-letter ISO 3166-1 alpha-2 country code.. +type CountryCode string + +const ( + CountryCodeAD = "AD" + CountryCodeAE = "AE" + CountryCodeAG = "AG" + CountryCodeAI = "AI" + CountryCodeAL = "AL" + CountryCodeAM = "AM" + CountryCodeAO = "AO" + CountryCodeAR = "AR" + CountryCodeAS = "AS" + CountryCodeAT = "AT" + CountryCodeAU = "AU" + CountryCodeAW = "AW" + CountryCodeAX = "AX" + CountryCodeAZ = "AZ" + CountryCodeBA = "BA" + CountryCodeBB = "BB" + CountryCodeBD = "BD" + CountryCodeBE = "BE" + CountryCodeBF = "BF" + CountryCodeBG = "BG" + CountryCodeBH = "BH" + CountryCodeBI = "BI" + CountryCodeBJ = "BJ" + CountryCodeBL = "BL" + CountryCodeBM = "BM" + CountryCodeBN = "BN" + CountryCodeBO = "BO" + CountryCodeBQ = "BQ" + CountryCodeBR = "BR" + CountryCodeBS = "BS" + CountryCodeBT = "BT" + CountryCodeBV = "BV" + CountryCodeBW = "BW" + CountryCodeBZ = "BZ" + CountryCodeCA = "CA" + CountryCodeCC = "CC" + CountryCodeCG = "CG" + CountryCodeCH = "CH" + CountryCodeCI = "CI" + CountryCodeCK = "CK" + CountryCodeCL = "CL" + CountryCodeCM = "CM" + CountryCodeCN = "CN" + CountryCodeCO = "CO" + CountryCodeCR = "CR" + CountryCodeCV = "CV" + CountryCodeCW = "CW" + CountryCodeCX = "CX" + CountryCodeCY = "CY" + CountryCodeCZ = "CZ" + CountryCodeDE = "DE" + CountryCodeDJ = "DJ" + CountryCodeDK = "DK" + CountryCodeDM = "DM" + CountryCodeDO = "DO" + CountryCodeDZ = "DZ" + CountryCodeEC = "EC" + CountryCodeEE = "EE" + CountryCodeEG = "EG" + CountryCodeEH = "EH" + CountryCodeER = "ER" + CountryCodeES = "ES" + CountryCodeET = "ET" + CountryCodeFI = "FI" + CountryCodeFJ = "FJ" + CountryCodeFK = "FK" + CountryCodeFM = "FM" + CountryCodeFO = "FO" + CountryCodeFR = "FR" + CountryCodeGA = "GA" + CountryCodeGB = "GB" + CountryCodeGD = "GD" + CountryCodeGE = "GE" + CountryCodeGF = "GF" + CountryCodeGG = "GG" + CountryCodeGH = "GH" + CountryCodeGI = "GI" + CountryCodeGL = "GL" + CountryCodeGM = "GM" + CountryCodeGN = "GN" + CountryCodeGP = "GP" + CountryCodeGQ = "GQ" + CountryCodeGR = "GR" + CountryCodeGS = "GS" + CountryCodeGT = "GT" + CountryCodeGU = "GU" + CountryCodeGW = "GW" + CountryCodeGY = "GY" + CountryCodeHK = "HK" + CountryCodeHM = "HM" + CountryCodeHN = "HN" + CountryCodeHR = "HR" + CountryCodeHU = "HU" + CountryCodeID = "ID" + CountryCodeIE = "IE" + CountryCodeIL = "IL" + CountryCodeIM = "IM" + CountryCodeIN = "IN" + CountryCodeIO = "IO" + CountryCodeIQ = "IQ" + CountryCodeIS = "IS" + CountryCodeIT = "IT" + CountryCodeJE = "JE" + CountryCodeJM = "JM" + CountryCodeJO = "JO" + CountryCodeJP = "JP" + CountryCodeKE = "KE" + CountryCodeKG = "KG" + CountryCodeKH = "KH" + CountryCodeKI = "KI" + CountryCodeKM = "KM" + CountryCodeKN = "KN" + CountryCodeKR = "KR" + CountryCodeKW = "KW" + CountryCodeKY = "KY" + CountryCodeKZ = "KZ" + CountryCodeLA = "LA" + CountryCodeLB = "LB" + CountryCodeLC = "LC" + CountryCodeLI = "LI" + CountryCodeLK = "LK" + CountryCodeLR = "LR" + CountryCodeLS = "LS" + CountryCodeLT = "LT" + CountryCodeLU = "LU" + CountryCodeLV = "LV" + CountryCodeMA = "MA" + CountryCodeMC = "MC" + CountryCodeMD = "MD" + CountryCodeME = "ME" + CountryCodeMF = "MF" + CountryCodeMG = "MG" + CountryCodeMH = "MH" + CountryCodeMK = "MK" + CountryCodeMN = "MN" + CountryCodeMO = "MO" + CountryCodeMP = "MP" + CountryCodeMQ = "MQ" + CountryCodeMR = "MR" + CountryCodeMS = "MS" + CountryCodeMT = "MT" + CountryCodeMU = "MU" + CountryCodeMV = "MV" + CountryCodeMW = "MW" + CountryCodeMX = "MX" + CountryCodeMY = "MY" + CountryCodeMZ = "MZ" + CountryCodeNA = "NA" + CountryCodeNC = "NC" + CountryCodeNE = "NE" + CountryCodeNF = "NF" + CountryCodeNG = "NG" + CountryCodeNL = "NL" + CountryCodeNO = "NO" + CountryCodeNP = "NP" + CountryCodeNR = "NR" + CountryCodeNU = "NU" + CountryCodeNZ = "NZ" + CountryCodeOM = "OM" + CountryCodePA = "PA" + CountryCodePE = "PE" + CountryCodePF = "PF" + CountryCodePG = "PG" + CountryCodePH = "PH" + CountryCodePK = "PK" + CountryCodePL = "PL" + CountryCodePM = "PM" + CountryCodePN = "PN" + CountryCodePR = "PR" + CountryCodePS = "PS" + CountryCodePT = "PT" + CountryCodePW = "PW" + CountryCodePY = "PY" + CountryCodeQA = "QA" + CountryCodeRE = "RE" + CountryCodeRO = "RO" + CountryCodeRS = "RS" + CountryCodeRW = "RW" + CountryCodeSA = "SA" + CountryCodeSB = "SB" + CountryCodeSC = "SC" + CountryCodeSE = "SE" + CountryCodeSG = "SG" + CountryCodeSH = "SH" + CountryCodeSI = "SI" + CountryCodeSJ = "SJ" + CountryCodeSK = "SK" + CountryCodeSL = "SL" + CountryCodeSM = "SM" + CountryCodeSN = "SN" + CountryCodeSR = "SR" + CountryCodeST = "ST" + CountryCodeSV = "SV" + CountryCodeSX = "SX" + CountryCodeSZ = "SZ" + CountryCodeTC = "TC" + CountryCodeTD = "TD" + CountryCodeTF = "TF" + CountryCodeTG = "TG" + CountryCodeTH = "TH" + CountryCodeTJ = "TJ" + CountryCodeTK = "TK" + CountryCodeTL = "TL" + CountryCodeTM = "TM" + CountryCodeTN = "TN" + CountryCodeTO = "TO" + CountryCodeTR = "TR" + CountryCodeTT = "TT" + CountryCodeTV = "TV" + CountryCodeTW = "TW" + CountryCodeTZ = "TZ" + CountryCodeUA = "UA" + CountryCodeUG = "UG" + CountryCodeUM = "UM" + CountryCodeUS = "US" + CountryCodeUY = "UY" + CountryCodeUZ = "UZ" + CountryCodeVA = "VA" + CountryCodeVC = "VC" + CountryCodeVG = "VG" + CountryCodeVI = "VI" + CountryCodeVN = "VN" + CountryCodeVU = "VU" + CountryCodeWF = "WF" + CountryCodeWS = "WS" + CountryCodeXK = "XK" + CountryCodeYT = "YT" + CountryCodeZA = "ZA" + CountryCodeZM = "ZM" +) + +// UnitPriceOverride: List of unit price overrides. Use to override the base price with a custom price and currency for a country or group of countries. +type UnitPriceOverride struct { + // CountryCodes: Supported two-letter ISO 3166-1 alpha-2 country code. + CountryCodes []CountryCode `json:"country_codes,omitempty"` + // UnitPrice: Override price. This price applies to customers located in the countries for this unit price override. + UnitPrice Money `json:"unit_price,omitempty"` +} + +// PriceQuantity: Limits on how many times the related product can be purchased at this price. Useful for discount campaigns. +type PriceQuantity struct { + // Minimum: Minimum quantity of the product related to this price that can be bought. Required if `maximum` set. + Minimum int `json:"minimum,omitempty"` + // Maximum: Maximum quantity of the product related to this price that can be bought. Required if `minimum` set. Must be greater than or equal to the `minimum` value. + Maximum int `json:"maximum,omitempty"` +} + +// Price: Represents a price entity. +type Price struct { + // ID: Unique Paddle ID for this price, prefixed with `pri_`. + ID string `json:"id,omitempty"` + // ProductID: Paddle ID for the product that this price is for, prefixed with `pro_`. + ProductID string `json:"product_id,omitempty"` + // Description: Internal description for this price, not shown to customers. Typically notes for your team. + Description string `json:"description,omitempty"` + // Type: Type of item. Standard items are considered part of your catalog and are shown on the Paddle web app. + Type string `json:"type,omitempty"` + // Name: Name of this price, shown to customers at checkout and on invoices. Typically describes how often the related product bills. + Name *string `json:"name,omitempty"` + // BillingCycle: How often this price should be charged. `null` if price is non-recurring (one-time). + BillingCycle *Duration `json:"billing_cycle,omitempty"` + // TrialPeriod: Trial period for the product related to this price. The billing cycle begins once the trial period is over. `null` for no trial period. Requires `billing_cycle`. + TrialPeriod *Duration `json:"trial_period,omitempty"` + // TaxMode: How tax is calculated for this price. + TaxMode string `json:"tax_mode,omitempty"` + // UnitPrice: Base price. This price applies to all customers, except for customers located in countries where you have `unit_price_overrides`. + UnitPrice Money `json:"unit_price,omitempty"` + // UnitPriceOverrides: List of unit price overrides. Use to override the base price with a custom price and currency for a country or group of countries. + UnitPriceOverrides []UnitPriceOverride `json:"unit_price_overrides,omitempty"` + // Quantity: Limits on how many times the related product can be purchased at this price. Useful for discount campaigns. + Quantity PriceQuantity `json:"quantity,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status string `json:"status,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` +} + +// Pagination: Keys used for working with paginated results. +type Pagination struct { + // PerPage: Number of entities per page for this response. May differ from the number requested if the requested number is greater than the maximum. + PerPage int `json:"per_page,omitempty"` + // Next: URL containing the query parameters of the original request, along with the `after` parameter that marks the starting point of the next page. Always returned, even if `has_more` is `false`. + Next string `json:"next,omitempty"` + // HasMore: Whether this response has another page. + HasMore bool `json:"has_more,omitempty"` + // EstimatedTotal: Estimated number of entities for this response. + EstimatedTotal int `json:"estimated_total,omitempty"` +} + +// MetaPaginated: Information about this response. +type MetaPaginated struct { + // RequestID: Unique ID for the request relating to this response. Provide this when contacting Paddle support about a specific request. + RequestID string `json:"request_id,omitempty"` + // Pagination: Keys used for working with paginated results. + Pagination Pagination `json:"pagination,omitempty"` +} + +// Product: Represents a product entity. +type Product struct { + // ID: Unique Paddle ID for this product, prefixed with `pro_`. + ID string `json:"id,omitempty"` + // Name: Name of this product. + Name string `json:"name,omitempty"` + // Description: Short description for this product. + Description *string `json:"description,omitempty"` + // Type: Type of item. Standard items are considered part of your catalog and are shown on the Paddle web app. + Type string `json:"type,omitempty"` + // TaxCategory: Tax category for this product. Used for charging the correct rate of tax. Selected tax category must be enabled on your Paddle account. + TaxCategory string `json:"tax_category,omitempty"` + // ImageURL: Image for this product. Included in the checkout and on some customer documents. + ImageURL *string `json:"image_url,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status string `json:"status,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` +} + +// Meta: Information about this response. +type Meta struct { + // RequestID: Unique ID for the request relating to this response. Provide this when contacting Paddle support about a specific request. + RequestID string `json:"request_id,omitempty"` +} + +// CollectionMode: Return entities that match the specified collection mode.. +type CollectionMode string + +const ( + CollectionModeAutomatic = "automatic" + CollectionModeManual = "manual" +) + +// TransactionStatus: Status of this transaction. You may set a transaction to `billed` or `canceled`, other statuses are set automatically by Paddle. Automatically-collected transactions may return `completed` if payment is captured successfully, or `past_due` if payment failed.. +type TransactionStatus string + +const ( + TransactionStatusDraft = "draft" + TransactionStatusReady = "ready" + TransactionStatusBilled = "billed" + TransactionStatusPaid = "paid" + TransactionStatusCompleted = "completed" + TransactionStatusCanceled = "canceled" + TransactionStatusPastDue = "past_due" +) + +// TransactionOrigin: Describes how this transaction was created.. +type TransactionOrigin string + +const ( + TransactionOriginAPI = "api" + TransactionOriginSubscriptionCharge = "subscription_charge" + TransactionOriginSubscriptionPaymentMethodChange = "subscription_payment_method_change" + TransactionOriginSubscriptionRecurring = "subscription_recurring" + TransactionOriginSubscriptionUpdate = "subscription_update" + TransactionOriginWeb = "web" +) + +// BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. +type BillingDetails struct { + // EnableCheckout: Whether the related transaction may be paid using a Paddle Checkout. + EnableCheckout bool `json:"enable_checkout,omitempty"` + // PurchaseOrderNumber: Customer purchase order number. Appears on invoice documents. + PurchaseOrderNumber string `json:"purchase_order_number,omitempty"` + // AdditionalInformation: Notes or other information to include on this invoice. Appears on invoice documents. + AdditionalInformation *string `json:"additional_information,omitempty"` + // PaymentTerms: How long a customer has to pay this invoice once issued. + PaymentTerms Duration `json:"payment_terms,omitempty"` +} + +// TimePeriod: Time period that this transaction is for. Set automatically by Paddle for subscription renewals to describe the period that charges are for. +type TimePeriod struct { + // StartsAt: RFC 3339 datetime string of when this period starts. + StartsAt string `json:"starts_at,omitempty"` + // EndsAt: RFC 3339 datetime string of when this period ends. + EndsAt string `json:"ends_at,omitempty"` +} + +// Proration: How proration was calculated for this item. Populated when a transaction is created from a subscription change, where `proration_billing_mode` was `prorated_immediately` or `prorated_next_billing_period`. Set automatically by Paddle. +type Proration struct { + // Rate: Rate used to calculate proration. + Rate string `json:"rate,omitempty"` + // BillingPeriod: Billing period that proration is based on. + BillingPeriod TimePeriod `json:"billing_period,omitempty"` +} + +// TransactionItem: List of items on this transaction. For calculated totals, use `details.line_items`. +type TransactionItem struct { + // PriceID: Paddle ID for the price to add to this transaction, prefixed with `pri_`. + PriceID string `json:"price_id,omitempty"` + // Price: Represents a price entity. + Price Price `json:"price,omitempty"` + // Quantity: Quantity of this item on the transaction. + Quantity int `json:"quantity,omitempty"` + // Proration: How proration was calculated for this item. Populated when a transaction is created from a subscription change, where `proration_billing_mode` was `prorated_immediately` or `prorated_next_billing_period`. Set automatically by Paddle. + Proration *Proration `json:"proration,omitempty"` +} + +// Totals: Calculated totals for the tax applied to this transaction. +type Totals struct { + // Subtotal: Subtotal before discount, tax, and deductions. If an item, unit price multiplied by quantity. + Subtotal string `json:"subtotal,omitempty"` + /* + Discount: Total discount as a result of any discounts applied. + + Except for percentage discounts, Paddle applies tax to discounts based on the line item `price.tax_mode`. If `price.tax_mode` for a line item is `internal`, Paddle removes tax from the discount applied. + */ + Discount string `json:"discount,omitempty"` + // Tax: Total tax on the subtotal. + Tax string `json:"tax,omitempty"` + // Total: Total after discount and tax. + Total string `json:"total,omitempty"` +} + +// TaxRatesUsed: List of tax rates applied for this transaction. +type TaxRatesUsed struct { + // TaxRate: Rate used to calculate tax for this transaction. + TaxRate string `json:"tax_rate,omitempty"` + // Totals: Calculated totals for the tax applied to this transaction. + Totals Totals `json:"totals,omitempty"` +} + +// TransactionTotals: Breakdown of the total for a transaction. These numbers can become negative when dealing with subscription updates that result in credit. +type TransactionTotals struct { + // Subtotal: Subtotal before discount, tax, and deductions. If an item, unit price multiplied by quantity. + Subtotal string `json:"subtotal,omitempty"` + /* + Discount: Total discount as a result of any discounts applied. + + Except for percentage discounts, Paddle applies tax to discounts based on the line item `price.tax_mode`. If `price.tax_mode` for a line item is `internal`, Paddle removes tax from the discount applied. + */ + Discount string `json:"discount,omitempty"` + // Tax: Total tax on the subtotal. + Tax string `json:"tax,omitempty"` + // Total: Total after discount and tax. + Total string `json:"total,omitempty"` + // Credit: Total credit applied to this transaction. This includes credits applied using a customer's credit balance and adjustments to a `billed` transaction. + Credit string `json:"credit,omitempty"` + // CreditToBalance: Additional credit generated from negative `details.line_items`. This credit is added to the customer balance. + CreditToBalance string `json:"credit_to_balance,omitempty"` + // Balance: Total due on a transaction after credits and any payments. + Balance string `json:"balance,omitempty"` + // GrandTotal: Total due on a transaction after credits but before any payments. + GrandTotal string `json:"grand_total,omitempty"` + // Fee: Total fee taken by Paddle for this transaction. `null` until the transaction is `completed` and the fee is processed. + Fee *string `json:"fee,omitempty"` + // Earnings: Total earnings for this transaction. This is the total minus the Paddle fee. `null` until the transaction is `completed` and the fee is processed. + Earnings *string `json:"earnings,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code of the currency used for this transaction. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// TransactionTotalsAdjusted: Breakdown of the payout totals for a transaction after adjustments. `null` until the transaction is `completed`. +type TransactionTotalsAdjusted struct { + // Subtotal: Subtotal before discount, tax, and deductions. If an item, unit price multiplied by quantity. + Subtotal string `json:"subtotal,omitempty"` + // Tax: Total tax on the subtotal. + Tax string `json:"tax,omitempty"` + // Total: Total after tax. + Total string `json:"total,omitempty"` + // GrandTotal: Total due after credits but before any payments. + GrandTotal string `json:"grand_total,omitempty"` + // Fee: Total fee taken by Paddle for this transaction. `null` until the transaction is `completed` and the fee is processed. + Fee *string `json:"fee,omitempty"` + /* + Earnings: Total earnings for this transaction. This is the total minus the Paddle fee. + `null` until the transaction is `completed` and the fee is processed. + */ + Earnings *string `json:"earnings,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code of the currency used for this transaction. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// CurrencyCodePayouts: Three-letter ISO 4217 currency code used for the payout for this transaction. If your primary currency has changed, this reflects the primary currency at the time the transaction was billed.. +type CurrencyCodePayouts string + +const ( + CurrencyCodePayoutsAUD = "AUD" + CurrencyCodePayoutsCAD = "CAD" + CurrencyCodePayoutsCHF = "CHF" + CurrencyCodePayoutsCNY = "CNY" + CurrencyCodePayoutsCZK = "CZK" + CurrencyCodePayoutsDKK = "DKK" + CurrencyCodePayoutsEUR = "EUR" + CurrencyCodePayoutsGBP = "GBP" + CurrencyCodePayoutsHUF = "HUF" + CurrencyCodePayoutsPLN = "PLN" + CurrencyCodePayoutsSEK = "SEK" + CurrencyCodePayoutsUSD = "USD" + CurrencyCodePayoutsZAR = "ZAR" +) + +// TransactionPayoutTotals: Breakdown of the payout total for a transaction. `null` until the transaction is `completed`. Returned in your payout currency. +type TransactionPayoutTotals struct { + // Subtotal: Total before tax and fees. + Subtotal string `json:"subtotal,omitempty"` + /* + Discount: Total discount as a result of any discounts applied. + Except for percentage discounts, Paddle applies tax to discounts based on the line item `price.tax_mode`. If `price.tax_mode` for a line item is `internal`, Paddle removes tax from the discount applied. + */ + Discount string `json:"discount,omitempty"` + // Tax: Total tax on the subtotal. + Tax string `json:"tax,omitempty"` + // Total: Total after tax. + Total string `json:"total,omitempty"` + // Credit: Total credit applied to this transaction. This includes credits applied using a customer's credit balance and adjustments to a `billed` transaction. + Credit string `json:"credit,omitempty"` + // CreditToBalance: Additional credit generated from negative `details.line_items`. This credit is added to the customer balance. + CreditToBalance string `json:"credit_to_balance,omitempty"` + // Balance: Total due on a transaction after credits and any payments. + Balance string `json:"balance,omitempty"` + // GrandTotal: Total due on a transaction after credits but before any payments. + GrandTotal string `json:"grand_total,omitempty"` + // Fee: Total fee taken by Paddle for this payout. + Fee string `json:"fee,omitempty"` + // Earnings: Total earnings for this payout. This is the subtotal minus the Paddle fee. + Earnings string `json:"earnings,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code used for the payout for this transaction. If your primary currency has changed, this reflects the primary currency at the time the transaction was billed. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// CurrencyCodeChargebacks: Three-letter ISO 4217 currency code for the original chargeback fee.. +type CurrencyCodeChargebacks string + +const ( + CurrencyCodeChargebacksEUR = "EUR" + CurrencyCodeChargebacksGBP = "GBP" + CurrencyCodeChargebacksUSD = "USD" +) + +// Original: Chargeback fee before conversion to the payout currency. `null` when the chargeback fee is the same as the payout currency. +type Original struct { + // Amount: Fee amount for this chargeback in the original currency. + Amount string `json:"amount,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code for the original chargeback fee. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// ChargebackFee: Details of any chargeback fees incurred for this transaction. +type ChargebackFee struct { + // Amount: Chargeback fee converted into the payout currency. + Amount string `json:"amount,omitempty"` + // Original: Chargeback fee before conversion to the payout currency. `null` when the chargeback fee is the same as the payout currency. + Original *Original `json:"original,omitempty"` +} + +// TransactionPayoutTotalsAdjusted: Breakdown of the payout total for a transaction after adjustments. `null` until the transaction is `completed`. +type TransactionPayoutTotalsAdjusted struct { + // Subtotal: Total before tax and fees. + Subtotal string `json:"subtotal,omitempty"` + // Tax: Total tax on the subtotal. + Tax string `json:"tax,omitempty"` + // Total: Total after tax. + Total string `json:"total,omitempty"` + // Fee: Total fee taken by Paddle for this payout. + Fee string `json:"fee,omitempty"` + // ChargebackFee: Details of any chargeback fees incurred for this transaction. + ChargebackFee ChargebackFee `json:"chargeback_fee,omitempty"` + // Earnings: Total earnings for this payout. This is the subtotal minus the Paddle fee, excluding chargeback fees. + Earnings string `json:"earnings,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code used for the payout for this transaction. If your primary currency has changed, this reflects the primary currency at the time the transaction was billed. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// UnitTotals: Breakdown of the charge for one unit in the lowest denomination of a currency (e.g. cents for USD). +type UnitTotals struct { + // Subtotal: Unit price. + Subtotal string `json:"subtotal,omitempty"` + /* + Discount: Total discount as a result of any discounts applied. + Except for percentage discounts, Paddle applies tax to discounts based on the line item `price.tax_mode`. If `price.tax_mode` for a line item is `internal`, Paddle removes tax from the discount applied. + */ + Discount string `json:"discount,omitempty"` + // Tax: Total tax on the subtotal. + Tax string `json:"tax,omitempty"` + // Total: Total after discount and tax. + Total string `json:"total,omitempty"` +} + +// TransactionLineItem: Information about line items for this transaction. Different from transaction `items` as they include totals calculated by Paddle. Considered the source of truth for line item totals. +type TransactionLineItem struct { + // ID: Unique Paddle ID for this transaction item, prefixed with `txnitm_`. + ID string `json:"id,omitempty"` + // PriceID: Paddle ID for the price related to this transaction line item, prefixed with `pri_`. + PriceID string `json:"price_id,omitempty"` + // Quantity: Quantity of this transaction line item. + Quantity int `json:"quantity,omitempty"` + // Proration: How proration was calculated for this item. Populated when a transaction is created from a subscription change, where `proration_billing_mode` was `prorated_immediately` or `prorated_next_billing_period`. Set automatically by Paddle. + Proration *Proration `json:"proration,omitempty"` + // TaxRate: Rate used to calculate tax for this transaction line item. + TaxRate string `json:"tax_rate,omitempty"` + // UnitTotals: Breakdown of the charge for one unit in the lowest denomination of a currency (e.g. cents for USD). + UnitTotals UnitTotals `json:"unit_totals,omitempty"` + // Totals: Breakdown of a charge in the lowest denomination of a currency (e.g. cents for USD). + Totals Totals `json:"totals,omitempty"` + // Product: Related product entity for this transaction line item price. + Product Product `json:"product,omitempty"` +} + +// TransactionDetails: Calculated totals for a transaction, including proration, discounts, tax, and currency conversion. Considered the source of truth for totals on a transaction. +type TransactionDetails struct { + // TaxRatesUsed: List of tax rates applied for this transaction. + TaxRatesUsed []TaxRatesUsed `json:"tax_rates_used,omitempty"` + // Totals: Breakdown of the total for a transaction. These numbers can become negative when dealing with subscription updates that result in credit. + Totals TransactionTotals `json:"totals,omitempty"` + // AdjustedTotals: Breakdown of the payout totals for a transaction after adjustments. `null` until the transaction is `completed`. + AdjustedTotals TransactionTotalsAdjusted `json:"adjusted_totals,omitempty"` + // PayoutTotals: Breakdown of the payout total for a transaction. `null` until the transaction is `completed`. Returned in your payout currency. + PayoutTotals *TransactionPayoutTotals `json:"payout_totals,omitempty"` + // AdjustedPayoutTotals: Breakdown of the payout total for a transaction after adjustments. `null` until the transaction is `completed`. + AdjustedPayoutTotals *TransactionPayoutTotalsAdjusted `json:"adjusted_payout_totals,omitempty"` + // LineItems: Information about line items for this transaction. Different from transaction `items` as they include totals calculated by Paddle. Considered the source of truth for line item totals. + LineItems []TransactionLineItem `json:"line_items,omitempty"` +} + +// PaymentAttemptStatus: Status of this payment attempt.. +type PaymentAttemptStatus string + +const ( + PaymentAttemptStatusAuthorized = "authorized" + PaymentAttemptStatusAuthorizedFlagged = "authorized_flagged" + PaymentAttemptStatusCanceled = "canceled" + PaymentAttemptStatusCaptured = "captured" + PaymentAttemptStatusError = "error" + PaymentAttemptStatusActionRequired = "action_required" + PaymentAttemptStatusPendingNoActionRequired = "pending_no_action_required" + PaymentAttemptStatusCreated = "created" + PaymentAttemptStatusUnknown = "unknown" + PaymentAttemptStatusDropped = "dropped" +) + +// ErrorCode: Reason why a payment attempt failed. Returns `null` if payment captured successfully.. +type ErrorCode *string + +const ( + ErrorCodeAlreadyCanceled = "already_canceled" + ErrorCodeAlreadyRefunded = "already_refunded" + ErrorCodeAuthenticationFailed = "authentication_failed" + ErrorCodeBlockedCard = "blocked_card" + ErrorCodeCanceled = "canceled" + ErrorCodeDeclined = "declined" + ErrorCodeDeclinedNotRetryable = "declined_not_retryable" + ErrorCodeExpiredCard = "expired_card" + ErrorCodeFraud = "fraud" + ErrorCodeInvalidAmount = "invalid_amount" + ErrorCodeInvalidPaymentDetails = "invalid_payment_details" + ErrorCodeIssuerUnavailable = "issuer_unavailable" + ErrorCodeNotEnoughBalance = "not_enough_balance" + ErrorCodePspError = "psp_error" + ErrorCodeRedactedPaymentMethod = "redacted_payment_method" + ErrorCodeSystemError = "system_error" + ErrorCodeTransactionNotPermitted = "transaction_not_permitted" + ErrorCodeUnknown = "unknown" +) + +// PaymentMethodType: Type of payment method used for this payment attempt.. +type PaymentMethodType string + +const ( + PaymentMethodTypeAlipay = "alipay" + PaymentMethodTypeApplePay = "apple_pay" + PaymentMethodTypeBancontact = "bancontact" + PaymentMethodTypeCard = "card" + PaymentMethodTypeGooglePay = "google_pay" + PaymentMethodTypeIdeal = "ideal" + PaymentMethodTypeOffline = "offline" + PaymentMethodTypePaypal = "paypal" + PaymentMethodTypeUnknown = "unknown" + PaymentMethodTypeWireTransfer = "wire_transfer" +) + +// CardType: Type of credit or debit card used to pay.. +type CardType string + +const ( + CardTypeAmericanExpress = "american_express" + CardTypeDinersClub = "diners_club" + CardTypeDiscover = "discover" + CardTypeJcb = "jcb" + CardTypeMada = "mada" + CardTypeMaestro = "maestro" + CardTypeMastercard = "mastercard" + CardTypeUnionPay = "union_pay" + CardTypeUnknown = "unknown" + CardTypeVisa = "visa" +) + +// Card: Information about the credit or debit card used to pay. `null` unless `type` is `card`. +type Card struct { + // Type: Type of credit or debit card used to pay. + Type string `json:"type,omitempty"` + // Last4: Last four digits of the card used to pay. + Last4 string `json:"last4,omitempty"` + // ExpiryMonth: Month of the expiry date of the card used to pay. + ExpiryMonth int `json:"expiry_month,omitempty"` + // ExpiryYear: Year of the expiry year of the card used to pay. + ExpiryYear int `json:"expiry_year,omitempty"` + // CardholderName: The name on the card used to pay. + CardholderName string `json:"cardholder_name,omitempty"` +} + +// MethodDetails: Information about the payment method used for a payment attempt. +type MethodDetails struct { + // Type: Type of payment method used for this payment attempt. + Type string `json:"type,omitempty"` + // Card: Information about the credit or debit card used to pay. `null` unless `type` is `card`. + Card *Card `json:"card,omitempty"` +} + +// TransactionPaymentAttempt: List of payment attempts for this transaction, including successful payments. Sorted by `created_at` in descending order, so most recent attempts are returned first. +type TransactionPaymentAttempt struct { + // PaymentAttemptID: UUID for this payment attempt. + PaymentAttemptID string `json:"payment_attempt_id,omitempty"` + // StoredPaymentMethodID: UUID for the stored payment method used for this payment attempt. Deprecated - use `payment_method_id` instead. + StoredPaymentMethodID string `json:"stored_payment_method_id,omitempty"` + // PaymentMethodID: Paddle ID of the payment method used for this payment attempt, prefixed with `paymtd_`. + PaymentMethodID string `json:"payment_method_id,omitempty"` + // Amount: Amount for collection in the lowest denomination of a currency (e.g. cents for USD). + Amount string `json:"amount,omitempty"` + // Status: Status of this payment attempt. + Status string `json:"status,omitempty"` + // ErrorCode: Reason why a payment attempt failed. Returns `null` if payment captured successfully. + ErrorCode *string `json:"error_code,omitempty"` + // MethodDetails: Information about the payment method used for a payment attempt. + MethodDetails MethodDetails `json:"method_details,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // CapturedAt: RFC 3339 datetime string of when this payment was captured. `null` if `status` is not `captured`. + CapturedAt *string `json:"captured_at,omitempty"` +} + +// Checkout: Paddle Checkout details for this transaction. Returned for automatically-collected transactions and where `billing_details.enable_checkout` is `true` for manually-collected transactions; `null` otherwise. +type Checkout struct { + // URL: Paddle Checkout URL for this transaction, composed of the URL passed in the request or your default payment URL + `_?txn=` and the Paddle ID for this transaction. + URL *string `json:"url,omitempty"` +} + +// Address: Address for this transaction. Returned when the `include` parameter is used with the `address` value and the transaction has an `address_id`. +type Address struct { + // ID: Unique Paddle ID for this address entity, prefixed with `add_`. + ID string `json:"id,omitempty"` + // CustomerID: Paddle ID for the customer related to this address, prefixed with `cus_`. + CustomerID string `json:"customer_id,omitempty"` + // Description: Memorable description for this address. + Description *string `json:"description,omitempty"` + // FirstLine: First line of this address. + FirstLine *string `json:"first_line,omitempty"` + // SecondLine: Second line of this address. + SecondLine *string `json:"second_line,omitempty"` + // City: City of this address. + City *string `json:"city,omitempty"` + // PostalCode: ZIP or postal code of this address. Required for some countries. + PostalCode *string `json:"postal_code,omitempty"` + // Region: State, county, or region of this address. + Region *string `json:"region,omitempty"` + // CountryCode: Supported two-letter ISO 3166-1 alpha-2 country code for this address. + CountryCode string `json:"country_code,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status string `json:"status,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` +} + +// Action: How this adjustment impacts the related transaction. `refund` adjustments must be approved by Paddle, and are created with the status `pending_approval`.. +type Action string + +const ( + ActionCredit = "credit" + ActionRefund = "refund" + ActionChargeback = "chargeback" + ActionChargebackReverse = "chargeback_reverse" + ActionChargebackWarning = "chargeback_warning" + ActionCreditReverse = "credit_reverse" +) + +/* +AdjustmentStatus: Status of this adjustment. Set automatically by Paddle. + +`refund` adjustments must be approved by Paddle, and are created with the status `pending_approval` +until they move to `approved` or `rejected` on review. Other kinds of adjustment do not need approval, +so are created with the status `approved`.. +*/ +type AdjustmentStatus string + +const ( + AdjustmentStatusPendingApproval = "pending_approval" + AdjustmentStatusApproved = "approved" + AdjustmentStatusRejected = "rejected" + AdjustmentStatusReversed = "reversed" +) + +/* +AdjustmentType: Type of adjustment for this transaction item. `tax` and `proration` are automatically created by Paddle. +Include `amount` when creating a `partial` adjustment.. +*/ +type AdjustmentType string + +const ( + AdjustmentTypeFull = "full" + AdjustmentTypePartial = "partial" + AdjustmentTypeTax = "tax" + AdjustmentTypeProration = "proration" +) + +// AdjustmentItemTotals: Breakdown of the total for an adjustment item. +type AdjustmentItemTotals struct { + // Subtotal: Amount multiplied by quantity. + Subtotal string `json:"subtotal,omitempty"` + // Tax: Total tax on the subtotal. + Tax string `json:"tax,omitempty"` + // Total: Total after tax. + Total string `json:"total,omitempty"` +} + +// AdjustmentItem: List of items on this adjustment. +type AdjustmentItem struct { + // ID: Unique Paddle ID for this adjustment item, prefixed with `adjitm_`. + ID string `json:"id,omitempty"` + // ItemID: Paddle ID for the transaction item that this adjustment item relates to, prefixed with `txnitm_`. + ItemID string `json:"item_id,omitempty"` + /* + Type: Type of adjustment for this transaction item. `tax` and `proration` are automatically created by Paddle. + Include `amount` when creating a `partial` adjustment. + */ + Type string `json:"type,omitempty"` + // Amount: Amount adjusted for this transaction item. Required when adjustment type is `partial`. + Amount *string `json:"amount,omitempty"` + /* + Proration: How proration was calculated for this adjustment item. Populated when an adjustment type is `proration`. + Set automatically by Paddle. + */ + Proration *Proration `json:"proration,omitempty"` + // Totals: Breakdown of the total for an adjustment item. + Totals AdjustmentItemTotals `json:"totals,omitempty"` +} + +// AdjustmentTotals: Breakdown of the total for an adjustment. +type AdjustmentTotals struct { + // Subtotal: Total before tax. For tax adjustments, the value is 0. + Subtotal string `json:"subtotal,omitempty"` + // Tax: Total tax on the subtotal. + Tax string `json:"tax,omitempty"` + // Total: Total after tax. + Total string `json:"total,omitempty"` + // Fee: Total fee taken by Paddle for this adjustment. + Fee string `json:"fee,omitempty"` + /* + Earnings: Total earnings. This is the subtotal minus the Paddle fee. + For tax adjustments, this value is negative, which means a positive effect in the transaction earnings. + This is because the fee is originally calculated from the transaction total, so if a tax adjustment is made, + then the fee portion of it is returned. + */ + Earnings string `json:"earnings,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code used for this adjustment. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// PayoutTotalsAdjustment: Breakdown of how this adjustment affects your payout balance. +type PayoutTotalsAdjustment struct { + // Subtotal: Adjustment total before tax and fees. + Subtotal string `json:"subtotal,omitempty"` + // Tax: Total tax on the adjustment subtotal. + Tax string `json:"tax,omitempty"` + // Total: Adjustment total after tax. + Total string `json:"total,omitempty"` + // Fee: Adjusted Paddle fee. + Fee string `json:"fee,omitempty"` + // ChargebackFee: Chargeback fees incurred for this adjustment. Only returned when the adjustment `action` is `chargeback` or `chargeback_warning`. + ChargebackFee ChargebackFee `json:"chargeback_fee,omitempty"` + // Earnings: Adjusted payout earnings. This is the adjustment total plus adjusted Paddle fees, excluding chargeback fees. + Earnings string `json:"earnings,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code used for the payout for this transaction. If your primary currency has changed, this reflects the primary currency at the time the transaction was billed. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// Adjustment: Represents an adjustment entity. +type Adjustment struct { + // ID: Unique Paddle ID for this adjustment entity, prefixed with `adj_`. + ID string `json:"id,omitempty"` + // Action: How this adjustment impacts the related transaction. `refund` adjustments must be approved by Paddle, and are created with the status `pending_approval`. + Action string `json:"action,omitempty"` + // TransactionID: Paddle ID for the transaction related to this adjustment, prefixed with `txn_`. + TransactionID string `json:"transaction_id,omitempty"` + /* + SubscriptionID: Paddle ID for the subscription related to this adjustment, prefixed with `sub_`. + Set automatically by Paddle based on the `subscription_id` of the related transaction. + */ + SubscriptionID *string `json:"subscription_id,omitempty"` + /* + CustomerID: Paddle ID for the customer related to this adjustment, prefixed with `ctm_`. + Set automatically by Paddle based on the `customer_id` of the related transaction. + */ + CustomerID string `json:"customer_id,omitempty"` + // Reason: Why this adjustment was created. Appears in the Paddle Dashboard. Retained for record-keeping purposes. + Reason string `json:"reason,omitempty"` + /* + CreditAppliedToBalance: Whether this adjustment was applied to the related customer's credit balance. Only returned for `credit` adjustments. + + `false` when the related transaction `collection_mode` is `manual` and its `status` is `billed`. The adjustment is used + to reduce the `balance` due on the transaction. + + `true` for automatically-collected transactions and `completed` manually-collected transactions. + */ + CreditAppliedToBalance *bool `json:"credit_applied_to_balance,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code for this adjustment. Set automatically by Paddle based on the `currency_code` of the related transaction. + CurrencyCode string `json:"currency_code,omitempty"` + /* + Status: Status of this adjustment. Set automatically by Paddle. + + `refund` adjustments must be approved by Paddle, and are created with the status `pending_approval` + until they move to `approved` or `rejected` on review. Other kinds of adjustment do not need approval, + so are created with the status `approved`. + */ + Status string `json:"status,omitempty"` + // Items: List of items on this adjustment. + Items []AdjustmentItem `json:"items,omitempty"` + // Totals: Breakdown of the total for an adjustment. + Totals AdjustmentTotals `json:"totals,omitempty"` + // PayoutTotals: Breakdown of how this adjustment affects your payout balance. + PayoutTotals *PayoutTotalsAdjustment `json:"payout_totals,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` +} + +// Contacts: List of contacts related to this business, typically used for sending invoices. +type Contacts struct { + // Name: Full name of this contact. + Name string `json:"name,omitempty"` + // Email: Email address for this contact. + Email string `json:"email,omitempty"` +} + +// Business: Business for this transaction. Returned when the `include` parameter is used with the `business` value and the transaction has a `business_id`. +type Business struct { + // ID: Unique Paddle ID for this business entity, prefixed with `biz_`. + ID string `json:"id,omitempty"` + // CustomerID: Paddle ID for the customer related to this business, prefixed with `cus_`. + CustomerID string `json:"customer_id,omitempty"` + // Name: Name of this business. + Name string `json:"name,omitempty"` + // CompanyNumber: Company number for this business. + CompanyNumber *string `json:"company_number,omitempty"` + // TaxIdentifier: Tax or VAT Number for this business. + TaxIdentifier *string `json:"tax_identifier,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status string `json:"status,omitempty"` + // Contacts: List of contacts related to this business, typically used for sending invoices. + Contacts []Contacts `json:"contacts,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` +} + +// Customer: Customer for this transaction. Returned when the `include` parameter is used with the `customer` value and the transaction has a `customer_id`. +type Customer struct { + // ID: Unique Paddle ID for this customer entity, prefixed with `ctm_`. + ID string `json:"id,omitempty"` + // Name: Full name of this customer. Required when creating transactions where `collection_mode` is `manual` (invoices). + Name *string `json:"name,omitempty"` + // Email: Email address for this customer. + Email string `json:"email,omitempty"` + /* + MarketingConsent: Whether this customer opted into marketing from you. + Set to `true` by Paddle where a customer checks the marketing consent box when using Paddle Checkout; `false` otherwise. + */ + MarketingConsent bool `json:"marketing_consent,omitempty"` + // Status: Whether this entity can be used in Paddle. + Status string `json:"status,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // Locale: Valid IETF BCP 47 short form locale tag. If omitted, defaults to `en`. + Locale string `json:"locale,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` +} + +// DiscountStatus: Whether this entity can be used in Paddle. `expired` and `used` are set automatically by Paddle.. +type DiscountStatus string + +const ( + DiscountStatusActive = "active" + DiscountStatusArchived = "archived" + DiscountStatusExpired = "expired" + DiscountStatusUsed = "used" +) + +// DiscountType: Type of discount.. +type DiscountType string + +const ( + DiscountTypeFlat = "flat" + DiscountTypeFlatPerSeat = "flat_per_seat" + DiscountTypePercentage = "percentage" +) + +// Discount: Discount for this transaction. Returned when the `include` parameter is used with the `discount` value and the transaction has a `discount_id`. +type Discount struct { + // ID: Unique Paddle ID for this discount, prefixed with `dsc_`. + ID string `json:"id,omitempty"` + // Status: Whether this entity can be used in Paddle. `expired` and `used` are set automatically by Paddle. + Status string `json:"status,omitempty"` + // Description: Short description for this discount for your reference. Not shown to customers. + Description string `json:"description,omitempty"` + // EnabledForCheckout: Whether this discount can be applied by a customer at checkout. + EnabledForCheckout bool `json:"enabled_for_checkout,omitempty"` + // Code: Unique code that customers can use to apply this discount at checkout. Use letters and numbers only, up to 16 characters. Paddle generates a random 10-character code if a code is not provided and `enabled_for_checkout` is `true`. + Code *string `json:"code,omitempty"` + // Type: Type of discount. + Type string `json:"type,omitempty"` + // Amount: Amount to discount by. For `percentage` discounts, must be an amount between `0.01` and `100`. For `flat` and `flat_per_seat` discounts, amount in the lowest denomination for a currency. + Amount string `json:"amount,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Required where discount type is `flat` or `flat_per_seat`. + CurrencyCode *string `json:"currency_code,omitempty"` + // Recur: Whether this discount applies for multiple billing periods. + Recur bool `json:"recur,omitempty"` + // MaximumRecurringIntervals: Amount of subscription billing periods that this discount recurs for. Requires `recur`. `null` if this discount recurs forever. + MaximumRecurringIntervals *int `json:"maximum_recurring_intervals,omitempty"` + // UsageLimit: Maximum amount of times this discount can be used. This is an overall limit, rather than a per-customer limit. `null` if this discount can be used an unlimited amount of times. + UsageLimit *int `json:"usage_limit,omitempty"` + // RestrictTo: Product or price IDs that this discount is for. When including a product ID, all prices for that product can be discounted. `null` if this discount applies to all products and prices. + RestrictTo []string `json:"restrict_to,omitempty"` + // ExpiresAt: RFC 3339 datetime string of when this discount expires. Discount can no longer be applied after this date has elapsed. `null` if this discount can be applied forever. + ExpiresAt *string `json:"expires_at,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // TimesUsed: How many times this discount has been redeemed. Automatically incremented by Paddle when an order completes. + TimesUsed int `json:"times_used,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` +} + +// TransactionSubscriptionPriceCreateWithProductID: Price object for a non-catalog item to charge for. Include a `product_id` to relate this non-catalog price to an existing catalog price. +type TransactionSubscriptionPriceCreateWithProductID struct { + // Description: Internal description for this price, not shown to customers. Typically notes for your team. + Description string `json:"description,omitempty"` + // Name: Name of this price, shown to customers at checkout and on invoices. Typically describes how often the related product bills. + Name *string `json:"name,omitempty"` + // BillingCycle: How often this price should be charged. `null` if price is non-recurring (one-time). + BillingCycle *Duration `json:"billing_cycle,omitempty"` + // TrialPeriod: Trial period for the product related to this price. The billing cycle begins once the trial period is over. `null` for no trial period. Requires `billing_cycle`. + TrialPeriod *Duration `json:"trial_period,omitempty"` + // TaxMode: How tax is calculated for this price. + TaxMode string `json:"tax_mode,omitempty"` + // UnitPrice: Base price. This price applies to all customers, except for customers located in countries where you have `unit_price_overrides`. + UnitPrice Money `json:"unit_price,omitempty"` + // UnitPriceOverrides: List of unit price overrides. Use to override the base price with a custom price and currency for a country or group of countries. + UnitPriceOverrides []UnitPriceOverride `json:"unit_price_overrides,omitempty"` + // Quantity: Limits on how many times the related product can be purchased at this price. Useful for discount campaigns. If omitted, defaults to 1-100. + Quantity PriceQuantity `json:"quantity,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // ProductID: Paddle ID for the product that this price is for, prefixed with `pro_`. + ProductID string `json:"product_id,omitempty"` +} + +// TransactionSubscriptionProductCreate: Product object for a non-catalog item to charge for. +type TransactionSubscriptionProductCreate struct { + // Name: Name of this product. + Name string `json:"name,omitempty"` + // Description: Short description for this product. + Description *string `json:"description,omitempty"` + // TaxCategory: Tax category for this product. Used for charging the correct rate of tax. Selected tax category must be enabled on your Paddle account. + TaxCategory string `json:"tax_category,omitempty"` + // ImageURL: Image for this product. Included in the checkout and on some customer documents. + ImageURL *string `json:"image_url,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` +} + +// TransactionSubscriptionPriceCreateWithProduct: Price object for a non-catalog item to charge for. Include a `product` object to create a non-catalog product for this non-catalog price. +type TransactionSubscriptionPriceCreateWithProduct struct { + // Description: Internal description for this price, not shown to customers. Typically notes for your team. + Description string `json:"description,omitempty"` + // Name: Name of this price, shown to customers at checkout and on invoices. Typically describes how often the related product bills. + Name *string `json:"name,omitempty"` + // BillingCycle: How often this price should be charged. `null` if price is non-recurring (one-time). + BillingCycle *Duration `json:"billing_cycle,omitempty"` + // TrialPeriod: Trial period for the product related to this price. The billing cycle begins once the trial period is over. `null` for no trial period. Requires `billing_cycle`. + TrialPeriod *Duration `json:"trial_period,omitempty"` + // TaxMode: How tax is calculated for this price. + TaxMode string `json:"tax_mode,omitempty"` + // UnitPrice: Base price. This price applies to all customers, except for customers located in countries where you have `unit_price_overrides`. + UnitPrice Money `json:"unit_price,omitempty"` + // UnitPriceOverrides: List of unit price overrides. Use to override the base price with a custom price and currency for a country or group of countries. + UnitPriceOverrides []UnitPriceOverride `json:"unit_price_overrides,omitempty"` + // Quantity: Limits on how many times the related product can be purchased at this price. Useful for discount campaigns. If omitted, defaults to 1-100. + Quantity PriceQuantity `json:"quantity,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // Product: Product object for a non-catalog item to charge for. + Product TransactionSubscriptionProductCreate `json:"product,omitempty"` +} + +// AddressPreview: Address for this preview. Send one of `address_id`, `customer_ip_address`, or the `address` object when previewing. +type AddressPreview struct { + // PostalCode: ZIP or postal code of this address. Include for more accurate tax calculations. + PostalCode *string `json:"postal_code,omitempty"` + // CountryCode: Supported two-letter ISO 3166-1 alpha-2 country code for this address. + CountryCode string `json:"country_code,omitempty"` +} + +// TransactionLineItemPreview: Information about line items for this transaction preview. Different from transaction preview `items` as they include totals calculated by Paddle. Considered the source of truth for line item totals. +type TransactionLineItemPreview struct { + // PriceID: Paddle ID for the price related to this transaction line item, prefixed with `pri_`. + PriceID string `json:"price_id,omitempty"` + // Quantity: Quantity of this transaction line item. + Quantity int `json:"quantity,omitempty"` + // TaxRate: Rate used to calculate tax for this transaction line item. + TaxRate string `json:"tax_rate,omitempty"` + // UnitTotals: Breakdown of the charge for one unit in the lowest denomination of a currency (e.g. cents for USD). + UnitTotals UnitTotals `json:"unit_totals,omitempty"` + // Totals: Breakdown of a charge in the lowest denomination of a currency (e.g. cents for USD). + Totals Totals `json:"totals,omitempty"` + // Product: Related product entity for this transaction line item price. + Product Product `json:"product,omitempty"` +} + +// BillingDetailsUpdate: Details for invoicing. Required if `collection_mode` is `manual`. +type BillingDetailsUpdate struct { + // EnableCheckout: Whether the related transaction may be paid using a Paddle Checkout. + EnableCheckout bool `json:"enable_checkout,omitempty"` + // PurchaseOrderNumber: Customer purchase order number. Appears on invoice documents. + PurchaseOrderNumber string `json:"purchase_order_number,omitempty"` + // AdditionalInformation: Notes or other information to include on this invoice. Appears on invoice documents. + AdditionalInformation *string `json:"additional_information,omitempty"` + // PaymentTerms: How long a customer has to pay this invoice once issued. + PaymentTerms Duration `json:"payment_terms,omitempty"` +} + +// EventType: Represents an event type. +type EventType struct { + // Name: Type of event sent by Paddle, in the format `entity.event_type`. + Name string `json:"name,omitempty"` + // Description: Short description of this event type. + Description string `json:"description,omitempty"` + // Group: Group for this event type. Typically the entity that this event relates to. + Group string `json:"group,omitempty"` + // AvailableVersions: List of API versions that this event type supports. + AvailableVersions []int `json:"available_versions,omitempty"` +} + +// SubscriptionStatus: Status of this subscription. Set automatically by Paddle. Use the pause subscription or cancel subscription operations to change.. +type SubscriptionStatus string + +const ( + SubscriptionStatusActive = "active" + SubscriptionStatusCanceled = "canceled" + SubscriptionStatusPastDue = "past_due" + SubscriptionStatusPaused = "paused" + SubscriptionStatusTrialing = "trialing" +) + +// SubscriptionDiscount: Details of the discount applied to this subscription. +type SubscriptionDiscount struct { + // ID: Unique Paddle ID for this discount, prefixed with `dsc_`. + ID string `json:"id,omitempty"` + // StartsAt: RFC 3339 datetime string of when this discount was first applied. + StartsAt string `json:"starts_at,omitempty"` + // EndsAt: RFC 3339 datetime string of when this discount no longer applies. Where a discount has `maximum_recurring_intervals`, this is the date of the last billing period where this discount applies. `null` where a discount recurs forever. + EndsAt *string `json:"ends_at,omitempty"` +} + +// ScheduledChangeAction: Kind of change that's scheduled to be applied to this subscription.. +type ScheduledChangeAction string + +const ( + ScheduledChangeActionCancel = "cancel" + ScheduledChangeActionPause = "pause" + ScheduledChangeActionResume = "resume" +) + +// SubscriptionScheduledChange: Change that's scheduled to be applied to a subscription. Use the pause subscription, cancel subscription, and resume subscription operations to create scheduled changes. `null` if no scheduled changes. +type SubscriptionScheduledChange struct { + // Action: Kind of change that's scheduled to be applied to this subscription. + Action string `json:"action,omitempty"` + // EffectiveAt: RFC 3339 datetime string of when this scheduled change takes effect. + EffectiveAt string `json:"effective_at,omitempty"` + // ResumeAt: RFC 3339 datetime string of when a paused subscription should resume. Only used for `pause` scheduled changes. + ResumeAt *string `json:"resume_at,omitempty"` +} + +// SubscriptionItemStatus: Status of this subscription item. Set automatically by Paddle.. +type SubscriptionItemStatus string + +const ( + SubscriptionItemStatusActive = "active" + SubscriptionItemStatusInactive = "inactive" + SubscriptionItemStatusTrialing = "trialing" +) + +// SubscriptionItem: Represents a subscription item. +type SubscriptionItem struct { + // Status: Status of this subscription item. Set automatically by Paddle. + Status string `json:"status,omitempty"` + // Quantity: Quantity of this item on the subscription. + Quantity int `json:"quantity,omitempty"` + // Recurring: Whether this is a recurring item. `false` if one-time. + Recurring bool `json:"recurring,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this item was added to this subscription. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this item was last updated on this subscription. + UpdatedAt string `json:"updated_at,omitempty"` + // PreviouslyBilledAt: RFC 3339 datetime string of when this item was last billed. + PreviouslyBilledAt *string `json:"previously_billed_at,omitempty"` + // NextBilledAt: RFC 3339 datetime string of when this item is next scheduled to be billed. + NextBilledAt *string `json:"next_billed_at,omitempty"` + // TrialDates: Trial dates for this item. + TrialDates *TimePeriod `json:"trial_dates,omitempty"` + // Price: Related price entity for this item. This reflects the price entity at the time it was added to the subscription. + Price Price `json:"price,omitempty"` +} + +/* +ReportStatus: Status of this report. Set automatically by Paddle. + +Reports are created as `pending` initially, then move to `ready` when they're available to download.. +*/ +type ReportStatus string + +const ( + ReportStatusPending = "pending" + ReportStatusReady = "ready" + ReportStatusFailed = "failed" + ReportStatusExpired = "expired" +) + +// ReportTypeTransactions: Type of report.. +type ReportTypeTransactions string + +const ( + ReportTypeTransactionsAdjustments = "adjustments" + ReportTypeTransactionsAdjustmentLineItems = "adjustment_line_items" + ReportTypeTransactionsTransactions = "transactions" + ReportTypeTransactionsTransactionLineItems = "transaction_line_items" + ReportTypeTransactionsProductsPrices = "products_prices" + ReportTypeTransactionsDiscounts = "discounts" +) + +// Name: Field name to filter by.. +type SharedName string + +const ( + SharedNameAction = "action" + SharedNameCurrencyCode = "currency_code" + SharedNameStatus = "status" + SharedNameUpdatedAt = "updated_at" + SharedNameCollectionMode = "collection_mode" + SharedNameOrigin = "origin" + SharedNameProductStatus = "product_status" + SharedNamePriceStatus = "price_status" + SharedNameProductType = "product_type" + SharedNamePriceType = "price_type" + SharedNameProductUpdatedAt = "product_updated_at" + SharedNamePriceUpdatedAt = "price_updated_at" + SharedNameType = "type" +) + +// Operator: Operator to use when filtering. Valid when filtering by `updated_at`, `null` otherwise.. +type SharedOperator *string + +const ( + SharedOperatorLt = "lt" + SharedOperatorGte = "gte" +) + +// ReportFilters: List of filters applied to this report. +type ReportFilters struct { + // Name: Field name to filter by. + Name string `json:"name,omitempty"` + // Operator: Operator to use when filtering. Valid when filtering by `updated_at`, `null` otherwise. + Operator *string `json:"operator,omitempty"` + // Value: Value to filter by. Check the allowed values descriptions for the `name` field to see valid values for a field. + Value string `json:"value,omitempty"` +} + +// Type: Type of report to create.. +type Type string + +const TypeProductsPrices = "products_prices" + +type CustomData map[string]any diff --git a/subscriptions.go b/subscriptions.go new file mode 100644 index 0000000..8306580 --- /dev/null +++ b/subscriptions.go @@ -0,0 +1,1205 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import ( + "context" + "encoding/json" + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrSubscriptionLockedRenewal represents a `subscription_locked_renewal` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_locked_renewal for more information. +var ErrSubscriptionLockedRenewal = &paddleerr.Error{ + Code: "subscription_locked_renewal", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionLockedProcessing represents a `subscription_locked_processing` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_locked_processing for more information. +var ErrSubscriptionLockedProcessing = &paddleerr.Error{ + Code: "subscription_locked_processing", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionLockedPendingChanges represents a `subscription_locked_pending_changes` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_locked_pending_changes for more information. +var ErrSubscriptionLockedPendingChanges = &paddleerr.Error{ + Code: "subscription_locked_pending_changes", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionUpdateWhenCanceled represents a `subscription_update_when_canceled` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_update_when_canceled for more information. +var ErrSubscriptionUpdateWhenCanceled = &paddleerr.Error{ + Code: "subscription_update_when_canceled", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionUpdateWhenTrialing represents a `subscription_update_when_trialing` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_update_when_trialing for more information. +var ErrSubscriptionUpdateWhenTrialing = &paddleerr.Error{ + Code: "subscription_update_when_trialing", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionCannotBePaused represents a `subscription_cannot_be_paused` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_cannot_be_paused for more information. +var ErrSubscriptionCannotBePaused = &paddleerr.Error{ + Code: "subscription_cannot_be_paused", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionIsCanceledActionInvalid represents a `subscription_is_canceled_action_invalid` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_is_canceled_action_invalid for more information. +var ErrSubscriptionIsCanceledActionInvalid = &paddleerr.Error{ + Code: "subscription_is_canceled_action_invalid", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionIsInactiveActionInvalid represents a `subscription_is_inactive_action_invalid` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_is_inactive_action_invalid for more information. +var ErrSubscriptionIsInactiveActionInvalid = &paddleerr.Error{ + Code: "subscription_is_inactive_action_invalid", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionUpdateWhenPastDue represents a `subscription_update_when_past_due` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_update_when_past_due for more information. +var ErrSubscriptionUpdateWhenPastDue = &paddleerr.Error{ + Code: "subscription_update_when_past_due", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionNotAutomaticCollection represents a `subscription_not_automatic_collection` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_not_automatic_collection for more information. +var ErrSubscriptionNotAutomaticCollection = &paddleerr.Error{ + Code: "subscription_not_automatic_collection", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionNotActive represents a `subscription_not_active` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_not_active for more information. +var ErrSubscriptionNotActive = &paddleerr.Error{ + Code: "subscription_not_active", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionNextBilledAtTooSoon represents a `subscription_next_billed_at_too_soon` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_next_billed_at_too_soon for more information. +var ErrSubscriptionNextBilledAtTooSoon = &paddleerr.Error{ + Code: "subscription_next_billed_at_too_soon", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionOutstandingTransaction represents a `subscription_outstanding_transaction` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_outstanding_transaction for more information. +var ErrSubscriptionOutstandingTransaction = &paddleerr.Error{ + Code: "subscription_outstanding_transaction", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionMustBePaused represents a `subscription_must_be_paused` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_must_be_paused for more information. +var ErrSubscriptionMustBePaused = &paddleerr.Error{ + Code: "subscription_must_be_paused", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionAllItemsRemoved represents a `subscription_all_items_removed` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_all_items_removed for more information. +var ErrSubscriptionAllItemsRemoved = &paddleerr.Error{ + Code: "subscription_all_items_removed", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionUpdateErrorWhenPaused represents a `subscription_update_error_when_paused` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_update_error_when_paused for more information. +var ErrSubscriptionUpdateErrorWhenPaused = &paddleerr.Error{ + Code: "subscription_update_error_when_paused", + Type: paddleerr.ErrorTypeAPIError, +} + +// ErrSubscriptionItemsUpdateMissingProrationBillingMode represents a `subscription_items_update_missing_proration_billing_mode` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_items_update_missing_proration_billing_mode for more information. +var ErrSubscriptionItemsUpdateMissingProrationBillingMode = &paddleerr.Error{ + Code: "subscription_items_update_missing_proration_billing_mode", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionDiscountNotValidForItems represents a `subscription_discount_not_valid_for_items` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_discount_not_valid_for_items for more information. +var ErrSubscriptionDiscountNotValidForItems = &paddleerr.Error{ + Code: "subscription_discount_not_valid_for_items", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionOneOffDiscountNotValid represents a `subscription_one_off_discount_not_valid` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_one_off_discount_not_valid for more information. +var ErrSubscriptionOneOffDiscountNotValid = &paddleerr.Error{ + Code: "subscription_one_off_discount_not_valid", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionDuplicatePriceIDs represents a `subscription_duplicate_price_ids` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_duplicate_price_ids for more information. +var ErrSubscriptionDuplicatePriceIDs = &paddleerr.Error{ + Code: "subscription_duplicate_price_ids", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionScheduledChangeInvalidUpdate represents a `subscription_scheduled_change_invalid_update` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_scheduled_change_invalid_update for more information. +var ErrSubscriptionScheduledChangeInvalidUpdate = &paddleerr.Error{ + Code: "subscription_scheduled_change_invalid_update", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionOnlyUpdateItemsOnPausedSubscription represents a `subscription_only_update_items_on_paused_subscription` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_only_update_items_on_paused_subscription for more information. +var ErrSubscriptionOnlyUpdateItemsOnPausedSubscription = &paddleerr.Error{ + Code: "subscription_only_update_items_on_paused_subscription", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionIncorrectProrationOnPausedSubscription represents a `subscription_incorrect_proration_on_paused_subscription` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_incorrect_proration_on_paused_subscription for more information. +var ErrSubscriptionIncorrectProrationOnPausedSubscription = &paddleerr.Error{ + Code: "subscription_incorrect_proration_on_paused_subscription", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionPaymentDeclined represents a `subscription_payment_declined` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_payment_declined for more information. +var ErrSubscriptionPaymentDeclined = &paddleerr.Error{ + Code: "subscription_payment_declined", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionBillingDetailsRequired represents a `subscription_billing_details_required` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_billing_details_required for more information. +var ErrSubscriptionBillingDetailsRequired = &paddleerr.Error{ + Code: "subscription_billing_details_required", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionNewItemsNotValid represents a `subscription_new_items_not_valid` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_new_items_not_valid for more information. +var ErrSubscriptionNewItemsNotValid = &paddleerr.Error{ + Code: "subscription_new_items_not_valid", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionQuantityMissingForNewItems represents a `subscription_quantity_missing_for_new_items` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_quantity_missing_for_new_items for more information. +var ErrSubscriptionQuantityMissingForNewItems = &paddleerr.Error{ + Code: "subscription_quantity_missing_for_new_items", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionChargeDuplicatePriceIDs represents a `subscription_charge_duplicate_price_ids` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_charge_duplicate_price_ids for more information. +var ErrSubscriptionChargeDuplicatePriceIDs = &paddleerr.Error{ + Code: "subscription_charge_duplicate_price_ids", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionNoRecurringItemsRemain represents a `subscription_no_recurring_items_remain` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_no_recurring_items_remain for more information. +var ErrSubscriptionNoRecurringItemsRemain = &paddleerr.Error{ + Code: "subscription_no_recurring_items_remain", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionQuantityNotValid represents a `subscription_quantity_not_valid` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_quantity_not_valid for more information. +var ErrSubscriptionQuantityNotValid = &paddleerr.Error{ + Code: "subscription_quantity_not_valid", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionCurrencyCodeNotValidForManual represents a `subscription_currency_code_not_valid_for_manual` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_currency_code_not_valid_for_manual for more information. +var ErrSubscriptionCurrencyCodeNotValidForManual = &paddleerr.Error{ + Code: "subscription_currency_code_not_valid_for_manual", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionCurrencyCodeIncompatibleWithPaymentMethodProvider represents a `subscription_currency_code_incompatible_with_payment_method_provider` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_currency_code_incompatible_with_payment_method_provider for more information. +var ErrSubscriptionCurrencyCodeIncompatibleWithPaymentMethodProvider = &paddleerr.Error{ + Code: "subscription_currency_code_incompatible_with_payment_method_provider", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionCustomerNotSuitableForCollectionMode represents a `subscription_customer_not_suitable_for_collection_mode` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_customer_not_suitable_for_collection_mode for more information. +var ErrSubscriptionCustomerNotSuitableForCollectionMode = &paddleerr.Error{ + Code: "subscription_customer_not_suitable_for_collection_mode", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionAddressNotSuitableForCollectionMode represents a `subscription_address_not_suitable_for_collection_mode` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_address_not_suitable_for_collection_mode for more information. +var ErrSubscriptionAddressNotSuitableForCollectionMode = &paddleerr.Error{ + Code: "subscription_address_not_suitable_for_collection_mode", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionInvalidDiscountCurrency represents a `subscription_invalid_discount_currency` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_invalid_discount_currency for more information. +var ErrSubscriptionInvalidDiscountCurrency = &paddleerr.Error{ + Code: "subscription_invalid_discount_currency", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionTrialingItemsUpdateInvalidOptions represents a `subscription_trialing_items_update_invalid_options` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_trialing_items_update_invalid_options for more information. +var ErrSubscriptionTrialingItemsUpdateInvalidOptions = &paddleerr.Error{ + Code: "subscription_trialing_items_update_invalid_options", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionCannotActivate represents a `subscription_cannot_activate` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_cannot_activate for more information. +var ErrSubscriptionCannotActivate = &paddleerr.Error{ + Code: "subscription_cannot_activate", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionOutstandingPendingRefund represents a `subscription_outstanding_pending_refund` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_outstanding_pending_refund for more information. +var ErrSubscriptionOutstandingPendingRefund = &paddleerr.Error{ + Code: "subscription_outstanding_pending_refund", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionTrialingDiscountUpdateInvalidOptions represents a `subscription_trialing_discount_update_invalid_options` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_trialing_discount_update_invalid_options for more information. +var ErrSubscriptionTrialingDiscountUpdateInvalidOptions = &paddleerr.Error{ + Code: "subscription_trialing_discount_update_invalid_options", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionUpdateTransactionBalanceLessThanChargeLimit represents a `subscription_update_transaction_balance_less_than_charge_limit` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_update_transaction_balance_less_than_charge_limit for more information. +var ErrSubscriptionUpdateTransactionBalanceLessThanChargeLimit = &paddleerr.Error{ + Code: "subscription_update_transaction_balance_less_than_charge_limit", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionCustomerEmailDomainNotAllowed represents a `subscription_customer_email_domain_not_allowed` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_customer_email_domain_not_allowed for more information. +var ErrSubscriptionCustomerEmailDomainNotAllowed = &paddleerr.Error{ + Code: "subscription_customer_email_domain_not_allowed", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionCurrencyUpdateNotAllowed represents a `subscription_currency_update_not_allowed` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_currency_update_not_allowed for more information. +var ErrSubscriptionCurrencyUpdateNotAllowed = &paddleerr.Error{ + Code: "subscription_currency_update_not_allowed", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionUpdateDifferentCurrencyCredits represents a `subscription_update_different_currency_credits` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_update_different_currency_credits for more information. +var ErrSubscriptionUpdateDifferentCurrencyCredits = &paddleerr.Error{ + Code: "subscription_update_different_currency_credits", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionCurrencyUpdateAndActionsCreatingCreditsNotAllowed represents a `subscription_currency_update_and_actions_creating_credits_not_allowed` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_currency_update_and_actions_creating_credits_not_allowed for more information. +var ErrSubscriptionCurrencyUpdateAndActionsCreatingCreditsNotAllowed = &paddleerr.Error{ + Code: "subscription_currency_update_and_actions_creating_credits_not_allowed", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionCreditCreationAgainstUncompletedTransactionNotAllowed represents a `subscription_credit_creation_against_uncompleted_transaction_not_allowed` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_credit_creation_against_uncompleted_transaction_not_allowed for more information. +var ErrSubscriptionCreditCreationAgainstUncompletedTransactionNotAllowed = &paddleerr.Error{ + Code: "subscription_credit_creation_against_uncompleted_transaction_not_allowed", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionUpdateCausingCustomerMismatchNotAllowed represents a `subscription_update_causing_customer_mismatch_not_allowed` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_update_causing_customer_mismatch_not_allowed for more information. +var ErrSubscriptionUpdateCausingCustomerMismatchNotAllowed = &paddleerr.Error{ + Code: "subscription_update_causing_customer_mismatch_not_allowed", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrSubscriptionCreditCreationAgainstProcessingTransaction represents a `subscription_credit_creation_against_processing_transaction` error. +// See https://developer.paddle.com/errors/subscriptions/subscription_credit_creation_against_processing_transaction for more information. +var ErrSubscriptionCreditCreationAgainstProcessingTransaction = &paddleerr.Error{ + Code: "subscription_credit_creation_against_processing_transaction", + Type: paddleerr.ErrorTypeRequestError, +} + +// SubscriptionManagementUrLs: Public URLs that customers can use to make changes to this subscription. For security, the `token` appended to each link is temporary. You shouldn't store these links. +type SubscriptionManagementUrLs struct { + // UpdatePaymentMethod: Public URL that lets customers update the payment method for this subscription. Creates or gets a transaction that can be used to update a payment method, then passes it to your default checkout page. + UpdatePaymentMethod *string `json:"update_payment_method,omitempty"` + // Cancel: Public URL that lets customers cancel this subscription. Takes customers to a Paddle page that lets them cancel their subscription. + Cancel string `json:"cancel,omitempty"` +} + +// SubscriptionsTaxRatesUsed: List of tax rates applied to this transaction preview. +type SubscriptionsTaxRatesUsed struct { + // TaxRate: Rate used to calculate tax for this transaction preview. + TaxRate string `json:"tax_rate,omitempty"` + // Totals: Calculated totals for the tax applied to this transaction preview. + Totals Totals `json:"totals,omitempty"` +} + +// SubscriptionsTransactionDetailsPreview: Calculated totals for a transaction preview, including discounts, tax, and currency conversion. Considered the source of truth for totals on a transaction preview. +type SubscriptionsTransactionDetailsPreview struct { + // TaxRatesUsed: List of tax rates applied to this transaction preview. + TaxRatesUsed []SubscriptionsTaxRatesUsed `json:"tax_rates_used,omitempty"` + // Totals: Breakdown of the total for a transaction preview. `fee` and `earnings` always return `null` for transaction previews. + Totals TransactionTotals `json:"totals,omitempty"` + // LineItems: Information about line items for this transaction preview. Different from transaction preview `items` as they include totals calculated by Paddle. Considered the source of truth for line item totals. + LineItems []TransactionLineItemPreview `json:"line_items,omitempty"` +} + +// SubscriptionsAdjustmentItem: List of transaction items that this adjustment is for. +type SubscriptionsAdjustmentItem struct { + // ItemID: Paddle ID for the transaction item that this adjustment item relates to, prefixed with `txnitm_`. + ItemID string `json:"item_id,omitempty"` + /* + Type: Type of adjustment for this transaction item. `tax` and `proration` are automatically created by Paddle. + Include `amount` when creating a `partial` adjustment. + */ + Type string `json:"type,omitempty"` + // Amount: Amount adjusted for this transaction item. Required when adjustment type is `partial`. + Amount *string `json:"amount,omitempty"` + /* + Proration: How proration was calculated for this adjustment item. Populated when an adjustment type is `proration`. + Set automatically by Paddle. + */ + Proration *Proration `json:"proration,omitempty"` + // Totals: Breakdown of the total for an adjustment item. + Totals AdjustmentItemTotals `json:"totals,omitempty"` +} + +// AdjustmentPreview: Represents an adjustment entity when previewing adjustments. +type AdjustmentPreview struct { + // TransactionID: Paddle ID for this transaction entity that this adjustment relates to, prefixed with `txn_`. + TransactionID string `json:"transaction_id,omitempty"` + // Items: List of transaction items that this adjustment is for. + Items []SubscriptionsAdjustmentItem `json:"items,omitempty"` + // Totals: Calculated totals for this adjustment. + Totals AdjustmentTotals `json:"totals,omitempty"` +} + +// NextTransaction: Preview of the next transaction for this subscription. May include prorated charges that are not yet billed and one-time charges. Returned when the `include` parameter is used with the `next_transaction` value. `null` if the subscription is scheduled to cancel or pause. +type NextTransaction struct { + // BillingPeriod: Billing period for the next transaction. + BillingPeriod TimePeriod `json:"billing_period,omitempty"` + // Details: Calculated totals for a transaction preview, including discounts, tax, and currency conversion. Considered the source of truth for totals on a transaction preview. + Details SubscriptionsTransactionDetailsPreview `json:"details,omitempty"` + // Adjustments: Represents an adjustment entity when previewing adjustments. + Adjustments []AdjustmentPreview `json:"adjustments,omitempty"` +} + +// SubscriptionIncludes: Represents a subscription entity with included entities. +type SubscriptionIncludes struct { + // ID: Unique Paddle ID for this subscription entity, prefixed with `sub_`. + ID string `json:"id,omitempty"` + // Status: Status of this subscription. Set automatically by Paddle. Use the pause subscription or cancel subscription operations to change. + Status string `json:"status,omitempty"` + // CustomerID: Paddle ID of the customer that this subscription is for, prefixed with `ctm_`. + CustomerID string `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this subscription is for, prefixed with `add_`. + AddressID string `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this subscription is for, prefixed with `biz_`. + BusinessID *string `json:"business_id,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Transactions for this subscription are created in this currency. Must be `USD`, `EUR`, or `GBP` if `collection_mode` is `manual`. + CurrencyCode string `json:"currency_code,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // StartedAt: RFC 3339 datetime string of when this subscription started. This may be different from `first_billed_at` if the subscription started in trial. + StartedAt *string `json:"started_at,omitempty"` + // FirstBilledAt: RFC 3339 datetime string of when this subscription was first billed. This may be different from `started_at` if the subscription started in trial. + FirstBilledAt *string `json:"first_billed_at,omitempty"` + // NextBilledAt: RFC 3339 datetime string of when this subscription is next scheduled to be billed. + NextBilledAt *string `json:"next_billed_at,omitempty"` + // PausedAt: RFC 3339 datetime string of when this subscription was paused. Set automatically by Paddle when the pause subscription operation is used. `null` if not paused. + PausedAt *string `json:"paused_at,omitempty"` + // CanceledAt: RFC 3339 datetime string of when this subscription was canceled. Set automatically by Paddle when the cancel subscription operation is used. `null` if not canceled. + CanceledAt *string `json:"canceled_at,omitempty"` + // Discount: Details of the discount applied to this subscription. + Discount *SubscriptionDiscount `json:"discount,omitempty"` + // CollectionMode: How payment is collected for transactions created for this subscription. `automatic` for checkout, `manual` for invoices. + CollectionMode string `json:"collection_mode,omitempty"` + // BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. + BillingDetails *BillingDetails `json:"billing_details,omitempty"` + // CurrentBillingPeriod: Current billing period for this subscription. Set automatically by Paddle based on the billing cycle. + CurrentBillingPeriod *TimePeriod `json:"current_billing_period,omitempty"` + // BillingCycle: How often this subscription renews. Set automatically by Paddle based on the prices on this subscription. + BillingCycle Duration `json:"billing_cycle,omitempty"` + // ScheduledChange: Change that's scheduled to be applied to a subscription. Use the pause subscription, cancel subscription, and resume subscription operations to create scheduled changes. `null` if no scheduled changes. + ScheduledChange *SubscriptionScheduledChange `json:"scheduled_change,omitempty"` + // ManagementURLs: Public URLs that customers can use to make changes to this subscription. For security, the `token` appended to each link is temporary. You shouldn't store these links. + ManagementURLs SubscriptionManagementUrLs `json:"management_urls,omitempty"` + // Items: Represents a subscription item. + Items []SubscriptionItem `json:"items,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` + // NextTransaction: Preview of the next transaction for this subscription. May include prorated charges that are not yet billed and one-time charges. Returned when the `include` parameter is used with the `next_transaction` value. `null` if the subscription is scheduled to cancel or pause. + NextTransaction *NextTransaction `json:"next_transaction,omitempty"` + // RecurringTransactionDetails: Preview of the recurring transaction for this subscription. This is what the customer can expect to be billed when there are no prorated or one-time charges. Returned when the `include` parameter is used with the `recurring_transaction_details` value. + RecurringTransactionDetails SubscriptionsTransactionDetailsPreview `json:"recurring_transaction_details,omitempty"` +} + +// EffectiveFrom: When this discount should take effect from.. +type EffectiveFrom string + +const ( + EffectiveFromNextBillingPeriod = "next_billing_period" + EffectiveFromImmediately = "immediately" +) + +// SubscriptionsDiscount: Details of the discount applied to this subscription. Include to add a discount to a subscription. `null` to remove a discount. +type SubscriptionsDiscount struct { + // ID: Unique Paddle ID for this discount, prefixed with `dsc_`. + ID string `json:"id,omitempty"` + // EffectiveFrom: When this discount should take effect from. + EffectiveFrom string `json:"effective_from,omitempty"` +} + +// SubscriptionsCatalogItem: Add or update a catalog item to a subscription. In this case, the product and price that you're billing for exist in your product catalog in Paddle. +type SubscriptionsCatalogItem struct { + // PriceID: Paddle ID for the price to add to this subscription, prefixed with `pri_`. + PriceID string `json:"price_id,omitempty"` + // Quantity: Quantity of this item to add to the subscription. If updating an existing item and not changing the quantity, you may omit `quantity`. + Quantity int `json:"quantity,omitempty"` +} + +/* +ProrationBillingMode: How Paddle should handle proration calculation for changes made to a subscription or its items. Required when making +changes that impact billing. + +For automatically-collected subscriptions, responses may take longer than usual if a proration billing mode that +collects for payment immediately is used.. +*/ +type ProrationBillingMode string + +const ( + ProrationBillingModeProratedImmediately = "prorated_immediately" + ProrationBillingModeProratedNextBillingPeriod = "prorated_next_billing_period" + ProrationBillingModeFullImmediately = "full_immediately" + ProrationBillingModeFullNextBillingPeriod = "full_next_billing_period" + ProrationBillingModeDoNotBill = "do_not_bill" +) + +// SubscriptionOnPaymentFailure: How Paddle should handle changes made to a subscription or its items if the payment fails during update. If omitted, defaults to `prevent_change`.. +type SubscriptionOnPaymentFailure string + +const ( + SubscriptionOnPaymentFailurePreventChange = "prevent_change" + SubscriptionOnPaymentFailureApplyChange = "apply_change" +) + +// Subscription: Represents a subscription entity. +type Subscription struct { + // ID: Unique Paddle ID for this subscription entity, prefixed with `sub_`. + ID string `json:"id,omitempty"` + // Status: Status of this subscription. Set automatically by Paddle. Use the pause subscription or cancel subscription operations to change. + Status string `json:"status,omitempty"` + // CustomerID: Paddle ID of the customer that this subscription is for, prefixed with `ctm_`. + CustomerID string `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this subscription is for, prefixed with `add_`. + AddressID string `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this subscription is for, prefixed with `biz_`. + BusinessID *string `json:"business_id,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Transactions for this subscription are created in this currency. Must be `USD`, `EUR`, or `GBP` if `collection_mode` is `manual`. + CurrencyCode string `json:"currency_code,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // StartedAt: RFC 3339 datetime string of when this subscription started. This may be different from `first_billed_at` if the subscription started in trial. + StartedAt *string `json:"started_at,omitempty"` + // FirstBilledAt: RFC 3339 datetime string of when this subscription was first billed. This may be different from `started_at` if the subscription started in trial. + FirstBilledAt *string `json:"first_billed_at,omitempty"` + // NextBilledAt: RFC 3339 datetime string of when this subscription is next scheduled to be billed. + NextBilledAt *string `json:"next_billed_at,omitempty"` + // PausedAt: RFC 3339 datetime string of when this subscription was paused. Set automatically by Paddle when the pause subscription operation is used. `null` if not paused. + PausedAt *string `json:"paused_at,omitempty"` + // CanceledAt: RFC 3339 datetime string of when this subscription was canceled. Set automatically by Paddle when the cancel subscription operation is used. `null` if not canceled. + CanceledAt *string `json:"canceled_at,omitempty"` + // Discount: Details of the discount applied to this subscription. + Discount *SubscriptionDiscount `json:"discount,omitempty"` + // CollectionMode: How payment is collected for transactions created for this subscription. `automatic` for checkout, `manual` for invoices. + CollectionMode string `json:"collection_mode,omitempty"` + // BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. + BillingDetails *BillingDetails `json:"billing_details,omitempty"` + // CurrentBillingPeriod: Current billing period for this subscription. Set automatically by Paddle based on the billing cycle. + CurrentBillingPeriod *TimePeriod `json:"current_billing_period,omitempty"` + // BillingCycle: How often this subscription renews. Set automatically by Paddle based on the prices on this subscription. + BillingCycle Duration `json:"billing_cycle,omitempty"` + // ScheduledChange: Change that's scheduled to be applied to a subscription. Use the pause subscription, cancel subscription, and resume subscription operations to create scheduled changes. `null` if no scheduled changes. + ScheduledChange *SubscriptionScheduledChange `json:"scheduled_change,omitempty"` + // ManagementURLs: Public URLs that customers can use to make changes to this subscription. For security, the `token` appended to each link is temporary. You shouldn't store these links. + ManagementURLs SubscriptionManagementUrLs `json:"management_urls,omitempty"` + // Items: Represents a subscription item. + Items []SubscriptionItem `json:"items,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` +} + +type ResumeOnASpecificDate struct { + /* + EffectiveFrom: When this scheduled change should take effect from. RFC 3339 datetime string of when the subscription should resume. + + Valid where subscriptions are `active` with a scheduled change to pause, or where they have the status of `paused`. + */ + EffectiveFrom string `json:"effective_from,omitempty"` +} + +type ResumeImmediately struct { + /* + EffectiveFrom: When this subscription change should take effect from. You can pass `immediately` to resume immediately. + + Valid where subscriptions have the status of `paused`. + + Defaults to `immediately` if omitted. + */ + EffectiveFrom *string `json:"effective_from,omitempty"` +} + +// Transaction: Represents a transaction entity. +type Transaction struct { + // ID: Unique Paddle ID for this transaction entity, prefixed with `txn_`. + ID string `json:"id,omitempty"` + // Status: Status of this transaction. You may set a transaction to `billed` or `canceled`, other statuses are set automatically by Paddle. Automatically-collected transactions may return `completed` if payment is captured successfully, or `past_due` if payment failed. + Status string `json:"status,omitempty"` + // CustomerID: Paddle ID of the customer that this transaction is for, prefixed with `ctm_`. + CustomerID *string `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this transaction is for, prefixed with `add_`. + AddressID *string `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this transaction is for, prefixed with `biz_`. + BusinessID *string `json:"business_id,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Must be `USD`, `EUR`, or `GBP` if `collection_mode` is `manual`. + CurrencyCode string `json:"currency_code,omitempty"` + // Origin: Describes how this transaction was created. + Origin string `json:"origin,omitempty"` + // SubscriptionID: Paddle ID of the subscription that this transaction is for, prefixed with `sub_`. + SubscriptionID *string `json:"subscription_id,omitempty"` + // InvoiceID: Paddle ID of the invoice that this transaction is related to, prefixed with `inv_`. Used for compatibility with the Paddle Invoice API, which is now deprecated. This field is scheduled to be removed in the next version of the Paddle API. + InvoiceID *string `json:"invoice_id,omitempty"` + // InvoiceNumber: Invoice number for this transaction. Automatically generated by Paddle when you mark a transaction as `billed` where `collection_mode` is `manual`. + InvoiceNumber *string `json:"invoice_number,omitempty"` + // CollectionMode: How payment is collected for this transaction. `automatic` for checkout, `manual` for invoices. + CollectionMode string `json:"collection_mode,omitempty"` + // DiscountID: Paddle ID of the discount applied to this transaction, prefixed with `dsc_`. + DiscountID *string `json:"discount_id,omitempty"` + // BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. + BillingDetails *BillingDetails `json:"billing_details,omitempty"` + // BillingPeriod: Time period that this transaction is for. Set automatically by Paddle for subscription renewals to describe the period that charges are for. + BillingPeriod *TimePeriod `json:"billing_period,omitempty"` + // Items: List of items on this transaction. For calculated totals, use `details.line_items`. + Items []TransactionItem `json:"items,omitempty"` + // Details: Calculated totals for a transaction, including proration, discounts, tax, and currency conversion. Considered the source of truth for totals on a transaction. + Details TransactionDetails `json:"details,omitempty"` + // Payments: List of payment attempts for this transaction, including successful payments. Sorted by `created_at` in descending order, so most recent attempts are returned first. + Payments []TransactionPaymentAttempt `json:"payments,omitempty"` + // Checkout: Paddle Checkout details for this transaction. Returned for automatically-collected transactions and where `billing_details.enable_checkout` is `true` for manually-collected transactions; `null` otherwise. + Checkout *Checkout `json:"checkout,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // BilledAt: RFC 3339 datetime string of when this transaction was marked as `billed`. `null` for transactions that are not `billed` or `completed`. Set automatically by Paddle. + BilledAt *string `json:"billed_at,omitempty"` + // Customer: Related customer for this transaction. + Customer Customer `json:"customer,omitempty"` + // Address: Related address for this transaction. + Address Address `json:"address,omitempty"` + // Business: Only returned if a business exists for this transaction. + Business Business `json:"business,omitempty"` + // Discount: Only returned if a discount exists for this transaction. + Discount Discount `json:"discount,omitempty"` + // Adjustments: Represents an adjustment entity. + Adjustments []Adjustment `json:"adjustments,omitempty"` +} + +// Credit: Details of any credit adjustments. Paddle creates adjustments against existing transactions when prorating. +type Credit struct { + // Amount: Total of any credit adjustments created for this update. + Amount string `json:"amount,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code for adjustments. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// Charge: Details of the transaction to be created for this update. Paddle creates a transaction to bill for new charges. +type Charge struct { + // Amount: Total of the transaction to be created for this update. + Amount string `json:"amount,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code for the transaction to be created. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// ResultAction: Whether the subscription change results in a prorated credit or a charge.. +type ResultAction string + +const ( + ResultActionCredit = "credit" + ResultActionCharge = "charge" +) + +// Result: Details of the result of credits and charges. Where the total of any credit adjustments is greater than the total charge, the result is a prorated credit; otherwise, the result is a prorated charge. +type Result struct { + // Action: Whether the subscription change results in a prorated credit or a charge. + Action string `json:"action,omitempty"` + // Amount: Amount representing the result of this update, either a charge or a credit. + Amount string `json:"amount,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code for the transaction or adjustment. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// PreviewSubscriptionUpdateSummary: Impact of this subscription change. Includes whether the change results in a charge or credit, and totals for prorated amounts. +type PreviewSubscriptionUpdateSummary struct { + // Credit: Details of any credit adjustments. Paddle creates adjustments against existing transactions when prorating. + Credit Credit `json:"credit,omitempty"` + // Charge: Details of the transaction to be created for this update. Paddle creates a transaction to bill for new charges. + Charge Charge `json:"charge,omitempty"` + // Result: Details of the result of credits and charges. Where the total of any credit adjustments is greater than the total charge, the result is a prorated credit; otherwise, the result is a prorated charge. + Result Result `json:"result,omitempty"` +} + +// SubscriptionPreview: Represents a subscription preview when previewing a subscription. +type SubscriptionPreview struct { + // Status: Status of this subscription. Set automatically by Paddle. Use the pause subscription or cancel subscription operations to change. + Status string `json:"status,omitempty"` + // CustomerID: Paddle ID of the customer that this subscription is for, prefixed with `ctm_`. + CustomerID string `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this subscription is for, prefixed with `add_`. + AddressID string `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this subscription is for, prefixed with `biz_`. + BusinessID *string `json:"business_id,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Transactions for this subscription are created in this currency. Must be `USD`, `EUR`, or `GBP` if `collection_mode` is `manual`. + CurrencyCode string `json:"currency_code,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // StartedAt: RFC 3339 datetime string of when this subscription started. This may be different from `first_billed_at` if the subscription started in trial. + StartedAt *string `json:"started_at,omitempty"` + // FirstBilledAt: RFC 3339 datetime string of when this subscription was first billed. This may be different from `started_at` if the subscription started in trial. + FirstBilledAt *string `json:"first_billed_at,omitempty"` + // NextBilledAt: RFC 3339 datetime string of when this subscription is next scheduled to be billed. + NextBilledAt *string `json:"next_billed_at,omitempty"` + // PausedAt: RFC 3339 datetime string of when this subscription was paused. Set automatically by Paddle when the pause subscription operation is used. `null` if not paused. + PausedAt *string `json:"paused_at,omitempty"` + // CanceledAt: RFC 3339 datetime string of when this subscription was canceled. Set automatically by Paddle when the cancel subscription operation is used. `null` if not canceled. + CanceledAt *string `json:"canceled_at,omitempty"` + // Discount: Details of the discount applied to this subscription. + Discount *SubscriptionDiscount `json:"discount,omitempty"` + // CollectionMode: How payment is collected for transactions created for this subscription. `automatic` for checkout, `manual` for invoices. + CollectionMode string `json:"collection_mode,omitempty"` + // BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. + BillingDetails *BillingDetails `json:"billing_details,omitempty"` + // CurrentBillingPeriod: Current billing period for this subscription. Set automatically by Paddle based on the billing cycle. + CurrentBillingPeriod *TimePeriod `json:"current_billing_period,omitempty"` + // BillingCycle: How often this subscription renews. Set automatically by Paddle based on the prices on this subscription. + BillingCycle Duration `json:"billing_cycle,omitempty"` + // ScheduledChange: Change that's scheduled to be applied to a subscription. Use the pause subscription, cancel subscription, and resume subscription operations to create scheduled changes. `null` if no scheduled changes. + ScheduledChange *SubscriptionScheduledChange `json:"scheduled_change,omitempty"` + // ManagementURLs: Public URLs that customers can use to make changes to this subscription. For security, the `token` appended to each link is temporary. You shouldn't store these links. + ManagementURLs SubscriptionManagementUrLs `json:"management_urls,omitempty"` + // Items: Represents a subscription item. + Items []SubscriptionItem `json:"items,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // ImmediateTransaction: Preview of the immediate transaction created as a result of changes to the subscription. Returns a complete object where `proration_billing_mode` is `prorated_immediately` or `full_immediately`; `null` otherwise. + ImmediateTransaction *NextTransaction `json:"immediate_transaction,omitempty"` + // NextTransaction: Preview of the next transaction for this subscription. Includes charges created where `proration_billing_mode` is `prorated_next_billing_period` or `full_next_billing_period`, as well as one-time charges. `null` if the subscription is scheduled to cancel or pause. + NextTransaction *NextTransaction `json:"next_transaction,omitempty"` + // RecurringTransactionDetails: Preview of the recurring transaction for this subscription. This is what the customer can expect to be billed when there are no prorated or one-time charges. + RecurringTransactionDetails SubscriptionsTransactionDetailsPreview `json:"recurring_transaction_details,omitempty"` + // UpdateSummary: Impact of this subscription change. Includes whether the change results in a charge or credit, and totals for prorated amounts. + UpdateSummary *PreviewSubscriptionUpdateSummary `json:"update_summary,omitempty"` + // ImportMeta: Import information for this entity. `null` if this entity is not imported. + ImportMeta *ImportMeta `json:"import_meta,omitempty"` +} + +// SubscriptionsSubscriptionsCatalogItem: Add a catalog item to a subscription. In this case, the product and price that you're billing for exist in your product catalog in Paddle. +type SubscriptionsSubscriptionsCatalogItem struct { + // Quantity: Quantity to bill for. + Quantity int `json:"quantity,omitempty"` + // PriceID: Paddle ID of an an existing catalog price to bill for. + PriceID string `json:"price_id,omitempty"` +} + +// SubscriptionsNonCatalogPriceForAnExistingProduct: Add a non-catalog price for an existing product in your catalog to a transaction. In this case, the product you're billing for is a catalog product, but you charge a specific price for it. +type SubscriptionsNonCatalogPriceForAnExistingProduct struct { + // Quantity: Quantity to bill for. + Quantity int `json:"quantity,omitempty"` + // Price: Price object for a non-catalog item to bill for. Include a `product_id` to relate this non-catalog price to an existing catalog price. + Price TransactionSubscriptionPriceCreateWithProductID `json:"price,omitempty"` +} + +// SubscriptionsNonCatalogPriceAndProduct: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. +type SubscriptionsNonCatalogPriceAndProduct struct { + // Quantity: Quantity to bill for. + Quantity int `json:"quantity,omitempty"` + // Price: Price object for a non-catalog item to charge for. Include a `product` object to create a non-catalog product for this non-catalog price. + Price TransactionSubscriptionPriceCreateWithProduct `json:"price,omitempty"` +} + +// SubscriptionsClient is a client for the Subscriptions resource. +type SubscriptionsClient struct { + doer Doer +} + +// GetSubscriptionRequest is given as an input to GetSubscription. +type GetSubscriptionRequest struct { + // URL path parameters. + SubscriptionID string `in:"path=subscription_id" json:"-"` + + // IncludeNextTransaction allows requesting the next_transaction sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeNextTransaction bool `in:"paddle_include=next_transaction" json:"-"` + + // IncludeRecurringTransactionDetails allows requesting the recurring_transaction_details sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeRecurringTransactionDetails bool `in:"paddle_include=recurring_transaction_details" json:"-"` +} + +// GetSubscription performs the GET operation on a Subscriptions resource. +func (c *SubscriptionsClient) GetSubscription(ctx context.Context, req *GetSubscriptionRequest) (res *SubscriptionIncludes, err error) { + if err := c.doer.Do(ctx, "GET", "/subscriptions/{subscription_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// UpdateSubscriptionRequest is given as an input to UpdateSubscription. +type UpdateSubscriptionRequest struct { + // URL path parameters. + SubscriptionID string `in:"path=subscription_id" json:"-"` + + // CustomerID: Paddle ID of the customer that this subscription is for, prefixed with `ctm_`. Include to change the customer for a subscription. + CustomerID *PatchField[string] `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this subscription is for, prefixed with `add_`. Include to change the address for a subscription. + AddressID *PatchField[string] `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this subscription is for, prefixed with `biz_`. Include to change the business for a subscription. + BusinessID *PatchField[*string] `json:"business_id,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Include to change the currency that a subscription bills in. When changing `collection_mode` to `manual`, you may need to change currency code to `USD`, `EUR`, or `GBP`. + CurrencyCode *PatchField[string] `json:"currency_code,omitempty"` + // NextBilledAt: RFC 3339 datetime string of when this subscription is next scheduled to be billed. Include to change the next billing date. + NextBilledAt *PatchField[string] `json:"next_billed_at,omitempty"` + // Discount: Details of the discount applied to this subscription. Include to add a discount to a subscription. `null` to remove a discount. + Discount *PatchField[*SubscriptionsDiscount] `json:"discount,omitempty"` + // CollectionMode: How payment is collected for transactions created for this subscription. `automatic` for checkout, `manual` for invoices. + CollectionMode *PatchField[string] `json:"collection_mode,omitempty"` + // BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. `null` if changing `collection_mode` to `automatic`. + BillingDetails *PatchField[*BillingDetailsUpdate] `json:"billing_details,omitempty"` + // ScheduledChange: Change that's scheduled to be applied to a subscription. When updating, you may only set to `null` to remove a scheduled change. Use the pause subscription, cancel subscription, and resume subscription operations to create scheduled changes. + ScheduledChange *PatchField[*SubscriptionScheduledChange] `json:"scheduled_change,omitempty"` + // Items: Add or update a catalog item to a subscription. In this case, the product and price that you're billing for exist in your product catalog in Paddle. + Items *PatchField[[]SubscriptionsCatalogItem] `json:"items,omitempty"` + // CustomData: Your own structured key-value data. + CustomData *PatchField[CustomData] `json:"custom_data,omitempty"` + /* + ProrationBillingMode: How Paddle should handle proration calculation for changes made to a subscription or its items. Required when making + changes that impact billing. + + For automatically-collected subscriptions, responses may take longer than usual if a proration billing mode that + collects for payment immediately is used. + */ + ProrationBillingMode *PatchField[string] `json:"proration_billing_mode,omitempty"` + // OnPaymentFailure: How Paddle should handle changes made to a subscription or its items if the payment fails during update. If omitted, defaults to `prevent_change`. + OnPaymentFailure *PatchField[string] `json:"on_payment_failure,omitempty"` +} + +// UpdateSubscription performs the PATCH operation on a Subscriptions resource. +func (c *SubscriptionsClient) UpdateSubscription(ctx context.Context, req *UpdateSubscriptionRequest) (res *Subscription, err error) { + if err := c.doer.Do(ctx, "PATCH", "/subscriptions/{subscription_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// ListSubscriptionsRequest is given as an input to ListSubscriptions. +type ListSubscriptionsRequest struct { + // AddressID is a query parameter. + // Return entities related to the specified address. Use a comma-separated list to specify multiple address IDs. + AddressID []string `in:"query=address_id,omitempty" json:"-"` + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // CollectionMode is a query parameter. + // Return entities that match the specified collection mode. + CollectionMode *string `in:"query=collection_mode,omitempty" json:"-"` + // CustomerID is a query parameter. + // Return entities related to the specified customer. Use a comma-separated list to specify multiple customer IDs. + CustomerID []string `in:"query=customer_id,omitempty" json:"-"` + // ID is a query parameter. + // Return only the IDs specified. Use a comma-separated list to get multiple entities. + ID []string `in:"query=id,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `id`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // PerPage is a query parameter. + /* + Set how many entities are returned per page. Paddle returns the maximum number of results if a number greater than the maximum is requested. Check `meta.pagination.per_page` in the response to see how many were returned. + + Default: `50`; Maximum: `200`. + */ + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // PriceID is a query parameter. + // Return entities related to the specified price. Use a comma-separated list to specify multiple price IDs. + PriceID []string `in:"query=price_id,omitempty" json:"-"` + // ScheduledChangeAction is a query parameter. + // Return subscriptions that have a scheduled change. Use a comma-separated list to specify multiple scheduled change actions. + ScheduledChangeAction []string `in:"query=scheduled_change_action,omitempty" json:"-"` + // Status is a query parameter. + // Return entities that match the specified status. Use a comma-separated list to specify multiple status values. + Status []string `in:"query=status,omitempty" json:"-"` +} + +// ListSubscriptions performs the GET operation on a Subscriptions resource. +func (c *SubscriptionsClient) ListSubscriptions(ctx context.Context, req *ListSubscriptionsRequest) (res *Collection[*Subscription], err error) { + if err := c.doer.Do(ctx, "GET", "/subscriptions", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// CancelSubscriptionRequest is given as an input to CancelSubscription. +type CancelSubscriptionRequest struct { + // URL path parameters. + SubscriptionID string `in:"path=subscription_id" json:"-"` + + /* + EffectiveFrom: When this subscription change should take effect from. Defaults to `next_billing_period` for active subscriptions, + which creates a `scheduled_change` to apply the subscription change at the end of the billing period. + */ + EffectiveFrom *string `json:"effective_from,omitempty"` +} + +// CancelSubscription performs the POST operation on a Subscriptions resource. +func (c *SubscriptionsClient) CancelSubscription(ctx context.Context, req *CancelSubscriptionRequest) (res *Subscription, err error) { + if err := c.doer.Do(ctx, "POST", "/subscriptions/{subscription_id}/cancel", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// PauseSubscriptionRequest is given as an input to PauseSubscription. +type PauseSubscriptionRequest struct { + // URL path parameters. + SubscriptionID string `in:"path=subscription_id" json:"-"` + + /* + EffectiveFrom: When this subscription change should take effect from. Defaults to `next_billing_period` for active subscriptions, + which creates a `scheduled_change` to apply the subscription change at the end of the billing period. + */ + EffectiveFrom *string `json:"effective_from,omitempty"` + // ResumeAt: RFC 3339 datetime string of when the paused subscription should resume. Omit to pause indefinitely until resumed. + ResumeAt *string `json:"resume_at,omitempty"` +} + +// PauseSubscription performs the POST operation on a Subscriptions resource. +func (c *SubscriptionsClient) PauseSubscription(ctx context.Context, req *PauseSubscriptionRequest) (res *Subscription, err error) { + if err := c.doer.Do(ctx, "POST", "/subscriptions/{subscription_id}/pause", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// NewResumeSubscriptionRequestResumeOnASpecificDate takes a ResumeOnASpecificDate type +// and creates a ResumeSubscriptionRequest for use in a request. +func NewResumeSubscriptionRequestResumeOnASpecificDate(r *ResumeOnASpecificDate) *ResumeSubscriptionRequest { + return &ResumeSubscriptionRequest{ResumeOnASpecificDate: r} +} + +// NewResumeSubscriptionRequestResumeImmediately takes a ResumeImmediately type +// and creates a ResumeSubscriptionRequest for use in a request. +func NewResumeSubscriptionRequestResumeImmediately(r *ResumeImmediately) *ResumeSubscriptionRequest { + return &ResumeSubscriptionRequest{ResumeImmediately: r} +} + +// ResumeSubscriptionRequest represents a union request type of the following types: +// - `ResumeOnASpecificDate` +// - `ResumeImmediately` +// +// The following constructor functions can be used to create a new instance of this type. +// - `NewResumeSubscriptionRequestResumeOnASpecificDate()` +// - `NewResumeSubscriptionRequestResumeImmediately()` +// +// Only one of the values can be set at a time, the first non-nil value will be used in the request. +type ResumeSubscriptionRequest struct { + *ResumeOnASpecificDate + *ResumeImmediately +} + +// ResumeSubscription performs the POST operation on a Subscriptions resource. +func (c *SubscriptionsClient) ResumeSubscription(ctx context.Context, req *ResumeSubscriptionRequest) (res *Subscription, err error) { + if err := c.doer.Do(ctx, "POST", "/subscriptions/{subscription_id}/resume", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (u ResumeSubscriptionRequest) MarshalJSON() ([]byte, error) { + if u.ResumeOnASpecificDate != nil { + return json.Marshal(u.ResumeOnASpecificDate) + } + + if u.ResumeImmediately != nil { + return json.Marshal(u.ResumeImmediately) + } + + return nil, nil +} + +// ActivateSubscriptionRequest is given as an input to ActivateSubscription. +type ActivateSubscriptionRequest struct { + // URL path parameters. + SubscriptionID string `in:"path=subscription_id" json:"-"` +} + +// ActivateSubscription performs the POST operation on a Subscriptions resource. +func (c *SubscriptionsClient) ActivateSubscription(ctx context.Context, req *ActivateSubscriptionRequest) (res *Subscription, err error) { + if err := c.doer.Do(ctx, "POST", "/subscriptions/{subscription_id}/activate", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// GetSubscriptionUpdatePaymentMethodTransactionRequest is given as an input to GetSubscriptionUpdatePaymentMethodTransaction. +type GetSubscriptionUpdatePaymentMethodTransactionRequest struct { + // URL path parameters. + SubscriptionID string `in:"path=subscription_id" json:"-"` +} + +// GetSubscriptionUpdatePaymentMethodTransaction performs the GET operation on a Subscriptions resource. +func (c *SubscriptionsClient) GetSubscriptionUpdatePaymentMethodTransaction(ctx context.Context, req *GetSubscriptionUpdatePaymentMethodTransactionRequest) (res *Transaction, err error) { + if err := c.doer.Do(ctx, "GET", "/subscriptions/{subscription_id}/update-payment-method-transaction", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// PreviewSubscriptionRequest is given as an input to PreviewSubscription. +type PreviewSubscriptionRequest struct { + // URL path parameters. + SubscriptionID string `in:"path=subscription_id" json:"-"` + + // CustomerID: Paddle ID of the customer that this subscription is for, prefixed with `ctm_`. Include to change the customer for a subscription. + CustomerID *string `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this subscription is for, prefixed with `add_`. Include to change the address for a subscription. + AddressID *string `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this subscription is for, prefixed with `biz_`. Include to change the business for a subscription. + BusinessID *string `json:"business_id,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Include to change the currency that a subscription bills in. When changing `collection_mode` to `manual`, you may need to change currency code to `USD`, `EUR`, or `GBP`. + CurrencyCode *string `json:"currency_code,omitempty"` + // NextBilledAt: RFC 3339 datetime string of when this subscription is next scheduled to be billed. Include to change the next billing date. + NextBilledAt *string `json:"next_billed_at,omitempty"` + // Discount: Details of the discount applied to this subscription. Include to add a discount to a subscription. `null` to remove a discount. + Discount *SubscriptionsDiscount `json:"discount,omitempty"` + // CollectionMode: How payment is collected for transactions created for this subscription. `automatic` for checkout, `manual` for invoices. + CollectionMode *string `json:"collection_mode,omitempty"` + // BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. `null` if changing `collection_mode` to `automatic`. + BillingDetails *BillingDetailsUpdate `json:"billing_details,omitempty"` + // ScheduledChange: Change that's scheduled to be applied to a subscription. When updating, you may only set to `null` to remove a scheduled change. Use the pause subscription, cancel subscription, and resume subscription operations to create scheduled changes. + ScheduledChange *SubscriptionScheduledChange `json:"scheduled_change,omitempty"` + // Items: Add or update a catalog item to a subscription. In this case, the product and price that you're billing for exist in your product catalog in Paddle. + Items []SubscriptionsCatalogItem `json:"items,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + /* + ProrationBillingMode: How Paddle should handle proration calculation for changes made to a subscription or its items. Required when making + changes that impact billing. + + For automatically-collected subscriptions, responses may take longer than usual if a proration billing mode that + collects for payment immediately is used. + */ + ProrationBillingMode *string `json:"proration_billing_mode,omitempty"` + // OnPaymentFailure: How Paddle should handle changes made to a subscription or its items if the payment fails during update. If omitted, defaults to `prevent_change`. + OnPaymentFailure *string `json:"on_payment_failure,omitempty"` +} + +// PreviewSubscription performs the POST operation on a Subscriptions resource. +func (c *SubscriptionsClient) PreviewSubscription(ctx context.Context, req *PreviewSubscriptionRequest) (res *SubscriptionPreview, err error) { + if err := c.doer.Do(ctx, "POST", "/subscriptions/{subscription_id}/preview", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// NewCreateSubscriptionChargeItemsSubscriptionsSubscriptionsCatalogItem takes a SubscriptionsSubscriptionsCatalogItem type +// and creates a CreateSubscriptionChargeItems for use in a request. +func NewCreateSubscriptionChargeItemsSubscriptionsSubscriptionsCatalogItem(r *SubscriptionsSubscriptionsCatalogItem) *CreateSubscriptionChargeItems { + return &CreateSubscriptionChargeItems{SubscriptionsSubscriptionsCatalogItem: r} +} + +// NewCreateSubscriptionChargeItemsSubscriptionsNonCatalogPriceForAnExistingProduct takes a SubscriptionsNonCatalogPriceForAnExistingProduct type +// and creates a CreateSubscriptionChargeItems for use in a request. +func NewCreateSubscriptionChargeItemsSubscriptionsNonCatalogPriceForAnExistingProduct(r *SubscriptionsNonCatalogPriceForAnExistingProduct) *CreateSubscriptionChargeItems { + return &CreateSubscriptionChargeItems{SubscriptionsNonCatalogPriceForAnExistingProduct: r} +} + +// NewCreateSubscriptionChargeItemsSubscriptionsNonCatalogPriceAndProduct takes a SubscriptionsNonCatalogPriceAndProduct type +// and creates a CreateSubscriptionChargeItems for use in a request. +func NewCreateSubscriptionChargeItemsSubscriptionsNonCatalogPriceAndProduct(r *SubscriptionsNonCatalogPriceAndProduct) *CreateSubscriptionChargeItems { + return &CreateSubscriptionChargeItems{SubscriptionsNonCatalogPriceAndProduct: r} +} + +// CreateSubscriptionChargeItems represents a union request type of the following types: +// - `SubscriptionsSubscriptionsCatalogItem` +// - `SubscriptionsNonCatalogPriceForAnExistingProduct` +// - `SubscriptionsNonCatalogPriceAndProduct` +// +// The following constructor functions can be used to create a new instance of this type. +// - `NewCreateSubscriptionChargeItemsSubscriptionsSubscriptionsCatalogItem()` +// - `NewCreateSubscriptionChargeItemsSubscriptionsNonCatalogPriceForAnExistingProduct()` +// - `NewCreateSubscriptionChargeItemsSubscriptionsNonCatalogPriceAndProduct()` +// +// Only one of the values can be set at a time, the first non-nil value will be used in the request. +// Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. +type CreateSubscriptionChargeItems struct { + *SubscriptionsSubscriptionsCatalogItem + *SubscriptionsNonCatalogPriceForAnExistingProduct + *SubscriptionsNonCatalogPriceAndProduct +} + +// MarshalJSON implements the json.Marshaler interface. +func (u CreateSubscriptionChargeItems) MarshalJSON() ([]byte, error) { + if u.SubscriptionsSubscriptionsCatalogItem != nil { + return json.Marshal(u.SubscriptionsSubscriptionsCatalogItem) + } + + if u.SubscriptionsNonCatalogPriceForAnExistingProduct != nil { + return json.Marshal(u.SubscriptionsNonCatalogPriceForAnExistingProduct) + } + + if u.SubscriptionsNonCatalogPriceAndProduct != nil { + return json.Marshal(u.SubscriptionsNonCatalogPriceAndProduct) + } + + return nil, nil +} + +// CreateSubscriptionChargeRequest is given as an input to CreateSubscriptionCharge. +type CreateSubscriptionChargeRequest struct { + // URL path parameters. + SubscriptionID string `in:"path=subscription_id" json:"-"` + + // EffectiveFrom: When one-time charges should be billed. + EffectiveFrom string `json:"effective_from,omitempty"` + // Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. + Items []CreateSubscriptionChargeItems `json:"items,omitempty"` + // OnPaymentFailure: How Paddle should handle changes made to a subscription or its items if the payment fails during update. If omitted, defaults to `prevent_change`. + OnPaymentFailure *string `json:"on_payment_failure,omitempty"` +} + +// CreateSubscriptionCharge performs the POST operation on a Subscriptions resource. +func (c *SubscriptionsClient) CreateSubscriptionCharge(ctx context.Context, req *CreateSubscriptionChargeRequest) (res *Subscription, err error) { + if err := c.doer.Do(ctx, "POST", "/subscriptions/{subscription_id}/charge", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// NewPreviewSubscriptionChargeItemsSubscriptionsSubscriptionsCatalogItem takes a SubscriptionsSubscriptionsCatalogItem type +// and creates a PreviewSubscriptionChargeItems for use in a request. +func NewPreviewSubscriptionChargeItemsSubscriptionsSubscriptionsCatalogItem(r *SubscriptionsSubscriptionsCatalogItem) *PreviewSubscriptionChargeItems { + return &PreviewSubscriptionChargeItems{SubscriptionsSubscriptionsCatalogItem: r} +} + +// NewPreviewSubscriptionChargeItemsSubscriptionsNonCatalogPriceForAnExistingProduct takes a SubscriptionsNonCatalogPriceForAnExistingProduct type +// and creates a PreviewSubscriptionChargeItems for use in a request. +func NewPreviewSubscriptionChargeItemsSubscriptionsNonCatalogPriceForAnExistingProduct(r *SubscriptionsNonCatalogPriceForAnExistingProduct) *PreviewSubscriptionChargeItems { + return &PreviewSubscriptionChargeItems{SubscriptionsNonCatalogPriceForAnExistingProduct: r} +} + +// NewPreviewSubscriptionChargeItemsSubscriptionsNonCatalogPriceAndProduct takes a SubscriptionsNonCatalogPriceAndProduct type +// and creates a PreviewSubscriptionChargeItems for use in a request. +func NewPreviewSubscriptionChargeItemsSubscriptionsNonCatalogPriceAndProduct(r *SubscriptionsNonCatalogPriceAndProduct) *PreviewSubscriptionChargeItems { + return &PreviewSubscriptionChargeItems{SubscriptionsNonCatalogPriceAndProduct: r} +} + +// PreviewSubscriptionChargeItems represents a union request type of the following types: +// - `SubscriptionsSubscriptionsCatalogItem` +// - `SubscriptionsNonCatalogPriceForAnExistingProduct` +// - `SubscriptionsNonCatalogPriceAndProduct` +// +// The following constructor functions can be used to create a new instance of this type. +// - `NewPreviewSubscriptionChargeItemsSubscriptionsSubscriptionsCatalogItem()` +// - `NewPreviewSubscriptionChargeItemsSubscriptionsNonCatalogPriceForAnExistingProduct()` +// - `NewPreviewSubscriptionChargeItemsSubscriptionsNonCatalogPriceAndProduct()` +// +// Only one of the values can be set at a time, the first non-nil value will be used in the request. +// Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. +type PreviewSubscriptionChargeItems struct { + *SubscriptionsSubscriptionsCatalogItem + *SubscriptionsNonCatalogPriceForAnExistingProduct + *SubscriptionsNonCatalogPriceAndProduct +} + +// MarshalJSON implements the json.Marshaler interface. +func (u PreviewSubscriptionChargeItems) MarshalJSON() ([]byte, error) { + if u.SubscriptionsSubscriptionsCatalogItem != nil { + return json.Marshal(u.SubscriptionsSubscriptionsCatalogItem) + } + + if u.SubscriptionsNonCatalogPriceForAnExistingProduct != nil { + return json.Marshal(u.SubscriptionsNonCatalogPriceForAnExistingProduct) + } + + if u.SubscriptionsNonCatalogPriceAndProduct != nil { + return json.Marshal(u.SubscriptionsNonCatalogPriceAndProduct) + } + + return nil, nil +} + +// PreviewSubscriptionChargeRequest is given as an input to PreviewSubscriptionCharge. +type PreviewSubscriptionChargeRequest struct { + // URL path parameters. + SubscriptionID string `in:"path=subscription_id" json:"-"` + + // EffectiveFrom: When one-time charges should be billed. + EffectiveFrom string `json:"effective_from,omitempty"` + // Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. + Items []PreviewSubscriptionChargeItems `json:"items,omitempty"` + // OnPaymentFailure: How Paddle should handle changes made to a subscription or its items if the payment fails during update. If omitted, defaults to `prevent_change`. + OnPaymentFailure *string `json:"on_payment_failure,omitempty"` +} + +// PreviewSubscriptionCharge performs the POST operation on a Subscriptions resource. +func (c *SubscriptionsClient) PreviewSubscriptionCharge(ctx context.Context, req *PreviewSubscriptionChargeRequest) (res *SubscriptionPreview, err error) { + if err := c.doer.Do(ctx, "POST", "/subscriptions/{subscription_id}/charge/preview", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/testdata/price_created.json b/testdata/price_created.json new file mode 100644 index 0000000..dfe1299 --- /dev/null +++ b/testdata/price_created.json @@ -0,0 +1,30 @@ +{ + "data": { + "id": "pri_01hsdn96k2hxjzsq5yerecdj9j", + "name": null, + "status": "active", + "quantity": { + "maximum": 999999, + "minimum": 1 + }, + "tax_mode": "account_setting", + "product_id": "pro_01hsdn8qp7yydry3x1yeg6a9rv", + "unit_price": { + "amount": "1000", + "currency_code": "USD" + }, + "custom_data": null, + "description": "testing", + "import_meta": null, + "trial_period": null, + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "unit_price_overrides": [] + }, + "event_id": "evt_01hsdn97563968dy0szkmgjwh3", + "event_type": "price.created", + "occurred_at": "2024-03-20T10:07:35.590857Z", + "notification_id": "ntf_01hsdn977e920kbgzt6r6c9rqc" +} diff --git a/testdata/transaction.json b/testdata/transaction.json new file mode 100644 index 0000000..da04be7 --- /dev/null +++ b/testdata/transaction.json @@ -0,0 +1,272 @@ +{ + "data": { + "id": "txn_01hv8m0mnx3sj85e7gxc6kga03", + "status": "ready", + "customer_id": "ctm_01hv6y1jedq4p1n0yqn5ba3ky4", + "address_id": "add_01hv8gq3318ktkfengj2r75gfx", + "business_id": null, + "custom_data": null, + "origin": "api", + "collection_mode": "manual", + "subscription_id": null, + "invoice_id": null, + "invoice_number": null, + "billing_details": { + "enable_checkout": false, + "payment_terms": { + "interval": "day", + "frequency": 14 + }, + "purchase_order_number": "PO-123", + "additional_information": null + }, + "billing_period": { + "starts_at": "2024-04-12T00:00:00Z", + "ends_at": "2025-04-11T23:59:00Z" + }, + "currency_code": "USD", + "discount_id": null, + "created_at": "2024-04-12T07:40:38.007040251Z", + "updated_at": "2024-04-12T07:40:38.007040251Z", + "billed_at": null, + "items": [ + { + "price": { + "id": "pri_01gsz91wy9k1yn7kx82aafwvea", + "description": "Annual", + "type": "standard", + "name": "Annual (per seat)", + "product_id": "pro_01gsz4vmqbjk3x4vvtafffd540", + "billing_cycle": { + "interval": "year", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "50000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 100 + }, + "status": "active", + "created_at": "2023-02-23T13:57:54.249913Z", + "updated_at": "2024-04-05T14:32:00.471447Z", + "import_meta": null + }, + "quantity": 20 + }, + { + "price": { + "id": "pri_01gsz96z29d88jrmsf2ztbfgjg", + "description": "Annual (recurring addon)", + "type": "standard", + "name": "Annual (recurring addon)", + "product_id": "pro_01gsz92krfzy3hcx5h5rtgnfwz", + "billing_cycle": { + "interval": "year", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "300000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 1 + }, + "status": "active", + "created_at": "2023-02-23T14:00:40.265185Z", + "updated_at": "2024-03-25T14:31:18.587603Z", + "import_meta": null + }, + "quantity": 1 + }, + { + "price": { + "id": "pri_01gsz98e27ak2tyhexptwc58yk", + "description": "One-time addon", + "type": "standard", + "name": "One-time addon", + "product_id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "billing_cycle": null, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "19900", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 1 + }, + "status": "active", + "created_at": "2023-02-23T14:01:28.391712Z", + "updated_at": "2024-04-09T07:23:10.921392Z", + "import_meta": null + }, + "quantity": 1 + } + ], + "details": { + "tax_rates_used": [ + { + "tax_rate": "0.08875", + "totals": { + "subtotal": "1319900", + "discount": "0", + "tax": "117141", + "total": "1437041" + } + } + ], + "totals": { + "subtotal": "1319900", + "tax": "117141", + "discount": "0", + "total": "1437041", + "grand_total": "1437041", + "fee": null, + "credit": "0", + "credit_to_balance": "0", + "balance": "1437041", + "earnings": null, + "currency_code": "USD" + }, + "adjusted_totals": { + "subtotal": "1319900", + "tax": "117141", + "total": "1437041", + "grand_total": "1437041", + "fee": "0", + "earnings": "0", + "currency_code": "USD" + }, + "payout_totals": null, + "adjusted_payout_totals": null, + "line_items": [ + { + "id": "txnitm_01hv8m0n6xqk32jsq5q6pv7mpk", + "price_id": "pri_01gsz91wy9k1yn7kx82aafwvea", + "quantity": 20, + "totals": { + "subtotal": "1000000", + "tax": "88750", + "discount": "0", + "total": "1088750" + }, + "product": { + "id": "pro_01gsz4vmqbjk3x4vvtafffd540", + "name": "AeroEdit Enterprise", + "description": "The ultimate solution for organizations, featuring all Pro capabilities plus multi-user support, advanced data storage capabilities, plus personalized onboarding, dedicated account management, and the ability to pay via invoice.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/Ws808ziTS76a6YbnMkiK_enterprise.png", + "custom_data": { + "features": { + "aircraft_performance": true, + "compliance_monitoring": true, + "flight_log_management": true, + "payment_by_invoice": true, + "route_planning": true, + "sso": true + }, + "suggested_addons": [], + "upgrade_description": "Ready to reach new heights? Upgrade to enterprise to unlock single sign-on, payment by invoice, and dedicated account management." + }, + "status": "active", + "created_at": "2023-02-23T12:44:34.923Z", + "updated_at": "2024-04-05T15:58:28.309Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "50000", + "tax": "4437", + "discount": "0", + "total": "54437" + } + }, + { + "id": "txnitm_01hv8m0n6yajavvy3q3297hd45", + "price_id": "pri_01gsz96z29d88jrmsf2ztbfgjg", + "quantity": 1, + "totals": { + "subtotal": "300000", + "tax": "26625", + "discount": "0", + "total": "326625" + }, + "product": { + "id": "pro_01gsz92krfzy3hcx5h5rtgnfwz", + "name": "VIP support", + "description": "Get exclusive access to our expert team of product specialists, available to help you make the most of your AeroEdit subscription.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/qgyipKJwRtq98YNboipo_vip-support.png", + "custom_data": null, + "status": "active", + "created_at": "2023-02-23T13:58:17.615Z", + "updated_at": "2024-04-05T15:44:02.893Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "300000", + "tax": "26625", + "discount": "0", + "total": "326625" + } + }, + { + "id": "txnitm_01hv8m0n6yajavvy3q376fjpht", + "price_id": "pri_01gsz98e27ak2tyhexptwc58yk", + "quantity": 1, + "totals": { + "subtotal": "19900", + "tax": "1766", + "discount": "0", + "total": "21666" + }, + "product": { + "id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "name": "Custom domains", + "description": "Make AeroEdit truly your own with custom domains. Custom domains reinforce your brand identity and make it easy for your team to access your account.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/XIG7UXoJQHmlIAiKcnkA_custom-domains.png", + "custom_data": null, + "status": "active", + "created_at": "2023-02-23T14:01:02.441Z", + "updated_at": "2024-04-05T15:43:28.971Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "19900", + "tax": "1766", + "discount": "0", + "total": "21666" + } + } + ] + }, + "payments": [], + "checkout": { + "url": null + } + }, + "meta": { + "request_id": "c7db9b4e-da8c-4b05-871b-f7f47264fd72" + } +} diff --git a/testdata/transactions.json b/testdata/transactions.json new file mode 100644 index 0000000..d2a63d1 --- /dev/null +++ b/testdata/transactions.json @@ -0,0 +1,851 @@ +{ + "data": [ + { + "id": "txn_01hv8xxw3etar07vaxsqbyqasy", + "status": "draft", + "customer_id": null, + "address_id": null, + "business_id": null, + "custom_data": null, + "origin": "web", + "collection_mode": "automatic", + "subscription_id": null, + "invoice_id": null, + "invoice_number": null, + "billing_details": null, + "billing_period": null, + "currency_code": "GBP", + "discount_id": null, + "created_at": "2024-04-12T10:33:52.610609Z", + "updated_at": "2024-04-12T10:33:52.610609Z", + "billed_at": null, + "items": [ + { + "price": { + "id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "description": "Monthly", + "type": "standard", + "name": "Monthly (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "3000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 999 + }, + "status": "active", + "created_at": "2023-02-23T13:55:22.538367Z", + "updated_at": "2024-04-11T13:54:52.254748Z", + "import_meta": null + }, + "quantity": 10 + }, + { + "price": { + "id": "pri_01h1vjfevh5etwq3rb416a23h2", + "description": "Monthly", + "type": "standard", + "name": "Monthly (recurring addon)", + "product_id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "10000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 100 + }, + "status": "active", + "created_at": "2023-06-01T13:31:12.625056Z", + "updated_at": "2024-04-09T07:23:00.907834Z", + "import_meta": null + }, + "quantity": 1 + }, + { + "price": { + "id": "pri_01gsz98e27ak2tyhexptwc58yk", + "description": "One-time addon", + "type": "standard", + "name": "One-time addon", + "product_id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "billing_cycle": null, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "19900", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 1 + }, + "status": "active", + "created_at": "2023-02-23T14:01:28.391712Z", + "updated_at": "2024-04-09T07:23:10.921392Z", + "import_meta": null + }, + "quantity": 1 + } + ], + "details": { + "tax_rates_used": [ + { + "tax_rate": "0.2", + "totals": { + "subtotal": "47924", + "discount": "0", + "tax": "9585", + "total": "57509" + } + } + ], + "totals": { + "subtotal": "47924", + "tax": "9585", + "discount": "0", + "total": "57509", + "grand_total": "57509", + "fee": null, + "credit": "0", + "credit_to_balance": "0", + "balance": "57509", + "earnings": null, + "currency_code": "GBP" + }, + "adjusted_totals": { + "subtotal": "47924", + "tax": "9585", + "total": "57509", + "grand_total": "57509", + "fee": "0", + "earnings": "0", + "currency_code": "GBP" + }, + "payout_totals": null, + "adjusted_payout_totals": null, + "line_items": [ + { + "id": "txnitm_01hv8xxw6qxaypzmf81xqgcba9", + "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "quantity": 10, + "totals": { + "subtotal": "24000", + "tax": "4800", + "discount": "0", + "total": "28800" + }, + "product": { + "id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "name": "AeroEdit Pro", + "description": "Designed for professional pilots, including all features plus in Basic plus compliance monitoring, route optimization, and third-party integrations.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/bT1XUOJAQhOUxGs83cbk_pro.png", + "custom_data": { + "features": { + "aircraft_performance": true, + "compliance_monitoring": true, + "flight_log_management": true, + "payment_by_invoice": false, + "route_planning": true, + "sso": false + }, + "suggested_addons": [ + "pro_01h1vjes1y163xfj1rh1tkfb65", + "pro_01gsz97mq9pa4fkyy0wqenepkz" + ], + "upgrade_description": "Move from Basic to Pro to take advantage of aircraft performance, advanced route planning, and compliance monitoring." + }, + "status": "active", + "created_at": "2023-02-23T12:43:46.605Z", + "updated_at": "2024-04-05T15:53:44.687Z", + "import_meta": null + }, + "tax_rate": "0.2", + "unit_totals": { + "subtotal": "2400", + "tax": "480", + "discount": "0", + "total": "2880" + } + }, + { + "id": "txnitm_01hv8xxw6qxaypzmf823nwmr95", + "price_id": "pri_01h1vjfevh5etwq3rb416a23h2", + "quantity": 1, + "totals": { + "subtotal": "8001", + "tax": "1600", + "discount": "0", + "total": "9601" + }, + "product": { + "id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "name": "Analytics addon", + "description": "Unlock advanced insights into your flight data with enhanced analytics and reporting features. Includes customizable reporting templates and trend analysis across flights.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": null, + "status": "active", + "created_at": "2023-06-01T13:30:50.302Z", + "updated_at": "2024-04-05T15:47:17.163Z", + "import_meta": null + }, + "tax_rate": "0.2", + "unit_totals": { + "subtotal": "8001", + "tax": "1600", + "discount": "0", + "total": "9601" + } + }, + { + "id": "txnitm_01hv8xxw6qxaypzmf829vtr6tx", + "price_id": "pri_01gsz98e27ak2tyhexptwc58yk", + "quantity": 1, + "totals": { + "subtotal": "15923", + "tax": "3185", + "discount": "0", + "total": "19108" + }, + "product": { + "id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "name": "Custom domains", + "description": "Make AeroEdit truly your own with custom domains. Custom domains reinforce your brand identity and make it easy for your team to access your account.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/XIG7UXoJQHmlIAiKcnkA_custom-domains.png", + "custom_data": null, + "status": "active", + "created_at": "2023-02-23T14:01:02.441Z", + "updated_at": "2024-04-05T15:43:28.971Z", + "import_meta": null + }, + "tax_rate": "0.2", + "unit_totals": { + "subtotal": "15923", + "tax": "3185", + "discount": "0", + "total": "19108" + } + } + ] + }, + "payments": [], + "checkout": { + "url": "https://aeroedit.com/pay?_ptxn=txn_01hv8xxw3etar07vaxsqbyqasy" + } + }, + { + "id": "txn_01hv8xbtmb6zc7c264ycteehth", + "status": "past_due", + "customer_id": "ctm_01hv8wt8nffez4p2t6typn4a5j", + "address_id": "add_01hv8wt8ny8ms5vtm71bj8vcdd", + "business_id": null, + "custom_data": null, + "origin": "subscription_recurring", + "collection_mode": "automatic", + "subscription_id": "sub_01hv8x29kz0t586xy6zn1a62ny", + "invoice_id": null, + "invoice_number": null, + "billing_details": null, + "billing_period": { + "starts_at": "2024-05-12T10:18:47.635628Z", + "ends_at": "2024-06-12T10:18:47.635628Z" + }, + "currency_code": "USD", + "discount_id": null, + "created_at": "2024-04-12T10:24:01.588479Z", + "updated_at": "2024-04-12T10:24:03.197001Z", + "billed_at": "2024-04-12T10:24:01.163479Z", + "items": [ + { + "price": { + "id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "description": "Monthly", + "type": "standard", + "name": "Monthly (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "3000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 999 + }, + "status": "active", + "created_at": "2023-02-23T13:55:22.538367Z", + "updated_at": "2024-04-11T13:54:52.254748Z", + "import_meta": null + }, + "quantity": 10, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-05-12T10:18:47.635628Z", + "ends_at": "2024-06-12T10:18:47.635628Z" + } + } + }, + { + "price": { + "id": "pri_01h1vjfevh5etwq3rb416a23h2", + "description": "Monthly", + "type": "standard", + "name": "Monthly (recurring addon)", + "product_id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "10000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 100 + }, + "status": "active", + "created_at": "2023-06-01T13:31:12.625056Z", + "updated_at": "2024-04-09T07:23:00.907834Z", + "import_meta": null + }, + "quantity": 1, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-05-12T10:18:47.635628Z", + "ends_at": "2024-06-12T10:18:47.635628Z" + } + } + } + ], + "details": { + "tax_rates_used": [ + { + "tax_rate": "0.08875", + "totals": { + "subtotal": "40000", + "discount": "0", + "tax": "3549", + "total": "43549" + } + } + ], + "totals": { + "subtotal": "40000", + "tax": "3549", + "discount": "0", + "total": "43549", + "grand_total": "43549", + "fee": null, + "credit": "0", + "credit_to_balance": "0", + "balance": "43549", + "earnings": null, + "currency_code": "USD" + }, + "adjusted_totals": { + "subtotal": "40000", + "tax": "3549", + "total": "43549", + "grand_total": "43549", + "fee": "0", + "earnings": "0", + "currency_code": "USD" + }, + "payout_totals": null, + "adjusted_payout_totals": null, + "line_items": [ + { + "id": "txnitm_01hv8xbv0wdggp4a9338b18ckn", + "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "quantity": 10, + "totals": { + "subtotal": "30000", + "tax": "2662", + "discount": "0", + "total": "32662" + }, + "product": { + "id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "name": "AeroEdit Pro", + "description": "Designed for professional pilots, including all features plus in Basic plus compliance monitoring, route optimization, and third-party integrations.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/bT1XUOJAQhOUxGs83cbk_pro.png", + "custom_data": { + "features": { + "aircraft_performance": true, + "compliance_monitoring": true, + "flight_log_management": true, + "payment_by_invoice": false, + "route_planning": true, + "sso": false + }, + "suggested_addons": [ + "pro_01h1vjes1y163xfj1rh1tkfb65", + "pro_01gsz97mq9pa4fkyy0wqenepkz" + ], + "upgrade_description": "Move from Basic to Pro to take advantage of aircraft performance, advanced route planning, and compliance monitoring." + }, + "status": "active", + "created_at": "2023-02-23T12:43:46.605Z", + "updated_at": "2024-04-05T15:53:44.687Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "3000", + "tax": "266", + "discount": "0", + "total": "3266" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-05-12T10:18:47.635628Z", + "ends_at": "2024-06-12T10:18:47.635628Z" + } + } + }, + { + "id": "txnitm_01hv8xbv0wdggp4a933cx2m9qc", + "price_id": "pri_01h1vjfevh5etwq3rb416a23h2", + "quantity": 1, + "totals": { + "subtotal": "10000", + "tax": "887", + "discount": "0", + "total": "10887" + }, + "product": { + "id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "name": "Analytics addon", + "description": "Unlock advanced insights into your flight data with enhanced analytics and reporting features. Includes customizable reporting templates and trend analysis across flights.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": null, + "status": "active", + "created_at": "2023-06-01T13:30:50.302Z", + "updated_at": "2024-04-05T15:47:17.163Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "10000", + "tax": "887", + "discount": "0", + "total": "10887" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-05-12T10:18:47.635628Z", + "ends_at": "2024-06-12T10:18:47.635628Z" + } + } + } + ] + }, + "payments": [ + { + "payment_attempt_id": "ff8123f6-9cfc-4f04-9984-75e4ad04b169", + "stored_payment_method_id": "281ff2ca-8550-42b9-bf39-15948e7de62d", + "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", + "amount": "43549", + "status": "error", + "error_code": "authentication_failed", + "method_details": { + "type": "card", + "card": { + "type": "visa", + "last4": "3184", + "expiry_month": 1, + "expiry_year": 2025, + "cardholder_name": "Michael McGovern" + } + }, + "created_at": "2024-04-12T10:24:01.692772Z", + "captured_at": null + } + ], + "checkout": { + "url": "https://aeroedit.com/pay?_ptxn=txn_01hv8xbtmb6zc7c264ycteehth" + } + }, + { + "id": "txn_01hv8wptq8987qeep44cyrewp9", + "status": "completed", + "customer_id": "ctm_01hv8wt8nffez4p2t6typn4a5j", + "address_id": "add_01hv8wt8ny8ms5vtm71bj8vcdd", + "business_id": null, + "custom_data": null, + "origin": "web", + "collection_mode": "automatic", + "subscription_id": "sub_01hv8x29kz0t586xy6zn1a62ny", + "invoice_id": "inv_01hv8x29nsh54c2pgt0hnq0zkx", + "invoice_number": "325-10566", + "billing_details": null, + "billing_period": { + "starts_at": "2024-04-12T10:18:47.635628Z", + "ends_at": "2024-05-12T10:18:47.635628Z" + }, + "currency_code": "USD", + "discount_id": null, + "created_at": "2024-04-12T10:12:33.2014Z", + "updated_at": "2024-04-12T10:20:21.386946Z", + "billed_at": "2024-04-12T10:18:48.294633Z", + "items": [ + { + "price": { + "id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "description": "Monthly", + "type": "standard", + "name": "Monthly (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "3000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 999 + }, + "status": "active", + "created_at": "2023-02-23T13:55:22.538367Z", + "updated_at": "2024-04-11T13:54:52.254748Z", + "import_meta": null + }, + "quantity": 10 + }, + { + "price": { + "id": "pri_01h1vjfevh5etwq3rb416a23h2", + "description": "Monthly", + "type": "standard", + "name": "Monthly (recurring addon)", + "product_id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "10000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 100 + }, + "status": "active", + "created_at": "2023-06-01T13:31:12.625056Z", + "updated_at": "2024-04-09T07:23:00.907834Z", + "import_meta": null + }, + "quantity": 1 + }, + { + "price": { + "id": "pri_01gsz98e27ak2tyhexptwc58yk", + "description": "One-time addon", + "type": "standard", + "name": "One-time addon", + "product_id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "billing_cycle": null, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "19900", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 1 + }, + "status": "active", + "created_at": "2023-02-23T14:01:28.391712Z", + "updated_at": "2024-04-09T07:23:10.921392Z", + "import_meta": null + }, + "quantity": 1 + } + ], + "details": { + "tax_rates_used": [ + { + "tax_rate": "0.08875", + "totals": { + "subtotal": "59900", + "discount": "0", + "tax": "5315", + "total": "65215" + } + } + ], + "totals": { + "subtotal": "59900", + "tax": "5315", + "discount": "0", + "total": "65215", + "grand_total": "65215", + "fee": "3311", + "credit": "0", + "credit_to_balance": "0", + "balance": "0", + "earnings": "56589", + "currency_code": "USD" + }, + "adjusted_totals": { + "subtotal": "59900", + "tax": "5315", + "total": "65215", + "grand_total": "65215", + "fee": "3311", + "earnings": "56589", + "currency_code": "USD" + }, + "payout_totals": { + "subtotal": "59900", + "tax": "5315", + "discount": "0", + "total": "65215", + "credit": "0", + "credit_to_balance": "0", + "balance": "0", + "grand_total": "65215", + "fee": "3311", + "earnings": "56589", + "currency_code": "USD" + }, + "adjusted_payout_totals": { + "subtotal": "59900", + "tax": "5315", + "total": "65215", + "fee": "3311", + "chargeback_fee": { + "amount": "0", + "original": null + }, + "earnings": "56589", + "currency_code": "USD" + }, + "line_items": [ + { + "id": "txnitm_01hv8wt98jahpbm1t1tzr06z6n", + "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "quantity": 10, + "totals": { + "subtotal": "30000", + "tax": "2662", + "discount": "0", + "total": "32662" + }, + "product": { + "id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "name": "AeroEdit Pro", + "description": "Designed for professional pilots, including all features plus in Basic plus compliance monitoring, route optimization, and third-party integrations.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/bT1XUOJAQhOUxGs83cbk_pro.png", + "custom_data": { + "features": { + "aircraft_performance": true, + "compliance_monitoring": true, + "flight_log_management": true, + "payment_by_invoice": false, + "route_planning": true, + "sso": false + }, + "suggested_addons": [ + "pro_01h1vjes1y163xfj1rh1tkfb65", + "pro_01gsz97mq9pa4fkyy0wqenepkz" + ], + "upgrade_description": "Move from Basic to Pro to take advantage of aircraft performance, advanced route planning, and compliance monitoring." + }, + "status": "active", + "created_at": "2023-02-23T12:43:46.605Z", + "updated_at": "2024-04-05T15:53:44.687Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "3000", + "tax": "266", + "discount": "0", + "total": "3266" + } + }, + { + "id": "txnitm_01hv8wt98jahpbm1t1v1sd067y", + "price_id": "pri_01h1vjfevh5etwq3rb416a23h2", + "quantity": 1, + "totals": { + "subtotal": "10000", + "tax": "887", + "discount": "0", + "total": "10887" + }, + "product": { + "id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "name": "Analytics addon", + "description": "Unlock advanced insights into your flight data with enhanced analytics and reporting features. Includes customizable reporting templates and trend analysis across flights.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": null, + "status": "active", + "created_at": "2023-06-01T13:30:50.302Z", + "updated_at": "2024-04-05T15:47:17.163Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "10000", + "tax": "887", + "discount": "0", + "total": "10887" + } + }, + { + "id": "txnitm_01hv8wt98jahpbm1t1v67vqnb6", + "price_id": "pri_01gsz98e27ak2tyhexptwc58yk", + "quantity": 1, + "totals": { + "subtotal": "19900", + "tax": "1766", + "discount": "0", + "total": "21666" + }, + "product": { + "id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "name": "Custom domains", + "description": "Make AeroEdit truly your own with custom domains. Custom domains reinforce your brand identity and make it easy for your team to access your account.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/XIG7UXoJQHmlIAiKcnkA_custom-domains.png", + "custom_data": null, + "status": "active", + "created_at": "2023-02-23T14:01:02.441Z", + "updated_at": "2024-04-05T15:43:28.971Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "19900", + "tax": "1766", + "discount": "0", + "total": "21666" + } + } + ] + }, + "payments": [ + { + "payment_attempt_id": "937640dd-e3dc-40df-a16c-bb75aafd8f71", + "stored_payment_method_id": "281ff2ca-8550-42b9-bf39-15948e7de62d", + "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", + "amount": "65215", + "status": "captured", + "error_code": null, + "method_details": { + "type": "card", + "card": { + "type": "visa", + "last4": "3184", + "expiry_month": 1, + "expiry_year": 2025, + "cardholder_name": "Michael McGovern" + } + }, + "created_at": "2024-04-12T10:18:33.579142Z", + "captured_at": "2024-04-12T10:18:47.635628Z" + }, + { + "payment_attempt_id": "8f72cfa6-26b4-4a57-91dc-8f2708f7822d", + "stored_payment_method_id": "a78ece50-356f-4e0c-b72d-ad5368b0a0d9", + "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne7z", + "amount": "65215", + "status": "error", + "error_code": "declined", + "method_details": { + "type": "card", + "card": { + "type": "visa", + "last4": "0002", + "expiry_month": 1, + "expiry_year": 2025, + "cardholder_name": "Michael McGovern" + } + }, + "created_at": "2024-04-12T10:15:57.888183Z", + "captured_at": null + } + ], + "checkout": { + "url": "https://aeroedit.com/pay?_ptxn=txn_01hv8wptq8987qeep44cyrewp9" + } + } + ], + "meta": { + "request_id": "b93d9c94-c28f-4e5d-af2e-044854d7afe8", + "pagination": { + "per_page": 30, + "next": "https://api.paddle.com/transactions?after=txn_01hv8kxg3hxyxs9t471ms9kfsz", + "has_more": false, + "estimated_total": 3 + } + } +} diff --git a/testdata/transactions_paginated_pg1.json b/testdata/transactions_paginated_pg1.json new file mode 100644 index 0000000..cf568ee --- /dev/null +++ b/testdata/transactions_paginated_pg1.json @@ -0,0 +1,851 @@ +{ + "data": [ + { + "id": "txn_01hv8xxw3etar07vaxsqbyqasy", + "status": "draft", + "customer_id": null, + "address_id": null, + "business_id": null, + "custom_data": null, + "origin": "web", + "collection_mode": "automatic", + "subscription_id": null, + "invoice_id": null, + "invoice_number": null, + "billing_details": null, + "billing_period": null, + "currency_code": "GBP", + "discount_id": null, + "created_at": "2024-04-12T10:33:52.610609Z", + "updated_at": "2024-04-12T10:33:52.610609Z", + "billed_at": null, + "items": [ + { + "price": { + "id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "description": "Monthly", + "type": "standard", + "name": "Monthly (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "3000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 999 + }, + "status": "active", + "created_at": "2023-02-23T13:55:22.538367Z", + "updated_at": "2024-04-11T13:54:52.254748Z", + "import_meta": null + }, + "quantity": 10 + }, + { + "price": { + "id": "pri_01h1vjfevh5etwq3rb416a23h2", + "description": "Monthly", + "type": "standard", + "name": "Monthly (recurring addon)", + "product_id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "10000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 100 + }, + "status": "active", + "created_at": "2023-06-01T13:31:12.625056Z", + "updated_at": "2024-04-09T07:23:00.907834Z", + "import_meta": null + }, + "quantity": 1 + }, + { + "price": { + "id": "pri_01gsz98e27ak2tyhexptwc58yk", + "description": "One-time addon", + "type": "standard", + "name": "One-time addon", + "product_id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "billing_cycle": null, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "19900", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 1 + }, + "status": "active", + "created_at": "2023-02-23T14:01:28.391712Z", + "updated_at": "2024-04-09T07:23:10.921392Z", + "import_meta": null + }, + "quantity": 1 + } + ], + "details": { + "tax_rates_used": [ + { + "tax_rate": "0.2", + "totals": { + "subtotal": "47924", + "discount": "0", + "tax": "9585", + "total": "57509" + } + } + ], + "totals": { + "subtotal": "47924", + "tax": "9585", + "discount": "0", + "total": "57509", + "grand_total": "57509", + "fee": null, + "credit": "0", + "credit_to_balance": "0", + "balance": "57509", + "earnings": null, + "currency_code": "GBP" + }, + "adjusted_totals": { + "subtotal": "47924", + "tax": "9585", + "total": "57509", + "grand_total": "57509", + "fee": "0", + "earnings": "0", + "currency_code": "GBP" + }, + "payout_totals": null, + "adjusted_payout_totals": null, + "line_items": [ + { + "id": "txnitm_01hv8xxw6qxaypzmf81xqgcba9", + "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "quantity": 10, + "totals": { + "subtotal": "24000", + "tax": "4800", + "discount": "0", + "total": "28800" + }, + "product": { + "id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "name": "AeroEdit Pro", + "description": "Designed for professional pilots, including all features plus in Basic plus compliance monitoring, route optimization, and third-party integrations.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/bT1XUOJAQhOUxGs83cbk_pro.png", + "custom_data": { + "features": { + "aircraft_performance": true, + "compliance_monitoring": true, + "flight_log_management": true, + "payment_by_invoice": false, + "route_planning": true, + "sso": false + }, + "suggested_addons": [ + "pro_01h1vjes1y163xfj1rh1tkfb65", + "pro_01gsz97mq9pa4fkyy0wqenepkz" + ], + "upgrade_description": "Move from Basic to Pro to take advantage of aircraft performance, advanced route planning, and compliance monitoring." + }, + "status": "active", + "created_at": "2023-02-23T12:43:46.605Z", + "updated_at": "2024-04-05T15:53:44.687Z", + "import_meta": null + }, + "tax_rate": "0.2", + "unit_totals": { + "subtotal": "2400", + "tax": "480", + "discount": "0", + "total": "2880" + } + }, + { + "id": "txnitm_01hv8xxw6qxaypzmf823nwmr95", + "price_id": "pri_01h1vjfevh5etwq3rb416a23h2", + "quantity": 1, + "totals": { + "subtotal": "8001", + "tax": "1600", + "discount": "0", + "total": "9601" + }, + "product": { + "id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "name": "Analytics addon", + "description": "Unlock advanced insights into your flight data with enhanced analytics and reporting features. Includes customizable reporting templates and trend analysis across flights.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": null, + "status": "active", + "created_at": "2023-06-01T13:30:50.302Z", + "updated_at": "2024-04-05T15:47:17.163Z", + "import_meta": null + }, + "tax_rate": "0.2", + "unit_totals": { + "subtotal": "8001", + "tax": "1600", + "discount": "0", + "total": "9601" + } + }, + { + "id": "txnitm_01hv8xxw6qxaypzmf829vtr6tx", + "price_id": "pri_01gsz98e27ak2tyhexptwc58yk", + "quantity": 1, + "totals": { + "subtotal": "15923", + "tax": "3185", + "discount": "0", + "total": "19108" + }, + "product": { + "id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "name": "Custom domains", + "description": "Make AeroEdit truly your own with custom domains. Custom domains reinforce your brand identity and make it easy for your team to access your account.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/XIG7UXoJQHmlIAiKcnkA_custom-domains.png", + "custom_data": null, + "status": "active", + "created_at": "2023-02-23T14:01:02.441Z", + "updated_at": "2024-04-05T15:43:28.971Z", + "import_meta": null + }, + "tax_rate": "0.2", + "unit_totals": { + "subtotal": "15923", + "tax": "3185", + "discount": "0", + "total": "19108" + } + } + ] + }, + "payments": [], + "checkout": { + "url": "https://aeroedit.com/pay?_ptxn=txn_01hv8xxw3etar07vaxsqbyqasy" + } + }, + { + "id": "txn_01hv8xbtmb6zc7c264ycteehth", + "status": "past_due", + "customer_id": "ctm_01hv8wt8nffez4p2t6typn4a5j", + "address_id": "add_01hv8wt8ny8ms5vtm71bj8vcdd", + "business_id": null, + "custom_data": null, + "origin": "subscription_recurring", + "collection_mode": "automatic", + "subscription_id": "sub_01hv8x29kz0t586xy6zn1a62ny", + "invoice_id": null, + "invoice_number": null, + "billing_details": null, + "billing_period": { + "starts_at": "2024-05-12T10:18:47.635628Z", + "ends_at": "2024-06-12T10:18:47.635628Z" + }, + "currency_code": "USD", + "discount_id": null, + "created_at": "2024-04-12T10:24:01.588479Z", + "updated_at": "2024-04-12T10:24:03.197001Z", + "billed_at": "2024-04-12T10:24:01.163479Z", + "items": [ + { + "price": { + "id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "description": "Monthly", + "type": "standard", + "name": "Monthly (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "3000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 999 + }, + "status": "active", + "created_at": "2023-02-23T13:55:22.538367Z", + "updated_at": "2024-04-11T13:54:52.254748Z", + "import_meta": null + }, + "quantity": 10, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-05-12T10:18:47.635628Z", + "ends_at": "2024-06-12T10:18:47.635628Z" + } + } + }, + { + "price": { + "id": "pri_01h1vjfevh5etwq3rb416a23h2", + "description": "Monthly", + "type": "standard", + "name": "Monthly (recurring addon)", + "product_id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "10000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 100 + }, + "status": "active", + "created_at": "2023-06-01T13:31:12.625056Z", + "updated_at": "2024-04-09T07:23:00.907834Z", + "import_meta": null + }, + "quantity": 1, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-05-12T10:18:47.635628Z", + "ends_at": "2024-06-12T10:18:47.635628Z" + } + } + } + ], + "details": { + "tax_rates_used": [ + { + "tax_rate": "0.08875", + "totals": { + "subtotal": "40000", + "discount": "0", + "tax": "3549", + "total": "43549" + } + } + ], + "totals": { + "subtotal": "40000", + "tax": "3549", + "discount": "0", + "total": "43549", + "grand_total": "43549", + "fee": null, + "credit": "0", + "credit_to_balance": "0", + "balance": "43549", + "earnings": null, + "currency_code": "USD" + }, + "adjusted_totals": { + "subtotal": "40000", + "tax": "3549", + "total": "43549", + "grand_total": "43549", + "fee": "0", + "earnings": "0", + "currency_code": "USD" + }, + "payout_totals": null, + "adjusted_payout_totals": null, + "line_items": [ + { + "id": "txnitm_01hv8xbv0wdggp4a9338b18ckn", + "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "quantity": 10, + "totals": { + "subtotal": "30000", + "tax": "2662", + "discount": "0", + "total": "32662" + }, + "product": { + "id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "name": "AeroEdit Pro", + "description": "Designed for professional pilots, including all features plus in Basic plus compliance monitoring, route optimization, and third-party integrations.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/bT1XUOJAQhOUxGs83cbk_pro.png", + "custom_data": { + "features": { + "aircraft_performance": true, + "compliance_monitoring": true, + "flight_log_management": true, + "payment_by_invoice": false, + "route_planning": true, + "sso": false + }, + "suggested_addons": [ + "pro_01h1vjes1y163xfj1rh1tkfb65", + "pro_01gsz97mq9pa4fkyy0wqenepkz" + ], + "upgrade_description": "Move from Basic to Pro to take advantage of aircraft performance, advanced route planning, and compliance monitoring." + }, + "status": "active", + "created_at": "2023-02-23T12:43:46.605Z", + "updated_at": "2024-04-05T15:53:44.687Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "3000", + "tax": "266", + "discount": "0", + "total": "3266" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-05-12T10:18:47.635628Z", + "ends_at": "2024-06-12T10:18:47.635628Z" + } + } + }, + { + "id": "txnitm_01hv8xbv0wdggp4a933cx2m9qc", + "price_id": "pri_01h1vjfevh5etwq3rb416a23h2", + "quantity": 1, + "totals": { + "subtotal": "10000", + "tax": "887", + "discount": "0", + "total": "10887" + }, + "product": { + "id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "name": "Analytics addon", + "description": "Unlock advanced insights into your flight data with enhanced analytics and reporting features. Includes customizable reporting templates and trend analysis across flights.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": null, + "status": "active", + "created_at": "2023-06-01T13:30:50.302Z", + "updated_at": "2024-04-05T15:47:17.163Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "10000", + "tax": "887", + "discount": "0", + "total": "10887" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-05-12T10:18:47.635628Z", + "ends_at": "2024-06-12T10:18:47.635628Z" + } + } + } + ] + }, + "payments": [ + { + "payment_attempt_id": "ff8123f6-9cfc-4f04-9984-75e4ad04b169", + "stored_payment_method_id": "281ff2ca-8550-42b9-bf39-15948e7de62d", + "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", + "amount": "43549", + "status": "error", + "error_code": "authentication_failed", + "method_details": { + "type": "card", + "card": { + "type": "visa", + "last4": "3184", + "expiry_month": 1, + "expiry_year": 2025, + "cardholder_name": "Michael McGovern" + } + }, + "created_at": "2024-04-12T10:24:01.692772Z", + "captured_at": null + } + ], + "checkout": { + "url": "https://aeroedit.com/pay?_ptxn=txn_01hv8xbtmb6zc7c264ycteehth" + } + }, + { + "id": "txn_01hv8wptq8987qeep44cyrewp9", + "status": "completed", + "customer_id": "ctm_01hv8wt8nffez4p2t6typn4a5j", + "address_id": "add_01hv8wt8ny8ms5vtm71bj8vcdd", + "business_id": null, + "custom_data": null, + "origin": "web", + "collection_mode": "automatic", + "subscription_id": "sub_01hv8x29kz0t586xy6zn1a62ny", + "invoice_id": "inv_01hv8x29nsh54c2pgt0hnq0zkx", + "invoice_number": "325-10566", + "billing_details": null, + "billing_period": { + "starts_at": "2024-04-12T10:18:47.635628Z", + "ends_at": "2024-05-12T10:18:47.635628Z" + }, + "currency_code": "USD", + "discount_id": null, + "created_at": "2024-04-12T10:12:33.2014Z", + "updated_at": "2024-04-12T10:20:21.386946Z", + "billed_at": "2024-04-12T10:18:48.294633Z", + "items": [ + { + "price": { + "id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "description": "Monthly", + "type": "standard", + "name": "Monthly (per seat)", + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "3000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 999 + }, + "status": "active", + "created_at": "2023-02-23T13:55:22.538367Z", + "updated_at": "2024-04-11T13:54:52.254748Z", + "import_meta": null + }, + "quantity": 10 + }, + { + "price": { + "id": "pri_01h1vjfevh5etwq3rb416a23h2", + "description": "Monthly", + "type": "standard", + "name": "Monthly (recurring addon)", + "product_id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "10000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 100 + }, + "status": "active", + "created_at": "2023-06-01T13:31:12.625056Z", + "updated_at": "2024-04-09T07:23:00.907834Z", + "import_meta": null + }, + "quantity": 1 + }, + { + "price": { + "id": "pri_01gsz98e27ak2tyhexptwc58yk", + "description": "One-time addon", + "type": "standard", + "name": "One-time addon", + "product_id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "billing_cycle": null, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "19900", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 1 + }, + "status": "active", + "created_at": "2023-02-23T14:01:28.391712Z", + "updated_at": "2024-04-09T07:23:10.921392Z", + "import_meta": null + }, + "quantity": 1 + } + ], + "details": { + "tax_rates_used": [ + { + "tax_rate": "0.08875", + "totals": { + "subtotal": "59900", + "discount": "0", + "tax": "5315", + "total": "65215" + } + } + ], + "totals": { + "subtotal": "59900", + "tax": "5315", + "discount": "0", + "total": "65215", + "grand_total": "65215", + "fee": "3311", + "credit": "0", + "credit_to_balance": "0", + "balance": "0", + "earnings": "56589", + "currency_code": "USD" + }, + "adjusted_totals": { + "subtotal": "59900", + "tax": "5315", + "total": "65215", + "grand_total": "65215", + "fee": "3311", + "earnings": "56589", + "currency_code": "USD" + }, + "payout_totals": { + "subtotal": "59900", + "tax": "5315", + "discount": "0", + "total": "65215", + "credit": "0", + "credit_to_balance": "0", + "balance": "0", + "grand_total": "65215", + "fee": "3311", + "earnings": "56589", + "currency_code": "USD" + }, + "adjusted_payout_totals": { + "subtotal": "59900", + "tax": "5315", + "total": "65215", + "fee": "3311", + "chargeback_fee": { + "amount": "0", + "original": null + }, + "earnings": "56589", + "currency_code": "USD" + }, + "line_items": [ + { + "id": "txnitm_01hv8wt98jahpbm1t1tzr06z6n", + "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "quantity": 10, + "totals": { + "subtotal": "30000", + "tax": "2662", + "discount": "0", + "total": "32662" + }, + "product": { + "id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "name": "AeroEdit Pro", + "description": "Designed for professional pilots, including all features plus in Basic plus compliance monitoring, route optimization, and third-party integrations.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/bT1XUOJAQhOUxGs83cbk_pro.png", + "custom_data": { + "features": { + "aircraft_performance": true, + "compliance_monitoring": true, + "flight_log_management": true, + "payment_by_invoice": false, + "route_planning": true, + "sso": false + }, + "suggested_addons": [ + "pro_01h1vjes1y163xfj1rh1tkfb65", + "pro_01gsz97mq9pa4fkyy0wqenepkz" + ], + "upgrade_description": "Move from Basic to Pro to take advantage of aircraft performance, advanced route planning, and compliance monitoring." + }, + "status": "active", + "created_at": "2023-02-23T12:43:46.605Z", + "updated_at": "2024-04-05T15:53:44.687Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "3000", + "tax": "266", + "discount": "0", + "total": "3266" + } + }, + { + "id": "txnitm_01hv8wt98jahpbm1t1v1sd067y", + "price_id": "pri_01h1vjfevh5etwq3rb416a23h2", + "quantity": 1, + "totals": { + "subtotal": "10000", + "tax": "887", + "discount": "0", + "total": "10887" + }, + "product": { + "id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "name": "Analytics addon", + "description": "Unlock advanced insights into your flight data with enhanced analytics and reporting features. Includes customizable reporting templates and trend analysis across flights.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": null, + "status": "active", + "created_at": "2023-06-01T13:30:50.302Z", + "updated_at": "2024-04-05T15:47:17.163Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "10000", + "tax": "887", + "discount": "0", + "total": "10887" + } + }, + { + "id": "txnitm_01hv8wt98jahpbm1t1v67vqnb6", + "price_id": "pri_01gsz98e27ak2tyhexptwc58yk", + "quantity": 1, + "totals": { + "subtotal": "19900", + "tax": "1766", + "discount": "0", + "total": "21666" + }, + "product": { + "id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "name": "Custom domains", + "description": "Make AeroEdit truly your own with custom domains. Custom domains reinforce your brand identity and make it easy for your team to access your account.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/XIG7UXoJQHmlIAiKcnkA_custom-domains.png", + "custom_data": null, + "status": "active", + "created_at": "2023-02-23T14:01:02.441Z", + "updated_at": "2024-04-05T15:43:28.971Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "19900", + "tax": "1766", + "discount": "0", + "total": "21666" + } + } + ] + }, + "payments": [ + { + "payment_attempt_id": "937640dd-e3dc-40df-a16c-bb75aafd8f71", + "stored_payment_method_id": "281ff2ca-8550-42b9-bf39-15948e7de62d", + "payment_method_id": "paymtd_01hv8x1tpjfnttxddw73xnqx6s", + "amount": "65215", + "status": "captured", + "error_code": null, + "method_details": { + "type": "card", + "card": { + "type": "visa", + "last4": "3184", + "expiry_month": 1, + "expiry_year": 2025, + "cardholder_name": "Michael McGovern" + } + }, + "created_at": "2024-04-12T10:18:33.579142Z", + "captured_at": "2024-04-12T10:18:47.635628Z" + }, + { + "payment_attempt_id": "8f72cfa6-26b4-4a57-91dc-8f2708f7822d", + "stored_payment_method_id": "a78ece50-356f-4e0c-b72d-ad5368b0a0d9", + "payment_method_id": "paymtd_01hv8wx2mka7dfsqjjsxh1ne7z", + "amount": "65215", + "status": "error", + "error_code": "declined", + "method_details": { + "type": "card", + "card": { + "type": "visa", + "last4": "0002", + "expiry_month": 1, + "expiry_year": 2025, + "cardholder_name": "Michael McGovern" + } + }, + "created_at": "2024-04-12T10:15:57.888183Z", + "captured_at": null + } + ], + "checkout": { + "url": "https://aeroedit.com/pay?_ptxn=txn_01hv8wptq8987qeep44cyrewp9" + } + } + ], + "meta": { + "request_id": "b93d9c94-c28f-4e5d-af2e-044854d7afe8", + "pagination": { + "per_page": 3, + "next": "https://api.paddle.com/transactions?after=txn_01hv8wptq8987qeep44cyrewp9", + "has_more": true, + "estimated_total": 6 + } + } +} diff --git a/testdata/transactions_paginated_pg2.json b/testdata/transactions_paginated_pg2.json new file mode 100644 index 0000000..9248508 --- /dev/null +++ b/testdata/transactions_paginated_pg2.json @@ -0,0 +1,824 @@ +{ + "data": [ + + { + "id": "txn_01hv8wnvvtedwjrhfhpr9vkq9w", + "status": "completed", + "customer_id": "ctm_01hchnxgrh0wcyngy8q9d1hpkz", + "address_id": "add_01hchnxgsa1v3791z2tvy0fc1t", + "business_id": null, + "custom_data": null, + "origin": "subscription_recurring", + "collection_mode": "automatic", + "subscription_id": "sub_01hchny8h8r5w9xtb514qs6rdy", + "invoice_id": "inv_01hv8wnzafnbv52m4zs73gghrq", + "invoice_number": "325-10565", + "billing_details": null, + "billing_period": { + "starts_at": "2024-04-12T10:11:57.907988Z", + "ends_at": "2024-05-12T10:11:57.907988Z" + }, + "currency_code": "USD", + "discount_id": null, + "created_at": "2024-04-12T10:12:01.643104Z", + "updated_at": "2024-04-12T10:15:53.705103Z", + "billed_at": "2024-04-12T10:12:01.530736Z", + "items": [ + { + "price": { + "id": "pri_01h1vjfevh5etwq3rb416a23h2", + "description": "Monthly (recurring addon)", + "type": "standard", + "name": null, + "product_id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "10000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 100 + }, + "status": "active", + "created_at": "2023-06-01T13:31:12.625056Z", + "updated_at": "2023-08-30T10:34:33.862679Z", + "import_meta": null + }, + "quantity": 1, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-04-12T10:11:57.907988Z", + "ends_at": "2024-05-12T10:11:57.907988Z" + } + } + }, + { + "price": { + "id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "description": "Monthly (per seat)", + "type": "standard", + "name": null, + "product_id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "billing_cycle": { + "interval": "month", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "3000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 10, + "maximum": 999 + }, + "status": "active", + "created_at": "2023-02-23T13:55:22.538367Z", + "updated_at": "2023-08-16T12:37:28.60409Z", + "import_meta": null + }, + "quantity": 10, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-04-12T10:11:57.907988Z", + "ends_at": "2024-05-12T10:11:57.907988Z" + } + } + } + ], + "details": { + "tax_rates_used": [ + { + "tax_rate": "0.19", + "totals": { + "subtotal": "40000", + "discount": "0", + "tax": "7600", + "total": "47600" + } + } + ], + "totals": { + "subtotal": "40000", + "tax": "7600", + "discount": "0", + "total": "47600", + "grand_total": "47600", + "fee": "2430", + "credit": "0", + "credit_to_balance": "0", + "balance": "0", + "earnings": "37570", + "currency_code": "USD" + }, + "adjusted_totals": { + "subtotal": "40000", + "tax": "7600", + "total": "47600", + "grand_total": "47600", + "fee": "2430", + "earnings": "37570", + "currency_code": "USD" + }, + "payout_totals": { + "subtotal": "40000", + "tax": "7600", + "discount": "0", + "total": "47600", + "credit": "0", + "credit_to_balance": "0", + "balance": "0", + "grand_total": "47600", + "fee": "2430", + "earnings": "37570", + "currency_code": "USD" + }, + "adjusted_payout_totals": { + "subtotal": "40000", + "tax": "7600", + "total": "47600", + "fee": "2430", + "chargeback_fee": { + "amount": "0", + "original": null + }, + "earnings": "37570", + "currency_code": "USD" + }, + "line_items": [ + { + "id": "txnitm_01hv8wnvyfe1jtd0jczr9wed4s", + "price_id": "pri_01h1vjfevh5etwq3rb416a23h2", + "quantity": 1, + "totals": { + "subtotal": "10000", + "tax": "1900", + "discount": "0", + "total": "11900" + }, + "product": { + "id": "pro_01h1vjes1y163xfj1rh1tkfb65", + "name": "Analytics addon", + "description": "Unlock advanced insights into your flight data with enhanced analytics and reporting features. Includes customizable reporting templates and trend analysis across flights.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/97dRpA6SXzcE6ekK9CAr_analytics.png", + "custom_data": null, + "status": "active", + "created_at": "2023-06-01T13:30:50.302Z", + "updated_at": "2024-04-05T15:47:17.163Z", + "import_meta": null + }, + "tax_rate": "0.19", + "unit_totals": { + "subtotal": "10000", + "tax": "1900", + "discount": "0", + "total": "11900" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-04-12T10:11:57.907988Z", + "ends_at": "2024-05-12T10:11:57.907988Z" + } + } + }, + { + "id": "txnitm_01hv8wnvyfe1jtd0jczy8s3mtk", + "price_id": "pri_01gsz8x8sawmvhz1pv30nge1ke", + "quantity": 10, + "totals": { + "subtotal": "30000", + "tax": "5700", + "discount": "0", + "total": "35700" + }, + "product": { + "id": "pro_01gsz4t5hdjse780zja8vvr7jg", + "name": "AeroEdit Pro", + "description": "Designed for professional pilots, including all features plus in Basic plus compliance monitoring, route optimization, and third-party integrations.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/bT1XUOJAQhOUxGs83cbk_pro.png", + "custom_data": { + "features": { + "aircraft_performance": true, + "compliance_monitoring": true, + "flight_log_management": true, + "payment_by_invoice": false, + "route_planning": true, + "sso": false + }, + "suggested_addons": [ + "pro_01h1vjes1y163xfj1rh1tkfb65", + "pro_01gsz97mq9pa4fkyy0wqenepkz" + ], + "upgrade_description": "Move from Basic to Pro to take advantage of aircraft performance, advanced route planning, and compliance monitoring." + }, + "status": "active", + "created_at": "2023-02-23T12:43:46.605Z", + "updated_at": "2024-04-05T15:53:44.687Z", + "import_meta": null + }, + "tax_rate": "0.19", + "unit_totals": { + "subtotal": "3000", + "tax": "570", + "discount": "0", + "total": "3570" + }, + "proration": { + "rate": "1", + "billing_period": { + "starts_at": "2024-04-12T10:11:57.907988Z", + "ends_at": "2024-05-12T10:11:57.907988Z" + } + } + } + ] + }, + "payments": [ + { + "payment_attempt_id": "37658fc8-5290-4384-a459-1e7a4e37174d", + "stored_payment_method_id": "1fa1a6bb-d2cd-4af3-88ec-45b0d88a066a", + "payment_method_id": "paymtd_01hchny0jr4h5b7adktfdacmgy", + "amount": "47600", + "status": "captured", + "error_code": null, + "method_details": { + "type": "card", + "card": { + "type": "visa", + "last4": "5556", + "expiry_month": 6, + "expiry_year": 2026, + "cardholder_name": "test" + } + }, + "created_at": "2024-04-12T10:12:01.729257Z", + "captured_at": "2024-04-12T10:12:04.496216Z" + } + ], + "checkout": { + "url": "https://aeroedit.com/pay?_ptxn=txn_01hv8wnvvtedwjrhfhpr9vkq9w" + } + }, + { + "id": "txn_01hv8m0mnx3sj85e7gxc6kga03", + "status": "canceled", + "customer_id": "ctm_01hv6y1jedq4p1n0yqn5ba3ky4", + "address_id": "add_01hv8gq3318ktkfengj2r75gfx", + "business_id": null, + "custom_data": null, + "origin": "api", + "collection_mode": "manual", + "subscription_id": "sub_01hv8xqmay5w5rfsnzkxzgy0yp", + "invoice_id": "inv_01hv8m0nn5nbvdejcvv9cpg8jf", + "invoice_number": "325-10567", + "billing_details": { + "enable_checkout": false, + "payment_terms": { + "interval": "day", + "frequency": 14 + }, + "purchase_order_number": "PO-123", + "additional_information": null + }, + "billing_period": { + "starts_at": "2024-04-12T00:00:00Z", + "ends_at": "2025-04-11T23:59:00Z" + }, + "currency_code": "USD", + "discount_id": "dsc_01gtgztp8fpchantd5g1wrksa3", + "created_at": "2024-04-12T07:40:38.00704Z", + "updated_at": "2024-04-12T10:31:27.360716Z", + "billed_at": "2024-04-12T10:30:27.198043Z", + "items": [ + { + "price": { + "id": "pri_01gsz91wy9k1yn7kx82aafwvea", + "description": "Annual", + "type": "standard", + "name": "Annual (per seat)", + "product_id": "pro_01gsz4vmqbjk3x4vvtafffd540", + "billing_cycle": { + "interval": "year", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "50000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 100 + }, + "status": "active", + "created_at": "2023-02-23T13:57:54.249913Z", + "updated_at": "2024-04-05T14:32:00.471447Z", + "import_meta": null + }, + "quantity": 50 + }, + { + "price": { + "id": "pri_01gsz96z29d88jrmsf2ztbfgjg", + "description": "Annual (recurring addon)", + "type": "standard", + "name": "Annual (recurring addon)", + "product_id": "pro_01gsz92krfzy3hcx5h5rtgnfwz", + "billing_cycle": { + "interval": "year", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "300000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 1 + }, + "status": "active", + "created_at": "2023-02-23T14:00:40.265185Z", + "updated_at": "2024-03-25T14:31:18.587603Z", + "import_meta": null + }, + "quantity": 1 + }, + { + "price": { + "id": "pri_01gsz98e27ak2tyhexptwc58yk", + "description": "One-time addon", + "type": "standard", + "name": "One-time addon", + "product_id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "billing_cycle": null, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "19900", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 1 + }, + "status": "active", + "created_at": "2023-02-23T14:01:28.391712Z", + "updated_at": "2024-04-09T07:23:10.921392Z", + "import_meta": null + }, + "quantity": 1 + } + ], + "details": { + "tax_rates_used": [ + { + "tax_rate": "0.08875", + "totals": { + "subtotal": "2819900", + "discount": "281990", + "tax": "225239", + "total": "2763149" + } + } + ], + "totals": { + "subtotal": "2819900", + "tax": "225239", + "discount": "281990", + "total": "2763149", + "grand_total": "2763149", + "fee": null, + "credit": "0", + "credit_to_balance": "0", + "balance": "2763149", + "earnings": null, + "currency_code": "USD" + }, + "adjusted_totals": { + "subtotal": "2537910", + "tax": "225239", + "total": "2763149", + "grand_total": "2763149", + "fee": "0", + "earnings": "0", + "currency_code": "USD" + }, + "payout_totals": null, + "adjusted_payout_totals": null, + "line_items": [ + { + "id": "txnitm_01hv8vzz0sjdj6grvpxyyjsmvf", + "price_id": "pri_01gsz91wy9k1yn7kx82aafwvea", + "quantity": 50, + "totals": { + "subtotal": "2500000", + "tax": "199687", + "discount": "250000", + "total": "2449687" + }, + "product": { + "id": "pro_01gsz4vmqbjk3x4vvtafffd540", + "name": "AeroEdit Enterprise", + "description": "The ultimate solution for organizations, featuring all Pro capabilities plus multi-user support, advanced data storage capabilities, plus personalized onboarding, dedicated account management, and the ability to pay via invoice.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/Ws808ziTS76a6YbnMkiK_enterprise.png", + "custom_data": { + "features": { + "aircraft_performance": true, + "compliance_monitoring": true, + "flight_log_management": true, + "payment_by_invoice": true, + "route_planning": true, + "sso": true + }, + "suggested_addons": [], + "upgrade_description": "Ready to reach new heights? Upgrade to enterprise to unlock single sign-on, payment by invoice, and dedicated account management." + }, + "status": "active", + "created_at": "2023-02-23T12:44:34.923Z", + "updated_at": "2024-04-05T15:58:28.309Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "50000", + "tax": "3994", + "discount": "5000", + "total": "48994" + } + }, + { + "id": "txnitm_01hv8vzz0sjdj6grvpy1fzm46f", + "price_id": "pri_01gsz96z29d88jrmsf2ztbfgjg", + "quantity": 1, + "totals": { + "subtotal": "300000", + "tax": "23962", + "discount": "30000", + "total": "293962" + }, + "product": { + "id": "pro_01gsz92krfzy3hcx5h5rtgnfwz", + "name": "VIP support", + "description": "Get exclusive access to our expert team of product specialists, available to help you make the most of your AeroEdit subscription.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/qgyipKJwRtq98YNboipo_vip-support.png", + "custom_data": null, + "status": "active", + "created_at": "2023-02-23T13:58:17.615Z", + "updated_at": "2024-04-05T15:44:02.893Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "300000", + "tax": "23962", + "discount": "30000", + "total": "293962" + } + }, + { + "id": "txnitm_01hv8vzz0sjdj6grvpy6g84gyw", + "price_id": "pri_01gsz98e27ak2tyhexptwc58yk", + "quantity": 1, + "totals": { + "subtotal": "19900", + "tax": "1590", + "discount": "1990", + "total": "19500" + }, + "product": { + "id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "name": "Custom domains", + "description": "Make AeroEdit truly your own with custom domains. Custom domains reinforce your brand identity and make it easy for your team to access your account.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/XIG7UXoJQHmlIAiKcnkA_custom-domains.png", + "custom_data": null, + "status": "active", + "created_at": "2023-02-23T14:01:02.441Z", + "updated_at": "2024-04-05T15:43:28.971Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "19900", + "tax": "1590", + "discount": "1990", + "total": "19500" + } + } + ] + }, + "payments": [], + "checkout": { + "url": null + } + }, + { + "id": "txn_01hv8kxg3hxyxs9t471ms9kfsz", + "status": "ready", + "customer_id": "ctm_01hv6y1jedq4p1n0yqn5ba3ky4", + "address_id": "add_01hv8gq3318ktkfengj2r75gfx", + "business_id": null, + "custom_data": null, + "origin": "api", + "collection_mode": "manual", + "subscription_id": null, + "invoice_id": "inv_01hv8kxgy9cx5mps83eqrm0v9c", + "invoice_number": null, + "billing_details": { + "enable_checkout": false, + "payment_terms": { + "interval": "day", + "frequency": 14 + }, + "purchase_order_number": "PO-123", + "additional_information": null + }, + "billing_period": { + "starts_at": "2023-08-01T00:00:00Z", + "ends_at": "2024-07-31T23:59:00Z" + }, + "currency_code": "USD", + "discount_id": null, + "created_at": "2024-04-12T07:38:54.904246Z", + "updated_at": "2024-04-12T07:38:57.079109Z", + "billed_at": null, + "items": [ + { + "price": { + "id": "pri_01gsz91wy9k1yn7kx82aafwvea", + "description": "Annual", + "type": "standard", + "name": "Annual (per seat)", + "product_id": "pro_01gsz4vmqbjk3x4vvtafffd540", + "billing_cycle": { + "interval": "year", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "50000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 100 + }, + "status": "active", + "created_at": "2023-02-23T13:57:54.249913Z", + "updated_at": "2024-04-05T14:32:00.471447Z", + "import_meta": null + }, + "quantity": 20 + }, + { + "price": { + "id": "pri_01gsz96z29d88jrmsf2ztbfgjg", + "description": "Annual (recurring addon)", + "type": "standard", + "name": "Annual (recurring addon)", + "product_id": "pro_01gsz92krfzy3hcx5h5rtgnfwz", + "billing_cycle": { + "interval": "year", + "frequency": 1 + }, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "300000", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 1 + }, + "status": "active", + "created_at": "2023-02-23T14:00:40.265185Z", + "updated_at": "2024-03-25T14:31:18.587603Z", + "import_meta": null + }, + "quantity": 1 + }, + { + "price": { + "id": "pri_01gsz98e27ak2tyhexptwc58yk", + "description": "One-time addon", + "type": "standard", + "name": "One-time addon", + "product_id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "billing_cycle": null, + "trial_period": null, + "tax_mode": "account_setting", + "unit_price": { + "amount": "19900", + "currency_code": "USD" + }, + "unit_price_overrides": [], + "custom_data": null, + "quantity": { + "minimum": 1, + "maximum": 1 + }, + "status": "active", + "created_at": "2023-02-23T14:01:28.391712Z", + "updated_at": "2024-04-09T07:23:10.921392Z", + "import_meta": null + }, + "quantity": 1 + } + ], + "details": { + "tax_rates_used": [ + { + "tax_rate": "0.08875", + "totals": { + "subtotal": "1319900", + "discount": "0", + "tax": "117141", + "total": "1437041" + } + } + ], + "totals": { + "subtotal": "1319900", + "tax": "117141", + "discount": "0", + "total": "1437041", + "grand_total": "1437041", + "fee": null, + "credit": "0", + "credit_to_balance": "0", + "balance": "1437041", + "earnings": null, + "currency_code": "USD" + }, + "adjusted_totals": { + "subtotal": "1319900", + "tax": "117141", + "total": "1437041", + "grand_total": "1437041", + "fee": "0", + "earnings": "0", + "currency_code": "USD" + }, + "payout_totals": null, + "adjusted_payout_totals": null, + "line_items": [ + { + "id": "txnitm_01hv8kxghfn69z43wr13hy15dy", + "price_id": "pri_01gsz91wy9k1yn7kx82aafwvea", + "quantity": 20, + "totals": { + "subtotal": "1000000", + "tax": "88750", + "discount": "0", + "total": "1088750" + }, + "product": { + "id": "pro_01gsz4vmqbjk3x4vvtafffd540", + "name": "AeroEdit Enterprise", + "description": "The ultimate solution for organizations, featuring all Pro capabilities plus multi-user support, advanced data storage capabilities, plus personalized onboarding, dedicated account management, and the ability to pay via invoice.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/Ws808ziTS76a6YbnMkiK_enterprise.png", + "custom_data": { + "features": { + "aircraft_performance": true, + "compliance_monitoring": true, + "flight_log_management": true, + "payment_by_invoice": true, + "route_planning": true, + "sso": true + }, + "suggested_addons": [], + "upgrade_description": "Ready to reach new heights? Upgrade to enterprise to unlock single sign-on, payment by invoice, and dedicated account management." + }, + "status": "active", + "created_at": "2023-02-23T12:44:34.923Z", + "updated_at": "2024-04-05T15:58:28.309Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "50000", + "tax": "4437", + "discount": "0", + "total": "54437" + } + }, + { + "id": "txnitm_01hv8kxghfn69z43wr164x2bmd", + "price_id": "pri_01gsz96z29d88jrmsf2ztbfgjg", + "quantity": 1, + "totals": { + "subtotal": "300000", + "tax": "26625", + "discount": "0", + "total": "326625" + }, + "product": { + "id": "pro_01gsz92krfzy3hcx5h5rtgnfwz", + "name": "VIP support", + "description": "Get exclusive access to our expert team of product specialists, available to help you make the most of your AeroEdit subscription.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/qgyipKJwRtq98YNboipo_vip-support.png", + "custom_data": null, + "status": "active", + "created_at": "2023-02-23T13:58:17.615Z", + "updated_at": "2024-04-05T15:44:02.893Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "300000", + "tax": "26625", + "discount": "0", + "total": "326625" + } + }, + { + "id": "txnitm_01hv8kxghfn69z43wr16v81qhb", + "price_id": "pri_01gsz98e27ak2tyhexptwc58yk", + "quantity": 1, + "totals": { + "subtotal": "19900", + "tax": "1766", + "discount": "0", + "total": "21666" + }, + "product": { + "id": "pro_01gsz97mq9pa4fkyy0wqenepkz", + "name": "Custom domains", + "description": "Make AeroEdit truly your own with custom domains. Custom domains reinforce your brand identity and make it easy for your team to access your account.", + "type": "standard", + "tax_category": "standard", + "image_url": "https://paddle.s3.amazonaws.com/user/165798/XIG7UXoJQHmlIAiKcnkA_custom-domains.png", + "custom_data": null, + "status": "active", + "created_at": "2023-02-23T14:01:02.441Z", + "updated_at": "2024-04-05T15:43:28.971Z", + "import_meta": null + }, + "tax_rate": "0.08875", + "unit_totals": { + "subtotal": "19900", + "tax": "1766", + "discount": "0", + "total": "21666" + } + } + ] + }, + "payments": [], + "checkout": { + "url": null + } + } + ], + "meta": { + "request_id": "913dee78-d496-4d13-a93e-09d834c208dd", + "pagination": { + "per_page": 3, + "next": "https://api.paddle.com/transactions?after=txn_01hv8kxg3hxyxs9t471ms9kfsz", + "has_more": false, + "estimated_total": 6 + } + } +} diff --git a/transactions.go b/transactions.go new file mode 100644 index 0000000..bb2f37d --- /dev/null +++ b/transactions.go @@ -0,0 +1,1153 @@ +// Code generated by the Paddle SDK Generator; DO NOT EDIT. +package paddle + +import ( + "context" + "encoding/json" + + paddleerr "github.com/PaddleHQ/paddle-go-sdk/pkg/paddleerr" +) + +// ErrTransactionImmutable represents a `transaction_immutable` error. +// See https://developer.paddle.com/errors/transactions/transaction_immutable for more information. +var ErrTransactionImmutable = &paddleerr.Error{ + Code: "transaction_immutable", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionDiscountNotEligible represents a `transaction_discount_not_eligible` error. +// See https://developer.paddle.com/errors/transactions/transaction_discount_not_eligible for more information. +var ErrTransactionDiscountNotEligible = &paddleerr.Error{ + Code: "transaction_discount_not_eligible", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionNotReadyCannotProcessPayment represents a `transaction_not_ready_cannot_process_payment` error. +// See https://developer.paddle.com/errors/transactions/transaction_not_ready_cannot_process_payment for more information. +var ErrTransactionNotReadyCannotProcessPayment = &paddleerr.Error{ + Code: "transaction_not_ready_cannot_process_payment", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionDefaultCheckoutURLNotSet represents a `transaction_default_checkout_url_not_set` error. +// See https://developer.paddle.com/errors/transactions/transaction_default_checkout_url_not_set for more information. +var ErrTransactionDefaultCheckoutURLNotSet = &paddleerr.Error{ + Code: "transaction_default_checkout_url_not_set", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionCheckoutNotEnabled represents a `transaction_checkout_not_enabled` error. +// See https://developer.paddle.com/errors/transactions/transaction_checkout_not_enabled for more information. +var ErrTransactionCheckoutNotEnabled = &paddleerr.Error{ + Code: "transaction_checkout_not_enabled", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionCustomerIsRequiredWithAddress represents a `transaction_customer_is_required_with_address` error. +// See https://developer.paddle.com/errors/transactions/transaction_customer_is_required_with_address for more information. +var ErrTransactionCustomerIsRequiredWithAddress = &paddleerr.Error{ + Code: "transaction_customer_is_required_with_address", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionCustomerIsRequiredForBusinessValidation represents a `transaction_customer_is_required_for_business_validation` error. +// See https://developer.paddle.com/errors/transactions/transaction_customer_is_required_for_business_validation for more information. +var ErrTransactionCustomerIsRequiredForBusinessValidation = &paddleerr.Error{ + Code: "transaction_customer_is_required_for_business_validation", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionPriceDifferentBillingCycle represents a `transaction_price_different_billing_cycle` error. +// See https://developer.paddle.com/errors/transactions/transaction_price_different_billing_cycle for more information. +var ErrTransactionPriceDifferentBillingCycle = &paddleerr.Error{ + Code: "transaction_price_different_billing_cycle", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionPriceDifferentTrialPeriod represents a `transaction_price_different_trial_period` error. +// See https://developer.paddle.com/errors/transactions/transaction_price_different_trial_period for more information. +var ErrTransactionPriceDifferentTrialPeriod = &paddleerr.Error{ + Code: "transaction_price_different_trial_period", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionItemQuantityOutOfRange represents a `transaction_item_quantity_out_of_range` error. +// See https://developer.paddle.com/errors/transactions/transaction_item_quantity_out_of_range for more information. +var ErrTransactionItemQuantityOutOfRange = &paddleerr.Error{ + Code: "transaction_item_quantity_out_of_range", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionBothPriceIDAndObjectFound represents a `transaction_both_price_id_and_object_found` error. +// See https://developer.paddle.com/errors/transactions/transaction_both_price_id_and_object_found for more information. +var ErrTransactionBothPriceIDAndObjectFound = &paddleerr.Error{ + Code: "transaction_both_price_id_and_object_found", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionPriceNotFound represents a `transaction_price_not_found` error. +// See https://developer.paddle.com/errors/transactions/transaction_price_not_found for more information. +var ErrTransactionPriceNotFound = &paddleerr.Error{ + Code: "transaction_price_not_found", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionProductNotFound represents a `transaction_product_not_found` error. +// See https://developer.paddle.com/errors/transactions/transaction_product_not_found for more information. +var ErrTransactionProductNotFound = &paddleerr.Error{ + Code: "transaction_product_not_found", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionCannotBeModifiedAndCanceled represents a `transaction_cannot_be_modified_and_canceled` error. +// See https://developer.paddle.com/errors/transactions/transaction_cannot_be_modified_and_canceled for more information. +var ErrTransactionCannotBeModifiedAndCanceled = &paddleerr.Error{ + Code: "transaction_cannot_be_modified_and_canceled", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionDiscountNotFound represents a `transaction_discount_not_found` error. +// See https://developer.paddle.com/errors/transactions/transaction_discount_not_found for more information. +var ErrTransactionDiscountNotFound = &paddleerr.Error{ + Code: "transaction_discount_not_found", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionCannotProvideBothDiscountCodeAndID represents a `transaction_cannot_provide_both_discount_code_and_id` error. +// See https://developer.paddle.com/errors/transactions/transaction_cannot_provide_both_discount_code_and_id for more information. +var ErrTransactionCannotProvideBothDiscountCodeAndID = &paddleerr.Error{ + Code: "transaction_cannot_provide_both_discount_code_and_id", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionInvalidStatusChange represents a `transaction_invalid_status_change` error. +// See https://developer.paddle.com/errors/transactions/transaction_invalid_status_change for more information. +var ErrTransactionInvalidStatusChange = &paddleerr.Error{ + Code: "transaction_invalid_status_change", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionBillingDetailsMustBeNull represents a `transaction_billing_details_must_be_null` error. +// See https://developer.paddle.com/errors/transactions/transaction_billing_details_must_be_null for more information. +var ErrTransactionBillingDetailsMustBeNull = &paddleerr.Error{ + Code: "transaction_billing_details_must_be_null", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionBillingDetailsObjectRequired represents a `transaction_billing_details_object_required` error. +// See https://developer.paddle.com/errors/transactions/transaction_billing_details_object_required for more information. +var ErrTransactionBillingDetailsObjectRequired = &paddleerr.Error{ + Code: "transaction_billing_details_object_required", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionPaymentTermsObjectRequired represents a `transaction_payment_terms_object_required` error. +// See https://developer.paddle.com/errors/transactions/transaction_payment_terms_object_required for more information. +var ErrTransactionPaymentTermsObjectRequired = &paddleerr.Error{ + Code: "transaction_payment_terms_object_required", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionBalanceLessThanChargeLimit represents a `transaction_balance_less_than_charge_limit` error. +// See https://developer.paddle.com/errors/transactions/transaction_balance_less_than_charge_limit for more information. +var ErrTransactionBalanceLessThanChargeLimit = &paddleerr.Error{ + Code: "transaction_balance_less_than_charge_limit", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionStatusMustBeReady represents a `transaction_status_must_be_ready` error. +// See https://developer.paddle.com/errors/transactions/transaction_status_must_be_ready for more information. +var ErrTransactionStatusMustBeReady = &paddleerr.Error{ + Code: "transaction_status_must_be_ready", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionCustomerNotSuitableForCollectionMode represents a `transaction_customer_not_suitable_for_collection_mode` error. +// See https://developer.paddle.com/errors/transactions/transaction_customer_not_suitable_for_collection_mode for more information. +var ErrTransactionCustomerNotSuitableForCollectionMode = &paddleerr.Error{ + Code: "transaction_customer_not_suitable_for_collection_mode", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionAddressNotSuitableForCollectionMode represents a `transaction_address_not_suitable_for_collection_mode` error. +// See https://developer.paddle.com/errors/transactions/transaction_address_not_suitable_for_collection_mode for more information. +var ErrTransactionAddressNotSuitableForCollectionMode = &paddleerr.Error{ + Code: "transaction_address_not_suitable_for_collection_mode", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionCurrencyCodeNotValidForManual represents a `transaction_currency_code_not_valid_for_manual` error. +// See https://developer.paddle.com/errors/transactions/transaction_currency_code_not_valid_for_manual for more information. +var ErrTransactionCurrencyCodeNotValidForManual = &paddleerr.Error{ + Code: "transaction_currency_code_not_valid_for_manual", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionPreviewAdjustmentSubscriptionConflict represents a `transaction_preview_adjustment_subscription_conflict` error. +// See https://developer.paddle.com/errors/transactions/transaction_preview_adjustment_subscription_conflict for more information. +var ErrTransactionPreviewAdjustmentSubscriptionConflict = &paddleerr.Error{ + Code: "transaction_preview_adjustment_subscription_conflict", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionInvalidDiscountCurrency represents a `transaction_invalid_discount_currency` error. +// See https://developer.paddle.com/errors/transactions/transaction_invalid_discount_currency for more information. +var ErrTransactionInvalidDiscountCurrency = &paddleerr.Error{ + Code: "transaction_invalid_discount_currency", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionBillingPeriodStartsAtGreaterThanNow represents a `transaction_billing_period_starts_at_greater_than_now` error. +// See https://developer.paddle.com/errors/transactions/transaction_billing_period_starts_at_greater_than_now for more information. +var ErrTransactionBillingPeriodStartsAtGreaterThanNow = &paddleerr.Error{ + Code: "transaction_billing_period_starts_at_greater_than_now", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionCheckoutURLDomainIsNotApproved represents a `transaction_checkout_url_domain_is_not_approved` error. +// See https://developer.paddle.com/errors/transactions/transaction_checkout_url_domain_is_not_approved for more information. +var ErrTransactionCheckoutURLDomainIsNotApproved = &paddleerr.Error{ + Code: "transaction_checkout_url_domain_is_not_approved", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionRecurringBalanceLessThanChargeLimit represents a `transaction_recurring_balance_less_than_charge_limit` error. +// See https://developer.paddle.com/errors/transactions/transaction_recurring_balance_less_than_charge_limit for more information. +var ErrTransactionRecurringBalanceLessThanChargeLimit = &paddleerr.Error{ + Code: "transaction_recurring_balance_less_than_charge_limit", + Type: paddleerr.ErrorTypeRequestError, +} + +// ErrTransactionDuplicatePriceIDs represents a `transaction_duplicate_price_ids` error. +// See https://developer.paddle.com/errors/transactions/transaction_duplicate_price_ids for more information. +var ErrTransactionDuplicatePriceIDs = &paddleerr.Error{ + Code: "transaction_duplicate_price_ids", + Type: paddleerr.ErrorTypeRequestError, +} + +// Breakdown: Breakdown of the total adjustments by adjustment action. +type Breakdown struct { + // Credit: Total amount of credit adjustments. + Credit string `json:"credit,omitempty"` + // Refund: Total amount of refund adjustments. + Refund string `json:"refund,omitempty"` + // Chargeback: Total amount of chargeback adjustments. + Chargeback string `json:"chargeback,omitempty"` +} + +// AdjustmentsTotals: Object containing totals for all adjustments on a transaction. Returned when the `include` parameter is used with the `adjustments_totals` value. +type AdjustmentsTotals struct { + // Subtotal: Total before tax. + Subtotal string `json:"subtotal,omitempty"` + // Tax: Total tax on the subtotal. + Tax string `json:"tax,omitempty"` + // Total: Total after tax. + Total string `json:"total,omitempty"` + // Fee: Total fee taken by Paddle. + Fee string `json:"fee,omitempty"` + /* + Earnings: Total earnings. This is the subtotal minus the Paddle fee. + For tax adjustments, this value is negative, which means a positive effect in the transaction earnings. + This is because the fee is originally calculated from the transaction total, so if a tax adjustment is made, + then the fee portion of it is returned. + As a result, the earnings from all the adjustments performed could be either negative, positive or zero. + */ + Earnings string `json:"earnings,omitempty"` + // Breakdown: Breakdown of the total adjustments by adjustment action. + Breakdown Breakdown `json:"breakdown,omitempty"` + // CurrencyCode: Three-letter ISO 4217 currency code used for adjustments for this transaction. + CurrencyCode string `json:"currency_code,omitempty"` +} + +// TransactionIncludes: Represents a transaction entity with included entities. +type TransactionIncludes struct { + // ID: Unique Paddle ID for this transaction entity, prefixed with `txn_`. + ID string `json:"id,omitempty"` + // Status: Status of this transaction. You may set a transaction to `billed` or `canceled`, other statuses are set automatically by Paddle. Automatically-collected transactions may return `completed` if payment is captured successfully, or `past_due` if payment failed. + Status string `json:"status,omitempty"` + // CustomerID: Paddle ID of the customer that this transaction is for, prefixed with `ctm_`. + CustomerID *string `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this transaction is for, prefixed with `add_`. + AddressID *string `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this transaction is for, prefixed with `biz_`. + BusinessID *string `json:"business_id,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Must be `USD`, `EUR`, or `GBP` if `collection_mode` is `manual`. + CurrencyCode string `json:"currency_code,omitempty"` + // Origin: Describes how this transaction was created. + Origin string `json:"origin,omitempty"` + // SubscriptionID: Paddle ID of the subscription that this transaction is for, prefixed with `sub_`. + SubscriptionID *string `json:"subscription_id,omitempty"` + // InvoiceID: Paddle ID of the invoice that this transaction is related to, prefixed with `inv_`. Used for compatibility with the Paddle Invoice API, which is now deprecated. This field is scheduled to be removed in the next version of the Paddle API. + InvoiceID *string `json:"invoice_id,omitempty"` + // InvoiceNumber: Invoice number for this transaction. Automatically generated by Paddle when you mark a transaction as `billed` where `collection_mode` is `manual`. + InvoiceNumber *string `json:"invoice_number,omitempty"` + // CollectionMode: How payment is collected for this transaction. `automatic` for checkout, `manual` for invoices. + CollectionMode string `json:"collection_mode,omitempty"` + // DiscountID: Paddle ID of the discount applied to this transaction, prefixed with `dsc_`. + DiscountID *string `json:"discount_id,omitempty"` + // BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. + BillingDetails *BillingDetails `json:"billing_details,omitempty"` + // BillingPeriod: Time period that this transaction is for. Set automatically by Paddle for subscription renewals to describe the period that charges are for. + BillingPeriod *TimePeriod `json:"billing_period,omitempty"` + // Items: List of items on this transaction. For calculated totals, use `details.line_items`. + Items []TransactionItem `json:"items,omitempty"` + // Details: Calculated totals for a transaction, including proration, discounts, tax, and currency conversion. Considered the source of truth for totals on a transaction. + Details TransactionDetails `json:"details,omitempty"` + // Payments: List of payment attempts for this transaction, including successful payments. Sorted by `created_at` in descending order, so most recent attempts are returned first. + Payments []TransactionPaymentAttempt `json:"payments,omitempty"` + // Checkout: Paddle Checkout details for this transaction. Returned for automatically-collected transactions and where `billing_details.enable_checkout` is `true` for manually-collected transactions; `null` otherwise. + Checkout *Checkout `json:"checkout,omitempty"` + // CreatedAt: RFC 3339 datetime string of when this entity was created. Set automatically by Paddle. + CreatedAt string `json:"created_at,omitempty"` + // UpdatedAt: RFC 3339 datetime string of when this entity was updated. Set automatically by Paddle. + UpdatedAt string `json:"updated_at,omitempty"` + // BilledAt: RFC 3339 datetime string of when this transaction was marked as `billed`. `null` for transactions that are not `billed` or `completed`. Set automatically by Paddle. + BilledAt *string `json:"billed_at,omitempty"` + // Address: Address for this transaction. Returned when the `include` parameter is used with the `address` value and the transaction has an `address_id`. + Address Address `json:"address,omitempty"` + // Adjustments: Represents an adjustment entity. + Adjustments []Adjustment `json:"adjustments,omitempty"` + // AdjustmentsTotals: Object containing totals for all adjustments on a transaction. Returned when the `include` parameter is used with the `adjustments_totals` value. + AdjustmentsTotals AdjustmentsTotals `json:"adjustments_totals,omitempty"` + // Business: Business for this transaction. Returned when the `include` parameter is used with the `business` value and the transaction has a `business_id`. + Business Business `json:"business,omitempty"` + // Customer: Customer for this transaction. Returned when the `include` parameter is used with the `customer` value and the transaction has a `customer_id`. + Customer Customer `json:"customer,omitempty"` + // Discount: Discount for this transaction. Returned when the `include` parameter is used with the `discount` value and the transaction has a `discount_id`. + Discount Discount `json:"discount,omitempty"` + // AvailablePaymentMethods: List of available payment methods for this transaction. Returned when the `include` parameter is used with the `available_payment_methods` value. + AvailablePaymentMethods []PaymentMethodType `json:"available_payment_methods,omitempty"` +} + +// CatalogItem: Add a catalog item to a transaction. In this case, the product and price that you're billing for exist in your product catalog in Paddle. +type CatalogItem struct { + // Quantity: Quantity of this item on the transaction. + Quantity int `json:"quantity,omitempty"` + // Proration: How proration was calculated for this item. Populated when a transaction is created from a subscription change, where `proration_billing_mode` was `prorated_immediately` or `prorated_next_billing_period`. Set automatically by Paddle. + Proration *Proration `json:"proration,omitempty"` + // PriceID: Paddle ID of an existing catalog price to add to this transaction, prefixed with `pri_`. + PriceID string `json:"price_id,omitempty"` +} + +// NonCatalogPriceForAnExistingProduct: Add a non-catalog price for an existing product in your catalog to a transaction. In this case, the product you're billing for is a catalog product, but you charge a specific price for it. +type NonCatalogPriceForAnExistingProduct struct { + // Quantity: Quantity of this item on the transaction. + Quantity int `json:"quantity,omitempty"` + // Proration: How proration was calculated for this item. Populated when a transaction is created from a subscription change, where `proration_billing_mode` was `prorated_immediately` or `prorated_next_billing_period`. Set automatically by Paddle. + Proration *Proration `json:"proration,omitempty"` + // Price: Price object for a non-catalog item to charge for. Include a `product_id` to relate this non-catalog price to an existing catalog price. + Price TransactionSubscriptionPriceCreateWithProductID `json:"price,omitempty"` +} + +// NonCatalogPriceAndProduct: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. +type NonCatalogPriceAndProduct struct { + // Quantity: Quantity of this item on the transaction. + Quantity int `json:"quantity,omitempty"` + // Proration: How proration was calculated for this item. Populated when a transaction is created from a subscription change, where `proration_billing_mode` was `prorated_immediately` or `prorated_next_billing_period`. Set automatically by Paddle. + Proration *Proration `json:"proration,omitempty"` + // Price: Price object for a non-catalog item to charge for. Include a `product` object to create a non-catalog product for this non-catalog price. + Price TransactionSubscriptionPriceCreateWithProduct `json:"price,omitempty"` +} + +// TransactionsCheckout: Paddle Checkout details for this transaction. You may pass a URL when creating or updating an automatically-collected transaction, or when creating or updating a manually-collected transaction where `billing_details.enable_checkout` is `true`. +type TransactionsCheckout struct { + /* + URL: Checkout URL to use for the payment link for this transaction. Pass the URL for an approved domain, or omit to use your default payment URL. + + Paddle returns a unique payment link composed of the URL passed or your default payment URL + `_?txn=` and the Paddle ID for this transaction. + */ + URL *string `json:"url,omitempty"` +} + +// TransactionsCatalogItem: Add a catalog item to a transaction. In this case, the product and price that you're billing for exist in your product catalog in Paddle. +type TransactionsCatalogItem struct { + // Quantity: Quantity of this item on the transaction. + Quantity int `json:"quantity,omitempty"` + // IncludeInTotals: Whether this item should be included in totals for this transaction preview. Typically used to exclude one-time charges from calculations. + IncludeInTotals bool `json:"include_in_totals,omitempty"` + // Proration: How proration was calculated for this item. `null` for transaction previews. + Proration *Proration `json:"proration,omitempty"` + // PriceID: Paddle ID of an existing catalog price to preview charging for, prefixed with `pri_`. + PriceID string `json:"price_id,omitempty"` +} + +// TransactionsNonCatalogPriceForAnExistingProduct: Add a non-catalog price for an existing product in your catalog to a transaction. In this case, the product you're billing for is a catalog product, but you charge a specific price for it. +type TransactionsNonCatalogPriceForAnExistingProduct struct { + // Quantity: Quantity of this item on the transaction. + Quantity int `json:"quantity,omitempty"` + // IncludeInTotals: Whether this item should be included in totals for this transaction preview. Typically used to exclude one-time charges from calculations. + IncludeInTotals bool `json:"include_in_totals,omitempty"` + // Proration: How proration was calculated for this item. `null` for transaction previews. + Proration *Proration `json:"proration,omitempty"` + // Price: Price object for a non-catalog item to preview charging for. Include a `product_id` to relate this non-catalog price to an existing catalog price. + Price TransactionSubscriptionPriceCreateWithProductID `json:"price,omitempty"` +} + +// TransactionsNonCatalogPriceAndProduct: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. +type TransactionsNonCatalogPriceAndProduct struct { + // Quantity: Quantity of this item on the transaction. + Quantity int `json:"quantity,omitempty"` + // IncludeInTotals: Whether this item should be included in totals for this transaction preview. Typically used to exclude one-time charges from calculations. + IncludeInTotals bool `json:"include_in_totals,omitempty"` + // Proration: How proration was calculated for this item. `null` for transaction previews. + Proration *Proration `json:"proration,omitempty"` + // Price: Price object for a non-catalog item to preview charging for. Include a `product` object to create a non-catalog product for this non-catalog price. + Price TransactionSubscriptionPriceCreateWithProduct `json:"price,omitempty"` +} + +// NewCountryAndZipPostalCodeItemsTransactionsCatalogItem takes a TransactionsCatalogItem type +// and creates a CountryAndZipPostalCodeItems for use in a request. +func NewCountryAndZipPostalCodeItemsTransactionsCatalogItem(r *TransactionsCatalogItem) *CountryAndZipPostalCodeItems { + return &CountryAndZipPostalCodeItems{TransactionsCatalogItem: r} +} + +// NewCountryAndZipPostalCodeItemsTransactionsNonCatalogPriceForAnExistingProduct takes a TransactionsNonCatalogPriceForAnExistingProduct type +// and creates a CountryAndZipPostalCodeItems for use in a request. +func NewCountryAndZipPostalCodeItemsTransactionsNonCatalogPriceForAnExistingProduct(r *TransactionsNonCatalogPriceForAnExistingProduct) *CountryAndZipPostalCodeItems { + return &CountryAndZipPostalCodeItems{TransactionsNonCatalogPriceForAnExistingProduct: r} +} + +// NewCountryAndZipPostalCodeItemsTransactionsNonCatalogPriceAndProduct takes a TransactionsNonCatalogPriceAndProduct type +// and creates a CountryAndZipPostalCodeItems for use in a request. +func NewCountryAndZipPostalCodeItemsTransactionsNonCatalogPriceAndProduct(r *TransactionsNonCatalogPriceAndProduct) *CountryAndZipPostalCodeItems { + return &CountryAndZipPostalCodeItems{TransactionsNonCatalogPriceAndProduct: r} +} + +// CountryAndZipPostalCodeItems represents a union request type of the following types: +// - `TransactionsCatalogItem` +// - `TransactionsNonCatalogPriceForAnExistingProduct` +// - `TransactionsNonCatalogPriceAndProduct` +// +// The following constructor functions can be used to create a new instance of this type. +// - `NewCountryAndZipPostalCodeItemsTransactionsCatalogItem()` +// - `NewCountryAndZipPostalCodeItemsTransactionsNonCatalogPriceForAnExistingProduct()` +// - `NewCountryAndZipPostalCodeItemsTransactionsNonCatalogPriceAndProduct()` +// +// Only one of the values can be set at a time, the first non-nil value will be used in the request. +// Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. +type CountryAndZipPostalCodeItems struct { + *TransactionsCatalogItem + *TransactionsNonCatalogPriceForAnExistingProduct + *TransactionsNonCatalogPriceAndProduct +} + +// MarshalJSON implements the json.Marshaler interface. +func (u CountryAndZipPostalCodeItems) MarshalJSON() ([]byte, error) { + if u.TransactionsCatalogItem != nil { + return json.Marshal(u.TransactionsCatalogItem) + } + + if u.TransactionsNonCatalogPriceForAnExistingProduct != nil { + return json.Marshal(u.TransactionsNonCatalogPriceForAnExistingProduct) + } + + if u.TransactionsNonCatalogPriceAndProduct != nil { + return json.Marshal(u.TransactionsNonCatalogPriceAndProduct) + } + + return nil, nil +} + +// CountryAndZipPostalCode: Paddle uses the country and ZIP code (where supplied) to calculate totals. +type CountryAndZipPostalCode struct { + // Address: Address for this transaction preview. + Address AddressPreview `json:"address,omitempty"` + // CustomerID: Paddle ID of the customer that this transaction preview is for, prefixed with `ctm_`. + CustomerID *string `json:"customer_id,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. + CurrencyCode string `json:"currency_code,omitempty"` + // DiscountID: Paddle ID of the discount applied to this transaction preview, prefixed with `dsc_`. + DiscountID *string `json:"discount_id,omitempty"` + /* + IgnoreTrials: Whether trials should be ignored for transaction preview calculations. + + By default, recurring items with trials are considered to have a zero charge when previewing. Set to `true` to disable this. + */ + IgnoreTrials bool `json:"ignore_trials,omitempty"` + // Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. + Items []CountryAndZipPostalCodeItems `json:"items,omitempty"` +} + +// NewIPAddressItemsTransactionsCatalogItem takes a TransactionsCatalogItem type +// and creates a IPAddressItems for use in a request. +func NewIPAddressItemsTransactionsCatalogItem(r *TransactionsCatalogItem) *IPAddressItems { + return &IPAddressItems{TransactionsCatalogItem: r} +} + +// NewIPAddressItemsTransactionsNonCatalogPriceForAnExistingProduct takes a TransactionsNonCatalogPriceForAnExistingProduct type +// and creates a IPAddressItems for use in a request. +func NewIPAddressItemsTransactionsNonCatalogPriceForAnExistingProduct(r *TransactionsNonCatalogPriceForAnExistingProduct) *IPAddressItems { + return &IPAddressItems{TransactionsNonCatalogPriceForAnExistingProduct: r} +} + +// NewIPAddressItemsTransactionsNonCatalogPriceAndProduct takes a TransactionsNonCatalogPriceAndProduct type +// and creates a IPAddressItems for use in a request. +func NewIPAddressItemsTransactionsNonCatalogPriceAndProduct(r *TransactionsNonCatalogPriceAndProduct) *IPAddressItems { + return &IPAddressItems{TransactionsNonCatalogPriceAndProduct: r} +} + +// IPAddressItems represents a union request type of the following types: +// - `TransactionsCatalogItem` +// - `TransactionsNonCatalogPriceForAnExistingProduct` +// - `TransactionsNonCatalogPriceAndProduct` +// +// The following constructor functions can be used to create a new instance of this type. +// - `NewIPAddressItemsTransactionsCatalogItem()` +// - `NewIPAddressItemsTransactionsNonCatalogPriceForAnExistingProduct()` +// - `NewIPAddressItemsTransactionsNonCatalogPriceAndProduct()` +// +// Only one of the values can be set at a time, the first non-nil value will be used in the request. +// Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. +type IPAddressItems struct { + *TransactionsCatalogItem + *TransactionsNonCatalogPriceForAnExistingProduct + *TransactionsNonCatalogPriceAndProduct +} + +// MarshalJSON implements the json.Marshaler interface. +func (u IPAddressItems) MarshalJSON() ([]byte, error) { + if u.TransactionsCatalogItem != nil { + return json.Marshal(u.TransactionsCatalogItem) + } + + if u.TransactionsNonCatalogPriceForAnExistingProduct != nil { + return json.Marshal(u.TransactionsNonCatalogPriceForAnExistingProduct) + } + + if u.TransactionsNonCatalogPriceAndProduct != nil { + return json.Marshal(u.TransactionsNonCatalogPriceAndProduct) + } + + return nil, nil +} + +// IPAddress: Paddle fetches location using the IP address to calculate totals. +type IPAddress struct { + // CustomerIPAddress: IP address for this transaction preview. + CustomerIPAddress string `json:"customer_ip_address,omitempty"` + // CustomerID: Paddle ID of the customer that this transaction preview is for, prefixed with `ctm_`. + CustomerID *string `json:"customer_id,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. + CurrencyCode string `json:"currency_code,omitempty"` + // DiscountID: Paddle ID of the discount applied to this transaction preview, prefixed with `dsc_`. + DiscountID *string `json:"discount_id,omitempty"` + /* + IgnoreTrials: Whether trials should be ignored for transaction preview calculations. + + By default, recurring items with trials are considered to have a zero charge when previewing. Set to `true` to disable this. + */ + IgnoreTrials bool `json:"ignore_trials,omitempty"` + // Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. + Items []IPAddressItems `json:"items,omitempty"` +} + +// NewExistingCustomerPaddleIDsItemsTransactionsCatalogItem takes a TransactionsCatalogItem type +// and creates a ExistingCustomerPaddleIDsItems for use in a request. +func NewExistingCustomerPaddleIDsItemsTransactionsCatalogItem(r *TransactionsCatalogItem) *ExistingCustomerPaddleIDsItems { + return &ExistingCustomerPaddleIDsItems{TransactionsCatalogItem: r} +} + +// NewExistingCustomerPaddleIDsItemsTransactionsNonCatalogPriceForAnExistingProduct takes a TransactionsNonCatalogPriceForAnExistingProduct type +// and creates a ExistingCustomerPaddleIDsItems for use in a request. +func NewExistingCustomerPaddleIDsItemsTransactionsNonCatalogPriceForAnExistingProduct(r *TransactionsNonCatalogPriceForAnExistingProduct) *ExistingCustomerPaddleIDsItems { + return &ExistingCustomerPaddleIDsItems{TransactionsNonCatalogPriceForAnExistingProduct: r} +} + +// NewExistingCustomerPaddleIDsItemsTransactionsNonCatalogPriceAndProduct takes a TransactionsNonCatalogPriceAndProduct type +// and creates a ExistingCustomerPaddleIDsItems for use in a request. +func NewExistingCustomerPaddleIDsItemsTransactionsNonCatalogPriceAndProduct(r *TransactionsNonCatalogPriceAndProduct) *ExistingCustomerPaddleIDsItems { + return &ExistingCustomerPaddleIDsItems{TransactionsNonCatalogPriceAndProduct: r} +} + +// ExistingCustomerPaddleIDsItems represents a union request type of the following types: +// - `TransactionsCatalogItem` +// - `TransactionsNonCatalogPriceForAnExistingProduct` +// - `TransactionsNonCatalogPriceAndProduct` +// +// The following constructor functions can be used to create a new instance of this type. +// - `NewExistingCustomerPaddleIDsItemsTransactionsCatalogItem()` +// - `NewExistingCustomerPaddleIDsItemsTransactionsNonCatalogPriceForAnExistingProduct()` +// - `NewExistingCustomerPaddleIDsItemsTransactionsNonCatalogPriceAndProduct()` +// +// Only one of the values can be set at a time, the first non-nil value will be used in the request. +// Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. +type ExistingCustomerPaddleIDsItems struct { + *TransactionsCatalogItem + *TransactionsNonCatalogPriceForAnExistingProduct + *TransactionsNonCatalogPriceAndProduct +} + +// MarshalJSON implements the json.Marshaler interface. +func (u ExistingCustomerPaddleIDsItems) MarshalJSON() ([]byte, error) { + if u.TransactionsCatalogItem != nil { + return json.Marshal(u.TransactionsCatalogItem) + } + + if u.TransactionsNonCatalogPriceForAnExistingProduct != nil { + return json.Marshal(u.TransactionsNonCatalogPriceForAnExistingProduct) + } + + if u.TransactionsNonCatalogPriceAndProduct != nil { + return json.Marshal(u.TransactionsNonCatalogPriceAndProduct) + } + + return nil, nil +} + +// ExistingCustomerPaddleIDs: Paddle uses existing customer data to calculate totals. Typically used for logged-in customers. +type ExistingCustomerPaddleIDs struct { + // AddressID: Paddle ID of the address that this transaction preview is for, prefixed with `add_`. Requires `customer_id`. + AddressID string `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this transaction preview is for, prefixed with `biz_`. + BusinessID *string `json:"business_id,omitempty"` + // CustomerID: Paddle ID of the customer that this transaction preview is for, prefixed with `ctm_`. + CustomerID *string `json:"customer_id,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. + CurrencyCode string `json:"currency_code,omitempty"` + // DiscountID: Paddle ID of the discount applied to this transaction preview, prefixed with `dsc_`. + DiscountID *string `json:"discount_id,omitempty"` + /* + IgnoreTrials: Whether trials should be ignored for transaction preview calculations. + + By default, recurring items with trials are considered to have a zero charge when previewing. Set to `true` to disable this. + */ + IgnoreTrials bool `json:"ignore_trials,omitempty"` + // Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. + Items []ExistingCustomerPaddleIDsItems `json:"items,omitempty"` +} + +// TransactionItemPreview: List of items to preview transaction calculations for. +type TransactionItemPreview struct { + // Quantity: Quantity of this item on the transaction. + Quantity int `json:"quantity,omitempty"` + // IncludeInTotals: Whether this item should be included in totals for this transaction preview. Typically used to exclude one-time charges from calculations. + IncludeInTotals bool `json:"include_in_totals,omitempty"` + // Proration: How proration was calculated for this item. `null` for transaction previews. + Proration *Proration `json:"proration,omitempty"` + // Price: Represents a price entity. + Price Price `json:"price,omitempty"` +} + +// TransactionsTaxRatesUsed: List of tax rates applied to this transaction preview. +type TransactionsTaxRatesUsed struct { + // TaxRate: Rate used to calculate tax for this transaction preview. + TaxRate string `json:"tax_rate,omitempty"` + // Totals: Calculated totals for the tax applied to this transaction preview. + Totals Totals `json:"totals,omitempty"` +} + +// TransactionDetailsPreview: Calculated totals for a transaction preview, including discounts, tax, and currency conversion. Considered the source of truth for totals on a transaction preview. +type TransactionDetailsPreview struct { + // TaxRatesUsed: List of tax rates applied to this transaction preview. + TaxRatesUsed []TransactionsTaxRatesUsed `json:"tax_rates_used,omitempty"` + // Totals: Breakdown of the total for a transaction preview. `fee` and `earnings` always return `null` for transaction previews. + Totals TransactionTotals `json:"totals,omitempty"` + // LineItems: Information about line items for this transaction preview. Different from transaction preview `items` as they include totals calculated by Paddle. Considered the source of truth for line item totals. + LineItems []TransactionLineItemPreview `json:"line_items,omitempty"` +} + +// TransactionPreview: Represents a transaction entity when previewing transactions. +type TransactionPreview struct { + // CustomerID: Paddle ID of the customer that this transaction preview is for, prefixed with `ctm_`. + CustomerID *string `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this transaction preview is for, prefixed with `add_`. Send one of `address_id`, `customer_ip_address`, or the `address` object when previewing. + AddressID *string `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this transaction preview is for, prefixed with `biz_`. + BusinessID *string `json:"business_id,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. + CurrencyCode string `json:"currency_code,omitempty"` + // DiscountID: Paddle ID of the discount applied to this transaction preview, prefixed with `dsc_`. + DiscountID *string `json:"discount_id,omitempty"` + // CustomerIPAddress: IP address for this transaction preview. Send one of `address_id`, `customer_ip_address`, or the `address` object when previewing. + CustomerIPAddress *string `json:"customer_ip_address,omitempty"` + // Address: Address for this transaction preview. Send one of `address_id`, `customer_ip_address`, or the `address` object when previewing. + Address *AddressPreview `json:"address,omitempty"` + /* + IgnoreTrials: Whether trials should be ignored for transaction preview calculations. + + By default, recurring items with trials are considered to have a zero charge when previewing. Set to `true` to disable this. + */ + IgnoreTrials bool `json:"ignore_trials,omitempty"` + // Items: List of items to preview transaction calculations for. + Items []TransactionItemPreview `json:"items,omitempty"` + // Details: Calculated totals for a transaction preview, including discounts, tax, and currency conversion. Considered the source of truth for totals on a transaction preview. + Details TransactionDetailsPreview `json:"details,omitempty"` + // AvailablePaymentMethods: List of available payment methods for Paddle Checkout given the price and location information passed. + AvailablePaymentMethods []PaymentMethodType `json:"available_payment_methods,omitempty"` +} + +// TransactionsTransactionsCheckout: Paddle Checkout details for this transaction. You may pass a URL when creating or updating an automatically-collected transaction, or when creating or updating a manually-collected transaction where `billing_details.enable_checkout` is `true`. +type TransactionsTransactionsCheckout struct { + /* + URL: Checkout URL to use for the payment link for this transaction. Pass the URL for an approved domain, or `null` to set to your default payment URL. + + Paddle returns a unique payment link composed of the URL passed or your default payment URL + `_?txn=` and the Paddle ID for this transaction. + */ + URL *string `json:"url,omitempty"` +} + +type TransactionInvoicePDF struct { + // URL: URL of the requested resource. + URL string `json:"url,omitempty"` +} + +// TransactionsClient is a client for the Transactions resource. +type TransactionsClient struct { + doer Doer +} + +// ListTransactionsRequest is given as an input to ListTransactions. +type ListTransactionsRequest struct { + // After is a query parameter. + // Return entities after the specified Paddle ID when working with paginated endpoints. Used in the `meta.pagination.next` URL in responses for list operations. + After *string `in:"query=after,omitempty" json:"-"` + // BilledAt is a query parameter. + // Return entities billed at a specific time. Pass an RFC 3339 datetime string, or use `[LT]` (less than), `[LTE]` (less than or equal to), `[GT]` (greater than), or `[GTE]` (greater than or equal to) operators. For example, `billed_at=2023-04-18T17:03:26` or `billed_at[LT]=2023-04-18T17:03:26`. + BilledAt *string `in:"query=billed_at,omitempty" json:"-"` + // CollectionMode is a query parameter. + // Return entities that match the specified collection mode. + CollectionMode *string `in:"query=collection_mode,omitempty" json:"-"` + // CreatedAt is a query parameter. + // Return entities created at a specific time. Pass an RFC 3339 datetime string, or use `[LT]` (less than), `[LTE]` (less than or equal to), `[GT]` (greater than), or `[GTE]` (greater than or equal to) operators. For example, `created_at=2023-04-18T17:03:26` or `created_at[LT]=2023-04-18T17:03:26`. + CreatedAt *string `in:"query=created_at,omitempty" json:"-"` + // CustomerID is a query parameter. + // Return entities related to the specified customer. Use a comma-separated list to specify multiple customer IDs. + CustomerID []string `in:"query=customer_id,omitempty" json:"-"` + // ID is a query parameter. + // Return only the IDs specified. Use a comma-separated list to get multiple entities. + ID []string `in:"query=id,omitempty" json:"-"` + // InvoiceNumber is a query parameter. + // Return entities that match the invoice number. Use a comma-separated list to specify multiple invoice numbers. + InvoiceNumber []string `in:"query=invoice_number,omitempty" json:"-"` + // Origin is a query parameter. + // Return entities related to the specified origin. Use a comma-separated list to specify multiple origins. + Origin []string `in:"query=origin,omitempty" json:"-"` + // OrderBy is a query parameter. + /* + Order returned entities by the specified field and direction (`[ASC]` or `[DESC]`). For example, `?order_by=id[ASC]`. + + Valid fields for ordering: `billed_at`, `created_at`, `id`, and `updated_at`. + */ + OrderBy *string `in:"query=order_by,omitempty" json:"-"` + // Status is a query parameter. + // Return entities that match the specified status. Use a comma-separated list to specify multiple status values. + Status []string `in:"query=status,omitempty" json:"-"` + // SubscriptionID is a query parameter. + // Return entities related to the specified subscription. Use a comma-separated list to specify multiple subscription IDs. Pass `null` to return entities that are not related to any subscription. + SubscriptionID []string `in:"query=subscription_id,omitempty" json:"-"` + // PerPage is a query parameter. + // Set how many entities are returned per page. + PerPage *int `in:"query=per_page,omitempty" json:"-"` + // UpdatedAt is a query parameter. + // Return entities updated at a specific time. Pass an RFC 3339 datetime string, or use `[LT]` (less than), `[LTE]` (less than or equal to), `[GT]` (greater than), or `[GTE]` (greater than or equal to) operators. For example, `updated_at=2023-04-18T17:03:26` or `updated_at[LT]=2023-04-18T17:03:26`. + UpdatedAt *string `in:"query=updated_at,omitempty" json:"-"` + + // IncludeAddress allows requesting the address sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAddress bool `in:"paddle_include=address" json:"-"` + + // IncludeAdjustments allows requesting the adjustment sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAdjustments bool `in:"paddle_include=adjustment" json:"-"` + + // IncludeAdjustmentsTotals allows requesting the adjustments_totals sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAdjustmentsTotals bool `in:"paddle_include=adjustments_totals" json:"-"` + + // IncludeBusiness allows requesting the business sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeBusiness bool `in:"paddle_include=business" json:"-"` + + // IncludeCustomer allows requesting the customer sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeCustomer bool `in:"paddle_include=customer" json:"-"` + + // IncludeDiscount allows requesting the discount sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeDiscount bool `in:"paddle_include=discount" json:"-"` + + // IncludeAvailablePaymentMethods allows requesting the available_payment_methods sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAvailablePaymentMethods bool `in:"paddle_include=available_payment_methods" json:"-"` +} + +// ListTransactions performs the GET operation on a Transactions resource. +func (c *TransactionsClient) ListTransactions(ctx context.Context, req *ListTransactionsRequest) (res *Collection[*TransactionIncludes], err error) { + if err := c.doer.Do(ctx, "GET", "/transactions", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// NewCreateTransactionItemsCatalogItem takes a CatalogItem type +// and creates a CreateTransactionItems for use in a request. +func NewCreateTransactionItemsCatalogItem(r *CatalogItem) *CreateTransactionItems { + return &CreateTransactionItems{CatalogItem: r} +} + +// NewCreateTransactionItemsNonCatalogPriceForAnExistingProduct takes a NonCatalogPriceForAnExistingProduct type +// and creates a CreateTransactionItems for use in a request. +func NewCreateTransactionItemsNonCatalogPriceForAnExistingProduct(r *NonCatalogPriceForAnExistingProduct) *CreateTransactionItems { + return &CreateTransactionItems{NonCatalogPriceForAnExistingProduct: r} +} + +// NewCreateTransactionItemsNonCatalogPriceAndProduct takes a NonCatalogPriceAndProduct type +// and creates a CreateTransactionItems for use in a request. +func NewCreateTransactionItemsNonCatalogPriceAndProduct(r *NonCatalogPriceAndProduct) *CreateTransactionItems { + return &CreateTransactionItems{NonCatalogPriceAndProduct: r} +} + +// CreateTransactionItems represents a union request type of the following types: +// - `CatalogItem` +// - `NonCatalogPriceForAnExistingProduct` +// - `NonCatalogPriceAndProduct` +// +// The following constructor functions can be used to create a new instance of this type. +// - `NewCreateTransactionItemsCatalogItem()` +// - `NewCreateTransactionItemsNonCatalogPriceForAnExistingProduct()` +// - `NewCreateTransactionItemsNonCatalogPriceAndProduct()` +// +// Only one of the values can be set at a time, the first non-nil value will be used in the request. +// Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. +type CreateTransactionItems struct { + *CatalogItem + *NonCatalogPriceForAnExistingProduct + *NonCatalogPriceAndProduct +} + +// MarshalJSON implements the json.Marshaler interface. +func (u CreateTransactionItems) MarshalJSON() ([]byte, error) { + if u.CatalogItem != nil { + return json.Marshal(u.CatalogItem) + } + + if u.NonCatalogPriceForAnExistingProduct != nil { + return json.Marshal(u.NonCatalogPriceForAnExistingProduct) + } + + if u.NonCatalogPriceAndProduct != nil { + return json.Marshal(u.NonCatalogPriceAndProduct) + } + + return nil, nil +} + +// CreateTransactionRequest is given as an input to CreateTransaction. +type CreateTransactionRequest struct { + + // Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. + Items []CreateTransactionItems `json:"items,omitempty"` + /* + Status: Status of this transaction. You may set a transaction to `billed` when creating, + or omit to let Paddle set the status. Transactions are created as `ready` if they have + an `address_id`, `customer_id`, and `items`, otherwise they are created as `draft`. + + Marking as `billed` when creating is typically used when working with manually-collected + transactions as part of an invoicing workflow. Billed transactions cannot be updated, only canceled. + */ + Status *string `json:"status,omitempty"` + // CustomerID: Paddle ID of the customer that this transaction is for, prefixed with `ctm_`. + CustomerID *string `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this transaction is for, prefixed with `add_`. + AddressID *string `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this transaction is for, prefixed with `biz_`. + BusinessID *string `json:"business_id,omitempty"` + // CustomData: Your own structured key-value data. + CustomData CustomData `json:"custom_data,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Must be `USD`, `EUR`, or `GBP` if `collection_mode` is `manual`. + CurrencyCode *string `json:"currency_code,omitempty"` + // CollectionMode: How payment is collected for this transaction. `automatic` for checkout, `manual` for invoices. + CollectionMode *string `json:"collection_mode,omitempty"` + // DiscountID: Paddle ID of the discount applied to this transaction, prefixed with `dsc_`. + DiscountID *string `json:"discount_id,omitempty"` + // BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. + BillingDetails *BillingDetails `json:"billing_details,omitempty"` + // BillingPeriod: Time period that this transaction is for. Set automatically by Paddle for subscription renewals to describe the period that charges are for. + BillingPeriod *TimePeriod `json:"billing_period,omitempty"` + // Checkout: Paddle Checkout details for this transaction. You may pass a URL when creating or updating an automatically-collected transaction, or when creating or updating a manually-collected transaction where `billing_details.enable_checkout` is `true`. + Checkout *TransactionsCheckout `json:"checkout,omitempty"` + + // IncludeAddress allows requesting the address sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAddress bool `in:"paddle_include=address" json:"-"` + + // IncludeAdjustments allows requesting the adjustment sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAdjustments bool `in:"paddle_include=adjustment" json:"-"` + + // IncludeAdjustmentsTotals allows requesting the adjustments_totals sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAdjustmentsTotals bool `in:"paddle_include=adjustments_totals" json:"-"` + + // IncludeBusiness allows requesting the business sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeBusiness bool `in:"paddle_include=business" json:"-"` + + // IncludeCustomer allows requesting the customer sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeCustomer bool `in:"paddle_include=customer" json:"-"` + + // IncludeDiscount allows requesting the discount sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeDiscount bool `in:"paddle_include=discount" json:"-"` + + // IncludeAvailablePaymentMethods allows requesting the available_payment_methods sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAvailablePaymentMethods bool `in:"paddle_include=available_payment_methods" json:"-"` +} + +// CreateTransaction performs the POST operation on a Transactions resource. +func (c *TransactionsClient) CreateTransaction(ctx context.Context, req *CreateTransactionRequest) (res *TransactionIncludes, err error) { + if err := c.doer.Do(ctx, "POST", "/transactions", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// NewPreviewTransactionRequestCountryAndZipPostalCode takes a CountryAndZipPostalCode type +// and creates a PreviewTransactionRequest for use in a request. +func NewPreviewTransactionRequestCountryAndZipPostalCode(r *CountryAndZipPostalCode) *PreviewTransactionRequest { + return &PreviewTransactionRequest{CountryAndZipPostalCode: r} +} + +// NewPreviewTransactionRequestIPAddress takes a IPAddress type +// and creates a PreviewTransactionRequest for use in a request. +func NewPreviewTransactionRequestIPAddress(r *IPAddress) *PreviewTransactionRequest { + return &PreviewTransactionRequest{IPAddress: r} +} + +// NewPreviewTransactionRequestExistingCustomerPaddleIDs takes a ExistingCustomerPaddleIDs type +// and creates a PreviewTransactionRequest for use in a request. +func NewPreviewTransactionRequestExistingCustomerPaddleIDs(r *ExistingCustomerPaddleIDs) *PreviewTransactionRequest { + return &PreviewTransactionRequest{ExistingCustomerPaddleIDs: r} +} + +// PreviewTransactionRequest represents a union request type of the following types: +// - `CountryAndZipPostalCode` +// - `IPAddress` +// - `ExistingCustomerPaddleIDs` +// +// The following constructor functions can be used to create a new instance of this type. +// - `NewPreviewTransactionRequestCountryAndZipPostalCode()` +// - `NewPreviewTransactionRequestIPAddress()` +// - `NewPreviewTransactionRequestExistingCustomerPaddleIDs()` +// +// Only one of the values can be set at a time, the first non-nil value will be used in the request. +type PreviewTransactionRequest struct { + *CountryAndZipPostalCode + *IPAddress + *ExistingCustomerPaddleIDs +} + +// PreviewTransaction performs the POST operation on a Transactions resource. +func (c *TransactionsClient) PreviewTransaction(ctx context.Context, req *PreviewTransactionRequest) (res *TransactionPreview, err error) { + if err := c.doer.Do(ctx, "POST", "/transactions/preview", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (u PreviewTransactionRequest) MarshalJSON() ([]byte, error) { + if u.CountryAndZipPostalCode != nil { + return json.Marshal(u.CountryAndZipPostalCode) + } + + if u.IPAddress != nil { + return json.Marshal(u.IPAddress) + } + + if u.ExistingCustomerPaddleIDs != nil { + return json.Marshal(u.ExistingCustomerPaddleIDs) + } + + return nil, nil +} + +// GetTransactionRequest is given as an input to GetTransaction. +type GetTransactionRequest struct { + // URL path parameters. + TransactionID string `in:"path=transaction_id" json:"-"` + + // IncludeAddress allows requesting the address sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAddress bool `in:"paddle_include=address" json:"-"` + + // IncludeAdjustments allows requesting the adjustment sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAdjustments bool `in:"paddle_include=adjustment" json:"-"` + + // IncludeAdjustmentsTotals allows requesting the adjustments_totals sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAdjustmentsTotals bool `in:"paddle_include=adjustments_totals" json:"-"` + + // IncludeBusiness allows requesting the business sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeBusiness bool `in:"paddle_include=business" json:"-"` + + // IncludeCustomer allows requesting the customer sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeCustomer bool `in:"paddle_include=customer" json:"-"` + + // IncludeDiscount allows requesting the discount sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeDiscount bool `in:"paddle_include=discount" json:"-"` + + // IncludeAvailablePaymentMethods allows requesting the available_payment_methods sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAvailablePaymentMethods bool `in:"paddle_include=available_payment_methods" json:"-"` +} + +// GetTransaction performs the GET operation on a Transactions resource. +func (c *TransactionsClient) GetTransaction(ctx context.Context, req *GetTransactionRequest) (res *TransactionIncludes, err error) { + if err := c.doer.Do(ctx, "GET", "/transactions/{transaction_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// NewUpdateTransactionItemsCatalogItem takes a CatalogItem type +// and creates a UpdateTransactionItems for use in a request. +func NewUpdateTransactionItemsCatalogItem(r *CatalogItem) *UpdateTransactionItems { + return &UpdateTransactionItems{CatalogItem: r} +} + +// NewUpdateTransactionItemsNonCatalogPriceForAnExistingProduct takes a NonCatalogPriceForAnExistingProduct type +// and creates a UpdateTransactionItems for use in a request. +func NewUpdateTransactionItemsNonCatalogPriceForAnExistingProduct(r *NonCatalogPriceForAnExistingProduct) *UpdateTransactionItems { + return &UpdateTransactionItems{NonCatalogPriceForAnExistingProduct: r} +} + +// NewUpdateTransactionItemsNonCatalogPriceAndProduct takes a NonCatalogPriceAndProduct type +// and creates a UpdateTransactionItems for use in a request. +func NewUpdateTransactionItemsNonCatalogPriceAndProduct(r *NonCatalogPriceAndProduct) *UpdateTransactionItems { + return &UpdateTransactionItems{NonCatalogPriceAndProduct: r} +} + +// UpdateTransactionItems represents a union request type of the following types: +// - `CatalogItem` +// - `NonCatalogPriceForAnExistingProduct` +// - `NonCatalogPriceAndProduct` +// +// The following constructor functions can be used to create a new instance of this type. +// - `NewUpdateTransactionItemsCatalogItem()` +// - `NewUpdateTransactionItemsNonCatalogPriceForAnExistingProduct()` +// - `NewUpdateTransactionItemsNonCatalogPriceAndProduct()` +// +// Only one of the values can be set at a time, the first non-nil value will be used in the request. +// Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. +type UpdateTransactionItems struct { + *CatalogItem + *NonCatalogPriceForAnExistingProduct + *NonCatalogPriceAndProduct +} + +// MarshalJSON implements the json.Marshaler interface. +func (u UpdateTransactionItems) MarshalJSON() ([]byte, error) { + if u.CatalogItem != nil { + return json.Marshal(u.CatalogItem) + } + + if u.NonCatalogPriceForAnExistingProduct != nil { + return json.Marshal(u.NonCatalogPriceForAnExistingProduct) + } + + if u.NonCatalogPriceAndProduct != nil { + return json.Marshal(u.NonCatalogPriceAndProduct) + } + + return nil, nil +} + +// UpdateTransactionRequest is given as an input to UpdateTransaction. +type UpdateTransactionRequest struct { + // URL path parameters. + TransactionID string `in:"path=transaction_id" json:"-"` + + /* + Status: Status of this transaction. You may set a transaction to `billed` or `canceled`. Billed transactions cannot be changed. + + For manually-collected transactions, marking as `billed` is essentially issuing an invoice. + */ + Status *PatchField[string] `json:"status,omitempty"` + // CustomerID: Paddle ID of the customer that this transaction is for, prefixed with `ctm_`. + CustomerID *PatchField[*string] `json:"customer_id,omitempty"` + // AddressID: Paddle ID of the address that this transaction is for, prefixed with `add_`. + AddressID *PatchField[*string] `json:"address_id,omitempty"` + // BusinessID: Paddle ID of the business that this transaction is for, prefixed with `biz_`. + BusinessID *PatchField[*string] `json:"business_id,omitempty"` + // CustomData: Your own structured key-value data. + CustomData *PatchField[CustomData] `json:"custom_data,omitempty"` + // CurrencyCode: Supported three-letter ISO 4217 currency code. Must be `USD`, `EUR`, or `GBP` if `collection_mode` is `manual`. + CurrencyCode *PatchField[string] `json:"currency_code,omitempty"` + // CollectionMode: How payment is collected for this transaction. `automatic` for checkout, `manual` for invoices. + CollectionMode *PatchField[string] `json:"collection_mode,omitempty"` + // DiscountID: Paddle ID of the discount applied to this transaction, prefixed with `dsc_`. + DiscountID *PatchField[*string] `json:"discount_id,omitempty"` + // BillingDetails: Details for invoicing. Required if `collection_mode` is `manual`. + BillingDetails *PatchField[*BillingDetailsUpdate] `json:"billing_details,omitempty"` + // BillingPeriod: Time period that this transaction is for. Set automatically by Paddle for subscription renewals to describe the period that charges are for. + BillingPeriod *PatchField[*TimePeriod] `json:"billing_period,omitempty"` + // Items: Add a non-catalog price for a non-catalog product in your catalog to a transaction. In this case, the product and price that you're billing for are specific to this transaction. + Items *PatchField[[]UpdateTransactionItems] `json:"items,omitempty"` + // Checkout: Paddle Checkout details for this transaction. You may pass a URL when creating or updating an automatically-collected transaction, or when creating or updating a manually-collected transaction where `billing_details.enable_checkout` is `true`. + Checkout *PatchField[*TransactionsTransactionsCheckout] `json:"checkout,omitempty"` + + // IncludeAddress allows requesting the address sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAddress bool `in:"paddle_include=address" json:"-"` + + // IncludeAdjustments allows requesting the adjustment sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAdjustments bool `in:"paddle_include=adjustment" json:"-"` + + // IncludeAdjustmentsTotals allows requesting the adjustments_totals sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAdjustmentsTotals bool `in:"paddle_include=adjustments_totals" json:"-"` + + // IncludeBusiness allows requesting the business sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeBusiness bool `in:"paddle_include=business" json:"-"` + + // IncludeCustomer allows requesting the customer sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeCustomer bool `in:"paddle_include=customer" json:"-"` + + // IncludeDiscount allows requesting the discount sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeDiscount bool `in:"paddle_include=discount" json:"-"` + + // IncludeAvailablePaymentMethods allows requesting the available_payment_methods sub-resource as part of this request. + // If set to true, will be included on the response. + IncludeAvailablePaymentMethods bool `in:"paddle_include=available_payment_methods" json:"-"` +} + +// UpdateTransaction performs the PATCH operation on a Transactions resource. +func (c *TransactionsClient) UpdateTransaction(ctx context.Context, req *UpdateTransactionRequest) (res *TransactionIncludes, err error) { + if err := c.doer.Do(ctx, "PATCH", "/transactions/{transaction_id}", req, &res); err != nil { + return nil, err + } + + return res, nil +} + +// GetTransactionInvoiceRequest is given as an input to GetTransactionInvoice. +type GetTransactionInvoiceRequest struct { + // URL path parameters. + TransactionID string `in:"path=transaction_id" json:"-"` +} + +// GetTransactionInvoice performs the GET operation on a Transactions resource. +func (c *TransactionsClient) GetTransactionInvoice(ctx context.Context, req *GetTransactionInvoiceRequest) (res *TransactionInvoicePDF, err error) { + if err := c.doer.Do(ctx, "GET", "/transactions/{transaction_id}/invoice", req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/webhook_verifier.go b/webhook_verifier.go new file mode 100644 index 0000000..dee08a2 --- /dev/null +++ b/webhook_verifier.go @@ -0,0 +1,96 @@ +package paddle + +import ( + "bytes" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "errors" + "io" + "net/http" + "regexp" +) + +var ( + // ErrMissingSignature is returned when the signature is missing. + ErrMissingSignature = errors.New("missing signature") + + // ErrInvalidSignatureFormat is returned when the signature format is invalid. + ErrInvalidSignatureFormat = errors.New("invalid signature format") +) + +// signatureRegexp matches the Paddle-Signature header format, e.g.: +// +// ts=1671552777;h1=eb4d0dc8853be92b7f063b9f3ba5233eb920a09459b6e6b2c26705b4364db151 +// +// More information can be found here: https://developer.paddle.com/webhooks/signature-verification. +var signatureRegexp = regexp.MustCompile(`^ts=(\d+);h1=(\w+)$`) + +// WebhookVerifier is used to verify webhook requests from Paddle. +type WebhookVerifier struct { + secretKey []byte +} + +// NewWebhookVerifier creates a new WebhookVerifier with the given secret key. +func NewWebhookVerifier(secretKey string) *WebhookVerifier { + return &WebhookVerifier{secretKey: []byte(secretKey)} +} + +// Verify verifies the signature of a webhook request. +func (wv *WebhookVerifier) Verify(req *http.Request) (bool, error) { + sig := req.Header.Get("Paddle-Signature") + if sig == "" { + return false, ErrMissingSignature + } + + matches := signatureRegexp.FindAllStringSubmatch(sig, -1) + if len(matches) != 1 || len(matches[0]) != 3 { + return false, ErrInvalidSignatureFormat + } + + ts := matches[0][1] + h1 := matches[0][2] + + body, err := io.ReadAll(req.Body) + if err != nil { + return false, err + } + + req.Body = io.NopCloser(bytes.NewBuffer(body)) + + mac := hmac.New(sha256.New, wv.secretKey) + mac.Write([]byte(ts)) + mac.Write([]byte(":")) + mac.Write(body) + + generatedMAC := mac.Sum(nil) + + dst, err := hex.DecodeString(h1) + if err != nil { + return false, err + } + + return hmac.Equal(dst, generatedMAC), nil +} + +// Middleware returns a middleware that verifies the signature of a webhook +// request. +func (wv *WebhookVerifier) Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ok, err := wv.Verify(r) + if err != nil && (errors.Is(err, ErrMissingSignature) || errors.Is(err, ErrInvalidSignatureFormat)) { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } else if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if !ok { + http.Error(w, "signature mismatch", http.StatusForbidden) + return + } + + next.ServeHTTP(w, r) + }) +} diff --git a/webhook_verifier_test.go b/webhook_verifier_test.go new file mode 100644 index 0000000..61342be --- /dev/null +++ b/webhook_verifier_test.go @@ -0,0 +1,163 @@ +package paddle_test + +import ( + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + paddle "github.com/PaddleHQ/paddle-go-sdk" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + testSignature = `ts=1710929255;h1=6c05ef8fa83c44d751be6d259ec955ce5638e2c54095bf128e408e2fce1589c8` + testPayload = `{"data":{"id":"pri_01hsdn96k2hxjzsq5yerecdj9j","name":null,"status":"active","quantity":{"maximum":999999,"minimum":1},"tax_mode":"account_setting","product_id":"pro_01hsdn8qp7yydry3x1yeg6a9rv","unit_price":{"amount":"1000","currency_code":"USD"},"custom_data":null,"description":"testing","import_meta":null,"trial_period":null,"billing_cycle":{"interval":"month","frequency":1},"unit_price_overrides":[]},"event_id":"evt_01hsdn97563968dy0szkmgjwh3","event_type":"price.created","occurred_at":"2024-03-20T10:07:35.590857Z","notification_id":"ntf_01hsdn977e920kbgzt6r6c9rqc"}` + testSecretKey = `pdl_ntfset_01hsdn8d43dt7mezr1ef2jtbaw_hKkRiCGyyRhbFwIUuqiTBgI7gnWoV0Gr` +) + +func TestVerifier_Verify(t *testing.T) { + type args struct { + name string + payload string + signature string + secretKey string + + expectedResult bool + expectedError error + } + + cases := []args{ + { + name: "valid signature", + + payload: testPayload, + signature: testSignature, + secretKey: testSecretKey, + + expectedResult: true, + }, + { + name: "missing signature", + + payload: testPayload, + secretKey: testSecretKey, + + expectedResult: false, + expectedError: paddle.ErrMissingSignature, + }, + { + name: "invalid payload", + + payload: `{}`, + signature: testSignature, + secretKey: testSecretKey, + + expectedResult: false, + }, + { + name: "invalid signature format", + + payload: testPayload, + signature: `ts=x;h1=y`, + secretKey: testSecretKey, + + expectedResult: false, + expectedError: paddle.ErrInvalidSignatureFormat, + }, + { + name: "invalid secret key", + + payload: testPayload, + signature: testSignature, + secretKey: `abc`, + + expectedResult: false, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(c.payload)) + req.Header.Set("Paddle-Signature", c.signature) + + ok, err := paddle.NewWebhookVerifier(c.secretKey).Verify(req) + assert.Equal(t, c.expectedResult, ok) + if c.expectedError == nil { + assert.NoError(t, err) + } else { + assert.ErrorIs(t, err, c.expectedError) + } + + // Assert that the body was put back onto the request after reading: + body, err := io.ReadAll(req.Body) + require.NoError(t, err) + + assert.Equal(t, c.payload, string(body)) + }) + } +} + +func TestVerifier_Middleware(t *testing.T) { + type args struct { + name string + + payload string + signature string + secretKey string + + expectedStatus int + expectedBody string + } + + cases := []args{ + { + name: "valid signature", + + payload: testPayload, + signature: testSignature, + secretKey: testSecretKey, + + expectedStatus: http.StatusOK, + }, + { + name: "missing signature", + + payload: testPayload, + secretKey: testSecretKey, + + expectedStatus: http.StatusBadRequest, + expectedBody: "missing signature\n", + }, + { + name: "invalid payload", + + payload: `{}`, + signature: testSignature, + secretKey: testSecretKey, + + expectedStatus: http.StatusForbidden, + expectedBody: "signature mismatch\n", + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(c.payload)) + req.Header.Set("Paddle-Signature", c.signature) + + rr := httptest.NewRecorder() + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + paddle.NewWebhookVerifier(c.secretKey).Middleware(next).ServeHTTP(rr, req) + + assert.Equal(t, c.expectedStatus, rr.Code) + assert.Equal(t, c.expectedBody, rr.Body.String()) + }) + } +}