From 5c91af289f63b107c5001dfdc449fb7db561c9ef Mon Sep 17 00:00:00 2001 From: John Hannagan Date: Wed, 16 Feb 2022 22:00:10 +0800 Subject: [PATCH] feat: Add Paypal components (#57) * Add PayPal elements * Update readme to include new HOCs * Bump version number --- README.md | 49 ++++++++++---- package.json | 2 +- src/index.ts | 8 ++- .../components/elements/paypal-element.tsx | 51 +++++++++++++++ src/lib/components/injector.tsx | 58 ++++++++++++++++- test/e2e/fixtures/nav.js | 3 +- test/e2e/fixtures/paypal.html | 15 +++++ test/e2e/fixtures/paypal.js | 64 +++++++++++++++++++ test/unit/specs/index.spec.ts | 3 +- types/elements.d.ts | 10 +++ types/injector.d.ts | 8 ++- types/rebilly/api.d.ts | 1 + .../payment-elements/elements/paypal.d.ts | 18 ++++++ .../payment-element-names.d.ts | 2 +- types/rebilly/settings.d.ts | 7 ++ 15 files changed, 278 insertions(+), 21 deletions(-) create mode 100644 src/lib/components/elements/paypal-element.tsx create mode 100644 test/e2e/fixtures/paypal.html create mode 100644 test/e2e/fixtures/paypal.js create mode 100644 types/rebilly/payment-elements/elements/paypal.d.ts diff --git a/README.md b/README.md index c68c38a..0ff4066 100644 --- a/README.md +++ b/README.md @@ -10,20 +10,30 @@ Supported: React 14, 15, 16 (latest) This package is a wrapper for [FramePay](https://rebilly.github.io/framepay-docs/) offering out-of-the-box support for Redux and other common React features. ## Table of Contents -- [FramePay documentation](#framepay-documentation) -- [Demos](#demos) -- [Installation](#installation) -- [Getting started](#getting-started) - - [The FramePay context (`FramePayProvider`)](#the-framepay-context-framepayprovider) - - [Setting up your payment form](#setting-up-your-payment-form) - - [Provided props](#provided-props) +- [framepay-react](#framepay-react) + - [Table of Contents](#table-of-contents) + - [FramePay documentation](#framepay-documentation) + - [Demos](#demos) + - [Installation](#installation) + - [Getting started](#getting-started) + - [The FramePay context (`FramePayProvider`)](#the-framepay-context-framepayprovider) + - [Setting up your payment form](#setting-up-your-payment-form) + - [WARNING](#warning) + - [withFramePay (All props)](#withframepay-all-props) + - [withFramePayCardComponent (Card props)](#withframepaycardcomponent-card-props) + - [withFramePayBankComponent (Bank props)](#withframepaybankcomponent-bank-props) + - [withFramePayApplePayComponent (Apple Pay props)](#withframepayapplepaycomponent-apple-pay-props) + - [withFramePayGooglePayComponent (Google Pay props)](#withframepaygooglepaycomponent-google-pay-props) + - [withFramePayPaypalComponent (Paypal props)](#withframepaypaypalcomponent-paypal-props) - [With FramePay (`withFramePay`) HOC](#with-framepay-withframepay-hoc) - [Card elements (`withFramePayCardComponent`) HOC](#card-elements-withframepaycardcomponent-hoc) - [Bank elements (`withFramePayBankComponent`) HOC](#bank-elements-withframepaybankcomponent-hoc) -- [Advanced options](#advanced-options) - - [Initialize settings](#initialization-settings) - - [Create Token params](#create-token-parameters) -- [Troubleshooting](#troubleshooting) + - [Advanced options](#advanced-options) + - [Initialization settings](#initialization-settings) + - [Create Token Parameters](#create-token-parameters) + - [Troubleshooting](#troubleshooting) + - [Incorrect](#incorrect) + - [Correct](#correct) ### FramePay documentation For more information on FramePay see its [official documentation](https://rebilly.github.io/framepay-docs/) or [repository](https://github.com/Rebilly/framepay-docs). @@ -97,6 +107,9 @@ The react lifecycle methods already implemented in the library. - `CardCvvElement` - `CardExpiryElement` - `CardNumberElement` +- `ApplePayElement` +- `GooglePayElement` +- `PaypalElement` ###### withFramePayCardComponent (Card props) - Rebilly @@ -111,12 +124,24 @@ The react lifecycle methods already implemented in the library. - `BankAccountTypeElement` - `BankRoutingNumberElement` +###### withFramePayApplePayComponent (Apple Pay props) +- Rebilly +- `ApplePayElement` + +###### withFramePayGooglePayComponent (Google Pay props) +- Rebilly +- `GooglePayElement` + +###### withFramePayPaypalComponent (Paypal props) +- Rebilly +- `PaypalElement` + ##### With FramePay (`withFramePay`) HOC This simple FramePay HOC is used to provide the `Rebilly` API in your component. It is most commonly used in combination with multiple payment methods. - [Payment cards and ACH (CodeSandbox)](https://codesandbox.io/s/z2q2lx9ry4?module=/src/elements/MultiplePaymentMethods.js) - [Payment cards and ACH Short version (CodeSandbox)](https://codesandbox.io/s/z2q2lx9ry4?module=/src/elements/MultiplePaymentMethodsShort.js) -- [PayPal and Bitcoin (CodeSandbox)](https://codesandbox.io/s/z2q2lx9ry4?module=/src/elements/OtherPaymentMethods.js) +- [Alternative methods (Bitcoin) (CodeSandbox)](https://codesandbox.io/s/z2q2lx9ry4?module=/src/elements/OtherPaymentMethods.js) ##### Card elements (`withFramePayCardComponent`) HOC diff --git a/package.json b/package.json index 4237dee..db6b7d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rebilly/framepay-react", - "version": "1.3.0", + "version": "1.4.0", "description": "A React wrapper for Rebilly's FramePay offering out-of-the-box support for Redux and other common React features", "main": "build/index.js", "author": "Rebilly", diff --git a/src/index.ts b/src/index.ts index d068131..db60d84 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,8 @@ import { withFramePayBankComponent, withFramePayCardComponent, withFramePayGooglePayComponent, - withFramePayIBANComponent + withFramePayIBANComponent, + withFramePayPaypalComponent } from './lib/components/injector'; import FramePayProvider from './lib/components/provider'; @@ -17,7 +18,8 @@ import { FramePayCardProps, FramePayComponentProps, FramePayGooglePayProps, - FramePayIBANProps + FramePayIBANProps, + FramePayPaypalProps } from '../types/injector'; export { @@ -29,10 +31,12 @@ export { withFramePayIBANComponent, withFramePayApplePayComponent, withFramePayGooglePayComponent, + withFramePayPaypalComponent, FramePayComponentProps, FramePayCardProps, FramePayBankProps, FramePayIBANProps, FramePayApplePayProps, FramePayGooglePayProps, + FramePayPaypalProps }; diff --git a/src/lib/components/elements/paypal-element.tsx b/src/lib/components/elements/paypal-element.tsx new file mode 100644 index 0000000..43a7826 --- /dev/null +++ b/src/lib/components/elements/paypal-element.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import FramePayError from '../../framepay-error'; +import BaseElement from './base-element'; + +export default class PaypalElement extends BaseElement< + PaypalProps, + PaypalState +> { + setupElement() { + const { Rebilly, onTokenReady } = this.props; + + const makeElement = () => { + // elementNode already checked in BaseElement.handleSetupElement + // just ts checks fix + if (!this.elementNode) { + throw FramePayError({ + code: FramePayError.codes.elementMountError, + details: `PaypalElement invalid elementNode` + }); + } + + try { + return Rebilly.paypal.mount(this.elementNode); + } catch (e) { + throw FramePayError({ + code: FramePayError.codes.elementMountError, + details: `PaypalElement error in remote api call`, + trace: e + }); + } + }; + + const element = makeElement(); + + try { + Rebilly.on('token-ready', (token: string) => { + if (onTokenReady) { + onTokenReady(token); + } + }); + + this.setState({ element }); + } catch (e) { + throw FramePayError({ + code: FramePayError.codes.elementMountError, + details: `PaypalElement events binding error`, + trace: e + }); + } + } +} diff --git a/src/lib/components/injector.tsx b/src/lib/components/injector.tsx index edb4fdc..b8b88e5 100644 --- a/src/lib/components/injector.tsx +++ b/src/lib/components/injector.tsx @@ -7,6 +7,7 @@ import BankElementComponent from './elements/bank-element'; import CardElementComponent from './elements/card-element'; import GooglePayElementComponent from './elements/googlepay-element'; import IBANElementComponent from './elements/iban-element'; +import PaypalElementComponent from './elements/paypal-element'; import { FramePayApplePayProps, @@ -14,7 +15,8 @@ import { FramePayCardProps, FramePayComponentProps, FramePayGooglePayProps, - FramePayIBANProps + FramePayIBANProps, + FramePayPaypalProps } from '../../../types/injector'; const makeRebillyProps = (data: FramePayContext): RebillyProps => @@ -230,6 +232,25 @@ const elementsFabric = (type: PaymentElements): object => { }; } + if (type === 'paypal') { + /** + * Paypal + */ + + const PaypalElement = Hoc( + 'PaypalElement', + PaypalElementComponent, + (data: FramePayContext) => + ({ + Rebilly: makeRebillyProps(data) + } as PaypalProps) + ); + + return { + PaypalElement + }; + } + /** * Throw the error by default. */ @@ -248,7 +269,8 @@ export function withFramePay( ...elementsFabric('bankAccount'), ...elementsFabric('iban'), ...elementsFabric('applePay'), - ...elementsFabric('googlePay') + ...elementsFabric('googlePay'), + ...elementsFabric('paypal') }; return class extends React.Component< OriginalProps & FramePayComponentProps, @@ -439,3 +461,35 @@ export function withFramePayGooglePayComponent( } }; } + +export function withFramePayPaypalComponent( + WrappedComponent: React.ComponentType +) { + const elements = elementsFabric('paypal'); + return class extends React.Component< + OriginalProps & FramePayPaypalProps, + {} + > { + static readonly displayName = `withFramePayPaypalComponent${name}(${WrappedComponent.displayName || + WrappedComponent.name || + 'Component'})`; + + render() { + return ( + + {(data: FramePayContext) => { + return ( + + ); + }} + + ); + } + }; +} diff --git a/test/e2e/fixtures/nav.js b/test/e2e/fixtures/nav.js index b50158a..300796d 100644 --- a/test/e2e/fixtures/nav.js +++ b/test/e2e/fixtures/nav.js @@ -9,7 +9,8 @@ setTimeout(() => { 'checkout-combined', 'google-pay', 'iban', - 'multiple-methods' + 'multiple-methods', + 'paypal', ] .forEach(route => { node.innerHTML += `
  • ${route}
  • `; diff --git a/test/e2e/fixtures/paypal.html b/test/e2e/fixtures/paypal.html new file mode 100644 index 0000000..83784dd --- /dev/null +++ b/test/e2e/fixtures/paypal.html @@ -0,0 +1,15 @@ + + + + + + Test Paypal + + + +
    + + + + + diff --git a/test/e2e/fixtures/paypal.js b/test/e2e/fixtures/paypal.js new file mode 100644 index 0000000..9f18520 --- /dev/null +++ b/test/e2e/fixtures/paypal.js @@ -0,0 +1,64 @@ +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; + +import { FramePayProvider, withFramePayPaypalComponent } from '../../../build'; +import { ReactVersion } from './util'; +import './style.css'; + +const params = { + publishableKey: 'pk_sandbox_S95ATjj4hXZs-T9QpZq1ENl2tDSrUkCGv98utc9', + organizationId: '5977150c-1c97-4dd4-9860-6bb2bab070b4', + websiteId: 'demo.com', + transactionData: { + amount: 10, + currency: 'USD', + }, +}; + +class PaypalElementComponent extends Component { + + constructor(props) { + super(props); + this.state = { + token: { + error: null, + data: null + } + }; + } + + render() { + return (
    +

    {this.props.title}

    +

    FramePay version: {this.props.Rebilly.version}

    +
    +
    + +
    +
    +
    ); + } +} + +const PaypalElement = withFramePayPaypalComponent(PaypalElementComponent); + +class App extends Component { + + render() { + return ( { + console.log('FramePayProvider.onReady'); + }} + onError={(err) => { + console.log('FramePayProvider.onError', err); + }}> +
    + {ReactVersion()} + +
    +
    ); + } +} + +ReactDOM.render(, document.getElementById('app')); diff --git a/test/unit/specs/index.spec.ts b/test/unit/specs/index.spec.ts index 1b52906..36af111 100644 --- a/test/unit/specs/index.spec.ts +++ b/test/unit/specs/index.spec.ts @@ -8,7 +8,8 @@ const exportKeys: ReadonlyArray = [ 'withFramePayCardComponent', 'withFramePayBankComponent', 'withFramePayGooglePayComponent', - 'withFramePayIBANComponent' + 'withFramePayIBANComponent', + 'withFramePayPaypalComponent' ].sort(); describe('lib/index', () => { diff --git a/types/elements.d.ts b/types/elements.d.ts index 64b4b40..4677302 100644 --- a/types/elements.d.ts +++ b/types/elements.d.ts @@ -45,6 +45,12 @@ interface GooglePayProps extends RebillyProps { readonly onTokenReady?: (data: string) => void; } +interface PaypalProps extends RebillyProps { + readonly Rebilly: RebillyProps; + readonly id?: string; + readonly onTokenReady?: (data: string) => void; +} + interface BankState extends PaymentComponentState { readonly element: PaymentElement | null; } @@ -64,3 +70,7 @@ interface ApplePayState extends PaymentComponentState { interface GooglePayState extends PaymentComponentState { readonly element: PaymentElement | null; } + +interface PaypalState extends PaymentComponentState { + readonly element: PaymentElement | null; +} diff --git a/types/injector.d.ts b/types/injector.d.ts index 7a67e45..5117e82 100644 --- a/types/injector.d.ts +++ b/types/injector.d.ts @@ -16,9 +16,10 @@ export interface FramePayComponentProps { // IBAN components readonly IBANElement: React.Component; - // Google Pay and Apple Pay components + // Express method components readonly ApplePayElement: React.Component; readonly GooglePayElement: React.Component; + readonly PaypalElement: React.Component; } export interface FramePayCardProps { @@ -50,3 +51,8 @@ export interface FramePayGooglePayProps { readonly Rebilly: RebillyProps; readonly GooglePayElement: React.Component; } + +export interface FramePayPaypalProps { + readonly Rebilly: RebillyProps; + readonly PaypalElement: React.Component; +} diff --git a/types/rebilly/api.d.ts b/types/rebilly/api.d.ts index 641d9cb..5ec944f 100644 --- a/types/rebilly/api.d.ts +++ b/types/rebilly/api.d.ts @@ -38,6 +38,7 @@ interface RebillyApi { readonly iban: IBANPaymentMethod; readonly applePay: ApplePayPaymentMethod; readonly googlePay: GooglePayPaymentMethod; + readonly paypal: PaypalPaymentMethod; readonly createToken: ( form: HTMLElement | HTMLFormElement, extraData?: TokenExtraData diff --git a/types/rebilly/payment-elements/elements/paypal.d.ts b/types/rebilly/payment-elements/elements/paypal.d.ts new file mode 100644 index 0000000..8e511da --- /dev/null +++ b/types/rebilly/payment-elements/elements/paypal.d.ts @@ -0,0 +1,18 @@ +/** + * Paypal Payment element + */ + +/** + * Paypal Payment Method + */ +declare type PaypalPaymentElementTypes = { + readonly form: HTMLElement | React.Component; + readonly extraData?: Object; +}; + +interface PaypalPaymentMethod extends PaymentElementWrapper { + readonly mount: ( + node: HTMLElement | HTMLDivElement, + options?: PaypalPaymentElementTypes + ) => PaymentElement; +} diff --git a/types/rebilly/payment-elements/payment-element-names.d.ts b/types/rebilly/payment-elements/payment-element-names.d.ts index 61d1469..b29cd34 100644 --- a/types/rebilly/payment-elements/payment-element-names.d.ts +++ b/types/rebilly/payment-elements/payment-element-names.d.ts @@ -1 +1 @@ -type PaymentElements = 'card' | 'bankAccount' | 'iban' | 'applePay' | 'googlePay'; +type PaymentElements = 'card' | 'bankAccount' | 'iban' | 'applePay' | 'googlePay' | 'paypal'; diff --git a/types/rebilly/settings.d.ts b/types/rebilly/settings.d.ts index 84539a1..5158fb4 100644 --- a/types/rebilly/settings.d.ts +++ b/types/rebilly/settings.d.ts @@ -95,6 +95,12 @@ interface GooglePayDisplayOptions { readonly buttonHeight: string; } +interface PaypalDisplayOptions { + readonly buttonShape: string; + readonly buttonColor: string; + readonly buttonHeight: string; +} + interface FramePaySettings { readonly publishableKey: string; readonly organizationId: string; @@ -107,4 +113,5 @@ interface FramePaySettings { readonly card?: FramePayCardSettings; readonly applePay?: ApplePayDisplayOptions; readonly googlePay?: GooglePayDisplayOptions; + readonly paypal?: PaypalDisplayOptions; }