├── .editorconfig ├── .env.example ├── .eslintrc.js ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── pull-request.yml │ └── release.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .prettierignore ├── .releaserc.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── babel.bundle.config.js ├── babel.config.js ├── bump.js ├── commitlint.config.js ├── cypress.config.ts ├── cypress ├── .eslintrc ├── e2e │ ├── api-error.cy.ts │ ├── applicationTemplates.cy.ts │ ├── applications.cy.ts │ ├── credit-card.cy.ts │ ├── elements.cy.ts │ ├── logs.cy.ts │ ├── permissions.cy.ts │ ├── proxies.cy.ts │ ├── reactor-formulas.cy.ts │ ├── reactors.cy.ts │ ├── tenants.cy.ts │ └── tokens.cy.ts ├── fixtures │ ├── credit_card_api_error.html │ ├── crud_client.html │ ├── elements.html │ └── event-informer.js ├── support │ ├── commands.ts │ ├── e2e.ts │ └── index.d.ts └── tsconfig.json ├── examples └── credit_card.html ├── jest.config.js ├── makefile ├── package.json ├── prepare.js ├── scripts ├── acceptance.sh ├── build.sh ├── setupinfra.sh ├── verify.sh └── writelibvars.sh ├── src ├── BasisTheory.ts ├── application-templates │ ├── BasisTheoryApplicationTemplates.ts │ └── index.ts ├── applicationKeys │ ├── BasisTheoryApplicationKeys.ts │ └── index.ts ├── applications │ ├── BasisTheoryApplications.ts │ └── index.ts ├── common │ ├── BasisTheoryApiError.ts │ ├── BasisTheoryValidationError.ts │ ├── HttpClientError.ts │ ├── constants.ts │ ├── index.ts │ ├── logging.ts │ └── utils.ts ├── elements │ ├── constants.ts │ ├── index.ts │ ├── load.ts │ ├── script.ts │ └── services │ │ ├── index.ts │ │ ├── proxy.ts │ │ ├── token-intents.ts │ │ ├── tokenize.ts │ │ └── tokens.ts ├── index.ts ├── logs │ ├── BasisTheoryLogs.ts │ └── index.ts ├── permissions │ ├── BasisTheoryPermissions.ts │ └── index.ts ├── proxies │ ├── BasisTheoryProxies.ts │ └── index.ts ├── proxy │ ├── BasisTheoryProxy.ts │ └── index.ts ├── reactor-formulas │ ├── BasisTheoryReactorFormulas.ts │ └── index.ts ├── reactors │ ├── BasisTheoryReactors.ts │ └── index.ts ├── service │ ├── BasisTheoryService.ts │ ├── CrudBuilder.ts │ ├── index.ts │ └── types.ts ├── sessions │ ├── BasisTheorySessions.ts │ └── index.ts ├── tenants │ ├── BasisTheoryTenants.ts │ └── index.ts ├── threeds │ ├── BasisTheoryThreeDs.ts │ └── index.ts ├── token-intents │ ├── BasisTheoryTokenIntents.ts │ └── index.ts ├── tokenize │ ├── BasisTheoryTokenize.ts │ └── index.ts ├── tokens │ ├── BasisTheoryTokens.ts │ └── index.ts └── types │ ├── elements │ ├── cardTypes.ts │ ├── elements.ts │ ├── events.ts │ ├── index.ts │ ├── options.ts │ ├── services │ │ ├── index.ts │ │ ├── proxy.ts │ │ ├── token-intents.ts │ │ ├── tokenize.ts │ │ └── tokens.ts │ ├── shared.ts │ └── styles.ts │ ├── models │ ├── application-templates.ts │ ├── applications.ts │ ├── banks.ts │ ├── bin-details.ts │ ├── card-details.ts │ ├── cards.ts │ ├── index.ts │ ├── logs.ts │ ├── permissions.ts │ ├── proxies.ts │ ├── reactor-formulas.ts │ ├── reactors.ts │ ├── shared.ts │ ├── tenants.ts │ ├── threeds.ts │ ├── token-intents.ts │ ├── tokenize.ts │ ├── tokens.ts │ └── util.ts │ └── sdk │ ├── index.ts │ ├── sdk.ts │ └── services │ ├── application-templates.ts │ ├── applicationKeys.ts │ ├── applications.ts │ ├── http.ts │ ├── index.ts │ ├── logs.ts │ ├── permissions.ts │ ├── proxies.ts │ ├── proxy.ts │ ├── reactor-formulas.ts │ ├── reactors.ts │ ├── sessions.ts │ ├── shared.ts │ ├── tenants.ts │ ├── threeds.ts │ ├── token-intents.ts │ ├── tokenize.ts │ └── tokens.ts ├── test ├── application-templates.test.ts ├── applicationKeys.test.ts ├── applications.test.ts ├── clients.test.ts ├── elements.test.ts ├── elements │ ├── create.test.ts │ ├── httpClient.test.ts │ ├── proxy.test.ts │ ├── tokenIntents.test.ts │ ├── tokenize.test.ts │ └── tokens.test.ts ├── init.test.ts ├── logs.test.ts ├── permissions.test.ts ├── proxies.test.ts ├── proxy.test.ts ├── reactor-formulas.test.ts ├── reactors.test.ts ├── script.test.ts ├── sessions.test.ts ├── setup │ └── utils.ts ├── tenants.test.ts ├── threeds.test.ts ├── token-intents.test.ts ├── tokenize.test.ts ├── tokens.test.ts ├── tsconfig.json └── utils.test.ts ├── tsconfig.json ├── webpack.common.js ├── webpack.dev.js ├── webpack.prod.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | # editorconfig-tools is unable to ignore longs strings or urls 11 | max_line_length = off 12 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | JS_HOST=js-dev.basistheory.com 2 | API_HOST=localhost:3333 3 | ELEMENTS_HOST=localhost:3000 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@basis-theory/eslint-config/typescript', 4 | rules: { 5 | 'unicorn/no-null': 'warn', 6 | camelcase: [ 7 | 'error', 8 | { 9 | allow: [ 10 | 'expiration_month', 11 | 'expiration_year', 12 | 'snake_case', 13 | 'total_items', 14 | 'page_number', 15 | 'page_size', 16 | 'total_pages', 17 | 'created_at', 18 | 'modified_at', 19 | 'created_by', 20 | 'modified_by', 21 | 'first_nested', 22 | 'second_nested', 23 | 'tenant_id', 24 | 'session_key', 25 | 'expires_at', 26 | 'owner_id', 27 | 'token_report', 28 | 'metrics_by_type', 29 | 'monthly_active_tokens', 30 | 'included_monthly_active_tokens', 31 | 'application_type', 32 | 'template_type', 33 | ], 34 | }, 35 | ], 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in 5 | # the repo. 6 | * @basis-theory/engineers 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | For additional support or to discuss issues/features, please reach out to us on our [Community](https://community.basistheory.com) or via email at [support@basistheory.com](mailto:support@basistheory.com) 2 | 3 | ## Expected Behavior 4 | - 5 | 6 | ## Actual Behavior 7 | - 8 | 9 | ## Steps to Reproduce the Problem 10 | 1. 11 | 1. 12 | 1. 13 | 14 | ## Specifications 15 | - SDK Version: 16 | - NodeJS version: 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ## Description 3 | 4 | - 5 | 6 | 7 | ## Testing required outside of automated testing? 8 | 9 | - [ ] Not Applicable 10 | 11 | 12 | ### Screenshots (if appropriate): 13 | 14 | - [ ] Not Applicable 15 | 16 | 17 | ## Rollback / Rollforward Procedure 18 | 19 | - [ ] Roll Forward 20 | - [ ] Roll Back 21 | 22 | ## Reviewer Checklist 23 | 24 | - [ ] Description of Change 25 | - [ ] Description of outside testing if applicable. 26 | - [ ] Description of Roll Forward / Backward Procedure 27 | - [ ] Documentation updated for Change 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | build: 13 | environment: DEV 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | node: ["14.x"] 18 | os: [ubuntu-latest] 19 | env: 20 | CI: 1 # prevents extra Cypress installation progress messages 21 | HUSKY: 0 # disables husky hooks 22 | steps: 23 | - name: Checkout repo 24 | uses: actions/checkout@v2 25 | 26 | - name: Use Node ${{ matrix.node }} 27 | uses: actions/setup-node@v1 28 | with: 29 | node-version: ${{ matrix.node }} 30 | 31 | - name: Install deps (with cache) 32 | uses: bahmutov/npm-install@v1 33 | env: 34 | CYPRESS_INSTALL_BINARY: 0 35 | 36 | - id: read-package-json 37 | name: Read package.json 38 | run: | 39 | content=`cat package.json` 40 | content="${content//'%'/'%25'}" 41 | content="${content//$'\n'/'%0A'}" 42 | content="${content//$'\r'/'%0D'}" 43 | echo "::set-output name=packageJson::$content" 44 | 45 | - name: Cache Cypress Binaries 46 | id: cache-cypress 47 | uses: actions/cache@v4 48 | with: 49 | path: ~/.cache/Cypress 50 | key: cypress-cache-v4-${{ runner.os }}-${{ fromJson(steps.read-package-json.outputs.packageJson).devDependencies.cypress }} 51 | 52 | # Install and check Cypress binary 53 | - run: yarn run cypress install 54 | - run: yarn run cypress cache list 55 | 56 | - name: Write .env 57 | run: make write-lib-vars 58 | env: 59 | ENVIRONMENT: dev 60 | DD_GIT_SHA: ${{ github.sha }} 61 | IS_PR_WORKFLOW: true 62 | 63 | - name: Verify 64 | run: make verify 65 | env: 66 | SKIP_INSTALL: 1 # install with cache was done already 67 | 68 | - name: Upload failed e2e tests screenshots 69 | uses: actions/upload-artifact@v4 70 | if: failure() 71 | with: 72 | name: cypress-screenshots 73 | path: cypress/screenshots 74 | 75 | - name: Verify Infrastructure 76 | run: make setup-infra 77 | env: 78 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 79 | AWS_REGION: us-east-2 80 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_ID }} 81 | ENVIRONMENT: dev 82 | IS_PR_WORKFLOW: true 83 | 84 | - name: Purge cache 85 | uses: jakejarvis/cloudflare-purge-action@master 86 | env: 87 | CLOUDFLARE_ZONE: ${{ vars.CF_DEV_ZONE_ID }} 88 | CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_PURGE_TOKEN }} 89 | PURGE_URLS: '["https://js.flock-dev.com/*"]' 90 | 91 | - name: Monitor coverage 92 | uses: slavcodev/coverage-monitor-action@1.2.0 93 | with: 94 | github_token: ${{ secrets.GITHUB_TOKEN }} 95 | clover_file: "coverage/clover.xml" 96 | threshold_alert: 80 97 | threshold_warning: 90 98 | 99 | pr-security-check: 100 | name: PR Security Check 101 | uses: Basis-Theory/public-security-workflows/.github/workflows/pr-check.yml@master 102 | secrets: inherit 103 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | build-release: 9 | environment: PROD 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | node: ["14.x"] 14 | os: [ubuntu-latest] 15 | env: 16 | CI: 1 # prevents extra Cypress installation progress messages 17 | HUSKY: 0 # disables husky hooks 18 | steps: 19 | - name: Checkout repo 20 | uses: actions/checkout@v2 21 | with: 22 | token: ${{ secrets.GH_SEMANTIC_RELEASE_PAT }} 23 | 24 | - name: Start Deploy Message 25 | uses: Basis-Theory/github-actions/deploy-slack-action@master 26 | with: 27 | slack-api-token: ${{ secrets.SLACK_DUCKBOT_API_KEY }} 28 | channel: ${{ vars.SLACK_DUCKBOT_RELEASE_CHANNEL_ID }} 29 | 30 | - name: Use Node ${{ matrix.node }} 31 | uses: actions/setup-node@v1 32 | with: 33 | node-version: ${{ matrix.node }} 34 | 35 | - name: Install deps (with cache) 36 | uses: bahmutov/npm-install@v1 37 | env: 38 | CYPRESS_INSTALL_BINARY: 0 39 | 40 | - name: Write .env 41 | run: make write-lib-vars 42 | env: 43 | ENVIRONMENT: prod 44 | DD_GIT_SHA: ${{ github.sha }} 45 | 46 | - name: Build 47 | run: make build 48 | env: 49 | SKIP_INSTALL: 1 # install with cache was done already 50 | 51 | - name: Release 52 | run: make release 53 | env: 54 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 55 | AWS_REGION: us-east-2 56 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_ID }} 57 | ENVIRONMENT: prod 58 | GITHUB_TOKEN: ${{ secrets.GH_SEMANTIC_RELEASE_PAT }} 59 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 60 | 61 | - name: Purge cache 62 | uses: jakejarvis/cloudflare-purge-action@master 63 | env: 64 | CLOUDFLARE_ZONE: ${{ vars.CF_PROD_ZONE_ID }} 65 | CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_PURGE_TOKEN }} 66 | PURGE_URLS: '["https://js.basistheory.com/*"]' 67 | 68 | - name: Stop Deploy Message 69 | if: always() 70 | uses: Basis-Theory/github-actions/deploy-slack-action@master 71 | with: 72 | slack-api-token: ${{ secrets.SLACK_DUCKBOT_API_KEY }} 73 | channel: ${{ vars.SLACK_DUCKBOT_RELEASE_CHANNEL_ID }} 74 | status: "done" 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | coverage 6 | cypress/screenshots 7 | cypress/downloads 8 | .nvmrc 9 | .npmrc 10 | .idea 11 | .env 12 | .vscode/ 13 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn pretty-quick -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | .github 5 | CHANGELOG.md -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["master"], 3 | "plugins": [ 4 | [ 5 | "@semantic-release/commit-analyzer", 6 | { 7 | "releaseRules": [{ "type": "refactor", "release": "patch" }] 8 | } 9 | ], 10 | "@semantic-release/release-notes-generator", 11 | "@semantic-release/changelog", 12 | [ 13 | "@semantic-release/npm", 14 | { 15 | "pkgRoot": "dist" 16 | } 17 | ], 18 | "@semantic-release/github", 19 | [ 20 | "@semantic-release/git", 21 | { 22 | "assets": ["package.json", "CHANGELOG.md"], 23 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 24 | } 25 | ], 26 | [ 27 | "@semantic-release/exec", 28 | { 29 | "verifyReleaseCmd": "echo VERSION=${nextRelease.version} >> .env" 30 | } 31 | ] 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [support@basistheory.com](mailto:support@basistheory.com?subject=Conduct). 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][mozilla coc]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][faq]. Translations are available 126 | at [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [mozilla coc]: https://github.com/mozilla/diversity 131 | [faq]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Basis Theory JS SDK (aka BasisTheory.js) 2 | 3 | > [!CAUTION] 4 | > This SDK has been deprecated for use in Node.js environments. It is still supported for use in web elements and reactor environments. 5 | > 6 | > Our new Node.js SDK can be found at https://github.com/Basis-Theory/node-sdk 7 | > 8 | > See our documentation site for more information. https://developers.basistheory.com/docs/sdks/server-side/node 9 | 10 | [![Version](https://img.shields.io/npm/v/@basis-theory/basis-theory-js.svg)](https://www.npmjs.org/package/@basis-theory/basis-theory-js) 11 | [![Downloads](https://img.shields.io/npm/dm/@basis-theory/basis-theory-js.svg)](https://www.npmjs.org/package/@basis-theory/basis-theory-js) 12 | [![Verify](https://github.com/Basis-Theory/basis-theory-js/actions/workflows/release.yml/badge.svg)](https://github.com/Basis-Theory/basis-theory-js/actions/workflows/release.yml) 13 | 14 | The [Basis Theory](https://basistheory.com/) JS SDK 15 | 16 | ## Installation 17 | 18 | Using [Node Package Manager](https://docs.npmjs.com/) 19 | 20 | ```sh 21 | npm install --save @basis-theory/basis-theory-js 22 | ``` 23 | 24 | Using [Yarn](https://classic.yarnpkg.com/en/docs/) 25 | 26 | ```sh 27 | yarn add @basis-theory/basis-theory-js 28 | ``` 29 | 30 | ## Documentation 31 | 32 | For a complete list of endpoints and examples, please refer to our [API docs](https://docs.basistheory.com/api-reference/?javascript#introduction) 33 | 34 | ## Usage 35 | 36 | ### Initialization 37 | 38 | ```javascript 39 | import { BasisTheory } from '@basis-theory/basis-theory-js'; 40 | 41 | const bt = await new BasisTheory().init(''); // replace with your application key 42 | ``` 43 | 44 | ### Per-request configuration 45 | 46 | All of the service methods accept an optional `RequestOptions` object. This is used if you want to set a per-request `BT-TRACE-ID`, `BT-API-KEY` and/or `BT-IDEMPOTENCY-KEY`. 47 | 48 | ```javascript 49 | import { v4 as uuid } from 'uuid'; 50 | 51 | await bt.applications.list( 52 | {}, 53 | { 54 | apiKey: '', 55 | correlationId: 'aa5d3379-6385-4ef4-9fdb-ca1341572153', 56 | idempotencyKey: 'bb5d3379-6385-4ef4-9fdb-ca1341572154', 57 | } 58 | ); 59 | 60 | await bt.tokens.create( 61 | { 62 | type: "token", 63 | data: "Sensitive Value", 64 | }, 65 | { 66 | apiKey: '', 67 | correlationId: 'aa5d3379-6385-4ef4-9fdb-ca1341572153', 68 | idempotencyKey: 'bb5d3379-6385-4ef4-9fdb-ca1341572154', 69 | } 70 | ); 71 | ``` 72 | 73 | ### Setting a custom API Url 74 | 75 | You can set a custom API Url to be used across all clients when creating a new SDK instance. 76 | 77 | ```javascript 78 | import { BasisTheory } from '@basis-theory/basis-theory-js'; 79 | 80 | const bt = await new BasisTheory().init('', { 81 | apiBaseUrl: 'https://api.somedomain.com', 82 | }); // replace with your application key and api base URL. 83 | ``` 84 | 85 | ### Elements 86 | 87 | Please, refer to the [Elements Documentation](https://docs.basistheory.com/elements) on how to use it. 88 | 89 | ## Development 90 | 91 | The provided scripts with the SDK will check for all dependencies, start docker, build the solution, and run all tests. 92 | 93 | ### Dependencies 94 | 95 | - [Docker](https://www.docker.com/products/docker-desktop) 96 | - [Docker Compose](https://www.docker.com/products/docker-desktop) 97 | - [NodeJS](https://nodejs.org/en/) > 10.12.0 98 | - [Yarn](https://classic.yarnpkg.com/en/docs/) 99 | 100 | ### Build the SDK and run Tests 101 | 102 | Run the following command from the root of the project: 103 | 104 | ```sh 105 | make verify 106 | ``` 107 | -------------------------------------------------------------------------------- /babel.bundle.config.js: -------------------------------------------------------------------------------- 1 | // this Babel configuration is used in webpack library bundle 2 | module.exports = { 3 | presets: [ 4 | [ 5 | '@babel/preset-env', 6 | { 7 | targets: '>0.25%', 8 | }, 9 | ], 10 | '@babel/typescript', 11 | ], 12 | plugins: [ 13 | '@babel/proposal-class-properties', 14 | '@babel/plugin-transform-runtime', 15 | [ 16 | 'babel-plugin-transform-builtin-extend', 17 | { 18 | globals: ['Error'], 19 | }, 20 | ], 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // this file is used to transpile Typescript files into Javascript files (dist/*) 2 | // for ES module usage 3 | module.exports = { 4 | presets: [ 5 | [ 6 | '@babel/preset-env', 7 | { 8 | targets: { 9 | node: '10', 10 | }, 11 | }, 12 | ], 13 | '@babel/typescript', 14 | ], 15 | plugins: [ 16 | 'tsconfig-paths-module-resolver', 17 | '@babel/proposal-class-properties', 18 | ['inline-dotenv', { unsafe: true }], 19 | [ 20 | 'babel-plugin-transform-builtin-extend', 21 | { 22 | globals: ['Error'], 23 | }, 24 | ], 25 | ], 26 | }; 27 | -------------------------------------------------------------------------------- /bump.js: -------------------------------------------------------------------------------- 1 | // copies the version from dist/package.json to package.json, after @semantic-release/npm bumps it 2 | const fs = require('fs'); 3 | const distPackage = require('./dist/package.json'); 4 | const libPackage = require('./package.json'); 5 | 6 | libPackage.version = distPackage.version; 7 | 8 | fs.writeFileSync( 9 | './package.json', 10 | `${JSON.stringify(libPackage, undefined, 2)}\n` 11 | ); 12 | 13 | // removes dist package scripts before publish 14 | delete distPackage.scripts; 15 | fs.writeFileSync( 16 | './dist/package.json', 17 | JSON.stringify(distPackage, undefined, 2) 18 | ); 19 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'body-max-line-length': [0], 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress'; 2 | 3 | export default defineConfig({ 4 | screenshotsFolder: 'cypress/screenshots', 5 | video: false, 6 | fixturesFolder: false, 7 | e2e: {}, 8 | }); 9 | -------------------------------------------------------------------------------- /cypress/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "@typescript-eslint/triple-slash-reference": "off" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /cypress/e2e/api-error.cy.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | context('API error', () => { 5 | beforeEach(() => { 6 | cy.visit('./cypress/fixtures/credit_card_api_error.html'); 7 | }); 8 | 9 | context('network error/network offline', () => { 10 | // eslint-disable-next-line jest/no-duplicate-hooks 11 | beforeEach(() => { 12 | cy.intercept('POST', '/tokens', { 13 | forceNetworkError: true, 14 | }); 15 | }); 16 | 17 | it('should return error with status of -1 and data of undefined', () => { 18 | cy.get('form').find('#card_number').type('4242424242424242'); 19 | cy.get('form').find('#expiration_month').type('10'); 20 | cy.get('form').find('#expiration_year').type('2029'); 21 | cy.get('form').find('#cvc').type('123'); 22 | cy.get('form').submit(); 23 | cy.get('#response').should( 24 | 'have.text', 25 | JSON.stringify({ 26 | status: -1, 27 | name: 'BasisTheoryApiError', 28 | }) 29 | ); 30 | }); 31 | }); 32 | 33 | context('error response', () => { 34 | const status = 400, 35 | message = 'some error response'; 36 | 37 | // eslint-disable-next-line jest/no-duplicate-hooks 38 | beforeEach(() => { 39 | cy.intercept('POST', '/tokens', { 40 | statusCode: status, 41 | body: { message }, 42 | }); 43 | }); 44 | 45 | it('should return error with the API status and data', () => { 46 | cy.get('form').find('#card_number').type('4242424242424242'); 47 | cy.get('form').find('#expiration_month').type('10'); 48 | cy.get('form').find('#expiration_year').type('2029'); 49 | cy.get('form').find('#cvc').type('123'); 50 | cy.get('form').submit(); 51 | cy.get('#response').should( 52 | 'have.text', 53 | JSON.stringify({ 54 | status, 55 | data: { 56 | message, 57 | }, 58 | name: 'BasisTheoryApiError', 59 | }) 60 | ); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /cypress/e2e/applicationTemplates.cy.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | context('Application Templates', () => { 5 | it('should retrieve', () => { 6 | cy.testRetrieve('application-templates'); 7 | }); 8 | it('should list', () => { 9 | cy.testList('application-templates'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /cypress/e2e/applications.cy.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | context('Applications', () => { 5 | it('should create', () => { 6 | cy.testCreate('applications'); 7 | }); 8 | it('should retrieve', () => { 9 | cy.testRetrieve('applications'); 10 | }); 11 | it('should update', () => { 12 | cy.testUpdate('applications'); 13 | }); 14 | it('should delete', () => { 15 | cy.testDelete('applications'); 16 | }); 17 | it('should list', () => { 18 | cy.testList('applications'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /cypress/e2e/credit-card.cy.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from 'uuid'; 2 | 3 | context('Credit Card example', () => { 4 | const port = Cypress.config('port'); 5 | 6 | beforeEach(() => { 7 | cy.intercept('https://js.basistheory.com/', (req) => { 8 | req.redirect(`http://localhost:${port}/dist/basis-theory-js.bundle.js`); 9 | }); 10 | 11 | cy.visit('examples/credit_card.html'); 12 | 13 | cy.intercept( 14 | { 15 | pathname: '/tokens', 16 | }, 17 | (req) => { 18 | req.reply({ 19 | statusCode: 201, 20 | body: { 21 | id: uuid(), 22 | data: { 23 | number: `${'X'.repeat( 24 | req.body.data.number.length - 4 25 | )}${req.body.data.number.slice(-4)}`, 26 | expiration_month: req.body.data.expiration_month, 27 | expiration_year: req.body.data.expiration_year, 28 | }, 29 | }, 30 | }); 31 | } 32 | ).as('createToken'); 33 | }); 34 | 35 | it('should load BasisTheory', () => { 36 | cy.window().should('have.property', 'BasisTheory'); 37 | }); 38 | 39 | it('should have all credit card form fields', () => { 40 | cy.get('form').should('have.length', 1); 41 | cy.get('form').find('#expiration_month').should('exist'); 42 | cy.get('form').find('#expiration_year').should('exist'); 43 | cy.get('form').find('#cvc').should('exist'); 44 | }); 45 | 46 | it('should be able to submit form with minimum information and get masked information back', () => { 47 | const year = (new Date().getFullYear() + 1).toString(); 48 | 49 | cy.get('form').find('#card_number').type('4242424242424242'); 50 | cy.get('form').find('#expiration_month').type('10'); 51 | cy.get('form').find('#expiration_year').type(year); 52 | cy.get('form').find('#cvc').type('123'); 53 | cy.get('form').submit(); 54 | 55 | cy.wait('@createToken').then((i) => { 56 | const alert = cy.stub(); 57 | 58 | cy.on('window:alert', alert); 59 | 60 | cy.get('#cards_wrapper').children().should('have.length', 1); 61 | cy.get('#cards_wrapper') 62 | .find('.card-info') 63 | .first() 64 | .should('have.text', `${'X'.repeat(12)}4242`) 65 | .next() 66 | .should('contain.text', `10/${year}`) 67 | .find('a') 68 | .click() 69 | .then(() => { 70 | expect(alert.getCall(0)).to.be.calledWith(i.response.body.id); 71 | }); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /cypress/e2e/elements.cy.ts: -------------------------------------------------------------------------------- 1 | context('Elements example', () => { 2 | it('should load BasisTheoryElements dynamically', () => { 3 | cy.intercept(/https:\/\/.+?\/elements/u, { 4 | body: ` 5 | window.BasisTheoryElements = { 6 | init: (apiKey, baseUrl) => { 7 | alert("BasisTheoryElements " + apiKey + " " + baseUrl); 8 | }, 9 | }; 10 | if (window.BasisTheory) { 11 | window.BasisTheory.elements = window.BasisTheoryElements; 12 | }`, 13 | }); 14 | cy.visit('cypress/fixtures/elements.html'); 15 | cy.on('window:alert', (val) => { 16 | expect(val).to.equal( 17 | `BasisTheoryElements 04ab9d12-4959-4c48-ba03-9ef722efcc5a https://js.basistheory.com` 18 | ); 19 | }); 20 | }); 21 | it('should handle blocked BasisTheoryElements script request', () => { 22 | cy.intercept(`http://cypress.test/events/initError`, (req) => { 23 | req.reply({ body: req.body }); 24 | }).as(`initErrorEvent`); 25 | cy.intercept(/https:\/\/.+?\/web-elements/u, { 26 | statusCode: 400, 27 | }); 28 | cy.visit('cypress/fixtures/elements.html'); 29 | cy.wait('@initErrorEvent') 30 | .its('request.body') 31 | .should( 32 | 'equal', 33 | 'Failed to deliver Elements script from Basis Theory. Check your network connection and try again or contact support@basistheory.com' 34 | ); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /cypress/e2e/logs.cy.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | context('Logs', () => { 5 | it('should list', () => { 6 | cy.testList('logs'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /cypress/e2e/permissions.cy.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | context('Permissions', () => { 5 | it('should list', () => { 6 | cy.testList('permissions'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /cypress/e2e/proxies.cy.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | context('Proxies', () => { 5 | it('should create', () => { 6 | cy.testCreate('proxies'); 7 | }); 8 | it('should retrieve', () => { 9 | cy.testRetrieve('proxies'); 10 | }); 11 | it('should update', () => { 12 | cy.testUpdate('proxies'); 13 | }); 14 | it('should patch', () => { 15 | cy.testPatch('proxies'); 16 | }); 17 | it('should delete', () => { 18 | cy.testDelete('proxies'); 19 | }); 20 | it('should list', () => { 21 | cy.testList('proxies'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /cypress/e2e/reactor-formulas.cy.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | context('Reactor Formulas', () => { 5 | it('should create', () => { 6 | cy.testCreate('reactor-formulas'); 7 | }); 8 | it('should retrieve', () => { 9 | cy.testRetrieve('reactor-formulas'); 10 | }); 11 | it('should update', () => { 12 | cy.testUpdate('reactor-formulas'); 13 | }); 14 | it('should delete', () => { 15 | cy.testDelete('reactor-formulas'); 16 | }); 17 | it('should list', () => { 18 | cy.testList('reactor-formulas'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /cypress/e2e/reactors.cy.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | context('Reactors', () => { 5 | it('should create', () => { 6 | cy.testCreate('reactors'); 7 | }); 8 | it('should retrieve', () => { 9 | cy.testRetrieve('reactors'); 10 | }); 11 | it('should update', () => { 12 | cy.testUpdate('reactors'); 13 | }); 14 | it('should patch', () => { 15 | cy.testPatch('reactors'); 16 | }); 17 | it('should delete', () => { 18 | cy.testDelete('reactors'); 19 | }); 20 | it('should list', () => { 21 | cy.testList('reactors'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /cypress/e2e/tenants.cy.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | context('Tenants', () => { 5 | it('should retrieve', () => { 6 | cy.testRetrieveNoId('tenants/self'); 7 | }); 8 | it('should update', () => { 9 | cy.testUpdateNoId('tenants/self'); 10 | }); 11 | it('should delete', () => { 12 | cy.testDeleteNoId('tenants/self'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /cypress/e2e/tokens.cy.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | context('Tokens', () => { 5 | it('should create', () => { 6 | cy.testCreate('tokens'); 7 | }); 8 | it('should retrieve', () => { 9 | cy.testRetrieve('tokens'); 10 | }); 11 | it('should update', () => { 12 | cy.testUpdate('tokens', 'PATCH'); 13 | }); 14 | it('should delete', () => { 15 | cy.testDelete('tokens'); 16 | }); 17 | it('should list', () => { 18 | cy.testList('tokens'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /cypress/fixtures/credit_card_api_error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Credit Card example 4 | 5 | 6 | 7 | 8 | 44 | 45 |
46 |
47 |

