mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-20 12:03:11 -04:00
* eat: add Stripe card form * Apply suggestions from code review Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * feat: adjust payload and error handling * feat: readjust error handling * Apply suggestions from code review Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * feat: refactors from comments * feat: prevent submition during processing * feat: redefine isSubmitting Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * fix: show the proper paypal button on donate page * fix: handle errors from stripe Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com>
211 lines
5.7 KiB
TypeScript
211 lines
5.7 KiB
TypeScript
/* eslint-disable camelcase */
|
|
import React, { Component } from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
|
|
import { scriptLoader, scriptRemover } from '../../utils/script-loaders';
|
|
|
|
import type { AddDonationData } from './PaypalButton';
|
|
|
|
type PayPalButtonScriptLoaderProps = {
|
|
isMinimalForm: boolean | undefined;
|
|
clientId: string;
|
|
createOrder: (
|
|
data: unknown,
|
|
actions: {
|
|
order: {
|
|
create: (arg0: {
|
|
purchase_units: {
|
|
amount: { currency_code: string; value: string };
|
|
}[];
|
|
}) => unknown;
|
|
};
|
|
}
|
|
) => unknown;
|
|
createSubscription: (
|
|
data: unknown,
|
|
actions: {
|
|
subscription: { create: (arg0: { plan_id: string | null }) => unknown };
|
|
}
|
|
) => unknown;
|
|
isSubscription: boolean;
|
|
onApprove: (
|
|
data: AddDonationData,
|
|
actions?: { order: { capture: () => Promise<unknown> } }
|
|
) => unknown;
|
|
isPaypalLoading: boolean;
|
|
onCancel: () => unknown;
|
|
onError: () => unknown;
|
|
onLoad: () => void;
|
|
style: {
|
|
color: string;
|
|
height: number;
|
|
tagline: boolean;
|
|
};
|
|
planId: string | null;
|
|
};
|
|
|
|
type PayPalButtonScriptLoaderState = {
|
|
isSdkLoaded: boolean;
|
|
isSubscription: boolean;
|
|
};
|
|
|
|
declare global {
|
|
interface Window {
|
|
paypal: {
|
|
Buttons: {
|
|
driver: (
|
|
react: string,
|
|
{ React, ReactDOM }: { React: unknown; ReactDOM: unknown }
|
|
) => unknown;
|
|
[key: string]: unknown;
|
|
};
|
|
[key: string]: unknown;
|
|
};
|
|
}
|
|
}
|
|
|
|
export class PayPalButtonScriptLoader extends Component<
|
|
PayPalButtonScriptLoaderProps,
|
|
PayPalButtonScriptLoaderState
|
|
> {
|
|
// Lint says that paypal does not exist on window
|
|
state = {
|
|
isSdkLoaded: window.paypal ? true : false,
|
|
isSubscription: true
|
|
};
|
|
|
|
static displayName = 'PayPalButtonScriptLoader';
|
|
|
|
static getDerivedStateFromProps(
|
|
props: PayPalButtonScriptLoaderProps,
|
|
state: PayPalButtonScriptLoaderState
|
|
): { isSubscription: boolean } | null {
|
|
const { isSubscription } = props;
|
|
if (isSubscription !== state.isSubscription) {
|
|
return { isSubscription: isSubscription };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
componentDidMount(): void {
|
|
this.loadScript(this.props.isSubscription, true);
|
|
}
|
|
|
|
componentWillUnmount(): void {
|
|
scriptRemover('paypal-sdk');
|
|
}
|
|
|
|
componentDidUpdate(prevProps: {
|
|
isSubscription: boolean;
|
|
style: {
|
|
color: string;
|
|
height: number;
|
|
tagline: boolean;
|
|
};
|
|
isMinimalForm: boolean | undefined;
|
|
}): void {
|
|
// We need to load a new script if any of the following changes.
|
|
if (
|
|
prevProps.isSubscription !== this.state.isSubscription ||
|
|
prevProps.style.color !== this.props.style.color ||
|
|
prevProps.style.tagline !== this.props.style.tagline ||
|
|
prevProps.style.height !== this.props.style.height ||
|
|
prevProps.isMinimalForm !== this.props.isMinimalForm
|
|
) {
|
|
// eslint-disable-next-line react/no-did-update-set-state
|
|
this.setState({ isSdkLoaded: false });
|
|
this.loadScript(this.state.isSubscription, true);
|
|
}
|
|
}
|
|
|
|
loadScript(subscription: boolean, deleteScript: boolean | undefined): void {
|
|
if (deleteScript) scriptRemover('paypal-sdk');
|
|
const allowCardPayment = this.props.isMinimalForm ? 'card,' : '';
|
|
let queries = `?client-id=${this.props.clientId}&disable-funding=${allowCardPayment}credit,bancontact,blik,eps,giropay,ideal,mybank,p24,sepa,sofort,venmo`;
|
|
if (subscription) queries += '&vault=true&intent=subscription';
|
|
|
|
scriptLoader(
|
|
'paypal-sdk',
|
|
true,
|
|
`https://www.paypal.com/sdk/js${queries}`,
|
|
this.onScriptLoad,
|
|
'paypal'
|
|
);
|
|
}
|
|
|
|
onScriptLoad = (): void => {
|
|
this.setState({ isSdkLoaded: true });
|
|
this.props.onLoad();
|
|
};
|
|
|
|
captureOneTimePayment(
|
|
data: unknown,
|
|
actions: { order: { capture: () => Promise<unknown> } }
|
|
): unknown {
|
|
return actions.order.capture().then((details: unknown) => {
|
|
// TODO: this looks like a bug (it probably should not be passing details)
|
|
// but the api does not care what data it gets (yet). If we start to use
|
|
// that, this will need to be changed.
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore
|
|
return this.props.onApprove(details, data);
|
|
});
|
|
}
|
|
|
|
render(): JSX.Element {
|
|
const {
|
|
isSdkLoaded,
|
|
isSubscription
|
|
}: { isSdkLoaded: boolean; isSubscription: boolean } = this.state;
|
|
const {
|
|
onApprove,
|
|
onError,
|
|
onCancel,
|
|
createSubscription,
|
|
createOrder,
|
|
style
|
|
} = this.props;
|
|
|
|
if (!isSdkLoaded) return <></>;
|
|
|
|
// TODO: fill in the full list of props instead of any
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const Button: React.ComponentType<any> = window.paypal.Buttons.driver(
|
|
'react',
|
|
{
|
|
React,
|
|
ReactDOM
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
) as React.ComponentType<any>;
|
|
|
|
return (
|
|
<Button
|
|
createOrder={isSubscription ? null : createOrder}
|
|
createSubscription={isSubscription ? createSubscription : null}
|
|
onApprove={
|
|
isSubscription
|
|
? (
|
|
data: AddDonationData,
|
|
actions: { order: { capture: () => Promise<unknown> } }
|
|
) => onApprove(data, actions)
|
|
: (
|
|
data: {
|
|
[key: string]: unknown;
|
|
error: string | null;
|
|
},
|
|
actions: { order: { capture: () => Promise<unknown> } }
|
|
) => this.captureOneTimePayment(data, actions)
|
|
}
|
|
onCancel={onCancel}
|
|
onError={onError}
|
|
style={style}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
PayPalButtonScriptLoader.displayName = 'PayPalButtonScriptLoader';
|
|
|
|
export default PayPalButtonScriptLoader;
|