├── .changeset ├── README.md └── config.json ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── 1-bug-report.md │ └── 2-feature-request.md ├── renovate.json └── workflows │ ├── main.yml │ ├── playwright.yml │ ├── release.yml │ └── stale.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── commitlint.config.js ├── package-lock.json ├── package.json ├── packages ├── paypal-js │ ├── .eslintignore │ ├── .eslintrc.json │ ├── CHANGELOG.md │ ├── README.md │ ├── bundle-tests │ │ ├── bundle-size.test.js │ │ └── es3.test.js │ ├── e2e-tests │ │ ├── browser-global.html │ │ ├── browser-global.test.ts │ │ ├── http-server.js │ │ ├── load-cached-script.html │ │ ├── load-cached-script.test.ts │ │ ├── mocks.ts │ │ ├── reload-script.html │ │ ├── reload-script.test.ts │ │ ├── validation-errors.html │ │ └── validation-errors.test.ts │ ├── index.js │ ├── package.json │ ├── playwright.config.ts │ ├── rollup.config.js │ ├── src │ │ ├── index.ts │ │ ├── legacy │ │ │ └── index.ts │ │ ├── load-script.node.test.ts │ │ ├── load-script.test.ts │ │ ├── load-script.ts │ │ ├── utils.test.ts │ │ └── utils.ts │ ├── types │ │ ├── apis │ │ │ ├── openapi │ │ │ │ ├── billing_subscriptions_v1.d.ts │ │ │ │ └── checkout_orders_v2.d.ts │ │ │ ├── orders.d.ts │ │ │ └── subscriptions.d.ts │ │ ├── components │ │ │ ├── buttons.d.ts │ │ │ ├── card-fields.d.ts │ │ │ ├── funding-eligibility.d.ts │ │ │ ├── hosted-fields.d.ts │ │ │ ├── marks.d.ts │ │ │ └── messages.d.ts │ │ ├── index.d.ts │ │ ├── script-options.d.ts │ │ └── tests │ │ │ ├── buttons.test.ts │ │ │ ├── card-fields.test.ts │ │ │ ├── hosted-fields.test.ts │ │ │ ├── load-script.test.ts │ │ │ └── server.test.ts │ └── vitest.config.ts └── react-paypal-js │ ├── .eslintignore │ ├── .eslintrc.json │ ├── .github │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yaml │ │ ├── config.yml │ │ ├── documentation_improvement.yaml │ │ └── feature_request.yaml │ └── workflows │ │ ├── close-stale-issues.yml │ │ ├── publish.yml │ │ ├── storybook.yml │ │ └── validate.yml │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── .storybook │ ├── main.js │ ├── manager-head.html │ ├── preview-head.html │ └── preview.js │ ├── CHANGELOG.md │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── README.md │ ├── babel.config.js │ ├── commitlint.config.js │ ├── index.js │ ├── jest.setup.ts │ ├── package.json │ ├── renovate.json │ ├── rollup.config.js │ ├── scripts │ └── check-node-version.js │ ├── src │ ├── components │ │ ├── PayPalButtons.test.tsx │ │ ├── PayPalButtons.tsx │ │ ├── PayPalMarks.test.tsx │ │ ├── PayPalMarks.tsx │ │ ├── PayPalMessages.test.tsx │ │ ├── PayPalMessages.tsx │ │ ├── PayPalScriptProvider.test.tsx │ │ ├── PayPalScriptProvider.tsx │ │ ├── __snapshots__ │ │ │ ├── PayPalButtons.test.tsx.snap │ │ │ ├── PayPalMarks.test.tsx.snap │ │ │ └── PayPalMessages.test.tsx.snap │ │ ├── braintree │ │ │ ├── BraintreePayPalButtons.test.tsx │ │ │ ├── BraintreePayPalButtons.tsx │ │ │ ├── utils.test.ts │ │ │ └── utils.ts │ │ ├── cardFields │ │ │ ├── PayPalCVVField.test.tsx │ │ │ ├── PayPalCVVField.tsx │ │ │ ├── PayPalCardField.tsx │ │ │ ├── PayPalCardFieldsForm.test.tsx │ │ │ ├── PayPalCardFieldsForm.tsx │ │ │ ├── PayPalCardFieldsProvider.test.tsx │ │ │ ├── PayPalCardFieldsProvider.tsx │ │ │ ├── PayPalExpiryField.test.tsx │ │ │ ├── PayPalExpiryField.tsx │ │ │ ├── PayPalNameField.test.tsx │ │ │ ├── PayPalNameField.tsx │ │ │ ├── PayPalNumberField.test.tsx │ │ │ ├── PayPalNumberField.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── PayPalCardFieldsProvider.test.tsx.snap │ │ │ ├── context.ts │ │ │ ├── hooks.ts │ │ │ └── utils.ts │ │ ├── hostedFields │ │ │ ├── PayPalHostedField.test.tsx │ │ │ ├── PayPalHostedField.tsx │ │ │ ├── PayPalHostedFieldsProvider.test.tsx │ │ │ ├── PayPalHostedFieldsProvider.tsx │ │ │ ├── hooks.ts │ │ │ ├── utils.test.ts │ │ │ └── utils.ts │ │ └── ui │ │ │ ├── FlexContainer.tsx │ │ │ └── FullWidthContainer.tsx │ ├── constants.ts │ ├── context │ │ ├── payPalHostedFieldsContext.ts │ │ ├── scriptProviderContext.test.ts │ │ └── scriptProviderContext.ts │ ├── hooks │ │ ├── contextValidator.test.ts │ │ ├── contextValidator.ts │ │ ├── payPalHostedFieldsHooks.test.ts │ │ ├── payPalHostedFieldsHooks.ts │ │ ├── scriptProviderHooks.ts │ │ ├── useProxyProps.test.ts │ │ └── useProxyProps.ts │ ├── index.ts │ ├── stories │ │ ├── GettingStarted.stories.mdx │ │ ├── PayPalMessages.stories.tsx │ │ ├── braintree │ │ │ ├── BraintreePayPalButtons.stories.tsx │ │ │ └── code.ts │ │ ├── commons.tsx │ │ ├── components │ │ │ ├── CopyButton.tsx │ │ │ ├── CustomSandpack.tsx │ │ │ └── DocPageStructure.tsx │ │ ├── constants.ts │ │ ├── payPalButtons │ │ │ ├── PayPalButtons.stories.tsx │ │ │ └── code.ts │ │ ├── payPalCardFields │ │ │ ├── code.ts │ │ │ ├── payPalCardFieldsForm.stories.tsx │ │ │ ├── payPalCardFieldsIndividual.stories.tsx │ │ │ ├── payPalCardFieldsProvider.stories.tsx │ │ │ └── usePayPalCardFields.stories.tsx │ │ ├── payPalHostedFields │ │ │ ├── PayPalHostedFields.stories.tsx │ │ │ ├── PayPalHostedFieldsProvider.stories.tsx │ │ │ ├── codeHostedFields.ts │ │ │ └── codeProvider.ts │ │ ├── payPalMarks │ │ │ ├── PayPalMarks.stories.tsx │ │ │ └── code.ts │ │ ├── payPalScriptProvider │ │ │ ├── PayPalScriptProvider.stories.tsx │ │ │ └── code.ts │ │ ├── subscriptions │ │ │ ├── Subscriptions.stories.tsx │ │ │ └── code.ts │ │ ├── utils.ts │ │ └── venmo │ │ │ ├── VenmoButton.stories.tsx │ │ │ └── code.ts │ ├── types │ │ ├── braintree │ │ │ ├── clientTypes.ts │ │ │ ├── commonsTypes.ts │ │ │ └── paypalCheckout.ts │ │ ├── braintreePayPalButtonTypes.ts │ │ ├── enums.ts │ │ ├── index.ts │ │ ├── payPalCardFieldsTypes.ts │ │ ├── payPalHostedFieldTypes.ts │ │ ├── paypal-sdk-constants.d.ts │ │ ├── paypalButtonTypes.ts │ │ └── scriptProviderTypes.ts │ ├── utils.test.ts │ └── utils.ts │ ├── tsconfig.declarations.json │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.test.json ├── scripts ├── check-node-version.js └── readme-package-version.js └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Owner for everything in the repo 2 | * @paypal/checkout-sdk 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐞 Bug report 3 | about: 4 | Report a bug with the PayPal JS SDK. Before you create a new issue, please search for similar issues. 5 | It's possible somebody has encountered this bug already. 6 | title: "[Bug] Bug report" 7 | labels: "bug" 8 | assignees: "" 9 | --- 10 | 11 | ### Library used 12 | 13 | 14 | 15 | ### 🐞 Describe the Bug 16 | 17 | A clear and concise description of what the bug is. 18 | 19 | ### 🔬 Minimal Reproduction 20 | 21 | Describe steps to reproduce. If possible, please, share a link with a minimal reproduction. 22 | 23 | ### 😕 Actual Behavior 24 | 25 | A clear and concise description of what is happening. Please include console logs during the time of the issue, especially error messages. 26 | 27 | ### 🤔 Expected Behavior 28 | 29 | A clear and concise description of what you expected to happen. 30 | 31 | ### 🌍 Environment 32 | 33 | - Node.js/npm: - 34 | - OS: - 35 | - Browser: - 36 | 37 | ### ➕ Additional Context 38 | 39 | Add any other context about the problem here. 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature request 3 | about: Suggest an idea to us! 4 | title: "[Feature] Feature request" 5 | labels: "feature" 6 | assignees: "" 7 | --- 8 | 9 | ### 🚀 Feature Proposal 10 | 11 | A clear and concise description of what the feature is. 12 | 13 | ### Motivation 14 | 15 | Please outline the motivation for the proposal. 16 | 17 | ### Example 18 | 19 | Please provide an example for how this feature would be used. 20 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: {} 7 | jobs: 8 | main: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: ⬇️ Checkout repo 12 | uses: actions/checkout@v3 13 | with: 14 | ref: ${{github.event.pull_request.head.sha}} 15 | fetch-depth: 0 16 | 17 | - name: 🤝 Set Node version from .nvmrc 18 | run: echo NVMRC=`cat .nvmrc` >> $GITHUB_ENV 19 | 20 | - name: ⎔ Setup node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ env.NVMRC }} 24 | 25 | - name: 📥 Download deps 26 | uses: bahmutov/npm-install@v1 27 | 28 | - name: 🧾 Check for changeset 29 | # blocks PRs without changesets 30 | run: npx changeset status --since=origin/main 31 | 32 | - name: 🚧 Run build script 33 | run: npm run build 34 | 35 | - name: 📐 Run format script 36 | run: npm run format 37 | 38 | - name: 📖 Run lint script 39 | run: npm run lint 40 | 41 | - name: 🧪 Run test script 42 | run: npm run test 43 | -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: playwright 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: {} 7 | jobs: 8 | test: 9 | timeout-minutes: 60 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: 🛑 Cancel Previous Runs 13 | uses: styfle/cancel-workflow-action@0.11.0 14 | with: 15 | access_token: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | - name: ⬇️ Checkout repo 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: 🤝 Set Node version from .nvmrc 23 | run: echo NVMRC=`cat .nvmrc` >> $GITHUB_ENV 24 | 25 | - name: ⎔ Setup node 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: ${{ env.NVMRC }} 29 | 30 | - name: 📥 Download deps 31 | uses: bahmutov/npm-install@v1 32 | 33 | - name: 🌎 Install Playwright Browsers 34 | run: npx playwright install --with-deps 35 | 36 | - name: ▶️ Build the dist folder 37 | run: npm run build 38 | 39 | - name: ▶️ Run Playwright tests 40 | run: npm run test:e2e 41 | 42 | - uses: actions/upload-artifact@v4 43 | if: always() 44 | with: 45 | name: playwright-report 46 | path: playwright-report/ 47 | retention-days: 30 48 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v3 17 | with: 18 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 19 | fetch-depth: 0 20 | 21 | - name: 🤝 Set Node version from .nvmrc 22 | run: echo NVMRC=`cat .nvmrc` >> $GITHUB_ENV 23 | 24 | - name: ⎔ Setup node 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ env.NVMRC }} 28 | 29 | - name: 📥 Download deps 30 | uses: bahmutov/npm-install@v1 31 | 32 | - name: Setup npmrc 33 | run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc 34 | 35 | - name: 🚢 Create Release Pull Request or Publish to npm 36 | id: changesets 37 | uses: changesets/action@v1 38 | with: 39 | publish: npm run changeset:release 40 | version: npm run changeset:version 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale Issues and PRs" 2 | on: 3 | schedule: 4 | - cron: "30 0 * * *" 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: No activity for 90 days 11 | uses: actions/stale@v8 12 | with: 13 | repo-token: ${{ secrets.GITHUB_TOKEN }} 14 | days-before-stale: 90 15 | days-before-close: 7 16 | exempt-issue-labels: "awaiting-approval,work-in-progress" 17 | stale-issue-message: > 18 | This issue has been automatically marked as stale. 19 | **If this issue is still affecting you, please leave any comment** (for example, "bump"), and we'll keep it open. 20 | We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment! 21 | stale-pr-message: > 22 | This pull request has been automatically marked as stale. 23 | **If this pull request is still relevant, please leave any comment** (for example, "bump"), and we'll keep it open. 24 | We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated. 25 | close-issue-message: > 26 | Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. 27 | close-pr-message: > 28 | Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | 4 | node_modules 5 | .DS_Store 6 | /test-results/ 7 | /playwright-report/ 8 | /playwright/.cache/ 9 | .idea 10 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | playwright-report 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## PayPal JS Monorepo 2 | 3 | This is a collection of libraries intended to help developers more easily integrate with PayPal's JS SDK 4 | 5 | ### Packages Available 6 | 7 | Below is a list of available packages to install. 8 | 9 | Each package has its own documentation in it's respective README. 10 | 11 | - [@paypal/paypal-js](./packages/paypal-js/README.md): PayPal's Vanilla JS loader 12 | - [@paypal/react-paypal-js](./packages/react-paypal-js/README.md): PayPal's React loader 13 | 14 | ### Contributing 15 | 16 | #### Tools used 17 | 18 | - [changesets](https://github.com/changesets/changesets) for tracking version changes 19 | - [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces/) for monorepo package management 20 | - [prettier](https://prettier.io) for code formatting 21 | 22 | #### Steps to make a change 23 | 24 | 1. Install dependencies: 25 | 26 | ``` 27 | npm install 28 | ``` 29 | 30 | 2. Make proposed changes 31 | 3. Run tests 32 | 33 | ``` 34 | npm test 35 | ``` 36 | 37 | 4. Add a changeset for versioning 38 | 39 | ``` 40 | npm run changeset:add 41 | ``` 42 | 43 | 5. Open a new PR 44 | 45 | ### Releasing 46 | 47 | #### Releasing a new latest 48 | 49 | To release a new version please leverage Github Actions. There is a release action that can be run to create a new release. 50 | 51 | #### Release a new alpha 52 | 53 | There is no Github Action for alpha release at this time. Because this repo utilizes changesets we can follow their process locally in the meantime. This document can be seen [here](https://github.com/changesets/changesets/blob/main/docs/prereleases.md). 54 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@paypal/paypal-js-root", 3 | "description": "Collection of libraries supporting PayPal's JS SDK", 4 | "private": true, 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "license": "Apache-2.0", 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/paypal/paypal-js.git" 12 | }, 13 | "scripts": { 14 | "build": "npm run build --workspaces --if-present", 15 | "changeset:add": "changeset add", 16 | "changeset:release": "npm run build && changeset publish", 17 | "changeset:version": "changeset version && npm i --package-lock-only", 18 | "clean": "npm exec --workspaces -- npx rimraf node_modules && npx rimraf node_modules", 19 | "format": "prettier --write --ignore-unknown .", 20 | "format:check": "prettier --check .", 21 | "lint": "npm run lint --workspaces --if-present", 22 | "test": "npm run lint && npm run test --workspaces --if-present", 23 | "test:e2e": "npm run test:e2e --workspaces --if-present", 24 | "prepare": "husky install" 25 | }, 26 | "lint-staged": { 27 | "*": [ 28 | "prettier --write --ignore-unknown" 29 | ] 30 | }, 31 | "devDependencies": { 32 | "@changesets/cli": "^2.27.7", 33 | "husky": "^8.0.1", 34 | "lint-staged": "^13.0.3", 35 | "prettier": "^3.3.3" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/paypal-js/.eslintignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | scripts/* 3 | -------------------------------------------------------------------------------- /packages/paypal-js/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint"], 4 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 5 | "env": { 6 | "browser": true, 7 | "es2020": true, 8 | "node": true 9 | }, 10 | "parserOptions": { 11 | "ecmaVersion": 11, 12 | "sourceType": "module" 13 | }, 14 | "globals": { 15 | "page": "readonly" 16 | }, 17 | "ignorePatterns": ["dist/*.js"], 18 | "rules": { 19 | "indent": ["error", 4], 20 | "semi": ["error", "always"] 21 | }, 22 | "settings": { 23 | "polyfills": ["Promise", "fetch"] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/paypal-js/bundle-tests/bundle-size.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from "vitest"; 2 | import fs from "fs"; 3 | import { filesize } from "filesize"; 4 | 5 | const maxBundleSizeInKiloBytes = 4; 6 | const maxLegacyBundleSizeInKiloBytes = 7.8; 7 | 8 | describe("bundle size", () => { 9 | test(`paypal-js.min.js should be less than ${maxBundleSizeInKiloBytes} KB`, () => { 10 | const { size: sizeInBytes } = fs.statSync("dist/iife/paypal-js.min.js"); 11 | const [sizeInKiloBytes] = filesize(sizeInBytes, { output: "array" }); 12 | 13 | expect(sizeInKiloBytes).toBeLessThan(maxBundleSizeInKiloBytes); 14 | }); 15 | 16 | test(`paypal-js.legacy.min.js should be less than ${maxLegacyBundleSizeInKiloBytes} KB`, () => { 17 | const { size: sizeInBytes } = fs.statSync( 18 | "dist/iife/paypal-js.legacy.min.js", 19 | ); 20 | const [sizeInKiloBytes] = filesize(sizeInBytes, { output: "array" }); 21 | 22 | expect(sizeInKiloBytes).toBeLessThan(maxLegacyBundleSizeInKiloBytes); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/paypal-js/bundle-tests/es3.test.js: -------------------------------------------------------------------------------- 1 | import { describe, test } from "vitest"; 2 | import childProcess from "child_process"; 3 | 4 | const command = 5 | 'npx eslint dist/iife/paypal-js.js dist/iife/paypal-js.legacy.js --no-eslintrc --parser-options="{ ecmaVersion: 3 }"'; 6 | 7 | describe("es3", () => { 8 | test("should parse browser bundle using eslint's es3 parser", (done) => { 9 | childProcess.exec(command, (error, stdout, stderr) => { 10 | if (error || stderr) { 11 | console.log(error, stderr); 12 | throw new Error(stderr); 13 | } 14 | done(); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/paypal-js/e2e-tests/browser-global.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo with window.paypalLoadScript | PayPal JS 6 | 7 | 8 |

