Overview:
- Add Stripe API Lambda on BE
- Add Stripe Hooks on FE
References:
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
-
In
amplify/backend/function/stripeCheckout/src, donpm install stripe -
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 })
}
});
amplify push- git commit and push changes
- Go to AWS Lambda Servicel. Select the newly created lambda and add the following to the env var section:
STRIPE_SECRET_KEY = <SECRET_KEY> - Done.
- Get stripe public key from Stripe and add to vercel env vars
- In client repo, do
yarn add @stripe/react-stripe-js @stripe/stripe-js - 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