Skip to content

Commit 2ec2bec

Browse files
[example] Upgrade the with-stripe-typescript example app (#33761)
Hi team! I found in the `with-stripe-typescript` example that I need to update these things. - `use-shopping-cart` is launched a new major version - Stripe launched a new useful payment element named Stripe Payment Element So I've updated the example app to support these updates. ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint` ## How to test it Please check the README.md of the example. https://github.com/vercel/next.js/blob/canary/examples/with-stripe-typescript/README.md Thanks!
1 parent f104e91 commit 2ec2bec

17 files changed

Lines changed: 228 additions & 136 deletions

File tree

examples/with-stripe-typescript/components/Cart.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import React, { ReactNode } from 'react'
2-
import { CartProvider } from 'use-shopping-cart'
3-
import getStripe from '../utils/get-stripejs'
2+
import { CartProvider } from 'use-shopping-cart/react'
43
import * as config from '../config'
54

65
const Cart = ({ children }: { children: ReactNode }) => (
76
<CartProvider
8-
mode="checkout-session"
9-
stripe={getStripe()}
7+
cartMode="checkout-session"
8+
stripe={process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string}
109
currency={config.CURRENCY}
1110
>
1211
<>{children}</>

examples/with-stripe-typescript/components/CartSummary.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import React, { useState, useEffect } from 'react'
22

33
import StripeTestCards from '../components/StripeTestCards'
44

5-
import { useShoppingCart } from 'use-shopping-cart'
5+
import { useShoppingCart } from 'use-shopping-cart/react'
66
import { fetchPostJSON } from '../utils/api-helpers'
77

88
const CartSummary = () => {
99
const [loading, setLoading] = useState(false)
1010
const [cartEmpty, setCartEmpty] = useState(true)
11+
const [errorMessage, setErrorMessage] = useState('')
1112
const {
1213
formattedTotalPrice,
1314
cartCount,
@@ -23,14 +24,17 @@ const CartSummary = () => {
2324
) => {
2425
event.preventDefault()
2526
setLoading(true)
27+
setErrorMessage('')
2628

2729
const response = await fetchPostJSON(
2830
'/api/checkout_sessions/cart',
2931
cartDetails
3032
)
3133

32-
if (response.statusCode === 500) {
34+
if (response.statusCode > 399) {
3335
console.error(response.message)
36+
setErrorMessage(response.message)
37+
setLoading(false)
3438
return
3539
}
3640

@@ -40,6 +44,9 @@ const CartSummary = () => {
4044
return (
4145
<form onSubmit={handleCheckout}>
4246
<h2>Cart summary</h2>
47+
{errorMessage ? (
48+
<p style={{ color: 'red' }}>Error: {errorMessage}</p>
49+
) : null}
4350
{/* This is where we'll render our cart */}
4451
<p suppressHydrationWarning>
4552
<strong>Number of Items:</strong> {cartCount}

examples/with-stripe-typescript/components/ClearCart.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect } from 'react'
2-
import { useShoppingCart } from 'use-shopping-cart'
2+
import { useShoppingCart } from 'use-shopping-cart/react'
33

44
export default function ClearCart() {
55
const { clearCart } = useShoppingCart()

examples/with-stripe-typescript/components/ElementsForm.tsx

Lines changed: 39 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,30 @@
1-
import React, { useState } from 'react'
1+
import React, { useState, FC } from 'react'
22

33
import CustomDonationInput from '../components/CustomDonationInput'
44
import StripeTestCards from '../components/StripeTestCards'
55
import PrintObject from '../components/PrintObject'
66

77
import { fetchPostJSON } from '../utils/api-helpers'
8-
import { formatAmountForDisplay } from '../utils/stripe-helpers'
8+
import {
9+
formatAmountForDisplay,
10+
formatAmountFromStripe,
11+
} from '../utils/stripe-helpers'
912
import * as config from '../config'
1013

11-
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'
12-
13-
const CARD_OPTIONS = {
14-
iconStyle: 'solid' as const,
15-
style: {
16-
base: {
17-
iconColor: '#6772e5',
18-
color: '#6772e5',
19-
fontWeight: '500',
20-
fontFamily: 'Roboto, Open Sans, Segoe UI, sans-serif',
21-
fontSize: '16px',
22-
fontSmoothing: 'antialiased',
23-
':-webkit-autofill': {
24-
color: '#fce883',
25-
},
26-
'::placeholder': {
27-
color: '#6772e5',
28-
},
29-
},
30-
invalid: {
31-
iconColor: '#ef2961',
32-
color: '#ef2961',
33-
},
34-
},
35-
}
14+
import { useStripe, useElements, PaymentElement } from '@stripe/react-stripe-js'
15+
import { PaymentIntent } from '@stripe/stripe-js'
3616

37-
const ElementsForm = () => {
17+
const ElementsForm: FC<{
18+
paymentIntent?: PaymentIntent | null
19+
}> = ({ paymentIntent = null }) => {
20+
const defaultAmout = paymentIntent
21+
? formatAmountFromStripe(paymentIntent.amount, paymentIntent.currency)
22+
: Math.round(config.MAX_AMOUNT / config.AMOUNT_STEP)
3823
const [input, setInput] = useState({
39-
customDonation: Math.round(config.MAX_AMOUNT / config.AMOUNT_STEP),
24+
customDonation: defaultAmout,
4025
cardholderName: '',
4126
})
27+
const [paymentType, setPaymentType] = useState('')
4228
const [payment, setPayment] = useState({ status: 'initial' })
4329
const [errorMessage, setErrorMessage] = useState('')
4430
const stripe = useStripe()
@@ -80,11 +66,13 @@ const ElementsForm = () => {
8066
e.preventDefault()
8167
// Abort if form isn't valid
8268
if (!e.currentTarget.reportValidity()) return
69+
if (!elements) return
8370
setPayment({ status: 'processing' })
8471

8572
// Create a PaymentIntent with the specified amount.
8673
const response = await fetchPostJSON('/api/payment_intents', {
8774
amount: input.customDonation,
75+
payment_intent_id: paymentIntent?.id,
8876
})
8977
setPayment(response)
9078

@@ -94,21 +82,18 @@ const ElementsForm = () => {
9482
return
9583
}
9684

97-
// Get a reference to a mounted CardElement. Elements knows how
98-
// to find your CardElement because there can only ever be one of
99-
// each type of element.
100-
const cardElement = elements!.getElement(CardElement)
101-
10285
// Use your card Element with other Stripe.js APIs
103-
const { error, paymentIntent } = await stripe!.confirmCardPayment(
104-
response.client_secret,
105-
{
106-
payment_method: {
107-
card: cardElement!,
108-
billing_details: { name: input.cardholderName },
86+
const { error } = await stripe!.confirmPayment({
87+
elements,
88+
confirmParams: {
89+
return_url: 'http://localhost:3000/donate-with-elements',
90+
payment_method_data: {
91+
billing_details: {
92+
name: input.cardholderName,
93+
},
10994
},
110-
}
111-
)
95+
},
96+
})
11297

11398
if (error) {
11499
setPayment({ status: 'error' })
@@ -134,24 +119,20 @@ const ElementsForm = () => {
134119
<StripeTestCards />
135120
<fieldset className="elements-style">
136121
<legend>Your payment details:</legend>
137-
<input
138-
placeholder="Cardholder name"
139-
className="elements-style"
140-
type="Text"
141-
name="cardholderName"
142-
onChange={handleInputChange}
143-
required
144-
/>
122+
{paymentType === 'card' ? (
123+
<input
124+
placeholder="Cardholder name"
125+
className="elements-style"
126+
type="Text"
127+
name="cardholderName"
128+
onChange={handleInputChange}
129+
required
130+
/>
131+
) : null}
145132
<div className="FormRow elements-style">
146-
<CardElement
147-
options={CARD_OPTIONS}
133+
<PaymentElement
148134
onChange={(e) => {
149-
if (e.error) {
150-
setPayment({ status: 'error' })
151-
setErrorMessage(
152-
e.error.message ?? 'An unknown error occurred'
153-
)
154-
}
135+
setPaymentType(e.value.type)
155136
}}
156137
/>
157138
</div>

examples/with-stripe-typescript/components/Products.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import products from '../data/products.json'
2-
import { useShoppingCart, formatCurrencyString } from 'use-shopping-cart'
1+
import products from '../data/products'
2+
import { formatCurrencyString } from 'use-shopping-cart'
3+
import { useShoppingCart } from 'use-shopping-cart/react'
34

45
const Products = () => {
56
const { addItem, removeItem } = useShoppingCart()
67

78
return (
89
<section className="products">
910
{products.map((product) => (
10-
<div key={product.sku} className="product">
11+
<div key={product.id} className="product">
1112
<img src={product.image} alt={product.name} />
1213
<h2>{product.name}</h2>
1314
<p className="price">
@@ -18,13 +19,16 @@ const Products = () => {
1819
</p>
1920
<button
2021
className="cart-style-background"
21-
onClick={() => addItem(product)}
22+
onClick={() => {
23+
console.log(product)
24+
addItem(product)
25+
}}
2226
>
2327
Add to cart
2428
</button>
2529
<button
2630
className="cart-style-background"
27-
onClick={() => removeItem(product.sku)}
31+
onClick={() => removeItem(product.id)}
2832
>
2933
Remove
3034
</button>

examples/with-stripe-typescript/data/products.json

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const product = [
2+
{
3+
name: 'Bananas',
4+
description: 'Yummy yellow fruit',
5+
id: 'sku_GBJ2Ep8246qeeT',
6+
price: 400,
7+
image:
8+
'https://images.unsplash.com/photo-1574226516831-e1dff420e562?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=225&q=80',
9+
attribution: 'Photo by Priscilla Du Preez on Unsplash',
10+
currency: 'USD',
11+
},
12+
{
13+
name: 'Tangerines',
14+
id: 'sku_GBJ2WWfMaGNC2Z',
15+
price: 100,
16+
image:
17+
'https://images.unsplash.com/photo-1482012792084-a0c3725f289f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=225&q=80',
18+
attribution: 'Photo by Jonathan Pielmayer on Unsplash',
19+
currency: 'USD',
20+
},
21+
]
22+
export default product

examples/with-stripe-typescript/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@
66
"start": "next start"
77
},
88
"dependencies": {
9-
"@stripe/react-stripe-js": "1.1.2",
10-
"@stripe/stripe-js": "1.5.0",
9+
"@stripe/react-stripe-js": "1.7.0",
10+
"@stripe/stripe-js": "1.22.0",
1111
"micro": "^9.3.4",
1212
"micro-cors": "^0.1.1",
1313
"next": "latest",
1414
"react": "^17.0.2",
1515
"react-dom": "^17.0.2",
16-
"stripe": "8.56.0",
16+
"stripe": "8.200.0",
1717
"swr": "^0.1.16",
18-
"use-shopping-cart": "2.1.0"
18+
"use-shopping-cart": "3.0.5"
1919
},
2020
"devDependencies": {
2121
"@types/micro": "^7.3.3",
2222
"@types/micro-cors": "^0.1.0",
2323
"@types/node": "^13.1.2",
2424
"@types/react": "^16.9.17",
25-
"typescript": "^3.7.4"
25+
"typescript": "4.5.5"
2626
}
2727
}

examples/with-stripe-typescript/pages/api/checkout_sessions/[id].ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { NextApiRequest, NextApiResponse } from 'next'
33
import Stripe from 'stripe'
44
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
55
// https://github.com/stripe/stripe-node#configuration
6-
apiVersion: '2020-03-02',
6+
apiVersion: '2020-08-27',
77
})
88

99
export default async function handler(
@@ -22,6 +22,8 @@ export default async function handler(
2222

2323
res.status(200).json(checkout_session)
2424
} catch (err) {
25-
res.status(500).json({ statusCode: 500, message: err.message })
25+
const errorMessage =
26+
err instanceof Error ? err.message : 'Internal server error'
27+
res.status(500).json({ statusCode: 500, message: errorMessage })
2628
}
2729
}

examples/with-stripe-typescript/pages/api/checkout_sessions/cart.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import { NextApiRequest, NextApiResponse } from 'next'
88
* The important thing is that the product info is loaded from somewhere trusted
99
* so you know the pricing information is accurate.
1010
*/
11-
import { validateCartItems } from 'use-shopping-cart/src/serverUtil'
12-
import inventory from '../../../data/products.json'
11+
import { validateCartItems } from 'use-shopping-cart/utilities/serverless'
12+
import inventory from '../../../data/products'
1313

1414
import Stripe from 'stripe'
1515
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
1616
// https://github.com/stripe/stripe-node#configuration
17-
apiVersion: '2020-03-02',
17+
apiVersion: '2020-08-27',
1818
})
1919

2020
export default async function handler(
@@ -24,8 +24,10 @@ export default async function handler(
2424
if (req.method === 'POST') {
2525
try {
2626
// Validate the cart details that were sent from the client.
27-
const cartItems = req.body
28-
const line_items = validateCartItems(inventory, cartItems)
27+
const line_items = validateCartItems(inventory as any, req.body)
28+
const hasSubscription = line_items.find((item) => {
29+
return !!item.price_data.recurring
30+
})
2931
// Create Checkout Sessions from body params.
3032
const params: Stripe.Checkout.SessionCreateParams = {
3133
submit_type: 'pay',
@@ -37,13 +39,18 @@ export default async function handler(
3739
line_items,
3840
success_url: `${req.headers.origin}/result?session_id={CHECKOUT_SESSION_ID}`,
3941
cancel_url: `${req.headers.origin}/use-shopping-cart`,
42+
mode: hasSubscription ? 'subscription' : 'payment',
4043
}
44+
4145
const checkoutSession: Stripe.Checkout.Session =
4246
await stripe.checkout.sessions.create(params)
4347

4448
res.status(200).json(checkoutSession)
4549
} catch (err) {
46-
res.status(500).json({ statusCode: 500, message: err.message })
50+
console.log(err)
51+
const errorMessage =
52+
err instanceof Error ? err.message : 'Internal server error'
53+
res.status(500).json({ statusCode: 500, message: errorMessage })
4754
}
4855
} else {
4956
res.setHeader('Allow', 'POST')

0 commit comments

Comments
 (0)