├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .dockerignore ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── feature_request.md │ └── housekeeping.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── labeler.yml ├── stale.yml └── workflows │ ├── codeql-analysis.yml │ ├── documentation-links.yml │ ├── labeler.yml │ ├── linter.yml │ ├── release.yml │ └── tests.yml ├── .gitignore ├── .golangci.yaml ├── .goreleaser.yaml ├── CODEOWNERS ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── Taskfile.yml ├── docs ├── README.md ├── pkg │ ├── connect │ │ └── README.md │ ├── idempotency │ │ └── README.md │ └── pagination │ │ └── README.md ├── v3-upgrade.md └── v4-upgrade.md ├── go.mod ├── go.sum ├── lychee.toml ├── mollie ├── app_fee.go ├── balances.go ├── balances_test.go ├── captures.go ├── captures_test.go ├── chargebacks.go ├── chargebacks_test.go ├── client_links.go ├── client_links_test.go ├── clients.go ├── clients_test.go ├── common_types.go ├── common_types_test.go ├── config.go ├── config_test.go ├── custom_types.go ├── custom_types_test.go ├── customers.go ├── customers_test.go ├── doc.go ├── errors.go ├── errors_test.go ├── gift_cards.go ├── invoices.go ├── invoices_test.go ├── mandates.go ├── mandates_test.go ├── mollie.go ├── mollie_test.go ├── onboarding.go ├── onboarding_test.go ├── orders.go ├── orders_test.go ├── organizations.go ├── organizations_test.go ├── payment_details.go ├── payment_links.go ├── payment_links_test.go ├── payment_methods.go ├── payment_methods_test.go ├── payments.go ├── payments_test.go ├── permissions.go ├── permissions_test.go ├── profiles.go ├── profiles_test.go ├── refunds.go ├── refunds_test.go ├── settlements.go ├── settlements_test.go ├── shipments.go ├── shipments_test.go ├── subscriptions.go ├── subscriptions_test.go ├── terminals.go ├── terminals_test.go ├── vouchers.go ├── wallets.go └── wallets_test.go ├── pkg ├── connect │ ├── oauth2.go │ └── oauth2_test.go ├── idempotency │ ├── contract.go │ ├── doc.go │ ├── nop.go │ ├── nop_test.go │ ├── std.go │ └── std_test.go └── pagination │ ├── doc.go │ ├── pagination.go │ └── pagination_test.go └── testdata ├── balances.go ├── captures.go ├── chargebacks.go ├── client_links.go ├── customers.go ├── errors.go ├── invoices.go ├── mandates.go ├── methods.go ├── miscellaneous.go ├── onboarding.go ├── orders.go ├── organizations.go ├── partners.go ├── payment_links.go ├── payments.go ├── permissions.go ├── profiles.go ├── refunds.go ├── settlements.go ├── shipments.go ├── subscriptions.go └── terminals.go /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG GO_VERSION=1-1-bullseye 2 | 3 | ARG VARIANT=1-bullseye 4 | FROM mcr.microsoft.com/vscode/devcontainers/go:${GO_VERSION} 5 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base devcontainer", 3 | "build": { 4 | "dockerfile": "Dockerfile" 5 | }, 6 | "capAdd": [ 7 | "SYS_PTRACE" 8 | ], 9 | "securityOpt": [ 10 | "seccomp=unconfined" 11 | ], 12 | "mounts": [ 13 | { 14 | "source": "${localEnv:HOME}/.zshrc", 15 | "target": "/root/.zshrc", 16 | "type": "bind" 17 | }, 18 | { 19 | "source": "${localEnv:HOME}/.config/starship.toml", 20 | "target": "/root/.config/starship.toml", 21 | "type": "bind" 22 | }, 23 | { 24 | "source": "${localEnv:HOME}/.config/bat/config", 25 | "target": "/root/.config/bat/config", 26 | "type": "bind" 27 | } 28 | ], 29 | "features": { 30 | "ghcr.io/devcontainers-contrib/features/act-asdf:2": {}, 31 | "ghcr.io/devcontainers/features/docker-in-docker:2": { 32 | "dockerDashComposeVersion": "v2" 33 | }, 34 | "ghcr.io/devcontainers/features/common-utils:2": { 35 | "configureZshAsDefaultShell": true, 36 | "username": "root" 37 | }, 38 | "ghcr.io/devcontainers-contrib/features/starship:1": {}, 39 | "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { 40 | "packages": "exa,bat,curl", 41 | "upgradePackages": true 42 | } 43 | }, 44 | "customizations": { 45 | "vscode": { 46 | "extensions": [ 47 | "GitHub.vscode-pull-request-github", 48 | "jasonlhy.hungry-delete", 49 | "formulahendry.auto-rename-tag", 50 | "DavidAnson.vscode-markdownlint", 51 | "aaron-bond.better-comments", 52 | "streetsidesoftware.code-spell-checker", 53 | "PKief.material-icon-theme", 54 | "ms-azuretools.vscode-docker", 55 | "shardulm94.trailing-spaces", 56 | "tamasfe.even-better-toml", 57 | "GitHub.copilot", 58 | "GitHub.vscode-github-actions", 59 | "GitHub.copilot-chat", 60 | "golang.Go", 61 | "usernamehw.errorlens", 62 | "redhat.vscode-yaml" 63 | ], 64 | "settings": { 65 | "files.eol": "\n", 66 | "remote.extensionKind": { 67 | "ms-azuretools.vscode-docker": "workspace" 68 | }, 69 | "go.toolsManagement.checkForUpdates": "local", 70 | "go.toolsManagement.autoUpdate": true, 71 | "go.gopath": "/go", 72 | "go.goroot": "/usr/local/go", 73 | "go.useLanguageServer": true, 74 | "[go]": { 75 | "editor.defaultFormatter": "golang.go", 76 | "editor.codeActionsOnSave": { 77 | "source.organizeImports": true 78 | } 79 | }, 80 | "[go.mod]": { 81 | "editor.codeActionsOnSave": { 82 | "source.organizeImports": true 83 | } 84 | }, 85 | "gopls": { 86 | "build.buildFlags": [ 87 | "-tags", 88 | "" 89 | ], 90 | "formatting.gofumpt": true, 91 | "ui.completion.usePlaceholders": false, 92 | "ui.diagnostic.staticcheck": true, 93 | "ui.semanticTokens": true 94 | }, 95 | "go.lintTool": "golangci-lint", 96 | "go.lintOnSave": "package", 97 | "go.testFlags": [ 98 | "-v", 99 | "-race" 100 | ], 101 | "go.testTimeout": "30s", 102 | "go.coverOnSingleTest": true, 103 | "go.coverOnSingleTestFile": true, 104 | "go.coverOnTestPackage": true, 105 | "editor.formatOnSave": true, 106 | "editor.formatOnPaste": true, 107 | "editor.bracketPairColorization.enabled": true, 108 | "editor.guides.bracketPairs": "active", 109 | "workbench.iconTheme": "material-icon-theme", 110 | "editor.fontFamily": "'Fira Code', Menlo, Monaco, 'Courier New', monospace", 111 | "editor.fontLigatures": true, 112 | "files.insertFinalNewline": true, 113 | "files.trimFinalNewlines": true 114 | } 115 | } 116 | }, 117 | "portsAttributes": { 118 | "9000": { 119 | "label": "Hello Remote World", 120 | "onAutoForward": "notify" 121 | } 122 | }, 123 | "postCreateCommand": { 124 | "install-zsh-plugins": "git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting && git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions", 125 | "modules": "go mod download", 126 | "taskfile": "go install github.com/go-task/task/v3/cmd/task@latest", 127 | "gomarkdoc": "go install github.com/princjef/gomarkdoc/cmd/gomarkdoc@latest", 128 | "goreleaser": "go install github.com/goreleaser/goreleaser/v2@latest" 129 | }, 130 | "remoteUser": "root", 131 | "containerUser": "root", 132 | "containerEnv": { 133 | "MOLLIE_API_TOKEN": "${localEnv:MOLLIE_API_TOKEN}", 134 | "MOLLIE_ORG_TOKEN": "${localEnv:MOLLIE_ORG_TOKEN}" 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | ## Project files and directories 2 | 3 | .git 4 | .github 5 | docs 6 | examples 7 | LICENSE 8 | .scrutinizer.yml 9 | .goreleaser.yml 10 | .gitignore 11 | .dockerignore 12 | Dockerfile 13 | Makefile 14 | *.md 15 | 16 | # Binaries for programs and plugins 17 | 18 | *.exe 19 | *.exe~ 20 | *.dll 21 | *.so 22 | *.dylib 23 | 24 | # Test binary, build with `go test -c` 25 | 26 | *.test 27 | 28 | # Output of the go coverage tool, specifically when used with LiteIDE 29 | 30 | *.out 31 | 32 | # Visual Studio Code 33 | 34 | .vscode 35 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at `deltatuts@gmail.com`. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/victoravelar/mollie-api-go). 6 | 7 | ## Before starting to code 8 | 9 | If you are contributing based on an issue, please drop a comment in the issue so that it can be labeled as `in progress` and assigned to you, this provides everyone visiting the repository with a clear overview of what is up for grabs and what is already taken. 10 | 11 | Also be sure to ask and clarify all your questions so that your coding experience is as smooth as possible. 12 | 13 | If your PR is not based on an issue, then please provide a relevant and descriptive PR description and branch name. 14 | 15 | ## Pull Requests 16 | 17 | - **Passes code style checks from scrutinizer** 18 | 19 | - **Add tests!** - Your code won't be accepted if it doesn't have tests. 20 | 21 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 22 | 23 | - **Create feature branches** - Don't ask us to pull from your master branch. 24 | 25 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 26 | 27 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 28 | 29 | 30 | ## Running Tests 31 | 32 | ``` bash 33 | $ go test -v -race ./... 34 | ``` 35 | 36 | **Happy coding**! 37 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['paypal.me/avelarossorio'] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: ['bug', 'help-wanted'] 6 | assignees: 'VictorAvelar' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Desktop (please complete the following information):** 28 | 29 | - OS: [e.g. iOS] 30 | - Browser [e.g. chrome, safari] 31 | - Version [e.g. 22] 32 | 33 | **Smartphone (please complete the following information):** 34 | 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Victor H. Avelar 4 | url: https://victoravelar.com 5 | about: Maintainer 6 | - name: Mollie's API Documentation 7 | url: https://docs.mollie.com/index 8 | about: Official API Documentation 9 | - name: Mollie's Changelog 10 | url: https://docs.mollie.com/changelog/v2/changelog 11 | about: Changes announced in the API 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: ['enhancement'] 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/housekeeping.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Repository housekeeping 3 | about: A regular code check for improvements or tech debt 4 | title: 'Library housekeeping - [PERIOD]' 5 | labels: 'housekeeping' 6 | assignees: 'VictorAvelar' 7 | 8 | --- 9 | 10 | ## List of changes 11 | 12 | - General change 13 | - Addition to x 14 | 15 | **External references** 16 | Links or additional resources to provide context to the introduced changes 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Description 4 | 5 | Describe your changes in detail. 6 | 7 | ## Motivation and context 8 | 9 | Why is this change required? What problem does it solve? 10 | 11 | If it fixes an open issue, please link to the issue here (if you write `fixes #num` 12 | or `closes #num`, the issue will be automatically closed when the pull is accepted.) 13 | 14 | ## How has this been tested? 15 | 16 | Please describe in detail how you tested your changes. 17 | 18 | Include details of your testing environment, and the tests you ran to 19 | see how your change affects other areas of the code, etc. 20 | 21 | - [ ] Unit tests added / updated 22 | - [ ] The tests run on docker (using `make test`) 23 | - [ ] The required `test data` has been added / updated 24 | 25 | If you are running the tests locally, please specify: 26 | 27 | - OS: [e.g. ubuntu/macos/windows] 28 | - Go version [e.g. 1.19.x] 29 | 30 | ## Types of changes 31 | 32 | What types of changes does your code introduce? Put an `x` in all the boxes that apply: 33 | 34 | - [ ] Bug fix (non-breaking change which fixes an issue) 35 | - [ ] New feature (non-breaking change which adds functionality) 36 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 37 | - [ ] This change requires a documentation update 38 | 39 | ## Checklist 40 | 41 | Go over all the following points, and put an `x` in all the boxes that apply. 42 | 43 | Please, please, please, don't send your pull request until all of the boxes are ticked. Once your pull request is created, it will trigger a build on our continuous integration server to make sure your [tests and code style pass](https://help.github.com/articles/about-required-status-checks/). 44 | 45 | - [ ] I have read the **[CONTRIBUTING](CONTRIBUTING.md)** document. 46 | - [ ] My pull request addresses exactly one patch/feature. 47 | - [ ] I have created a branch for this patch/feature. 48 | - [ ] Each individual commit in the pull request is meaningful. 49 | - [ ] If my change requires a change to the documentation, I have updated it accordingly. 50 | 51 | 52 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | reviewers: 10 | - VictorAvelar 11 | 12 | - package-ecosystem: github-actions 13 | directory: '/' 14 | schedule: 15 | interval: daily 16 | target-branch: master 17 | reviewers: 18 | - VictorAvelar 19 | 20 | - package-ecosystem: docker 21 | directory: '/' 22 | schedule: 23 | interval: weekly 24 | target-branch: master 25 | reviewers: 26 | - VictorAvelar 27 | 28 | - package-ecosystem: docker 29 | directory: '.devcontainer' 30 | schedule: 31 | interval: weekly 32 | target-branch: master 33 | reviewers: 34 | - VictorAvelar 35 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | tests: 2 | - changed-files: 3 | - any-glob-to-any-file: ["mollie/*_test.go", "pkg/**/*_test.go"] 4 | 5 | documentation: 6 | - changed-files: 7 | - any-glob-to-any-file: ["docs/*"] 8 | 9 | docker: 10 | - changed-files: 11 | - any-glob-to-any-file: ["Dockerfile", ".dockerignore"] 12 | 13 | devcontainers: 14 | - changed-files: 15 | - any-glob-to-any-file: [".devcontainer/*"] 16 | 17 | gh-actions: 18 | - changed-files: 19 | - any-glob-to-any-file: [".github/**/*.yml"] 20 | 21 | core: 22 | - changed-files: 23 | - any-glob-to-any-file: 24 | ["mollie/*.go", "!mollie/*_tests.go", "!mollie/tools/*"] 25 | 26 | modules: 27 | - changed-files: 28 | - any-glob-to-any-file: ["go.*"] 29 | 30 | testdata: 31 | - changed-files: 32 | - any-glob-to-any-file: ["testdata/*"] 33 | 34 | config: 35 | - changed-files: 36 | - any-glob-to-any-file: [".*.yml"] 37 | 38 | meta-files: 39 | - changed-files: 40 | - any-glob-to-any-file: 41 | ["Taskfile", "README.md", "LICENSE", "SECURITY.md"] 42 | 43 | pkg: 44 | - changed-files: 45 | - any-glob-to-any-file: ["mollie/pkg/**/*.go"] 46 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 5 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 10 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - question 10 | - request-info 11 | - documentation 12 | - investigation 13 | # Label to use when marking an issue as stale 14 | staleLabel: stale 15 | # Comment to post when marking an issue as stale. Set to `false` to disable 16 | markComment: > 17 | This issue has been automatically marked as stale because it has not had 18 | recent activity. It will be closed if no further activity occurs. Thank you 19 | for your contributions. 20 | # Comment to post when closing a stale issue. Set to `false` to disable 21 | closeComment: > 22 | This issue has been automatically closed because it has not had 23 | any activity after being labeled as staled. 24 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths-ignore: 7 | - "**.md" 8 | - "*.yml" 9 | - "Makefile" 10 | - "docs/*.md" 11 | - ".gitignore" 12 | - "LICENSE" 13 | - ".github/*.yml" 14 | - ".github/ISSUE_TEMPLATE/*.md" 15 | - ".github/*.md" 16 | - ".github/workflows/main.yml" 17 | - ".github/workflows/release.yml" 18 | - ".devcontainers/**" 19 | schedule: 20 | - cron: "21 11 * * 4" 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze 25 | runs-on: ubuntu-latest 26 | permissions: 27 | actions: read 28 | contents: read 29 | security-events: write 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | language: ["go"] 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@81644f35ff20aa6b0e7b936f0e8716419ba7d295 43 | with: 44 | languages: 'go' 45 | - name: Autobuild 46 | uses: github/codeql-action/autobuild@81644f35ff20aa6b0e7b936f0e8716419ba7d295 47 | 48 | - name: Perform CodeQL Analysis 49 | uses: github/codeql-action/analyze@81644f35ff20aa6b0e7b936f0e8716419ba7d295 50 | -------------------------------------------------------------------------------- /.github/workflows/documentation-links.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | branches: 5 | - master 6 | paths: 7 | - "docs/**" 8 | - ".github/workflows/documentation-links.yml" 9 | pull_request: 10 | paths: 11 | - "docs/**" 12 | - ".github/workflows/documentation-links.yml" 13 | 14 | jobs: 15 | linkChecker: 16 | if: github.actor != 'dependabot[bot]' 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 20 | 21 | - name: Restore lychee cache 22 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 23 | with: 24 | path: .lycheecache 25 | key: cache-lychee-${{ github.ref }} 26 | restore-keys: cache-lychee- 27 | 28 | - name: Link Checker 29 | id: lychee 30 | uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1 31 | with: 32 | args: "--base . --cache --max-cache-age 1d ." 33 | output: ./lychee/out.md 34 | fail: false 35 | 36 | - name: 'Look for an existing issue' 37 | id: last-issue 38 | uses: micalevisk/last-issue-action@044e1cb7e9a4dde20e22969cb67818bfca0797be # v2.0.0 39 | with: 40 | state: open 41 | labels: lychee 42 | 43 | - name: Create Issue From File 44 | if: steps.lychee.outputs.exit_code != 0 45 | uses: peter-evans/create-issue-from-file@e8ef132d6df98ed982188e460ebb3b5d4ef3a9cd # 5.0.1 46 | with: 47 | title: Link Checker Report 48 | issue-number: ${{ steps.last-issue.outputs.issue_number }} 49 | content-filepath: ./lychee/out.md 50 | labels: report, automated-issue, lychee 51 | assignees: VictorAvelar 52 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: "PR label assigner" 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: 7 | - master 8 | pull_request_target: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | triage: 14 | permissions: 15 | checks: write 16 | contents: read 17 | pull-requests: write 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 21 | with: 22 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 23 | sync-labels: true 24 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | paths-ignore: 6 | - "**.md" 7 | - "*.yml" 8 | - "Makefile" 9 | - "docs/*.md" 10 | - ".gitignore" 11 | - "LICENSE" 12 | - ".github/*.yml" 13 | - ".github/ISSUE_TEMPLATE/*.md" 14 | - ".github/*.md" 15 | - ".github/workflows/main.yml" 16 | - ".github/workflows/release.yml" 17 | pull_request: 18 | paths-ignore: 19 | - "**.md" 20 | - "*.yml" 21 | - "Makefile" 22 | - "docs/*.md" 23 | - ".gitignore" 24 | - "LICENSE" 25 | - ".github/*.yml" 26 | - ".github/ISSUE_TEMPLATE/*.md" 27 | - ".github/*.md" 28 | - ".github/workflows/main.yml" 29 | - ".github/workflows/release.yml" 30 | jobs: 31 | golangci: 32 | name: linter 33 | runs-on: ubuntu-latest 34 | permissions: 35 | contents: read 36 | pull-requests: read 37 | checks: write 38 | steps: 39 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 40 | with: 41 | fetch-depth: '0' 42 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a 43 | with: 44 | go-version: 1.x 45 | - uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd 46 | with: 47 | version: latest 48 | install-mode: binary 49 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - "*" 8 | 9 | jobs: 10 | goreleaser: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 14 | with: 15 | fetch-depth: 0 16 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a 17 | with: 18 | go-version: 1.23.x 19 | - uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf 20 | with: 21 | distribution: goreleaser 22 | version: latest 23 | args: release --clean 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | concurrency: 2 | group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} 3 | cancel-in-progress: true 4 | 5 | name: testing 6 | 7 | permissions: 8 | contents: read 9 | 10 | on: 11 | push: 12 | branches: 13 | - master 14 | paths-ignore: 15 | - "**.md" 16 | - "*.yml" 17 | - "Makefile" 18 | - "docs/*.md" 19 | - ".gitignore" 20 | - "LICENSE" 21 | - ".github/*.yml" 22 | - ".github/ISSUE_TEMPLATE/*.md" 23 | - ".github/*.md" 24 | - ".github/workflows/release.yml" 25 | pull_request: 26 | paths-ignore: 27 | - "**.md" 28 | - "*.yml" 29 | - "Makefile" 30 | - "docs/*.md" 31 | - ".gitignore" 32 | - "LICENSE" 33 | - ".github/*.yml" 34 | - ".github/ISSUE_TEMPLATE/*.md" 35 | - ".github/*.md" 36 | - ".github/workflows/release.yml" 37 | jobs: 38 | tests: 39 | permissions: 40 | contents: read 41 | strategy: 42 | matrix: 43 | go: [1.x, 1.23.x] 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 47 | - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # 5.2.0 48 | with: 49 | go-version: ${{ matrix.go }} 50 | # Get values for cache paths to be used in later steps 51 | - id: cache-paths 52 | run: | 53 | echo "go-cache=$(go env GOCACHE)" >> $GITHUB_OUTPUT 54 | echo "go-mod-cache=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT 55 | - name: Cache go modules 56 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 57 | with: 58 | path: | 59 | ${{ steps.cache-paths.outputs.go-cache }} 60 | ${{ steps.cache-paths.outputs.go-mod-cache }} 61 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 62 | restore-keys: ${{ runner.os }}-go- 63 | - run: go test -failfast -timeout 5m ./... 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Visual Studio Code 15 | .vscode 16 | 17 | # Wiki pages 18 | wiki 19 | 20 | dist/ 21 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | concurrency: 4 4 | go: "1.23" 5 | modules-download-mode: readonly 6 | issues-exit-code: 2 7 | tests: false 8 | output: 9 | formats: 10 | text: 11 | path: stdout 12 | tab: 13 | path: stdout 14 | colors: false 15 | linters: 16 | enable: 17 | - asciicheck 18 | - bidichk 19 | - copyloopvar 20 | - cyclop 21 | - decorder 22 | - depguard 23 | - dogsled 24 | - dupl 25 | - dupword 26 | - funlen 27 | - gocheckcompilerdirectives 28 | - gochecknoinits 29 | - gocognit 30 | - goconst 31 | - gocyclo 32 | - godot 33 | - godox 34 | - goheader 35 | - gomoddirectives 36 | - gomodguard 37 | - goprintffuncname 38 | - grouper 39 | - inamedparam 40 | - interfacebloat 41 | - lll 42 | - maintidx 43 | - misspell 44 | - mnd 45 | - nakedret 46 | - nestif 47 | - nlreturn 48 | - nolintlint 49 | - nosprintfhostport 50 | - prealloc 51 | - predeclared 52 | - promlinter 53 | - tagalign 54 | - testpackage 55 | - usestdlibvars 56 | - whitespace 57 | - wsl 58 | disable: 59 | - tagliatelle 60 | - unused 61 | settings: 62 | depguard: 63 | rules: 64 | main: 65 | files: 66 | - $all 67 | allow: 68 | - $gostd 69 | - github.com/google 70 | - golang.org/x/oauth2 71 | - github.com/VictorAvelar/mollie-api-go/v4/ 72 | tests: 73 | files: 74 | - $test 75 | allow: 76 | - github.com/stretchr/testify 77 | exclusions: 78 | generated: lax 79 | presets: 80 | - comments 81 | - common-false-positives 82 | - legacy 83 | - std-error-handling 84 | rules: 85 | - linters: 86 | - lll 87 | source: BusinessCategory 88 | paths: 89 | - third_party$ 90 | - builtin$ 91 | - examples$ 92 | issues: 93 | new: true 94 | fix: true 95 | formatters: 96 | enable: 97 | - gci 98 | - gofmt 99 | - gofumpt 100 | - goimports 101 | exclusions: 102 | generated: lax 103 | paths: 104 | - third_party$ 105 | - builtin$ 106 | - examples$ 107 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | builds: 4 | - skip: true 5 | 6 | changelog: 7 | use: github 8 | sort: asc 9 | groups: 10 | - title: "New Features and updates" 11 | regexp: "^.*feat[(\\w)]*:+.*$" 12 | order: 0 13 | - title: "Bug fixes" 14 | regexp: "^.*fix[(\\w)]*:+.*$" 15 | order: 10 16 | - title: "Documentation updates" 17 | regexp: "^.*docs[(\\w)]*:+.*$" 18 | order: 20 19 | - title: Other work 20 | order: 999 21 | filters: 22 | exclude: 23 | - ^test 24 | - changelog 25 | - typo 26 | - Readme 27 | - ^Merge pull request 28 | - ^Merge branch 29 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @VictorAvelar -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24.4-alpine 2 | 3 | ENV CGO_ENABLED=0 4 | 5 | WORKDIR /app 6 | 7 | COPY go.mod . 8 | COPY go.sum . 9 | 10 | RUN go mod download 11 | 12 | COPY . . 13 | 14 | ENTRYPOINT ["go", "test", "-v", "./mollie/...", "-coverprofile", "cover.out"] 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Victor Hugo Avelar Ossorio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mollie API Golang client 2 | 3 | ## Deepwiki 4 | 5 | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/VictorAvelar/mollie-api-go) 6 | 7 | ## Actions 8 | 9 | [![testing](https://github.com/VictorAvelar/mollie-api-go/actions/workflows/tests.yml/badge.svg)](https://github.com/VictorAvelar/mollie-api-go/actions/workflows/tests.yml) 10 | [![Linting](https://github.com/VictorAvelar/mollie-api-go/actions/workflows/linter.yml/badge.svg)](https://github.com/VictorAvelar/mollie-api-go/actions/workflows/linter.yml) 11 | [![CodeQL](https://github.com/VictorAvelar/mollie-api-go/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/VictorAvelar/mollie-api-go/actions/workflows/codeql-analysis.yml) 12 | 13 | ## Go ecosystem 14 | 15 | [![Go Reference](https://pkg.go.dev/badge/github.com/VictorAvelar/mollie-api-go/v4/mollie.svg)](https://pkg.go.dev/github.com/VictorAvelar/mollie-api-go/v4/mollie) 16 | [![Go Report Card](https://goreportcard.com/badge/github.com/VictorAvelar/mollie-api-go/v3)](https://goreportcard.com/report/github.com/VictorAvelar/mollie-api-go/v3) 17 | [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/3522/badge)](https://bestpractices.coreinfrastructure.org/projects/3522) 18 | 19 | Accepting [iDEAL](https://www.mollie.com/payments/ideal/), [Apple Pay](https://www.mollie.com/payments/apple-pay), [Bancontact](https://www.mollie.com/payments/bancontact/), [SOFORT Banking](https://www.mollie.com/payments/sofort/), [Creditcard](https://www.mollie.com/payments/credit-card/), [SEPA Bank transfer](https://www.mollie.com/payments/bank-transfer/), [SEPA Direct debit](https://www.mollie.com/payments/direct-debit/), [PayPal](https://www.mollie.com/payments/paypal/), [Belfius Direct Net](https://www.mollie.com/payments/belfius/), [KBC/CBC](https://www.mollie.com/payments/kbc-cbc/), [paysafecard](https://www.mollie.com/payments/paysafecard/), [Giftcards](https://www.mollie.com/payments/gift-cards/), [EPS](https://www.mollie.com/payments/eps/) and [Przelewy24](https://www.mollie.com/payments/przelewy24/) online payments without fixed monthly costs or any punishing registration procedures. Just use the Mollie API to receive payments directly on your website or easily refund transactions to your customers. 20 | 21 | ## Requirements 22 | 23 | To use the Mollie API client, the following things are required: 24 | 25 | - Get yourself a free [Mollie account](https://www.mollie.com/signup). No sign up costs. 26 | - Now you're ready to use the Mollie API client in test mode. 27 | - Follow [a few steps](https://www.mollie.com/dashboard/?modal=onboarding) to enable payment methods in live mode, and let us handle the rest. 28 | - Up-to-date OpenSSL (or other SSL/TLS toolkit) 29 | 30 | For leveraging [Mollie Connect](https://docs.mollie.com/oauth/overview) (advanced use cases only), it is recommended to be familiar with the OAuth2 protocol. 31 | 32 | ## Install 33 | 34 | ```sh 35 | go get -u github.com/VictorAvelar/mollie-api-go/v4/mollie 36 | ``` 37 | 38 | ## Notice 39 | 40 | > Version 4.6.0 raises the minimum go version to v1.23, patches will likely be backported to v4.5.x but new features will only be available in versions > v4.6.0. 41 | 42 | The above notice aligns with the go [EOL](https://endoflife.date/go) policy schedule. 43 | 44 | ## Usage 45 | 46 | ### Testing using API tokens 47 | 48 | #### Using the config helper 49 | 50 | ```go 51 | // Create a configuration object with idempotency enabled. 52 | config := mollie.NewAPITestingConfig(true) 53 | ``` 54 | 55 | #### Using the NewConfig method 56 | 57 | ```go 58 | // Create a configuration object with idempotency enabled. 59 | config := mollie.NewConfig(true, mollie.ApiTokenEnv) 60 | 61 | _ := config.ToggleIdempotency() 62 | ``` 63 | 64 | ### Testing using Organization access tokens 65 | 66 | #### Using the config helper for org tokens 67 | 68 | ```go 69 | // Create a configuration object with idempotency enabled. 70 | config := mollie.NewOrgTestingConfig(true) 71 | ``` 72 | 73 | #### Using the NewConfig method for org tokens 74 | 75 | ```go 76 | // Create a configuration object with idempotency enabled. 77 | config := mollie.NewConfig(true, mollie.OrgTokenEnv) 78 | 79 | _ := config.ToggleIdempotency() 80 | ``` 81 | 82 | ### Create an API client 83 | 84 | ```go 85 | // build your desired config 86 | client, err := mollie.NewClient(config) 87 | if err != nil { 88 | log.Fatal(err) 89 | } 90 | // perform operations with the API. 91 | ``` 92 | 93 | ## Upgrade guide 94 | 95 | - If you want to upgrade from v2 -> v3, the list of breaking and notable changes can be found in the [docs](docs/v3-upgrade.md). 96 | - If you want to upgrade from v3 -> v4, the list of breaking and notable changes can be found in the [docs](docs/v4-upgrade.md). 97 | 98 | ## API parity 99 | 100 | Checks to the API changelog are performed constantly to ensure API parity and compatibility, however it might happen that not all the changes are implemented right away. 101 | 102 | For checking all the related tasks you can check the issues labeled with the [API parity](https://github.com/VictorAvelar/mollie-api-go/labels/API%20parity) label. 103 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Even though go offers full backwards compatibility, this package will only be concerned about the versions tested and 6 | declared inside the `.github/workflows/main.yml` file. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 1.13.x | :warning: | 11 | | 1.14.x | :warning: | 12 | | 1.15.x | :warning: | 13 | | 1.16.x | :warning: | 14 | | 1.17.x | :warning: | 15 | | 1.18.x | :warning: | 16 | | 1.19.x | :warning: | 17 | | 1.20.x | :white_check_mark: | 18 | | 1.21.x | :white_check_mark: | 19 | | 1.22.x | :white_check_mark: | 20 | | 1.23.x | :white_check_mark: | 21 | | master | :x: | 22 | 23 | ## Reporting a Vulnerability 24 | 25 | For a vulnerability in a dependency or third party package, please create an issue and also be sure to report the problem in the repository 26 | were the dependency is developed. 27 | 28 | For a vulnerability issue detected on this repository, please send an email to [deltatuts@gmail.com](mailto:deltatuts@gmail.com) 29 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | build: 5 | desc: "Build the project using the Dockerfile go installation" 6 | cmds: 7 | - docker build -t mollie-go:latest -f Dockerfile . 8 | silent: true 9 | 10 | run: 11 | desc: "Run the tests using the Dockerfile go installation" 12 | cmds: 13 | - docker run --rm mollie-go:latest 14 | silent: true 15 | 16 | ci-lint: 17 | desc: "Run all linters used in the CI pipeline" 18 | cmds: 19 | - golangci-lint run --output.tab.path stdout -n --uniq-by-line 20 | silent: true 21 | 22 | ci-lint-all: 23 | desc: "Run all linters used in the CI pipeline" 24 | cmds: 25 | - golangci-lint run --output.tab.path stdout --uniq-by-line 26 | silent: true 27 | 28 | lint: 29 | desc: "Run the go linters" 30 | cmds: 31 | - go version 32 | - echo "Running go lint" 33 | - golint ./... 34 | - echo "Running go vet" 35 | - go vet ./... 36 | silent: false 37 | 38 | test: 39 | desc: "Run tests using the Dockerfile go installation" 40 | cmds: 41 | - task: run 42 | silent: false 43 | 44 | test-local: 45 | desc: "Run tests using a local go installation" 46 | cmds: 47 | - go test -failfast ./... -coverprofile cover.out 48 | silent: false 49 | 50 | coverage: 51 | desc: "Run tests and generate a default coverage report" 52 | cmds: 53 | - go tool cover -func=cover.out 54 | silent: false 55 | 56 | cover-report: 57 | desc: "Run tests and generate coverage report in HTML format" 58 | cmds: 59 | - go tool cover -html=cover.out 60 | silent: false 61 | 62 | clean: 63 | desc: "Verify and tidy go modules" 64 | cmds: 65 | - go mod verify 66 | - go mod tidy 67 | silent: false 68 | 69 | update-docs: 70 | desc: "Update the generated docs for the mollie package" 71 | cmds: 72 | - gomarkdoc --output docs/README.md ./mollie 73 | - git add --all 74 | - "git commit --message 'chore(docs): update generated docs'" 75 | silent: false 76 | 77 | sub-pkg-docs: 78 | desc: "Update the generated docs for the sub-packages" 79 | cmds: 80 | - gomarkdoc ./pkg/connect > docs/pkg/connect/README.md 81 | - gomarkdoc ./pkg/idempotency > docs/pkg/idempotency/README.md 82 | - gomarkdoc ./pkg/pagination > docs/pkg/pagination/README.md 83 | silent: false 84 | -------------------------------------------------------------------------------- /docs/pkg/connect/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # connect 4 | 5 | ```go 6 | import "github.com/VictorAvelar/mollie-api-go/v4/pkg/connect" 7 | ``` 8 | 9 | ## Index 10 | 11 | - [func OauthEndpoint\(\) \*oauth2.Endpoint](<#OauthEndpoint>) 12 | 13 | 14 | 15 | ## func [OauthEndpoint]() 16 | 17 | ```go 18 | func OauthEndpoint() *oauth2.Endpoint 19 | ``` 20 | 21 | OauthEndpoint is Mollies's OAuth 2.0 endpoint. 22 | 23 | Generated by [gomarkdoc]() 24 | -------------------------------------------------------------------------------- /docs/pkg/idempotency/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # idempotency 4 | 5 | ```go 6 | import "github.com/VictorAvelar/mollie-api-go/v4/pkg/idempotency" 7 | ``` 8 | 9 | Package idempotency contains the services in charge of generating a unique keys to be passed in POST requests to ensure operation uniqueness. 10 | 11 | See: https://docs.mollie.com/overview/api-idempotency 12 | 13 | The std generator uses google's uuid library to return a new uuid as unique idempotency key. 14 | 15 | You can build your own generator and pass it to the library by implementing the KeyGenerator interface. 16 | 17 | ## Index 18 | 19 | - [Constants](<#constants>) 20 | - [type KeyGenerator](<#KeyGenerator>) 21 | - [func NewNopGenerator\(exp string\) KeyGenerator](<#NewNopGenerator>) 22 | - [func NewStdGenerator\(\) KeyGenerator](<#NewStdGenerator>) 23 | 24 | 25 | ## Constants 26 | 27 | TestKeyExpected is the default value for the NOpGenerator. 28 | 29 | ```go 30 | const ( 31 | TestKeyExpected = "test_ikg_key" 32 | ) 33 | ``` 34 | 35 | 36 | ## type [KeyGenerator]() 37 | 38 | KeyGenerator describes the service in charge of generating a unique idempotency key to be passed in POST requests to ensure operation uniqueness. 39 | 40 | See: https://docs.mollie.com/overview/api-idempotency 41 | 42 | ```go 43 | type KeyGenerator interface { 44 | // Generate encapsulates the logic to return a string representation of 45 | // a unique idempotency key. 46 | Generate() string 47 | } 48 | ``` 49 | 50 | 51 | ### func [NewNopGenerator]() 52 | 53 | ```go 54 | func NewNopGenerator(exp string) KeyGenerator 55 | ``` 56 | 57 | NewNopGenerator returns a dummy implementation of the IdempotencyKeyGenerator interface. 58 | 59 | Good for testing or when a predictable result is required. 60 | 61 | If exp is an empty string, then TestKeyExpected is used as default value for the NOpGenerator. 62 | 63 | 64 | ### func [NewStdGenerator]() 65 | 66 | ```go 67 | func NewStdGenerator() KeyGenerator 68 | ``` 69 | 70 | NewStdGenerator returns an standard and common way of generating idempotency unique keys. 71 | 72 | Generated by [gomarkdoc]() 73 | -------------------------------------------------------------------------------- /docs/pkg/pagination/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # pagination 4 | 5 | ```go 6 | import "github.com/VictorAvelar/mollie-api-go/v4/pkg/pagination" 7 | ``` 8 | 9 | Package pagination provides utilities to handle pagination in API responses. 10 | 11 | Pagination is a common feature in APIs that allows retrieval of large datasets in smaller chunks, enhancing performance and resource usage. This package aims to simplify pagination\-related tasks by providing helpful functions. 12 | 13 | ## Index 14 | 15 | - [func ExtractFromQueryParam\(uri string\) \(lastID string, err error\)](<#ExtractFromQueryParam>) 16 | 17 | 18 | 19 | ## func [ExtractFromQueryParam]() 20 | 21 | ```go 22 | func ExtractFromQueryParam(uri string) (lastID string, err error) 23 | ``` 24 | 25 | ExtractFromQueryParam extracts the lastID from the given URI, which is assumed to be a URL with query parameters. It specifically looks for a query parameter named 'from' and returns its value as a string. If the URI cannot be parsed or the query parameter is not found, it returns an empty string and the encountered error. 26 | 27 | Generated by [gomarkdoc]() 28 | -------------------------------------------------------------------------------- /docs/v3-upgrade.md: -------------------------------------------------------------------------------- 1 | # Changes included in v3 2 | 3 | ## Breaking changes 4 | 5 | - Minimum go version is now v1.17 6 | - All service actions accept a `context.Context` as first parameter. 7 | - Methods service actions now return pointers instead of struct literals. 8 | - Invoice service actions now return pointers instead of struct literals. 9 | - Mandates service actions now return pointers instead of struct literals. 10 | - Payments service actions now return pointers instead of struct literals. 11 | - Get permissions now accepts a direct reference to a PermissionGrant struct instead of a string. 12 | - All services now return an instance of mollie.Response as first value changing the signature from (value, error) to (response, value, error). 13 | - All data structures are changed to the following naming patterns 14 | - For Lists: {Value}List 15 | - For Options: {Value}Options | {Value}ListOptions 16 | - For Links: {Value}Links | {Value}ListLinks 17 | - Params now contain the full name of the value they contain. Ex. pID now is payment. 18 | - Methods and its references is now PaymentMethods to make its purpose clear. 19 | - Services that now return the full response object as part of the returned values (the response is always the first value of `n` returned): 20 | - Captures 21 | - Chargebacks 22 | - Customers 23 | - Invoices 24 | - Mandates 25 | - Miscellaneous 26 | - Onboarding 27 | - Partners 28 | - Orders 29 | - Organizations 30 | - PaymentLinks 31 | - PaymentMethods 32 | - Payments 33 | - Permissions 34 | - Profiles 35 | - Refunds 36 | - Settlements 37 | - Shipments 38 | - Subscriptions 39 | - Errors now use the `mollie.BaseError` type to provide better error reporting. 40 | - The type `mollie.Error` is removed from the codebase. 41 | - PaginationLinks changed to pointers. 42 | 43 | ## Other changes 44 | 45 | - All the tests are now using testify.Suite 46 | - Removed the example tests as it was not accurate nor well implemented. 47 | - Remove CHANGELOG.md as the releases now provide a more accurate report of changes. 48 | - Some typos were fixed on several query serializable param tags 49 | - Client now contains helpers for the used http actions (get, post, patch & delete) to simplify the way the requests are dispatched. 50 | -------------------------------------------------------------------------------- /docs/v4-upgrade.md: -------------------------------------------------------------------------------- 1 | # Notable changes included in v4 2 | 3 | ## Breaking changes 4 | 5 | - `idempotency` package has moved from the `mollie` directory to the `pkg` directory. 6 | - `pagination` package has moved from the`mollie` directory to the `pkg` directory. 7 | - `connect` package has moved from the `mollie` directory to the `pkg` directory. 8 | - root namespace is not `github.com/VictorAvelar/mollie-api-go/v4/`. 9 | - Changes in all resources: 10 | - Data structures to describe request content in create, update and get operations are no longer the same, e.g. for `payments` there is a `CreatePayment` struct, a `UpdatePayment` struct and a `Payment` struct. This enables future extensions and modifications without having to check for cross request compatibility. 11 | - Data structures that describe available url parameters are consistently names `List{ResourceName}Options`. 12 | - Data structures that describe lists responses are consistently named as follows: e.g. for payments: `PaymentsList` 13 | - API aliases now use the parent objects, e.g. for settlements when listing payments the options passed to the request are using the `ListPaymentsOptions` object and not a local object. 14 | - All resources were checked for API consistency and parity, optional resources with custom types are now pointers to ensure proper json encoding and decoding to avoid issues as the one mentioned un #271 15 | - All resources embed a struct containing all the fields specific to access tokens, following this pattern the same happens for fields specific to Mollie connect 16 | 17 | ## Other changes 18 | 19 | - `testify.Suite` was removed from all testing. 20 | - Improvements for devcontainer files 21 | - Major versions of multiple github actions updated 22 | - Base `Dockerfile` using Go 1.22.x 23 | - Tests were update to use the new types. 24 | - Test coverage was slightly improved. 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/VictorAvelar/mollie-api-go/v4 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/google/go-querystring v1.1.0 7 | github.com/google/uuid v1.6.0 8 | github.com/stretchr/testify v1.10.0 9 | golang.org/x/oauth2 v0.30.0 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | gopkg.in/yaml.v3 v3.0.1 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 4 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 5 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 6 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 7 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 8 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 12 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 13 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 14 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 15 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 18 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 19 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | -------------------------------------------------------------------------------- /lychee.toml: -------------------------------------------------------------------------------- 1 | exclude_path = [".github", ".devcontainer", "wiki", "mollie", "testdata"] 2 | exclude = ['^https://github\.com', '^https://api\.mollie\.com'] 3 | cache = true 4 | max_redirects = 3 5 | require_https = true 6 | timeout = 10 7 | retry_wait_time = 15 8 | verbose = "error" 9 | no_progress = true 10 | -------------------------------------------------------------------------------- /mollie/app_fee.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | // ApplicationFee allows you to split a payment between a platform and connected merchant accounts. 4 | type ApplicationFee struct { 5 | Amount *Amount `json:"amount,omitempty"` 6 | Description string `json:"description,omitempty"` 7 | } 8 | -------------------------------------------------------------------------------- /mollie/captures.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | // CaptureMode describes the mode of a capture. 11 | type CaptureMode string 12 | 13 | // CaptureMode possible values. 14 | const ( 15 | AutomaticCapture CaptureMode = "automatic" 16 | ManualCapture CaptureMode = "manual" 17 | ) 18 | 19 | // CaptureStatus describes the status of a capture. 20 | type CaptureStatus string 21 | 22 | // CaptureStatus possible values. 23 | const ( 24 | CaptureStatusPending CaptureStatus = "pending" 25 | CaptureStatusSucceeded CaptureStatus = "succeeded" 26 | CaptureStatusFailed CaptureStatus = "failed" 27 | ) 28 | 29 | // CreateCapture describes the payload for creating a capture. 30 | type CreateCapture struct { 31 | Description string `json:"description,omitempty"` 32 | Metadata any `json:"metadata,omitempty"` 33 | Amount *Amount `json:"amount,omitempty"` 34 | CaptureAccessTokenFields 35 | } 36 | 37 | // CaptureAccessTokenFields describes the payload for creating a capture with an access token. 38 | type CaptureAccessTokenFields struct { 39 | Testmode bool `json:"testmode,omitempty"` 40 | } 41 | 42 | // Capture describes a single capture. 43 | // Captures are used for payments that have the authorize-then-capture flow. 44 | type Capture struct { 45 | Resource string `json:"resource,omitempty"` 46 | ID string `json:"id,omitempty"` 47 | Mode Mode `json:"mode,omitempty"` 48 | Amount *Amount `json:"amount,omitempty"` 49 | Status CaptureStatus `json:"status,omitempty"` 50 | SettlementAmount *Amount `json:"settlementAmount,omitempty"` 51 | PaymentID string `json:"paymentId,omitempty"` 52 | ShipmentID string `json:"shipmentId,omitempty"` 53 | SettlementID string `json:"settlementId,omitempty"` 54 | CreatedAt *time.Time `json:"createdAt,omitempty"` 55 | Metadata any `json:"metadata,omitempty"` 56 | Links CaptureLinks `json:"_links,omitempty"` 57 | CaptureAccessTokenFields 58 | } 59 | 60 | // CaptureLinks contains relevant links for a capture object. 61 | type CaptureLinks struct { 62 | Self *URL `json:"self,omitempty"` 63 | Payment *URL `json:"payment,omitempty"` 64 | Shipment *URL `json:"shipment,omitempty"` 65 | Settlement *URL `json:"settlement,omitempty"` 66 | Documentation *URL `json:"documentation,omitempty"` 67 | } 68 | 69 | // CaptureOptions describes the query params available to use when retrieving captures. 70 | // 71 | // See: https://docs.mollie.com/reference/get-capture#embedding-of-related-resources 72 | type CaptureOptions struct { 73 | Embed []EmbedValue `url:"embed,omitempty"` 74 | } 75 | 76 | // CapturesList describes a list of captures. 77 | type CapturesList struct { 78 | Count int `json:"count,omitempty"` 79 | Embedded struct { 80 | Captures []*Capture 81 | } `json:"_embedded,omitempty"` 82 | Links PaginationLinks `json:"_links,omitempty"` 83 | } 84 | 85 | // CapturesService operates over captures resource. 86 | type CapturesService service 87 | 88 | // Get retrieves a single capture by its ID. 89 | // Note the original payment’s ID is needed as well. 90 | // 91 | // See: https://docs.mollie.com/reference/get-capture 92 | func (cs *CapturesService) Get(ctx context.Context, payment, capture string, options *CaptureOptions) ( 93 | res *Response, 94 | c *Capture, 95 | err error, 96 | ) { 97 | u := fmt.Sprintf("v2/payments/%s/captures/%s", payment, capture) 98 | 99 | res, err = cs.client.get(ctx, u, options) 100 | if err != nil { 101 | return 102 | } 103 | 104 | if err = json.Unmarshal(res.content, &c); err != nil { 105 | return 106 | } 107 | 108 | return 109 | } 110 | 111 | // Create creates a new capture for a payment. 112 | // 113 | // See: https://docs.mollie.com/reference/create-capture 114 | func (cs *CapturesService) Create(ctx context.Context, payment string, capture CreateCapture) ( 115 | res *Response, 116 | c *Capture, 117 | err error, 118 | ) { 119 | u := fmt.Sprintf("v2/payments/%s/captures", payment) 120 | 121 | if cs.client.HasAccessToken() && cs.client.config.testing { 122 | capture.Testmode = true 123 | } 124 | 125 | res, err = cs.client.post(ctx, u, capture, nil) 126 | if err != nil { 127 | return 128 | } 129 | 130 | if err = json.Unmarshal(res.content, &c); err != nil { 131 | return 132 | } 133 | 134 | return 135 | } 136 | 137 | // List retrieves all captures for a certain payment. 138 | // 139 | // See: https://docs.mollie.com/reference/list-captures 140 | func (cs *CapturesService) List(ctx context.Context, payment string, options *CaptureOptions) ( 141 | res *Response, 142 | cl *CapturesList, 143 | err error, 144 | ) { 145 | u := fmt.Sprintf("v2/payments/%s/captures", payment) 146 | 147 | res, err = cs.client.get(ctx, u, options) 148 | if err != nil { 149 | return 150 | } 151 | 152 | if err = json.Unmarshal(res.content, &cl); err != nil { 153 | return 154 | } 155 | 156 | return 157 | } 158 | -------------------------------------------------------------------------------- /mollie/chargebacks.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | // Chargeback describes a forced transaction reversal initiated by the cardholder's bank. 11 | type Chargeback struct { 12 | Resource string `json:"resource,omitempty"` 13 | ID string `json:"id,omitempty"` 14 | PaymentID string `json:"paymentId,omitempty"` 15 | Amount *Amount `json:"amount,omitempty"` 16 | SettlementAmount *Amount `json:"settlementAmount,omitempty"` 17 | Reason *ChargebackReason `json:"reason,omitempty"` 18 | CreatedAt *time.Time `json:"createdAt,omitempty"` 19 | ReversedAt *time.Time `json:"reversedAt,omitempty"` 20 | Links ChargebackLinks `json:"_links,omitempty"` 21 | ChargebackAccessTokenFields 22 | } 23 | 24 | // ChargebackReason describes the reason for the chargeback as given by the bank. 25 | type ChargebackReason struct { 26 | Code string `json:"code,omitempty"` 27 | Description string `json:"description,omitempty"` 28 | } 29 | 30 | // ChargebackAccessTokenFields describes the fields to be used to create a chargeback access token. 31 | type ChargebackAccessTokenFields struct { 32 | ProfileID string `json:"profileId,omitempty"` 33 | Testmode bool `json:"testmode,omitempty"` 34 | } 35 | 36 | // ChargebackLinks describes all the possible links to be returned with 37 | // a chargeback object. 38 | type ChargebackLinks struct { 39 | Self *URL `json:"self,omitempty"` 40 | Payment *URL `json:"payment,omitempty"` 41 | Settlement *URL `json:"settlement,omitempty"` 42 | Documentation *URL `json:"documentation,omitempty"` 43 | } 44 | 45 | // ChargebackOptions describes chargeback endpoint valid query string parameters. 46 | type ChargebackOptions struct { 47 | Include []IncludeValue `url:"include,omitempty"` 48 | Embed []EmbedValue `url:"embed,omitempty"` 49 | } 50 | 51 | // ListChargebacksOptions describes list chargebacks endpoint valid query string parameters. 52 | type ListChargebacksOptions struct { 53 | From string `url:"from,omitempty"` 54 | Limit int `url:"limit,omitempty"` 55 | Include []IncludeValue `url:"include,omitempty"` 56 | Embed []EmbedValue `url:"embed,omitempty"` 57 | ProfileID string `url:"profileId,omitempty"` 58 | } 59 | 60 | // ChargebacksList describes how a list of chargebacks will be retrieved by Mollie. 61 | type ChargebacksList struct { 62 | Count int `json:"count,omitempty"` 63 | Embedded struct { 64 | Chargebacks []*Chargeback 65 | } `json:"_embedded,omitempty"` 66 | Links PaginationLinks `json:"_links,omitempty"` 67 | } 68 | 69 | // ChargebacksService instance operates over chargeback resources. 70 | type ChargebacksService service 71 | 72 | // Get retrieves a single chargeback by its ID. 73 | // Note the original payment’s ID is needed as well. 74 | // 75 | // See: https://docs.mollie.com/reference/get-chargeback 76 | func (cs *ChargebacksService) Get(ctx context.Context, payment, chargeback string, opts *ChargebackOptions) ( 77 | res *Response, 78 | p *Chargeback, 79 | err error, 80 | ) { 81 | u := fmt.Sprintf("v2/payments/%s/chargebacks/%s", payment, chargeback) 82 | 83 | res, err = cs.client.get(ctx, u, opts) 84 | if err != nil { 85 | return 86 | } 87 | 88 | if err = json.Unmarshal(res.content, &p); err != nil { 89 | return 90 | } 91 | 92 | return 93 | } 94 | 95 | // List retrieves a list of chargebacks associated with your account/organization. 96 | // 97 | // See: https://docs.mollie.com/reference/list-chargebacks 98 | func (cs *ChargebacksService) List(ctx context.Context, options *ListChargebacksOptions) ( 99 | res *Response, 100 | cl *ChargebacksList, 101 | err error, 102 | ) { 103 | return cs.list(ctx, "v2/chargebacks", options) 104 | } 105 | 106 | // ListForPayment retrieves a list of chargebacks associated with a single payment. 107 | // 108 | // See: https://docs.mollie.com/reference/list-chargebacks 109 | func (cs *ChargebacksService) ListForPayment(ctx context.Context, payment string, options *ListChargebacksOptions) ( 110 | res *Response, 111 | cl *ChargebacksList, 112 | err error, 113 | ) { 114 | return cs.list(ctx, fmt.Sprintf("v2/payments/%s/chargebacks", payment), options) 115 | } 116 | 117 | // encapsulates the shared list methods logic. 118 | func (cs *ChargebacksService) list(ctx context.Context, uri string, options interface{}) ( 119 | res *Response, 120 | cl *ChargebacksList, 121 | err error, 122 | ) { 123 | res, err = cs.client.get(ctx, uri, options) 124 | if err != nil { 125 | return 126 | } 127 | 128 | if err = json.Unmarshal(res.content, &cl); err != nil { 129 | return 130 | } 131 | 132 | return 133 | } 134 | -------------------------------------------------------------------------------- /mollie/client_links.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/google/go-querystring/query" 9 | ) 10 | 11 | // CreateClientLink contains information to link a new organization to an 12 | // OAuth application. 13 | type CreateClientLink struct { 14 | Owner Owner `json:"owner,omitempty"` 15 | Name string `json:"name,omitempty"` 16 | Address *Address `json:"address,omitempty"` 17 | RegistrationNumber string `json:"registrationNumber,omitempty"` 18 | VATNumber string `json:"vatNumber,omitempty"` 19 | } 20 | 21 | // ClientLinkLinks describes all the possible links to be returned with 22 | // a client links response object. 23 | type ClientLinkLinks struct { 24 | ClientLink *URL `json:"clientLink,omitempty"` 25 | Documentation *URL `json:"documentation,omitempty"` 26 | } 27 | 28 | // ClientLink object with redirect target. 29 | type ClientLink struct { 30 | ID string `json:"id,omitempty"` 31 | Resource string `json:"resource,omitempty"` 32 | Links ClientLinkLinks `json:"_links,omitempty"` 33 | } 34 | 35 | // ClientLinksService interacts with the Client Links API to create 36 | // new organizations for your customers. 37 | type ClientLinksService service 38 | 39 | // Create a client link based on the provided CreateClientLink values. 40 | // 41 | // See: https://docs.mollie.com/reference/create-client-link 42 | func (cls *ClientLinksService) Create(ctx context.Context, cd CreateClientLink) ( 43 | res *Response, 44 | cl *ClientLink, 45 | err error, 46 | ) { 47 | res, err = cls.client.post(ctx, "v2/client-links", cd, nil) 48 | if err != nil { 49 | return 50 | } 51 | 52 | if err = json.Unmarshal(res.content, &cl); err != nil { 53 | return 54 | } 55 | 56 | return 57 | } 58 | 59 | // ApprovalPromptAction represents possible actions to be performed 60 | // once the client link is created and redirected to the dashboard. 61 | type ApprovalPromptAction string 62 | 63 | // Possible approval prompt actions. 64 | const ( 65 | ForceApproval ApprovalPromptAction = "force" 66 | AutoApproval ApprovalPromptAction = "auto" 67 | ) 68 | 69 | // ClientLinkAuthorizeOptions subset of the parameters allowed for the Authorize endpoint. 70 | type ClientLinkAuthorizeOptions struct { 71 | ClientID string `url:"clientId,omitempty"` 72 | State string `url:"state,omitempty"` 73 | Scope []PermissionGrant `del:"+" url:"scope,omitempty"` 74 | ApprovalPrompt ApprovalPromptAction `url:"approvalPrompt,omitempty"` 75 | } 76 | 77 | // GetFinalClientLink returns the final client link URI with the provided options. 78 | func (cls *ClientLinksService) GetFinalClientLink( 79 | ctx context.Context, 80 | clientLink string, 81 | options *ClientLinkAuthorizeOptions, 82 | ) ( 83 | clientLinkURI string, 84 | ) { 85 | if options != nil { 86 | v, _ := query.Values(options) 87 | clientLinkURI = fmt.Sprintf("%s?%s", clientLink, v.Encode()) 88 | } 89 | 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /mollie/client_links_test.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/VictorAvelar/mollie-api-go/v4/testdata" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestClientLinkService_Create(t *testing.T) { 14 | setEnv() 15 | defer unsetEnv() 16 | 17 | type args struct { 18 | ctx context.Context 19 | cd CreateClientLink 20 | } 21 | 22 | cases := []struct { 23 | name string 24 | args args 25 | wantErr bool 26 | err error 27 | handler http.HandlerFunc 28 | pre func() 29 | }{ 30 | { 31 | "create new client link", 32 | args{ 33 | context.Background(), 34 | CreateClientLink{}, 35 | }, 36 | false, 37 | nil, 38 | func(w http.ResponseWriter, r *http.Request) { 39 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 40 | testMethod(t, r, "POST") 41 | if _, ok := r.Header[AuthHeader]; !ok { 42 | w.WriteHeader(http.StatusUnauthorized) 43 | } 44 | 45 | _, _ = w.Write([]byte(testdata.CreateClientLinkResponse)) 46 | }, 47 | noPre, 48 | }, 49 | { 50 | "create client link, an error is returned from the server", 51 | args{ 52 | context.Background(), 53 | CreateClientLink{}, 54 | }, 55 | true, 56 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 57 | errorHandler, 58 | noPre, 59 | }, 60 | { 61 | "create client link, an error occurs when parsing json", 62 | args{ 63 | context.Background(), 64 | CreateClientLink{}, 65 | }, 66 | true, 67 | fmt.Errorf("invalid character 'h' looking for beginning of object key string"), 68 | encodingHandler, 69 | noPre, 70 | }, 71 | { 72 | "create client link, invalid url when building request", 73 | args{ 74 | context.Background(), 75 | CreateClientLink{}, 76 | }, 77 | true, 78 | errBadBaseURL, 79 | errorHandler, 80 | crashSrv, 81 | }, 82 | } 83 | 84 | for _, c := range cases { 85 | setup() 86 | defer teardown() 87 | t.Run(c.name, func(t *testing.T) { 88 | tMux.HandleFunc( 89 | "/v2/client-links", 90 | c.handler, 91 | ) 92 | c.pre() 93 | 94 | res, cb, err := tClient.ClientLinks.Create(c.args.ctx, c.args.cd) 95 | if c.wantErr { 96 | assert.Error(t, err) 97 | assert.EqualError(t, err, c.err.Error()) 98 | } else { 99 | assert.Nil(t, err) 100 | assert.EqualValues(t, c.args.ctx, res.Request.Context()) 101 | assert.IsType(t, &ClientLink{}, cb) 102 | assert.IsType(t, &http.Response{}, res.Response) 103 | } 104 | }) 105 | } 106 | } 107 | 108 | func TestClientLinkService_GetFinalClientLink(t *testing.T) { 109 | setEnv() 110 | defer unsetEnv() 111 | 112 | type args struct { 113 | ctx context.Context 114 | clientLink string 115 | options *ClientLinkAuthorizeOptions 116 | } 117 | tests := []struct { 118 | name string 119 | args args 120 | wantClientLinkURI string 121 | }{ 122 | { 123 | "constructs client link finalize step correctly.", 124 | args{ 125 | context.Background(), 126 | "https://my.mollie.com/dashboard/client-link/finalize/csr_vZCnNQsV2UtfXxYifWKWH", 127 | &ClientLinkAuthorizeOptions{ 128 | ClientID: "app_j9Pakf56Ajta6Y65AkdTtAv", 129 | State: "unique_string_to_compare", 130 | Scope: []PermissionGrant{OnboardingRead, OnboardingWrite}, 131 | }, 132 | }, 133 | "https://my.mollie.com/dashboard/client-link/finalize/csr_vZCnNQsV2UtfXxYifWKWH?clientId=app_j9Pakf56Ajta6Y65AkdTtAv&scope=onboarding.read%2Bonbording.write&state=unique_string_to_compare", 134 | }, 135 | { 136 | "constructs client link finalize with complex values", 137 | args{ 138 | context.Background(), 139 | "https://my.mollie.com/dashboard/client-link/finalize/csr_vZCnNQsV2UtfXxYifWKWH", 140 | &ClientLinkAuthorizeOptions{ 141 | ClientID: "", 142 | State: "\ns\\s\\s\\s\n", 143 | Scope: []PermissionGrant{}, 144 | }, 145 | }, 146 | "https://my.mollie.com/dashboard/client-link/finalize/csr_vZCnNQsV2UtfXxYifWKWH?state=%0As%5Cs%5Cs%5Cs%0A", 147 | }, 148 | { 149 | "constructs client link finalize with no query params", 150 | args{ 151 | context.Background(), 152 | "https://my.mollie.com/dashboard/client-link/finalize/csr_vZCnNQsV2UtfXxYifWKWH", 153 | &ClientLinkAuthorizeOptions{}, 154 | }, 155 | "https://my.mollie.com/dashboard/client-link/finalize/csr_vZCnNQsV2UtfXxYifWKWH?", 156 | }, 157 | } 158 | 159 | setup() 160 | defer teardown() 161 | for _, tt := range tests { 162 | t.Run(tt.name, func(t *testing.T) { 163 | gotClientLinkURI := tClient.ClientLinks.GetFinalClientLink(tt.args.ctx, tt.args.clientLink, tt.args.options) 164 | 165 | assert.Equal(t, tt.wantClientLinkURI, gotClientLinkURI) 166 | }) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /mollie/clients.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | // LinkedClient describes a single client, linked to your partner account. 11 | type LinkedClient struct { 12 | Resource string `json:"resource,omitempty"` 13 | ID string `json:"id,omitempty"` 14 | OrganizationCreatedAt *time.Time `json:"organizationCreatedAt,omitempty"` 15 | Links LinkedClientLinks `json:"_links,omitempty"` 16 | } 17 | 18 | // LinkedClientLinks contains URL objects relevant to the client. 19 | type LinkedClientLinks struct { 20 | Self *URL `json:"self,omitempty"` 21 | Organization *URL `json:"organization,omitempty"` 22 | Onboarding *URL `json:"onboarding,omitempty"` 23 | Documentation *URL `json:"documentation,omitempty"` 24 | } 25 | 26 | // GetLinkedClientOptions contains valid query parameters for the get clients endpoint. 27 | type GetLinkedClientOptions struct { 28 | Embed []EmbedValue `url:"embed,omitempty"` 29 | } 30 | 31 | // LinkedClientList describes a list of partner clients. 32 | type LinkedClientList struct { 33 | Count int `json:"count,omitempty"` 34 | PartnerClients struct { 35 | Clients []*LinkedClient `json:"clients,omitempty"` 36 | } `json:"_embedded,omitempty"` 37 | Links PaginationLinks `json:"_links,omitempty"` 38 | } 39 | 40 | // ListLinkedClientsOptions contains valid query parameters for the list clients endpoint. 41 | type ListLinkedClientsOptions struct { 42 | Limit int `url:"limit,omitempty"` 43 | From string `url:"from,omitempty"` 44 | Embed []EmbedValue `url:"embed,omitempty"` 45 | } 46 | 47 | // ClientsService operates over the partners API. 48 | type ClientsService service 49 | 50 | // List retrieves all clients. 51 | // 52 | // See: https://docs.mollie.com/reference/list-clients 53 | func (ps *ClientsService) List(ctx context.Context, opts *ListLinkedClientsOptions) ( 54 | res *Response, 55 | pc *LinkedClientList, 56 | err error, 57 | ) { 58 | res, err = ps.client.get(ctx, "v2/clients", opts) 59 | if err != nil { 60 | return 61 | } 62 | 63 | if err = json.Unmarshal(res.content, &pc); err != nil { 64 | return 65 | } 66 | 67 | return 68 | } 69 | 70 | // Get retrieves a single client, linked to your partner account, by its ID. 71 | // 72 | // See: https://docs.mollie.com/reference/get-client 73 | func (ps *ClientsService) Get(ctx context.Context, id string, opts *GetLinkedClientOptions) ( 74 | res *Response, 75 | pc *LinkedClient, 76 | err error, 77 | ) { 78 | res, err = ps.client.get(ctx, fmt.Sprintf("v2/clients/%s", id), opts) 79 | if err != nil { 80 | return 81 | } 82 | 83 | if err = json.Unmarshal(res.content, &pc); err != nil { 84 | return 85 | } 86 | 87 | return 88 | } 89 | -------------------------------------------------------------------------------- /mollie/clients_test.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/VictorAvelar/mollie-api-go/v4/testdata" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestClientsService_Get(t *testing.T) { 14 | setEnv() 15 | defer unsetEnv() 16 | 17 | type args struct { 18 | ctx context.Context 19 | client string 20 | opts *GetLinkedClientOptions 21 | } 22 | 23 | cases := []struct { 24 | name string 25 | args args 26 | wantErr bool 27 | err error 28 | pre func() 29 | handler http.HandlerFunc 30 | }{ 31 | { 32 | "get partner client works as expected.", 33 | args{ 34 | context.Background(), 35 | "org_1337", 36 | nil, 37 | }, 38 | false, 39 | nil, 40 | noPre, 41 | func(w http.ResponseWriter, r *http.Request) { 42 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 43 | testMethod(t, r, "GET") 44 | 45 | if _, ok := r.Header[AuthHeader]; !ok { 46 | w.WriteHeader(http.StatusUnauthorized) 47 | } 48 | _, _ = w.Write([]byte(testdata.GetPartnerClientResponse)) 49 | }, 50 | }, 51 | { 52 | "get partner client with options works as expected.", 53 | args{ 54 | context.Background(), 55 | "org_1337", 56 | &GetLinkedClientOptions{ 57 | Embed: []EmbedValue{EmbedOrganization}, 58 | }, 59 | }, 60 | false, 61 | nil, 62 | noPre, 63 | func(w http.ResponseWriter, r *http.Request) { 64 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 65 | testMethod(t, r, "GET") 66 | testQuery(t, r, "embed=organization") 67 | 68 | if _, ok := r.Header[AuthHeader]; !ok { 69 | w.WriteHeader(http.StatusUnauthorized) 70 | } 71 | _, _ = w.Write([]byte(testdata.GetPartnerClientResponse)) 72 | }, 73 | }, 74 | { 75 | "get partner client, an error is returned from the server", 76 | args{ 77 | context.Background(), 78 | "org_1337", 79 | nil, 80 | }, 81 | true, 82 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 83 | noPre, 84 | errorHandler, 85 | }, 86 | { 87 | "get partner client, an error occurs when parsing json", 88 | args{ 89 | context.Background(), 90 | "org_1337", 91 | nil, 92 | }, 93 | true, 94 | fmt.Errorf("invalid character 'h' looking for beginning of object key string"), 95 | noPre, 96 | encodingHandler, 97 | }, 98 | { 99 | "get partner client, invalid url when building request", 100 | args{ 101 | context.Background(), 102 | "org_1337", 103 | nil, 104 | }, 105 | true, 106 | errBadBaseURL, 107 | crashSrv, 108 | errorHandler, 109 | }, 110 | } 111 | 112 | for _, c := range cases { 113 | setup() 114 | defer teardown() 115 | 116 | t.Run(c.name, func(t *testing.T) { 117 | c.pre() 118 | tMux.HandleFunc(fmt.Sprintf("/v2/clients/%s", c.args.client), c.handler) 119 | 120 | res, m, err := tClient.Clients.Get(c.args.ctx, c.args.client, c.args.opts) 121 | if c.wantErr { 122 | assert.NotNil(t, err) 123 | assert.EqualError(t, err, c.err.Error()) 124 | } else { 125 | assert.Nil(t, err) 126 | assert.IsType(t, &LinkedClient{}, m) 127 | assert.IsType(t, &http.Response{}, res.Response) 128 | } 129 | }) 130 | } 131 | } 132 | 133 | func TestClientService_List(t *testing.T) { 134 | setEnv() 135 | defer unsetEnv() 136 | 137 | type args struct { 138 | ctx context.Context 139 | client string 140 | opts *ListLinkedClientsOptions 141 | } 142 | 143 | cases := []struct { 144 | name string 145 | args args 146 | wantErr bool 147 | err error 148 | pre func() 149 | handler http.HandlerFunc 150 | }{ 151 | { 152 | "list partner client works as expected.", 153 | args{ 154 | context.Background(), 155 | "org_1337", 156 | nil, 157 | }, 158 | false, 159 | nil, 160 | noPre, 161 | func(w http.ResponseWriter, r *http.Request) { 162 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 163 | testMethod(t, r, "GET") 164 | 165 | if _, ok := r.Header[AuthHeader]; !ok { 166 | w.WriteHeader(http.StatusUnauthorized) 167 | } 168 | _, _ = w.Write([]byte(testdata.GetPartnerClientResponse)) 169 | }, 170 | }, 171 | { 172 | "list partner client with options works as expected.", 173 | args{ 174 | context.Background(), 175 | "org_1337", 176 | &ListLinkedClientsOptions{ 177 | Embed: []EmbedValue{EmbedOrganization}, 178 | }, 179 | }, 180 | false, 181 | nil, 182 | noPre, 183 | func(w http.ResponseWriter, r *http.Request) { 184 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 185 | testMethod(t, r, "GET") 186 | testQuery(t, r, "embed=organization") 187 | 188 | if _, ok := r.Header[AuthHeader]; !ok { 189 | w.WriteHeader(http.StatusUnauthorized) 190 | } 191 | _, _ = w.Write([]byte(testdata.GetPartnerClientResponse)) 192 | }, 193 | }, 194 | { 195 | "list partner client, an error is returned from the server", 196 | args{ 197 | context.Background(), 198 | "org_1337", 199 | nil, 200 | }, 201 | true, 202 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 203 | noPre, 204 | errorHandler, 205 | }, 206 | { 207 | "list partner client, an error occurs when parsing json", 208 | args{ 209 | context.Background(), 210 | "org_1337", 211 | nil, 212 | }, 213 | true, 214 | fmt.Errorf("invalid character 'h' looking for beginning of object key string"), 215 | noPre, 216 | encodingHandler, 217 | }, 218 | { 219 | "list partner client, invalid url when building request", 220 | args{ 221 | context.Background(), 222 | "org_1337", 223 | nil, 224 | }, 225 | true, 226 | errBadBaseURL, 227 | crashSrv, 228 | errorHandler, 229 | }, 230 | } 231 | 232 | for _, c := range cases { 233 | setup() 234 | defer teardown() 235 | 236 | t.Run(c.name, func(t *testing.T) { 237 | c.pre() 238 | tMux.HandleFunc("/v2/clients", c.handler) 239 | 240 | res, m, err := tClient.Clients.List(c.args.ctx, c.args.opts) 241 | if c.wantErr { 242 | assert.NotNil(t, err) 243 | assert.EqualError(t, err, c.err.Error()) 244 | } else { 245 | assert.Nil(t, err) 246 | assert.IsType(t, &LinkedClientList{}, m) 247 | assert.IsType(t, &http.Response{}, res.Response) 248 | } 249 | }) 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /mollie/common_types_test.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestShortDate_UnmarshalJSON(t *testing.T) { 11 | type args struct { 12 | b []byte 13 | } 14 | tests := []struct { 15 | name string 16 | args args 17 | wantErr bool 18 | }{ 19 | { 20 | "unmarshal fails with invalid date format", 21 | args{b: []byte("30-12-1991")}, 22 | true, 23 | }, 24 | { 25 | "unmarshal is successful", 26 | args{b: []byte("1991-12-30")}, 27 | false, 28 | }, 29 | } 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | d := &ShortDate{} 33 | err := d.UnmarshalJSON(tt.args.b) 34 | if tt.wantErr { 35 | assert.NotNil(t, err) 36 | } else { 37 | assert.Nil(t, err) 38 | } 39 | }) 40 | } 41 | } 42 | 43 | func TestShortDate_MarshalJSON(t *testing.T) { 44 | t.Run("marshal is successful", func(t *testing.T) { 45 | n := time.Now() 46 | d := &ShortDate{} 47 | d.Time = n 48 | _, err := d.MarshalJSON() 49 | assert.Nil(t, err) 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /mollie/config.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | // Config contains information that helps during the setup of a new Mollie client. 4 | type Config struct { 5 | testing bool 6 | auth string 7 | reqIdempotency bool 8 | } 9 | 10 | // ToggleTesting enables/disables the test-mode in the current Config. 11 | func (c *Config) ToggleTesting() bool { 12 | c.testing = !c.testing 13 | 14 | return c.testing 15 | } 16 | 17 | // ToggleIdempotency enables/disables the request idempotency feature 18 | // in the current Config. 19 | func (c *Config) ToggleIdempotency() bool { 20 | c.reqIdempotency = !c.reqIdempotency 21 | 22 | return c.reqIdempotency 23 | } 24 | 25 | // SwitchAuthStrategy changes the environment variable used to fetch the 26 | // auth tokens. 27 | // 28 | // Known values are: [MOLLIE_API_TOKEN,MOLLIE_ORG_TOKEN], if you use a custom 29 | // environment variable pass it as argument. 30 | func (c *Config) SwitchAuthStrategy(auth string) string { 31 | c.auth = auth 32 | 33 | return c.auth 34 | } 35 | 36 | /* Configuration init helpers. */ 37 | 38 | // NewConfig builds a Mollie configuration object, 39 | // it takes t to indicate if our client is meant to create requests for testing, 40 | // and auth to indicate the authentication method we want to use. 41 | func NewConfig(t bool, auth string) *Config { 42 | return createConfig(t, false, auth) 43 | } 44 | 45 | // NewAPITestingConfig builds a configuration object with the following settings: 46 | // tests mode: enabled 47 | // api token source: MOLLIE_API_TOKEN 48 | // 49 | // it receives `reqIdem (boolean)` to enable the request idempotency feature. 50 | func NewAPITestingConfig(reqIdem bool) *Config { 51 | return createConfig(true, reqIdem, APITokenEnv) 52 | } 53 | 54 | // NewAPIConfig builds a configuration object with the following settings: 55 | // tests mode: disabled 56 | // api token source: MOLLIE_API_TOKEN 57 | // 58 | // it receives `reqIdem (boolean)` to enable the request idempotency feature. 59 | func NewAPIConfig(reqIdem bool) *Config { 60 | return createConfig(false, reqIdem, APITokenEnv) 61 | } 62 | 63 | // NewOrgTestingConfig builds a configuration object with the following settings: 64 | // tests mode: enabled 65 | // api token source: MOLLIE_ORG_TOKEN 66 | // 67 | // it receives `reqIdem (boolean)` to enable the request idempotency feature. 68 | func NewOrgTestingConfig(reqIdem bool) *Config { 69 | return createConfig(true, reqIdem, OrgTokenEnv) 70 | } 71 | 72 | // NewOrgConfig builds a configuration object with the following settings: 73 | // tests mode: disabled 74 | // Org token source: MOLLIE_ORG_TOKEN 75 | // 76 | // it receives `reqIdem (boolean)` to enable the request idempotency feature. 77 | func NewOrgConfig(reqIdem bool) *Config { 78 | return createConfig(false, reqIdem, OrgTokenEnv) 79 | } 80 | 81 | func createConfig(test, reqIdem bool, auth string) *Config { 82 | return &Config{ 83 | testing: test, 84 | auth: auth, 85 | reqIdempotency: reqIdem, 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /mollie/config_test.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestConfig_ToggleTesting(t *testing.T) { 11 | c := NewAPITestingConfig(false) 12 | 13 | assert.True(t, c.testing) 14 | c.ToggleTesting() 15 | assert.False(t, c.testing) 16 | } 17 | 18 | func TestConfig_ToggleIdempotency(t *testing.T) { 19 | c := NewAPITestingConfig(false) 20 | 21 | assert.False(t, c.reqIdempotency) 22 | assert.True(t, c.ToggleIdempotency()) 23 | assert.True(t, c.reqIdempotency) 24 | } 25 | 26 | func TestConfig_SwitchAuthStrategy(t *testing.T) { 27 | c := NewAPITestingConfig(false) 28 | 29 | assert.Equal(t, APITokenEnv, c.auth) 30 | c.SwitchAuthStrategy(OrgTokenEnv) 31 | assert.Equal(t, OrgTokenEnv, c.auth) 32 | } 33 | 34 | func TestNewConfig(t *testing.T) { 35 | type args struct { 36 | t bool 37 | auth string 38 | } 39 | tests := []struct { 40 | name string 41 | args args 42 | want *Config 43 | }{ 44 | { 45 | "config set to testing and with API token", 46 | args{ 47 | t: true, 48 | auth: APITokenEnv, 49 | }, 50 | &Config{ 51 | testing: true, 52 | auth: "MOLLIE_API_TOKEN", 53 | }, 54 | }, 55 | { 56 | "config set to testing and with ORG token", 57 | args{ 58 | t: true, 59 | auth: OrgTokenEnv, 60 | }, 61 | &Config{ 62 | testing: true, 63 | auth: "MOLLIE_ORG_TOKEN", 64 | }, 65 | }, 66 | { 67 | "config set to production and with ORG token", 68 | args{ 69 | t: true, 70 | auth: OrgTokenEnv, 71 | }, 72 | &Config{ 73 | testing: true, 74 | auth: "MOLLIE_ORG_TOKEN", 75 | }, 76 | }, 77 | { 78 | "config set to production and with API token", 79 | args{ 80 | t: true, 81 | auth: APITokenEnv, 82 | }, 83 | &Config{ 84 | testing: true, 85 | auth: "MOLLIE_API_TOKEN", 86 | }, 87 | }, 88 | } 89 | for _, tt := range tests { 90 | t.Run(tt.name, func(t *testing.T) { 91 | got := NewConfig(tt.args.t, tt.args.auth) 92 | assert.Equal(t, got, tt.want) 93 | }) 94 | } 95 | } 96 | 97 | // ExampleNewConfig demonstrates how to initialize a Config 98 | // struct with the specified values for testing and token source. 99 | func ExampleNewConfig() { 100 | conf := NewConfig(true, APITokenEnv) 101 | fmt.Printf( 102 | "testing config, testing: %v, req_idempotency: %v, token source: %s.", 103 | conf.testing, 104 | conf.reqIdempotency, 105 | conf.auth, 106 | ) 107 | // Output: testing config, testing: true, req_idempotency: false, token source: MOLLIE_API_TOKEN. 108 | } 109 | 110 | // ExampleNewAPITestingConfig demonstrates how to initialize a Config 111 | // struct with testing mode enabled, token source from the default API 112 | // token env variable (MOLLIE_API_TOKEN) and request idempotency feature 113 | // enabled. 114 | func ExampleNewAPITestingConfig() { 115 | conf := NewAPITestingConfig(true) 116 | fmt.Printf( 117 | "testing api config, testing: %v, req_idempotency: %v, token source: %s.", 118 | conf.testing, 119 | conf.reqIdempotency, 120 | conf.auth, 121 | ) 122 | // Output: testing api config, testing: true, req_idempotency: true, token source: MOLLIE_API_TOKEN. 123 | } 124 | 125 | // ExampleNewOrgTestingConfig demonstrates how to initialize a Config 126 | // struct with testing mode enabled, token source from the default Org 127 | // token env variable (MOLLIE_ORG_TOKEN) and request idempotency feature 128 | // enabled. 129 | func ExampleNewOrgTestingConfig() { 130 | conf := NewOrgTestingConfig(true) 131 | fmt.Printf( 132 | "testing org config, testing: %v, req_idempotency: %v, token source: %s.", 133 | conf.testing, 134 | conf.reqIdempotency, 135 | conf.auth, 136 | ) 137 | // Output: testing org config, testing: true, req_idempotency: true, token source: MOLLIE_ORG_TOKEN. 138 | } 139 | 140 | // ExampleNewAPIConfig demonstrates how to initialize a Config 141 | // struct with testing mode disabled, token source from the default API 142 | // token env variable (MOLLIE_API_TOKEN) and request idempotency feature 143 | // enabled. 144 | func ExampleNewAPIConfig() { 145 | conf := NewAPIConfig(true) 146 | fmt.Printf( 147 | "testing api config, testing: %v, req_idempotency: %v, token source: %s.", 148 | conf.testing, 149 | conf.reqIdempotency, 150 | conf.auth, 151 | ) 152 | // Output: testing api config, testing: false, req_idempotency: true, token source: MOLLIE_API_TOKEN. 153 | } 154 | 155 | // ExampleNewOrgConfig demonstrates how to initialize a Config struct 156 | // with testing mode disabled, token source from the default Org token 157 | // env variable (MOLLIE_ORG_TOKEN) and request idempotency feature enabled. 158 | func ExampleNewOrgConfig() { 159 | conf := NewOrgConfig(true) 160 | fmt.Printf( 161 | "testing org config, testing: %v, req_idempotency: %v, token source: %s.", 162 | conf.testing, 163 | conf.reqIdempotency, 164 | conf.auth, 165 | ) 166 | // Output: testing org config, testing: false, req_idempotency: true, token source: MOLLIE_ORG_TOKEN. 167 | } 168 | -------------------------------------------------------------------------------- /mollie/custom_types.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // ContextValues is a map of TransactionType to ContextValue. 8 | type ContextValues map[TransactionType]ContextValue 9 | 10 | // UnmarshalJSON implements the json.Unmarshaler interface on ContextValues. 11 | // 12 | // See: https://github.com/VictorAvelar/mollie-api-go/issues/251 13 | func (cv *ContextValues) UnmarshalJSON(data []byte) error { 14 | var d map[TransactionType]ContextValue 15 | 16 | if err := json.Unmarshal(data, &d); err != nil { 17 | if _, ok := err.(*json.UnmarshalTypeError); ok { 18 | *cv = make(ContextValues) 19 | 20 | return nil 21 | } 22 | 23 | return err 24 | } 25 | 26 | *cv = ContextValues(d) 27 | 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /mollie/custom_types_test.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-querystring/query" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestContextValues_UnmarshalJSON(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | data []byte 14 | want ContextValues 15 | wantErr bool 16 | }{ 17 | // Add test cases here 18 | { 19 | name: "Correct decoding JSON", 20 | data: []byte(`{"type1": "value1", "type2": "value2"}`), 21 | want: ContextValues{ 22 | "type1": "value1", 23 | "type2": "value2", 24 | }, 25 | wantErr: false, 26 | }, 27 | { 28 | name: "Handle empty JSON", 29 | data: []byte(`{}`), 30 | want: ContextValues{}, 31 | wantErr: false, 32 | }, 33 | { 34 | name: "Invalid JSON", 35 | data: []byte(`{"type1": "value1", "type2": "value2"`), 36 | want: make(ContextValues), 37 | wantErr: true, 38 | }, 39 | { 40 | name: "Incorrect type in json returns an empty map", 41 | data: []byte(`{"type1":["value1", "value2"]}`), 42 | want: make(ContextValues), 43 | wantErr: false, 44 | }, 45 | { 46 | name: "Test correct case described on issue #251", 47 | data: []byte(`{"context": { 48 | "paymentId": "tr_xxxxxxxx" 49 | }}`), 50 | want: make(ContextValues), 51 | wantErr: false, 52 | }, 53 | { 54 | name: "Test failing case described on issue #251", 55 | data: []byte(`{"context": []}`), 56 | want: make(ContextValues), 57 | wantErr: false, 58 | }, 59 | } 60 | 61 | for _, tt := range tests { 62 | t.Run(tt.name, func(t *testing.T) { 63 | var cv ContextValues 64 | err := cv.UnmarshalJSON(tt.data) 65 | 66 | if tt.wantErr { 67 | assert.Error(t, err) 68 | } else { 69 | assert.NoError(t, err) 70 | assert.Equal(t, tt.want, cv) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | func TestAmount_URLEncodingSimple(t *testing.T) { 77 | tests := []struct { 78 | name string 79 | a Amount 80 | want string 81 | }{ 82 | { 83 | name: "Test URL encoding simple.", 84 | a: Amount{ 85 | Value: "10.00", 86 | Currency: "EUR", 87 | }, 88 | want: "currency=EUR&value=10.00", 89 | }, 90 | } 91 | for _, tt := range tests { 92 | t.Run(tt.name, func(t *testing.T) { 93 | v, err := query.Values(tt.a) 94 | assert.Nil(t, err) 95 | assert.Equal(t, tt.want, v.Encode()) 96 | }) 97 | } 98 | } 99 | 100 | func TestAmount_URLEncodingNested(t *testing.T) { 101 | tests := []struct { 102 | name string 103 | a struct { 104 | Amount Amount `url:"amount"` 105 | } 106 | want string 107 | }{ 108 | { 109 | name: "Test URL encoding nested amount in struct.", 110 | a: struct { 111 | Amount Amount `url:"amount"` 112 | }{ 113 | Amount{ 114 | Value: "10.00", 115 | Currency: "EUR", 116 | }, 117 | }, 118 | want: "amount%5Bcurrency%5D=EUR&amount%5Bvalue%5D=10.00", 119 | }, 120 | } 121 | for _, tt := range tests { 122 | t.Run(tt.name, func(t *testing.T) { 123 | v, err := query.Values(tt.a) 124 | assert.Nil(t, err) 125 | assert.Equal(t, tt.want, v.Encode()) 126 | }) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /mollie/customers.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | // CreateCustomer contains the parameters to create a customer. 11 | type CreateCustomer struct { 12 | Name string `json:"name,omitempty"` 13 | Email string `json:"email,omitempty"` 14 | Locale Locale `json:"locale,omitempty"` 15 | Metadata any `json:"metadata,omitempty"` 16 | } 17 | 18 | // UpdateCustomer contains the parameters to update a customer. 19 | type UpdateCustomer struct { 20 | Name string `json:"name,omitempty"` 21 | Email string `json:"email,omitempty"` 22 | Locale Locale `json:"locale,omitempty"` 23 | Metadata any `json:"metadata,omitempty"` 24 | } 25 | 26 | // CustomerLinks contains the HAL resources for a customer response. 27 | type CustomerLinks struct { 28 | Self *URL `json:"self,omitempty"` 29 | Dashboard *URL `json:"dashboard,omitempty"` 30 | Mandates *URL `json:"mandates,omitempty"` 31 | Subscriptions *URL `json:"subscriptions,omitempty"` 32 | Payments *URL `json:"payments,omitempty"` 33 | Documentation *URL `json:"documentation,omitempty"` 34 | } 35 | 36 | // Customer represents buyers. 37 | type Customer struct { 38 | Resource string `json:"resource,omitempty"` 39 | ID string `json:"id,omitempty"` 40 | Mode Mode `json:"mode,omitempty"` 41 | Name string `json:"name,omitempty"` 42 | Email string `json:"email,omitempty"` 43 | Locale Locale `json:"locale,omitempty"` 44 | Metadata any `json:"metadata,omitempty"` 45 | CreatedAt *time.Time `json:"createdAt,omitempty"` 46 | Links CustomerLinks `json:"_links,omitempty"` 47 | } 48 | 49 | // ListCustomersOptions contains valid query parameters for the list customers endpoint. 50 | type ListCustomersOptions struct { 51 | From string `url:"from,omitempty"` 52 | Limit int `url:"limit,omitempty"` 53 | ProfileID string `url:"profileId,omitempty"` 54 | SequenceType SequenceType `url:"sequenceType,omitempty"` 55 | RedirectURL string `url:"redirectUrl,omitempty"` 56 | } 57 | 58 | // CustomersList contains a embedded list of customers 59 | // wrapped in a standard Mollie paginated response. 60 | type CustomersList struct { 61 | Count int `json:"count,omitempty"` 62 | Embedded struct { 63 | Customers []*Customer `json:"customers,omitempty"` 64 | } `json:"_embedded,omitempty"` 65 | Links PaginationLinks `json:"links,omitempty"` 66 | } 67 | 68 | // CustomersService operates over the customer resource. 69 | type CustomersService service 70 | 71 | // Get finds a customer by its ID. 72 | // 73 | // See: https://docs.mollie.com/reference/get-customer 74 | func (cs *CustomersService) Get(ctx context.Context, id string) (res *Response, c *Customer, err error) { 75 | u := fmt.Sprintf("v2/customers/%s", id) 76 | 77 | res, err = cs.client.get(ctx, u, nil) 78 | if err != nil { 79 | return 80 | } 81 | 82 | if err = json.Unmarshal(res.content, &c); err != nil { 83 | return 84 | } 85 | 86 | return 87 | } 88 | 89 | // Create creates a simple minimal representation of a customer in the Mollie API 90 | // to use for the Mollie Checkout and Recurring features. 91 | // 92 | // See: https://docs.mollie.com/reference/create-customer 93 | func (cs *CustomersService) Create(ctx context.Context, c CreateCustomer) (res *Response, cc *Customer, err error) { 94 | res, err = cs.client.post(ctx, "v2/customers", c, nil) 95 | if err != nil { 96 | return 97 | } 98 | 99 | if err = json.Unmarshal(res.content, &cc); err != nil { 100 | return 101 | } 102 | 103 | return 104 | } 105 | 106 | // Update an existing customer. 107 | // 108 | // See: https://docs.mollie.com/reference/update-customer 109 | func (cs *CustomersService) Update(ctx context.Context, id string, c UpdateCustomer) ( 110 | res *Response, 111 | cc *Customer, 112 | err error, 113 | ) { 114 | u := fmt.Sprintf("v2/customers/%s", id) 115 | 116 | res, err = cs.client.patch(ctx, u, c) 117 | if err != nil { 118 | return 119 | } 120 | 121 | if err = json.Unmarshal(res.content, &cc); err != nil { 122 | return 123 | } 124 | 125 | return 126 | } 127 | 128 | // Delete a customer. 129 | // 130 | // All mandates and subscriptions created for this customer will be canceled as well. 131 | // 132 | // See: https://docs.mollie.com/reference/delete-customer 133 | func (cs *CustomersService) Delete(ctx context.Context, id string) (res *Response, err error) { 134 | u := fmt.Sprintf("v2/customers/%s", id) 135 | 136 | res, err = cs.client.delete(ctx, u) 137 | if err != nil { 138 | return 139 | } 140 | 141 | return 142 | } 143 | 144 | // List retrieves all customers created. 145 | // 146 | // See: https://docs.mollie.com/reference/list-customers 147 | func (cs *CustomersService) List(ctx context.Context, options *ListCustomersOptions) ( 148 | res *Response, 149 | cl *CustomersList, 150 | err error, 151 | ) { 152 | res, err = cs.list(ctx, "v2/customers", options) 153 | if err != nil { 154 | return 155 | } 156 | 157 | if err = json.Unmarshal(res.content, &cl); err != nil { 158 | return 159 | } 160 | 161 | return 162 | } 163 | 164 | // GetPayments retrieves all payments linked to the customer. 165 | // 166 | // See: https://docs.mollie.com/reference/list-customer-payments 167 | func (cs *CustomersService) GetPayments(ctx context.Context, id string, options *ListCustomersOptions) ( 168 | res *Response, 169 | pl *PaymentList, 170 | err error, 171 | ) { 172 | u := fmt.Sprintf("v2/customers/%s/payments", id) 173 | 174 | res, err = cs.list(ctx, u, options) 175 | if err != nil { 176 | return 177 | } 178 | 179 | if err = json.Unmarshal(res.content, &pl); err != nil { 180 | return 181 | } 182 | 183 | return 184 | } 185 | 186 | // CreatePayment creates a payment for the customer. 187 | // 188 | // See: https://docs.mollie.com/reference/create-customer-payment 189 | func (cs *CustomersService) CreatePayment(ctx context.Context, id string, p CreatePayment) ( 190 | res *Response, 191 | pp *Payment, 192 | err error, 193 | ) { 194 | u := fmt.Sprintf("v2/customers/%s/payments", id) 195 | 196 | res, err = cs.client.post(ctx, u, p, nil) 197 | if err != nil { 198 | return 199 | } 200 | 201 | if err = json.Unmarshal(res.content, &pp); err != nil { 202 | return 203 | } 204 | 205 | return 206 | } 207 | 208 | func (cs *CustomersService) list(ctx context.Context, uri string, options interface{}) (r *Response, err error) { 209 | r, err = cs.client.get(ctx, uri, options) 210 | if err != nil { 211 | return 212 | } 213 | 214 | return 215 | } 216 | -------------------------------------------------------------------------------- /mollie/doc.go: -------------------------------------------------------------------------------- 1 | // Package mollie is a wrapper around Mollie's REST API. 2 | // 3 | // See: https://www.mollie.com/developers 4 | // 5 | // The Mollie API is a straightforward REST API. This means all endpoints 6 | // either create, retrieve, or update objects. API calls are authenticated 7 | // with an [API credential](https://docs.mollie.com/reference/authentication). 8 | // In some cases, you can receive live updates via [webhooks](https://docs.mollie.com/reference/webhooks). 9 | package mollie 10 | -------------------------------------------------------------------------------- /mollie/errors.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import "fmt" 4 | 5 | // ErrorLinks container references to common urls 6 | // returned with errors. 7 | type ErrorLinks struct { 8 | Documentation *URL `json:"documentation,omitempty"` 9 | } 10 | 11 | // BaseError contains the general error structure 12 | // returned by mollie. 13 | type BaseError struct { 14 | Status int `json:"status,omitempty"` 15 | Title string `json:"title,omitempty"` 16 | Detail string `json:"detail,omitempty"` 17 | Field string `json:"field,omitempty"` 18 | Links *ErrorLinks `json:"_links,omitempty"` 19 | } 20 | 21 | // Error interface compliance. 22 | func (be *BaseError) Error() string { 23 | str := fmt.Sprintf("%d %s: %s", be.Status, be.Title, be.Detail) 24 | 25 | if len(be.Field) > 0 { 26 | str = fmt.Sprintf("%s, affected field: %s", str, be.Field) 27 | } 28 | 29 | return str 30 | } 31 | -------------------------------------------------------------------------------- /mollie/errors_test.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/VictorAvelar/mollie-api-go/v4/testdata" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestBaseError(t *testing.T) { 12 | cases := []struct { 13 | name string 14 | body []byte 15 | wantField bool 16 | }{ 17 | { 18 | "standard base error", 19 | []byte(testdata.UnauthorizedErrorResponse), 20 | false, 21 | }, 22 | { 23 | "error with field included", 24 | []byte(testdata.UnprocessableEntityErrorResponse), 25 | true, 26 | }, 27 | } 28 | 29 | for _, c := range cases { 30 | g := &BaseError{} 31 | t.Run(c.name, func(t *testing.T) { 32 | err := json.Unmarshal(c.body, g) 33 | assert.Nil(t, err) 34 | if c.wantField { 35 | assert.Contains(t, g.Error(), "field") 36 | } else { 37 | assert.NotContains(t, g.Error(), "field") 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mollie/gift_cards.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | // GiftCardIssuer type describes issuers supported 4 | // by mollie. 5 | type GiftCardIssuer string 6 | 7 | // Supported gift card issuers. 8 | // #nosec G101 -- This are the brands issuing gift cards. 9 | const ( 10 | BloemenCadeuKaart GiftCardIssuer = "bloemencadeaukaart" 11 | BloemPlantGiftCard GiftCardIssuer = "bloemplantgiftcard" 12 | Boekenbon GiftCardIssuer = "boekenbon" 13 | DaGiftCard GiftCardIssuer = "dagiftcard" 14 | DecaudeuKaart GiftCardIssuer = "decadeaukaart" 15 | DelokaleDecauKaart GiftCardIssuer = "delokalecadeaukaart" 16 | Dinercadeau GiftCardIssuer = "dinercadeau" 17 | Doenkadotickets GiftCardIssuer = "doenkadotickets" 18 | Fashioncheque GiftCardIssuer = "fashioncheque" 19 | Festivalcadeau GiftCardIssuer = "festivalcadeau" 20 | Good4fun GiftCardIssuer = "good4fun" 21 | Horseandgifts GiftCardIssuer = "horseandgifts" 22 | HuistuinCadeauKaart GiftCardIssuer = "huistuincadeaukaart" 23 | JewelCard GiftCardIssuer = "jewelcard" 24 | KlusCadeu GiftCardIssuer = "kluscadeau" 25 | Kunstencultuurcadeaukaart GiftCardIssuer = "kunstencultuurcadeaukaart" 26 | Nationalebioscoopbon GiftCardIssuer = "nationalebioscoopbon" 27 | Nationaleentertainmentcard GiftCardIssuer = "nationaleentertainmentcard" 28 | Nationalegolfbon GiftCardIssuer = "nationalegolfbon" 29 | Ohmygood GiftCardIssuer = "ohmygood" 30 | Podiumcadeaukaart GiftCardIssuer = "podiumcadeaukaart" 31 | Reiscadeau GiftCardIssuer = "reiscadeau" 32 | Restaurantcadeau GiftCardIssuer = "restaurantcadeau" 33 | Shoesandsneakerscadeu GiftCardIssuer = "shoesandsneakerscadeau" 34 | SodexoSportCulturePass GiftCardIssuer = "sodexosportculturepass" 35 | Sportenfitcadeau GiftCardIssuer = "sportenfitcadeau" 36 | Sustainablefashion GiftCardIssuer = "sustainablefashion" 37 | Travelcheq GiftCardIssuer = "travelcheq" 38 | Vvvgiftcard GiftCardIssuer = "vvvgiftcard" 39 | Vvvdinercheque GiftCardIssuer = "vvvdinercheque" 40 | Vvvlekkerweg GiftCardIssuer = "vvvlekkerweg" 41 | Webshopgiftcard GiftCardIssuer = "webshopgiftcard" 42 | Wijncadeukaart GiftCardIssuer = "wijncadeaukaart" 43 | Yourgift GiftCardIssuer = "yourgift" 44 | ) 45 | 46 | // IssuerStatus describes the status of a gift 47 | // card issuer in your account. 48 | type IssuerStatus string 49 | 50 | // Valid issuer statuses. 51 | const ( 52 | PendingIssuer IssuerStatus = "pending-issuer" 53 | EnabledIssuer IssuerStatus = "enabled" 54 | ) 55 | 56 | // GiftCardEnabled describes the response of a gift card 57 | // issuer enable operation. 58 | type GiftCardEnabled struct { 59 | Resource string `json:"resource,omitempty"` 60 | ID GiftCardIssuer `json:"id,omitempty"` 61 | Description string `json:"description,omitempty"` 62 | Status IssuerStatus `json:"status,omitempty"` 63 | Links GiftCardLinks `json:"_links,omitempty"` 64 | } 65 | 66 | // GiftCardLinks are links embedded when a gift card is enabled. 67 | type GiftCardLinks struct { 68 | Self *URL `json:"self,omitempty"` 69 | Documentation *URL `json:"documentation,omitempty"` 70 | } 71 | -------------------------------------------------------------------------------- /mollie/invoices.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | // InvoiceStatus status of the invoice. 10 | type InvoiceStatus string 11 | 12 | // Valid status of the invoice. 13 | const ( 14 | InvoiceStatusOpen InvoiceStatus = "open" 15 | InvoiceStatusPaid InvoiceStatus = "paid" 16 | InvoiceStatusOverdue InvoiceStatus = "overdue" 17 | ) 18 | 19 | // Invoice describes an invoice details. 20 | type Invoice struct { 21 | Resource string `json:"resource,omitempty"` 22 | ID string `json:"id,omitempty"` 23 | Reference string `json:"reference,omitempty"` 24 | VatNumber string `json:"vatNumber,omitempty"` 25 | IssuedAt string `json:"issuedAt,omitempty"` 26 | PaidAt string `json:"paidAt,omitempty"` 27 | DueAt string `json:"dueAt,omitempty"` 28 | NetAmount *Amount `json:"netAmount,omitempty"` 29 | VatAmount *Amount `json:"vatAmount,omitempty"` 30 | GrossAmount *Amount `json:"grossAmount,omitempty"` 31 | Lines []*LineItem `json:"lines,omitempty"` 32 | Status InvoiceStatus `json:"status,omitempty"` 33 | Links InvoiceLinks `json:"_links,omitempty"` 34 | } 35 | 36 | // LineItem product details. 37 | type LineItem struct { 38 | Count int64 `json:"count,omitempty"` 39 | VatPercentage float64 `json:"vatPercentage,omitempty"` 40 | Period string `json:"period,omitempty"` 41 | Description string `json:"description,omitempty"` 42 | Amount *Amount `json:"amount,omitempty"` 43 | } 44 | 45 | // InvoiceLinks describes all the possible links to be returned with 46 | // a invoice object. 47 | type InvoiceLinks struct { 48 | Self *URL `json:"self,omitempty"` 49 | PDF *URL `json:"pdf,omitempty"` 50 | Documentation *URL `json:"documentation,omitempty"` 51 | } 52 | 53 | // ListInvoicesOptions describes list invoices endpoint valid query string parameters. 54 | type ListInvoicesOptions struct { 55 | Limit int64 `url:"limit,omitempty"` 56 | Reference string `url:"reference,omitempty"` 57 | Year string `url:"year,omitempty"` 58 | From string `url:"from,omitempty"` 59 | } 60 | 61 | // InvoicesList describes how a list of invoices will be retrieved by Mollie. 62 | type InvoicesList struct { 63 | Count int `json:"count,omitempty"` 64 | Embedded struct { 65 | Invoices []*Invoice `json:"invoices"` 66 | } `json:"_embedded,omitempty"` 67 | Links PaginationLinks `json:"_links,omitempty"` 68 | } 69 | 70 | // InvoicesService instance operates over invoice resources. 71 | type InvoicesService service 72 | 73 | // Get retrieve details of an invoice, using the invoice’s identifier. 74 | func (is *InvoicesService) Get(ctx context.Context, id string) (res *Response, i *Invoice, err error) { 75 | u := fmt.Sprintf("v2/invoices/%s", id) 76 | 77 | res, err = is.client.get(ctx, u, nil) 78 | if err != nil { 79 | return 80 | } 81 | 82 | if err = json.Unmarshal(res.content, &i); err != nil { 83 | return 84 | } 85 | 86 | return 87 | } 88 | 89 | // List retrieves a list of invoices associated with your account/organization. 90 | func (is *InvoicesService) List(ctx context.Context, options *ListInvoicesOptions) ( 91 | res *Response, 92 | il *InvoicesList, 93 | err error, 94 | ) { 95 | res, err = is.client.get(ctx, "v2/invoices", options) 96 | if err != nil { 97 | return 98 | } 99 | 100 | if err = json.Unmarshal(res.content, &il); err != nil { 101 | return 102 | } 103 | 104 | return 105 | } 106 | -------------------------------------------------------------------------------- /mollie/invoices_test.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | "testing" 9 | "time" 10 | 11 | "github.com/VictorAvelar/mollie-api-go/v4/testdata" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestInvoicesService_Get(t *testing.T) { 16 | setEnv() 17 | defer unsetEnv() 18 | 19 | type args struct { 20 | ctx context.Context 21 | invoice string 22 | options *ListInvoicesOptions 23 | } 24 | 25 | cases := []struct { 26 | name string 27 | args args 28 | wantErr bool 29 | err error 30 | pre func() 31 | handler http.HandlerFunc 32 | }{ 33 | { 34 | "get invoice works as expecter", 35 | args{ 36 | context.Background(), 37 | "inv_xBEbP9rvAq", 38 | nil, 39 | }, 40 | false, 41 | nil, 42 | noPre, 43 | func(w http.ResponseWriter, r *http.Request) { 44 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 45 | testMethod(t, r, "GET") 46 | if _, ok := r.Header[AuthHeader]; !ok { 47 | w.WriteHeader(http.StatusUnauthorized) 48 | } 49 | 50 | w.WriteHeader(http.StatusOK) 51 | _, _ = w.Write([]byte(testdata.GetInvoiceResponse)) 52 | }, 53 | }, 54 | { 55 | "get invoice, an error is returned from the server", 56 | args{ 57 | context.Background(), 58 | "inv_xBEbP9rvAq", 59 | nil, 60 | }, 61 | true, 62 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 63 | noPre, 64 | errorHandler, 65 | }, 66 | { 67 | "get invoice, an error occurs when parsing json", 68 | args{ 69 | context.Background(), 70 | "inv_xBEbP9rvAq", 71 | nil, 72 | }, 73 | true, 74 | fmt.Errorf("invalid character 'h' looking for beginning of object key string"), 75 | noPre, 76 | encodingHandler, 77 | }, 78 | { 79 | "get invoice, invalid url when building request", 80 | args{ 81 | context.Background(), 82 | "inv_xBEbP9rvAq", 83 | nil, 84 | }, 85 | true, 86 | errBadBaseURL, 87 | crashSrv, 88 | errorHandler, 89 | }, 90 | } 91 | 92 | for _, c := range cases { 93 | setup() 94 | defer teardown() 95 | t.Run(c.name, func(t *testing.T) { 96 | c.pre() 97 | tMux.HandleFunc(fmt.Sprintf("/v2/invoices/%s", c.args.invoice), c.handler) 98 | 99 | res, i, err := tClient.Invoices.Get(c.args.ctx, c.args.invoice) 100 | if c.wantErr { 101 | assert.NotNil(t, err) 102 | assert.EqualError(t, err, c.err.Error()) 103 | } else { 104 | assert.Nil(t, err) 105 | assert.IsType(t, &Invoice{}, i) 106 | assert.EqualValues(t, c.args.ctx, res.Request.Context()) 107 | assert.IsType(t, &http.Response{}, res.Response) 108 | } 109 | }) 110 | } 111 | } 112 | 113 | func TestInvoicesService_List(t *testing.T) { 114 | setEnv() 115 | defer unsetEnv() 116 | 117 | type args struct { 118 | ctx context.Context 119 | invoice string 120 | options *ListInvoicesOptions 121 | } 122 | 123 | cases := []struct { 124 | name string 125 | args args 126 | wantErr bool 127 | err error 128 | pre func() 129 | handler http.HandlerFunc 130 | }{ 131 | { 132 | "list invoices works as expecter", 133 | args{ 134 | context.Background(), 135 | "inv_xBEbP9rvAq", 136 | nil, 137 | }, 138 | false, 139 | nil, 140 | noPre, 141 | func(w http.ResponseWriter, r *http.Request) { 142 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 143 | testMethod(t, r, "GET") 144 | if _, ok := r.Header[AuthHeader]; !ok { 145 | w.WriteHeader(http.StatusUnauthorized) 146 | } 147 | 148 | w.WriteHeader(http.StatusOK) 149 | _, _ = w.Write([]byte(testdata.ListInvoicesResponse)) 150 | }, 151 | }, 152 | { 153 | "list invoices with options works as expecter", 154 | args{ 155 | context.Background(), 156 | "inv_xBEbP9rvAq", 157 | &ListInvoicesOptions{ 158 | Year: strconv.Itoa(time.Now().Year()), 159 | }, 160 | }, 161 | false, 162 | nil, 163 | noPre, 164 | func(w http.ResponseWriter, r *http.Request) { 165 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 166 | testMethod(t, r, "GET") 167 | if _, ok := r.Header[AuthHeader]; !ok { 168 | w.WriteHeader(http.StatusUnauthorized) 169 | } 170 | 171 | w.WriteHeader(http.StatusOK) 172 | _, _ = w.Write([]byte(testdata.ListInvoicesResponse)) 173 | }, 174 | }, 175 | { 176 | "list invoices, an error is returned from the server", 177 | args{ 178 | context.Background(), 179 | "inv_xBEbP9rvAq", 180 | nil, 181 | }, 182 | true, 183 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 184 | noPre, 185 | errorHandler, 186 | }, 187 | { 188 | "list invoices, an error occurs when parsing json", 189 | args{ 190 | context.Background(), 191 | "inv_xBEbP9rvAq", 192 | nil, 193 | }, 194 | true, 195 | fmt.Errorf("invalid character 'h' looking for beginning of object key string"), 196 | noPre, 197 | encodingHandler, 198 | }, 199 | { 200 | "list invoices, invalid url when building request", 201 | args{ 202 | context.Background(), 203 | "inv_xBEbP9rvAq", 204 | nil, 205 | }, 206 | true, 207 | errBadBaseURL, 208 | crashSrv, 209 | errorHandler, 210 | }, 211 | } 212 | 213 | for _, c := range cases { 214 | setup() 215 | defer teardown() 216 | t.Run(c.name, func(t *testing.T) { 217 | c.pre() 218 | tMux.HandleFunc("/v2/invoices", c.handler) 219 | 220 | res, i, err := tClient.Invoices.List(c.args.ctx, c.args.options) 221 | if c.wantErr { 222 | assert.NotNil(t, err) 223 | assert.EqualError(t, err, c.err.Error()) 224 | } else { 225 | assert.Nil(t, err) 226 | assert.IsType(t, &InvoicesList{}, i) 227 | assert.EqualValues(t, c.args.ctx, res.Request.Context()) 228 | assert.IsType(t, &http.Response{}, res.Response) 229 | } 230 | }) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /mollie/onboarding.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "time" 7 | ) 8 | 9 | const onboardingURLPath = "v2/onboarding/me" 10 | 11 | // OnboardingStatus describes status of the organization’s onboarding process. 12 | type OnboardingStatus string 13 | 14 | // Possible status values. 15 | const ( 16 | NeedsDataOnboardingStatus OnboardingStatus = "needs-data" 17 | InReviewOnboardingStatus OnboardingStatus = "in-review" 18 | CompletedOnboardingStatus OnboardingStatus = "completed" 19 | ) 20 | 21 | // OnboardingLinks contains URL objects relevant to the onboarding status. 22 | type OnboardingLinks struct { 23 | Self *URL `json:"self,omitempty"` 24 | Dashboard *URL `json:"dashboard,omitempty"` 25 | Organization *URL `json:"organization,omitempty"` 26 | Documentation *URL `json:"documentation,omitempty"` 27 | } 28 | 29 | // Onboarding data for an organization. 30 | type Onboarding struct { 31 | CanReceivePayments bool `json:"canReceivePayments,omitempty"` 32 | CanReceiveSettlements bool `json:"canReceiveSettlements,omitempty"` 33 | Resource string `json:"reference,omitempty"` 34 | Name string `json:"name,omitempty"` 35 | SignedUpAt *time.Time `json:"signedUpAt,omitempty"` 36 | Status OnboardingStatus `json:"status,omitempty"` 37 | Links OnboardingLinks `json:"_links,omitempty"` 38 | } 39 | 40 | // OnboardingData request possible values. 41 | // 42 | // Please note that even though all parameters are optional, 43 | // at least one of them needs to be provided in the request. 44 | // 45 | // Information that the merchant has entered in their dashboard will not be overwritten. 46 | 47 | // OnboardingDataOrganization contains data of the organization you want to provide. 48 | type OnboardingDataOrganization struct { 49 | Name string `json:"name,omitempty"` 50 | RegistrationNumber string `json:"registrationNumber,omitempty"` 51 | VatNumber string `json:"vatNumber,omitempty"` 52 | VatRegulation string `json:"vatRegulation,omitempty"` 53 | Address *Address `json:"address,omitempty"` 54 | } 55 | 56 | // OnboardingDataProfile contains data of the payment profile you want to provide. 57 | type OnboardingDataProfile struct { 58 | Name string `json:"name,omitempty"` 59 | URL string `json:"url,omitempty"` 60 | Email string `json:"email,omitempty"` 61 | Description string `json:"description,omitempty"` 62 | Phone string `json:"phone,omitempty"` 63 | BusinessCategory BusinessCategory `json:"businessCategory,omitempty"` 64 | } 65 | 66 | // OnboardingData describes the information to be submitted. 67 | type OnboardingData struct { 68 | Organization OnboardingDataOrganization `json:"organization,omitempty"` 69 | Profile OnboardingDataProfile `json:"profile,omitempty"` 70 | } 71 | 72 | // OnboardingService operates over the onboarding API. 73 | type OnboardingService service 74 | 75 | // GetOnboardingStatus gets the status of onboarding of the authenticated organization. 76 | // 77 | // See: https://docs.mollie.com/reference/get-onboarding-status 78 | func (os *OnboardingService) GetOnboardingStatus(ctx context.Context) (res *Response, o *Onboarding, err error) { 79 | res, err = os.client.get(ctx, onboardingURLPath, nil) 80 | if err != nil { 81 | return 82 | } 83 | 84 | if err = json.Unmarshal(res.content, &o); err != nil { 85 | return 86 | } 87 | 88 | return 89 | } 90 | 91 | // SubmitOnboardingData sends data that will be prefilled in the merchant’s onboarding. 92 | // Please note that the data you submit will only be processed when the onboarding status is needs-data. 93 | // 94 | // This endpoint has been deprecated. It will be supported for the foreseeable future, but new implementations should 95 | // use the Create client link endpoint to create new clients and submit their organization’s details in one go. 96 | // 97 | // See: https://docs.mollie.com/reference/submit-onboarding-data 98 | func (os *OnboardingService) SubmitOnboardingData(ctx context.Context, d *OnboardingData) (res *Response, err error) { 99 | res, err = os.client.post(ctx, onboardingURLPath, d, nil) 100 | if err != nil { 101 | return 102 | } 103 | 104 | return 105 | } 106 | -------------------------------------------------------------------------------- /mollie/onboarding_test.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/VictorAvelar/mollie-api-go/v4/testdata" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestOnboardingService_GetOnboardingStatus(t *testing.T) { 14 | setEnv() 15 | defer unsetEnv() 16 | 17 | cases := []struct { 18 | name string 19 | wantErr bool 20 | err error 21 | pre func() 22 | handler http.HandlerFunc 23 | }{ 24 | { 25 | "get onboarding status works as expected.", 26 | false, 27 | nil, 28 | noPre, 29 | func(w http.ResponseWriter, r *http.Request) { 30 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 31 | testMethod(t, r, "GET") 32 | 33 | if _, ok := r.Header[AuthHeader]; !ok { 34 | w.WriteHeader(http.StatusUnauthorized) 35 | } 36 | _, _ = w.Write([]byte(testdata.GetOnboardingStatusResponse)) 37 | }, 38 | }, 39 | { 40 | "get onboarding status, an error is returned from the server", 41 | true, 42 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 43 | noPre, 44 | errorHandler, 45 | }, 46 | { 47 | "get onboarding status, an error occurs when parsing json", 48 | true, 49 | fmt.Errorf("invalid character 'h' looking for beginning of object key string"), 50 | noPre, 51 | encodingHandler, 52 | }, 53 | { 54 | "get onboarding status, invalid url when building request", 55 | true, 56 | errBadBaseURL, 57 | crashSrv, 58 | errorHandler, 59 | }, 60 | } 61 | 62 | for _, c := range cases { 63 | setup() 64 | defer teardown() 65 | 66 | t.Run(c.name, func(t *testing.T) { 67 | c.pre() 68 | tMux.HandleFunc("/v2/onboarding/me", c.handler) 69 | 70 | res, m, err := tClient.Onboarding.GetOnboardingStatus(context.Background()) 71 | if c.wantErr { 72 | assert.NotNil(t, err) 73 | assert.EqualError(t, err, c.err.Error()) 74 | } else { 75 | assert.Nil(t, err) 76 | assert.IsType(t, &Onboarding{}, m) 77 | assert.IsType(t, &http.Response{}, res.Response) 78 | } 79 | }) 80 | } 81 | } 82 | 83 | func TestOnboardingService_SubmitOnboardingData(t *testing.T) { 84 | setEnv() 85 | defer unsetEnv() 86 | 87 | cases := []struct { 88 | name string 89 | data *OnboardingData 90 | wantErr bool 91 | err error 92 | pre func() 93 | handler http.HandlerFunc 94 | }{ 95 | { 96 | "get onboarding status works as expected.", 97 | &OnboardingData{}, 98 | false, 99 | nil, 100 | noPre, 101 | func(w http.ResponseWriter, r *http.Request) { 102 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 103 | testMethod(t, r, "POST") 104 | 105 | if _, ok := r.Header[AuthHeader]; !ok { 106 | w.WriteHeader(http.StatusUnauthorized) 107 | } 108 | _, _ = w.Write([]byte(testdata.GetOnboardingStatusResponse)) 109 | }, 110 | }, 111 | { 112 | "get onboarding status, an error is returned from the server", 113 | &OnboardingData{}, 114 | true, 115 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 116 | noPre, 117 | errorHandler, 118 | }, 119 | { 120 | "get onboarding status, invalid url when building request", 121 | &OnboardingData{}, 122 | true, 123 | errBadBaseURL, 124 | crashSrv, 125 | errorHandler, 126 | }, 127 | } 128 | 129 | for _, c := range cases { 130 | setup() 131 | defer teardown() 132 | 133 | t.Run(c.name, func(t *testing.T) { 134 | c.pre() 135 | tMux.HandleFunc("/v2/onboarding/me", c.handler) 136 | 137 | res, err := tClient.Onboarding.SubmitOnboardingData(context.Background(), c.data) 138 | if c.wantErr { 139 | assert.NotNil(t, err) 140 | assert.EqualError(t, err, c.err.Error()) 141 | } else { 142 | assert.Nil(t, err) 143 | assert.IsType(t, &http.Response{}, res.Response) 144 | } 145 | }) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /mollie/organizations.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | // Organization describes an organization detail. 11 | type Organization struct { 12 | Resource string `json:"resource,omitempty"` 13 | ID string `json:"id,omitempty"` 14 | Name string `json:"name,omitempty"` 15 | Email string `json:"email,omitempty"` 16 | Locale string `json:"locale,omitempty"` 17 | Address *Address `json:"address,omitempty"` 18 | RegistrationNumber string `json:"registrationNumber,omitempty"` 19 | VatNumber string `json:"vatNumber,omitempty"` 20 | VatRegulation string `json:"vatRegulation,omitempty"` 21 | Links OrganizationLinks `json:"_links,omitempty"` 22 | } 23 | 24 | // OrganizationLinks describes all the possible links to be returned with 25 | // a organization object. 26 | type OrganizationLinks struct { 27 | Self *URL `json:"self,omitempty"` 28 | Chargebacks *URL `json:"chargebacks,omitempty"` 29 | Customers *URL `json:"customers,omitempty"` 30 | Dashboard *URL `json:"dashboard,omitempty"` 31 | Invoices *URL `json:"invoices,omitempty"` 32 | Payments *URL `json:"payments,omitempty"` 33 | Profiles *URL `json:"profiles,omitempty"` 34 | Refunds *URL `json:"refunds,omitempty"` 35 | Settlements *URL `json:"settlements,omitempty"` 36 | Documentation *URL `json:"documentation,omitempty"` 37 | } 38 | 39 | // PartnerType alias for organization partner types. 40 | type PartnerType string 41 | 42 | // Available partner types. 43 | const ( 44 | PartnerTypeOauth PartnerType = "oauth" 45 | PartnerTypeSignUpLink PartnerType = "signuplink" 46 | PartnerTypeUserAgent PartnerType = "useragent" 47 | ) 48 | 49 | // UserAgentToken are time limited valid access tokens. 50 | type UserAgentToken struct { 51 | Token string 52 | StartsAt *time.Time 53 | EndsAt *time.Time 54 | } 55 | 56 | // OrganizationPartnerLinks is an object with several URL objects 57 | // relevant to the partner resource. 58 | type OrganizationPartnerLinks struct { 59 | Self *URL `json:"self,omitempty"` 60 | Documentation *URL `json:"documentation,omitempty"` 61 | SignUpLink *URL `json:"signuplink,omitempty"` 62 | } 63 | 64 | // OrganizationPartnerStatus response descriptor. 65 | type OrganizationPartnerStatus struct { 66 | IsCommissionPartner bool `json:"isCommissionPartner,omitempty"` 67 | PartnerContractUpdateAvailable bool `json:"partnerContractUpdate_available,omitempty"` 68 | Resource string `json:"resource,omitempty"` 69 | PartnerType PartnerType `json:"partnerType,omitempty"` 70 | UserAgentTokens []*UserAgentToken `json:"userAgentTokens,omitempty"` 71 | PartnerContractSignedAt *time.Time `json:"partnerContractSignedAt,omitempty"` 72 | PartnerContractExpiresAt *time.Time `json:"partnerContractExpiresAt,omitempty"` 73 | Links OrganizationPartnerLinks `json:"_links,omitempty"` 74 | } 75 | 76 | // OrganizationsService instance operates over organization resources. 77 | type OrganizationsService service 78 | 79 | // Get retrieve an organization by its id. 80 | func (os *OrganizationsService) Get(ctx context.Context, id string) (res *Response, o *Organization, err error) { 81 | return os.get(ctx, fmt.Sprintf("v2/organizations/%s", id)) 82 | } 83 | 84 | // GetCurrent retrieve the currently authenticated organization. 85 | func (os *OrganizationsService) GetCurrent(ctx context.Context) (res *Response, o *Organization, err error) { 86 | return os.get(ctx, "v2/organizations/me") 87 | } 88 | 89 | // GetPartnerStatus retrieves details about the partner status 90 | // of the currently authenticated organization. 91 | // 92 | // See: https://docs.mollie.com/reference/get-partner-status 93 | func (os *OrganizationsService) GetPartnerStatus(ctx context.Context) ( 94 | res *Response, 95 | ops *OrganizationPartnerStatus, 96 | err error, 97 | ) { 98 | res, err = os.client.get(ctx, "v2/organizations/me/partner", nil) 99 | if err != nil { 100 | return 101 | } 102 | 103 | if err = json.Unmarshal(res.content, &ops); err != nil { 104 | return 105 | } 106 | 107 | return 108 | } 109 | 110 | func (os *OrganizationsService) get(ctx context.Context, uri string) (res *Response, o *Organization, err error) { 111 | res, err = os.client.get(ctx, uri, nil) 112 | if err != nil { 113 | return 114 | } 115 | 116 | if err = json.Unmarshal(res.content, &o); err != nil { 117 | return 118 | } 119 | 120 | return 121 | } 122 | -------------------------------------------------------------------------------- /mollie/payment_methods.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | // PaymentMethodStatus tels the status that the method is in. 10 | // Possible values: activated pending-boarding pending-review 11 | // pending-external rejected. 12 | type PaymentMethodStatus string 13 | 14 | // Available payment method statuses. 15 | const ( 16 | PaymentMethodActivated PaymentMethodStatus = "activated" 17 | PaymentMethodPendingBoarding PaymentMethodStatus = "pending-boarding" 18 | PaymentMethodPendingReview PaymentMethodStatus = "pending-review" 19 | PaymentMethodPendingExternal PaymentMethodStatus = "pending-external" 20 | PaymentMethodRejected PaymentMethodStatus = "pending-rejected" 21 | ) 22 | 23 | // PaymentMethodDetails describes a single method with details. 24 | type PaymentMethodDetails struct { 25 | Resource string `json:"resource,omitempty"` 26 | ID string `json:"id,omitempty"` 27 | Description string `json:"description,omitempty"` 28 | MinimumAmount *Amount `json:"minimumAmount,omitempty"` 29 | MaximumAmount *Amount `json:"maximumAmount,omitempty"` 30 | Image *Image `json:"image,omitempty"` 31 | Pricing []*PaymentMethodPricing `json:"pricing,omitempty"` 32 | Issuers []*PaymentMethodIssuer `json:"issuers,omitempty"` 33 | Status *PaymentMethodStatus `json:"status,omitempty"` 34 | Links MethodsLinks `json:"_links,omitempty"` 35 | } 36 | 37 | // MethodsLinks describes links attached to methods service responses. 38 | type MethodsLinks struct { 39 | Self *URL `json:"self,omitempty"` 40 | Documentation *URL `json:"documentation,omitempty"` 41 | } 42 | 43 | // PaymentMethodPricing contains information about commissions and fees 44 | // applicable to a payment method. 45 | type PaymentMethodPricing struct { 46 | Description string `json:"description,omitempty"` 47 | Variable string `json:"variable,omitempty"` 48 | Fixed *Amount `json:"fixed,omitempty"` 49 | FeeRegion FeeRegion `json:"feeRegion,omitempty"` 50 | } 51 | 52 | // PaymentMethodIssuer available for the payment method 53 | // (for iDEAL, KBC/CBC payment button, gift cards, or meal vouchers). 54 | type PaymentMethodIssuer struct { 55 | Resource string `json:"resource,omitempty"` 56 | ID string `json:"id,omitempty"` 57 | Name string `json:"name,omitempty"` 58 | Image *Image `json:"image,omitempty"` 59 | } 60 | 61 | // PaymentMethodsList describes a list of paginated payment methods. 62 | type PaymentMethodsList struct { 63 | Count int `json:"count,omitempty"` 64 | Embedded struct { 65 | Methods []*PaymentMethodDetails 66 | } `json:"_embedded,omitempty"` 67 | Links PaginationLinks `json:"_links,omitempty"` 68 | } 69 | 70 | // PaymentMethodOptions are applicable query string parameters to get methods 71 | // from mollie's API. 72 | type PaymentMethodOptions struct { 73 | Locale Locale `url:"locale,omitempty"` 74 | Currency string `url:"currency,omitempty"` 75 | ProfileID string `url:"profileId,omitempty"` 76 | Include []IncludeValue `url:"include,omitempty"` 77 | } 78 | 79 | // ListPaymentMethodsOptions are applicable query string parameters to list methods 80 | // from mollie's API. 81 | // 82 | // It contains list specific options and embeds GetMethodOptions. 83 | type ListPaymentMethodsOptions struct { 84 | PaymentMethodOptions 85 | Resource string `url:"resource,omitempty"` 86 | BillingCountry string `url:"billingCountry,omitempty"` 87 | Amount *Amount `url:"amount,omitempty"` 88 | IncludeWallets []Wallet `url:"includeWallets,omitempty"` 89 | OrderLineCategories []OrderLineOperationProductCategory `url:"orderLineCategories,omitempty"` 90 | Locale Locale `url:"locale,omitempty"` 91 | SequenceType SequenceType `url:"sequenceType,omitempty"` 92 | } 93 | 94 | // PaymentMethodsService operates on methods endpoints. 95 | type PaymentMethodsService service 96 | 97 | // Get returns information about the payment method specified by id, 98 | // it also receives a pointer to the method options containing applicable 99 | // query string parameters. 100 | // 101 | // See: https://docs.mollie.com/reference/get-method 102 | func (ms *PaymentMethodsService) Get(ctx context.Context, id PaymentMethod, options *PaymentMethodOptions) ( 103 | res *Response, 104 | pmd *PaymentMethodDetails, 105 | err error, 106 | ) { 107 | u := fmt.Sprintf("v2/methods/%s", id) 108 | 109 | res, err = ms.client.get(ctx, u, options) 110 | if err != nil { 111 | return 112 | } 113 | 114 | if err = json.Unmarshal(res.content, &pmd); err != nil { 115 | return 116 | } 117 | 118 | return 119 | } 120 | 121 | // All retrieves all the payment methods enabled for your account/organization. 122 | // 123 | // See: https://docs.mollie.com/reference/list-all-methods 124 | func (ms *PaymentMethodsService) All(ctx context.Context, options *ListPaymentMethodsOptions) ( 125 | res *Response, 126 | pm *PaymentMethodsList, 127 | err error, 128 | ) { 129 | return ms.list(ctx, "v2/methods/all", options) 130 | } 131 | 132 | // List retrieves all enabled payment methods. 133 | // 134 | // The results are not paginated. 135 | // 136 | // See: https://docs.mollie.com/reference/list-methods 137 | func (ms *PaymentMethodsService) List(ctx context.Context, options *ListPaymentMethodsOptions) ( 138 | res *Response, 139 | pm *PaymentMethodsList, 140 | err error, 141 | ) { 142 | return ms.list(ctx, "v2/methods", options) 143 | } 144 | 145 | func (ms *PaymentMethodsService) list(ctx context.Context, uri string, options interface{}) ( 146 | res *Response, 147 | pm *PaymentMethodsList, 148 | err error, 149 | ) { 150 | res, err = ms.client.get(ctx, uri, options) 151 | if err != nil { 152 | return 153 | } 154 | 155 | if err = json.Unmarshal(res.content, &pm); err != nil { 156 | return 157 | } 158 | 159 | return 160 | } 161 | -------------------------------------------------------------------------------- /mollie/permissions.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | // PermissionGrant defines supported permissions. 10 | type PermissionGrant string 11 | 12 | // Available permission grants. 13 | const ( 14 | PaymentsRead PermissionGrant = "payments.read" 15 | PaymentsWrite PermissionGrant = "payments.write" 16 | RefundsRead PermissionGrant = "refunds.read" 17 | RefundsWrite PermissionGrant = "refunds.write" 18 | CustomersRead PermissionGrant = "customers.read" 19 | CustomersWrite PermissionGrant = "customers.write" 20 | MandatesRead PermissionGrant = "mandates.read" 21 | MandatesWrite PermissionGrant = "mandates.write" 22 | SubscriptionsRead PermissionGrant = "subscriptions.read" 23 | SubscriptionsWrite PermissionGrant = "subscriptions.write" 24 | ProfilesRead PermissionGrant = "profiles.read" 25 | ProfilesWrite PermissionGrant = "profiles.write" 26 | InvoicesRead PermissionGrant = "invoices.read" 27 | SettlementsRead PermissionGrant = "settlements.read" 28 | OrdersRead PermissionGrant = "orders.read" 29 | OrdersWrite PermissionGrant = "orders.write" 30 | ShipmentsRead PermissionGrant = "shipments.read" 31 | ShipmentsWrite PermissionGrant = "shipments.write" 32 | OrganizationsRead PermissionGrant = "organizations.read" 33 | OrganizationsWrite PermissionGrant = "organizations.write" 34 | OnboardingRead PermissionGrant = "onboarding.read" 35 | OnboardingWrite PermissionGrant = "onbording.write" 36 | PaymentLinksRead PermissionGrant = "payment-links.read" 37 | PaymentLinksWrite PermissionGrant = "payment-links.write" 38 | BalancesRead PermissionGrant = "balances.read" 39 | TerminalsRead PermissionGrant = "terminals.read" 40 | TerminalsWrite PermissionGrant = "terminals.write" 41 | ) 42 | 43 | // Permission represents an action that 44 | // can be performed by any API actor. 45 | type Permission struct { 46 | Granted bool `json:"granted,omitempty"` 47 | Resource string `json:"resource,omitempty"` 48 | Description string `json:"description,omitempty"` 49 | ID PermissionGrant `json:"id,omitempty"` 50 | Links PermissionLinks `json:"_links,omitempty"` 51 | } 52 | 53 | // PermissionLinks contains URL objects that make 54 | // reference to an http address related to permissions. 55 | type PermissionLinks struct { 56 | Self *URL `json:"self,omitempty"` 57 | Documentation *URL `json:"documentation,omitempty"` 58 | } 59 | 60 | // PermissionsList lists all the permissions given to an 61 | // API actor. 62 | type PermissionsList struct { 63 | Count int `json:"count,omitempty"` 64 | Embedded struct { 65 | Permissions []*Permission `json:"permissions,omitempty"` 66 | } `json:"_embedded,omitempty"` 67 | Links PermissionLinks `json:"_links,omitempty"` 68 | } 69 | 70 | // PermissionsService operates over permission resources. 71 | type PermissionsService service 72 | 73 | // Get returns a permission by its id. 74 | // 75 | // See: https://docs.mollie.com/reference/get-permission 76 | func (ps *PermissionsService) Get(ctx context.Context, id PermissionGrant) (res *Response, p *Permission, err error) { 77 | res, err = ps.client.get(ctx, fmt.Sprintf("v2/permissions/%s", id), nil) 78 | if err != nil { 79 | return 80 | } 81 | 82 | if err = json.Unmarshal(res.content, &p); err != nil { 83 | return 84 | } 85 | 86 | return 87 | } 88 | 89 | // List retrieves all permissions available with the current app access token. 90 | // The list is not paginated. 91 | // 92 | // See: https://docs.mollie.com/reference/list-permissions 93 | func (ps *PermissionsService) List(ctx context.Context) (res *Response, pl *PermissionsList, err error) { 94 | res, err = ps.client.get(ctx, "v2/permissions", nil) 95 | if err != nil { 96 | return 97 | } 98 | 99 | if err = json.Unmarshal(res.content, &pl); err != nil { 100 | return 101 | } 102 | 103 | return 104 | } 105 | -------------------------------------------------------------------------------- /mollie/permissions_test.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/VictorAvelar/mollie-api-go/v4/testdata" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestPermissionsService_Get(t *testing.T) { 14 | setEnv() 15 | defer unsetEnv() 16 | 17 | type args struct { 18 | ctx context.Context 19 | permission PermissionGrant 20 | } 21 | cases := []struct { 22 | name string 23 | args args 24 | wantErr bool 25 | err error 26 | pre func() 27 | handler http.HandlerFunc 28 | }{ 29 | { 30 | "get permission works as expected.", 31 | args{ 32 | context.Background(), 33 | PaymentsRead, 34 | }, 35 | false, 36 | nil, 37 | func() { 38 | tClient.WithAuthenticationValue("access_X12b31ggg23") 39 | }, 40 | func(w http.ResponseWriter, r *http.Request) { 41 | testHeader(t, r, AuthHeader, "Bearer access_X12b31ggg23") 42 | testMethod(t, r, "GET") 43 | testQuery(t, r, "testmode=true") 44 | 45 | if _, ok := r.Header[AuthHeader]; !ok { 46 | w.WriteHeader(http.StatusUnauthorized) 47 | } 48 | _, _ = w.Write([]byte(testdata.GetPermissionsResponse)) 49 | }, 50 | }, 51 | { 52 | "get permission, an error is returned from the server", 53 | args{ 54 | context.Background(), 55 | PaymentsWrite, 56 | }, 57 | true, 58 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 59 | noPre, 60 | errorHandler, 61 | }, 62 | { 63 | "get permission, an error occurs when parsing json", 64 | args{ 65 | context.Background(), 66 | PaymentsWrite, 67 | }, 68 | true, 69 | fmt.Errorf("invalid character 'h' looking for beginning of object key string"), 70 | noPre, 71 | encodingHandler, 72 | }, 73 | { 74 | "get permission, invalid url when building request", 75 | args{ 76 | context.Background(), 77 | PaymentsWrite, 78 | }, 79 | true, 80 | errBadBaseURL, 81 | crashSrv, 82 | errorHandler, 83 | }, 84 | } 85 | 86 | for _, c := range cases { 87 | setup() 88 | defer teardown() 89 | 90 | t.Run(c.name, func(t *testing.T) { 91 | c.pre() 92 | tMux.HandleFunc(fmt.Sprintf("/v2/permissions/%s", c.args.permission), c.handler) 93 | 94 | res, m, err := tClient.Permissions.Get(c.args.ctx, c.args.permission) 95 | if c.wantErr { 96 | assert.NotNil(t, err) 97 | assert.EqualError(t, err, c.err.Error()) 98 | } else { 99 | assert.Nil(t, err) 100 | assert.IsType(t, &Permission{}, m) 101 | assert.IsType(t, &http.Response{}, res.Response) 102 | } 103 | }) 104 | } 105 | } 106 | 107 | func TestPermissionsService_List(t *testing.T) { 108 | setEnv() 109 | defer unsetEnv() 110 | 111 | type args struct { 112 | ctx context.Context 113 | } 114 | cases := []struct { 115 | name string 116 | args args 117 | wantErr bool 118 | err error 119 | pre func() 120 | handler http.HandlerFunc 121 | }{ 122 | { 123 | "get permission works as expected.", 124 | args{ 125 | context.Background(), 126 | }, 127 | false, 128 | nil, 129 | noPre, 130 | func(w http.ResponseWriter, r *http.Request) { 131 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 132 | testMethod(t, r, "GET") 133 | 134 | if _, ok := r.Header[AuthHeader]; !ok { 135 | w.WriteHeader(http.StatusUnauthorized) 136 | } 137 | _, _ = w.Write([]byte(testdata.GetPermissionsResponse)) 138 | }, 139 | }, 140 | { 141 | "get permission, an error is returned from the server", 142 | args{ 143 | context.Background(), 144 | }, 145 | true, 146 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 147 | noPre, 148 | errorHandler, 149 | }, 150 | { 151 | "get permission, an error occurs when parsing json", 152 | args{ 153 | context.Background(), 154 | }, 155 | true, 156 | fmt.Errorf("invalid character 'h' looking for beginning of object key string"), 157 | noPre, 158 | encodingHandler, 159 | }, 160 | { 161 | "get permission, invalid url when building request", 162 | args{ 163 | context.Background(), 164 | }, 165 | true, 166 | errBadBaseURL, 167 | crashSrv, 168 | errorHandler, 169 | }, 170 | } 171 | 172 | setEnv() 173 | defer unsetEnv() 174 | for _, c := range cases { 175 | setup() 176 | defer teardown() 177 | t.Run(c.name, func(t *testing.T) { 178 | c.pre() 179 | tMux.HandleFunc("/v2/permissions", c.handler) 180 | 181 | res, m, err := tClient.Permissions.List(c.args.ctx) 182 | if c.wantErr { 183 | assert.NotNil(t, err) 184 | assert.EqualError(t, err, c.err.Error()) 185 | } else { 186 | assert.Nil(t, err) 187 | assert.IsType(t, &PermissionsList{}, m) 188 | assert.IsType(t, &http.Response{}, res.Response) 189 | } 190 | }) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /mollie/shipments.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | // CreateShipment contains information required to create a new shipment. 11 | type CreateShipment struct { 12 | Lines []*OrderLine `json:"lines,omitempty"` 13 | Tracking *ShipmentTracking `json:"tracking,omitempty"` 14 | ShipmentAccessTokenFields 15 | } 16 | 17 | // ShipmentAccessTokenFields describes the fields available when using an access token. 18 | type ShipmentAccessTokenFields struct { 19 | Testmode bool `json:"testmode,omitempty"` 20 | } 21 | 22 | // Shipment contains information about a user service/product delivery and 23 | // is used in the figurative sense here. 24 | // It can also mean that a service was provided or digital content was delivered. 25 | type Shipment struct { 26 | Resource string `json:"resource,omitempty"` 27 | ID string `json:"id,omitempty"` 28 | OrderID string `json:"orderId,omitempty"` 29 | CreatedAt *time.Time `json:"createdAt,omitempty"` 30 | Tracking *ShipmentTracking `json:"tracking,omitempty"` 31 | Lines []*OrderLine `json:"lines,omitempty"` 32 | Links ShipmentLinks `json:"_links,omitempty"` 33 | ShipmentAccessTokenFields 34 | } 35 | 36 | // UpdateShipment contains information required to update a shipment. 37 | type UpdateShipment struct { 38 | Tracking *ShipmentTracking `json:"tracking,omitempty"` 39 | ShipmentAccessTokenFields 40 | } 41 | 42 | // ShipmentTracking contains shipment tracking details. 43 | type ShipmentTracking struct { 44 | Carrier string `json:"carrier,omitempty"` 45 | Code string `json:"code,omitempty"` 46 | URL string `json:"url,omitempty"` 47 | } 48 | 49 | // ShipmentLinks contains URL objects with shipment relevant 50 | // information for the user. 51 | type ShipmentLinks struct { 52 | Self *URL `json:"self,omitempty"` 53 | Order *URL `json:"order,omitempty"` 54 | Documentation *URL `json:"documentation,omitempty"` 55 | } 56 | 57 | // ShipmentsList describes how a list of payments will be retrieved by Mollie. 58 | type ShipmentsList struct { 59 | Count int `json:"count,omitempty"` 60 | Embedded struct { 61 | Shipments []Shipment 62 | } `json:"_embedded,omitempty"` 63 | Links PaginationLinks `json:"_links,omitempty"` 64 | } 65 | 66 | // ShipmentsService operates on shipments endpoints. 67 | type ShipmentsService service 68 | 69 | // Get retrieves a single shipment and the order lines shipped by a shipment’s ID. 70 | // 71 | // See: https://docs.mollie.com/reference/get-shipment# 72 | func (ss *ShipmentsService) Get(ctx context.Context, order string, shipment string) ( 73 | res *Response, 74 | s *Shipment, 75 | err error, 76 | ) { 77 | u := fmt.Sprintf("v2/orders/%s/shipments/%s", order, shipment) 78 | 79 | res, err = ss.client.get(ctx, u, nil) 80 | if err != nil { 81 | return 82 | } 83 | 84 | if err = json.Unmarshal(res.content, &s); err != nil { 85 | return 86 | } 87 | 88 | return 89 | } 90 | 91 | // Create can be used to ship order lines. 92 | // 93 | // See: https://docs.mollie.com/reference/create-shipment 94 | func (ss *ShipmentsService) Create(ctx context.Context, order string, cs CreateShipment) ( 95 | res *Response, 96 | s *Shipment, 97 | err error, 98 | ) { 99 | uri := fmt.Sprintf("v2/orders/%s/shipments", order) 100 | 101 | if ss.client.HasAccessToken() && ss.client.config.testing { 102 | cs.Testmode = true 103 | } 104 | 105 | res, err = ss.client.post(ctx, uri, cs, nil) 106 | if err != nil { 107 | return 108 | } 109 | 110 | if err = json.Unmarshal(res.content, &s); err != nil { 111 | return 112 | } 113 | 114 | return 115 | } 116 | 117 | // List retrieves all shipments for an order. 118 | // 119 | // See: https://docs.mollie.com/reference/list-shipments 120 | func (ss *ShipmentsService) List(ctx context.Context, order string) (res *Response, sl *ShipmentsList, err error) { 121 | u := fmt.Sprintf("v2/orders/%s/shipments", order) 122 | 123 | res, err = ss.client.get(ctx, u, nil) 124 | if err != nil { 125 | return 126 | } 127 | 128 | if err = json.Unmarshal(res.content, &sl); err != nil { 129 | return 130 | } 131 | 132 | return 133 | } 134 | 135 | // Update can be used to update the tracking information of a shipment 136 | // 137 | // See: https://docs.mollie.com/reference/update-shipment 138 | func (ss *ShipmentsService) Update(ctx context.Context, order string, shipment string, us UpdateShipment) ( 139 | res *Response, 140 | s *Shipment, 141 | err error, 142 | ) { 143 | u := fmt.Sprintf("v2/orders/%s/shipments/%s", order, shipment) 144 | 145 | res, err = ss.client.patch(ctx, u, us) 146 | if err != nil { 147 | return 148 | } 149 | 150 | if err = json.Unmarshal(res.content, &s); err != nil { 151 | return 152 | } 153 | 154 | return 155 | } 156 | -------------------------------------------------------------------------------- /mollie/terminals.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | // TerminalStatus is the status of the terminal, which is a read-only value determined by Mollie. 11 | type TerminalStatus string 12 | 13 | // Possible terminal statuses. 14 | const ( 15 | TerminalPending TerminalStatus = "pending" 16 | TerminalActive TerminalStatus = "active" 17 | TerminalInactive TerminalStatus = "inactive" 18 | ) 19 | 20 | // Terminal symbolizes a physical device to receive payments. 21 | type Terminal struct { 22 | ID string `json:"id,omitempty"` 23 | Resource string `json:"resource,omitempty"` 24 | ProfileID string `json:"profileID,omitempty"` 25 | Brand string `json:"brand,omitempty"` 26 | Model string `json:"model,omitempty"` 27 | SerialNumber string `json:"serialNumber,omitempty"` 28 | Currency string `json:"currency,omitempty"` 29 | Description string `json:"description,omitempty"` 30 | CreatedAt *time.Time `json:"createdAt,omitempty"` 31 | UpdatedAt *time.Time `json:"updatedAt,omitempty"` 32 | Status TerminalStatus `json:"status,omitempty"` 33 | Links PaginationLinks `json:"_links,omitempty"` 34 | } 35 | 36 | // ListTerminalsOptions holds query string parameters valid for terminals lists. 37 | // 38 | // ProfileID and TestMode are valid only when using access tokens. 39 | type ListTerminalsOptions struct { 40 | Testmode bool `url:"testMode,omitempty"` 41 | Limit int `url:"limit,omitempty"` 42 | From string `url:"from,omitempty"` 43 | ProfileID string `url:"profileID,omitempty"` 44 | } 45 | 46 | // TerminalList describes the response for terminals list endpoints. 47 | type TerminalList struct { 48 | Count int `json:"count,omitempty"` 49 | Embedded struct { 50 | Terminals []*Terminal `json:"terminals,omitempty"` 51 | } `json:"_embedded,omitempty"` 52 | Links PaginationLinks `json:"_links,omitempty"` 53 | } 54 | 55 | // TerminalsService operates over terminals resource. 56 | type TerminalsService service 57 | 58 | // Get terminal retrieves a single terminal object by its terminal ID. 59 | func (ts *TerminalsService) Get(ctx context.Context, id string) (res *Response, t *Terminal, err error) { 60 | res, err = ts.client.get(ctx, fmt.Sprintf("v2/terminals/%s", id), nil) 61 | if err != nil { 62 | return 63 | } 64 | 65 | if err = json.Unmarshal(res.content, &t); err != nil { 66 | return 67 | } 68 | 69 | return 70 | } 71 | 72 | // List retrieves a list of terminals symbolizing the physical devices to receive payments. 73 | func (ts *TerminalsService) List(ctx context.Context, options *ListTerminalsOptions) ( 74 | res *Response, 75 | tl *TerminalList, 76 | err error, 77 | ) { 78 | if ts.client.HasAccessToken() && ts.client.config.testing { 79 | options.Testmode = true 80 | } 81 | 82 | res, err = ts.client.get(ctx, "v2/terminals", options) 83 | if err != nil { 84 | return 85 | } 86 | 87 | if err = json.Unmarshal(res.content, &tl); err != nil { 88 | return 89 | } 90 | 91 | return 92 | } 93 | -------------------------------------------------------------------------------- /mollie/terminals_test.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/VictorAvelar/mollie-api-go/v4/testdata" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestTerminalsService_Get(t *testing.T) { 14 | setEnv() 15 | defer unsetEnv() 16 | 17 | type args struct { 18 | ctx context.Context 19 | id string 20 | } 21 | 22 | cases := []struct { 23 | name string 24 | args args 25 | wantErr bool 26 | err error 27 | want string 28 | pre func() 29 | handler http.HandlerFunc 30 | }{ 31 | { 32 | "get terminal correctly", 33 | args{ 34 | context.Background(), 35 | "term_7MgL4wea46qkRcoTZjWEH", 36 | }, 37 | false, 38 | nil, 39 | testdata.GetTerminalResponse, 40 | noPre, 41 | func(w http.ResponseWriter, r *http.Request) { 42 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 43 | testMethod(t, r, "GET") 44 | 45 | if _, ok := r.Header[AuthHeader]; !ok { 46 | w.WriteHeader(http.StatusUnauthorized) 47 | } 48 | _, _ = w.Write([]byte(testdata.GetTerminalResponse)) 49 | }, 50 | }, 51 | { 52 | "get terminal, an error is returned from the server", 53 | args{ 54 | context.Background(), 55 | "term_7MgL4wea46qkRcoTZjWEH", 56 | }, 57 | true, 58 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 59 | "", 60 | noPre, 61 | errorHandler, 62 | }, 63 | { 64 | "get terminal, an error occurs when parsing json", 65 | args{ 66 | context.Background(), 67 | "term_7MgL4wea46qkRcoTZjWEH", 68 | }, 69 | true, 70 | fmt.Errorf("invalid character 'h' looking for beginning of object key string"), 71 | "", 72 | noPre, 73 | encodingHandler, 74 | }, 75 | { 76 | "get terminal, invalid url when building request", 77 | args{ 78 | context.Background(), 79 | "term_7MgL4wea46qkRcoTZjWEH", 80 | }, 81 | true, 82 | errBadBaseURL, 83 | "", 84 | crashSrv, 85 | errorHandler, 86 | }, 87 | } 88 | 89 | for _, c := range cases { 90 | setup() 91 | defer teardown() 92 | 93 | t.Run(c.name, func(t *testing.T) { 94 | c.pre() 95 | tMux.HandleFunc(fmt.Sprintf("/v2/terminals/%s", c.args.id), c.handler) 96 | 97 | res, m, err := tClient.Terminals.Get(c.args.ctx, c.args.id) 98 | if c.wantErr { 99 | assert.NotNil(t, err) 100 | assert.EqualError(t, err, c.err.Error()) 101 | } else { 102 | assert.Nil(t, err) 103 | assert.IsType(t, &Terminal{}, m) 104 | assert.IsType(t, &http.Response{}, res.Response) 105 | } 106 | }) 107 | } 108 | } 109 | 110 | func TestTerminalsService_List(t *testing.T) { 111 | setEnv() 112 | defer unsetEnv() 113 | 114 | type args struct { 115 | ctx context.Context 116 | options *ListTerminalsOptions 117 | } 118 | 119 | cases := []struct { 120 | name string 121 | args args 122 | wantErr bool 123 | err error 124 | want string 125 | pre func() 126 | handler http.HandlerFunc 127 | }{ 128 | { 129 | "list terminals correctly", 130 | args{ 131 | context.Background(), 132 | &ListTerminalsOptions{}, 133 | }, 134 | false, 135 | nil, 136 | testdata.GetTerminalResponse, 137 | noPre, 138 | func(w http.ResponseWriter, r *http.Request) { 139 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 140 | testMethod(t, r, "GET") 141 | 142 | if _, ok := r.Header[AuthHeader]; !ok { 143 | w.WriteHeader(http.StatusUnauthorized) 144 | } 145 | _, _ = w.Write([]byte(testdata.ListTerminalsResponse)) 146 | }, 147 | }, 148 | { 149 | "list terminals correctly with an access token", 150 | args{ 151 | context.Background(), 152 | &ListTerminalsOptions{}, 153 | }, 154 | false, 155 | nil, 156 | testdata.GetTerminalResponse, 157 | setAccessToken, 158 | func(w http.ResponseWriter, r *http.Request) { 159 | testHeader(t, r, AuthHeader, "Bearer access_token_test") 160 | testMethod(t, r, "GET") 161 | 162 | if _, ok := r.Header[AuthHeader]; !ok { 163 | w.WriteHeader(http.StatusUnauthorized) 164 | } 165 | _, _ = w.Write([]byte(testdata.ListTerminalsResponse)) 166 | }, 167 | }, 168 | { 169 | "get terminals list, an error is returned from the server", 170 | args{ 171 | context.Background(), 172 | nil, 173 | }, 174 | true, 175 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 176 | "", 177 | noPre, 178 | errorHandler, 179 | }, 180 | { 181 | "get terminals list, an error occurs when parsing json", 182 | args{ 183 | context.Background(), 184 | nil, 185 | }, 186 | true, 187 | fmt.Errorf("invalid character 'h' looking for beginning of object key string"), 188 | "", 189 | noPre, 190 | encodingHandler, 191 | }, 192 | { 193 | "get terminals list, invalid url when building request", 194 | args{ 195 | context.Background(), 196 | nil, 197 | }, 198 | true, 199 | errBadBaseURL, 200 | "", 201 | crashSrv, 202 | errorHandler, 203 | }, 204 | } 205 | 206 | for _, c := range cases { 207 | setup() 208 | defer teardown() 209 | 210 | t.Run(c.name, func(t *testing.T) { 211 | c.pre() 212 | tMux.HandleFunc("/v2/terminals", c.handler) 213 | 214 | res, m, err := tClient.Terminals.List(c.args.ctx, c.args.options) 215 | if c.wantErr { 216 | assert.NotNil(t, err) 217 | assert.EqualError(t, err, c.err.Error()) 218 | } else { 219 | assert.Nil(t, err) 220 | assert.IsType(t, &TerminalList{}, m) 221 | assert.IsType(t, &http.Response{}, res.Response) 222 | } 223 | }) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /mollie/vouchers.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | // VoucherIssuer represents the issuer of a voucher. 4 | type VoucherIssuer string 5 | 6 | // List of known voucher issuers. 7 | const ( 8 | EdenredBelgiumCadeauVoucher VoucherIssuer = "edenred-belgium-cadeau" 9 | EdenredBelgiumEcoVoucher VoucherIssuer = "edenred-belgium-eco" 10 | EdenredBelgiumMealVoucher VoucherIssuer = "edenred-belgium-meal" 11 | EdenredBelgiumSportsVoucher VoucherIssuer = "edenred-belgium-sports" 12 | EdenredBelgiumAdditionalVoucher VoucherIssuer = "edenred-belgium-additional" 13 | EdenredBelgiumConsumeVoucher VoucherIssuer = "edenred-belgium-consume" 14 | MonizzeCadeauVoucher VoucherIssuer = "monizze-cadeau" 15 | MonizzeEcoVoucher VoucherIssuer = "monizze-eco" 16 | MonizzeMealVoucher VoucherIssuer = "monizze-meal" 17 | PluxeeCadeauVoucher VoucherIssuer = "sodexo-cadeau" 18 | PluxeeEcoVoucher VoucherIssuer = "sodexo-ecopass" 19 | PluxeeLunchVoucher VoucherIssuer = "sodexo-lunchpass" 20 | ) 21 | 22 | // VoucherIssuerEnabled describes the response of a voucher enable operation. 23 | type VoucherIssuerEnabled struct { 24 | ID string `json:"id,omitempty"` 25 | Description string `json:"description,omitempty"` 26 | Status IssuerStatus `json:"status,omitempty"` 27 | Contractor VoucherContractor `json:"contractor,omitempty"` 28 | Links VoucherLinks `json:"_links,omitempty"` 29 | } 30 | 31 | // VoucherLinks are links embedded when a voucher is enabled. 32 | type VoucherLinks struct { 33 | Self *URL `json:"self,omitempty"` 34 | Documentation *URL `json:"documentation,omitempty"` 35 | } 36 | 37 | // VoucherContractor represents a contractor for a voucher. 38 | type VoucherContractor struct { 39 | ID string `json:"id,omitempty"` 40 | Name string `json:"name,omitempty"` 41 | ContractorID string `json:"contractorId,omitempty"` 42 | } 43 | -------------------------------------------------------------------------------- /mollie/wallets.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | ) 7 | 8 | // Wallet describes the wallet types that Mollie supports. 9 | type Wallet string 10 | 11 | // Available wallet types. 12 | const ( 13 | ApplePayWallet Wallet = "applepay" 14 | ) 15 | 16 | // WalletsService operates over the resources described 17 | // in Mollie's wallets API endpoints section. 18 | type WalletsService service 19 | 20 | // ApplePaymentSession contains information about an Apple pay session. 21 | type ApplePaymentSession struct { 22 | EpochTimestamp int `json:"epochTimestamp,omitempty"` 23 | ExpiresAt int `json:"expiresAt,omitempty"` 24 | MerchantSessionID string `json:"merchantSessionIdentifier,omitempty"` 25 | Nonce string `json:"nonce,omitempty"` 26 | MerchantID string `json:"merchantIdentified,omitempty"` 27 | DomainName string `json:"domainName,omitempty"` 28 | DisplayName string `json:"displayName,omitempty"` 29 | Signature string `json:"signature,omitempty"` 30 | } 31 | 32 | // ApplePaymentSessionRequest contains the body parameters for requesting 33 | // a valid PaymentSession from Apple. 34 | type ApplePaymentSessionRequest struct { 35 | Domain string `json:"domain,omitempty"` 36 | ValidationURL string `json:"validationUrl,omitempty"` 37 | } 38 | 39 | // ApplePaymentSession returns an Apple Payment Session object valid for one transaction. 40 | // 41 | // See: https://docs.mollie.com/reference/request-apple-pay-payment-session 42 | func (ms *WalletsService) ApplePaymentSession(ctx context.Context, asr *ApplePaymentSessionRequest) ( 43 | res *Response, 44 | aps *ApplePaymentSession, 45 | err error, 46 | ) { 47 | u := "v2/wallets/applepay/sessions" 48 | 49 | res, err = ms.client.post(ctx, u, asr, nil) 50 | if err != nil { 51 | return 52 | } 53 | 54 | if err = json.Unmarshal(res.content, &aps); err != nil { 55 | return 56 | } 57 | 58 | return 59 | } 60 | -------------------------------------------------------------------------------- /mollie/wallets_test.go: -------------------------------------------------------------------------------- 1 | package mollie 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/VictorAvelar/mollie-api-go/v4/testdata" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestMiscellaneousService_ApplePaymentSession(t *testing.T) { 14 | setEnv() 15 | defer unsetEnv() 16 | 17 | type args struct { 18 | ctx context.Context 19 | appleSess *ApplePaymentSessionRequest 20 | } 21 | 22 | cases := []struct { 23 | name string 24 | args args 25 | wantErr bool 26 | err error 27 | pre func() 28 | handler http.HandlerFunc 29 | }{ 30 | { 31 | "get apple payment session works as expected.", 32 | args{ 33 | context.Background(), 34 | &ApplePaymentSessionRequest{ 35 | Domain: "https://example.com", 36 | }, 37 | }, 38 | false, 39 | nil, 40 | noPre, 41 | func(w http.ResponseWriter, r *http.Request) { 42 | testHeader(t, r, AuthHeader, "Bearer token_X12b31ggg23") 43 | testMethod(t, r, "POST") 44 | 45 | if _, ok := r.Header[AuthHeader]; !ok { 46 | w.WriteHeader(http.StatusUnauthorized) 47 | } 48 | _, _ = w.Write([]byte(testdata.ListMethodsResponse)) 49 | }, 50 | }, 51 | { 52 | "get apple payment session, an error is returned from the server", 53 | args{ 54 | context.Background(), 55 | nil, 56 | }, 57 | true, 58 | fmt.Errorf("500 Internal Server Error: An internal server error occurred while processing your request"), 59 | noPre, 60 | errorHandler, 61 | }, 62 | { 63 | "get apple payment session, an error occurs when parsing json", 64 | args{ 65 | context.Background(), 66 | nil, 67 | }, 68 | true, 69 | fmt.Errorf("invalid character 'h' looking for beginning of object key string"), 70 | noPre, 71 | encodingHandler, 72 | }, 73 | { 74 | "get apple payment session, invalid url when building request", 75 | args{ 76 | context.Background(), 77 | nil, 78 | }, 79 | true, 80 | errBadBaseURL, 81 | crashSrv, 82 | errorHandler, 83 | }, 84 | } 85 | 86 | for _, c := range cases { 87 | setup() 88 | defer teardown() 89 | 90 | t.Run(c.name, func(t *testing.T) { 91 | c.pre() 92 | tMux.HandleFunc("/v2/wallets/applepay/sessions", c.handler) 93 | 94 | res, m, err := tClient.Wallets.ApplePaymentSession(c.args.ctx, c.args.appleSess) 95 | if c.wantErr { 96 | assert.NotNil(t, err) 97 | assert.EqualError(t, err, c.err.Error()) 98 | } else { 99 | assert.Nil(t, err) 100 | assert.IsType(t, &ApplePaymentSession{}, m) 101 | assert.IsType(t, &http.Response{}, res.Response) 102 | } 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /pkg/connect/oauth2.go: -------------------------------------------------------------------------------- 1 | package connect 2 | 3 | import ( 4 | "golang.org/x/oauth2" 5 | ) 6 | 7 | // Mollie's Oauth2 server URLs. 8 | const ( 9 | authURL = "https://www.mollie.com/oauth2/authorize" 10 | tokensURL = "https://api.mollie.com/oauth2/tokens" //#nosec G101 -- This is the url used to retrieve oauth2 tokens. 11 | ) 12 | 13 | // OauthEndpoint is Mollies's OAuth 2.0 endpoint. 14 | func OauthEndpoint() *oauth2.Endpoint { 15 | return &oauth2.Endpoint{ 16 | AuthURL: authURL, 17 | TokenURL: tokensURL, 18 | AuthStyle: 0, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg/connect/oauth2_test.go: -------------------------------------------------------------------------------- 1 | package connect 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | "golang.org/x/oauth2" 8 | ) 9 | 10 | func Test_Endpoint(t *testing.T) { 11 | ept := OauthEndpoint() 12 | require.Equal(t, ept.AuthURL, authURL) 13 | require.Equal(t, ept.AuthStyle, oauth2.AuthStyleAutoDetect) 14 | require.Equal(t, ept.TokenURL, tokensURL) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/idempotency/contract.go: -------------------------------------------------------------------------------- 1 | package idempotency 2 | 3 | // KeyGenerator describes the service in charge 4 | // of generating a unique idempotency key to be passed in 5 | // POST requests to ensure operation uniqueness. 6 | // 7 | // See: https://docs.mollie.com/overview/api-idempotency 8 | type KeyGenerator interface { 9 | // Generate encapsulates the logic to return a string representation of 10 | // a unique idempotency key. 11 | Generate() string 12 | } 13 | -------------------------------------------------------------------------------- /pkg/idempotency/doc.go: -------------------------------------------------------------------------------- 1 | // Package idempotency contains the services in charge 2 | // of generating a unique keys to be passed in 3 | // POST requests to ensure operation uniqueness. 4 | // 5 | // See: https://docs.mollie.com/overview/api-idempotency 6 | // 7 | // The std generator uses google's uuid library to return a new uuid 8 | // as unique idempotency key. 9 | // 10 | // You can build your own generator and pass it to the library by 11 | // implementing the KeyGenerator interface. 12 | package idempotency 13 | -------------------------------------------------------------------------------- /pkg/idempotency/nop.go: -------------------------------------------------------------------------------- 1 | package idempotency 2 | 3 | // NOpIdempotencyGenerator is a dummy implementation of the 4 | // IdempotencyKeyGenerator interface. 5 | // 6 | // Good for testing or when a predictable result is required. 7 | type nOpIdempotencyGenerator struct { 8 | expected string 9 | } 10 | 11 | // TestKeyExpected is the default value for the NOpGenerator. 12 | const ( 13 | TestKeyExpected = "test_ikg_key" 14 | ) 15 | 16 | // Generate encapsulates the logic to return a string representation of 17 | // a unique idempotency key. 18 | func (nopIKG nOpIdempotencyGenerator) Generate() string { 19 | return nopIKG.expected 20 | } 21 | 22 | // NewNopGenerator returns a dummy implementation of the 23 | // IdempotencyKeyGenerator interface. 24 | // 25 | // Good for testing or when a predictable result is required. 26 | // 27 | // If exp is an empty string, then TestKeyExpected is used as 28 | // default value for the NOpGenerator. 29 | func NewNopGenerator(exp string) KeyGenerator { 30 | if exp == "" { 31 | exp = TestKeyExpected 32 | } 33 | 34 | return nOpIdempotencyGenerator{ 35 | expected: exp, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pkg/idempotency/nop_test.go: -------------------------------------------------------------------------------- 1 | package idempotency 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNopGenerator(t *testing.T) { 10 | assert.Implements(t, new(KeyGenerator), NewNopGenerator("dummy")) 11 | } 12 | 13 | func TestGenerateDefault(t *testing.T) { 14 | g := NewNopGenerator("") 15 | 16 | assert.Equal(t, TestKeyExpected, g.Generate()) 17 | } 18 | 19 | func TestGenerateNonDefault(t *testing.T) { 20 | g := NewNopGenerator("testing") 21 | 22 | assert.NotEqual(t, TestKeyExpected, g.Generate()) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/idempotency/std.go: -------------------------------------------------------------------------------- 1 | package idempotency 2 | 3 | import "github.com/google/uuid" 4 | 5 | type stdGenerator struct{} 6 | 7 | // Generate encapsulates the logic to return a string representation of 8 | // a unique idempotency key. 9 | // 10 | // A string representation of a v4 uuid for this implementation. 11 | func (ikg stdGenerator) Generate() string { 12 | return uuid.New().String() 13 | } 14 | 15 | // NewStdGenerator returns an standard and common way of generating 16 | // idempotency unique keys. 17 | func NewStdGenerator() KeyGenerator { 18 | return new(stdGenerator) 19 | } 20 | -------------------------------------------------------------------------------- /pkg/idempotency/std_test.go: -------------------------------------------------------------------------------- 1 | package idempotency 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNewStdGenerator(t *testing.T) { 10 | assert.Implements(t, new(KeyGenerator), NewStdGenerator()) 11 | } 12 | 13 | func TestStandardGenerator(t *testing.T) { 14 | key := NewStdGenerator().Generate() 15 | 16 | assert.Len(t, key, 36) 17 | 18 | assert.NotEqual(t, key, NewStdGenerator().Generate()) 19 | } 20 | -------------------------------------------------------------------------------- /pkg/pagination/doc.go: -------------------------------------------------------------------------------- 1 | // Package pagination provides utilities to handle pagination in API responses. 2 | // 3 | // Pagination is a common feature in APIs that allows retrieval of large datasets 4 | // in smaller chunks, enhancing performance and resource usage. This package aims 5 | // to simplify pagination-related tasks by providing helpful functions. 6 | package pagination 7 | -------------------------------------------------------------------------------- /pkg/pagination/pagination.go: -------------------------------------------------------------------------------- 1 | package pagination 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | // ExtractFromQueryParam extracts the lastID from the given URI, which is assumed to be a URL with query parameters. 8 | // It specifically looks for a query parameter named 'from' and returns its value as a string. 9 | // If the URI cannot be parsed or the query parameter is not found, it returns an empty string and the encountered 10 | // error. 11 | func ExtractFromQueryParam(uri string) (lastID string, err error) { 12 | const from = "from" 13 | 14 | return parseURIAndReturnQueryParam(uri, from) 15 | } 16 | 17 | func parseURIAndReturnQueryParam(uri string, param string) (val string, err error) { 18 | u, err := url.Parse(uri) 19 | if err != nil { 20 | return "", err 21 | } 22 | 23 | v := u.Query().Get(param) 24 | 25 | return v, nil 26 | } 27 | -------------------------------------------------------------------------------- /pkg/pagination/pagination_test.go: -------------------------------------------------------------------------------- 1 | package pagination 2 | 3 | import "testing" 4 | 5 | func TestExtractFromQueryParam(t *testing.T) { 6 | type args struct { 7 | uri string 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | wantLastID string 13 | wantErr bool 14 | }{ 15 | { 16 | name: "test extracts correct parameter", 17 | args: args{ 18 | "https://api.mollie.com/v2/payments?from=tr_EkceGSH8Ga&limit=5", 19 | }, 20 | wantLastID: "tr_EkceGSH8Ga", 21 | wantErr: false, 22 | }, 23 | { 24 | name: "test wrong url error parameter", 25 | args: args{ 26 | "h%%s12", 27 | }, 28 | wantLastID: "", 29 | wantErr: true, 30 | }, 31 | } 32 | for _, tt := range tests { 33 | t.Run(tt.name, func(t *testing.T) { 34 | gotLastID, err := ExtractFromQueryParam(tt.args.uri) 35 | t.Log(err) 36 | if (err != nil) != tt.wantErr { 37 | t.Errorf("ExtractFromQueryParam() error = %v, wantErr %v", err, tt.wantErr) 38 | return 39 | } 40 | if gotLastID != tt.wantLastID { 41 | t.Errorf("ExtractFromQueryParam() = %v, want %v", gotLastID, tt.wantLastID) 42 | } 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /testdata/captures.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // ListCapturesResponse example 4 | const ListCapturesResponse = `{ 5 | "_embedded": { 6 | "captures": [ 7 | { 8 | "resource": "capture", 9 | "id": "cpt_4qqhO89gsT", 10 | "mode": "live", 11 | "amount": { 12 | "value": "1027.99", 13 | "currency": "EUR" 14 | }, 15 | "settlementAmount": { 16 | "value": "399.00", 17 | "currency": "EUR" 18 | }, 19 | "paymentId": "tr_WDqYK6vllg", 20 | "shipmentId": "shp_3wmsgCJN4U", 21 | "settlementId": "stl_jDk30akdN", 22 | "createdAt": "2018-08-02T09:29:56+00:00", 23 | "_links": { 24 | "self": { 25 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/captures/cpt_4qqhO89gsT", 26 | "type": "application/hal+json" 27 | }, 28 | "payment": { 29 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg", 30 | "type": "application/hal+json" 31 | }, 32 | "shipment": { 33 | "href": "https://api.mollie.com/v2/orders/ord_8wmqcHMN4U/shipments/shp_3wmsgCJN4U", 34 | "type": "application/hal+json" 35 | }, 36 | "settlement": { 37 | "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN", 38 | "type": "application/hal+json" 39 | }, 40 | "documentation": { 41 | "href": "https://docs.mollie.com/reference/captures-api/get-capture", 42 | "type": "text/html" 43 | } 44 | } 45 | } 46 | ] 47 | }, 48 | "count": 1, 49 | "_links": { 50 | "documentation": { 51 | "href": "https://docs.mollie.com/reference/captures-api/list-captures", 52 | "type": "text/html" 53 | }, 54 | "self": { 55 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/captures?limit=50", 56 | "type": "application/hal+json" 57 | }, 58 | "previous": null, 59 | "next": null 60 | } 61 | }` 62 | 63 | // GetCaptureResponse example 64 | const GetCaptureResponse = `{ 65 | "resource": "capture", 66 | "id": "cpt_4qqhO89gsT", 67 | "mode": "live", 68 | "amount": { 69 | "value": "1027.99", 70 | "currency": "EUR" 71 | }, 72 | "settlementAmount": { 73 | "value": "399.00", 74 | "currency": "EUR" 75 | }, 76 | "paymentId": "tr_WDqYK6vllg", 77 | "shipmentId": "shp_3wmsgCJN4U", 78 | "settlementId": "stl_jDk30akdN", 79 | "createdAt": "2018-08-02T09:29:56+00:00", 80 | "_links": { 81 | "self": { 82 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/captures/cpt_4qqhO89gsT", 83 | "type": "application/hal+json" 84 | }, 85 | "payment": { 86 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg", 87 | "type": "application/hal+json" 88 | }, 89 | "shipment": { 90 | "href": "https://api.mollie.com/v2/orders/ord_8wmqcHMN4U/shipments/shp_3wmsgCJN4U", 91 | "type": "application/hal+json" 92 | }, 93 | "settlement": { 94 | "href": "https://api.mollie.com/v2/settlements/stl_jDk30akdN", 95 | "type": "application/hal+json" 96 | }, 97 | "documentation": { 98 | "href": "https://docs.mollie.com/reference/captures-api/get-capture", 99 | "type": "text/html" 100 | } 101 | } 102 | }` 103 | 104 | // CreateCaptureResponse example. 105 | const CreateCaptureResponse = `{ 106 | "resource": "capture", 107 | "id": "cpt_mNepDkEtco6ah3QNPUGYH", 108 | "mode": "live", 109 | "amount": { 110 | "value": "35.95", 111 | "currency": "EUR" 112 | }, 113 | "paymentId": "tr_WDqYK6vllg", 114 | "createdAt": "2018-08-02T09:29:56+00:00", 115 | "description": "Capture for cart #12345", 116 | "_links": { 117 | "self": { 118 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/captures/cpt_mNepDkEtco6ah3QNPUGYH", 119 | "type": "application/hal+json" 120 | }, 121 | "payment": { 122 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg", 123 | "type": "application/hal+json" 124 | }, 125 | "documentation": { 126 | "href": "https://docs.mollie.com/reference/captures-api/create-capture", 127 | "type": "text/html" 128 | } 129 | } 130 | }` 131 | 132 | // CreateCaptureWithAccessTokenResponse example. 133 | const CreateCaptureWithAccessTokenResponse = `{ 134 | "resource": "capture", 135 | "id": "cpt_mNepDkEtco6ah3QNPUGYH", 136 | "mode": "live", 137 | "amount": { 138 | "value": "35.95", 139 | "currency": "EUR" 140 | }, 141 | "paymentId": "tr_WDqYK6vllg", 142 | "createdAt": "2018-08-02T09:29:56+00:00", 143 | "description": "Capture for cart #12345", 144 | "testmode": true, 145 | "_links": { 146 | "self": { 147 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/captures/cpt_mNepDkEtco6ah3QNPUGYH", 148 | "type": "application/hal+json" 149 | }, 150 | "payment": { 151 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg", 152 | "type": "application/hal+json" 153 | }, 154 | "documentation": { 155 | "href": "https://docs.mollie.com/reference/captures-api/create-capture", 156 | "type": "text/html" 157 | } 158 | } 159 | }` 160 | -------------------------------------------------------------------------------- /testdata/chargebacks.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | const ( 4 | GetChargebackResponse = `{ 5 | "resource": "chargeback", 6 | "id": "chb_n9z0tp", 7 | "amount": { 8 | "currency": "USD", 9 | "value": "43.38" 10 | }, 11 | "settlementAmount": { 12 | "currency": "EUR", 13 | "value": "-35.07" 14 | }, 15 | "createdAt": "2018-03-14T17:00:52.0Z", 16 | "reason": { 17 | "code": "AC01", 18 | "description": "Account identifier incorrect (i.e. invalid IBAN)" 19 | }, 20 | "reversedAt": null, 21 | "paymentId": "tr_WDqYK6vllg", 22 | "_links": { 23 | "self": { 24 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/chargebacks/chb_n9z0tp", 25 | "type": "application/hal+json" 26 | }, 27 | "payment": { 28 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg", 29 | "type": "application/hal+json" 30 | }, 31 | "documentation": { 32 | "href": "https://docs.mollie.com/reference/chargebacks-api/get-payment-chargeback", 33 | "type": "text/html" 34 | } 35 | } 36 | }` 37 | ListChargebacksResponse = `{ 38 | "count": 3, 39 | "_embedded": { 40 | "chargebacks": [ 41 | { 42 | "resource": "chargeback", 43 | "id": "chb_n9z0tp", 44 | "amount": { 45 | "currency": "USD", 46 | "value": "43.38" 47 | }, 48 | "settlementAmount": { 49 | "currency": "EUR", 50 | "value": "35.07" 51 | }, 52 | "createdAt": "2018-03-14T17:00:52.0Z", 53 | "reversedAt": null, 54 | "paymentId": "tr_WDqYK6vllg", 55 | "_links": { 56 | "self": { 57 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg/chargebacks/chb_n9z0tp", 58 | "type": "application/hal+json" 59 | }, 60 | "payment": { 61 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg", 62 | "type": "application/hal+json" 63 | }, 64 | "documentation": { 65 | "href": "https://docs.mollie.com/reference/chargebacks-api/get-chargeback", 66 | "type": "text/html" 67 | } 68 | } 69 | }, 70 | { }, 71 | { } 72 | ] 73 | }, 74 | "_links": { 75 | "self": { 76 | "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS/chargebacks", 77 | "type": "application/hal+json" 78 | }, 79 | "documentation": { 80 | "href": "https://docs.mollie.com/reference/chargebacks-api/list-chargebacks", 81 | "type": "text/html" 82 | } 83 | } 84 | }` 85 | ) 86 | -------------------------------------------------------------------------------- /testdata/client_links.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // CreateClientLinkResponse examples. 4 | const CreateClientLinkResponse = `{ 5 | "id": "csr_vZCnNQsV2UtfXxYifWKWH", 6 | "resource": "client-link", 7 | "_links": { 8 | "clientLink": { 9 | "href": "https://my.mollie.com/dashboard/client-link/finalize/csr_vZCnNQsV2UtfXxYifWKWH", 10 | "type": "text/html" 11 | }, 12 | "documentation": { 13 | "href": "https://docs.mollie.com/reference/clients-api/create-client-link", 14 | "type": "text/html" 15 | } 16 | } 17 | }` 18 | -------------------------------------------------------------------------------- /testdata/customers.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // GetCustomerResponse example 4 | const GetCustomerResponse = `{ 5 | "resource": "customer", 6 | "id": "cst_kEn1PlbGa", 7 | "mode": "test", 8 | "name": "Customer A", 9 | "email": "customer@example.org", 10 | "locale": "nl_NL", 11 | "metadata": null, 12 | "createdAt": "2018-04-06T13:23:21.0Z", 13 | "_links": { 14 | "self": { 15 | "href": "https://api.mollie.com/v2/customers/cst_kEn1PlbGa", 16 | "type": "application/hal+json" 17 | }, 18 | "dashboard": { 19 | "href": "https://www.mollie.com/dashboard/org_123456789/customers/cst_kEn1PlbGa", 20 | "type": "text/html" 21 | }, 22 | "mandates": { 23 | "href": "https://api.mollie.com/v2/customers/cst_kEn1PlbGa/mandates", 24 | "type": "application/hal+json" 25 | }, 26 | "subscriptions": { 27 | "href": "https://api.mollie.com/v2/customers/cst_kEn1PlbGa/subscriptions", 28 | "type": "application/hal+json" 29 | }, 30 | "payments": { 31 | "href": "https://api.mollie.com/v2/customers/cst_kEn1PlbGa/payments", 32 | "type": "application/hal+json" 33 | }, 34 | "documentation": { 35 | "href": "https://docs.mollie.com/reference/customers-api/get-customer", 36 | "type": "text/html" 37 | } 38 | } 39 | }` 40 | 41 | // CreateCustomerResponse example 42 | const CreateCustomerResponse = `{ 43 | "resource": "customer", 44 | "id": "cst_8wmqcHMN4U", 45 | "mode": "test", 46 | "name": "Customer A", 47 | "email": "customer@example.org", 48 | "locale": null, 49 | "metadata": null, 50 | "createdAt": "2018-04-06T13:10:19.0Z", 51 | "_links": { 52 | "self": { 53 | "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U", 54 | "type": "application/hal+json" 55 | }, 56 | "documentation": { 57 | "href": "https://docs.mollie.com/reference/customers-api/create-customer", 58 | "type": "text/html" 59 | } 60 | } 61 | }` 62 | 63 | // UpdateCustomerResponse example 64 | const UpdateCustomerResponse = `{ 65 | "resource": "customer", 66 | "id": "cst_8wmqcHMN4U", 67 | "mode": "test", 68 | "name": "Updated Customer A", 69 | "email": "updated-customer@example.org", 70 | "locale": "nl_NL", 71 | "metadata": null, 72 | "createdAt": "2018-04-06T13:23:21.0Z", 73 | "_links": { 74 | "self": { 75 | "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U", 76 | "type": "application/hal+json" 77 | }, 78 | "documentation": { 79 | "href": "https://docs.mollie.com/reference/customers-api/get-customer", 80 | "type": "text/html" 81 | } 82 | } 83 | }` 84 | 85 | // ListCustomersResponse example 86 | const ListCustomersResponse = `{ 87 | "count": 3, 88 | "_embedded": { 89 | "customers": [ 90 | { 91 | "resource": "customer", 92 | "id": "cst_kEn1PlbGa", 93 | "mode": "test", 94 | "name": "Customer A", 95 | "email": "customer@example.org", 96 | "locale": "nl_NL", 97 | "metadata": null, 98 | "createdAt": "2018-04-06T13:23:21.0Z", 99 | "_links": { 100 | "self": { 101 | "href": "https://api.mollie.com/v2/customers/cst_kEn1PlbGa", 102 | "type": "application/hal+json" 103 | }, 104 | "documentation": { 105 | "href": "https://docs.mollie.com/reference/customers-api/get-customer", 106 | "type": "text/html" 107 | } 108 | } 109 | }, 110 | { }, 111 | { } 112 | ] 113 | }, 114 | "_links": { 115 | "self": { 116 | "href": "https://api.mollie.com/v2/customers", 117 | "type": "application/hal+json" 118 | }, 119 | "previous": null, 120 | "next": { 121 | "href": "https://api.mollie.com/v2/customers?from=cst_stTC2WHAuS", 122 | "type": "application/hal+json" 123 | }, 124 | "documentation": { 125 | "href": "https://docs.mollie.com/reference/customers-api/list-customers", 126 | "type": "text/html" 127 | } 128 | } 129 | }` 130 | -------------------------------------------------------------------------------- /testdata/errors.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // UnauthorizedErrorResponse example. 4 | const UnauthorizedErrorResponse = `{ 5 | "status": 401, 6 | "title": "Unauthorized Request", 7 | "detail": "Missing authentication, or failed to authenticate", 8 | "_links": { 9 | "documentation": { 10 | "href": "https://docs.mollie.com/overview/authentication", 11 | "type": "text/html" 12 | } 13 | } 14 | }` 15 | 16 | // NotFoundErrorResponse example. 17 | const NotFoundErrorResponse = `{ 18 | "status": 404, 19 | "title": "Not Found", 20 | "detail": "No payment exists with token tr_I_dont_exist.", 21 | "_links": { 22 | "documentation": { 23 | "href": "https://docs.mollie.com/errors", 24 | "type": "text/html" 25 | } 26 | } 27 | }` 28 | 29 | // UnprocessableEntityErrorResponse example. 30 | const UnprocessableEntityErrorResponse = `{ 31 | "status": 422, 32 | "title": "Unprocessable Entity", 33 | "detail": "The amount is higher than the maximum", 34 | "field": "amount", 35 | "_links": { 36 | "documentation": { 37 | "href": "https://docs.mollie.com/errors", 38 | "type": "text/html" 39 | } 40 | } 41 | }` 42 | 43 | // InternalServerErrorResponse example. 44 | const InternalServerErrorResponse = `{ 45 | "status": 500, 46 | "title": "Internal Server Error", 47 | "detail": "An internal server error occurred while processing your request", 48 | "_links": { 49 | "documentation": { 50 | "href": "https://docs.mollie.com/overview/authentication", 51 | "type": "text/html" 52 | } 53 | } 54 | }` 55 | -------------------------------------------------------------------------------- /testdata/invoices.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // GetInvoiceResponse example 4 | const GetInvoiceResponse = `{ 5 | "resource": "invoice", 6 | "id": "inv_xBEbP9rvAq", 7 | "reference": "2016.10000", 8 | "vatNumber": "NL001234567B01", 9 | "status": "open", 10 | "issuedAt": "2016-08-31", 11 | "dueAt": "2016-09-14", 12 | "netAmount": { 13 | "value": "45.00", 14 | "currency": "EUR" 15 | }, 16 | "vatAmount": { 17 | "value": "9.45", 18 | "currency": "EUR" 19 | }, 20 | "grossAmount": { 21 | "value": "54.45", 22 | "currency": "EUR" 23 | }, 24 | "lines":[ 25 | { 26 | "period": "2016-09", 27 | "description": "iDEAL transactiekosten", 28 | "count": 100, 29 | "vatPercentage": 21, 30 | "amount": { 31 | "value": "45.00", 32 | "currency": "EUR" 33 | } 34 | } 35 | ], 36 | "_links": { 37 | "self": { 38 | "href": "https://api.mollie.com/v2/invoices/inv_xBEbP9rvAq", 39 | "type": "application/hal+json" 40 | }, 41 | "pdf": { 42 | "href": "https://www.mollie.com/merchant/download/invoice/xBEbP9rvAq/2ab44d60b35b1d06090bba955fa2c602", 43 | "type": "application/pdf", 44 | "expiresAt": "2018-11-09T14:10:36+00:00" 45 | } 46 | } 47 | }` 48 | 49 | // ListInvoicesResponse example 50 | const ListInvoicesResponse = `{ 51 | "count": 5, 52 | "_embedded": { 53 | "invoices": [ 54 | { 55 | "resource": "invoice", 56 | "id": "inv_xBEbP9rvAq", 57 | "reference": "2016.10000", 58 | "vatNumber": "NL001234567B01", 59 | "status": "open", 60 | "issuedAt": "2016-08-31", 61 | "dueAt": "2016-09-14", 62 | "netAmount": { 63 | "value": "45.00", 64 | "currency": "EUR" 65 | }, 66 | "vatAmount": { 67 | "value": "9.45", 68 | "currency": "EUR" 69 | }, 70 | "grossAmount": { 71 | "value": "54.45", 72 | "currency": "EUR" 73 | }, 74 | "lines":[ 75 | { 76 | "period": "2016-09", 77 | "description": "iDEAL transactiekosten", 78 | "count": 100, 79 | "vatPercentage": 21, 80 | "amount": { 81 | "value": "45.00", 82 | "currency": "EUR" 83 | } 84 | } 85 | ], 86 | "_links": { 87 | "self": { 88 | "href": "https://api.mollie.com/v2/invoices/inv_xBEbP9rvAq", 89 | "type": "application/hal+json" 90 | }, 91 | "pdf": { 92 | "href": "https://www.mollie.com/merchant/download/invoice/xBEbP9rvAq/2ab44d60b35955fa2c602", 93 | "type": "application/pdf", 94 | "expiresAt": "2018-11-09T14:10:36+00:00" 95 | } 96 | } 97 | } 98 | ] 99 | }, 100 | "_links": { 101 | "self": { 102 | "href": "https://api.mollie.nl/v2/invoices?limit=5", 103 | "type": "application/hal+json" 104 | }, 105 | "previous": null, 106 | "next": { 107 | "href": "https://api.mollie.nl/v2/invoices?from=inv_xBEbP9rvAq&limit=5", 108 | "type": "application/hal+json" 109 | }, 110 | "documentation": { 111 | "href": "https://docs.mollie.com/reference/invoices-api/list-invoices", 112 | "type": "text/html" 113 | } 114 | } 115 | }` 116 | -------------------------------------------------------------------------------- /testdata/mandates.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // CreateMandateResponse 4 | const CreateMandateResponse = `{ 5 | "resource": "mandate", 6 | "id": "mdt_h3gAaD5zP", 7 | "mode": "test", 8 | "status": "valid", 9 | "method": "directdebit", 10 | "details": { 11 | "consumerName": "John Doe", 12 | "consumerAccount": "NL55INGB0000000000", 13 | "consumerBic": "INGBNL2A" 14 | }, 15 | "mandateReference": "YOUR-COMPANY-MD13804", 16 | "signatureDate": "2018-05-07", 17 | "createdAt": "2018-05-07T10:49:08+00:00", 18 | "_links": { 19 | "self": { 20 | "href": "https://api.mollie.com/v2/customers/cst_4qqhO89gsT/mandates/mdt_h3gAaD5zP", 21 | "type": "application/hal+json" 22 | }, 23 | "customer": { 24 | "href": "https://api.mollie.com/v2/customers/cst_4qqhO89gsT", 25 | "type": "application/hal+json" 26 | }, 27 | "documentation": { 28 | "href": "https://docs.mollie.com/reference/mandates-api/create-mandate", 29 | "type": "text/html" 30 | } 31 | } 32 | }` 33 | 34 | // GetMandateResponse 35 | const GetMandateResponse = `{ 36 | "resource": "mandate", 37 | "id": "mdt_h3gAaD5zP", 38 | "mode": "test", 39 | "status": "valid", 40 | "method": "directdebit", 41 | "details": { 42 | "consumerName": "John Doe", 43 | "consumerAccount": "NL55INGB0000000000", 44 | "consumerBic": "INGBNL2A" 45 | }, 46 | "mandateReference": "YOUR-COMPANY-MD1380", 47 | "signatureDate": "2018-05-07", 48 | "createdAt": "2018-05-07T10:49:08+00:00", 49 | "_links": { 50 | "self": { 51 | "href": "https://api.mollie.com/v2/customers/cst_4qqhO89gsT/mandates/mdt_h3gAaD5zP", 52 | "type": "application/hal+json" 53 | }, 54 | "customer": { 55 | "href": "https://api.mollie.com/v2/customers/cst_4qqhO89gsT", 56 | "type": "application/hal+json" 57 | }, 58 | "documentation": { 59 | "href": "https://docs.mollie.com/reference/mandates-api/get-mandate", 60 | "type": "text/html" 61 | } 62 | } 63 | }` 64 | 65 | const ListMandatesResponse = ` 66 | { 67 | "count": 2, 68 | "_embedded": { 69 | "mandates": [ 70 | { 71 | "resource": "mandate", 72 | "id": "mdt_AcQl5fdL4h", 73 | "mode": "test", 74 | "status": "valid", 75 | "method": "directdebit", 76 | "details": { 77 | "consumerName": "John Doe", 78 | "consumerAccount": "NL55INGB0000000000", 79 | "consumerBic": "INGBNL2A" 80 | }, 81 | "mandateReference": null, 82 | "signatureDate": "2018-05-07", 83 | "createdAt": "2018-05-07T10:49:08+00:00", 84 | "_links": { 85 | "self": { 86 | "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U/mandates/mdt_AcQl5fdL4h", 87 | "type": "application/hal+json" 88 | }, 89 | "customer": { 90 | "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U", 91 | "type": "application/hal+json" 92 | }, 93 | "documentation": { 94 | "href": "https://mollie.com/en/docs/reference/customers/create-mandate", 95 | "type": "text/html" 96 | } 97 | } 98 | }, 99 | { 100 | "resource": "mandate", 101 | "id": "mdt_AcQl5fdL4h", 102 | "mode": "test", 103 | "status": "valid", 104 | "method": "directdebit", 105 | "details": { 106 | "consumerName": "John Doe", 107 | "consumerAccount": "NL55INGB0000000000", 108 | "consumerBic": "INGBNL2A" 109 | }, 110 | "mandateReference": null, 111 | "signatureDate": "2018-05-07", 112 | "createdAt": "2018-05-07T10:49:08+00:00", 113 | "_links": { 114 | "self": { 115 | "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U/mandates/mdt_AcQl5fdL4h", 116 | "type": "application/hal+json" 117 | }, 118 | "customer": { 119 | "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U", 120 | "type": "application/hal+json" 121 | }, 122 | "documentation": { 123 | "href": "https://mollie.com/en/docs/reference/customers/create-mandate", 124 | "type": "text/html" 125 | } 126 | } 127 | } 128 | ] 129 | }, 130 | "_links": { 131 | "self": { 132 | "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U/mandates?limit=5", 133 | "type": "application/hal+json" 134 | }, 135 | "previous": null, 136 | "next": { 137 | "href": "https://api.mollie.com/v2/customers/cst_8wmqcHMN4U/mandates?from=mdt_AcQl5fdL4h&limit=5", 138 | "type": "application/hal+json" 139 | }, 140 | "documentation": { 141 | "href": "https://docs.mollie.com/reference/mandates-api/revoke-mandate", 142 | "type": "text/html" 143 | } 144 | } 145 | }` 146 | -------------------------------------------------------------------------------- /testdata/miscellaneous.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | const ApplePaySessionResponse = `{ 4 | "epochTimestamp": 1555507053169, 5 | "expiresAt": 1555510653169, 6 | "merchantSessionIdentifier": "SSH2EAF8AFAEAA94DEEA898162A5DAFD36E_916523AAED1343F5BC5815E12BEE9250AFFDC1A17C46B0DE5A943F0F94927C24", 7 | "nonce": "0206b8db", 8 | "merchantIdentifier": "BD62FEB196874511C22DB28A9E14A89E3534C93194F73EA417EC566368D391EB", 9 | "domainName": "pay.example.org", 10 | "displayName": "Chuck Norris's Store", 11 | "signature": "308006092a864886f7...8cc030ad3000000000000" 12 | }` 13 | -------------------------------------------------------------------------------- /testdata/onboarding.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // GetOnboardingStatusResponse example. 4 | const GetOnboardingStatusResponse = `{ 5 | "resource": "onboarding", 6 | "name": "Mollie B.V.", 7 | "signedUpAt": "2018-12-20T10:49:08+00:00", 8 | "status": "completed", 9 | "canReceivePayments": true, 10 | "canReceiveSettlements": true, 11 | "_links": { 12 | "self": { 13 | "href": "https://api.mollie.com/v2/onboarding/me", 14 | "type": "application/hal+json" 15 | }, 16 | "dashboard": { 17 | "href": "https://www.mollie.com/dashboard/onboarding", 18 | "type": "text/html" 19 | }, 20 | "organization": { 21 | "href": "https://api.mollie.com/v2/organization/org_12345", 22 | "type": "application/hal+json" 23 | }, 24 | "documentation": { 25 | "href": "https://docs.mollie.com/reference/onboarding-api/get-onboarding-status", 26 | "type": "text/html" 27 | } 28 | } 29 | } 30 | ` 31 | -------------------------------------------------------------------------------- /testdata/organizations.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // GetOrganizationResponse example 4 | const GetOrganizationResponse = `{ 5 | "resource": "organization", 6 | "id": "org_12345678", 7 | "name": "Mollie B.V.", 8 | "email": "info@mollie.com", 9 | "address": { 10 | "streetAndNumber": "Keizersgracht 313", 11 | "postalCode": "1016 EE", 12 | "city": "Amsterdam", 13 | "country": "NL" 14 | }, 15 | "registrationNumber": "30204462", 16 | "vatNumber": "NL815839091B01", 17 | "_links": { 18 | "self": { 19 | "href": "https://api.mollie.com/v2/organizations/org_12345678", 20 | "type": "application/hal+json" 21 | }, 22 | "documentation": { 23 | "href": "https://docs.mollie.com/reference/organizations-api/get-organization", 24 | "type": "text/html" 25 | } 26 | } 27 | }` 28 | 29 | // GetCurrentOrganizationResponse example 30 | const GetCurrentOrganizationResponse = `{ 31 | "resource": "organization", 32 | "id": "org_12345678", 33 | "name": "Mollie B.V.", 34 | "email": "info@mollie.com", 35 | "address": { 36 | "streetAndNumber" : "Keizersgracht 313", 37 | "postalCode": "1016 EE", 38 | "city": "Amsterdam", 39 | "country": "NL" 40 | }, 41 | "registrationNumber": "30204462", 42 | "vatNumber": "NL815839091B01", 43 | "_links": { 44 | "self": { 45 | "href": "https://api.mollie.com/v2/organizations/me", 46 | "type": "application/hal+json" 47 | }, 48 | "chargebacks": { 49 | "href": "https://api.mollie.com/v2/chargebacks", 50 | "type": "application/hal+json" 51 | }, 52 | "customers": { 53 | "href": "https://api.mollie.com/v2/customers", 54 | "type": "application/hal+json" 55 | }, 56 | "invoices": { 57 | "href": "https://api.mollie.com/v2/invoices", 58 | "type": "application/hal+json" 59 | }, 60 | "payments": { 61 | "href": "https://api.mollie.com/v2/payments", 62 | "type": "application/hal+json" 63 | }, 64 | "profiles": { 65 | "href": "https://api.mollie.com/v2/profiles", 66 | "type": "application/hal+json" 67 | }, 68 | "refunds": { 69 | "href": "https://api.mollie.com/v2/refunds", 70 | "type": "application/hal+json" 71 | }, 72 | "settlements": { 73 | "href": "https://api.mollie.com/v2/settlements", 74 | "type": "application/hal+json" 75 | }, 76 | "documentation": { 77 | "href": "https://docs.mollie.com/reference/organizations-api/current-organization", 78 | "type": "text/html" 79 | } 80 | } 81 | }` 82 | 83 | const GetPartnerStatusResponse = `{ 84 | "resource": "partner", 85 | "partnerType": "signuplink", 86 | "partnerContractSignedAt": "2018-03-20T13:13:37+00:00", 87 | "_links": { 88 | "self": { 89 | "href": "https://api.mollie.com/v2/organizations/me/partner", 90 | "type": "application/hal+json" 91 | }, 92 | "documentation": { 93 | "href": "https://docs.mollie.com/reference/partners-api/get-partner", 94 | "type": "text/html" 95 | }, 96 | "signuplink": { 97 | "href": "https://www.mollie.com/dashboard/signup/myCode?lang=en", 98 | "type": "text/html" 99 | } 100 | } 101 | }` 102 | -------------------------------------------------------------------------------- /testdata/partners.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | const ListPartnerClientsResponse = `{ 4 | "count":1, 5 | "_embedded":{ 6 | "clients":[ 7 | { 8 | "resource":"client", 9 | "id":"org_1337", 10 | "organizationCreatedAt":"2018-03-21T13:13:37+00:00", 11 | "commission":{ 12 | "count":200, 13 | "totalAmount":{ 14 | "currency":"EUR", 15 | "value":"10.00" 16 | } 17 | }, 18 | "_links":{ 19 | "self":{ 20 | "href":"https://api.mollie.com/v2/clients/org_1337", 21 | "type":"application/hal+json" 22 | }, 23 | "organization":{ 24 | "href":"https://api.mollie.com/v2/organizations/org_1337", 25 | "type":"application/hal+json" 26 | }, 27 | "onboarding":{ 28 | "href":"https://api.mollie.com/v2/onboarding/org_1337", 29 | "type":"application/hal+json" 30 | }, 31 | "documentation":{ 32 | "href":"https://docs.mollie.com/reference/partners-api/get-client", 33 | "type":"text/html" 34 | } 35 | } 36 | } 37 | ] 38 | }, 39 | "_links":{ 40 | "self":{ 41 | "href":"https://api.mollie.com/v2/clients?limit=3", 42 | "type":"application/hal+json" 43 | }, 44 | "previous":null, 45 | "next":{ 46 | "href":"https://api.mollie.com/v2/clients?from=org_1379&limit=3", 47 | "type":"application/hal+json" 48 | }, 49 | "documentation":{ 50 | "href":"https://docs.mollie.com/reference/partners-api/list-clients", 51 | "type":"text/html" 52 | } 53 | } 54 | }` 55 | 56 | const GetPartnerClientResponse = `{ 57 | "resource": "client", 58 | "id": "org_1337", 59 | "organizationCreatedAt": "2018-03-21T13:13:37+00:00", 60 | "commission": { 61 | "count": 200, 62 | "totalAmount": { 63 | "currency": "EUR", 64 | "value": "10.00" 65 | } 66 | }, 67 | "_links": { 68 | "self": { 69 | "href": "https://api.mollie.com/v2/clients/org_1337", 70 | "type": "application/hal+json" 71 | }, 72 | "organization": { 73 | "href": "https://api.mollie.com/v2/organizations/org_1337", 74 | "type": "application/hal+json" 75 | }, 76 | "onboarding": { 77 | "href": "https://api.mollie.com/v2/onboarding/org_1337", 78 | "type": "application/hal+json" 79 | }, 80 | "documentation": { 81 | "href": "https://docs.mollie.com/reference/partners-api/get-client", 82 | "type": "text/html" 83 | } 84 | } 85 | }` 86 | -------------------------------------------------------------------------------- /testdata/payments.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // GetPaymentResponse example 4 | const GetPaymentResponse = `{ 5 | "resource": "payment", 6 | "id": "tr_WDqYK6vllg", 7 | "mode": "test", 8 | "createdAt": "2018-03-20T13:13:37+00:00", 9 | "amount": { 10 | "value": "10.00", 11 | "currency": "EUR" 12 | }, 13 | "description": "Order #12345", 14 | "method": null, 15 | "metadata": { 16 | "order_id": "12345" 17 | }, 18 | "status": "open", 19 | "isCancelable": false, 20 | "locale": "nl_NL", 21 | "restrictPaymentMethodsToCountry": "NL", 22 | "expiresAt": "2018-03-20T13:28:37+00:00", 23 | "details": null, 24 | "profileId": "pfl_QkEhN94Ba", 25 | "sequenceType": "oneoff", 26 | "redirectUrl": "https://webshop.example.org/order/12345/", 27 | "webhookUrl": "https://webshop.example.org/payments/webhook/", 28 | "_links": { 29 | "self": { 30 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg", 31 | "type": "application/hal+json" 32 | }, 33 | "checkout": { 34 | "href": "https://www.mollie.com/payscreen/select-method/WDqYK6vllg", 35 | "type": "text/html" 36 | }, 37 | "dashboard": { 38 | "href": "https://www.mollie.com/dashboard/org_12345678/payments/tr_WDqYK6vllg", 39 | "type": "application/json" 40 | }, 41 | "documentation": { 42 | "href": "https://docs.mollie.com/reference/payments-api/get-payment", 43 | "type": "text/html" 44 | } 45 | } 46 | }` 47 | 48 | // CancelPaymentResponse example 49 | const CancelPaymentResponse = `{ 50 | "resource": "payment", 51 | "id": "tr_WDqYK6vllg", 52 | "mode": "live", 53 | "createdAt": "2018-03-19T10:18:33+00:00", 54 | "amount": { 55 | "value": "35.07", 56 | "currency": "EUR" 57 | }, 58 | "description": "Order 33", 59 | "method": "banktransfer", 60 | "metadata": null, 61 | "status": "canceled", 62 | "canceledAt": "2018-03-19T10:19:15+00:00", 63 | "details": { 64 | "bankName": "Stichting Mollie Payments", 65 | "bankAccount": "NL53ABNA0627535577", 66 | "bankBic": "ABNANL2A", 67 | "transferReference": "RF12-3456-7890-1234" 68 | }, 69 | "profileId": "pfl_QkEhN94Ba", 70 | "sequenceType": "oneoff", 71 | "redirectUrl": "https://webshop.example.org/order/33/", 72 | "_links": { 73 | "self": { 74 | "href": "https://api.mollie.com/v2/payments/tr_WDqYK6vllg", 75 | "type": "application/hal+json" 76 | }, 77 | "documentation": { 78 | "href": "https://docs.mollie.com/reference/payments-api/cancel-payment", 79 | "type": "text/html" 80 | } 81 | } 82 | }` 83 | 84 | // UpdatePaymentResponse example 85 | const UpdatePaymentResponse = `{ 86 | "resource": "payment", 87 | "id": "tr_7UhSN1zuXS", 88 | "mode": "test", 89 | "createdAt": "2018-03-20T09:13:37+00:00", 90 | "amount": { 91 | "value": "10.00", 92 | "currency": "EUR" 93 | }, 94 | "description": "Order #98765", 95 | "method": null, 96 | "metadata": { 97 | "order_id": "98765" 98 | }, 99 | "status": "open", 100 | "isCancelable": false, 101 | "expiresAt": "2018-03-20T09:28:37+00:00", 102 | "details": null, 103 | "profileId": "pfl_QkEhN94Ba", 104 | "sequenceType": "oneoff", 105 | "redirectUrl": "https://example.org/webshop/order/98765/", 106 | "webhookUrl": "https://example.org/webshop/payments/webhook/", 107 | "_links": { 108 | "self": { 109 | "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS", 110 | "type": "application/json" 111 | }, 112 | "checkout": { 113 | "href": "https://www.mollie.com/payscreen/select-method/7UhSN1zuXS", 114 | "type": "text/html" 115 | }, 116 | "documentation": { 117 | "href": "https://docs.mollie.com/reference/payments-api/update-payment", 118 | "type": "text/html" 119 | } 120 | } 121 | } 122 | ` 123 | 124 | // ListPaymentsResponse example 125 | const ListPaymentsResponse = `{ 126 | "count": 5, 127 | "_embedded": { 128 | "payments": [ 129 | { 130 | "resource": "payment", 131 | "id": "tr_7UhSN1zuXS", 132 | "mode": "test", 133 | "createdAt": "2018-02-12T11:58:35.0Z", 134 | "expiresAt": "2018-02-12T12:13:35.0Z", 135 | "status": "open", 136 | "isCancelable": false, 137 | "amount": { 138 | "value": "75.00", 139 | "currency": "GBP" 140 | }, 141 | "description": "Order #12345", 142 | "method": "ideal", 143 | "metadata": null, 144 | "details": null, 145 | "profileId": "pfl_QkEhN94Ba", 146 | "redirectUrl": "https://webshop.example.org/order/12345/", 147 | "_links": { 148 | "checkout": { 149 | "href": "https://www.mollie.com/paymentscreen/issuer/select/ideal/7UhSN1zuXS", 150 | "type": "text/html" 151 | }, 152 | "self": { 153 | "href": "https://api.mollie.com/v2/payments/tr_7UhSN1zuXS", 154 | "type": "application/hal+json" 155 | } 156 | } 157 | } 158 | ] 159 | }, 160 | "_links": { 161 | "self": { 162 | "href": "https://api.mollie.com/v2/payments?limit=5", 163 | "type": "application/hal+json" 164 | }, 165 | "previous": null, 166 | "next": { 167 | "href": "https://api.mollie.com/v2/payments?from=tr_SDkzMggpvx&limit=5", 168 | "type": "application/hal+json" 169 | }, 170 | "documentation": { 171 | "href": "https://docs.mollie.com/reference/payments-api/list-payments", 172 | "type": "text/html" 173 | } 174 | } 175 | }` 176 | -------------------------------------------------------------------------------- /testdata/permissions.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // GetPermissionsResponse example. 4 | const GetPermissionsResponse = `{ 5 | "resource": "permission", 6 | "id": "payments.read", 7 | "description": "View your payments", 8 | "granted": true, 9 | "_links": { 10 | "self": { 11 | "href": "https://api.mollie.com/v2/permissions/payments.read", 12 | "type": "application/hal+json" 13 | }, 14 | "documentation": { 15 | "href": "https://docs.mollie.com/reference/permissions-api/get-permission", 16 | "type": "text/html" 17 | } 18 | } 19 | } 20 | ` 21 | 22 | // ListPermissionsResponse example. 23 | const ListPermissionsResponse = `{ 24 | "_embedded": { 25 | "permissions": [ 26 | { 27 | "resource": "permission", 28 | "id": "payments.write", 29 | "description": "Create new payments", 30 | "granted": false, 31 | "_links": { 32 | "self": { 33 | "href": "https://api.mollie.com/v2/permissions/payments.write", 34 | "type": "application/hal+json" 35 | } 36 | } 37 | }, 38 | { 39 | "resource": "permission", 40 | "id": "payments.read", 41 | "description": "View your payments", 42 | "granted": true, 43 | "_links": { 44 | "self": { 45 | "href": "https://api.mollie.com/v2/permissions/payments.read", 46 | "type": "application/hal+json" 47 | } 48 | } 49 | } 50 | ] 51 | }, 52 | "count": 15, 53 | "_links": { 54 | "documentation": { 55 | "href": "https://docs.mollie.com/reference/permissions-api/list-permissions", 56 | "type": "text/html" 57 | }, 58 | "self": { 59 | "href": "https://api.mollie.com/v2/permissions", 60 | "type": "application/hal+json" 61 | } 62 | } 63 | } 64 | ` 65 | -------------------------------------------------------------------------------- /testdata/subscriptions.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // GetSubscriptionResponse example 4 | const GetSubscriptionResponse = `{ 5 | "resource": "subscription", 6 | "id": "sub_rVKGtNd6s3", 7 | "mode": "live", 8 | "createdAt": "2016-06-01T12:23:34+00:00", 9 | "status": "active", 10 | "amount": { 11 | "value": "25.00", 12 | "currency": "EUR" 13 | }, 14 | "times": 4, 15 | "timesRemaining": 4, 16 | "interval": "3 months", 17 | "startDate": "2016-06-01", 18 | "nextPaymentDate": "2016-09-01", 19 | "description": "Quarterly payment", 20 | "method": null, 21 | "mandateId": "mdt_38HS4fsS", 22 | "webhookUrl": "https://webshop.example.org/payments/webhook", 23 | "metadata": { 24 | "plan": "small" 25 | }, 26 | "_links": { 27 | "self": { 28 | "href": "https://api.mollie.com/v2/customers/cst_stTC2WHAuS/subscriptions/sub_rVKGtNd6s3", 29 | "type": "application/hal+json" 30 | }, 31 | "customer": { 32 | "href": "https://api.mollie.com/v2/customers/cst_stTC2WHAuS", 33 | "type": "application/hal+json" 34 | }, 35 | "profile": { 36 | "href": "https://api.mollie.com/v2/profiles/pfl_URR55HPMGx", 37 | "type": "application/hal+json" 38 | }, 39 | "payments": { 40 | "href": "https://api.mollie.com/v2/customers/cst_stTC2WHAuS/subscriptions/sub_rVKGtNd6s3/payments", 41 | "type": "application/hal+json" 42 | }, 43 | "documentation": { 44 | "href": "https://docs.mollie.com/reference/subscriptions-api/get-subscription", 45 | "type": "text/html" 46 | } 47 | } 48 | }` 49 | 50 | // DeleteSubscriptionResponse example 51 | const DeleteSubscriptionResponse = `{ 52 | "resource": "subscription", 53 | "id": "sub_rVKGtNd6s3", 54 | "mode": "live", 55 | "createdAt": "2018-06-01T12:23:34+00:00", 56 | "status": "canceled", 57 | "amount": { 58 | "value": "25.00", 59 | "currency": "EUR" 60 | }, 61 | "times": 4, 62 | "interval": "3 months", 63 | "nextPaymentDate": null, 64 | "description": "Quarterly payment", 65 | "method": null, 66 | "startDate": "2016-06-01", 67 | "webhookUrl": "https://webshop.example.org/payments/webhook", 68 | "canceledAt": "2018-08-01T11:04:21+00:00", 69 | "_links": { 70 | "self": { 71 | "href": "https://api.mollie.com/v2/customers/cst_stTC2WHAuS/subscriptions/sub_rVKGtNd6s3", 72 | "type": "application/hal+json" 73 | }, 74 | "customer": { 75 | "href": "https://api.mollie.com/v2/customers/cst_stTC2WHAuS", 76 | "type": "application/hal+json" 77 | }, 78 | "documentation": { 79 | "href": "https://docs.mollie.com/reference/subscriptions-api/cancel-subscription", 80 | "type": "text/html" 81 | } 82 | } 83 | }` 84 | 85 | // ListAllSubscriptionsResponse example 86 | const ListAllSubscriptionsResponse = `{ 87 | "count": 3, 88 | "_embedded": { 89 | "subscriptions": [ 90 | { 91 | "resource": "subscription", 92 | "id": "sub_rVKGtNd6s3", 93 | "mode": "live", 94 | "createdAt": "2018-06-01T12:23:34+00:00", 95 | "status": "active", 96 | "amount": { 97 | "value": "25.00", 98 | "currency": "EUR" 99 | }, 100 | "times": 4, 101 | "timesRemaining": 3, 102 | "interval": "3 months", 103 | "startDate": "2016-06-01", 104 | "nextPaymentDate": "2016-09-01", 105 | "description": "Quarterly payment", 106 | "method": null, 107 | "webhookUrl": "https://webshop.example.org/subscriptions/webhook", 108 | "_links": { 109 | "self": { 110 | "href": "https://api.mollie.com/v2/customers/cst_stTC2WHAuS/subscriptions/sub_rVKGtNd6s3", 111 | "type": "application/hal+json" 112 | }, 113 | "profile": { 114 | "href": "https://api.mollie.com/v2/profiles/pfl_URR55HPMGx", 115 | "type": "application/hal+json" 116 | }, 117 | "customer": { 118 | "href": "https://api.mollie.com/v2/customers/cst_stTC2WHAuS", 119 | "type": "application/hal+json" 120 | } 121 | } 122 | } 123 | ] 124 | }, 125 | "_links": { 126 | "self": { 127 | "href": "https://api.mollie.com/v2/subscriptions", 128 | "type": "application/hal+json" 129 | }, 130 | "previous": null, 131 | "next": { 132 | "href": "https://api.mollie.com/v2/subscriptions?from=sub_mnfbwhMfvo", 133 | "type": "application/hal+json" 134 | }, 135 | "documentation": { 136 | "href": "https://docs.mollie.com/reference/subscriptions-api/list-all-subscriptions", 137 | "type": "text/html" 138 | } 139 | } 140 | }` 141 | -------------------------------------------------------------------------------- /testdata/terminals.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | // ListTerminalsResponse example. 4 | const ListTerminalsResponse = `{ 5 | "count": 5, 6 | "_embedded": { 7 | "terminals": [ 8 | { 9 | "id": "term_7MgL4wea46qkRcoTZjWEH", 10 | "profileId": "pfl_QkEhN94Ba", 11 | "status": "active", 12 | "brand": "PAX", 13 | "model": "A920", 14 | "serialNumber": "1234567890", 15 | "currency": "EUR", 16 | "description": "Terminal #12341", 17 | "createdAt": "2022-02-12T11:58:35.0Z", 18 | "updatedAt": "2022-11-15T13:32:11.0Z", 19 | "_links": { 20 | "self": { 21 | "href": "https://api.mollie.com/v2/terminals/term_7MgL4wea46qkRcoTZjWEH", 22 | "type": "application/hal+json" 23 | } 24 | } 25 | }, 26 | { 27 | "id": "term_7sgL4wea46qkRcoysdiWEH", 28 | "profileId": "pfl_QkEhN94Ba", 29 | "status": "active", 30 | "brand": "PAX", 31 | "model": "A920", 32 | "serialNumber": "1234567890", 33 | "currency": "MEX", 34 | "description": "Terminal #12342", 35 | "createdAt": "2022-02-12T11:58:35.0Z", 36 | "updatedAt": "2022-11-15T13:32:11.0Z", 37 | "_links": { 38 | "self": { 39 | "href": "https://api.mollie.com/v2/terminals/term_7sgL4wea46qkRcoysdiWEH", 40 | "type": "application/hal+json" 41 | } 42 | } 43 | }, 44 | { 45 | "id": "term_7MgLsdD*b3asDayWEH", 46 | "profileId": "pfl_QkEhN94Ba", 47 | "status": "active", 48 | "brand": "PAX", 49 | "model": "A920", 50 | "serialNumber": "1234567890", 51 | "currency": "GBP", 52 | "description": "Terminal #12343", 53 | "createdAt": "2022-02-12T11:58:35.0Z", 54 | "updatedAt": "2022-11-15T13:32:11.0Z", 55 | "_links": { 56 | "self": { 57 | "href": "https://api.mollie.com/v2/terminals/term_7MgLsdD*b3asDayWEH", 58 | "type": "application/hal+json" 59 | } 60 | } 61 | }, 62 | { 63 | "id": "term_7MgL4j5jAowWqkRcoTZjWEH", 64 | "profileId": "pfl_QkEhN94Ba", 65 | "status": "active", 66 | "brand": "PAX", 67 | "model": "A920", 68 | "serialNumber": "1234567890", 69 | "currency": "DLS", 70 | "description": "Terminal #12344", 71 | "createdAt": "2022-02-12T11:58:35.0Z", 72 | "updatedAt": "2022-11-15T13:32:11.0Z", 73 | "_links": { 74 | "self": { 75 | "href": "https://api.mollie.com/v2/terminals/term_7MgL4j5jAowWqkRcoTZjWEH", 76 | "type": "application/hal+json" 77 | } 78 | } 79 | }, 80 | { 81 | "id": "term_7MgL4we02ujSeRcoTZjWEH", 82 | "profileId": "pfl_QkEhN94Ba", 83 | "status": "active", 84 | "brand": "PAX", 85 | "model": "A920", 86 | "serialNumber": "1234567890", 87 | "currency": "COP", 88 | "description": "Terminal #12345", 89 | "createdAt": "2022-02-12T11:58:35.0Z", 90 | "updatedAt": "2022-11-15T13:32:11.0Z", 91 | "_links": { 92 | "self": { 93 | "href": "https://api.mollie.com/v2/terminals/term_7MgL4we02ujSeRcoTZjWEH", 94 | "type": "application/hal+json" 95 | } 96 | } 97 | } 98 | ] 99 | }, 100 | "_links": { 101 | "self": { 102 | "href": "https://api.mollie.com/v2/terminals?limit=5", 103 | "type": "application/hal+json" 104 | }, 105 | "previous": null, 106 | "next": { 107 | "href": "https://api.mollie.com/v2/terminals?from=term_7MgL4we02ujSeRcoTZjWEH&limit=5", 108 | "type": "application/hal+json" 109 | }, 110 | "documentation": { 111 | "href": "https://docs.mollie.com/reference/terminals-api/list-terminals", 112 | "type": "text/html" 113 | } 114 | } 115 | }` 116 | 117 | // GetTerminalResponse example. 118 | const GetTerminalResponse = `{ 119 | "id": "term_7MgL4wea46qkRcoTZjWEH", 120 | "profileId": "pfl_QkEhN94Ba", 121 | "status": "active", 122 | "brand": "PAX", 123 | "model": "A920", 124 | "serialNumber": "1234567890", 125 | "currency": "EUR", 126 | "description": "Terminal #12345", 127 | "createdAt": "2022-02-12T11:58:35.0Z", 128 | "updatedAt": "2022-11-15T13:32:11.0Z", 129 | "_links": { 130 | "self": { 131 | "href": "https://api.mollie.com/v2/terminals/term_7MgL4wea46qkRcoTZjWEH", 132 | "type": "application/hal+json" 133 | }, 134 | "documentation": { 135 | "href": "https://docs.mollie.com/reference/terminals-api/get-terminal", 136 | "type": "text/html" 137 | } 138 | } 139 | }` 140 | --------------------------------------------------------------------------------