client/components/mma/paymentUpdate/card/StripeCheckoutSessionButton.tsx (95 lines of code) (raw):
import { css } from '@emotion/react';
import { space } from '@guardian/source/foundations';
import {
Button,
SvgArrowRightStraight,
} from '@guardian/source/react-components';
import * as Sentry from '@sentry/browser';
import type { PaymentMethod } from '@stripe/stripe-js';
import { useState } from 'react';
import type { StripeCreateCheckoutSessionRequest } from '@/shared/requests/stripe-create-checkout-session';
import { STRIPE_PUBLIC_KEY_HEADER } from '../../../../../shared/stripeSetupIntent';
import { LoadingCircleIcon } from '../../shared/assets/LoadingCircleIcon';
export enum StripeCheckoutSessionPaymentMethodType {
Card = 'card',
}
export interface StripeCheckoutSessionButtonProps {
stripeApiKey: string;
productTypeUrlPart: string;
paymentMethodType: StripeCheckoutSessionPaymentMethodType;
subscriptionId: string;
}
/**
* https://docs.stripe.com/api/checkout/sessions/object
*/
export interface StripeCheckoutSession {
id: string;
url?: string;
/**
* https://docs.stripe.com/api/setup_intents/object
*/
setup_intent?: {
/**
* https://docs.stripe.com/api/payment_methods/object
*/
payment_method?: PaymentMethod;
};
}
export const StripeCheckoutSessionButton = (
props: StripeCheckoutSessionButtonProps,
) => {
const [preparingCheckout, setPreparingCheckout] = useState<boolean>(false);
const loadSetupIntent = () => {
// Start loading
setPreparingCheckout(true);
// Create Checkout Session on the server
const body: StripeCreateCheckoutSessionRequest = {
paymentMethodType: props.paymentMethodType,
productTypeUrlPart: props.productTypeUrlPart,
subscriptionId: props.subscriptionId,
};
fetch('/api/payment/checkout-session', {
method: 'POST',
credentials: 'include',
headers: {
[STRIPE_PUBLIC_KEY_HEADER]: props.stripeApiKey,
},
body: JSON.stringify(body),
})
.then((response) => {
if (response.ok) {
return response.json();
} else {
throw new Error(
`Failed to create CheckoutSession : ${
response.status
} ${
response.statusText
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- we believe this function will not evaluate to '[object Object'
} : ${response.text()}`,
);
}
})
.then((checkoutSession: StripeCheckoutSession) => {
// Redirect to Checkout
window.location.href = checkoutSession.url ?? '/';
})
.catch((error) => {
Sentry.captureException(error);
setPreparingCheckout(false);
});
};
return (
<Button
disabled={preparingCheckout}
priority="primary"
onClick={() => {
// Load the setup intent
loadSetupIntent();
}}
icon={
preparingCheckout ? (
<LoadingCircleIcon
additionalCss={css`
padding: 3px;
`}
/>
) : (
<SvgArrowRightStraight />
)
}
iconSide="right"
cssOverrides={css`
margin-top: ${space[4]}px;
margin-bottom: ${space[9]}px;
`}
>
Update payment method
</Button>
);
};