Add New Credit Card

48 |
49 | 50 | 58 |
59 |
60 | 61 | / 70 | 79 | 80 | 88 |
89 |
90 | 91 |
92 |
93 | 94 | 95 | -------------------------------------------------------------------------------- /cypress/fixtures/crud_client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 79 |
80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
101 | 102 | 103 | -------------------------------------------------------------------------------- /cypress/fixtures/elements.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Elements example 5 | 6 | 7 | 8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /cypress/fixtures/event-informer.js: -------------------------------------------------------------------------------- 1 | window.informEventCypress = (event, type = event.type) => { 2 | if (window.Cypress) { 3 | return fetch(`http://cypress.test/events/${type}`, { 4 | body: JSON.stringify(event), 5 | method: 'POST', 6 | headers: { 7 | 'Content-Type': 'application/json', 8 | }, 9 | }); 10 | } 11 | 12 | return Promise.resolve(); 13 | }; 14 | -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | Cypress.Commands.add('testCreate', (serviceName: string) => { 2 | cy.intercept('POST', `/${serviceName}`, {}).as('create'); 3 | 4 | cy.visit('./cypress/fixtures/crud_client.html'); 5 | 6 | cy.get('#service').type(serviceName); 7 | cy.get('#create').click(); 8 | 9 | cy.get('#submit').click(); 10 | 11 | cy.wait('@create'); 12 | }); 13 | 14 | Cypress.Commands.add('testRetrieve', (serviceName: string) => { 15 | cy.intercept('GET', new RegExp(`/${serviceName}/.+`, 'u'), {}).as('retrieve'); 16 | 17 | cy.visit('./cypress/fixtures/crud_client.html'); 18 | 19 | cy.get('#service').type(serviceName); 20 | cy.get('#retrieve').click(); 21 | 22 | cy.get('#submit').click(); 23 | 24 | cy.wait('@retrieve'); 25 | }); 26 | 27 | Cypress.Commands.add( 28 | 'testUpdate', 29 | (serviceName: string, method: 'PUT' | 'PATCH' = 'PUT') => { 30 | cy.intercept(method, new RegExp(`/${serviceName}/.+`, 'u'), {}).as( 31 | 'update' 32 | ); 33 | 34 | cy.visit('./cypress/fixtures/crud_client.html'); 35 | 36 | cy.get('#service').type(serviceName); 37 | cy.get('#update').click(); 38 | 39 | cy.get('#submit').click(); 40 | 41 | cy.wait('@update'); 42 | } 43 | ); 44 | 45 | Cypress.Commands.add('testPatch', (serviceName: string) => { 46 | cy.intercept('PATCH', new RegExp(`/${serviceName}/.+`, 'u'), {}).as('patch'); 47 | 48 | cy.visit('./cypress/fixtures/crud_client.html'); 49 | 50 | cy.get('#service').type(serviceName); 51 | cy.get('#patch').click(); 52 | 53 | cy.get('#submit').click(); 54 | 55 | cy.wait('@patch'); 56 | }); 57 | 58 | Cypress.Commands.add('testDelete', (serviceName: string) => { 59 | cy.intercept('DELETE', new RegExp(`/${serviceName}/.+`, 'u'), {}).as( 60 | 'delete' 61 | ); 62 | 63 | cy.visit('./cypress/fixtures/crud_client.html'); 64 | 65 | cy.get('#service').type(serviceName); 66 | cy.get('#delete').click(); 67 | 68 | cy.get('#submit').click(); 69 | 70 | cy.wait('@delete'); 71 | }); 72 | 73 | Cypress.Commands.add('testRetrieveNoId', (serviceName: string) => { 74 | cy.intercept('GET', `/${serviceName}`, {}).as('retrieve'); 75 | 76 | cy.visit('./cypress/fixtures/crud_client.html'); 77 | 78 | cy.get('#service').type(serviceName); 79 | cy.get('#retrieve-no-id').click(); 80 | 81 | cy.get('#submit').click(); 82 | 83 | cy.wait('@retrieve'); 84 | }); 85 | 86 | Cypress.Commands.add('testUpdateNoId', (serviceName: string) => { 87 | cy.intercept('PUT', `/${serviceName}`, {}).as('update'); 88 | 89 | cy.visit('./cypress/fixtures/crud_client.html'); 90 | 91 | cy.get('#service').type(serviceName); 92 | cy.get('#update-no-id').click(); 93 | 94 | cy.get('#submit').click(); 95 | 96 | cy.wait('@update'); 97 | }); 98 | 99 | Cypress.Commands.add('testDeleteNoId', (serviceName: string) => { 100 | cy.intercept('DELETE', `/${serviceName}`, {}).as('delete'); 101 | 102 | cy.visit('./cypress/fixtures/crud_client.html'); 103 | 104 | cy.get('#service').type(serviceName); 105 | cy.get('#delete-no-id').click(); 106 | 107 | cy.get('#submit').click(); 108 | 109 | cy.wait('@delete'); 110 | }); 111 | 112 | Cypress.Commands.add('testList', (serviceName: string) => { 113 | cy.intercept('GET', `/${serviceName}/`, {}).as('list'); 114 | 115 | cy.visit('./cypress/fixtures/crud_client.html'); 116 | 117 | cy.get('#service').type(serviceName); 118 | cy.get('#list').click(); 119 | 120 | cy.get('#submit').click(); 121 | 122 | cy.wait('@list'); 123 | }); 124 | -------------------------------------------------------------------------------- /cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | import './commands'; 2 | -------------------------------------------------------------------------------- /cypress/support/index.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line spaced-comment 2 | /// 3 | 4 | declare namespace Cypress { 5 | interface Chainable { 6 | testCreate(serviceName: string); 7 | testRetrieve(serviceName: string); 8 | testUpdate(serviceName: string, method?: 'PUT' | 'PATCH'); 9 | testPatch(serviceName: string); 10 | testDelete(serviceName: string); 11 | testRetrieveNoId(serviceName: string); 12 | testUpdateNoId(serviceName: string); 13 | testDeleteNoId(serviceName: string); 14 | testList(serviceName: string); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["es6", "dom"], 5 | "types": ["cypress"], 6 | "moduleResolution": "node" 7 | }, 8 | "include": ["./**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /examples/credit_card.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Credit Card example 4 | 5 | 6 | 7 | 8 | 39 | 109 | 110 | 111 |
112 |
113 | 114 |
115 |

