/react-payment

💳💰 React components for credit card and bank account forms, using material-ui

Primary LanguageJavaScriptMIT LicenseMIT

npm version

React components for payments:

  • <CardForm>: credit card entry (with validation)
  • <BankForm>: bank account entry (with validation)
  • <PaymentMethods>: list of payment methods (with add / remove buttons)

You can configure/modify some things with props and CSS, and if you need to do any further customization, they're small files—send me a quick PR!

Demo

Component demo

CardForm

Blank CardForm

Invalid CardForm

BankForm

Valid BankForm

Invalid BankForm

PaymentMethods

PaymentMethods

Usage

yarn add react-payment

Since this library uses Material-UI components, you need to have a Material-UI theme. To get the default style, just wrap this module's components in a <MuiThemeProvider> tag (see the full example).

The alternate syntax for partial imports is react-payment/dist/ComponentName:

import { CardForm } from 'react-payment';

OR

import CardForm from 'react-payment/dist/CardForm';

CardForm usage

<CardForm> is a credit card form. By default it only has inputs for number, expiration, and CVC.

Props:

  • onSubmit(card => {})
  • getName: show the name input, default false
  • getZip: show the zip code input, default false
  • styles: override styles on the elements
  • defaultValues: initial input values. Object of the form { inputName: defaultString }, and the input names are: name, number, expiration, cvc, zip. Expiration is of the format "01/44" for January 2044.
import { CardForm } from 'react-payment';

onSubmit: (card) => {
  const { number, exp_month, exp_year, cvc, name, zip } = card;
  Stripe.card.createToken({
    number,
    exp_month,
    exp_year,
    cvc,
    name,
    address_zip: zip
  }, (status, response) => {
    if (response.error) {
      alert('Adding card failed with error: ' + response.error.message);
    } else {
      const cardToken = response.id;
      // send cardToken to server to be saved under the current user
      // show success message and navigate away from form
    }
  });
}

<CardForm
  onSubmit={this.onSubmit}
  getName={true}
  getZip={true}
/>

BankForm usage

<BankForm> is a form for entering US bank account information.

If you would like BankForm to intelligently validate the account & routing number, make sure that Stripe.js is loaded (see the full example below).

Props:

  • onSubmit(account => {})
  • defaultValues: initial input values. Object of the form { inputName: defaultString }, and the input names are name, accountNumber, routingNumber.
import BankForm from 'react-payment';

onSubmit(account) {
  const { name, accountNumber, routingNumber, accountType } = account;
  const account_holder_type = accountType === 'personal' ? 'individual' : 'company';

  Stripe.bankAccount.createToken({
    country: 'US',
    currency: 'USD',
    routing_number: routingNumber,
    account_number: accountNumber,
    account_holder_name: name,
    account_holder_type
  }, (status, response) => {
    if (response.error) {
      alert('Adding bank account failed with error: ' + response.error.message);
    } else {
      const bankAccountToken = response.id;
      // send bankAccountToken to server to be saved under the current user
      // show success message and navigate away from form
    }
  });
}

<BankForm
  onSubmit={this.onSubmit}
/>

PaymentMethods usage

<PaymentMethods> is a list of your credit cards and/or bank accounts.

Props:

  • showCards: whether to show the card list & add button
  • showBanks: whether to show the bank list & add button
  • cards: array of cards, in the format { id: '1', last4: '1234', brand: 'visa' }
  • banks: array of banks, in the format { id: '1', last4: '1234' }
  • onAddCard
  • onAddBank
  • onRemoveCard(id => {})
  • onRemoveBank(id => {})
import { PaymentMethods } from 'react-payment';

<PaymentMethods
  showCards={true}
  showBanks={false}
  cards={[{ id: '1', last4: '1234', brand: 'visa' }]}
  onAddCard={this.showCardFormDialog}
  onRemoveCard={this.removeCard}
  />

Full example

import { CardForm, BankForm, PaymentMethods } from 'react-payment';
import React, { Component } from 'react'
import Dialog from 'material-ui/Dialog';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

import server from './server';

let loadedStripe = false;

export default class PaymentExample extends Component {

  state = {
    dialogOpen: false
    cardDialog: true
  };

  componentWillMount() {
    if (loadedStripe) {
      return;
    }

    const script = document.createElement("script");
    script.src = "https://js.stripe.com/v2/";
    script.type = "text/javascript";
    script.async = true;
    script.onload = () => {
      Stripe.setPublishableKey('pk_test_6pRNASCoBOKtIshFeQd4XMUh');
    };
    document.body.appendChild(script);

    loadedStripe = true;
  }

  openDialog = (type) => {
    this.setState({
      dialogOpen: true,
      cardDialog: type === 'card' ? true : false
    });
  };

  closeDialog = () => {
    this.setState({dialogOpen: false});
  };

  removeCard = (id) => {
    server.removeCard(id);
  };

  removeBank = (id) => {
    server.removeBankAccount(id);
  };

  onSubmitCard = (card) => {
    const { number, exp_month, exp_year, cvc, name, zip } = card;
    Stripe.card.createToken({
      number,
      exp_month,
      exp_year,
      cvc,
      name,
      address_zip: zip
    }, (status, response) => {
      if (response.error) {
        alert('Adding card failed with error: ' + response.error.message)
      } else {
        const cardToken = response.id;
        server.saveCard(cardToken);
        this.closeDialog();
        // show success message
      }
    });
  };

  onSubmitBank = (account) => {
    const { name, accountNumber, routingNumber, accountType } = account;
    const account_holder_type = accountType === 'personal' ? 'individual' : 'company';

    Stripe.bankAccount.createToken({
      country: 'US',
      currency: 'USD',
      routing_number: routingNumber,
      account_number: accountNumber,
      account_holder_name: name,
      account_holder_type
    }, (status, response) => {
      if (response.error) {
        alert('Adding bank account failed with error: ' + response.error.message);
      } else {
        const bankAccountToken = response.id;
        server.saveBankAccount(bankAccountToken);
        this.closeDialog();
        // show success message
      }
    })
  };

  render() {
    const title = this.state.cardDialog ? 'Add credit card' : 'Add bank account';

    return (
      <MuiThemeProvider>
        <PaymentMethods
          showCards={true}
          showBanks={true}
          cards={[{ id: '1', last4: '1234', brand: 'visa' }]}
          banks={[]}
          onAddCard={() => this.openDialog('card')}
          onAddBank={() => this.openDialog('bank')}
          onRemoveCard={this.removeCard}
          onRemoveBank={this.removeBank}
          />
        <Dialog
          title={title}
          modal={false}
          open={this.state.dialogOpen}
          onRequestClose={this.closeDialog}
        >
          {
            this.state.cardDialog ?
            <CardForm
              onSubmit={this.onSubmitCard}
              getName={true}
              getZip={true}
            />
            :
            <BankForm
              onSubmit={this.onSubmitBank}
            />
          }
        </Dialog>
      </MuiThemeProvider>
    );
  }
}

Development

git clone git@github.com:lorensr/react-payment.git
npm install
npm run storybook

http://localhost:9001

Deployment

npm version patch
npm publish
npm run deploy-storybook

Credits