Skip to content

Instantly share code, notes, and snippets.

@clodal
Last active August 28, 2020 04:34
Show Gist options
  • Select an option

  • Save clodal/ebf19650d47e55c2402afff59b1166b3 to your computer and use it in GitHub Desktop.

Select an option

Save clodal/ebf19650d47e55c2402afff59b1166b3 to your computer and use it in GitHub Desktop.

Add Stripe

Overview:

  1. Add Stripe API Lambda on BE
  2. Add Stripe Hooks on FE

References:

Add Stripe Lambda (Server-side)

  1. amplify add api
➜  cur-api git:(develop) amplify add api
? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the project: stripe
? Provide a path (e.g., /items) /checkout
? Choose a Lambda source Create a new Lambda function
? Provide a friendly name for your resource to be used as a label for this category in the project: stripeCheckout
? Provide the AWS Lambda function name: stripeCheckout
? Choose the function template that you want to use: Serverless express function (Integration with Amazon API Gateway)
? Do you want to access other resources created in this project from your Lambda function? No
? Do you want to edit the local lambda function now? No
Succesfully added the Lambda function locally
? Restrict API access No
? Do you want to add another path? No
Successfully added resource stripe locally

Some next steps:
"amplify push" will build all your local backend resources and provision it in the cloud
"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud
  1. In amplify/backend/function/stripeCheckout/src, do npm install stripe

  2. In amplify/backend/function/stripeCheckout/src/app.js, add the following code:

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY)

app.post('/checkout', async function (req, res) {
  try {
    const paymentIntent = await stripe.paymentIntents.create({
      amount: req.body.amount,
      currency: req.body.currency,
      // Verify your integration in this guide by including this parameter
      metadata: { integration_check: 'accept_a_payment' },
    });

    res.json({ client_secret: paymentIntent.client_secret })
  } catch (err) {
    res.json({ err: err })
  }
});
  1. amplify push
  2. git commit and push changes
  3. Go to AWS Lambda Servicel. Select the newly created lambda and add the following to the env var section: STRIPE_SECRET_KEY = <SECRET_KEY>
  4. Done.

Add Stripe Hooks (Client-side)

  1. Get stripe public key from Stripe and add to vercel env vars
  2. In client repo, do yarn add @stripe/react-stripe-js @stripe/stripe-js
  3. Then refer to the following code example:
// StripeForm.tsx

import React, { useState, useRef, useImperativeHandle } from 'react'
import { startCase } from 'lodash'
import { API } from 'aws-amplify'
import { useRouter } from 'next/router'
import { loadStripe } from '@stripe/stripe-js'
import {
  useElements,
  useStripe,
  Elements,
  CardNumberElement,
  CardExpiryElement,
  CardCvcElement,
} from '@stripe/react-stripe-js'
import { FormControl, TextField } from '@material-ui/core'
import config from '../../config'

const stripePromise = loadStripe(config.stripe.publicKey)

const StripeInput = (props) => {
  const { component: Component, inputRef, ...rest } = props

  const elementRef = useRef()
  useImperativeHandle(inputRef, () => ({
    focus: () => {
      if (elementRef && elementRef.current) {
        // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
        // @ts-ignore
        // eslint-disable-next-line no-unused-expressions
        elementRef.current.focus
      }
    },
  }))

  // eslint-disable-next-line no-return-assign
  return <Component onReady={(element) => (elementRef.current = element)} {...rest} />
}

const CardFieldWrapper = (props) => {
  const { component, name, label, ...rest } = props

  const [error, setError] = useState()

  // Handle real-time validation errors from the card Element.
  const handleChange = (e) => {
    // Set errors
    if (e.error) return setError(e.error.message)

    // Clear existing errors
    if (error && !e.error) return setError(null)
  }

  return (
    <FormControl fullWidth margin="normal" {...rest} error={error}>
      <TextField
        name={name}
        label={label || startCase(name)}
        variant="outlined"
        fullWidth
        required
        onChange={handleChange}
        error={error}
        helperText={error}
        InputLabelProps={{ shrink: true }}
        InputProps={{
          inputComponent: StripeInput,
          inputProps: { component },
        }}
      />
    </FormControl>
  )
}

const StripeForm = (props) => {
  const [loading, setLoading] = useState(false)

  // Init router
  const router = useRouter()

  // Init Stripe
  const stripe = useStripe()
  const elements = useElements()

  const handleSubmit = async (e) => {
    try {
      // Prevent native form submission
      e.preventDefault()

      // Return early as Stripe.js has not loaded yet
      if (!stripe || !elements) return

      // Set loading to prevent form from being resubmitted
      setLoading(true)

      // Get client secret from Stripe Lambda
      const total = 499 // TODO: Input amount
      const { client_secret } = await API.post('stripe', '/checkout', {
        body: {
          amount: total * 100, // This is in cents i.e. $5.50 = 550
          currency: 'sgd',
        },
      })

      // Confirm Stripe payment
      const onPayment = await stripe.confirmCardPayment(client_secret, {
        payment_method: {
          card: elements.getElement(CardNumberElement),
          billing_details: {
            name: 'Jenny Rosen', // TODO: Input customer details
          },
        },
      })

      // Handle errors
      if (onPayment.error || onPayment.paymentIntent.status !== 'succeeded') {
        setLoading(false)
        // Show error to customer here
        return
      }

      // Create order in application
      // const onCreate = await handleCreateOrder(orderInput) // TODO: Handle application data

      // Redirect to success page
      return router.push('/order/success') // TODO: Define success route
    } catch (err) {
      setLoading(false)
    }
  }

  return (
    <form onSubmit={handleSubmit} {...props}>
      <CardFieldWrapper name="cardNumber" component={CardNumberElement} />
      <CardFieldWrapper name="expiry" component={CardExpiryElement} />
      <CardFieldWrapper name="cvc" label="CVC" component={CardCvcElement} />
      <button type="submit" disabled={loading}>
        Pay
      </button>
    </form>
  )
}

const StripeFormWithElement = (props) => {
  return (
    <Elements stripe={stripePromise}>
      <StripeForm {...props} />
    </Elements>
  )
}

export default StripeFormWithElement
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment