Skip to content

Commit

Permalink
Add CustomCheckoutProvider (#439)
Browse files Browse the repository at this point in the history
* pl - add .vscode to .gitignore

* pl - bump stripe-js version

* pl - add CustomCheckoutProvider

* pl - add CustomCheckout example in storybook

* pl - render null when customCheckoutSdk is not set yet (not resolved yet)

* pl - move useStripe back to Elements and default to Elements missing
error message

* pl - update the CustomCheckout storybook example

* pl - memorize customCheckoutContextValue

* pl - add tests to cover the case nested Elements +
CustomCheckoutProvider context

* pl - fix the ServerElement

* pl - change to useRef instead of state to prevent multiple calls of initCustomCheckout
  • Loading branch information
pololi-stripe authored Sep 8, 2023
1 parent aba8948 commit 3b993da
Show file tree
Hide file tree
Showing 13 changed files with 1,909 additions and 65 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ es
lib
*.log
.vim
.vscode/
185 changes: 185 additions & 0 deletions examples/hooks/11-Custom-Checkout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import React from 'react';
import {loadStripe} from '@stripe/stripe-js';
import {
PaymentElement,
useStripe,
CustomCheckoutProvider,
useCustomCheckout,
AddressElement,
} from '../../src';

import '../styles/common.css';

const CustomerDetails = () => {
const {
phoneNumber,
updatePhoneNumber,
email,
updateEmail,
} = useCustomCheckout();

const handlePhoneNumberChange = (event) => {
updatePhoneNumber(event.target.value);
};

const handleEmailChange = (event) => {
updateEmail(event.target.value);
};

return (
<div>
<h3>Customer Details</h3>
<label htmlFor="phoneNumber">Phone Number</label>
<input
id="phoneNumber"
name="phoneNumber"
type="text"
autoComplete="off"
onChange={handlePhoneNumberChange}
value={phoneNumber || ''}
/>
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
type="email"
autoComplete="email"
onChange={handleEmailChange}
value={email || ''}
/>
</div>
);
};

const CheckoutForm = () => {
const customCheckout = useCustomCheckout();
const [status, setStatus] = React.useState();
const [loading, setLoading] = React.useState(false);
const stripe = useStripe();

React.useEffect(() => {
const {confirmationRequirements} = customCheckout || {};
setStatus(
confirmationRequirements && confirmationRequirements.length > 0
? `Missing: ${confirmationRequirements.join(', ')}`
: ''
);
}, [customCheckout, setStatus]);

const handleSubmit = async (event) => {
event.preventDefault();

if (!stripe || !customCheckout) {
return;
}

const {canConfirm, confirm} = customCheckout;
if (!canConfirm) {
return;
}

try {
setLoading(true);
await confirm({return_url: window.location.href});
setLoading(false);
} catch (err) {
console.error(err);
setStatus(err.message);
}
};

const buttonDisabled =
!stripe || !customCheckout || !customCheckout.canConfirm || loading;

return (
<form onSubmit={handleSubmit}>
<CustomerDetails />
<h3>Payment Dettails</h3>
<PaymentElement />
<h3>Billing Details</h3>
<AddressElement options={{mode: 'billing'}} />
<button type="submit" disabled={buttonDisabled}>
{loading ? 'Processing...' : 'Pay'}
</button>
{status && <p>{status}</p>}
</form>
);
};

const THEMES = ['stripe', 'flat', 'night'];

const App = () => {
const [pk, setPK] = React.useState(
window.sessionStorage.getItem('react-stripe-js-pk') || ''
);
const [clientSecret, setClientSecret] = React.useState('');

React.useEffect(() => {
window.sessionStorage.setItem('react-stripe-js-pk', pk || '');
}, [pk]);

const [stripePromise, setStripePromise] = React.useState();
const [theme, setTheme] = React.useState('stripe');

const handleSubmit = (e) => {
e.preventDefault();
setStripePromise(
loadStripe(pk, {
betas: ['custom_checkout_beta_1'],
})
);
};

const handleThemeChange = (e) => {
setTheme(e.target.value);
};

const handleUnload = () => {
setStripePromise(null);
setClientSecret(null);
};

return (
<>
<form onSubmit={handleSubmit}>
<label>
CheckoutSession client_secret
<input
value={clientSecret}
onChange={(e) => setClientSecret(e.target.value)}
/>
</label>
<label>
Publishable key{' '}
<input value={pk} onChange={(e) => setPK(e.target.value)} />
</label>
<button style={{marginRight: 10}} type="submit">
Load
</button>
<button type="button" onClick={handleUnload}>
Unload
</button>
<label>
Theme
<select onChange={handleThemeChange}>
{THEMES.map((val) => (
<option key={val} value={val}>
{val}
</option>
))}
</select>
</label>
</form>
{stripePromise && clientSecret && (
<CustomCheckoutProvider
stripe={stripePromise}
options={{clientSecret, elementsOptions: {appearance: {theme}}}}
>
<CheckoutForm />
</CustomCheckoutProvider>
)}
</>
);
};

export default App;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"@babel/preset-env": "^7.7.1",
"@babel/preset-react": "^7.7.0",
"@storybook/react": "^6.5.0-beta.8",
"@stripe/stripe-js": "^2.0.0",
"@stripe/stripe-js": "2.1.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.1.1",
"@testing-library/react-hooks": "^8.0.0",
Expand Down
Loading

0 comments on commit 3b993da

Please sign in to comment.