├── .circleci
└── config.yml
├── .gitignore
├── .npmignore
├── README.md
├── babel.config.js
├── example
├── Example.tsx
├── SubscriptionExample.tsx
├── index.html
├── index.tsx
├── style.css
└── webpack.config.js
├── jest.config.js
├── package-lock.json
├── package.json
├── paypalImage.png
├── src
├── ErrorBoundary.tsx
├── PayPalButton.tsx
├── PayPalButtonBase.tsx
├── index.ts
├── setupTests.ts
├── types.ts
└── utils
│ ├── __snapshots__
│ ├── composeUrl.test.ts.snap
│ ├── usePaypalMethods.test.ts.snap
│ └── usePaypalScript.test.ts.snap
│ ├── composeUrl.test.ts
│ ├── composeUrl.ts
│ ├── constants.ts
│ ├── index.ts
│ ├── usePaypalMethods.test.ts
│ ├── usePaypalMethods.ts
│ ├── usePaypalScript.test.ts
│ ├── usePaypalScript.ts
│ └── usePaypalScriptOptions.ts
├── tsconfig.json
├── tslint.json
└── webpack.config.js
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | _common:
3 | node-docker: &node-docker
4 | - image: circleci/node:10.13.0
5 | restore-cache: &restore-cache
6 | keys:
7 | - bootstrap-v1-{{ .Branch }}-{{ .Revision }}
8 | attach-workspace: &attach-workspace
9 | at: .
10 |
11 | jobs:
12 | bootstrap:
13 | docker: *node-docker
14 | steps:
15 | - restore_cache: *restore-cache
16 | - checkout
17 | - setup_remote_docker:
18 | docker_layer_caching: false
19 | - run:
20 | name: Install deps
21 | command: npm install
22 | - save_cache:
23 | key: bootstrap-v1-{{ .Branch }}-{{ .Revision }}
24 | paths:
25 | - ~/.
26 |
27 | test:
28 | docker: *node-docker
29 | steps:
30 | - restore_cache: *restore-cache
31 | - checkout
32 | - run:
33 | name: Install deps
34 | command: npm install
35 | - run:
36 | name: Run tests
37 | command: npm run test:once
38 |
39 | workflows:
40 | version: 2
41 | build-and-deploy:
42 | jobs:
43 | - bootstrap
44 | - test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | example/dist
3 | .env
4 | example/.env
5 | bin/
6 | react-paypal-button-3.2.0.tgz
7 |
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | example
3 | .env
4 | jest.config.js
5 | paypalImage.png
6 | webpack.config.js
7 | tsconfig.json
8 | tsconfig.test.json
9 | tslint.json
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React-Paypal-Button
2 |
3 | This repository is DEPRECATED. Please see [PayPal's supported solution](https://www.npmjs.com/package/@paypal/react-paypal-js)
4 |
5 |
6 |
7 |
8 |
9 | [](https://badge.fury.io/js/react-paypal-button)
10 | [](https://circleci.com/gh/andrewangelle/react-paypal-button)
11 |
12 |
13 |
14 | A button component to implement PayPal's Express Checkout in React
15 |
16 |
17 | ## Prerequisites
18 |
19 | * To use PayPal's Express Checkout you must have a PayPal Business account set up and verified. After this is done, you'll have access to your API credentials to use with this button. Once you have your account set up you will have 2 different sets of credentials for sandbox mode and prouduction mode. Both will have a clientID, this is what you will use to pass to `paypalOptions`.
20 |
21 | * Because the internals of this library use hooks, npm version `4.x.x` and above requires a peer dependency of `react-v16.8.x` `react-dom-v16.8.x`.
22 |
23 | ## Installation
24 |
25 | ```sh
26 | $ npm install react-paypal-button --save
27 | ```
28 |
29 | ## Usage
30 |
31 | ```javascript
32 | import { PayPalButton } from 'react-paypal-button'
33 |
34 | export default function App() {
35 | const paypalOptions = {
36 | clientId: '12345',
37 | intent: 'capture'
38 | }
39 |
40 | const buttonStyles = {
41 | layout: 'vertical',
42 | shape: 'rect',
43 | }
44 | return (
45 |
50 | )
51 | }
52 | ```
53 |
54 | ### Types
55 |
56 | * All relevant types are bundled and exported with the npm package
57 |
58 | ```typescript
59 |
60 | type PayPalButtonProps = {
61 | paypalOptions: PaypalOptions;
62 | buttonStyles: ButtonStylingOptions;
63 | amount: number;
64 | subscriptionPlanId?: string;
65 | onApprove?: (data, authId) => void;
66 | onPaymentStart?: () => void;
67 | onPaymentSuccess?: (response: OnCaptureData) => void;
68 | onPaymentError?: (msg: string) => void;
69 | onPaymentCancel?: (data: OnCancelData) => void;
70 | onShippingChange?: (data: OnShippingChangeData) =>
71 | Promise |
72 | string |
73 | number |
74 | void;
75 | }
76 |
77 | ```
78 |
79 | * See [list and documentation on styling options](https://developer.paypal.com/docs/checkout/integration-features/customize-button/#color) that are to be passed to `buttonStyles` prop
80 |
81 | * See [list and documentation on values](https://developer.paypal.com/docs/checkout/reference/customize-sdk/#query-parameters) that are to be passed to `paypalOptions`prop
82 |
83 | * See examples folder for more examples
84 |
85 | ## Development
86 |
87 | Install dependencies:
88 |
89 | ```
90 | $ npm install
91 | ```
92 |
93 | Run the example app at [http://localhost:8008](http://localhost:8008):
94 |
95 | ```
96 | $ npm start
97 | ```
98 |
99 | Generate UMD output in the `bin` folder:
100 |
101 | ```
102 | $ npm run build
103 | ```
104 |
105 | Run tests in watch mode:
106 |
107 | ```
108 | $ npm test
109 | ```
110 |
111 | Perform a single run of tests:
112 |
113 | ```
114 | $ npm run test:once
115 | ```
116 |
117 | ## License
118 |
119 | MIT
120 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@babel/preset-env',
4 | '@babel/preset-react',
5 | '@babel/preset-typescript',
6 | ],
7 | plugins: [
8 | '@babel/plugin-transform-runtime',
9 | '@babel/plugin-proposal-class-properties',
10 | '@babel/plugin-proposal-object-rest-spread'
11 | ],
12 | };
13 |
--------------------------------------------------------------------------------
/example/Example.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PayPalButton, PaypalOptions, ButtonStylingOptions } from '../src';
3 |
4 | const wrapperStyles: React.CSSProperties = {
5 | textAlign: 'center',
6 | padding: '5rem',
7 | width: '30%',
8 | margin: '5rem auto'
9 | }
10 |
11 | const paypalOptions: PaypalOptions = {
12 | clientId: process.env.PAYPAL_CLIENT_ID!,
13 | intent:'capture',
14 | currency:'USD',
15 | };
16 |
17 | const buttonStyles: ButtonStylingOptions = {
18 | layout: 'vertical',
19 | shape: 'rect',
20 | label: 'checkout',
21 | tagline: false
22 | }
23 |
24 | export function Example() {
25 | return (
26 |
27 |
console.log('onApprove', data, authId)}
32 | onPaymentStart={() => console.log('onPaymentStart')}
33 | onPaymentSuccess={data => console.log('onPaymentSuccess', data)}
34 | onPaymentError={msg => console.log('payment error', msg)}
35 | onPaymentCancel={data => console.log(data)}
36 | onShippingChange={data => console.log('onShippingChange', data)}
37 | />
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/example/SubscriptionExample.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PayPalButton, PaypalOptions, ButtonStylingOptions } from '../src';
3 |
4 | const wrapperStyles: React.CSSProperties = {
5 | textAlign: 'center',
6 | padding: '5rem',
7 | width: '30%',
8 | margin: '5rem auto'
9 | }
10 |
11 | const paypalOptions: PaypalOptions = {
12 | clientId: process.env.PAYPAL_CLIENT_ID!,
13 | currency:'USD',
14 | vault: true // required
15 | };
16 |
17 | const buttonStyles: ButtonStylingOptions = {
18 | label: 'installment',
19 | }
20 |
21 | // To use this button with subscriptions the paypal account that accepts payments
22 | // must already be setup to handle subscription plans and recurring payments
23 | // Checkout paypal developer docs for more info
24 |
25 | export function SubscriptionExample() {
26 | return (
27 |
28 |
console.log('onPaymentSuccess', data)}
34 | />
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | paypal-button
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/example/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { Example } from './Example';
4 | import './style.css';
5 |
6 | render(
7 | ,
8 | document.getElementById('root')
9 | );
10 |
--------------------------------------------------------------------------------
/example/style.css:
--------------------------------------------------------------------------------
1 | /* styles here */
2 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 | const libraryName = 'reactGlide';
5 | const DotEnv = require('dotenv-webpack')
6 |
7 | const config = {
8 | entry: path.join(__dirname),
9 | output: {
10 | path: path.join(__dirname, 'dist'),
11 | publicPath: '/'
12 | },
13 | optimization: {
14 | splitChunks: {
15 | cacheGroups: {
16 | styles: {
17 | name: 'styles',
18 | test: /\.css$/,
19 | chunks: 'all',
20 | enforce: true
21 | }
22 | }
23 | },
24 | },
25 | module: {
26 | rules: [
27 | {
28 | test: /^(?!.*test\.tsx|\.ts?$).*\.tsx|\.ts?$/,
29 | exclude: /node_modules/,
30 | use: ['babel-loader']
31 | },
32 | {
33 | test: /\.css/,
34 | exclude: /node_modules/,
35 | use: [
36 | 'style-loader',
37 | 'css-loader',
38 | ]
39 | },
40 | {
41 | test: /^(?!.*test\.tsx|\.ts?$).*\.tsx|\.ts?$/,
42 | exclude: /node_modules/,
43 | use: ["source-map-loader"],
44 | enforce: "pre"
45 | },
46 | ]
47 | },
48 | resolve: {
49 | extensions: ['.js', '.tsx', '.css', '.ts', '.jsx'],
50 | },
51 | devServer: {
52 | contentBase: 'dist',
53 | port: 8008,
54 | open: true,
55 | host: 'localhost',
56 | hot: true
57 | },
58 | plugins: [
59 | new webpack.HotModuleReplacementPlugin(),
60 | new HtmlWebpackPlugin({
61 | template: 'example/index.html',
62 | filename: 'index.html'
63 | }),
64 | new DotEnv({
65 | path: '../.env'
66 | })
67 | ]
68 | };
69 |
70 | module.exports = config;
71 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | collectCoverageFrom: [
4 | "src/**/*.{tsx}"
5 | ],
6 | transform: {
7 | '^.+\\.ts?$': 'babel-jest',
8 | '^.+\\.tsx?$': 'babel-jest',
9 | },
10 | roots: [
11 | "/src"
12 | ],
13 | globals: {
14 | "ts-jest": {
15 | "babelConfig": true
16 | },
17 | },
18 | transformIgnorePatterns: ['/node_modules/'],
19 | snapshotSerializers: ["enzyme-to-json/serializer"],
20 | moduleFileExtensions: [
21 | "ts",
22 | "tsx",
23 | "js",
24 | "jsx",
25 | "json",
26 | "node"
27 | ],
28 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-paypal-button",
3 | "version": "4.1.1",
4 | "description": "a button component to implement PayPal's Express Checkout in React",
5 | "author": "andrewangelle",
6 | "email": "andrewangelle@gmail.com",
7 | "repository": "http://github.com/andrewangelle/react-paypal-button.git",
8 | "license": "MIT",
9 | "main": "bin/index.min.js",
10 | "types": "bin/types/src/index.d.ts",
11 | "scripts": {
12 | "build": "rm -rf bin && tsc --emitDeclarationOnly && webpack --config webpack.config.js",
13 | "start": "webpack-dev-server --config ./example/webpack.config.js --mode development --open --hot",
14 | "test": "jest --watchAll",
15 | "test:once": "jest",
16 | "coverage": "jest --coverage && cat ./coverage/lcov.info | coveralls",
17 | "preversion": "rm -rf bin && tsc --emitDeclarationOnly && webpack --config webpack.config.js",
18 | "postversion": "git push --follow-tags origin master"
19 | },
20 | "files": [
21 | "bin",
22 | "bin/types"
23 | ],
24 | "keywords": [
25 | "react",
26 | "component"
27 | ],
28 | "peerDependencies": {
29 | "react": "^16.8.x",
30 | "react-dom": "^16.8.x"
31 | },
32 | "resolutions": {
33 | "babel-core": "7.0.0-bridge.0"
34 | },
35 | "devDependencies": {
36 | "@babel/cli": "^7.5.5",
37 | "@babel/core": "^7.5.5",
38 | "@babel/plugin-proposal-class-properties": "^7.5.5",
39 | "@babel/plugin-proposal-object-rest-spread": "^7.5.5",
40 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
41 | "@babel/plugin-transform-runtime": "^7.5.5",
42 | "@babel/polyfill": "^7.4.4",
43 | "@babel/preset-env": "^7.5.5",
44 | "@babel/preset-react": "^7.0.0",
45 | "@babel/preset-typescript": "^7.3.3",
46 | "@babel/runtime": "^7.5.5",
47 | "@testing-library/jest-dom": "^4.1.0",
48 | "@testing-library/react": "^9.1.3",
49 | "@testing-library/react-hooks": "^2.0.1",
50 | "@types/enzyme": "^3.10.3",
51 | "@types/enzyme-adapter-react-16": "^1.0.4",
52 | "@types/jest": "^23.3.14",
53 | "@types/node": "^11.13.20",
54 | "@types/react": "^16.9.2",
55 | "@types/react-dom": "^16.9.0",
56 | "@types/testing-library__react": "^9.1.1",
57 | "@types/webpack-env": "^1.14.0",
58 | "babel-core": "^7.0.0-bridge.0",
59 | "babel-jest": "^25.0.0",
60 | "babel-loader": "^8.0.6",
61 | "cross-env": "^5.2.0",
62 | "css-loader": "^2.1.0",
63 | "dotenv": "^8.1.0",
64 | "dotenv-webpack": "^1.7.0",
65 | "enzyme": "^3.10.0",
66 | "enzyme-adapter-react-16": "^1.14.0",
67 | "enzyme-to-json": "^3.4.0",
68 | "html-webpack-plugin": "^3.2.0",
69 | "jest": "^25.0.0",
70 | "mini-css-extract-plugin": "^0.5.0",
71 | "optimize-css-assets-webpack-plugin": "^5.0.3",
72 | "path": "^0.12.7",
73 | "react": "^16.9.0",
74 | "react-dom": "^16.9.0",
75 | "source-map-loader": "^0.2.4",
76 | "style-loader": "^0.23.1",
77 | "ts-jest": "^23.10.5",
78 | "ts-loader": "^5.4.5",
79 | "tslint": "^5.19.0",
80 | "tslint-react": "^3.6.0",
81 | "typescript": "^3.5.3",
82 | "uglifyjs-webpack-plugin": "^2.2.0",
83 | "webpack": "^4.39.3",
84 | "webpack-cli": "^3.3.7",
85 | "webpack-dev-server": "^3.8.0"
86 | },
87 | "dependencies": {}
88 | }
89 |
--------------------------------------------------------------------------------
/paypalImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewangelle/react-paypal-button/a56678a647ab77f42fbd6845833cf7f9be36c215/paypalImage.png
--------------------------------------------------------------------------------
/src/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class ErrorBoundary extends React.Component<{}, {hasError: boolean}> {
4 | state={
5 | hasError: false
6 | }
7 | componentDidCatch(){
8 | this.setError()
9 | }
10 | setError = () => {
11 | this.setState({hasError: true})
12 | }
13 | render() {
14 | if(this.state.hasError){
15 | return null
16 | } else {
17 | return this.props.children
18 | }
19 | }
20 | }
21 |
22 | export default ErrorBoundary
23 |
--------------------------------------------------------------------------------
/src/PayPalButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import PayPalButtonBase from './PayPalButtonBase';
4 | import ErrorBoundary from './ErrorBoundary';
5 | import { PayPalButtonProps } from './types';
6 |
7 |
8 | function PayPalButton(props: PayPalButtonProps){
9 | return (
10 |
11 |
12 |
13 | )
14 | }
15 |
16 | export default PayPalButton
17 |
--------------------------------------------------------------------------------
/src/PayPalButtonBase.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 |
3 | import { usePaypalScriptOptions, usePaypalScript, scriptLoadError } from './utils';
4 | import { PayPalButtonProps } from './types';
5 |
6 | function PayPalButtonBase(props: PayPalButtonProps) {
7 | const { loading, done } = usePaypalScript(props.paypalOptions);
8 | const options = usePaypalScriptOptions(props);
9 |
10 | useEffect(() => {
11 | const hasWindow = window !== undefined && window.paypal !== undefined;
12 |
13 | if(hasWindow) {// check to support SSR
14 | if(!loading && done){
15 | try {
16 | window.paypal.Buttons(options).render('#paypal-button');
17 | } catch (e){
18 | console.error(scriptLoadError)
19 | }
20 | }
21 |
22 | }
23 | },[ loading, done ])
24 |
25 | return
26 | }
27 |
28 | export default PayPalButtonBase
29 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { default as PayPalButton } from './PayPalButton';
2 | export * from './types'
3 |
4 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | import { configure, shallow, mount, render } from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16'
3 |
4 | configure({ adapter: new Adapter() });
5 |
6 | export { shallow, mount, render };
7 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 |
2 | declare global {
3 | interface Window {
4 | paypal: any
5 | }
6 | }
7 |
8 | export type OnCancelData = {
9 | billingID: string;
10 | cancelUrl: string;
11 | intent: string;
12 | paymentID: string;
13 | paymentToken: string;
14 | }
15 |
16 | export type OnShippingChangeData = {
17 | amount: {
18 | value: string,
19 | currency_code: string,
20 | breakdown: {}
21 | },
22 | orderID: string;
23 | paymentID: string;
24 | paymentToken: string;
25 | shipping_address: {
26 | city: string;
27 | country_code: string;
28 | postal_code: string;
29 | state: string;
30 | }
31 | }
32 |
33 | export type OnApproveData = {
34 | orderID: string,
35 | payerID: string
36 | };
37 |
38 | export type OnCaptureData = {
39 | create_time: string;
40 | id: string;
41 | intent: String;
42 | links: Array<{href: string; method: string; rel: string; title: string;}>
43 | payer: {
44 | address: {country_code: string}
45 | email_address: string;
46 | name: {
47 | given_name: string; // first name
48 | surname: string; // last name
49 | },
50 | payer_id: string;
51 | };
52 | purchase_units: Array