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

File tree

17 files changed

+228
-136
lines changed

17 files changed

+228
-136
lines changed

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy