├── .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 |
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 `