Add New Credit Card

116 |
117 | 118 | 126 |
127 |
128 | 129 | / 138 | 147 | 148 | 156 |
157 |
158 | 159 |
160 |
161 | 162 | 163 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest'); 2 | const { compilerOptions } = require('./tsconfig'); 3 | 4 | const common = { 5 | automock: false, 6 | coveragePathIgnorePatterns: ['test', 'dist'], 7 | transform: { '^.+\\.(t|j)sx?$': ['@swc/jest'] }, 8 | testPathIgnorePatterns: ['cypress'], 9 | roots: [''], 10 | modulePaths: [''], 11 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths), 12 | }; 13 | 14 | module.exports = { 15 | projects: [ 16 | { 17 | ...common, 18 | displayName: 'jsdom', 19 | testEnvironment: 'jsdom', 20 | }, 21 | { 22 | ...common, 23 | displayName: 'node', 24 | testEnvironment: 'node', 25 | }, 26 | ], 27 | coverageThreshold: { 28 | global: { 29 | statements: 80, 30 | branches: 80, 31 | lines: 80, 32 | functions: 80, 33 | }, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | MAKEFLAGS += --silent 2 | 3 | verify: 4 | ./scripts/verify.sh 5 | 6 | build: 7 | ./scripts/build.sh 8 | 9 | acceptance: 10 | ./scripts/acceptance.sh 11 | 12 | setup-infra: 13 | ./scripts/setupinfra.sh 14 | 15 | release: 16 | yarn release 17 | $(MAKE) setup-infra 18 | 19 | write-lib-vars: 20 | ./scripts/writelibvars.sh 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@basis-theory/basis-theory-js", 3 | "version": "4.28.2", 4 | "repository": "https://github.com/Basis-Theory/basis-theory-js", 5 | "license": "Apache-2.0", 6 | "author": { 7 | "name": "Basis Theory", 8 | "email": "support@basistheory.com" 9 | }, 10 | "main": "dist/index.js", 11 | "module": "dist/index.js", 12 | "typings": "dist/index.d.ts", 13 | "files": [ 14 | "dist" 15 | ], 16 | "prettier": "@basis-theory/eslint-config/prettier", 17 | "scripts": { 18 | "analyze": "size-limit --why", 19 | "build": "yarn clean && concurrently 'yarn build:bundle' 'yarn build:module' 'yarn build:declarations' 'yarn build:package' -n 'bundle,module,declarations,package' -c 'yellow,magenta,blue,green' --kill-others-on-fail -g", 20 | "build:bundle": "webpack --config webpack.prod.js", 21 | "build:declarations": "tsc && tsc-alias", 22 | "build:module": "babel src --extensions .ts --out-dir dist", 23 | "build:package": "node prepare.js", 24 | "clean": "rimraf dist", 25 | "cypress:ci": "cypress run --headless -b chrome", 26 | "cypress:open": "cypress open", 27 | "lint": "eslint . --quiet --ignore-path .gitignore", 28 | "lint:fix": "eslint . --fix --ignore-path .gitignore", 29 | "prepare": "yarn build", 30 | "release": "semantic-release", 31 | "size": "size-limit", 32 | "start": "webpack --config webpack.dev.js --watch", 33 | "test": "jest", 34 | "test:cov": "jest --coverage", 35 | "test:e2e": "yarn cypress:ci", 36 | "pretty-quick": "pretty-quick --staged", 37 | "postinstall": "husky install" 38 | }, 39 | "dependencies": { 40 | "axios": "^1.8.2", 41 | "camelcase-keys": "^6.2.2", 42 | "csstype": "^3.0.11", 43 | "os": "^0.1.2", 44 | "os-browserify": "^0.3.0", 45 | "snake-case": "^3.0.4", 46 | "snakecase-keys": "^3.2.1" 47 | }, 48 | "devDependencies": { 49 | "@babel/cli": "^7.22.15", 50 | "@babel/core": "^7.15.8", 51 | "@babel/plugin-proposal-class-properties": "^7.12.13", 52 | "@babel/plugin-transform-arrow-functions": "^7.12.13", 53 | "@babel/plugin-transform-runtime": "^7.22.15", 54 | "@babel/preset-env": "^7.16.5", 55 | "@babel/preset-typescript": "^7.12.13", 56 | "@basis-theory/eslint-config": "^1.0.13", 57 | "@commitlint/cli": "^12.1.1", 58 | "@commitlint/config-conventional": "^17.6.3", 59 | "@peculiar/webcrypto": "^1.4.3", 60 | "@semantic-release/changelog": "^6.0.3", 61 | "@semantic-release/commit-analyzer": "^8.0.1", 62 | "@semantic-release/exec": "^6.0.3", 63 | "@semantic-release/git": "^9.0.0", 64 | "@semantic-release/github": "^8.0.5", 65 | "@semantic-release/npm": "^7.0.10", 66 | "@semantic-release/release-notes-generator": "^10.0.3", 67 | "@size-limit/preset-small-lib": "^7.0.5", 68 | "@swc/core": "^1.3.75", 69 | "@swc/jest": "^0.2.28", 70 | "@types/chance": "^1.1.3", 71 | "@types/dotenv-webpack": "^7.0.4", 72 | "@types/eccrypto": "^1.1.5", 73 | "@types/is-base64": "^1.1.1", 74 | "@types/jest": "^27.4.0", 75 | "@types/uuid": "^8.3.3", 76 | "axios-mock-adapter": "^1.21.2", 77 | "babel-loader": "^8.2.2", 78 | "babel-plugin-inline-dotenv": "^1.1.0", 79 | "babel-plugin-transform-builtin-extend": "^1.1.2", 80 | "babel-plugin-tsconfig-paths-module-resolver": "^1.0.3", 81 | "chance": "^1.1.11", 82 | "concurrently": "^8.2.2", 83 | "cypress": "^12.16.0", 84 | "dotenv-webpack": "^8.0.1", 85 | "eslint": "^7.32.0", 86 | "husky": "^8.0.1", 87 | "is-base64": "^1.1.0", 88 | "jest": "^29.5.0", 89 | "jest-environment-jsdom": "^29.5.0", 90 | "prettier": "^2.2.1", 91 | "pretty-quick": "^3.1.3", 92 | "rimraf": "^3.0.2", 93 | "semantic-release": "^19.0.3", 94 | "size-limit": "^7.0.5", 95 | "ts-jest": "^29.1.1", 96 | "tsc-alias": "^1.7.0", 97 | "tsconfig-paths-webpack-plugin": "^3.5.2", 98 | "typescript": "^5.0.4", 99 | "uuid": "^8.3.2", 100 | "webpack": "^5.84.1", 101 | "webpack-bundle-analyzer": "^4.9.0", 102 | "webpack-cli": "^4.10.0", 103 | "webpack-merge": "^5.8.0" 104 | }, 105 | "resolutions": { 106 | "glob-parent": "^5.1.2", 107 | "postcss": "^8.4.31", 108 | "normalize-url": "^5.3.1", 109 | "is-svg": "^4.2.2", 110 | "yargs-parser": "^21.0.0", 111 | "mem": "^4.0.0", 112 | "semver-regex": "^3.1.4", 113 | "tmpl": "^1.0.5", 114 | "tar": "^4.4.19", 115 | "minimist": "^1.2.6", 116 | "json-schema": "^0.4.0", 117 | "terser": "^5.14.2", 118 | "word-wrap": "^1.2.5" 119 | }, 120 | "engines": { 121 | "node": ">=10.12.0" 122 | }, 123 | "publishConfig": { 124 | "access": "public" 125 | }, 126 | "size-limit": [ 127 | { 128 | "path": "dist/basis-theory-js.bundle.js", 129 | "limit": "50 KB" 130 | } 131 | ] 132 | } 133 | -------------------------------------------------------------------------------- /prepare.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const libPackage = require('./package.json'); 3 | 4 | // remove not required fields 5 | delete libPackage.devDependencies; 6 | delete libPackage['size-limit']; 7 | delete libPackage.prettier; 8 | 9 | // use only required temporary script in dist 10 | libPackage.scripts = { 11 | postversion: 'cd .. && node bump.js', 12 | }; 13 | 14 | // include all 'dist/*' files, but bundles 15 | libPackage.files = ['*', '!*.bundle.js']; 16 | 17 | // updates source flags removing 'dist' path 18 | ['main', 'module', 'typings'].forEach((prop) => { 19 | libPackage[prop] = libPackage[prop].replace('dist/', ''); 20 | }); 21 | 22 | fs.mkdirSync('./dist', { recursive: true }); 23 | fs.copyFileSync('README.md', './dist/README.md'); 24 | fs.copyFileSync('LICENSE', './dist/LICENSE'); 25 | fs.writeFileSync( 26 | './dist/package.json', 27 | JSON.stringify(libPackage, undefined, 2) 28 | ); 29 | -------------------------------------------------------------------------------- /scripts/acceptance.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | current_directory="$PWD" 5 | 6 | cd $(dirname $0)/.. 7 | 8 | yarn lint 9 | 10 | # unit 11 | yarn test --coverage 12 | # e2e 13 | yarn test:e2e 14 | 15 | result=$? 16 | 17 | cd "$current_directory" 18 | 19 | exit $result 20 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | current_directory="$PWD" 5 | 6 | cd $(dirname $0) 7 | 8 | if [ "$SKIP_INSTALL" = true ] || [ "$SKIP_INSTALL" = 1 ] 9 | then 10 | echo SKIP_INSTALL is set, skipping dependency installation... 11 | else 12 | yarn install --frozen-lockfile 13 | fi 14 | 15 | yarn build 16 | 17 | cd "$current_directory" 18 | -------------------------------------------------------------------------------- /scripts/setupinfra.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | script_directory="$PWD" 5 | 6 | # get bundle source 7 | cd $(dirname $0)/../dist 8 | dist_directory="$PWD" 9 | 10 | # back to script directory 11 | cd $script_directory 12 | 13 | if [[ -z "${ENVIRONMENT}" ]]; then 14 | echo "environment variable is not set" 15 | exit 1 16 | fi 17 | 18 | if [ "${ENVIRONMENT}" = dev ]; then 19 | JS_HOST="js.flock-dev.com" 20 | else 21 | JS_HOST="js.basistheory.com" 22 | fi 23 | 24 | MAJOR_VERSION=$(cat package.json | jq -r '.version' | cut -d. -f1) 25 | BUNDLE_PATH=$dist_directory/basis-theory-js.bundle.js 26 | BLOB_DIR=v$MAJOR_VERSION 27 | INDEX_JS_NAME=$BLOB_DIR/index.js 28 | VERSIONED_JS_NAME=$(cat package.json | jq -r '.version') 29 | # create script hash file to be used in sub-resource integrity checks, i.e. 30 | # https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity#using_shasum 31 | JS_HASH_PATH=$BUNDLE_PATH-hash 32 | shasum -b -a 384 $dist_directory/basis-theory-js.bundle.js | awk '{ print $1 }' | xxd -r -p | base64 > $JS_HASH_PATH 33 | 34 | echo "Uploading bundle to $JS_HOST/$INDEX_JS_NAME" 35 | 36 | if [[ -z ${AWS} ]]; then 37 | JS_BUCKET_NAME=$(aws s3 cp s3://basis-theory-tf-state/basistheory-cloudflare/$ENVIRONMENT/terraform.tfstate - | jq -r .outputs.js_bucket_name.value) 38 | else 39 | JS_BUCKET_NAME="${ENVIRONMENT}-${JS_HOST}" 40 | fi 41 | 42 | # Upload Content 43 | aws s3 cp --acl public-read "$BUNDLE_PATH" s3://"${JS_BUCKET_NAME}"/"${INDEX_JS_NAME}" 44 | aws s3 cp --acl public-read --content-type text/plain "$JS_HASH_PATH" s3://"${JS_BUCKET_NAME}"/"${INDEX_JS_NAME}-hash" 45 | 46 | if [ "$IS_PR_WORKFLOW" = true ] ; then 47 | BLOB_NAME=$BLOB_DIR/$(git rev-parse HEAD).js 48 | 49 | echo "Uploading bundle to $JS_HOST/$BLOB_NAME" 50 | 51 | aws s3 cp --acl public-read "$BUNDLE_PATH" s3://"${JS_BUCKET_NAME}"/"${BLOB_NAME}" 52 | aws s3 cp --acl public-read "$JS_HASH_PATH" s3://"${JS_BUCKET_NAME}"/"${BLOB_NAME}-hash" 53 | else 54 | echo "Uploading bundle to $JS_HOST/$VERSIONED_JS_NAME" 55 | 56 | aws s3 cp --acl public-read "$BUNDLE_PATH" s3://"${JS_BUCKET_NAME}"/"${VERSIONED_JS_NAME}" 57 | aws s3 cp --acl public-read --content-type text/plain "$JS_HASH_PATH" s3://"${JS_BUCKET_NAME}"/"${VERSIONED_JS_NAME}-hash" 58 | fi 59 | 60 | result=$? 61 | 62 | cd "$script_directory" 63 | 64 | exit $result 65 | -------------------------------------------------------------------------------- /scripts/verify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # verify the software 5 | 6 | current_directory="$PWD" 7 | 8 | cd $(dirname $0) 9 | 10 | time { 11 | ./build.sh 12 | ./acceptance.sh 13 | } 14 | 15 | cd "$current_directory" 16 | -------------------------------------------------------------------------------- /scripts/writelibvars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | current_directory="$PWD" 5 | 6 | if [ "$ENVIRONMENT" = dev ]; then 7 | JS_HOST="js.flock-dev.com" 8 | API_HOST="api.flock-dev.com" 9 | ELEMENTS_HOST="js.flock-dev.com/hosted-elements" 10 | else 11 | JS_HOST="js.basistheory.com" 12 | API_HOST="api.basistheory.com" 13 | ELEMENTS_HOST="js.basistheory.com/hosted-elements" 14 | fi 15 | 16 | DD_TOKEN="pub5f53501515584007899577554c4aeda6" 17 | 18 | printf 'JS_HOST=%s\n' "$JS_HOST" >> .env 19 | printf 'API_HOST=%s\n' "$API_HOST" >> .env 20 | printf 'ELEMENTS_HOST=%s\n' "$ELEMENTS_HOST" >> .env 21 | printf 'DD_TOKEN=%s\n' "$DD_TOKEN" >> .env 22 | printf 'DD_GIT_SHA=%s\n' "$DD_GIT_SHA" >> .env 23 | 24 | result=$? 25 | 26 | cd "$current_directory" 27 | 28 | exit $result 29 | -------------------------------------------------------------------------------- /src/application-templates/BasisTheoryApplicationTemplates.ts: -------------------------------------------------------------------------------- 1 | import { createRequestConfig, dataExtractor } from '@/common'; 2 | import { BasisTheoryService } from '@/service'; 3 | import { CrudBuilder } from '@/service/CrudBuilder'; 4 | import { ApplicationTemplate } from '@/types/models/application-templates'; 5 | import { RequestOptions } from '@/types/sdk'; 6 | 7 | export const BasisTheoryApplicationTemplates = new CrudBuilder( 8 | class BasisTheoryApplicationTemplates extends BasisTheoryService { 9 | public list(options?: RequestOptions): Promise { 10 | return this.client 11 | .get('/', createRequestConfig(options)) 12 | .then(dataExtractor); 13 | } 14 | } 15 | ) 16 | .retrieve() 17 | .build(); 18 | 19 | export type BasisTheoryApplicationTemplates = InstanceType< 20 | typeof BasisTheoryApplicationTemplates 21 | >; 22 | -------------------------------------------------------------------------------- /src/application-templates/index.ts: -------------------------------------------------------------------------------- 1 | export { BasisTheoryApplicationTemplates } from './BasisTheoryApplicationTemplates'; 2 | -------------------------------------------------------------------------------- /src/applicationKeys/BasisTheoryApplicationKeys.ts: -------------------------------------------------------------------------------- 1 | import { dataExtractor } from '@/common'; 2 | import { BasisTheoryService } from '@/service'; 3 | import { CrudBuilder } from '@/service/CrudBuilder'; 4 | import type { ApplicationKey } from '@/types/models'; 5 | 6 | export const BasisTheoryApplicationKeys = new CrudBuilder( 7 | class BasisTheoryApplicationKeys extends BasisTheoryService { 8 | public create(applicationId: string): Promise { 9 | return this.client.post(`${applicationId}/keys`).then(dataExtractor); 10 | } 11 | 12 | public get(applicationId: string): Promise { 13 | return this.client.get(`${applicationId}/keys`).then(dataExtractor); 14 | } 15 | 16 | public getById( 17 | applicationId: string, 18 | keyId: string 19 | ): Promise { 20 | return this.client 21 | .get(`${applicationId}/keys/${keyId}`) 22 | .then(dataExtractor); 23 | } 24 | 25 | public delete(applicationId: string, keyId: string): Promise { 26 | return this.client 27 | .delete(`${applicationId}/keys/${keyId}`) 28 | .then(dataExtractor); 29 | } 30 | } 31 | ).build(); 32 | 33 | export type BasisTheoryApplicationKeys = InstanceType< 34 | typeof BasisTheoryApplicationKeys 35 | >; 36 | -------------------------------------------------------------------------------- /src/applicationKeys/index.ts: -------------------------------------------------------------------------------- 1 | export { BasisTheoryApplicationKeys } from './BasisTheoryApplicationKeys'; 2 | -------------------------------------------------------------------------------- /src/applications/BasisTheoryApplications.ts: -------------------------------------------------------------------------------- 1 | import { createRequestConfig, dataExtractor } from '@/common'; 2 | import { BasisTheoryService } from '@/service'; 3 | import { CrudBuilder } from '@/service/CrudBuilder'; 4 | import type { 5 | Application, 6 | CreateApplication, 7 | UpdateApplication, 8 | } from '@/types/models'; 9 | import type { ListApplicationsQuery, RequestOptions } from '@/types/sdk'; 10 | 11 | export const BasisTheoryApplications = new CrudBuilder( 12 | class BasisTheoryApplications extends BasisTheoryService { 13 | /** 14 | * @deprecated use {@link retrieveByKey} instead 15 | */ 16 | public getApplicationByKey(): Promise { 17 | return this.retrieveByKey(); 18 | } 19 | 20 | public retrieveByKey(options?: RequestOptions): Promise { 21 | return this.client 22 | .get('/key', createRequestConfig(options)) 23 | .then(dataExtractor); 24 | } 25 | 26 | /** 27 | * @deprecated This method is deprecated. Use Terraform or https://portal.basistheory.com to manage keys instead. 28 | */ 29 | public regenerateKey( 30 | id: string, 31 | options?: RequestOptions 32 | ): Promise { 33 | return this.client 34 | .post(`${id}/regenerate`, undefined, createRequestConfig(options)) 35 | .then(dataExtractor); 36 | } 37 | } 38 | ) 39 | .create() 40 | .retrieve() 41 | .update() 42 | .delete() 43 | .list() 44 | .build(); 45 | 46 | export type BasisTheoryApplications = InstanceType< 47 | typeof BasisTheoryApplications 48 | >; 49 | -------------------------------------------------------------------------------- /src/applications/index.ts: -------------------------------------------------------------------------------- 1 | export { BasisTheoryApplications } from './BasisTheoryApplications'; 2 | -------------------------------------------------------------------------------- /src/common/BasisTheoryApiError.ts: -------------------------------------------------------------------------------- 1 | export class BasisTheoryApiError extends Error { 2 | public constructor( 3 | message: string, 4 | public readonly status: number, 5 | public readonly data?: unknown, 6 | public readonly _debug?: Record 7 | ) { 8 | super(message); 9 | this.name = 'BasisTheoryApiError'; 10 | 11 | Object.setPrototypeOf(this, BasisTheoryApiError.prototype); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/common/BasisTheoryValidationError.ts: -------------------------------------------------------------------------------- 1 | import type { FieldError } from '@/types/elements'; 2 | 3 | export class BasisTheoryValidationError< 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | Details = Record 6 | > extends Error { 7 | public constructor( 8 | message: string, 9 | public readonly details: Details, 10 | /** 11 | * @deprecated use {@link details} 12 | */ 13 | public readonly validation?: FieldError[] 14 | ) { 15 | super(message); 16 | this.name = 'BasisTheoryValidationError'; 17 | 18 | Object.setPrototypeOf(this, BasisTheoryValidationError.prototype); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/common/HttpClientError.ts: -------------------------------------------------------------------------------- 1 | export class HttpClientError extends Error { 2 | public readonly status: number; 3 | 4 | public readonly data?: unknown; 5 | 6 | public readonly headers?: unknown; 7 | 8 | public constructor( 9 | message: string, 10 | status: number, 11 | data?: unknown, 12 | headers?: unknown 13 | ) { 14 | super(message); 15 | this.name = 'HttpClientError'; 16 | this.status = status; 17 | this.data = data; 18 | this.headers = headers; 19 | 20 | Object.setPrototypeOf(this, HttpClientError.prototype); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/common/constants.ts: -------------------------------------------------------------------------------- 1 | import type { BasisTheoryServicesBasePathMap } from '@/types/sdk'; 2 | 3 | const WEB_ELEMENTS_VERSION = '1.7.2'; 4 | 5 | const API_KEY_HEADER = 'BT-API-KEY'; 6 | 7 | const BT_TRACE_ID_HEADER = 'bt-trace-id'; 8 | 9 | const CF_RAY_HEADER = 'cf-ray'; 10 | 11 | const BT_IDEMPOTENCY_KEY_HEADER = 'bt-idempotency-key'; 12 | 13 | const BT_EXPOSE_PROXY_RESPONSE_HEADER = 'BT-EXPOSE-RAW-PROXY-RESPONSE'; 14 | 15 | const CONTENT_TYPE_HEADER = 'Content-Type'; 16 | 17 | const MERGE_CONTENT_TYPE = 'application/merge-patch+json'; 18 | 19 | const USER_AGENT_HEADER = 'User-Agent'; 20 | 21 | const CLIENT_USER_AGENT_HEADER = 'BT-CLIENT-USER-AGENT'; 22 | 23 | const USER_AGENT_CLIENT = 'BasisTheoryJS'; 24 | 25 | const DEFAULT_BASE_URL = `https://${process.env.API_HOST}`; 26 | 27 | const DEFAULT_ELEMENTS_BASE_URL = `https://${process.env.ELEMENTS_HOST}`; 28 | 29 | const DD_TOKEN = process.env.DD_TOKEN; 30 | 31 | const DD_GIT_SHA = process.env.DD_GIT_SHA; 32 | 33 | const CLIENT_BASE_PATHS: BasisTheoryServicesBasePathMap = { 34 | tokens: 'tokens', 35 | tokenize: 'tokenize', 36 | applications: 'applications', 37 | applicationKeys: 'applications', 38 | applicationTemplates: 'application-templates', 39 | tenants: 'tenants/self', 40 | logs: 'logs', 41 | reactorFormulas: 'reactor-formulas', 42 | reactors: 'reactors', 43 | permissions: 'permissions', 44 | proxies: 'proxies', 45 | proxy: 'proxy', 46 | sessions: 'sessions', 47 | threeds: '3ds', 48 | tokenIntents: 'token-intents', 49 | }; 50 | 51 | const BROWSER_LIST = [ 52 | { 53 | browserName: 'Firefox', 54 | browserUA: 'Firefox', 55 | }, 56 | { 57 | browserName: 'SamsungBrowser', 58 | browserUA: 'SamsungBrowser', 59 | }, 60 | { 61 | browserName: 'Opera', 62 | browserUA: 'Opera', 63 | }, 64 | { 65 | browserName: 'Opera', 66 | browserUA: 'OPR', 67 | }, 68 | { 69 | browserName: 'Microsoft Internet Explorer', 70 | browserUA: 'Trident', 71 | }, 72 | { 73 | browserName: 'Microsoft Edge (Legacy)', 74 | browserUA: 'Edge', 75 | }, 76 | { 77 | browserName: 'Microsoft Edge (Chromium)', 78 | browserUA: 'Edg', 79 | }, 80 | { 81 | browserName: 'Google Chrome/Chromium', 82 | browserUA: 'Chrome', 83 | }, 84 | { 85 | browserName: 'Safari', 86 | browserUA: 'Safari', 87 | }, 88 | ]; 89 | 90 | export { 91 | API_KEY_HEADER, 92 | BROWSER_LIST, 93 | BT_EXPOSE_PROXY_RESPONSE_HEADER, 94 | BT_IDEMPOTENCY_KEY_HEADER, 95 | BT_TRACE_ID_HEADER, 96 | CF_RAY_HEADER, 97 | CLIENT_BASE_PATHS, 98 | CLIENT_USER_AGENT_HEADER, 99 | CONTENT_TYPE_HEADER, 100 | DD_GIT_SHA, 101 | DD_TOKEN, 102 | DEFAULT_BASE_URL, 103 | DEFAULT_ELEMENTS_BASE_URL, 104 | MERGE_CONTENT_TYPE, 105 | USER_AGENT_CLIENT, 106 | USER_AGENT_HEADER, 107 | WEB_ELEMENTS_VERSION, 108 | }; 109 | -------------------------------------------------------------------------------- /src/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants'; 2 | export * from './utils'; 3 | export { BasisTheoryApiError } from './BasisTheoryApiError'; 4 | export { BasisTheoryValidationError } from './BasisTheoryValidationError'; 5 | export { HttpClientError } from './HttpClientError'; 6 | -------------------------------------------------------------------------------- /src/common/logging.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_BASE_URL } from './constants'; 2 | 3 | export const logger = (() => { 4 | const ddTok = 'pubb96b84a13912504f4354f2d794ea4fab'; 5 | 6 | let isTelemetryEnabled = process.env.NODE_ENV !== 'test'; 7 | 8 | const log = async ( 9 | message: string, 10 | level: string, 11 | attributes = {} 12 | ): Promise => { 13 | if (!isTelemetryEnabled) { 14 | return; 15 | } 16 | 17 | try { 18 | let env; 19 | 20 | if (DEFAULT_BASE_URL.includes('localhost')) { 21 | env = 'local'; 22 | } else if (DEFAULT_BASE_URL.includes('dev')) { 23 | env = 'dev'; 24 | } else { 25 | env = 'prod'; 26 | } 27 | 28 | const payload = { 29 | // dd info 30 | level, 31 | message, 32 | 33 | // for basis theory tracking 34 | service: 'js-sdk', 35 | env, 36 | 37 | // browser information 38 | referrer: document?.referrer, 39 | origin: window?.location?.origin, 40 | url: window?.location?.href, 41 | userAgent: navigator?.userAgent, 42 | 43 | // custom values 44 | ...attributes, 45 | }; 46 | 47 | await fetch(`https://http-intake.logs.datadoghq.com/v1/input/${ddTok}`, { 48 | method: 'POST', 49 | body: JSON.stringify(payload), 50 | headers: { 51 | 'Content-Type': 'application/json', 52 | }, 53 | }); 54 | } catch { 55 | // eslint-disable-next-line no-console 56 | console.warn('There was an error sending telemetry.'); 57 | } 58 | }; 59 | 60 | const setTelemetryEnabled = (enabled: boolean): void => { 61 | isTelemetryEnabled = enabled; 62 | }; 63 | 64 | return { 65 | setTelemetryEnabled, 66 | log: { 67 | error: (message: string, attributes = {}) => 68 | log(message, 'error', attributes), 69 | info: (message: string, attributes = {}) => 70 | log(message, 'info', attributes), 71 | warn: (message: string, attributes = {}) => 72 | log(message, 'warn', attributes), 73 | }, 74 | }; 75 | })(); 76 | -------------------------------------------------------------------------------- /src/elements/constants.ts: -------------------------------------------------------------------------------- 1 | const ELEMENTS_INIT_ERROR_MESSAGE = 2 | 'BasisTheory Elements was not properly initialized.'; 3 | 4 | const ELEMENTS_NOM_DOM_ERROR_MESSAGE = 5 | 'Tried to load BasisTheoryElements in a non-DOM environment.'; 6 | 7 | const ELEMENTS_SCRIPT_LOAD_ERROR_MESSAGE = 8 | 'Basis Theory Elements did not load properly. Check network tab for more details.'; 9 | 10 | const ELEMENTS_SCRIPT_UNKNOWN_ERROR_MESSAGE = 11 | 'Unable to load the Elements script. This may be due to network restrictions or browser extensions like ad blockers interfering with script loading. Check browser settings or network connection and try again.'; 12 | 13 | const ELEMENTS_SCRIPT_FAILED_TO_DELIVER = 14 | 'Failed to deliver Elements script from Basis Theory. Check your network connection and try again or contact support@basistheory.com'; 15 | 16 | const CARD_BRANDS = [ 17 | 'visa', 18 | 'mastercard', 19 | 'american-express', 20 | 'discover', 21 | 'diners-club', 22 | 'jcb', 23 | 'unionpay', 24 | 'maestro', 25 | 'elo', 26 | 'hiper', 27 | 'hipercard', 28 | 'mir', 29 | 'unknown', 30 | ] as const; 31 | 32 | const CARD_ICON_POSITIONS = ['left', 'right', 'none'] as const; 33 | 34 | // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete 35 | const AUTOCOMPLETE_VALUES = [ 36 | 'additional-name', 37 | 'address-level1', 38 | 'address-level2', 39 | 'address-level3', 40 | 'address-level4', 41 | 'address-line1', 42 | 'address-line2', 43 | 'address-line3', 44 | 'bday-day', 45 | 'bday-month', 46 | 'bday-year', 47 | 'bday', 48 | 'billing', 49 | 'cc-additional-name', 50 | 'cc-csc', 51 | 'cc-exp-month', 52 | 'cc-exp-year', 53 | 'cc-exp', 54 | 'cc-family-name', 55 | 'cc-given-name', 56 | 'cc-name', 57 | 'cc-number', 58 | 'cc-type', 59 | 'country-name', 60 | 'country', 61 | 'current-password', 62 | 'email', 63 | 'family-name', 64 | 'fax', 65 | 'given-name', 66 | 'home', 67 | 'honorific-prefix', 68 | 'honorific-suffix', 69 | 'language', 70 | 'mobile', 71 | 'name', 72 | 'new-password', 73 | 'nickname', 74 | 'off', 75 | 'on', 76 | 'one-time-code', 77 | 'organization-title', 78 | 'organization', 79 | 'page', 80 | 'postal-code', 81 | 'sex', 82 | 'shipping', 83 | 'street-address', 84 | 'tel-area-code', 85 | 'tel-country-code', 86 | 'tel-extension', 87 | 'tel-local-prefix', 88 | 'tel-local-suffix', 89 | 'tel-local', 90 | 'tel-national', 91 | 'tel', 92 | 'transaction-amount', 93 | 'transaction-currency', 94 | 'url', 95 | 'username', 96 | 'work', 97 | ] as const; 98 | 99 | export { 100 | ELEMENTS_INIT_ERROR_MESSAGE, 101 | ELEMENTS_NOM_DOM_ERROR_MESSAGE, 102 | ELEMENTS_SCRIPT_LOAD_ERROR_MESSAGE, 103 | ELEMENTS_SCRIPT_UNKNOWN_ERROR_MESSAGE, 104 | ELEMENTS_SCRIPT_FAILED_TO_DELIVER, 105 | CARD_BRANDS, 106 | CARD_ICON_POSITIONS, 107 | AUTOCOMPLETE_VALUES, 108 | }; 109 | -------------------------------------------------------------------------------- /src/elements/index.ts: -------------------------------------------------------------------------------- 1 | export * from './services'; 2 | export * from './script'; 3 | export * from './load'; 4 | -------------------------------------------------------------------------------- /src/elements/script.ts: -------------------------------------------------------------------------------- 1 | const findScript = (url: string): HTMLScriptElement | null => 2 | document.querySelector(`script[src^="${url}"]`); 3 | 4 | const injectScript = (url: string): HTMLScriptElement => { 5 | const script = document.createElement('script'); 6 | 7 | script.src = url; 8 | 9 | const parent = document.head || document.body; 10 | 11 | if (!parent) { 12 | throw new Error('No or elements found in document.'); 13 | } 14 | 15 | parent.append(script); 16 | 17 | return script; 18 | }; 19 | 20 | export { findScript, injectScript }; 21 | -------------------------------------------------------------------------------- /src/elements/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tokenize'; 2 | export * from './tokens'; 3 | export * from './proxy'; 4 | export * from './token-intents'; 5 | -------------------------------------------------------------------------------- /src/elements/services/proxy.ts: -------------------------------------------------------------------------------- 1 | import { BasisTheoryProxy } from '@/proxy'; 2 | import type { 3 | Proxy as ElementsProxy, 4 | BasisTheoryElementsInternal, 5 | } from '@/types/elements'; 6 | import type { Proxy, ProxyRequestOptions } from '@/types/sdk'; 7 | 8 | const delegateProxy = ( 9 | elements?: BasisTheoryElementsInternal 10 | ): new ( 11 | ...args: ConstructorParameters 12 | ) => ElementsProxy & Proxy => 13 | class BasisTheoryProxyElementsDelegate 14 | extends BasisTheoryProxy 15 | implements ElementsProxy { 16 | public get(options?: ProxyRequestOptions): Promise { 17 | if (elements !== undefined) { 18 | return elements.proxy.get(options); 19 | } 20 | 21 | return super.get(options); 22 | } 23 | 24 | public post(options?: ProxyRequestOptions): Promise { 25 | if (elements !== undefined) { 26 | return elements.proxy.post(options); 27 | } 28 | 29 | return super.post(options); 30 | } 31 | 32 | public put(options?: ProxyRequestOptions): Promise { 33 | if (elements !== undefined) { 34 | return elements.proxy.put(options); 35 | } 36 | 37 | return super.put(options); 38 | } 39 | 40 | public patch(options?: ProxyRequestOptions): Promise { 41 | if (elements !== undefined) { 42 | return elements.proxy.patch(options); 43 | } 44 | 45 | return super.patch(options); 46 | } 47 | 48 | public delete(options?: ProxyRequestOptions): Promise { 49 | if (elements !== undefined) { 50 | return elements.proxy.delete(options); 51 | } 52 | 53 | return super.delete(options); 54 | } 55 | }; 56 | 57 | export { delegateProxy }; 58 | -------------------------------------------------------------------------------- /src/elements/services/token-intents.ts: -------------------------------------------------------------------------------- 1 | import { BasisTheoryTokenIntents } from '@/token-intents'; 2 | import type { 3 | TokenIntents as ElementsTokenIntents, 4 | BasisTheoryElementsInternal, 5 | CreateTokenIntent as ElementsCreateTokenIntent, 6 | } from '@/types/elements'; 7 | import type { CreateTokenIntent, TokenIntent } from '@/types/models'; 8 | import type { RequestOptions, TokenIntents } from '@/types/sdk'; 9 | 10 | const delegateTokenIntents = ( 11 | elements?: BasisTheoryElementsInternal 12 | ): new ( 13 | ...args: ConstructorParameters 14 | ) => ElementsTokenIntents & TokenIntents => 15 | class BasisTheoryTokenIntentsElementsDelegate 16 | extends BasisTheoryTokenIntents 17 | implements ElementsTokenIntents { 18 | public create( 19 | payload: CreateTokenIntent | ElementsCreateTokenIntent, 20 | requestOptions?: RequestOptions 21 | ): Promise { 22 | if (elements?.hasElement(payload)) { 23 | return elements.tokenIntents.create( 24 | payload as ElementsCreateTokenIntent, 25 | requestOptions 26 | ); 27 | } 28 | 29 | return super.create(payload as CreateTokenIntent, requestOptions); 30 | } 31 | }; 32 | 33 | export { delegateTokenIntents }; 34 | -------------------------------------------------------------------------------- /src/elements/services/tokenize.ts: -------------------------------------------------------------------------------- 1 | import { BasisTheoryTokenize } from '@/tokenize'; 2 | import type { 3 | Tokenize as ElementsTokenize, 4 | BasisTheoryElementsInternal, 5 | TokenizeData as ElementsTokenizeData, 6 | } from '@/types/elements'; 7 | import type { TokenizeData } from '@/types/models'; 8 | import type { RequestOptions, Tokenize } from '@/types/sdk'; 9 | 10 | const delegateTokenize = ( 11 | elements?: BasisTheoryElementsInternal 12 | ): new ( 13 | ...args: ConstructorParameters 14 | ) => ElementsTokenize & Tokenize => 15 | class BasisTheoryTokenizesElementsDelegate 16 | extends BasisTheoryTokenize 17 | implements ElementsTokenize { 18 | public tokenize( 19 | payload: ElementsTokenizeData | TokenizeData, 20 | requestOptions?: RequestOptions 21 | ): Promise { 22 | if (elements?.hasElement(payload)) { 23 | return elements.tokenize( 24 | payload as ElementsTokenizeData, 25 | requestOptions 26 | ); 27 | } 28 | 29 | return super.tokenize(payload as TokenizeData, requestOptions); 30 | } 31 | }; 32 | 33 | export { delegateTokenize }; 34 | -------------------------------------------------------------------------------- /src/elements/services/tokens.ts: -------------------------------------------------------------------------------- 1 | import { BasisTheoryTokens } from '@/tokens'; 2 | import type { 3 | Tokens as ElementsTokens, 4 | BasisTheoryElementsInternal, 5 | CreateToken as ElementsCreateToken, 6 | UpdateToken as ElementsUpdateToken, 7 | } from '@/types/elements'; 8 | import type { CreateToken, Token, UpdateToken } from '@/types/models'; 9 | import type { RequestOptions, Tokens } from '@/types/sdk'; 10 | 11 | const delegateTokens = ( 12 | elements?: BasisTheoryElementsInternal 13 | ): new ( 14 | ...args: ConstructorParameters 15 | ) => ElementsTokens & Tokens => 16 | class BasisTheoryTokensElementsDelegate 17 | extends BasisTheoryTokens 18 | implements ElementsTokens { 19 | public create( 20 | payload: CreateToken | ElementsCreateToken, 21 | requestOptions?: RequestOptions 22 | ): Promise { 23 | if (elements?.hasElement(payload)) { 24 | return elements.tokens.create( 25 | payload as ElementsCreateToken, 26 | requestOptions 27 | ); 28 | } 29 | 30 | return super.create(payload as CreateToken, requestOptions); 31 | } 32 | 33 | public update( 34 | id: string, 35 | payload: UpdateToken | ElementsUpdateToken, 36 | requestOptions?: RequestOptions 37 | ): Promise { 38 | if (elements?.hasElement(payload)) { 39 | return elements.tokens.update( 40 | id, 41 | payload as ElementsUpdateToken, 42 | requestOptions 43 | ); 44 | } 45 | 46 | return super.update(id, payload as UpdateToken, requestOptions); 47 | } 48 | 49 | public retrieve( 50 | id: string, 51 | requestOptions?: RequestOptions 52 | // avoid casting when accessing token data props 53 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 54 | ): Promise> { 55 | if (elements !== undefined) { 56 | return elements.tokens.retrieve(id, requestOptions); 57 | } 58 | 59 | return super.retrieve(id, requestOptions); 60 | } 61 | }; 62 | 63 | export { delegateTokens }; 64 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import type { BasisTheoryInit } from '@/types/sdk'; 2 | import { BasisTheory } from './BasisTheory'; 3 | 4 | /** 5 | * Default instance used to expose bundle in