Demo with window.paypalLoadScript

9 | 10 | 11 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/paypal-js/e2e-tests/browser-global.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | import { successfulSDKResponseMock } from "./mocks"; 3 | 4 | test("Browser global window.paypalLoadScript", async ({ page }) => { 5 | page.route("https://www.paypal.com/sdk/js?**", (route) => 6 | route.fulfill({ 7 | status: 200, 8 | body: successfulSDKResponseMock(), 9 | }), 10 | ); 11 | 12 | await page.goto("/e2e-tests/browser-global.html"); 13 | 14 | await expect(page).toHaveTitle( 15 | "Demo with window.paypalLoadScript | PayPal JS", 16 | ); 17 | 18 | const scriptElement = await page.locator( 19 | 'script[src^="https://www.paypal.com/sdk/js"]', 20 | ); 21 | const uidFromDOM = await scriptElement.getAttribute("data-uid-auto"); 22 | 23 | expect(uidFromDOM).toMatch(/^\d+$/); 24 | 25 | const button = await page.locator(".paypal-button"); 26 | await expect(button).toBeVisible(); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/paypal-js/e2e-tests/http-server.js: -------------------------------------------------------------------------------- 1 | // source: https://developer.mozilla.org/en-US/docs/Learn/Server-side/Node_server_without_framework 2 | 3 | /* eslint @typescript-eslint/no-var-requires: "off" */ 4 | const http = require("http"); 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | 8 | const port = process.argv[2] || 8888; 9 | 10 | http.createServer(function (request, response) { 11 | console.log("request ", request.url); 12 | 13 | var filePath = "." + request.url; 14 | if (filePath == "./") { 15 | filePath = "./index.html"; 16 | } 17 | 18 | var extname = String(path.extname(filePath)).toLowerCase(); 19 | var mimeTypes = { 20 | ".html": "text/html", 21 | ".js": "text/javascript", 22 | ".css": "text/css", 23 | ".json": "application/json", 24 | ".png": "image/png", 25 | ".jpg": "image/jpg", 26 | ".gif": "image/gif", 27 | ".svg": "image/svg+xml", 28 | ".wav": "audio/wav", 29 | ".mp4": "video/mp4", 30 | ".woff": "application/font-woff", 31 | ".ttf": "application/font-ttf", 32 | ".eot": "application/vnd.ms-fontobject", 33 | ".otf": "application/font-otf", 34 | ".wasm": "application/wasm", 35 | }; 36 | 37 | var contentType = mimeTypes[extname] || "application/octet-stream"; 38 | 39 | fs.readFile(filePath, function (error, content) { 40 | if (error) { 41 | if (error.code == "ENOENT") { 42 | fs.readFile("./404.html", function (error, content) { 43 | response.writeHead(404, { "Content-Type": "text/html" }); 44 | response.end(content, "utf-8"); 45 | }); 46 | } else { 47 | response.writeHead(500); 48 | response.end( 49 | "Sorry, check with the site admin for error: " + 50 | error.code + 51 | " ..\n", 52 | ); 53 | } 54 | } else { 55 | response.writeHead(200, { "Content-Type": contentType }); 56 | response.end(content, "utf-8"); 57 | } 58 | }); 59 | }).listen(port); 60 | console.log(`Server running at http://127.0.0.1:${port}/`); 61 | -------------------------------------------------------------------------------- /packages/paypal-js/e2e-tests/load-cached-script.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Load Cached Script | PayPal JS 6 | 7 | 8 |

Load Cached Script

9 | 10 | 59 | 60 | 61 |
62 | 63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /packages/paypal-js/e2e-tests/load-cached-script.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | import { successfulSDKResponseMock } from "./mocks"; 3 | 4 | test("Load cached script", async ({ page }) => { 5 | page.route("https://www.paypal.com/sdk/js?**", (route) => 6 | route.fulfill({ 7 | status: 200, 8 | body: successfulSDKResponseMock(), 9 | }), 10 | ); 11 | 12 | let sdkRequestCounter = 0; 13 | await page.on("request", (request) => { 14 | if (request.url().startsWith("https://www.paypal.com/sdk/js")) { 15 | sdkRequestCounter++; 16 | } 17 | }); 18 | 19 | await page.goto("/e2e-tests/load-cached-script.html"); 20 | await expect(page).toHaveTitle("Load Cached Script | PayPal JS"); 21 | 22 | // should not reload the script when the loadScript options have not changed 23 | expect(sdkRequestCounter).toEqual(1); 24 | 25 | await page.locator("#btn-reload").click(); 26 | 27 | // wait 1 second 28 | await new Promise((resolve) => setTimeout(resolve, 1000)); 29 | 30 | expect(sdkRequestCounter).toEqual(1); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/paypal-js/e2e-tests/mocks.ts: -------------------------------------------------------------------------------- 1 | export function successfulSDKResponseMock() { 2 | return ` 3 | const button = document.createElement('button'); 4 | button.setAttribute('class', 'loaded'); 5 | button.setAttribute('class', 'paypal-button'); 6 | button.innerHTML = 'PayPal'; 7 | 8 | window.paypal = {}; 9 | window.paypal.Buttons = () => { 10 | return { 11 | render: (container) => { 12 | document.querySelector(container).appendChild(button); 13 | } 14 | } 15 | }; 16 | 17 | const randomNumber = Math.floor(Math.random()*90000) + 10000; 18 | document.currentScript.setAttribute("data-uid-auto", randomNumber); 19 | `.trim(); 20 | } 21 | 22 | export function validationErrorSDKResponseMock() { 23 | return ` 24 | throw new Error("SDK Validation error: 'Expected client-id to be passed'" ); 25 | 26 | /* Original Error: 27 | 28 | Expected client-id to be passed (debug id: 0808a38319a91) 29 | 30 | */`.trim(); 31 | } 32 | -------------------------------------------------------------------------------- /packages/paypal-js/e2e-tests/reload-script.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Reload Script Demo | PayPal JS 6 | 7 | 8 |

Reload Script Demo

9 | 10 | 36 | 37 |
38 | 39 | 40 | 45 |
46 |
47 | 48 |
49 | 50 | 51 | -------------------------------------------------------------------------------- /packages/paypal-js/e2e-tests/reload-script.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | import { successfulSDKResponseMock } from "./mocks"; 3 | 4 | test("Reload script", async ({ page }) => { 5 | page.route("https://www.paypal.com/sdk/js?**", (route) => { 6 | return route.fulfill({ 7 | status: 200, 8 | body: successfulSDKResponseMock(), 9 | }); 10 | }); 11 | 12 | await page.goto("/e2e-tests/reload-script.html"); 13 | await page.locator("select#currency").selectOption("USD"); 14 | 15 | await expect(page).toHaveTitle("Reload Script Demo | PayPal JS"); 16 | 17 | let sdkRequest; 18 | await page.on("request", (request) => { 19 | if (request.url().startsWith("https://www.paypal.com/sdk/js")) { 20 | sdkRequest = request.url(); 21 | } 22 | }); 23 | 24 | await page.locator("select#currency").selectOption("EUR"); 25 | expect(sdkRequest.includes("currency=EUR")).toBe(true); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/paypal-js/e2e-tests/validation-errors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Validation Errors | PayPal JS 6 | 7 | 8 |

Validation Errors

9 | 10 | 13 | 16 |

17 | 18 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/paypal-js/e2e-tests/validation-errors.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | import { validationErrorSDKResponseMock } from "./mocks"; 3 | 4 | test("Validation errors", async ({ page }) => { 5 | page.route("https://www.paypal.com/sdk/js?", (route) => 6 | route.fulfill({ 7 | status: 400, 8 | body: validationErrorSDKResponseMock(), 9 | }), 10 | ); 11 | 12 | await page.goto("/e2e-tests/validation-errors.html"); 13 | await expect(page).toHaveTitle("Validation Errors | PayPal JS"); 14 | await page.locator("#btn-load-no-client-id").click(); 15 | 16 | await page.waitForResponse((response) => 17 | response.url().startsWith("https://www.paypal.com/sdk/js"), 18 | ); 19 | 20 | await page.waitForFunction( 21 | 'document.querySelector("#error-message").innerText.length', 22 | ); 23 | 24 | const errorMessage = await page.locator("#error-message").innerText(); 25 | expect(errorMessage).toEqual( 26 | 'Error: The script "https://www.paypal.com/sdk/js?" failed to load. Check the HTTP status code and response body in DevTools to learn more.', 27 | ); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/paypal-js/index.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === "production") { 2 | module.exports = require("./dist/cjs/paypal-js.min.js"); 3 | } else { 4 | module.exports = require("./dist/cjs/paypal-js.js"); 5 | } 6 | -------------------------------------------------------------------------------- /packages/paypal-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@paypal/paypal-js", 3 | "version": "8.2.0", 4 | "description": "Loading wrapper and TypeScript types for the PayPal JS SDK", 5 | "main": "index.js", 6 | "module": "dist/esm/paypal-js.js", 7 | "types": "types/index.d.ts", 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "scripts": { 12 | "build": "rm -rf dist && rollup --config --bundleConfigAsCjs", 13 | "lint": "eslint .", 14 | "prerelease": "npm run validate", 15 | "release": "standard-version --commit-all", 16 | "postrelease": "git push && git push --follow-tags && npm run build && npm publish", 17 | "test": "vitest src", 18 | "test:bundle": "vitest bundle-tests/**", 19 | "test:e2e": "playwright test", 20 | "test:e2e:debug": "PWDEBUG=1 playwright test --project=\"chromium\"", 21 | "test:e2e:start": "node e2e-tests/http-server.js", 22 | "typecheck": "tsc --noEmit --allowSyntheticDefaultImports", 23 | "validate": "npm run check-node-version && npm run typecheck && npm run build && npm run lint && npm test -- --watch=false --coverage.enabled --coverage.include=src && npm run test:bundle -- --watch=false", 24 | "openapi": "npm run openapi-orders && npm run openapi-subscriptions", 25 | "openapi-orders": "openapi-typescript node_modules/@paypal/paypal-rest-api-specifications/openapi/checkout_orders_v2.json -o types/apis/openapi/checkout_orders_v2.d.ts", 26 | "openapi-subscriptions": "openapi-typescript node_modules/@paypal/paypal-rest-api-specifications/openapi/billing_subscriptions_v1.json -o types/apis/openapi/billing_subscriptions_v1.d.ts" 27 | }, 28 | "files": [ 29 | "dist", 30 | "types", 31 | "!types/**/*.test.ts" 32 | ], 33 | "keywords": [ 34 | "paypal", 35 | "sdk", 36 | "async", 37 | "script", 38 | "loader", 39 | "types", 40 | "typescript", 41 | "typescript-definitions" 42 | ], 43 | "license": "Apache-2.0", 44 | "repository": { 45 | "type": "git", 46 | "url": "git://github.com/paypal/paypal-js.git", 47 | "directory": "packages/paypal-js" 48 | }, 49 | "devDependencies": { 50 | "@commitlint/cli": "18.4.3", 51 | "@commitlint/config-conventional": "18.4.3", 52 | "@paypal/paypal-rest-api-specifications": "^1.0.0", 53 | "@playwright/test": "^1.40.1", 54 | "@rollup/plugin-commonjs": "25.0.7", 55 | "@rollup/plugin-node-resolve": "15.2.3", 56 | "@rollup/plugin-replace": "5.0.5", 57 | "@rollup/plugin-terser": "^0.4.4", 58 | "@rollup/plugin-typescript": "11.1.5", 59 | "@types/node": "^20.10.5", 60 | "@types/promise-polyfill": "6.0.6", 61 | "@typescript-eslint/eslint-plugin": "6.15.0", 62 | "@typescript-eslint/parser": "6.15.0", 63 | "@vitest/coverage-v8": "^1.1.0", 64 | "eslint": "8.56.0", 65 | "filesize": "10.1.0", 66 | "husky": "8.0.3", 67 | "jsdom": "^23.0.1", 68 | "lint-staged": "15.2.0", 69 | "openapi-typescript": "^6.7.3", 70 | "rollup": "4.9.1", 71 | "semver": "7.5.4", 72 | "standard-version": "9.5.0", 73 | "tslib": "2.6.2", 74 | "typescript": "5.3.3", 75 | "vitest": "1.1.0" 76 | }, 77 | "dependencies": { 78 | "promise-polyfill": "^8.3.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /packages/paypal-js/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from "@playwright/test"; 2 | import { devices } from "@playwright/test"; 3 | 4 | /** 5 | * See https://playwright.dev/docs/test-configuration. 6 | */ 7 | const config: PlaywrightTestConfig = { 8 | testDir: "./e2e-tests", 9 | /* Maximum time one test can run for. */ 10 | timeout: 30 * 1000, 11 | expect: { 12 | /** 13 | * Maximum time expect() should wait for the condition to be met. 14 | * For example in `await expect(locator).toHaveText();` 15 | */ 16 | timeout: 5000, 17 | }, 18 | /* Run tests in files in parallel */ 19 | fullyParallel: true, 20 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 21 | forbidOnly: !!process.env.CI, 22 | /* Retry on CI only */ 23 | retries: process.env.CI ? 2 : 0, 24 | /* Opt out of parallel tests on CI. */ 25 | workers: process.env.CI ? 1 : undefined, 26 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 27 | reporter: "html", 28 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 29 | use: { 30 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ 31 | actionTimeout: 0, 32 | /* Base URL to use in actions like `await page.goto('/')`. */ 33 | baseURL: "http://localhost:4444/", 34 | 35 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 36 | trace: "on-first-retry", 37 | }, 38 | 39 | /* Configure projects for major browsers */ 40 | projects: [ 41 | { 42 | name: "chromium", 43 | use: { 44 | ...devices["Desktop Chrome"], 45 | }, 46 | }, 47 | 48 | { 49 | name: "firefox", 50 | use: { 51 | ...devices["Desktop Firefox"], 52 | }, 53 | }, 54 | 55 | { 56 | name: "webkit", 57 | use: { 58 | ...devices["Desktop Safari"], 59 | }, 60 | }, 61 | ], 62 | 63 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */ 64 | // outputDir: 'test-results/', 65 | 66 | /* Run your local dev server before starting the tests */ 67 | webServer: { 68 | command: "npm run test:e2e:start -- 4444", 69 | port: 4444, 70 | timeout: 120 * 1000, 71 | reuseExistingServer: !process.env.CI, 72 | }, 73 | }; 74 | 75 | export default config; 76 | -------------------------------------------------------------------------------- /packages/paypal-js/rollup.config.js: -------------------------------------------------------------------------------- 1 | import replace from "@rollup/plugin-replace"; 2 | import { nodeResolve } from "@rollup/plugin-node-resolve"; 3 | import commonjs from "@rollup/plugin-commonjs"; 4 | import terser from "@rollup/plugin-terser"; 5 | import typescript from "@rollup/plugin-typescript"; 6 | import pkg from "./package.json"; 7 | 8 | const pkgName = pkg.name.split("@paypal/")[1]; 9 | const banner = getBannerText(); 10 | const outputConfigForBrowserBundle = { 11 | format: "iife", 12 | name: "paypalLoadScript", 13 | banner, 14 | // declare global variables for loadScript() and loadCustomScript() 15 | footer: 16 | "window.paypalLoadCustomScript = paypalLoadScript.loadCustomScript;" + 17 | "\nwindow.paypalLoadScript = paypalLoadScript.loadScript;", 18 | }; 19 | const tsconfigOverride = { 20 | exclude: ["node_modules", "**/*.test.ts"], 21 | }; 22 | 23 | export default [ 24 | { 25 | input: "src/index.ts", 26 | plugins: [ 27 | typescript({ ...tsconfigOverride }), 28 | replace({ 29 | __VERSION__: pkg.version, 30 | preventAssignment: true, 31 | }), 32 | ], 33 | output: [ 34 | { 35 | file: `dist/esm/${pkgName}.js`, 36 | format: "esm", 37 | banner, 38 | }, 39 | { 40 | file: `dist/esm/${pkgName}.min.js`, 41 | format: "esm", 42 | banner, 43 | plugins: [terser()], 44 | }, 45 | { 46 | file: `dist/cjs/${pkgName}.js`, 47 | format: "cjs", 48 | banner, 49 | }, 50 | { 51 | file: `dist/cjs/${pkgName}.min.js`, 52 | format: "cjs", 53 | banner, 54 | plugins: [terser()], 55 | }, 56 | { 57 | file: `dist/iife/${pkgName}.js`, 58 | ...outputConfigForBrowserBundle, 59 | }, 60 | { 61 | file: `dist/iife/${pkgName}.min.js`, 62 | ...outputConfigForBrowserBundle, 63 | plugins: [terser()], 64 | }, 65 | ], 66 | }, 67 | { 68 | input: "src/legacy/index.ts", 69 | plugins: [ 70 | typescript({ ...tsconfigOverride }), 71 | nodeResolve({ 72 | browser: true, 73 | }), 74 | commonjs(), 75 | replace({ 76 | __VERSION__: pkg.version, 77 | preventAssignment: true, 78 | }), 79 | ], 80 | output: [ 81 | { 82 | file: `dist/iife/${pkgName}.legacy.js`, 83 | ...outputConfigForBrowserBundle, 84 | }, 85 | { 86 | file: `dist/iife/${pkgName}.legacy.min.js`, 87 | ...outputConfigForBrowserBundle, 88 | plugins: [terser()], 89 | }, 90 | ], 91 | }, 92 | ]; 93 | 94 | function getBannerText() { 95 | return ` 96 | /*! 97 | * ${pkgName} v${pkg.version} (${new Date().toISOString()}) 98 | * Copyright 2020-present, PayPal, Inc. All rights reserved. 99 | * 100 | * Licensed under the Apache License, Version 2.0 (the "License"); 101 | * you may not use this file except in compliance with the License. 102 | * You may obtain a copy of the License at 103 | * 104 | * http://www.apache.org/licenses/LICENSE-2.0 105 | * 106 | * Unless required by applicable law or agreed to in writing, software 107 | * distributed under the License is distributed on an "AS IS" BASIS, 108 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 109 | * See the License for the specific language governing permissions and 110 | * limitations under the License. 111 | */`.trim(); 112 | } 113 | -------------------------------------------------------------------------------- /packages/paypal-js/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./load-script"; 2 | 3 | // replaced with the package.json version at build time 4 | export const version = "__VERSION__"; 5 | -------------------------------------------------------------------------------- /packages/paypal-js/src/legacy/index.ts: -------------------------------------------------------------------------------- 1 | import * as Promise from "promise-polyfill"; 2 | import { 3 | loadScript as originalLoadScript, 4 | loadCustomScript as originalLoadCustomScript, 5 | } from "../load-script"; 6 | import type { PayPalScriptOptions } from "../../types/script-options"; 7 | import type { PayPalNamespace } from "../../types/index"; 8 | 9 | export function loadScript( 10 | options: PayPalScriptOptions, 11 | ): Promise { 12 | return originalLoadScript(options, Promise); 13 | } 14 | 15 | export function loadCustomScript(options: { 16 | url: string; 17 | attributes?: Record; 18 | }): Promise { 19 | return originalLoadCustomScript(options, Promise); 20 | } 21 | 22 | // replaced with the package.json version at build time 23 | export const version = "__VERSION__"; 24 | -------------------------------------------------------------------------------- /packages/paypal-js/src/load-script.node.test.ts: -------------------------------------------------------------------------------- 1 | // @vitest-environment node 2 | 3 | import { test, expect } from "vitest"; 4 | 5 | import { loadScript, loadCustomScript } from "./load-script"; 6 | 7 | test("should still resolve when global window object does not exist", async () => { 8 | await expect(loadScript({ clientId: "test" })).resolves.toBeNull(); 9 | await expect( 10 | loadCustomScript({ url: "https://www.example.com/index.js" }), 11 | ).resolves.toBeUndefined(); 12 | }); 13 | -------------------------------------------------------------------------------- /packages/paypal-js/types/apis/orders.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | components, 3 | operations, 4 | paths, 5 | } from "./openapi/checkout_orders_v2"; 6 | 7 | export interface CheckoutOrdersV2 { 8 | components: components; 9 | operations: operations; 10 | paths: paths; 11 | } 12 | 13 | export type CreateOrderRequestBody = 14 | CheckoutOrdersV2["components"]["schemas"]["order_request"]; 15 | export type OrderResponseBody = 16 | CheckoutOrdersV2["components"]["schemas"]["order"]; 17 | export type PatchOrderRequestBody = 18 | CheckoutOrdersV2["components"]["schemas"]["patch_request"]; 19 | 20 | export type PurchaseUnit = components["schemas"]["purchase_unit_request"]; 21 | export type PurchaseUnitItem = components["schemas"]["item"]; 22 | export type ShippingAddress = components["schemas"]["address_portable"]; 23 | export type ShippingOption = components["schemas"]["shipping_option"]; 24 | -------------------------------------------------------------------------------- /packages/paypal-js/types/apis/subscriptions.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | components, 3 | operations, 4 | paths, 5 | } from "./openapi/billing_subscriptions_v1"; 6 | 7 | export interface BillingSubscriptionsV1 { 8 | components: components; 9 | operations: operations; 10 | paths: paths; 11 | } 12 | 13 | export type CreateSubscriptionRequestBody = 14 | BillingSubscriptionsV1["components"]["schemas"]["subscription_request_post"]; 15 | export type ReviseSubscriptionRequestBody = 16 | BillingSubscriptionsV1["components"]["schemas"]["subscription_revise_request"]; 17 | export type SubscriptionResponseBody = 18 | BillingSubscriptionsV1["components"]["schemas"]["plan"]; 19 | -------------------------------------------------------------------------------- /packages/paypal-js/types/components/funding-eligibility.d.ts: -------------------------------------------------------------------------------- 1 | export type FUNDING_SOURCE = 2 | | "paypal" 3 | | "venmo" 4 | | "applepay" 5 | | "itau" 6 | | "credit" 7 | | "paylater" 8 | | "card" 9 | | "ideal" 10 | | "sepa" 11 | | "bancontact" 12 | | "giropay" 13 | | "sofort" 14 | | "eps" 15 | | "mybank" 16 | | "p24" 17 | | "verkkopankki" 18 | | "payu" 19 | | "blik" 20 | | "trustly" 21 | | "zimpler" 22 | | "maxima" 23 | | "oxxo" 24 | | "boletobancario" 25 | | "wechatpay" 26 | | "mercadopago" 27 | | "multibanco"; 28 | 29 | export type getFundingSources = () => FUNDING_SOURCE[]; 30 | export type isFundingEligible = (fundingSource: FUNDING_SOURCE) => boolean; 31 | export type rememberFunding = (fundingSource: FUNDING_SOURCE[]) => void; 32 | -------------------------------------------------------------------------------- /packages/paypal-js/types/components/marks.d.ts: -------------------------------------------------------------------------------- 1 | export interface PayPalMarksComponentOptions { 2 | /** 3 | * Used for defining a standalone mark. 4 | */ 5 | fundingSource?: string; 6 | } 7 | 8 | export interface PayPalMarksComponent { 9 | isEligible: () => boolean; 10 | render: (container: HTMLElement | string) => Promise; 11 | } 12 | -------------------------------------------------------------------------------- /packages/paypal-js/types/components/messages.d.ts: -------------------------------------------------------------------------------- 1 | export interface PayPalMessagesComponentOptions { 2 | amount?: number | string; 3 | currency?: "USD" | "GBP" | "EUR"; 4 | style?: { 5 | layout?: "text" | "flex" | "custom"; 6 | color?: string; 7 | logo?: { 8 | type?: "primary" | "alternative" | "inline" | "none"; 9 | position?: "left" | "right" | "top"; 10 | }; 11 | text?: { 12 | color?: "black" | "white" | "monochrome" | "grayscale"; 13 | size?: 10 | 11 | 12 | 13 | 14 | 15 | 16; 14 | align?: "left" | "center" | "right"; 15 | }; 16 | ratio?: "1x1" | "1x4" | "8x1" | "20x1"; 17 | }; 18 | placement?: "home" | "category" | "product" | "cart" | "payment"; 19 | onApply?: (data: Record) => void; 20 | onClick?: (data: Record) => void; 21 | onRender?: (data: Record) => void; 22 | } 23 | 24 | export interface PayPalMessagesComponent { 25 | render: (container: HTMLElement | string) => Promise; 26 | } 27 | -------------------------------------------------------------------------------- /packages/paypal-js/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { PayPalScriptOptions } from "./script-options"; 2 | import type { 3 | PayPalButtonsComponentOptions, 4 | PayPalButtonsComponent, 5 | } from "./components/buttons"; 6 | import type { 7 | PayPalMarksComponentOptions, 8 | PayPalMarksComponent, 9 | } from "./components/marks"; 10 | import type { 11 | PayPalMessagesComponentOptions, 12 | PayPalMessagesComponent, 13 | } from "./components/messages"; 14 | import type { 15 | FUNDING_SOURCE, 16 | getFundingSources, 17 | isFundingEligible, 18 | rememberFunding, 19 | } from "./components/funding-eligibility"; 20 | import type { PayPalHostedFieldsComponent } from "./components/hosted-fields"; 21 | import type { 22 | PayPalCardFieldsComponentOptions, 23 | PayPalCardFieldsComponent, 24 | } from "./components/card-fields"; 25 | 26 | export interface PayPalNamespace { 27 | Buttons?: ( 28 | options?: PayPalButtonsComponentOptions, 29 | ) => PayPalButtonsComponent; 30 | Marks?: (options?: PayPalMarksComponentOptions) => PayPalMarksComponent; 31 | Messages?: ( 32 | options?: PayPalMessagesComponentOptions, 33 | ) => PayPalMessagesComponent; 34 | HostedFields?: PayPalHostedFieldsComponent; 35 | CardFields?: ( 36 | options?: PayPalCardFieldsComponentOptions, 37 | ) => PayPalCardFieldsComponent; 38 | getFundingSources?: getFundingSources; 39 | isFundingEligible?: isFundingEligible; 40 | rememberFunding?: rememberFunding; 41 | FUNDING?: Record; 42 | version: string; 43 | } 44 | 45 | export function loadScript( 46 | options: PayPalScriptOptions, 47 | PromisePonyfill?: PromiseConstructor, 48 | ): Promise; 49 | 50 | export function loadCustomScript(options: { 51 | url: string; 52 | attributes?: Record; 53 | PromisePonyfill?: PromiseConstructor; 54 | }): Promise; 55 | 56 | export const version: string; 57 | 58 | declare global { 59 | interface Window { 60 | paypal?: PayPalNamespace | null; 61 | } 62 | } 63 | 64 | // Export components 65 | export * from "./components/buttons"; 66 | export * from "./components/funding-eligibility"; 67 | export * from "./components/hosted-fields"; 68 | export * from "./components/marks"; 69 | export * from "./components/messages"; 70 | export * from "./components/card-fields"; 71 | 72 | // Export apis 73 | export * from "./apis/orders"; 74 | export * from "./apis/subscriptions"; 75 | 76 | // Export script-options 77 | export * from "./script-options"; 78 | -------------------------------------------------------------------------------- /packages/paypal-js/types/script-options.d.ts: -------------------------------------------------------------------------------- 1 | interface PayPalScriptQueryParameters { 2 | buyerCountry?: string; 3 | clientId: string; 4 | commit?: boolean; 5 | components?: string[] | string; 6 | currency?: string; 7 | debug?: boolean | string; 8 | // loadScript() supports an array and will convert it 9 | // to the correct disable-funding and enable-funding string values 10 | disableFunding?: string[] | string; 11 | enableFunding?: string[] | string; 12 | integrationDate?: string; 13 | intent?: string; 14 | locale?: string; 15 | // loadScript() supports an array for merchantId, even though 16 | // merchant-id technically may not contain multiple values. 17 | // For an array with a length of > 1 it automatically sets 18 | // merchantId to "*" and moves the actual values to dataMerchantId 19 | merchantId?: string[] | string; 20 | vault?: boolean | string; 21 | } 22 | 23 | interface PayPalScriptDataAttributes { 24 | dataClientToken?: string; 25 | dataCspNonce?: string; 26 | dataClientMetadataId?: string; 27 | dataJsSdkLibrary?: string; 28 | // loadScript() supports an array and will convert it 29 | // to the correct dataMerchantId string values 30 | dataMerchantId?: string[] | string; 31 | dataNamespace?: string; 32 | dataPageType?: string; 33 | dataPartnerAttributionId?: string; 34 | dataSdkIntegrationSource?: string; 35 | dataUid?: string; 36 | dataUserIdToken?: string; 37 | } 38 | 39 | interface ScriptAttributes { 40 | crossorigin?: "anonymous" | "use-credentials"; 41 | } 42 | 43 | export interface PayPalScriptOptions 44 | extends PayPalScriptQueryParameters, 45 | PayPalScriptDataAttributes, 46 | ScriptAttributes { 47 | environment?: "production" | "sandbox"; 48 | sdkBaseUrl?: string; 49 | } 50 | -------------------------------------------------------------------------------- /packages/paypal-js/types/tests/card-fields.test.ts: -------------------------------------------------------------------------------- 1 | import { test, vi, describe, expect } from "vitest"; 2 | 3 | import { PayPalCardFieldsComponentOptions } from "../components/card-fields"; 4 | 5 | describe.only("testing createOrder and createVaultToken types", () => { 6 | test("only creatOrder", () => { 7 | const createOrderCallback: PayPalCardFieldsComponentOptions = { 8 | createOrder: vi.fn(), 9 | onApprove: vi.fn(), 10 | onError: vi.fn(), 11 | }; 12 | 13 | expect(createOrderCallback.createVaultSetupToken).toBeUndefined(); 14 | }); 15 | 16 | test("only createVaultSetupToken", () => { 17 | const createVaultSetupTokenCallback: PayPalCardFieldsComponentOptions = 18 | { 19 | createVaultSetupToken: vi.fn(), 20 | onApprove: vi.fn(), 21 | onError: vi.fn(), 22 | }; 23 | 24 | expect(createVaultSetupTokenCallback.createOrder).toBeUndefined(); 25 | }); 26 | 27 | test.skip("Can't have both createOrdre and createVaultSetupToken", () => { 28 | // @ts-expect-error : should throw error if both createOrder and createVaultSetupToken called 29 | const callback: PayPalCardFieldsComponentOptions = { 30 | createOrder: vi.fn(), 31 | createVaultSetupToken: vi.fn(), 32 | onApprove: vi.fn(), 33 | onError: vi.fn(), 34 | }; 35 | 36 | expect(callback).toThrowError(); 37 | }); 38 | 39 | test.skip("Can't implement neither", () => { 40 | // @ts-expect-error: should throw error if neither createOrder or createVaultSetuptoken called 41 | const callback: PayPalCardFieldsComponentOptions = { 42 | onApprove: vi.fn(), 43 | onError: vi.fn(), 44 | }; 45 | 46 | expect(callback).toThrowError(); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/paypal-js/types/tests/hosted-fields.test.ts: -------------------------------------------------------------------------------- 1 | import { loadScript } from "../../src/index"; 2 | import type { PayPalNamespace } from "../index"; 3 | 4 | // hosted-fields example 5 | // https://developer.paypal.com/docs/checkout/advanced/ 6 | 7 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 8 | async function main() { 9 | let paypal: PayPalNamespace | null; 10 | 11 | try { 12 | paypal = await loadScript({ 13 | clientId: "test", 14 | components: "buttons,hosted-fields", 15 | dataClientToken: "123456789", 16 | }); 17 | } catch (err) { 18 | throw new Error(`Failed to load the paypal sdk script: ${err}`); 19 | } 20 | 21 | if (!paypal?.HostedFields) { 22 | throw new Error("Invalid paypal object for hosted-fields component"); 23 | } 24 | 25 | if (paypal.HostedFields.isEligible() === false) { 26 | throw new Error("The hosted-fields component is ineligible"); 27 | } 28 | 29 | const cardFields = await paypal.HostedFields.render({ 30 | createOrder: () => { 31 | // Call your server to create the order 32 | return Promise.resolve("7632736476738"); 33 | }, 34 | styles: { 35 | ".valid": { 36 | color: "green", 37 | }, 38 | ".invalid": { 39 | color: "red", 40 | }, 41 | }, 42 | fields: { 43 | number: { 44 | selector: "#card-number", 45 | placeholder: "4111 1111 1111 1111", 46 | }, 47 | cvv: { 48 | selector: "#cvv", 49 | placeholder: "123", 50 | }, 51 | expirationDate: { 52 | selector: "#expiration-date", 53 | placeholder: "MM/YY", 54 | }, 55 | }, 56 | }); 57 | 58 | cardFields.on("validityChange", (event) => { 59 | const field = event.fields[event.emittedBy]; 60 | 61 | if (field.isValid) { 62 | console.log(event.emittedBy, "is fully valid"); 63 | } else if (field.isPotentiallyValid) { 64 | console.log(event.emittedBy, "is potentially valid"); 65 | } else { 66 | console.log(event.emittedBy, "is not valid"); 67 | } 68 | }); 69 | 70 | document 71 | .querySelector("#card-form") 72 | ?.addEventListener("submit", (event) => { 73 | const formFieldValues = Object.values(cardFields.getState().fields); 74 | const isFormValid = formFieldValues.every((field) => field.isValid); 75 | 76 | if (isFormValid === false) { 77 | return alert("The payment form is invalid"); 78 | } 79 | 80 | event.preventDefault(); 81 | cardFields.submit({}); 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /packages/paypal-js/types/tests/load-script.test.ts: -------------------------------------------------------------------------------- 1 | import { loadScript } from "../../src/index"; 2 | import type { PayPalNamespace } from "../index"; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 5 | async function main() { 6 | // @ts-expect-error - Property '"client-id"' is missing in type '{}' but required in type 'PayPalScriptOptions' 7 | loadScript({}); 8 | 9 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 10 | const loadScriptPromise: Promise = loadScript({ 11 | clientId: "test", 12 | }); 13 | 14 | let paypal: PayPalNamespace | null; 15 | 16 | try { 17 | paypal = await loadScript({ 18 | clientId: "test", 19 | currency: "USD", 20 | dataPageType: "checkout", 21 | disableFunding: "card", 22 | }); 23 | } catch (err) { 24 | throw new Error(`Failed to load the paypal sdk script: ${err}`); 25 | } 26 | 27 | paypal?.Buttons?.(); 28 | 29 | // you can also use window.paypal 30 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 31 | const paypalFromWindow = (window as any).paypal as PayPalNamespace; 32 | paypalFromWindow.Buttons?.(); 33 | } 34 | -------------------------------------------------------------------------------- /packages/paypal-js/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | environment: "jsdom", 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.eslintignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | scripts/* 3 | storybook-static/* 4 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint", "import"], 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react/recommended", 8 | "plugin:react-hooks/recommended" 9 | ], 10 | "env": { 11 | "browser": true, 12 | "es2020": true, 13 | "node": true, 14 | "jest": true 15 | }, 16 | "parserOptions": { 17 | "ecmaVersion": 11, 18 | "sourceType": "module" 19 | }, 20 | "globals": { 21 | "page": "readonly" 22 | }, 23 | "ignorePatterns": ["dist/*.js"], 24 | "settings": { 25 | "react": { 26 | "version": "16.3" 27 | } 28 | }, 29 | "rules": { 30 | "react/prop-types": "off", 31 | "@typescript-eslint/explicit-module-boundary-types": "off", 32 | "curly": "warn", 33 | "eqeqeq": "warn", 34 | "import/order": [ 35 | 1, 36 | { 37 | "groups": [ 38 | "external", 39 | "builtin", 40 | ["internal", "sibling", "parent"], 41 | "index", 42 | "type", 43 | "object" 44 | ], 45 | "newlines-between": "always" 46 | } 47 | ] 48 | }, 49 | "overrides": [ 50 | { 51 | "files": ["*.ts", "*.tsx"], 52 | "rules": { 53 | "@typescript-eslint/explicit-module-boundary-types": ["error"] 54 | } 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Owner for everything in the repo 2 | * @paypal/checkout-sdk -------------------------------------------------------------------------------- /packages/react-paypal-js/.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: Report a bug with react-paypal-js. 3 | title: "[BUG]: " 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: checkboxes 10 | attributes: 11 | label: Is there an existing issue for this? 12 | description: Please search to see if an issue already exists for the bug you encountered. 13 | options: 14 | - label: I have searched the existing issues. 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: 🐞 Describe the Bug 19 | description: | 20 | A clear and concise description of what the bug is. 21 | validations: 22 | required: false 23 | - type: textarea 24 | attributes: 25 | label: 😕 Current Behavior 26 | description: | 27 | A clear and concise description of what is happening. Please include console logs during the time of the issue, especially error messages. 28 | placeholder: | 29 | When I do , happens and I see the error message attached below: 30 | ```...``` 31 | validations: 32 | required: false 33 | - type: textarea 34 | attributes: 35 | label: 🤔 Expected Behavior 36 | description: A clear and concise description of what you expected to happen. 37 | placeholder: When I do , should happen instead. 38 | validations: 39 | required: false 40 | - type: textarea 41 | attributes: 42 | label: 🔬 Minimal Reproduction 43 | description: Describe steps to reproduce. If possible, please, share a link with a minimal reproduction. 44 | placeholder: | 45 | 1. Go to '...' 46 | 2. Click on '....' 47 | 3. Scroll down to '....' 48 | 4. See error 49 | render: markdown 50 | validations: 51 | required: false 52 | - type: textarea 53 | attributes: 54 | label: 🌍 Environment 55 | description: | 56 | Provide version numbers for the following components: 57 | value: | 58 | | Software | Version(s) | 59 | | ---------------- | ---------- | 60 | | react-paypal-js | | 61 | | Browser | | 62 | | Operating System | | 63 | render: markdown 64 | validations: 65 | required: false 66 | - type: textarea 67 | id: logs 68 | attributes: 69 | label: Relevant log output 70 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 71 | render: shell 72 | - type: checkboxes 73 | id: terms 74 | attributes: 75 | label: Code of Conduct 76 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/NativeScript/NativeScript/blob/master/tools/notes/CONTRIBUTING.md#coc) 77 | options: 78 | - label: I agree to follow this project's Code of Conduct 79 | validations: 80 | required: true 81 | - type: textarea 82 | attributes: 83 | label: ➕ Anything else? 84 | description: | 85 | Links? References? Anything that will give us more context about the issue you are encountering! 86 | validations: 87 | required: false 88 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.github/ISSUE_TEMPLATE/documentation_improvement.yaml: -------------------------------------------------------------------------------- 1 | name: 📖 Documentation Improvement 2 | description: Suggest improvements to our documentation 3 | title: "[DOCS]:" 4 | labels: [Documentation] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this documentation improvement request! 10 | - type: checkboxes 11 | attributes: 12 | label: Is there an existing issue for this? 13 | description: Please search to see if an issue realated to this already exists. 14 | options: 15 | - label: I have searched the existing issues 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Documentation Link 20 | description: Add a link to the page which needs improvement (if relevant) 21 | validations: 22 | required: false 23 | - type: textarea 24 | attributes: 25 | label: Describe the problem 26 | description: Is the documentation missing? Or is it confusing? Why is it confusing? 27 | validations: 28 | required: false 29 | - type: textarea 30 | attributes: 31 | label: Describe the improvement 32 | description: A clear and concise description of the improvement. 33 | validations: 34 | required: false 35 | - type: textarea 36 | attributes: 37 | label: Anything else? 38 | description: | 39 | Links? References? Anything that will give us more context about the issue you are encountering! 40 | validations: 41 | required: false 42 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature request 2 | description: Suggest an idea to us! 3 | title: "[FEATURE]: " 4 | labels: [Feature] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to request a feature for us! 10 | - type: checkboxes 11 | attributes: 12 | label: Is there an existing issue for this? 13 | description: Please search to see if an issue related to this feature request already exists. 14 | options: 15 | - label: I have searched the existing issues 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Is your feature request related to a problem? Please describe. 20 | description: A clear and concise description of what the problem is. 21 | placeholder: | 22 | I wish I could use this to do [...] 23 | OR 24 | I'm always frustrated when [...] 25 | validations: 26 | required: false 27 | - type: textarea 28 | attributes: 29 | label: Describe the solution you'd like 30 | description: A clear and concise description of what you want to happen. 31 | validations: 32 | required: false 33 | - type: textarea 34 | attributes: 35 | label: Describe alternatives you've considered 36 | description: A clear and concise description of any alternative solutions or features you've considered. 37 | validations: 38 | required: false 39 | - type: textarea 40 | attributes: 41 | label: ➕ Anything else? 42 | description: Add any other context, code examples, or references to existing implementations about the feature request here. 43 | validations: 44 | required: false 45 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.github/workflows/close-stale-issues.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v3 11 | with: 12 | repo-token: ${{ secrets.GITHUB_TOKEN }} 13 | days-before-stale: 45 14 | days-before-close: 5 15 | exempt-issue-labels: "awaiting-approval,work-in-progress" 16 | stale-issue-message: > 17 | This issue has been automatically marked as stale. 18 | **If this issue is still affecting you, please leave any comment** (for example, "bump"), and we'll keep it open. 19 | We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment! 20 | stale-pr-message: > 21 | This pull request has been automatically marked as stale. 22 | **If this pull request is still relevant, please leave any comment** (for example, "bump"), and we'll keep it open. 23 | We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated. 24 | close-issue-message: > 25 | Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you! 26 | close-pr-message: > 27 | Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you! 28 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: "publish to npm" 2 | on: workflow_dispatch 3 | jobs: 4 | main: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: ⬇️ Checkout repo 8 | uses: actions/checkout@v4 9 | with: 10 | token: ${{ secrets.ACCESS_TOKEN }} 11 | ref: ${{github.event.pull_request.head.sha}} 12 | fetch-depth: 0 13 | 14 | - name: 🤝 Set Node version from .nvmrc 15 | run: echo NVMRC=`cat .nvmrc` >> $GITHUB_ENV 16 | 17 | - name: ⎔ Setup node 18 | # sets up the .npmrc file to publish to npm 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: ${{ env.NVMRC }} 22 | registry-url: "https://registry.npmjs.org" 23 | 24 | - name: 📥 Download deps 25 | uses: bahmutov/npm-install@v1 26 | with: 27 | useLockFile: false 28 | 29 | - name: Configure git user 30 | run: | 31 | git config --global user.email ${{ github.actor }}@users.noreply.github.com 32 | git config --global user.name ${{ github.actor }} 33 | 34 | - name: ▶️ Run release 35 | run: npm run release 36 | env: 37 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.github/workflows/storybook.yml: -------------------------------------------------------------------------------- 1 | name: "Deploy Storybook to Github Pages" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: ["src/**", ".storybook/**", "README.md"] 8 | 9 | jobs: 10 | ci: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: ⬇️ Checkout repo 15 | uses: actions/checkout@v4 16 | with: 17 | ref: ${{github.event.pull_request.head.sha}} 18 | fetch-depth: 0 19 | 20 | - name: 🤝 Set Node version from .nvmrc 21 | run: echo NVMRC=`cat .nvmrc` >> $GITHUB_ENV 22 | 23 | - name: ⎔ Setup node 24 | uses: actions/setup-node@v2 25 | with: 26 | node-version: ${{ env.NVMRC }} 27 | 28 | - name: 📥 Download deps 29 | uses: bahmutov/npm-install@v1 30 | with: 31 | useLockFile: false 32 | 33 | - name: ▶️ Deploy Storybook 34 | run: npm run deploy-storybook -- --ci 35 | env: 36 | GH_TOKEN: ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: validate 2 | on: 3 | # run on push but only for the main branch 4 | push: 5 | branches: 6 | - main 7 | # run for every pull request 8 | pull_request: {} 9 | jobs: 10 | main: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: ⬇️ Checkout repo 14 | uses: actions/checkout@v4 15 | with: 16 | ref: ${{github.event.pull_request.head.sha}} 17 | fetch-depth: 0 18 | 19 | - name: 🤝 Set Node version from .nvmrc 20 | run: echo NVMRC=`cat .nvmrc` >> $GITHUB_ENV 21 | 22 | - name: ⎔ Setup node 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: ${{ env.NVMRC }} 26 | 27 | - name: 📥 Download deps 28 | uses: bahmutov/npm-install@v1 29 | with: 30 | useLockFile: false 31 | 32 | - name: ▶️ Run validate script 33 | run: npm run validate 34 | 35 | - name: 👕 Lint commit messages 36 | uses: wagoid/commitlint-github-action@v4 37 | 38 | - name: ⬆️ Upload coverage report 39 | uses: codecov/codecov-action@v2 40 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | storybook-static 4 | 5 | node_modules 6 | .DS_Store 7 | yarn.lock 8 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4 3 | } 4 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: [ 3 | "../src/**/*.stories.mdx", 4 | "../src/**/*.stories.@(js|jsx|ts|tsx)", 5 | ], 6 | addons: [ 7 | "@storybook/addon-links", 8 | { 9 | name: "@storybook/addon-essentials", 10 | options: { 11 | controls: true, 12 | actions: true, 13 | }, 14 | }, 15 | ], 16 | typescript: { 17 | check: false, 18 | checkOptions: {}, 19 | reactDocgen: "react-docgen-typescript-plugin", 20 | reactDocgenTypescriptOptions: { 21 | // the Storybook docs need this to render the props table for 22 | compilerOptions: { 23 | allowSyntheticDefaultImports: false, 24 | esModuleInterop: false, 25 | }, 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.storybook/manager-head.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 112 | -------------------------------------------------------------------------------- /packages/react-paypal-js/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | actions: { argTypesRegex: "^on[A-Z].*" }, 3 | options: { 4 | storySort: { 5 | order: [ 6 | "PayPal", 7 | [ 8 | "PayPalButtons", 9 | "PayPalCardFields", 10 | "PayPalHostedFields", 11 | "PayPalHostedFieldsProvider", 12 | "PayPalMarks", 13 | "PayPalMessages", 14 | "Subscriptions", 15 | "VenmoButton", 16 | "PayPalScriptProvider", 17 | ], 18 | "Braintree", 19 | ], 20 | }, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/react-paypal-js/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to react-paypal-js 2 | 3 | We are always looking for ways to make our modules better. Adding features and fixing bugs allows everyone who depends 4 | on this code to create better, more stable applications. 5 | Feel free to raise a pull request to us. Our team would review your proposed modifications and, if appropriate, merge 6 | your changes into our code. Ideas and other comments are also welcome. 7 | 8 | ## Getting Started 9 | 10 | 1. Create your own [fork](https://help.github.com/articles/fork-a-repo) of this [repository](../../fork). 11 | 12 | ```bash 13 | # Clone it 14 | $ git clone git@github.com:me/react-paypal-js.git 15 | 16 | # Change directory 17 | $ cd react-paypal-js 18 | 19 | # Add the upstream repo 20 | $ git remote add upstream git@github.com:paypal/react-paypal-js.git 21 | 22 | # Get the latest upstream changes 23 | $ git pull upstream 24 | 25 | # Install dependencies 26 | $ npm install 27 | 28 | # Run scripts to verify installation 29 | $ npm run validate 30 | ``` 31 | 32 | ## Making Changes 33 | 34 | 1. Make sure that your changes adhere to the current coding conventions used throughout the project, indentation, accurate comments, etc. 35 | 2. Ensure existing tests pass (`$ npm test`) and include test cases which fail without your change and succeed with it. 36 | 37 | ## Submitting Changes 38 | 39 | 1. Ensure that no errors are generated by ESLint (run during `$ npm test`). 40 | 2. Commit your changes in logical chunks, i.e. keep your changes small per single commit. 41 | 3. Locally merge (or rebase) the upstream branch into your topic branch: `$ git pull upstream && git merge`. 42 | 4. Push your topic branch up to your fork: `$ git push origin `. 43 | 5. Open a [Pull Request](https://help.github.com/articles/using-pull-requests) with a clear title and description. 44 | 6. You should follow the [Angular.js commit message guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines) for both your commit messages and PR titles. 45 | 46 | If you have any questions about contributing, please feel free to contact us by posting your questions on GitHub. 47 | 48 | Copyright 2020-present, PayPal, Inc. under [the Apache 2.0 license](LICENSE.txt). 49 | -------------------------------------------------------------------------------- /packages/react-paypal-js/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | const isTest = api.env("test"); 3 | 4 | if (isTest) { 5 | return { 6 | presets: [ 7 | ["@babel/preset-react"], 8 | ["@babel/preset-env", { targets: { node: "current" } }], 9 | "@babel/preset-typescript", 10 | ], 11 | }; 12 | } 13 | 14 | return { 15 | presets: [ 16 | ["@babel/preset-react"], 17 | [ 18 | "@babel/preset-env", 19 | { 20 | targets: { node: "current" }, 21 | exclude: ["@babel/plugin-transform-typeof-symbol"], 22 | }, 23 | ], 24 | ], 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/react-paypal-js/commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/react-paypal-js/index.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === "production") { 2 | module.exports = require("./dist/cjs/react-paypal-js.min.js"); 3 | } else { 4 | module.exports = require("./dist/cjs/react-paypal-js.js"); 5 | } 6 | -------------------------------------------------------------------------------- /packages/react-paypal-js/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom"; 2 | -------------------------------------------------------------------------------- /packages/react-paypal-js/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base", ":preserveSemverRanges"], 3 | "prCreation": "immediate", 4 | "prHourlyLimit": 0, 5 | "rangeStrategy": "status-success", 6 | "separateMajorMinor": false, 7 | "semanticCommits": true, 8 | "timezone": "America/Los_Angeles", 9 | "rebaseStalePrs": true, 10 | "bumpVersion": "major", 11 | "labels": [":christmas_tree: dependencies"], 12 | "packageRules": [ 13 | { 14 | "packagePatterns": "^babel", 15 | "groupName": ["babel packages"] 16 | }, 17 | { 18 | "packagePatterns": "^eslint", 19 | "groupName": ["eslint packages"] 20 | }, 21 | { 22 | "packagePatterns": "^jest", 23 | "groupName": ["jest packages"] 24 | }, 25 | { 26 | "packagePatterns": "^rollup", 27 | "groupName": ["rollup packages"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/react-paypal-js/rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel, { getBabelOutputPlugin } from "@rollup/plugin-babel"; 2 | import { nodeResolve } from "@rollup/plugin-node-resolve"; 3 | import typescript from "@rollup/plugin-typescript"; 4 | import { terser } from "rollup-plugin-terser"; 5 | import cleanup from "rollup-plugin-cleanup"; 6 | 7 | import pkg from "./package.json"; 8 | 9 | const pkgName = pkg.name.split("@paypal/")[1]; 10 | const banner = getBannerText(); 11 | const tsconfigOverride = { 12 | exclude: ["node_modules", "**/*.test.ts*"], 13 | outDir: "./dist", 14 | target: "es5", 15 | }; 16 | 17 | export default [ 18 | // CommonJS 19 | { 20 | input: "src/index.ts", 21 | plugins: [ 22 | typescript({ 23 | tsconfig: "./tsconfig.lib.json", 24 | ...tsconfigOverride, 25 | }), 26 | nodeResolve(), 27 | babel({ 28 | babelHelpers: "bundled", 29 | presets: ["@babel/preset-react"], 30 | }), 31 | cleanup({ 32 | comments: "none", 33 | }), 34 | ], 35 | external: ["react"], 36 | output: [ 37 | { 38 | file: `dist/cjs/${pkgName}.js`, 39 | format: "cjs", 40 | globals: { 41 | react: "React", 42 | }, 43 | banner, 44 | }, 45 | { 46 | file: `dist/cjs/${pkgName}.min.js`, 47 | format: "cjs", 48 | globals: { 49 | react: "React", 50 | }, 51 | plugins: [terser()], 52 | banner, 53 | }, 54 | ], 55 | }, 56 | 57 | // ESM 58 | { 59 | input: "src/index.ts", 60 | plugins: [ 61 | typescript({ 62 | tsconfig: "./tsconfig.lib.json", 63 | ...tsconfigOverride, 64 | }), 65 | nodeResolve(), 66 | cleanup({ 67 | comments: "none", 68 | }), 69 | ], 70 | external: ["react"], 71 | output: [ 72 | { 73 | file: `dist/esm/${pkgName}.js`, 74 | format: "esm", 75 | globals: { 76 | react: "React", 77 | }, 78 | plugins: [getBabelOutputPlugin()], 79 | banner, 80 | }, 81 | { 82 | file: `dist/esm/${pkgName}.min.js`, 83 | format: "esm", 84 | globals: { 85 | react: "React", 86 | }, 87 | plugins: [getBabelOutputPlugin(), terser()], 88 | banner, 89 | }, 90 | ], 91 | }, 92 | ]; 93 | 94 | function getBannerText() { 95 | return ` 96 | /*! 97 | * ${pkgName} v${pkg.version} (${new Date().toISOString()}) 98 | * Copyright 2020-present, PayPal, Inc. All rights reserved. 99 | * 100 | * Licensed under the Apache License, Version 2.0 (the "License"); 101 | * you may not use this file except in compliance with the License. 102 | * You may obtain a copy of the License at 103 | * 104 | * https://www.apache.org/licenses/LICENSE-2.0 105 | * 106 | * Unless required by applicable law or agreed to in writing, software 107 | * distributed under the License is distributed on an "AS IS" BASIS, 108 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 109 | * See the License for the specific language governing permissions and 110 | * limitations under the License. 111 | */`.trim(); 112 | } 113 | -------------------------------------------------------------------------------- /packages/react-paypal-js/scripts/check-node-version.js: -------------------------------------------------------------------------------- 1 | const { readFileSync } = require("fs"); 2 | const { join } = require("path"); 3 | const semver = require("semver"); 4 | 5 | const expectedNodeVersion = readFileSync( 6 | join(__dirname, "../", ".nvmrc"), 7 | "utf-8", 8 | ); 9 | const isValidNodeVersion = semver.satisfies( 10 | process.version, 11 | expectedNodeVersion, 12 | ); 13 | 14 | // successfully exit when Node version is valid 15 | if (isValidNodeVersion) { 16 | return; 17 | } 18 | 19 | const output = ` 20 | node: ${process.version} 21 | Wanted node version ${expectedNodeVersion} 22 | `; 23 | 24 | console.error(output); 25 | process.exit(1); 26 | -------------------------------------------------------------------------------- /packages/react-paypal-js/src/components/PayPalMarks.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | 3 | import { usePayPalScriptReducer } from "../hooks/scriptProviderHooks"; 4 | import { getPayPalWindowNamespace, generateErrorMessage } from "../utils"; 5 | import { SDK_SETTINGS } from "../constants"; 6 | 7 | import type { FC, ReactNode } from "react"; 8 | import type { 9 | PayPalMarksComponentOptions, 10 | PayPalMarksComponent, 11 | } from "@paypal/paypal-js"; 12 | 13 | export interface PayPalMarksComponentProps extends PayPalMarksComponentOptions { 14 | /** 15 | * Pass a css class to the div container. 16 | */ 17 | className?: string; 18 | children?: ReactNode; 19 | } 20 | 21 | /** 22 | The `` component is used for conditionally rendering different payment options using radio buttons. 23 | The [Display PayPal Buttons with other Payment Methods guide](https://developer.paypal.com/docs/business/checkout/add-capabilities/buyer-experience/#display-paypal-buttons-with-other-payment-methods) describes this style of integration in detail. 24 | It relies on the `` parent component for managing state related to loading the JS SDK script. 25 | 26 | This component can also be configured to use a single funding source similar to the [standalone buttons](https://developer.paypal.com/docs/business/checkout/configure-payments/standalone-buttons/) approach. 27 | A `FUNDING` object is exported by this library which has a key for every available funding source option. 28 | */ 29 | export const PayPalMarks: FC = ({ 30 | className = "", 31 | children, 32 | ...markProps 33 | }: PayPalMarksComponentProps) => { 34 | const [{ isResolved, options }] = usePayPalScriptReducer(); 35 | const markContainerRef = useRef(null); 36 | const [isEligible, setIsEligible] = useState(true); 37 | const [, setErrorState] = useState(null); 38 | 39 | /** 40 | * Render PayPal Mark into the DOM 41 | */ 42 | const renderPayPalMark = (mark: PayPalMarksComponent) => { 43 | const { current } = markContainerRef; 44 | 45 | // only render the mark when eligible 46 | if (!current || !mark.isEligible()) { 47 | return setIsEligible(false); 48 | } 49 | // Remove any children before render it again 50 | if (current.firstChild) { 51 | current.removeChild(current.firstChild); 52 | } 53 | 54 | mark.render(current).catch((err) => { 55 | // component failed to render, possibly because it was closed or destroyed. 56 | if (current === null || current.children.length === 0) { 57 | // paypal marks container is no longer in the DOM, we can safely ignore the error 58 | return; 59 | } 60 | // paypal marks container is still in the DOM 61 | setErrorState(() => { 62 | throw new Error( 63 | `Failed to render component. ${err}`, 64 | ); 65 | }); 66 | }); 67 | }; 68 | 69 | useEffect(() => { 70 | // verify the sdk script has successfully loaded 71 | if (isResolved === false) { 72 | return; 73 | } 74 | 75 | const paypalWindowNamespace = getPayPalWindowNamespace( 76 | options[SDK_SETTINGS.DATA_NAMESPACE], 77 | ); 78 | 79 | // verify dependency on window object 80 | if ( 81 | paypalWindowNamespace === undefined || 82 | paypalWindowNamespace.Marks === undefined 83 | ) { 84 | return setErrorState(() => { 85 | throw new Error( 86 | generateErrorMessage({ 87 | reactComponentName: PayPalMarks.displayName as string, 88 | sdkComponentKey: "marks", 89 | sdkRequestedComponents: options.components, 90 | sdkDataNamespace: options[SDK_SETTINGS.DATA_NAMESPACE], 91 | }), 92 | ); 93 | }); 94 | } 95 | 96 | renderPayPalMark(paypalWindowNamespace.Marks({ ...markProps })); 97 | // eslint-disable-next-line react-hooks/exhaustive-deps 98 | }, [isResolved, markProps.fundingSource]); 99 | 100 | return ( 101 | <> 102 | {isEligible ? ( 103 |
104 | ) : ( 105 | children 106 | )} 107 | 108 | ); 109 | }; 110 | 111 | PayPalMarks.displayName = "PayPalMarks"; 112 | -------------------------------------------------------------------------------- /packages/react-paypal-js/src/components/PayPalMessages.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | 3 | import { usePayPalScriptReducer } from "../hooks/scriptProviderHooks"; 4 | import { getPayPalWindowNamespace, generateErrorMessage } from "../utils"; 5 | import { SDK_SETTINGS } from "../constants"; 6 | 7 | import type { FC } from "react"; 8 | import type { 9 | PayPalMessagesComponentOptions, 10 | PayPalMessagesComponent, 11 | } from "@paypal/paypal-js"; 12 | 13 | export interface PayPalMessagesComponentProps 14 | extends PayPalMessagesComponentOptions { 15 | forceReRender?: unknown[]; 16 | className?: string; 17 | } 18 | 19 | /** 20 | This `` messages component renders a credit messaging on upstream merchant sites. 21 | It relies on the `` parent component for managing state related to loading the JS SDK script. 22 | */ 23 | export const PayPalMessages: FC = ({ 24 | className = "", 25 | forceReRender = [], 26 | ...messageProps 27 | }: PayPalMessagesComponentProps) => { 28 | const [{ isResolved, options }] = usePayPalScriptReducer(); 29 | const messagesContainerRef = useRef(null); 30 | const messages = useRef(null); 31 | const [, setErrorState] = useState(null); 32 | 33 | useEffect(() => { 34 | // verify the sdk script has successfully loaded 35 | if (isResolved === false) { 36 | return; 37 | } 38 | 39 | const paypalWindowNamespace = getPayPalWindowNamespace( 40 | options[SDK_SETTINGS.DATA_NAMESPACE], 41 | ); 42 | 43 | // verify dependency on window object 44 | if ( 45 | paypalWindowNamespace === undefined || 46 | paypalWindowNamespace.Messages === undefined 47 | ) { 48 | return setErrorState(() => { 49 | throw new Error( 50 | generateErrorMessage({ 51 | reactComponentName: 52 | PayPalMessages.displayName as string, 53 | sdkComponentKey: "messages", 54 | sdkRequestedComponents: options.components, 55 | sdkDataNamespace: options[SDK_SETTINGS.DATA_NAMESPACE], 56 | }), 57 | ); 58 | }); 59 | } 60 | 61 | messages.current = paypalWindowNamespace.Messages({ 62 | ...messageProps, 63 | }); 64 | 65 | messages.current 66 | .render(messagesContainerRef.current as HTMLDivElement) 67 | .catch((err) => { 68 | // component failed to render, possibly because it was closed or destroyed. 69 | if ( 70 | messagesContainerRef.current === null || 71 | messagesContainerRef.current.children.length === 0 72 | ) { 73 | // paypal messages container is no longer in the DOM, we can safely ignore the error 74 | return; 75 | } 76 | // paypal messages container is still in the DOM 77 | setErrorState(() => { 78 | throw new Error( 79 | `Failed to render component. ${err}`, 80 | ); 81 | }); 82 | }); 83 | // eslint-disable-next-line react-hooks/exhaustive-deps 84 | }, [isResolved, ...forceReRender]); 85 | 86 | return
; 87 | }; 88 | 89 | PayPalMessages.displayName = "PayPalMessages"; 90 | -------------------------------------------------------------------------------- /packages/react-paypal-js/src/components/PayPalScriptProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useReducer } from "react"; 2 | import { loadScript } from "@paypal/paypal-js"; 3 | 4 | import { 5 | getScriptID, 6 | ScriptContext, 7 | scriptReducer, 8 | } from "../context/scriptProviderContext"; 9 | import { SCRIPT_ID, SDK_SETTINGS, LOAD_SCRIPT_ERROR } from "../constants"; 10 | import { SCRIPT_LOADING_STATE, DISPATCH_ACTION } from "../types"; 11 | 12 | import type { FC } from "react"; 13 | import type { ScriptProviderProps } from "../types"; 14 | 15 | /** 16 | This `` component takes care of loading the JS SDK `