├── .editorconfig ├── .github ├── renovate.json └── workflows │ ├── ci.yml │ ├── plan-release.yml │ ├── publish.yml │ └── push-dist.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.cjs ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── RELEASE.md ├── config └── ember-cli-update.json ├── ember-cli-stripe ├── .gitignore ├── .prettierignore ├── .prettierrc.cjs ├── .template-lintrc.cjs ├── addon-main.cjs ├── babel.config.json ├── eslint.config.mjs ├── package.json ├── rollup.config.mjs └── src │ ├── .gitkeep │ ├── components │ └── stripe-checkout.gjs │ ├── services │ └── stripe.js │ └── utils │ ├── configuration-options.js │ └── load-script.js ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── test-app ├── .editorconfig ├── .ember-cli ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .stylelintignore ├── .stylelintrc.js ├── .template-lintrc.js ├── .watchmanconfig ├── README.md ├── app ├── app.js ├── components │ └── .gitkeep ├── controllers │ ├── .gitkeep │ ├── advanced-usage.js │ ├── multiple-keys-usage.js │ └── simple-usage.js ├── helpers │ └── .gitkeep ├── index.html ├── models │ └── .gitkeep ├── router.js ├── routes │ └── .gitkeep ├── styles │ └── app.css └── templates │ ├── advanced-usage.hbs │ ├── application.hbs │ ├── components │ └── code-snippet.hbs │ ├── multiple-keys-usage.hbs │ └── simple-usage.hbs ├── config ├── ember-cli-update.json ├── ember-try.js ├── environment.js ├── optional-features.json └── targets.js ├── ember-cli-build.js ├── eslint.config.mjs ├── package.json ├── public └── robots.txt ├── testem.js └── tests ├── helpers └── index.js ├── index.html ├── integration └── components │ └── stripe-checkout-test.js ├── test-helper.js └── unit ├── .gitkeep └── services └── stripe-test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@smile-io:addon"], 3 | "enabled": false 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: {} 9 | 10 | concurrency: 11 | group: ci-${{ github.head_ref || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | test: 16 | name: 'Tests' 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 10 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: pnpm/action-setup@v4 23 | - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4 24 | with: 25 | # uses Volta! 26 | node-version-file: 'package.json' 27 | cache: pnpm 28 | - name: Install Dependencies 29 | run: pnpm install --frozen-lockfile 30 | - name: Lint 31 | run: pnpm lint 32 | - name: Run Tests 33 | run: pnpm test 34 | 35 | floating: 36 | name: 'Floating Dependencies' 37 | runs-on: ubuntu-latest 38 | timeout-minutes: 10 39 | 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: pnpm/action-setup@v4 43 | - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4 44 | with: 45 | # uses Volta! 46 | node-version-file: 'package.json' 47 | cache: pnpm 48 | - name: Install Dependencies 49 | run: pnpm install --no-lockfile 50 | - name: Run Tests 51 | run: pnpm test 52 | 53 | try-scenarios: 54 | name: ${{ matrix.try-scenario }} 55 | runs-on: ubuntu-latest 56 | needs: 'test' 57 | timeout-minutes: 10 58 | 59 | strategy: 60 | fail-fast: false 61 | matrix: 62 | try-scenario: 63 | - ember-lts-4.12 64 | - ember-lts-5.4 65 | - ember-release 66 | - ember-beta 67 | - ember-canary 68 | - embroider-safe 69 | - embroider-optimized 70 | 71 | steps: 72 | - uses: actions/checkout@v4 73 | - uses: pnpm/action-setup@v4 74 | - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4 75 | with: 76 | # uses Volta! 77 | node-version-file: 'package.json' 78 | cache: pnpm 79 | - name: Install Dependencies 80 | run: pnpm install --frozen-lockfile 81 | - name: Run Tests 82 | run: ./node_modules/.bin/ember try:one ${{ matrix.try-scenario }} --skip-cleanup 83 | working-directory: test-app 84 | -------------------------------------------------------------------------------- /.github/workflows/plan-release.yml: -------------------------------------------------------------------------------- 1 | name: Release Plan Review 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - master 7 | pull_request_target: # This workflow has permissions on the repo, do NOT run code from PRs in this workflow. See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ 8 | types: 9 | - labeled 10 | - unlabeled 11 | 12 | concurrency: 13 | group: plan-release # only the latest one of these should ever be running 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | check-plan: 18 | name: "Check Release Plan" 19 | runs-on: ubuntu-latest 20 | outputs: 21 | command: ${{ steps.check-release.outputs.command }} 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | ref: 'main' 28 | # This will only cause the `check-plan` job to have a "command" of `release` 29 | # when the .release-plan.json file was changed on the last commit. 30 | - id: check-release 31 | run: if git diff --name-only HEAD HEAD~1 | grep -w -q ".release-plan.json"; then echo "command=release"; fi >> $GITHUB_OUTPUT 32 | 33 | prepare-release-notes: 34 | name: Prepare Release Notes 35 | runs-on: ubuntu-latest 36 | timeout-minutes: 5 37 | needs: check-plan 38 | permissions: 39 | contents: write 40 | issues: read 41 | pull-requests: write 42 | outputs: 43 | explanation: ${{ steps.explanation.outputs.text }} 44 | # only run on push event if plan wasn't updated (don't create a release plan when we're releasing) 45 | # only run on labeled event if the PR has already been merged 46 | if: (github.event_name == 'push' && needs.check-plan.outputs.command != 'release') || (github.event_name == 'pull_request_target' && github.event.pull_request.merged == true) 47 | 48 | steps: 49 | - uses: actions/checkout@v4 50 | # We need to download lots of history so that 51 | # github-changelog can discover what's changed since the last release 52 | with: 53 | fetch-depth: 0 54 | ref: 'main' 55 | - uses: pnpm/action-setup@v4 56 | - uses: actions/setup-node@v4 57 | with: 58 | node-version: 18 59 | cache: pnpm 60 | - run: pnpm install --frozen-lockfile 61 | - name: "Generate Explanation and Prep Changelogs" 62 | id: explanation 63 | run: | 64 | set +e 65 | pnpm release-plan prepare 2> >(tee -a release-plan-stderr.txt >&2) 66 | 67 | if [ $? -ne 0 ]; then 68 | echo 'text<> $GITHUB_OUTPUT 69 | cat release-plan-stderr.txt >> $GITHUB_OUTPUT 70 | echo 'EOF' >> $GITHUB_OUTPUT 71 | else 72 | echo 'text<> $GITHUB_OUTPUT 73 | jq .description .release-plan.json -r >> $GITHUB_OUTPUT 74 | echo 'EOF' >> $GITHUB_OUTPUT 75 | rm release-plan-stderr.txt 76 | fi 77 | env: 78 | GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} 79 | 80 | - uses: peter-evans/create-pull-request@v7 81 | with: 82 | commit-message: "Prepare Release using 'release-plan'" 83 | labels: "internal" 84 | branch: release-preview 85 | title: Prepare Release 86 | body: | 87 | This PR is a preview of the release that [release-plan](https://github.com/embroider-build/release-plan) has prepared. To release you should just merge this PR 👍 88 | 89 | ----------------------------------------- 90 | 91 | ${{ steps.explanation.outputs.text }} 92 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # For every push to the main branch, this checks if the release-plan was 2 | # updated and if it was it will publish stable npm packages based on the 3 | # release plan 4 | 5 | name: Publish Stable 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - main 12 | - master 13 | 14 | concurrency: 15 | group: publish-${{ github.head_ref || github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | check-plan: 20 | name: "Check Release Plan" 21 | runs-on: ubuntu-latest 22 | outputs: 23 | command: ${{ steps.check-release.outputs.command }} 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | ref: 'main' 30 | # This will only cause the `check-plan` job to have a result of `success` 31 | # when the .release-plan.json file was changed on the last commit. This 32 | # plus the fact that this action only runs on main will be enough of a guard 33 | - id: check-release 34 | run: if git diff --name-only HEAD HEAD~1 | grep -w -q ".release-plan.json"; then echo "command=release"; fi >> $GITHUB_OUTPUT 35 | 36 | publish: 37 | name: "NPM Publish" 38 | runs-on: ubuntu-latest 39 | needs: check-plan 40 | if: needs.check-plan.outputs.command == 'release' 41 | permissions: 42 | contents: write 43 | pull-requests: write 44 | id-token: write 45 | attestations: write 46 | 47 | steps: 48 | - uses: actions/checkout@v4 49 | - uses: pnpm/action-setup@v4 50 | - uses: actions/setup-node@v4 51 | with: 52 | node-version: 18 53 | # This creates an .npmrc that reads the NODE_AUTH_TOKEN environment variable 54 | registry-url: 'https://registry.npmjs.org' 55 | cache: pnpm 56 | - run: pnpm install --frozen-lockfile 57 | - name: npm publish 58 | run: NPM_CONFIG_PROVENANCE=true pnpm release-plan publish 59 | env: 60 | GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} 61 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 62 | -------------------------------------------------------------------------------- /.github/workflows/push-dist.yml: -------------------------------------------------------------------------------- 1 | # Because this library needs to be built, 2 | # we can't easily point package.json files at the git repo for easy cross-repo testing. 3 | # 4 | # This workflow brings back that capability by placing the compiled assets on a "dist" branch 5 | # (configurable via the "branch" option below) 6 | name: Push dist 7 | 8 | on: 9 | push: 10 | branches: 11 | - main 12 | - master 13 | 14 | jobs: 15 | push-dist: 16 | name: Push dist 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 10 19 | permissions: 20 | contents: write 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: pnpm/action-setup@v4 25 | - uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4 26 | with: 27 | # uses Volta! 28 | node-version-file: 'package.json' 29 | cache: pnpm 30 | - name: Install Dependencies 31 | run: pnpm install --frozen-lockfile 32 | - uses: kategengler/put-built-npm-package-contents-on-branch@v2.0.0 33 | with: 34 | branch: dist 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | working-directory: 'ember-cli-stripe' 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # misc 7 | .env* 8 | .pnp* 9 | .pnpm-debug.log 10 | .sass-cache 11 | .eslintcache 12 | coverage/ 13 | npm-debug.log* 14 | yarn-error.log 15 | 16 | # ember-try 17 | /.node_modules.ember-try/ 18 | /package.json.ember-try 19 | /package-lock.json.ember-try 20 | /yarn.lock.ember-try 21 | /pnpm-lock.ember-try.yaml 22 | 23 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Prettier is also run from each package, so the ignores here 2 | # protect against files that may not be within a package 3 | 4 | # misc 5 | !.* 6 | .lint-todo/ 7 | 8 | # ember-try 9 | /.node_modules.ember-try/ 10 | /pnpm-lock.ember-try.yaml 11 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | plugins: ['prettier-plugin-ember-template-tag'], 5 | singleQuote: true, 6 | }; 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | - `git clone ` 6 | - `cd ember-cli-stripe` 7 | - `pnpm install` 8 | 9 | ## Linting 10 | 11 | - `pnpm lint` 12 | - `pnpm lint:fix` 13 | 14 | ## Building the addon 15 | 16 | - `cd ember-cli-stripe` 17 | - `pnpm build` 18 | 19 | ## Running tests 20 | 21 | - `cd test-app` 22 | - `pnpm test` – Runs the test suite on the current Ember version 23 | - `pnpm test:watch` – Runs the test suite in "watch mode" 24 | 25 | ## Running the test application 26 | 27 | - `cd test-app` 28 | - `pnpm start` 29 | - Visit the test application at [http://localhost:4200](http://localhost:4200). 30 | 31 | For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/). 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-cli-stripe 2 | 3 | [![Build Status](https://travis-ci.org/smile-io/ember-cli-stripe.svg?branch=main)](http://travis-ci.org/smile-io/ember-cli-stripe) 4 | 5 | ![Preview](https://user-images.githubusercontent.com/160955/42161490-d734da26-7e03-11e8-97ca-761285ac2dff.png) 6 | 7 | ## Description 8 | 9 | Stripe checkout for Ember is the simplest way to implement card payments in your Ember app. 10 | 11 | This addon integrates Stripe's embedded payment form, Checkout. 12 | See [Stripe Checkout](https://stripe.com/docs/checkout#integration-custom) docs. 13 | 14 | The best documentation is the sample application in `tests/dummy`. 15 | 16 | 17 | ## Installation 18 | 19 | ```sh 20 | ember install ember-cli-stripe 21 | ``` 22 | 23 | ## Usage 24 | 25 | ```handlebars 26 | 33 | ``` 34 | 35 | ## Component properties 36 | 37 | | Property | Purpose | 38 | | -------------- | ---------------------------------------------------------- | 39 | | `label` | Stripe Checkout button text. | 40 | | `isDisabled` | When true, the Stripe Checkout button is disabled. | 41 | | `showCheckout` | Can be used to open the Stripe Checkout modal dynamically. | 42 | 43 | 44 | Besides the above, all [Stripe Checkout configuration options](https://stripe.com/docs/checkout#integration-custom) 45 | are supported. If you notice anything missing please open an issue. 46 | 47 | ### Actions 48 | 49 | The primary action of this component, `onToken` is called when the Stripe checkout succeeds. Its main param is a [Stripe Token](https://stripe.com/docs/api#tokens) object. 50 | 51 | ```javascript 52 | import Ember from "ember"; 53 | 54 | export default Ember.Controller.extend({ 55 | actions: { 56 | /** 57 | * Receives a Stripe token after checkout succeeds 58 | * The token looks like this https://stripe.com/docs/api#tokens 59 | */ 60 | processStripeToken(token, args) { 61 | // Send token to the server to associate with account/user/etc 62 | }, 63 | }, 64 | }); 65 | ``` 66 | 67 | List of all actions: 68 | 69 | | Action | Purpose | 70 | | ---------- | ----------------------------------------------------------- | 71 | | `onToken` | The callback invoked when the Checkout process is complete. | 72 | | `onOpened` | The callback invoked when Checkout is opened. | 73 | | `onClosed` | The callback invoked when Checkout is closed. | 74 | 75 | ## Configuration 76 | 77 | All Stripe Checkout configuration options can be set in your apps config. 78 | 79 | In most cases, you will want to add at least your Stripe **publishable key** to your app's config, but this can be set as a property on the component too. 80 | 81 | ```javascript 82 | // config/environment.js 83 | module.exports = function(environment) { 84 | var ENV = { 85 | stripe: { 86 | key: 'pk_test_C0sa3IlkLWBlrB8laH2fbqfh', 87 | .... 88 | }, 89 | }; 90 | 91 | return ENV; 92 | }; 93 | ``` 94 | 95 | **Note:** If Stripe options are set in the _environment.js_ file **and** when invoking the component, the later value will win. 96 | 97 | Multiple Stripe keys are supported, when passed directly to the component. 98 | 99 | ## Compatibility 100 | 101 | * Ember.js v4.4 or above 102 | * Ember CLI v4.4 or above 103 | * Node.js v14 or above 104 | 105 | ### For older versions of Ember.js 106 | 107 | | Ember.js version | ember-cli-stripe version | 108 | | ---------------- | ------------------------ | 109 | | `4+` | `4.x` | 110 | | `3.4+` | `3.x` | 111 | | `2.18+` | `2.x` | 112 | | `1.13+` | `0.x` | 113 | 114 | **Note:** At your own risk, feel free to try current version, it might still work. 115 | 116 | ## Contributing 117 | 118 | See the [Contributing](CONTRIBUTING.md) guide for details. 119 | 120 | ## License 121 | 122 | This project is licensed under the [MIT License](LICENSE.md). 123 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | Releases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/release-plan/). Once you label all your PRs correctly (see below) you will have an automatically generated PR that updates your CHANGELOG.md file and a `.release-plan.json` that is used to prepare the release once the PR is merged. 4 | 5 | ## Preparation 6 | 7 | Since the majority of the actual release process is automated, the remaining tasks before releasing are: 8 | 9 | - correctly labeling **all** pull requests that have been merged since the last release 10 | - updating pull request titles so they make sense to our users 11 | 12 | Some great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.1.0/), but the overall 13 | guiding principle here is that changelogs are for humans, not machines. 14 | 15 | When reviewing merged PR's the labels to be used are: 16 | 17 | - breaking - Used when the PR is considered a breaking change. 18 | - enhancement - Used when the PR adds a new feature or enhancement. 19 | - bug - Used when the PR fixes a bug included in a previous release. 20 | - documentation - Used when the PR adds or updates documentation. 21 | - internal - Internal changes or things that don't fit in any other category. 22 | 23 | **Note:** `release-plan` requires that **all** PRs are labeled. If a PR doesn't fit in a category it's fine to label it as `internal` 24 | 25 | ## Release 26 | 27 | Once the prep work is completed, the actual release is straight forward: you just need to merge the open [Plan Release](https://github.com/smile-io/ember-cli-stripe/pulls?q=is%3Apr+is%3Aopen+%22Prepare+Release%22+in%3Atitle) PR 28 | -------------------------------------------------------------------------------- /config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "projectName": "ember-cli-stripe", 4 | "packages": [ 5 | { 6 | "name": "@embroider/addon-blueprint", 7 | "version": "4.0.0", 8 | "blueprints": [ 9 | { 10 | "name": "@embroider/addon-blueprint", 11 | "isBaseBlueprint": true, 12 | "options": [ 13 | "--ci-provider=github", 14 | "--pnpm" 15 | ] 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /ember-cli-stripe/.gitignore: -------------------------------------------------------------------------------- 1 | # The authoritative copies of these live in the monorepo root (because they're 2 | # more useful on github that way), but the build copies them into here so they 3 | # will also appear in published NPM packages. 4 | /README.md 5 | /LICENSE.md 6 | 7 | # compiled output 8 | dist/ 9 | declarations/ 10 | 11 | # npm/pnpm/yarn pack output 12 | *.tgz 13 | 14 | # deps & caches 15 | node_modules/ 16 | .eslintcache 17 | .prettiercache 18 | -------------------------------------------------------------------------------- /ember-cli-stripe/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | /declarations/ 7 | 8 | # misc 9 | /coverage/ 10 | -------------------------------------------------------------------------------- /ember-cli-stripe/.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | plugins: ['prettier-plugin-ember-template-tag'], 5 | overrides: [ 6 | { 7 | files: '*.{js,gjs,ts,gts,mjs,mts,cjs,cts}', 8 | options: { 9 | singleQuote: true, 10 | templateSingleQuote: false, 11 | }, 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /ember-cli-stripe/.template-lintrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /ember-cli-stripe/addon-main.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { addonV1Shim } = require('@embroider/addon-shim'); 4 | module.exports = addonV1Shim(__dirname); 5 | -------------------------------------------------------------------------------- /ember-cli-stripe/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@embroider/addon-dev/template-colocation-plugin", 4 | [ 5 | "babel-plugin-ember-template-compilation", 6 | { 7 | "targetFormat": "hbs", 8 | "transforms": [] 9 | } 10 | ], 11 | [ 12 | "module:decorator-transforms", 13 | { "runtime": { "import": "decorator-transforms/runtime" } } 14 | ] 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /ember-cli-stripe/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Debugging: 3 | * https://eslint.org/docs/latest/use/configure/debug 4 | * ---------------------------------------------------- 5 | * 6 | * Print a file's calculated configuration 7 | * 8 | * npx eslint --print-config path/to/file.js 9 | * 10 | * Inspecting the config 11 | * 12 | * npx eslint --inspect-config 13 | * 14 | */ 15 | import babelParser from '@babel/eslint-parser'; 16 | import js from '@eslint/js'; 17 | import prettier from 'eslint-config-prettier'; 18 | import ember from 'eslint-plugin-ember/recommended'; 19 | import importPlugin from 'eslint-plugin-import'; 20 | import n from 'eslint-plugin-n'; 21 | import globals from 'globals'; 22 | 23 | const esmParserOptions = { 24 | ecmaFeatures: { modules: true }, 25 | ecmaVersion: 'latest', 26 | }; 27 | 28 | export default [ 29 | js.configs.recommended, 30 | prettier, 31 | ember.configs.base, 32 | ember.configs.gjs, 33 | /** 34 | * Ignores must be in their own object 35 | * https://eslint.org/docs/latest/use/configure/ignore 36 | */ 37 | { 38 | ignores: ['dist/', 'declarations/', 'node_modules/', 'coverage/', '!**/.*'], 39 | }, 40 | /** 41 | * https://eslint.org/docs/latest/use/configure/configuration-files#configuring-linter-options 42 | */ 43 | { 44 | linterOptions: { 45 | reportUnusedDisableDirectives: 'error', 46 | }, 47 | }, 48 | { 49 | files: ['**/*.js'], 50 | languageOptions: { 51 | parser: babelParser, 52 | }, 53 | }, 54 | { 55 | files: ['**/*.{js,gjs}'], 56 | languageOptions: { 57 | parserOptions: esmParserOptions, 58 | globals: { 59 | ...globals.browser, 60 | }, 61 | }, 62 | }, 63 | { 64 | files: ['src/**/*'], 65 | plugins: { 66 | import: importPlugin, 67 | }, 68 | rules: { 69 | // require relative imports use full extensions 70 | 'import/extensions': ['error', 'always', { ignorePackages: true }], 71 | }, 72 | }, 73 | /** 74 | * CJS node files 75 | */ 76 | { 77 | files: [ 78 | '**/*.cjs', 79 | '.prettierrc.js', 80 | '.stylelintrc.js', 81 | '.template-lintrc.js', 82 | 'addon-main.cjs', 83 | ], 84 | plugins: { 85 | n, 86 | }, 87 | 88 | languageOptions: { 89 | sourceType: 'script', 90 | ecmaVersion: 'latest', 91 | globals: { 92 | ...globals.node, 93 | }, 94 | }, 95 | }, 96 | /** 97 | * ESM node files 98 | */ 99 | { 100 | files: ['**/*.mjs'], 101 | plugins: { 102 | n, 103 | }, 104 | 105 | languageOptions: { 106 | sourceType: 'module', 107 | ecmaVersion: 'latest', 108 | parserOptions: esmParserOptions, 109 | globals: { 110 | ...globals.node, 111 | }, 112 | }, 113 | }, 114 | ]; 115 | -------------------------------------------------------------------------------- /ember-cli-stripe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-cli-stripe", 3 | "version": "4.0.1", 4 | "description": "Stripe checkout for Ember", 5 | "keywords": [ 6 | "ember-addon", 7 | "stripe", 8 | "stripe.js", 9 | "stripejs", 10 | "smile", 11 | "smile.io" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/smile-io/ember-cli-stripe", 16 | "directory": "ember-cli-stripe" 17 | }, 18 | "license": "MIT", 19 | "author": "Smile.io ", 20 | "exports": { 21 | ".": "./dist/index.js", 22 | "./*": "./dist/*.js", 23 | "./addon-main.js": "./addon-main.cjs" 24 | }, 25 | "files": [ 26 | "addon-main.cjs", 27 | "declarations", 28 | "dist" 29 | ], 30 | "scripts": { 31 | "build": "rollup --config", 32 | "format": "prettier . --cache --write", 33 | "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto", 34 | "lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto && pnpm run format", 35 | "lint:format": "prettier . --cache --check", 36 | "lint:hbs": "ember-template-lint . --no-error-on-unmatched-pattern", 37 | "lint:hbs:fix": "ember-template-lint . --fix --no-error-on-unmatched-pattern", 38 | "lint:js": "eslint . --cache", 39 | "lint:js:fix": "eslint . --fix", 40 | "prepack": "rollup --config", 41 | "start": "rollup --config --watch", 42 | "test": "echo 'A v2 addon does not have tests, run tests in test-app'" 43 | }, 44 | "release-plan": { 45 | "semverIncrementAs": { 46 | "major": "prerelease", 47 | "minor": "prerelease", 48 | "patch": "prerelease" 49 | }, 50 | "semverIncrementTag": "alpha" 51 | }, 52 | "dependencies": { 53 | "@embroider/addon-shim": "^1.9.0", 54 | "decorator-transforms": "^2.3.0" 55 | }, 56 | "devDependencies": { 57 | "@babel/core": "7.26.7", 58 | "@babel/eslint-parser": "^7.26.5", 59 | "@babel/runtime": "^7.26.10", 60 | "@embroider/addon-dev": "^7.1.1", 61 | "@eslint/js": "^9.17.0", 62 | "@glimmer/component": "^2.0.0", 63 | "@rollup/plugin-babel": "^6.0.4", 64 | "babel-plugin-ember-template-compilation": "^2.3.0", 65 | "concurrently": "^9.1.2", 66 | "ember-modifier": "^4.2.0", 67 | "ember-source": "^6.2.0", 68 | "ember-template-imports": "^4.3.0", 69 | "ember-template-lint": "^6.1.0", 70 | "eslint": "^9.17.0", 71 | "eslint-config-prettier": "^9.1.0", 72 | "eslint-plugin-ember": "^12.3.3", 73 | "eslint-plugin-import": "^2.31.0", 74 | "eslint-plugin-n": "^17.15.1", 75 | "globals": "^15.14.0", 76 | "prettier": "^3.4.2", 77 | "prettier-plugin-ember-template-tag": "^2.0.4", 78 | "rollup": "^4.22.5", 79 | "rollup-plugin-copy": "^3.5.0" 80 | }, 81 | "peerDependencies": { 82 | "ember-source": ">= 4.12.0", 83 | "ember-modifier": ">= 4.2.0" 84 | }, 85 | "ember": { 86 | "edition": "octane" 87 | }, 88 | "ember-addon": { 89 | "version": 2, 90 | "main": "addon-main.cjs", 91 | "type": "addon", 92 | "app-js": { 93 | "./components/stripe-checkout.js": "./dist/_app_/components/stripe-checkout.js", 94 | "./services/stripe.js": "./dist/_app_/services/stripe.js", 95 | "./utils/configuration-options.js": "./dist/_app_/utils/configuration-options.js", 96 | "./utils/load-script.js": "./dist/_app_/utils/load-script.js" 97 | } 98 | }, 99 | "volta": { 100 | "extends": "../package.json" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ember-cli-stripe/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { babel } from '@rollup/plugin-babel'; 2 | import copy from 'rollup-plugin-copy'; 3 | import { Addon } from '@embroider/addon-dev/rollup'; 4 | 5 | const addon = new Addon({ 6 | srcDir: 'src', 7 | destDir: 'dist', 8 | }); 9 | 10 | export default { 11 | // This provides defaults that work well alongside `publicEntrypoints` below. 12 | // You can augment this if you need to. 13 | output: addon.output(), 14 | 15 | plugins: [ 16 | // These are the modules that users should be able to import from your 17 | // addon. Anything not listed here may get optimized away. 18 | // By default all your JavaScript modules (**/*.js) will be importable. 19 | // But you are encouraged to tweak this to only cover the modules that make 20 | // up your addon's public API. Also make sure your package.json#exports 21 | // is aligned to the config here. 22 | // See https://github.com/embroider-build/embroider/blob/main/docs/v2-faq.md#how-can-i-define-the-public-exports-of-my-addon 23 | addon.publicEntrypoints(['**/*.js', 'index.js']), 24 | 25 | // These are the modules that should get reexported into the traditional 26 | // "app" tree. Things in here should also be in publicEntrypoints above, but 27 | // not everything in publicEntrypoints necessarily needs to go here. 28 | addon.appReexports([ 29 | 'components/**/*.js', 30 | 'helpers/**/*.js', 31 | 'modifiers/**/*.js', 32 | 'services/**/*.js', 33 | 'utils/**/*.js', 34 | ]), 35 | 36 | // Follow the V2 Addon rules about dependencies. Your code can import from 37 | // `dependencies` and `peerDependencies` as well as standard Ember-provided 38 | // package names. 39 | addon.dependencies(), 40 | 41 | // This babel config should *not* apply presets or compile away ES modules. 42 | // It exists only to provide development niceties for you, like automatic 43 | // template colocation. 44 | // 45 | // By default, this will load the actual babel config from the file 46 | // babel.config.json. 47 | babel({ 48 | extensions: ['.js', '.gjs'], 49 | babelHelpers: 'bundled', 50 | }), 51 | 52 | // Ensure that standalone .hbs files are properly integrated as Javascript. 53 | addon.hbs(), 54 | 55 | // Ensure that .gjs files are properly integrated as Javascript 56 | addon.gjs(), 57 | 58 | // addons are allowed to contain imports of .css files, which we want rollup 59 | // to leave alone and keep in the published output. 60 | addon.keepAssets(['**/*.css']), 61 | 62 | // Remove leftover build artifacts when starting a new build. 63 | addon.clean(), 64 | 65 | // Copy Readme and License into published package 66 | copy({ 67 | targets: [ 68 | { src: '../README.md', dest: '.' }, 69 | { src: '../LICENSE.md', dest: '.' }, 70 | ], 71 | }), 72 | ], 73 | }; 74 | -------------------------------------------------------------------------------- /ember-cli-stripe/src/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smile-io/ember-cli-stripe/da89fb2cb36aa080502bea7dd4dd8a7e82bd52c5/ember-cli-stripe/src/.gitkeep -------------------------------------------------------------------------------- /ember-cli-stripe/src/components/stripe-checkout.gjs: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { inject as service } from '@ember/service'; 3 | import { on } from '@ember/modifier'; 4 | import { 5 | configurationOptions, 6 | compactOptions, 7 | } from '../utils/configuration-options.js'; 8 | import { modifier } from 'ember-modifier'; 9 | 10 | /** 11 | * Stripe checkout component for accepting payments with 12 | * an embedded form. 13 | * 14 | * Stripe docs: https://stripe.com/docs/tutorials/checkout 15 | * List of possible Stripe options: ../utils/configuration-options.js 16 | * 17 | * Usage: 18 | * 25 | * 26 | */ 27 | export default class StripeCheckout extends Component { 28 | @service stripe; 29 | 30 | /** 31 | * Stripe checkout button text. 32 | */ 33 | get label() { 34 | return this.args.label ?? 'Pay with card'; 35 | } 36 | 37 | get stripeConfig() { 38 | return { 39 | ...compactOptions( 40 | Object.fromEntries( 41 | configurationOptions.map((key) => [key, this.args[key]]), 42 | ), 43 | ), 44 | onToken: this.args.onToken, 45 | onOpened: this.args.onOpened, 46 | onClosed: this.args.onClosed, 47 | }; 48 | } 49 | 50 | handleClick = () => { 51 | this.stripe.open(this.stripeConfig); 52 | }; 53 | 54 | autoOpenCheckout = modifier((element, [autoOpen]) => { 55 | if (autoOpen) { 56 | this.handleClick(); 57 | } 58 | }); 59 | 60 | 75 | } 76 | -------------------------------------------------------------------------------- /ember-cli-stripe/src/services/stripe.js: -------------------------------------------------------------------------------- 1 | /* global StripeCheckout */ 2 | import Service from '@ember/service'; 3 | import { cached } from '@glimmer/tracking'; 4 | import { getOwner } from '@ember/application'; 5 | import { loadScript } from '../utils/load-script.js'; 6 | import { compactOptions } from '../utils/configuration-options.js'; 7 | 8 | const STRIPE_CHECKOUT_SCRIPT_URL = 'https://checkout.stripe.com/checkout.js'; 9 | let stripeCheckoutScript; 10 | 11 | export default class StripeService extends Service { 12 | @cached 13 | get config() { 14 | return ( 15 | compactOptions( 16 | getOwner(this).resolveRegistration('config:environment').stripe, 17 | ) || {} 18 | ); 19 | } 20 | 21 | async open(config = {}, onScriptLoad, onScriptError) { 22 | await this.loadStripeCheckout(onScriptLoad, onScriptError); 23 | 24 | const fullConfig = this.#fullConfig(config); 25 | if (!('key' in fullConfig)) { 26 | throw new Error('[ember-cli-stripe] Missing required `key` param!'); 27 | } 28 | 29 | StripeCheckout.open(fullConfig); 30 | } 31 | 32 | loadStripeCheckout(onScriptLoad, onScriptError) { 33 | if (stripeCheckoutScript) { 34 | return stripeCheckoutScript; 35 | } 36 | 37 | stripeCheckoutScript = loadScript(STRIPE_CHECKOUT_SCRIPT_URL, { 38 | onLoad: onScriptLoad, 39 | onError: onScriptError, 40 | }); 41 | return stripeCheckoutScript; 42 | } 43 | 44 | /** 45 | * Final Stripe config options with env configs merged with the ones provided explicltly on checkout open. 46 | */ 47 | #fullConfig(config) { 48 | return Object.assign( 49 | { 50 | token: config.onToken, 51 | opened: () => config.onOpened?.(), 52 | closed: () => config.onClosed?.(), 53 | }, 54 | this.config, 55 | compactOptions(config), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ember-cli-stripe/src/utils/configuration-options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * All the configuration options supported by Stripe Checkout. 3 | * Docs: https://stripe.com/docs/checkout#integration-custom 4 | * @type {Array} 5 | */ 6 | const configurationOptions = [ 7 | // Required 8 | 'key', 9 | 10 | // Highly recommended 11 | 12 | /** 13 | * A relative or absolute URL pointing to a square image of your brand or 14 | * product. The recommended minimum size is 128x128px. The supported image 15 | * types are: .gif, .jpeg, and .png. 16 | */ 17 | 'image', 18 | 19 | /** 20 | * The name of your company or website. 21 | */ 22 | 'name', 23 | 24 | /** 25 | * A description of the product or service being purchased. 26 | */ 27 | 'description', 28 | 29 | /** 30 | * The amount (in cents) that's shown to the user. Note that you will still 31 | * have to explicitly include the amount when you create a charge using 32 | * the API. 33 | */ 34 | 'amount', 35 | 36 | /** 37 | * Specify auto to display Checkout in the user's preferred language, 38 | * if available. English will be used by default. 39 | */ 40 | 'locale', 41 | 42 | /** 43 | * Specify whether Checkout should validate the billing postal code 44 | * (true or false). The default is false, but we highly recommend 45 | * setting to true. 46 | */ 47 | 'zipCode', 48 | 49 | /** 50 | * Specify whether Checkout should collect the user's billing address 51 | * (true or false). The default is false. 52 | */ 53 | 'billingAddress', 54 | 55 | // Optional 56 | 57 | /** 58 | * The currency of the amount (3-letter ISO code). The default is USD. 59 | */ 60 | 'currency', 61 | 62 | /** 63 | * The label of the payment button in the Checkout form (e.g. Subscribe, 64 | * Pay {{amount}}, etc.). If you include {{amount}} in the label value, 65 | * it will be replaced by a localized version of data-amount. Otherwise, 66 | * a localized data-amount will be appended to the end of your label. 67 | * Checkout does not translate custom labels to the user's preferred 68 | * language. 69 | */ 70 | 'panelLabel', 71 | 72 | /** 73 | * Specify whether Checkout should collect the user's shipping address 74 | * (true or false). The default is false. 75 | */ 76 | 'shippingAddress', 77 | 78 | /** 79 | * If you already know the email address of your user, you can provide it 80 | * to Checkout to be prefilled. 81 | */ 82 | 'email', 83 | 84 | /** 85 | * Specify whether to include the option to "Remember Me" for future 86 | * purchases (true or false). The default is true. 87 | */ 88 | 'allowRememberMe', 89 | 90 | /** 91 | * Specify whether to accept Bitcoin (true or false). The default is false. 92 | */ 93 | 'bitcoin', 94 | 95 | /** 96 | * Specify whether to accept Alipay ("auto", true, or false). The 97 | * default is false. 98 | */ 99 | 'alipay', 100 | 101 | /** 102 | * Specify if you need reusable access to the customer's Alipay account 103 | * (true or false). The default is false. 104 | */ 105 | 'alipayReusable', 106 | ]; 107 | 108 | const compactOptions = (options) => { 109 | let cleanedOptions = {}; 110 | 111 | for (let key in options) { 112 | if ( 113 | configurationOptions.includes(key) && 114 | typeof options[key] !== 'undefined' 115 | ) { 116 | cleanedOptions[key] = options[key]; 117 | } 118 | } 119 | 120 | return cleanedOptions; 121 | }; 122 | 123 | export { configurationOptions, compactOptions }; 124 | -------------------------------------------------------------------------------- /ember-cli-stripe/src/utils/load-script.js: -------------------------------------------------------------------------------- 1 | import { isTesting, macroCondition } from '@embroider/macros'; 2 | 3 | const loadedScripts = {}; 4 | let loadScript; 5 | 6 | if (macroCondition(isTesting())) { 7 | loadScript = async () => { 8 | return Promise.resolve(); 9 | }; 10 | } else { 11 | /** 12 | * Loads a script into a document, ensuring it's loaded only once when loading in the main browser 13 | * document. 14 | * 15 | * @param {string} url The script to load 16 | * @param {object} opts Options for loading the script 17 | * @param {boolean} opts.async Optional. Whether to load the script asynchronously (default: true). 18 | * @returns {Promise} Resolves when the script is successfully loaded. 19 | */ 20 | loadScript = async ( 21 | url, 22 | { async = true, onLoad = () => {}, onError = () => {} } = {}, 23 | ) => { 24 | // Return cached promise if present 25 | if (loadedScripts[url]) { 26 | return loadedScripts[url]; 27 | } 28 | 29 | // Create a new promise for loading the script 30 | const promise = new Promise((resolve, reject) => { 31 | const script = document.createElement('script'); 32 | script.type = 'text/javascript'; 33 | script.async = async; 34 | script.src = url; 35 | 36 | script.addEventListener( 37 | 'load', 38 | () => { 39 | onLoad(); 40 | resolve(); 41 | }, 42 | false, 43 | ); 44 | script.addEventListener( 45 | 'error', 46 | () => { 47 | const error = new Error(`Could not load script: ${url}`); 48 | onError(error); 49 | reject(error); 50 | }, 51 | false, 52 | ); 53 | 54 | document.body.appendChild(script); 55 | }); 56 | 57 | loadedScripts[url] = promise; 58 | 59 | return promise; 60 | }; 61 | } 62 | 63 | export { loadScript }; 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "repository": { 4 | "type": "git", 5 | "url": "git@github.com:smile-io/ember-cli-stripe.git" 6 | }, 7 | "license": "MIT", 8 | "author": "", 9 | "scripts": { 10 | "build": "pnpm --filter ember-cli-stripe build", 11 | "lint": "pnpm --filter '*' lint", 12 | "lint:fix": "pnpm --filter '*' lint:fix", 13 | "prepare": "pnpm build", 14 | "start": "concurrently 'pnpm:start:*' --restart-after 5000 --prefix-colors cyan,white,yellow", 15 | "start:addon": "pnpm --filter ember-cli-stripe start", 16 | "start:test-app": "pnpm --filter test-app start", 17 | "test": "pnpm --filter '*' test" 18 | }, 19 | "devDependencies": { 20 | "concurrently": "^8.2.0", 21 | "prettier": "^3.0.3", 22 | "prettier-plugin-ember-template-tag": "^2.0.2", 23 | "release-plan": "^0.16.0" 24 | }, 25 | "packageManager": "pnpm@10.2.1", 26 | "volta": { 27 | "node": "22.14.0", 28 | "pnpm": "10.4.0" 29 | }, 30 | "pnpm": { 31 | "overrides": { 32 | "micromatch@3.1.10>braces": "3.0.3", 33 | "broccoli@3.5.2>ansi-html": "0.0.8", 34 | "broccoli-middleware@2.1.1>ansi-html": "0.0.8", 35 | "ember-cli-babel>@babel/runtime": "7.26.10", 36 | "@babel/helpers": "7.26.10", 37 | "sane@4.1.0>micromatch": "4.0.8", 38 | "anymatch@2.0.0>micromatch": "4.0.8", 39 | "package-json@6.5.0>got": "11.8.5" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'ember-cli-stripe' 3 | - 'test-app' 4 | -------------------------------------------------------------------------------- /test-app/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /test-app/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript 4 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available. 5 | */ 6 | "isTypeScriptProject": false 7 | } 8 | -------------------------------------------------------------------------------- /test-app/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: {} 9 | 10 | concurrency: 11 | group: ci-${{ github.head_ref || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | lint: 16 | name: "Lint" 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 10 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: pnpm/action-setup@v2 23 | with: 24 | version: 8 25 | - name: Install Node 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: 18 29 | cache: pnpm 30 | - name: Install Dependencies 31 | run: pnpm install --frozen-lockfile 32 | - name: Lint 33 | run: pnpm lint 34 | 35 | test: 36 | name: "Test" 37 | runs-on: ubuntu-latest 38 | timeout-minutes: 10 39 | 40 | steps: 41 | - uses: actions/checkout@v3 42 | - uses: pnpm/action-setup@v2 43 | with: 44 | version: 8 45 | - name: Install Node 46 | uses: actions/setup-node@v3 47 | with: 48 | node-version: 18 49 | cache: pnpm 50 | - name: Install Dependencies 51 | run: pnpm install --frozen-lockfile 52 | - name: Run Tests 53 | run: pnpm test 54 | -------------------------------------------------------------------------------- /test-app/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /declarations/ 4 | 5 | # dependencies 6 | /node_modules/ 7 | 8 | # misc 9 | /.env* 10 | /.pnp* 11 | /.eslintcache 12 | /coverage/ 13 | /npm-debug.log* 14 | /testem.log 15 | /yarn-error.log 16 | 17 | # ember-try 18 | /.node_modules.ember-try/ 19 | /npm-shrinkwrap.json.ember-try 20 | /package.json.ember-try 21 | /package-lock.json.ember-try 22 | /yarn.lock.ember-try 23 | 24 | # broccoli-debug 25 | /DEBUG/ 26 | -------------------------------------------------------------------------------- /test-app/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | 7 | # misc 8 | /coverage/ 9 | !.* 10 | .*/ 11 | 12 | # ember-try 13 | /.node_modules.ember-try/ 14 | -------------------------------------------------------------------------------- /test-app/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | plugins: ['prettier-plugin-ember-template-tag'], 5 | overrides: [ 6 | { 7 | files: '*.{js,gjs,ts,gts,mjs,mts,cjs,cts}', 8 | options: { 9 | singleQuote: true, 10 | }, 11 | }, 12 | { 13 | files: '*.{gjs,gts}', 14 | options: { 15 | singleQuote: true, 16 | templateSingleQuote: false, 17 | }, 18 | }, 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /test-app/.stylelintignore: -------------------------------------------------------------------------------- 1 | # unconventional files 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | 7 | # addons 8 | /.node_modules.ember-try/ 9 | -------------------------------------------------------------------------------- /test-app/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'], 5 | }; 6 | -------------------------------------------------------------------------------- /test-app/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /test-app/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["dist"] 3 | } 4 | -------------------------------------------------------------------------------- /test-app/README.md: -------------------------------------------------------------------------------- 1 | # test-app 2 | 3 | [Short description of the addon.] 4 | 5 | ## Compatibility 6 | 7 | - Ember.js v4.12 or above 8 | - Ember CLI v4.12 or above 9 | - Node.js v18 or above 10 | 11 | ## Installation 12 | 13 | ``` 14 | ember install test-app 15 | ``` 16 | 17 | ## Usage 18 | 19 | [Longer description of how to use the addon in apps.] 20 | 21 | ## Contributing 22 | 23 | See the [Contributing](CONTRIBUTING.md) guide for details. 24 | 25 | ## License 26 | 27 | This project is licensed under the [MIT License](LICENSE.md). 28 | -------------------------------------------------------------------------------- /test-app/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'test-app/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /test-app/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smile-io/ember-cli-stripe/da89fb2cb36aa080502bea7dd4dd8a7e82bd52c5/test-app/app/components/.gitkeep -------------------------------------------------------------------------------- /test-app/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smile-io/ember-cli-stripe/da89fb2cb36aa080502bea7dd4dd8a7e82bd52c5/test-app/app/controllers/.gitkeep -------------------------------------------------------------------------------- /test-app/app/controllers/advanced-usage.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET advanced-usage-actions 2 | import Controller from '@ember/controller'; 3 | import { action } from '@ember/object'; 4 | 5 | export default class AdvancedUsageController extends Controller { 6 | @action 7 | handleToken(/*amount, token, args*/) { 8 | console.log(...arguments); 9 | } 10 | 11 | @action 12 | checkoutOpened() {} 13 | 14 | @action 15 | checkoutClosed() {} 16 | } 17 | // END-SNIPPET 18 | -------------------------------------------------------------------------------- /test-app/app/controllers/multiple-keys-usage.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | 4 | export default class MultipleKeysUsageController extends Controller { 5 | @action 6 | checkoutToken(/*amount, token, args*/) { 7 | console.log(...arguments); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test-app/app/controllers/simple-usage.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET simple-usage-actions 2 | import Controller from '@ember/controller'; 3 | import { tracked } from '@glimmer/tracking'; 4 | 5 | export default class SimpleUsageController extends Controller { 6 | @tracked showCheckout = true; 7 | amount = 1000; 8 | 9 | handleClick = () => { 10 | this.showCheckout = !this.showCheckout; 11 | }; 12 | 13 | handleToken = (token) => { 14 | console.log('StripeCheckout TOKEN', token); 15 | }; 16 | 17 | handleOpened = () => { 18 | console.log('StripeCheckout opened'); 19 | }; 20 | 21 | handleClosed = () => { 22 | console.log('StripeCheckout closed'); 23 | this.showCheckout = false; 24 | }; 25 | } 26 | // END-SNIPPET 27 | -------------------------------------------------------------------------------- /test-app/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smile-io/ember-cli-stripe/da89fb2cb36aa080502bea7dd4dd8a7e82bd52c5/test-app/app/helpers/.gitkeep -------------------------------------------------------------------------------- /test-app/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 14 | {{content-for "head-footer"}} 15 | 16 | 17 | {{content-for "body"}} 18 | 19 | 20 | 21 | 22 | {{content-for "body-footer"}} 23 | 24 | 25 | -------------------------------------------------------------------------------- /test-app/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smile-io/ember-cli-stripe/da89fb2cb36aa080502bea7dd4dd8a7e82bd52c5/test-app/app/models/.gitkeep -------------------------------------------------------------------------------- /test-app/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'test-app/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () { 10 | this.route('simple-usage', { path: '/' }); 11 | this.route('advanced-usage'); 12 | this.route('multiple-keys-usage'); 13 | }); 14 | -------------------------------------------------------------------------------- /test-app/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smile-io/ember-cli-stripe/da89fb2cb36aa080502bea7dd4dd8a7e82bd52c5/test-app/app/routes/.gitkeep -------------------------------------------------------------------------------- /test-app/app/styles/app.css: -------------------------------------------------------------------------------- 1 | .v-container { 2 | height: 100vh; 3 | } 4 | 5 | nav { 6 | width: 100%; 7 | padding: 1.5em; 8 | } 9 | 10 | nav a { 11 | display: inline-block; 12 | padding: 0.5em; 13 | color: #1997c6; 14 | text-decoration: none; 15 | } 16 | 17 | nav a.active { 18 | color: #fff; 19 | border-radius: 0.25em; 20 | background-color: #1997c6; 21 | } 22 | 23 | .container { 24 | padding: 1.5em; 25 | } 26 | -------------------------------------------------------------------------------- /test-app/app/templates/advanced-usage.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET advanced-usage-button-1 }} 2 | 12 | {{! END-SNIPPET }} 13 | 14 | {{! BEGIN-SNIPPET advanced-usage-button-2 }} 15 | 25 | {{! END-SNIPPET }} 26 | 27 | {{! BEGIN-SNIPPET advanced-usage-button-3 }} 28 | 39 | {{! END-SNIPPET }} 40 | 41 | {{!

