├── .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 ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .npmrc ├── .nvmrc ├── .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 ├── lint-staged.config.js ├── package-lock.json ├── 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 ├── 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 /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | scripts/* 3 | storybook-static/* 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Owner for everything in the repo 2 | * @paypal/checkout-sdk -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | storybook-static 4 | 5 | node_modules 6 | .DS_Store 7 | yarn.lock 8 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4 3 | } 4 | -------------------------------------------------------------------------------- /.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", 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 | -------------------------------------------------------------------------------- /.storybook/manager-head.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 109 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MOVED 2 | 3 | This repo has been moved to the paypal-js monorepo: https://github.com/paypal/paypal-js 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | }; 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "**/*.ts?(x)": (filenames) => [ 3 | "npm run typecheck", 4 | `prettier --write ${filenames.join(" ")}`, 5 | `eslint ${filenames.join(" ")}`, 6 | ], 7 | "**/*.js": (filenames) => [ 8 | `prettier --write ${filenames.join(" ")}`, 9 | `eslint ${filenames.join(" ")}`, 10 | ], 11 | "**/*.{json,css,html,md,yml}": (filenames) => 12 | `prettier --write ${filenames.join(" ")}`, 13 | }; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@paypal/react-paypal-js", 3 | "version": "8.4.0", 4 | "description": "React components for the PayPal JS SDK", 5 | "keywords": [ 6 | "react", 7 | "component", 8 | "paypal", 9 | "button", 10 | "checkout", 11 | "payment", 12 | "paypal javascript sdk", 13 | "paypal smart buttons" 14 | ], 15 | "main": "index.js", 16 | "module": "dist/esm/react-paypal-js.js", 17 | "types": "dist/types/index.d.ts", 18 | "scripts": { 19 | "build": "rollup --config", 20 | "check-node-version": "node scripts/check-node-version.js", 21 | "format": "prettier --write .", 22 | "format:check": "prettier --check .", 23 | "lint": "eslint .", 24 | "prepare": "husky install", 25 | "prepack": "rimraf dist && npm run build && npm run type-declarations", 26 | "test": "jest --env=jsdom", 27 | "start": "npm run check-node-version && npm run storybook", 28 | "storybook": "start-storybook -p 6006", 29 | "build-storybook": "build-storybook", 30 | "deploy-storybook": "storybook-to-ghpages", 31 | "prerelease": "npm run validate", 32 | "release": "standard-version", 33 | "postrelease": "git push && git push --follow-tags && npm run build && npm publish", 34 | "typecheck": "tsc --noEmit", 35 | "type-declarations": "tsc --emitDeclarationOnly --outDir dist/types --project tsconfig.declarations.json", 36 | "validate": "npm run check-node-version && npm run format:check && npm run typecheck && npm run build && npm run lint && npm test -- --coverage" 37 | }, 38 | "files": [ 39 | "dist" 40 | ], 41 | "license": "Apache-2.0", 42 | "repository": { 43 | "type": "git", 44 | "url": "git://github.com/paypal/react-paypal-js.git" 45 | }, 46 | "homepage": "https://paypal.github.io/react-paypal-js/", 47 | "dependencies": { 48 | "@paypal/paypal-js": "^8.1.0", 49 | "@paypal/sdk-constants": "^1.0.122" 50 | }, 51 | "devDependencies": { 52 | "@babel/core": "^7.17.5", 53 | "@babel/preset-env": "^7.16.11", 54 | "@babel/preset-react": "^7.16.7", 55 | "@babel/preset-typescript": "^7.16.7", 56 | "@codesandbox/sandpack-react": "^0.14.0", 57 | "@commitlint/cli": "^16.2.1", 58 | "@commitlint/config-conventional": "^16.2.1", 59 | "@rollup/plugin-babel": "^5.3.0", 60 | "@rollup/plugin-node-resolve": "^13.1.3", 61 | "@rollup/plugin-replace": "^3.1.0", 62 | "@rollup/plugin-typescript": "^8.3.0", 63 | "@storybook/addon-actions": "^6.4.9", 64 | "@storybook/addon-docs": "^6.4.9", 65 | "@storybook/addon-essentials": "^6.4.9", 66 | "@storybook/addon-links": "^6.4.9", 67 | "@storybook/react": "^6.4.9", 68 | "@storybook/storybook-deployer": "^2.8.10", 69 | "@testing-library/react": "^12.1.3", 70 | "@testing-library/react-hooks": "^7.0.2", 71 | "@types/jest": "^27.4.0", 72 | "@types/react": "^17.0.39", 73 | "@types/react-dom": "^17.0.11", 74 | "@types/string-template": "^1.0.2", 75 | "@typescript-eslint/eslint-plugin": "^5.12.0", 76 | "@typescript-eslint/parser": "^5.12.0", 77 | "babel-jest": "^27.5.1", 78 | "babel-loader": "^8.2.3", 79 | "eslint": "^8.9.0", 80 | "eslint-config-prettier": "^8.4.0", 81 | "eslint-plugin-import": "^2.25.4", 82 | "eslint-plugin-react": "^7.28.0", 83 | "eslint-plugin-react-hooks": "^4.3.0", 84 | "husky": "^7.0.4", 85 | "jest": "^27.5.1", 86 | "jest-mock-extended": "^2.0.4", 87 | "lint-staged": "^12.3.4", 88 | "prettier": "^2.5.1", 89 | "react": "^16.14.0", 90 | "react-dom": "^16.14.0", 91 | "react-element-to-jsx-string": "^14.3.4", 92 | "react-error-boundary": "^3.1.4", 93 | "react-is": "^17.0.2", 94 | "rimraf": "^3.0.2", 95 | "rollup": "^2.67.3", 96 | "rollup-plugin-cleanup": "^3.2.1", 97 | "rollup-plugin-terser": "^7.0.2", 98 | "semver": "^7.3.5", 99 | "standard-version": "^9.3.2", 100 | "string-template": "^1.0.0", 101 | "typescript": "^4.7.2" 102 | }, 103 | "peerDependencies": { 104 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0", 105 | "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" 106 | }, 107 | "jest": { 108 | "transformIgnorePatterns": [ 109 | "/!node_modules\\/@paypal\\/sdk-constants/" 110 | ] 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | target: "es5", 14 | }; 15 | 16 | export default [ 17 | // CommonJS 18 | { 19 | input: "src/index.ts", 20 | plugins: [ 21 | typescript({ 22 | ...tsconfigOverride, 23 | }), 24 | nodeResolve(), 25 | babel({ 26 | babelHelpers: "bundled", 27 | presets: ["@babel/preset-react"], 28 | }), 29 | cleanup({ 30 | comments: "none", 31 | }), 32 | ], 33 | external: ["react"], 34 | output: [ 35 | { 36 | file: `dist/cjs/${pkgName}.js`, 37 | format: "cjs", 38 | globals: { 39 | react: "React", 40 | }, 41 | banner, 42 | }, 43 | { 44 | file: `dist/cjs/${pkgName}.min.js`, 45 | format: "cjs", 46 | globals: { 47 | react: "React", 48 | }, 49 | plugins: [terser()], 50 | banner, 51 | }, 52 | ], 53 | }, 54 | 55 | // ESM 56 | { 57 | input: "src/index.ts", 58 | plugins: [ 59 | typescript({ ...tsconfigOverride }), 60 | nodeResolve(), 61 | cleanup({ 62 | comments: "none", 63 | }), 64 | ], 65 | external: ["react"], 66 | output: [ 67 | { 68 | file: `dist/esm/${pkgName}.js`, 69 | format: "esm", 70 | globals: { 71 | react: "React", 72 | }, 73 | plugins: [getBabelOutputPlugin()], 74 | banner, 75 | }, 76 | { 77 | file: `dist/esm/${pkgName}.min.js`, 78 | format: "esm", 79 | globals: { 80 | react: "React", 81 | }, 82 | plugins: [getBabelOutputPlugin(), terser()], 83 | banner, 84 | }, 85 | ], 86 | }, 87 | ]; 88 | 89 | function getBannerText() { 90 | return ` 91 | /*! 92 | * ${pkgName} v${pkg.version} (${new Date().toISOString()}) 93 | * Copyright 2020-present, PayPal, Inc. All rights reserved. 94 | * 95 | * Licensed under the Apache License, Version 2.0 (the "License"); 96 | * you may not use this file except in compliance with the License. 97 | * You may obtain a copy of the License at 98 | * 99 | * https://www.apache.org/licenses/LICENSE-2.0 100 | * 101 | * Unless required by applicable law or agreed to in writing, software 102 | * distributed under the License is distributed on an "AS IS" BASIS, 103 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 104 | * See the License for the specific language governing permissions and 105 | * limitations under the License. 106 | */`.trim(); 107 | } 108 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/PayPalButtons.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 { FunctionComponent } from "react"; 8 | import type { PayPalButtonsComponent, OnInitActions } from "@paypal/paypal-js"; 9 | import type { PayPalButtonsComponentProps } from "../types"; 10 | 11 | /** 12 | This `` component supports rendering [buttons](https://developer.paypal.com/docs/business/javascript-sdk/javascript-sdk-reference/#buttons) for PayPal, Venmo, and alternative payment methods. 13 | It relies on the `` parent component for managing state related to loading the JS SDK script. 14 | */ 15 | export const PayPalButtons: FunctionComponent = ({ 16 | className = "", 17 | disabled = false, 18 | children, 19 | forceReRender = [], 20 | ...buttonProps 21 | }: PayPalButtonsComponentProps) => { 22 | const isDisabledStyle = disabled ? { opacity: 0.38 } : {}; 23 | const classNames = `${className} ${ 24 | disabled ? "paypal-buttons-disabled" : "" 25 | }`.trim(); 26 | const buttonsContainerRef = useRef(null); 27 | const buttons = useRef(null); 28 | 29 | const [{ isResolved, options }] = usePayPalScriptReducer(); 30 | const [initActions, setInitActions] = useState(null); 31 | const [isEligible, setIsEligible] = useState(true); 32 | const [, setErrorState] = useState(null); 33 | 34 | function closeButtonsComponent() { 35 | if (buttons.current !== null) { 36 | buttons.current.close().catch(() => { 37 | // ignore errors when closing the component 38 | }); 39 | } 40 | } 41 | 42 | // useEffect hook for rendering the buttons 43 | useEffect(() => { 44 | // verify the sdk script has successfully loaded 45 | if (isResolved === false) { 46 | return closeButtonsComponent; 47 | } 48 | 49 | const paypalWindowNamespace = getPayPalWindowNamespace( 50 | options.dataNamespace 51 | ); 52 | 53 | // verify dependency on window object 54 | if ( 55 | paypalWindowNamespace === undefined || 56 | paypalWindowNamespace.Buttons === undefined 57 | ) { 58 | setErrorState(() => { 59 | throw new Error( 60 | generateErrorMessage({ 61 | reactComponentName: PayPalButtons.displayName as string, 62 | sdkComponentKey: "buttons", 63 | sdkRequestedComponents: options.components, 64 | sdkDataNamespace: options[SDK_SETTINGS.DATA_NAMESPACE], 65 | }) 66 | ); 67 | }); 68 | return closeButtonsComponent; 69 | } 70 | 71 | const decoratedOnInit = ( 72 | data: Record, 73 | actions: OnInitActions 74 | ) => { 75 | setInitActions(actions); 76 | if (typeof buttonProps.onInit === "function") { 77 | buttonProps.onInit(data, actions); 78 | } 79 | }; 80 | 81 | try { 82 | buttons.current = paypalWindowNamespace.Buttons({ 83 | ...buttonProps, 84 | onInit: decoratedOnInit, 85 | }); 86 | } catch (err) { 87 | return setErrorState(() => { 88 | throw new Error( 89 | `Failed to render component. Failed to initialize: ${err}` 90 | ); 91 | }); 92 | } 93 | 94 | // only render the button when eligible 95 | if (buttons.current.isEligible() === false) { 96 | setIsEligible(false); 97 | return closeButtonsComponent; 98 | } 99 | 100 | if (!buttonsContainerRef.current) { 101 | return closeButtonsComponent; 102 | } 103 | 104 | buttons.current.render(buttonsContainerRef.current).catch((err) => { 105 | // component failed to render, possibly because it was closed or destroyed. 106 | if ( 107 | buttonsContainerRef.current === null || 108 | buttonsContainerRef.current.children.length === 0 109 | ) { 110 | // paypal buttons container is no longer in the DOM, we can safely ignore the error 111 | return; 112 | } 113 | // paypal buttons container is still in the DOM 114 | setErrorState(() => { 115 | throw new Error( 116 | `Failed to render component. ${err}` 117 | ); 118 | }); 119 | }); 120 | 121 | return closeButtonsComponent; 122 | // eslint-disable-next-line react-hooks/exhaustive-deps 123 | }, [ 124 | isResolved, 125 | ...forceReRender, 126 | buttonProps.fundingSource, 127 | buttonProps.message, 128 | ]); 129 | 130 | // useEffect hook for managing disabled state 131 | useEffect(() => { 132 | if (initActions === null) { 133 | return; 134 | } 135 | 136 | if (disabled === true) { 137 | initActions.disable().catch(() => { 138 | // ignore errors when disabling the component 139 | }); 140 | } else { 141 | initActions.enable().catch(() => { 142 | // ignore errors when enabling the component 143 | }); 144 | } 145 | }, [disabled, initActions]); 146 | 147 | return ( 148 | <> 149 | {isEligible ? ( 150 |
155 | ) : ( 156 | children 157 | )} 158 | 159 | ); 160 | }; 161 | 162 | PayPalButtons.displayName = "PayPalButtons"; 163 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 `