Skip to content

Commit

Permalink
Add global CSS styles to example/with-stripe-typescript (#10520)
Browse files Browse the repository at this point in the history
* Update stripe-node.

* Update casting.

* Add global CSS styles 💅

* Remove emojies from README.

* Add twitter meta.

* Add demo gifs to README.

* Solid card icon.

* lint fix

Co-authored-by: Luis Alvarez D. <luis@zeit.co>
  • Loading branch information
thorsten-stripe and Luis Alvarez D. committed Feb 13, 2020
1 parent 2e8d638 commit d5222f8
Show file tree
Hide file tree
Showing 22 changed files with 469 additions and 121 deletions.
28 changes: 26 additions & 2 deletions examples/with-stripe-typescript/README.md
@@ -1,7 +1,7 @@
# Example using Stripe with TypeScript and react-stripe-js 🔒💸
# Example using Stripe with TypeScript and react-stripe-js

- Demo: https://nextjs-typescript-react-stripe-js.now.sh/
- CodeSandbox: https://codesandbox.io/s/nextjs-typescript-react-stripe-js-rqrss
- CodeSandbox: https://codesandbox.io/s/github/stripe-samples/nextjs-typescript-react-stripe-js
- Tutorial: https://dev.to/thorwebdev/type-safe-payments-with-next-js-typescript-and-stripe-4jo7

This is a full-stack TypeScript example using:
Expand All @@ -13,8 +13,27 @@ This is a full-stack TypeScript example using:
- Next.js [API routes](https://nextjs.org/docs/api-routes/introduction)
- [stripe-node with TypeScript](https://github.com/stripe/stripe-node#usage-with-typescript)

**Demo**

See the sample [live](https://nextjs-typescript-react-stripe-js.now.sh/) or [fork](https://codesandbox.io/s/github/stripe-samples/nextjs-typescript-react-stripe-js) on CodeSandbox.

The demo is running in test mode -- use `4242424242424242` as a test card number with any CVC + future expiration date.

Use the `4000000000003220` test card number to trigger a 3D Secure challenge flow.

Read more about testing on Stripe at https://stripe.com/docs/testing.

<details open><summary>Checkout Demo</summary>
<img src="./public/checkout_demo.gif" alt="A gif of the Checkout payment page." align="center">
</details>

<details><summary>Elements Demo</summary>
<img src="./public/elements_demo.gif" alt="A gif of the custom Elements checkout page." align="center">
</details>

### Included functionality

- [Global CSS styles](https://nextjs.org/blog/next-9-2#built-in-css-support-for-global-stylesheets)
- Making `.env` variables available to next: [next.config.js](next.config.js)
- **Note**: When deploying with Now you need to [add your secrets](https://zeit.co/docs/v2/serverless-functions/env-and-secrets) and specify a [now.json](/now.json) file.
- Implementation of a Layout component that loads and sets up Stripe.js and Elements for usage with SSR via `loadStripe` helper: [components/Layout.tsx](components/Layout.tsx).
Expand Down Expand Up @@ -116,3 +135,8 @@ now secrets add stripe_webhook_secret whsec_***
```

As the secrets are set as env vars in the project at deploy time, we will need to redeploy our app after we made changes to the secrets.

### Authors

- [@thorsten-stripe](https://twitter.com/thorwebdev)
- [@lfades](https://twitter.com/luis_fades)
11 changes: 9 additions & 2 deletions examples/with-stripe-typescript/components/CheckoutForm.tsx
Expand Up @@ -9,7 +9,9 @@ import * as config from '../config'
import { useStripe } from '@stripe/react-stripe-js'

const CheckoutForm: React.FunctionComponent = () => {
const [input, setInput] = useState({ customDonation: config.MIN_AMOUNT })
const [input, setInput] = useState({
customDonation: Math.round(config.MAX_AMOUNT / config.AMOUNT_STEP),
})
const stripe = useStripe()

const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = e =>
Expand Down Expand Up @@ -46,6 +48,7 @@ const CheckoutForm: React.FunctionComponent = () => {
return (
<form onSubmit={handleSubmit}>
<CustomDonationInput
className="checkout-style"
name={'customDonation'}
value={input.customDonation}
min={config.MIN_AMOUNT}
Expand All @@ -54,7 +57,11 @@ const CheckoutForm: React.FunctionComponent = () => {
currency={config.CURRENCY}
onChange={handleInputChange}
/>
<button type="submit" disabled={!stripe}>
<button
className="checkout-style-background"
type="submit"
disabled={!stripe}
>
Donate {formatAmountForDisplay(input.customDonation, config.CURRENCY)}
</button>
</form>
Expand Down
12 changes: 12 additions & 0 deletions examples/with-stripe-typescript/components/CustomDonationInput.tsx
Expand Up @@ -9,6 +9,7 @@ type Props = {
currency: string
step: number
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
className?: string
}

const CustomDonationInput: React.FunctionComponent<Props> = ({
Expand All @@ -19,11 +20,13 @@ const CustomDonationInput: React.FunctionComponent<Props> = ({
currency,
step,
onChange,
className,
}) => (
<label>
Custom donation amount ({formatAmountForDisplay(min, currency)}-
{formatAmountForDisplay(max, currency)}):
<input
className={className}
type="number"
name={name}
value={value}
Expand All @@ -32,6 +35,15 @@ const CustomDonationInput: React.FunctionComponent<Props> = ({
step={step}
onChange={onChange}
></input>
<input
type="range"
name={name}
value={value}
min={min}
max={max}
step={step}
onChange={onChange}
></input>
</label>
)

Expand Down
76 changes: 49 additions & 27 deletions examples/with-stripe-typescript/components/ElementsForm.tsx
Expand Up @@ -9,9 +9,33 @@ import * as config from '../config'

import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'

const CARD_OPTIONS = {
iconStyle: 'solid' as const,
style: {
base: {
iconColor: '#6772e5',
color: '#6772e5',
fontWeight: '500',
fontFamily: 'Roboto, Open Sans, Segoe UI, sans-serif',
fontSize: '16px',
fontSmoothing: 'antialiased',
':-webkit-autofill': {
color: '#fce883',
},
'::placeholder': {
color: '#6772e5',
},
},
invalid: {
iconColor: '#ef2961',
color: '#ef2961',
},
},
}

const ElementsForm: React.FunctionComponent = () => {
const [input, setInput] = useState({
customDonation: config.MIN_AMOUNT,
customDonation: Math.round(config.MAX_AMOUNT / config.AMOUNT_STEP),
cardholderName: '',
})
const [payment, setPayment] = useState({ status: 'initial' })
Expand All @@ -36,7 +60,7 @@ const ElementsForm: React.FunctionComponent = () => {
return (
<>
<h2>Error 😭</h2>
<p>{errorMessage}</p>
<p className="error-message">{errorMessage}</p>
</>
)

Expand All @@ -53,6 +77,8 @@ const ElementsForm: React.FunctionComponent = () => {

const handleSubmit: React.FormEventHandler<HTMLFormElement> = async e => {
e.preventDefault()
// Abort if form isn't valid
if (!e.currentTarget.reportValidity()) return
setPayment({ status: 'processing' })

// Create a PaymentIntent with the specified amount.
Expand Down Expand Up @@ -95,6 +121,7 @@ const ElementsForm: React.FunctionComponent = () => {
<>
<form onSubmit={handleSubmit}>
<CustomDonationInput
className="elements-style"
name="customDonation"
value={input.customDonation}
min={config.MIN_AMOUNT}
Expand All @@ -103,35 +130,30 @@ const ElementsForm: React.FunctionComponent = () => {
currency={config.CURRENCY}
onChange={handleInputChange}
/>
<fieldset>
<fieldset className="elements-style">
<legend>Your payment details:</legend>
<label>
Cardholder name:
<input
type="Text"
name="cardholderName"
onChange={handleInputChange}
required={true}
/>
</label>
<CardElement
options={{
style: {
base: {
fontSize: '16px',
color: '#424770',
'::placeholder': {
color: '#aab7c4',
},
},
invalid: {
color: '#9e2146',
},
},
}}
<input
placeholder="Cardholder name"
className="elements-style"
type="Text"
name="cardholderName"
onChange={handleInputChange}
required
/>
<div className="FormRow elements-style">
<CardElement
options={CARD_OPTIONS}
onChange={e => {
if (e.error) {
setPayment({ status: 'error' })
setErrorMessage(e.error.message ?? 'An unknown error occured')
}
}}
/>
</div>
</fieldset>
<button
className="elements-style-background"
type="submit"
disabled={
!['initial', 'succeeded', 'error'].includes(payment.status) ||
Expand Down
55 changes: 33 additions & 22 deletions examples/with-stripe-typescript/components/Layout.tsx
@@ -1,6 +1,6 @@
import React from 'react'
import Link from 'next/link'
import Head from 'next/head'
import Link from 'next/link'
import { Elements } from '@stripe/react-stripe-js'
import { loadStripe } from '@stripe/stripe-js'

Expand All @@ -19,25 +19,36 @@ const Layout: React.FunctionComponent<Props> = ({
<title>{title}</title>
<meta charSet="utf-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@thorwebdev" />
<meta name="twitter:title" content="TypeScript Next.js Stripe Example" />
<meta
name="twitter:description"
content="Full-stack TypeScript example using Next.js, react-stripe-js, and stripe-node."
/>
<meta
name="twitter:image"
content="https://nextjs-typescript-react-stripe-js.now.sh/social_card.png"
/>
</Head>
<header>
<nav>
<Link href="/">
<a>Home</a>
</Link>{' '}
|{' '}
<Link href="/donate-with-checkout">
<a>Donate with Checkout</a>
</Link>{' '}
|{' '}
<Link href="/donate-with-elements">
<a>Donate with Elements</a>
</Link>
</nav>
</header>
{children}
<footer>
<hr />
<div className="container">
<header>
<div className="header-content">
<Link href="/">
<a className="logo">
<img src="/logo.png" />
</a>
</Link>
<h1>
<span className="light">Stripe Sample</span>
<br />
Next.js, TypeScript, and Stripe 🔒💸
</h1>
</div>
</header>
{children}
</div>
<div className="banner">
<span>
This is a{' '}
<a
Expand All @@ -47,17 +58,17 @@ const Layout: React.FunctionComponent<Props> = ({
>
Stripe Sample
</a>
.{' '}
.{' View code on '}
<a
href="https://github.com/zeit/next.js/tree/canary/examples/with-stripe-typescript"
target="_blank"
rel="noopener noreferrer"
>
View code on GitHub
GitHub
</a>
.
</span>
</footer>
</div>
</Elements>
)

Expand Down
2 changes: 1 addition & 1 deletion examples/with-stripe-typescript/package.json
Expand Up @@ -17,7 +17,7 @@
"next": "latest",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"stripe": "^8.8.1",
"stripe": "^8.15.0",
"swr": "^0.1.16"
},
"devDependencies": {
Expand Down
9 changes: 9 additions & 0 deletions examples/with-stripe-typescript/pages/_app.tsx
@@ -0,0 +1,9 @@
import { AppProps } from 'next/app'

import '../styles.css'

function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}

export default MyApp
@@ -1,12 +1,9 @@
import { NextApiRequest, NextApiResponse } from 'next'

import Stripe from 'stripe'
const stripeSecretKey: string = process.env.STRIPE_SECRET_KEY!
const stripe = new Stripe(stripeSecretKey, {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: '2019-12-03',
typescript: true,
telemetry: true,
})

export default async (req: NextApiRequest, res: NextApiResponse) => {
Expand Down
Expand Up @@ -4,12 +4,9 @@ import { CURRENCY, MIN_AMOUNT, MAX_AMOUNT } from '../../../config'
import { formatAmountForStripe } from '../../../utils/stripe-helpers'

import Stripe from 'stripe'
const stripeSecretKey: string = process.env.STRIPE_SECRET_KEY!
const stripe = new Stripe(stripeSecretKey, {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: '2019-12-03',
typescript: true,
telemetry: true,
})

export default async (req: NextApiRequest, res: NextApiResponse) => {
Expand Down
Expand Up @@ -4,12 +4,9 @@ import { CURRENCY, MIN_AMOUNT, MAX_AMOUNT } from '../../../config'
import { formatAmountForStripe } from '../../../utils/stripe-helpers'

import Stripe from 'stripe'
const stripeSecretKey: string = process.env.STRIPE_SECRET_KEY!
const stripe = new Stripe(stripeSecretKey, {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
// https://github.com/stripe/stripe-node#configuration
apiVersion: '2019-12-03',
typescript: true,
telemetry: true,
})

export default async (req: NextApiRequest, res: NextApiResponse) => {
Expand Down

0 comments on commit d5222f8

Please sign in to comment.