Advanced usage

42 |

43 | You can render as many stripe-checkout components on a page as you wish. 44 |

45 |

46 | You can also bind a value to isDisabled to disable/enable the checkout button 47 | dynamically. 48 | 49 | 50 | Handling the actions is pretty straight-forward 51 | 52 |

}} -------------------------------------------------------------------------------- /test-app/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{page-title "ember-cli-stripe"}} 2 | 3 |
4 | 9 | 10 |
11 | {{outlet}} 12 |
13 |
14 | -------------------------------------------------------------------------------- /test-app/app/templates/components/code-snippet.hbs: -------------------------------------------------------------------------------- 1 | {{#let (get-code-snippet @name) as |snippet|}} 2 | 3 | {{snippet.source}} 4 | 5 | {{/let}} 6 | -------------------------------------------------------------------------------- /test-app/app/templates/multiple-keys-usage.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET multiple-keys-usage-config-key }} 2 | {{! Uses Stripe API key from configuration file }} 3 | 11 | {{! END-SNIPPET }} 12 | 13 | {{! BEGIN-SNIPPET multiple-keys-usage-custom-key }} 14 | {{! Uses custom Stripe API key that overwrites configuration file one }} 15 | 24 | {{!-- {{!- END-SNIPPET }} 25 | 26 |

Multiple keys usage

27 |

28 | The addon also supports using different Stripe API keys on the same page (ex: a marketplace). 29 |

30 |

31 | First Stripe checkout component uses the API key from the configuration file, 32 | while the second uses a custom one, passed directly to the component which overwrites 33 | the default configuration one. 34 | 35 | 36 |

--}} -------------------------------------------------------------------------------- /test-app/app/templates/simple-usage.hbs: -------------------------------------------------------------------------------- 1 | {{! BEGIN-SNIPPET simple-usage }} 2 | 11 | {{! END-SNIPPET }} 12 | {{! 13 |

Simple usage

14 |

15 | In the most simplest use-case, all you need is to render the stripe-checkout 16 | component passing any options supported by 17 | Stripe Checkout. 18 | 19 |

20 | 21 |

22 | You'll probably want to pass at least the amount and handle onToken action. 23 | 24 |

}} -------------------------------------------------------------------------------- /test-app/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "6.1.0", 7 | "blueprints": [ 8 | { 9 | "name": "app", 10 | "outputRepo": "https://github.com/ember-cli/ember-new-output", 11 | "codemodsSource": "ember-app-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--no-welcome", 15 | "--pnpm", 16 | "--ci-provider=github" 17 | ] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /test-app/config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 5 | 6 | module.exports = async function () { 7 | return { 8 | usePnpm: true, 9 | scenarios: [ 10 | { 11 | name: 'ember-lts-4.12', 12 | npm: { 13 | devDependencies: { 14 | 'ember-source': '~4.12.0', 15 | }, 16 | }, 17 | }, 18 | { 19 | name: 'ember-lts-5.4', 20 | npm: { 21 | devDependencies: { 22 | 'ember-source': '~5.4.0', 23 | }, 24 | }, 25 | }, 26 | { 27 | name: 'ember-release', 28 | npm: { 29 | devDependencies: { 30 | 'ember-source': await getChannelURL('release'), 31 | }, 32 | }, 33 | }, 34 | { 35 | name: 'ember-beta', 36 | npm: { 37 | devDependencies: { 38 | 'ember-source': await getChannelURL('beta'), 39 | }, 40 | }, 41 | }, 42 | { 43 | name: 'ember-canary', 44 | npm: { 45 | devDependencies: { 46 | 'ember-source': await getChannelURL('canary'), 47 | }, 48 | }, 49 | }, 50 | embroiderSafe(), 51 | embroiderOptimized(), 52 | ], 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /test-app/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'test-app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | EXTEND_PROTOTYPES: false, 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 14 | }, 15 | }, 16 | 17 | APP: { 18 | // Here you can pass flags/options to your application instance 19 | // when it is created 20 | }, 21 | 22 | stripe: { 23 | key: 'pk_test_C0sa3IlkLWBlrB8laH2fbqfh', 24 | name: 'My Company', 25 | }, 26 | }; 27 | 28 | if (environment === 'development') { 29 | // ENV.APP.LOG_RESOLVER = true; 30 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 31 | // ENV.APP.LOG_TRANSITIONS = true; 32 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 33 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 34 | } 35 | 36 | if (environment === 'test') { 37 | // Testem prefers this... 38 | ENV.locationType = 'none'; 39 | 40 | // keep test console output quieter 41 | ENV.APP.LOG_ACTIVE_GENERATION = false; 42 | ENV.APP.LOG_VIEW_LOOKUPS = false; 43 | 44 | ENV.APP.rootElement = '#ember-testing'; 45 | ENV.APP.autoboot = false; 46 | } 47 | 48 | if (environment === 'production') { 49 | // here you can enable a production-specific feature 50 | } 51 | 52 | return ENV; 53 | }; 54 | -------------------------------------------------------------------------------- /test-app/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true, 6 | "no-implicit-route-model": true 7 | } 8 | -------------------------------------------------------------------------------- /test-app/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | module.exports = { 10 | browsers, 11 | }; 12 | -------------------------------------------------------------------------------- /test-app/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function (defaults) { 6 | let app = new EmberApp(defaults, { 7 | autoImport: { 8 | watchDependencies: ['ember-cli-stripe'], 9 | }, 10 | }); 11 | 12 | const { maybeEmbroider } = require('@embroider/test-setup'); 13 | return maybeEmbroider(app); 14 | }; 15 | -------------------------------------------------------------------------------- /test-app/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Debugging: 3 | * https://eslint.org/docs/latest/use/configure/debug 4 | * ---------------------------------------------------- 5 | * 6 | * Print a file's calculated configuration 7 | * 8 | * npx eslint --print-config path/to/file.js 9 | * 10 | * Inspecting the config 11 | * 12 | * npx eslint --inspect-config 13 | * 14 | */ 15 | import globals from 'globals'; 16 | import js from '@eslint/js'; 17 | 18 | import ember from 'eslint-plugin-ember/recommended'; 19 | import prettier from 'eslint-plugin-prettier/recommended'; 20 | import qunit from 'eslint-plugin-qunit'; 21 | import n from 'eslint-plugin-n'; 22 | 23 | import babelParser from '@babel/eslint-parser'; 24 | 25 | const esmParserOptions = { 26 | ecmaFeatures: { modules: true }, 27 | ecmaVersion: 'latest', 28 | requireConfigFile: false, 29 | babelOptions: { 30 | plugins: [ 31 | ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }], 32 | ], 33 | }, 34 | }; 35 | 36 | export default [ 37 | js.configs.recommended, 38 | prettier, 39 | ember.configs.base, 40 | ember.configs.gjs, 41 | /** 42 | * Ignores must be in their own object 43 | * https://eslint.org/docs/latest/use/configure/ignore 44 | */ 45 | { 46 | ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'], 47 | }, 48 | /** 49 | * https://eslint.org/docs/latest/use/configure/configuration-files#configuring-linter-options 50 | */ 51 | { 52 | linterOptions: { 53 | reportUnusedDisableDirectives: 'error', 54 | }, 55 | }, 56 | { 57 | files: ['**/*.js'], 58 | languageOptions: { 59 | parser: babelParser, 60 | }, 61 | }, 62 | { 63 | files: ['**/*.{js,gjs}'], 64 | languageOptions: { 65 | parserOptions: esmParserOptions, 66 | globals: { 67 | ...globals.browser, 68 | }, 69 | }, 70 | }, 71 | { 72 | files: ['tests/**/*-test.{js,gjs}'], 73 | plugins: { 74 | qunit, 75 | }, 76 | }, 77 | /** 78 | * CJS node files 79 | */ 80 | { 81 | files: [ 82 | '**/*.cjs', 83 | 'config/**/*.js', 84 | 'testem.js', 85 | 'testem*.js', 86 | '.prettierrc.js', 87 | '.stylelintrc.js', 88 | '.template-lintrc.js', 89 | 'ember-cli-build.js', 90 | ], 91 | plugins: { 92 | n, 93 | }, 94 | 95 | languageOptions: { 96 | sourceType: 'script', 97 | ecmaVersion: 'latest', 98 | globals: { 99 | ...globals.node, 100 | }, 101 | }, 102 | }, 103 | /** 104 | * ESM node files 105 | */ 106 | { 107 | files: ['**/*.mjs'], 108 | plugins: { 109 | n, 110 | }, 111 | 112 | languageOptions: { 113 | sourceType: 'module', 114 | ecmaVersion: 'latest', 115 | parserOptions: esmParserOptions, 116 | globals: { 117 | ...globals.node, 118 | }, 119 | }, 120 | }, 121 | ]; 122 | -------------------------------------------------------------------------------- /test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-app", 3 | "version": "4.0.1", 4 | "private": true, 5 | "description": "Test app for ember-cli-stripe addon", 6 | "keywords": [ 7 | "stripe", 8 | "stripe.js", 9 | "stripejs", 10 | "smile", 11 | "smile.io" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/smile-io/ember-cli-stripe", 16 | "directory": "test-app" 17 | }, 18 | "license": "MIT", 19 | "author": "Smile.io ", 20 | "directories": { 21 | "doc": "doc", 22 | "test": "tests" 23 | }, 24 | "scripts": { 25 | "build": "ember build --environment=production", 26 | "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto", 27 | "lint:css": "stylelint \"**/*.css\"", 28 | "lint:css:fix": "concurrently \"pnpm:lint:css -- --fix\"", 29 | "lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto", 30 | "lint:hbs": "ember-template-lint .", 31 | "lint:hbs:fix": "ember-template-lint . --fix", 32 | "lint:js": "eslint . --cache", 33 | "lint:js:fix": "eslint . --fix", 34 | "start": "ember serve", 35 | "test": "concurrently \"pnpm:lint\" \"pnpm:test:*\" --names \"lint,test:\" --prefixColors auto", 36 | "test:ember": "ember test" 37 | }, 38 | "devDependencies": { 39 | "@babel/core": "7.26.7", 40 | "@babel/eslint-parser": "^7.26.5", 41 | "@babel/plugin-proposal-decorators": "^7.25.9", 42 | "@ember/optional-features": "^2.2.0", 43 | "@ember/string": "^4.0.0", 44 | "@ember/test-helpers": "^5.1.0", 45 | "@embroider/test-setup": "^4.0.0", 46 | "@eslint/js": "^9.19.0", 47 | "@glimmer/component": "^2.0.0", 48 | "@glimmer/tracking": "^1.1.2", 49 | "broccoli-asset-rev": "^3.0.0", 50 | "concurrently": "^9.1.2", 51 | "ember-auto-import": "^2.10.0", 52 | "ember-cli": "~6.1.0", 53 | "ember-cli-app-version": "^7.0.0", 54 | "ember-cli-babel": "^8.2.0", 55 | "ember-cli-clean-css": "^3.0.0", 56 | "ember-cli-dependency-checker": "^3.3.3", 57 | "ember-cli-htmlbars": "^6.3.0", 58 | "ember-cli-inject-live-reload": "^2.1.0", 59 | "ember-cli-sri": "^2.1.1", 60 | "ember-cli-terser": "^4.0.2", 61 | "ember-data": "~5.3.10", 62 | "ember-load-initializers": "^3.0.1", 63 | "ember-modifier": "^4.2.0", 64 | "ember-page-title": "^8.2.3", 65 | "ember-qunit": "^9.0.1", 66 | "ember-resolver": "^13.1.0", 67 | "ember-source": "~6.2.0", 68 | "ember-source-channel-url": "^3.0.0", 69 | "ember-template-imports": "^4.3.0", 70 | "ember-template-lint": "^6.1.0", 71 | "ember-try": "^3.0.0", 72 | "ember-cli-stripe": "workspace:*", 73 | "eslint": "^9.19.0", 74 | "eslint-config-prettier": "^9.1.0", 75 | "eslint-plugin-ember": "^12.5.0", 76 | "eslint-plugin-n": "^17.15.1", 77 | "eslint-plugin-prettier": "^5.2.3", 78 | "eslint-plugin-qunit": "^8.1.2", 79 | "globals": "^15.14.0", 80 | "loader.js": "^4.7.0", 81 | "prettier": "^3.4.2", 82 | "prettier-plugin-ember-template-tag": "^2.0.4", 83 | "qunit": "^2.24.1", 84 | "qunit-dom": "^3.4.0", 85 | "sinon": "^19.0.2", 86 | "stylelint": "^16.14.1", 87 | "stylelint-config-standard": "^37.0.0", 88 | "stylelint-prettier": "^5.0.3", 89 | "tracked-built-ins": "^4.0.0", 90 | "webpack": "^5.97.1" 91 | }, 92 | "engines": { 93 | "node": ">= 18" 94 | }, 95 | "ember": { 96 | "edition": "octane" 97 | }, 98 | "volta": { 99 | "extends": "../package.json" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /test-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test-app/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /test-app/tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | } from 'ember-qunit'; 6 | 7 | // This file exists to provide wrappers around ember-qunit's 8 | // test setup functions. This way, you can easily extend the setup that is 9 | // needed per test type. 10 | 11 | function setupApplicationTest(hooks, options) { 12 | upstreamSetupApplicationTest(hooks, options); 13 | 14 | // Additional setup for application tests can be done here. 15 | // 16 | // For example, if you need an authenticated session for each 17 | // application test, you could do: 18 | // 19 | // hooks.beforeEach(async function () { 20 | // await authenticateSession(); // ember-simple-auth 21 | // }); 22 | // 23 | // This is also a good place to call test setup functions coming 24 | // from other addons: 25 | // 26 | // setupIntl(hooks, 'en-us'); // ember-intl 27 | // setupMirage(hooks); // ember-cli-mirage 28 | } 29 | 30 | function setupRenderingTest(hooks, options) { 31 | upstreamSetupRenderingTest(hooks, options); 32 | 33 | // Additional setup for rendering tests can be done here. 34 | } 35 | 36 | function setupTest(hooks, options) { 37 | upstreamSetupTest(hooks, options); 38 | 39 | // Additional setup for unit tests can be done here. 40 | } 41 | 42 | export { setupApplicationTest, setupRenderingTest, setupTest }; 43 | -------------------------------------------------------------------------------- /test-app/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp Tests 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} 17 | {{content-for "test-head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | {{content-for "test-body"}} 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{content-for "body-footer"}} 37 | {{content-for "test-body-footer"}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /test-app/tests/integration/components/stripe-checkout-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render, click } from '@ember/test-helpers'; 4 | import { hbs } from 'ember-cli-htmlbars'; 5 | import Service from '@ember/service'; 6 | 7 | class StripeStub extends Service { 8 | openCalls = []; 9 | 10 | async open(config) { 11 | this.openCalls.push(config); 12 | } 13 | } 14 | 15 | module('Integration | Component | stripe-checkout', function (hooks) { 16 | setupRenderingTest(hooks); 17 | 18 | hooks.beforeEach(function () { 19 | this.owner.register('service:stripe', StripeStub); 20 | this.stripe = this.owner.lookup('service:stripe'); 21 | }); 22 | 23 | test('renders with default label', async function (assert) { 24 | await render(hbs``); 25 | assert.dom('button').hasText('Pay with card'); 26 | }); 27 | 28 | test('renders with custom label', async function (assert) { 29 | await render(hbs``); 30 | assert.dom('button').hasText('Custom Payment'); 31 | }); 32 | 33 | test('renders block content', async function (assert) { 34 | await render(hbs` 35 | 36 | card 37 | Pay Now 38 | 39 | `); 40 | assert.dom('button').hasText('Pay Now'); 41 | assert.dom('img').exists(); 42 | }); 43 | 44 | test('handles disabled state', async function (assert) { 45 | await render(hbs``); 46 | assert.dom('button').hasAttribute('disabled'); 47 | }); 48 | 49 | test('opens checkout on button click', async function (assert) { 50 | this.config = { 51 | key: 'test-key', 52 | amount: 2000, 53 | description: 'Test Purchase', 54 | }; 55 | 56 | await render(hbs` 57 | `); 62 | 63 | await click('button'); 64 | 65 | assert.strictEqual( 66 | this.stripe.openCalls.length, 67 | 1, 68 | 'stripe.open was called once', 69 | ); 70 | assert.deepEqual( 71 | this.stripe.openCalls[0], 72 | { 73 | ...this.config, 74 | onToken: undefined, 75 | onOpened: undefined, 76 | onClosed: undefined, 77 | }, 78 | 'called with correct config', 79 | ); 80 | }); 81 | 82 | test('auto-opens checkout when showCheckout is true', async function (assert) { 83 | await render(hbs``); 84 | 85 | assert.strictEqual(this.stripe.openCalls.length, 1, 'auto-opens checkout'); 86 | }); 87 | 88 | test('forwards callbacks to stripe service', async function (assert) { 89 | this.setProperties({ 90 | onToken: () => assert.step('token'), 91 | onOpened: () => assert.step('opened'), 92 | onClosed: () => assert.step('closed'), 93 | }); 94 | 95 | await render(hbs` 96 | `); 101 | 102 | await click('button'); 103 | 104 | const config = this.stripe.openCalls[0]; 105 | config.onToken(); 106 | config.onOpened(); 107 | config.onClosed(); 108 | 109 | assert.verifySteps( 110 | ['token', 'opened', 'closed'], 111 | 'callbacks are forwarded correctly', 112 | ); 113 | }); 114 | 115 | test('forwards all valid stripe configuration options', async function (assert) { 116 | this.config = { 117 | key: 'test-key', 118 | amount: 2000, 119 | currency: 'EUR', 120 | name: 'Test Store', 121 | description: 'Test Purchase', 122 | image: 'https://test.com/image.png', 123 | locale: 'auto', 124 | zipCode: true, 125 | billingAddress: true, 126 | shippingAddress: true, 127 | email: 'test@example.com', 128 | allowRememberMe: true, 129 | bitcoin: false, 130 | alipay: false, 131 | alipayReusable: false, 132 | }; 133 | 134 | await render(hbs` 135 | `); 152 | 153 | await click('button'); 154 | 155 | assert.deepEqual( 156 | this.stripe.openCalls[0], 157 | { 158 | ...this.config, 159 | onToken: undefined, 160 | onOpened: undefined, 161 | onClosed: undefined, 162 | }, 163 | 'forwards all valid config options', 164 | ); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /test-app/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'test-app/app'; 2 | import config from 'test-app/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { loadTests } from 'ember-qunit/test-loader'; 7 | import { start, setupEmberOnerrorValidation } from 'ember-qunit'; 8 | 9 | setApplication(Application.create(config.APP)); 10 | 11 | setup(QUnit.assert); 12 | setupEmberOnerrorValidation(); 13 | loadTests(); 14 | start(); 15 | -------------------------------------------------------------------------------- /test-app/tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smile-io/ember-cli-stripe/da89fb2cb36aa080502bea7dd4dd8a7e82bd52c5/test-app/tests/unit/.gitkeep -------------------------------------------------------------------------------- /test-app/tests/unit/services/stripe-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | import sinon from 'sinon'; 4 | 5 | module('Unit | Service | stripe', function (hooks) { 6 | setupTest(hooks); 7 | 8 | hooks.beforeEach(function () { 9 | this.owner.register( 10 | 'config:environment', 11 | { 12 | stripe: { 13 | key: 'env-key', 14 | currency: 'EUR', 15 | }, 16 | }, 17 | { instantiate: false }, 18 | ); 19 | 20 | window.StripeCheckout = { 21 | open: () => {}, 22 | }; 23 | 24 | this.service = this.owner.lookup('service:stripe'); 25 | }); 26 | 27 | hooks.afterEach(function () { 28 | delete window.StripeCheckout; 29 | sinon.restore(); 30 | }); 31 | 32 | module('config', function () { 33 | test('loads config from environment', function (assert) { 34 | assert.deepEqual( 35 | this.service.config, 36 | { key: 'env-key', currency: 'EUR' }, 37 | 'loads environment config', 38 | ); 39 | }); 40 | 41 | test('handles missing environment config', function (assert) { 42 | this.owner.register('config:environment', {}, { instantiate: false }); 43 | const service = this.owner.lookup('service:stripe'); 44 | 45 | assert.deepEqual(service.config, {}, 'defaults to empty config'); 46 | }); 47 | 48 | test('filters out invalid config options', function (assert) { 49 | this.owner.register( 50 | 'config:environment', 51 | { 52 | stripe: { 53 | key: 'env-key', 54 | invalidOption: 'value', 55 | currency: 'EUR', 56 | }, 57 | }, 58 | { instantiate: false }, 59 | ); 60 | 61 | const service = this.owner.lookup('service:stripe'); 62 | assert.false( 63 | 'invalidOption' in service.config, 64 | 'removes invalid options', 65 | ); 66 | assert.true('key' in service.config, 'keeps valid options'); 67 | }); 68 | }); 69 | 70 | module('loadStripeCheckout', function () { 71 | test('reuses existing script promise', async function (assert) { 72 | const promise1 = this.service.loadStripeCheckout(); 73 | const promise2 = this.service.loadStripeCheckout(); 74 | 75 | assert.strictEqual( 76 | await promise1, 77 | await promise2, 78 | 'returns same promise for multiple calls', 79 | ); 80 | }); 81 | 82 | test('handles script load errors', async function (assert) { 83 | // TODO 84 | const error = new Error('Failed to load'); 85 | sinon.stub(this.service, 'loadStripeCheckout').rejects(error); 86 | 87 | try { 88 | await this.service.loadStripeCheckout(); 89 | assert.false('should have thrown'); 90 | } catch (e) { 91 | assert.strictEqual(e, error, 'propagates load error'); 92 | } 93 | }); 94 | }); 95 | 96 | module('open', function () { 97 | test('open calls StripeCheckout.open with merged config', async function (assert) { 98 | const fakeOpen = sinon.fake(); 99 | sinon.replace(window.StripeCheckout, 'open', fakeOpen); 100 | 101 | const config = { 102 | key: 'runtime-key', 103 | amount: 2000, 104 | onToken: () => {}, 105 | onOpened: () => {}, 106 | onClosed: () => {}, 107 | }; 108 | 109 | await this.service.open(config); 110 | 111 | assert.ok(fakeOpen.calledOnce, 'open was called once'); 112 | assert.ok( 113 | sinon 114 | .match({ 115 | key: 'runtime-key', 116 | currency: 'EUR', 117 | amount: 2000, 118 | token: sinon.match.func, 119 | opened: sinon.match.func, 120 | closed: sinon.match.func, 121 | }) 122 | .test(fakeOpen.firstArg), 123 | 'called with merged config', 124 | ); 125 | }); 126 | 127 | test('open handles callback functions', async function (assert) { 128 | const fakeOpen = sinon.fake(); 129 | sinon.replace(window.StripeCheckout, 'open', fakeOpen); 130 | 131 | const onToken = () => assert.step('onToken'); 132 | const onOpened = () => assert.step('onOpened'); 133 | const onClosed = () => assert.step('onClosed'); 134 | 135 | await this.service.open({ 136 | key: 'test-key', 137 | onToken, 138 | onOpened, 139 | onClosed, 140 | }); 141 | 142 | const config = fakeOpen.firstArg; 143 | config.token(); 144 | config.opened(); 145 | config.closed(); 146 | 147 | assert.verifySteps( 148 | ['onToken', 'onOpened', 'onClosed'], 149 | 'callbacks are called', 150 | ); 151 | }); 152 | 153 | test('open removes undefined values from config', async function (assert) { 154 | const fakeOpen = sinon.fake(); 155 | sinon.replace(window.StripeCheckout, 'open', fakeOpen); 156 | 157 | await this.service.open({ 158 | key: 'test-key', 159 | description: null, 160 | }); 161 | 162 | const config = fakeOpen.firstArg; 163 | assert.false('amount' in config, 'undefined values are removed'); 164 | }); 165 | 166 | test('waits for script to load before opening', async function (assert) { 167 | const fakeOpen = sinon.fake(); 168 | sinon.replace(window.StripeCheckout, 'open', fakeOpen); 169 | 170 | let resolveScript; 171 | const scriptPromise = new Promise((resolve) => { 172 | resolveScript = resolve; 173 | }); 174 | sinon.stub(this.service, 'loadStripeCheckout').returns(scriptPromise); 175 | 176 | const openPromise = this.service.open({ key: 'test-key' }); 177 | assert.false(fakeOpen.called, 'does not open before script loads'); 178 | 179 | resolveScript(); 180 | await openPromise; 181 | 182 | assert.true(fakeOpen.called, 'opens after script loads'); 183 | }); 184 | 185 | test('handles missing callback functions', async function (assert) { 186 | const fakeOpen = sinon.fake(); 187 | sinon.replace(window.StripeCheckout, 'open', fakeOpen); 188 | 189 | await this.service.open({ key: 'test-key' }); 190 | 191 | const config = fakeOpen.firstArg; 192 | assert.strictEqual(config.token, undefined, 'token is optional'); 193 | assert.strictEqual( 194 | typeof config.opened, 195 | 'function', 196 | 'opened handler is always function', 197 | ); 198 | assert.strictEqual( 199 | typeof config.closed, 200 | 'function', 201 | 'closed handler is always function', 202 | ); 203 | 204 | // Verify handlers don't throw when callbacks are missing 205 | try { 206 | config.opened(); 207 | config.closed(); 208 | assert.ok(true, 'handlers are safe to call without callbacks'); 209 | } catch (error) { 210 | assert.ok(false, 'an error was thrown', error.message); 211 | } 212 | }); 213 | 214 | test('config precedence', async function (assert) { 215 | const fakeOpen = sinon.fake(); 216 | sinon.replace(window.StripeCheckout, 'open', fakeOpen); 217 | 218 | await this.service.open({ 219 | key: 'runtime-key', 220 | currency: 'USD', 221 | }); 222 | 223 | const config = fakeOpen.firstArg; 224 | assert.strictEqual( 225 | config.key, 226 | 'runtime-key', 227 | 'runtime config overrides env config', 228 | ); 229 | assert.strictEqual( 230 | config.currency, 231 | 'USD', 232 | 'runtime config overrides env config', 233 | ); 234 | }); 235 | 236 | test('throws an error if not `key` is specified', async function (assert) { 237 | this.owner.register('config:environment', {}, { instantiate: false }); 238 | const service = this.owner.lookup('service:stripe'); 239 | 240 | await assert.rejects( 241 | service.open(), 242 | /\[ember-cli-stripe\] Missing required `key` param!/, 243 | 'throws an error if no Stripe `key` is specified', 244 | ); 245 | }); 246 | }); 247 | }); 248 | --------------------------------------------------------------------------------