Skip to content

Commit

Permalink
turbo payment intent
Browse files Browse the repository at this point in the history
  • Loading branch information
NickJ202 committed Feb 14, 2024
1 parent bd8a7bc commit 249599f
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 34 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"dependencies": {
"@irys/sdk": "^0.1.0-a1",
"@permaweb/stampjs": "0.6.1",
"@rive-app/react-canvas": "^4.5.4",
"@stripe/react-stripe-js": "^2.4.0",
"@stripe/stripe-js": "^2.4.0",
"@types/react-router-dom": "^5.3.3",
"arbundles": "^0.10.1",
"arweave-wallet-connector": "^1.0.2",
Expand Down
2 changes: 1 addition & 1 deletion src/components/molecules/Modal/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const Container = styled.div<{
max-width: ${(props) => (props.noHeader ? '100%' : '90vw')};
background: ${(props) => (props.noHeader ? 'transparent' : props.theme.colors.container.primary.background)};
border-radius: ${STYLING.dimensions.radius.primary};
margin: 60px auto;
margin: 20px auto;
`;

export const Header = styled.div`
Expand Down
178 changes: 148 additions & 30 deletions src/components/molecules/TurboBalanceFund/TurboBalanceFund.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import React from 'react';
import { Elements, PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
import { useTheme } from 'styled-components';

import { Button } from 'components/atoms/Button';
import { FormField } from 'components/atoms/FormField';
import { Loader } from 'components/atoms/Loader';
import { Notification } from 'components/atoms/Notification';
import { STRIPE_PUBLISHABLE_KEY, STYLING } from 'helpers/config';
import { getTurboCheckoutEndpoint, getTurboPriceWincEndpoint } from 'helpers/endpoints';
import { NotificationResponseType } from 'helpers/types';
import { formatTurboAmount, formatUSDAmount, getARAmountFromWinc } from 'helpers/utils';
import { useArweaveProvider } from 'providers/ArweaveProvider';
import { useLanguageProvider } from 'providers/LanguageProvider';
Expand All @@ -13,13 +19,121 @@ import { Modal } from '../Modal';
import * as S from './styles';
import { IProps } from './types';

const stripePromise = loadStripe(STRIPE_PUBLISHABLE_KEY);

function CheckoutForm(props: {
handleGoBack: () => void;
amount: number;
wincConversion: number;
currency: string;
handleClose: () => void;
}) {
const stripe = useStripe();
const elements = useElements();

const arProvider = useArweaveProvider();

const languageProvider = useLanguageProvider();
const language = languageProvider.object[languageProvider.current];

const [loading, setLoading] = React.useState<boolean>(false);
const [result, setResult] = React.useState<NotificationResponseType | null>(null);

const [mounting, setMounting] = React.useState<boolean>(true);

React.useEffect(() => {
(async function () {
await new Promise((r) => setTimeout(r, 500));
setMounting(false);
})();
}, []);

async function handleSubmit(e: any) {
e.preventDefault();
setLoading(true);
if (!stripe || !elements) {
return;
}
try {
if (arProvider.walletAddress) {
const paymentResponse = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `https://helix.arweave.dev`,
},
redirect: 'if_required',
});

if (paymentResponse.error) {
console.error(paymentResponse.error.message);
} else {
if (paymentResponse && paymentResponse.paymentIntent && paymentResponse.paymentIntent.status) {
if (paymentResponse.paymentIntent.status === 'succeeded') {
setResult({ status: true, message: language.successfullyFunded });
} else {
setResult({ status: false, message: language.errorOccurred });
}
}
}
}
} catch (e: any) {
console.error(e);
}
setLoading(false);
}

return (
<>
<S.COWrapperAlt className={'border-wrapper-alt1'}>
<S.COHeader>
<span>{language.amount}</span>
</S.COHeader>
<span>{`${formatUSDAmount(props.amount)} = ${formatTurboAmount(props.wincConversion)}`}</span>
</S.COWrapperAlt>
<S.CheckoutForm disabled={loading || result !== null}>
{mounting ? <Loader sm relative /> : <PaymentElement options={{ layout: 'accordion' }} />}
</S.CheckoutForm>
<S.MActions>
<Button
type={'primary'}
label={language.back}
handlePress={props.handleGoBack}
disabled={loading || result !== null}
noMinWidth
/>
<Button
type={'alt1'}
label={language.submit}
handlePress={handleSubmit}
disabled={loading || result !== null}
loading={loading}
formSubmit
noMinWidth
/>
</S.MActions>
{result && (
<Notification
message={result.message!}
callback={() => {
setResult(null);
props.handleClose();
}}
/>
)}
</>
);
}

export default function TurboBalanceFund(props: IProps) {
const theme = useTheme();

const arProvider = useArweaveProvider();
const languageProvider = useLanguageProvider();
const language = languageProvider.object[languageProvider.current];

const [checkout, setCheckout] = React.useState<any>(null);
const [checkoutStep, setCheckoutStep] = React.useState<'amount' | 'payment'>('amount');
const [clientSecret, setClientSecret] = React.useState<string>('');

const [currency, _setCurrency] = React.useState<string>('usd');
const [amount, setAmount] = React.useState<number>(0);
Expand Down Expand Up @@ -56,21 +170,23 @@ export default function TurboBalanceFund(props: IProps) {
try {
const checkoutResponse = await fetch(getTurboCheckoutEndpoint(arProvider.walletAddress, currency, amount));
if (checkoutResponse.ok) {
setCheckout(await checkoutResponse.json());
const checkoutResponseJson = await checkoutResponse.json();
setCheckout(checkoutResponseJson);
setClientSecret(checkoutResponseJson.paymentSession.client_secret);
}
} catch (e: any) {
console.error(e);
}
}
})();
}, [checkoutStep]);
}, [arProvider.walletAddress, checkoutStep]);

function getInvalidAmount() {
if (amount && (amount < 5 || amount > 10000)) return { status: true, message: language.invalidAmountTurbo };
return { status: false, message: null };
}

const DEFAULT_AMOUNTS = [10, 25, 50, 75];
const DEFAULT_AMOUNTS = [5, 10, 25, 50, 75];

function handleGoBack() {
setCheckout(null);
Expand Down Expand Up @@ -150,40 +266,42 @@ export default function TurboBalanceFund(props: IProps) {
</S.MWrapper>
);
case 'payment':
if (checkout && checkout.paymentSession && checkout.paymentSession.url) {
if (checkout && checkout.paymentSession && clientSecret) {
return (
<S.MWrapper>
<S.MInfo>
<p>{language.fundTurboPaymentHeader}</p>
<span>{language.fundTurboPaymentDetail}</span>
</S.MInfo>
<S.DWrapper>
<S.DHeader>
<span>{`${language.amount}: ${formatUSDAmount(amount)}`}</span>
</S.DHeader>
</S.DWrapper>
<S.COWrapperAlt className={'border-wrapper-alt1'}>
<S.COHeader>
<span>{language.conversion}</span>
</S.COHeader>
<span>
{fetchingConversion
? `${language.fetching}...`
: `${formatUSDAmount(amount)} = ${formatTurboAmount(wincConversion)}`}
</span>
</S.COWrapperAlt>
<S.MActions>
<Button type={'primary'} label={language.back} handlePress={handleGoBack} disabled={false} noMinWidth />
<Button
type={'alt1'}
label={language.goToPayment}
handlePress={() => window.open(checkout.paymentSession.url, '_blank')}
disabled={false}
loading={false}
formSubmit
noMinWidth
<Elements
stripe={stripePromise}
options={{
clientSecret: clientSecret,
appearance: {
theme: 'stripe',
variables: {
colorBackground: theme.colors.container.primary.background,
colorPrimary: theme.colors.font.alt5,
colorText: theme.colors.font.primary,
fontSizeBase: theme.typography.size.small,
fontWeightLight: theme.typography.weight.medium,
fontWeightNormal: theme.typography.weight.medium,
fontWeightMedium: theme.typography.weight.medium,
fontWeightBold: theme.typography.weight.medium,
borderRadius: STYLING.dimensions.radius.primary,
spacingUnit: '3.5px',
},
},
}}
>
<CheckoutForm
handleGoBack={handleGoBack}
wincConversion={wincConversion}
currency={currency}
amount={amount}
handleClose={props.handleClose}
/>
</S.MActions>
</Elements>
</S.MWrapper>
);
} else {
Expand Down
7 changes: 7 additions & 0 deletions src/components/molecules/TurboBalanceFund/styles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import styled from 'styled-components';

import { fadeIn2, open } from 'helpers/animations';
import { STYLING } from 'helpers/config';

export const MWrapper = styled.div`
Expand Down Expand Up @@ -82,3 +83,9 @@ export const COWrapperAlt = styled(COWrapper)`
export const LWrapper = styled.div`
margin: 0 auto 20px auto;
`;

export const CheckoutForm = styled.form<{ disabled: boolean }>`
pointer-events: ${(props) => (props.disabled ? 'none' : 'all')};
margin: 20px 0 0 0;
animation: ${open} ${fadeIn2};
`;
3 changes: 3 additions & 0 deletions src/helpers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,6 @@ export const REDIRECTS = {
};

export const DRE_NODE = 'https://dre-u.warp.cc/contract';

export const STRIPE_PUBLISHABLE_KEY =
'pk_live_51JUAtwC8apPOWkDLMQqNF9sPpfneNSPnwX8YZ8y1FNDl6v94hZIwzgFSYl27bWE4Oos8CLquunUswKrKcaDhDO6m002Yj9AeKj';
2 changes: 1 addition & 1 deletion src/helpers/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function getTurboPriceWincEndpoint(currency: string, amount: number) {
}

export function getTurboCheckoutEndpoint(walletAddress: string, currency: string, amount: number) {
return `${turboEndpoint}/top-up/checkout-session/${walletAddress}/${currency}/${amount * 100}`;
return `${turboEndpoint}/top-up/payment-intent/${walletAddress}/${currency}/${amount * 100}`;
}

export function getTurboBalanceEndpoint() {
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const language = {
fundTurboBalanceInfoHeader: `Buy Credits`,
fundTurboBalanceInfoDetail: `Credits will be automatically added to your Turbo balance, and you can start using them right away.`,
fundTurboPaymentHeader: `Complete Payment`,
fundTurboPaymentDetail: `In order to securely complete your payment, click Go to payment below. This will open a new window that will allow you to complete the purchase.`,
fundTurboPaymentDetail: `Payments are powered by ArDrive Turbo, and handled securely by Stripe.`,
goToPayment: `Go to payment`,
handle: `Handle`,
home: `Home`,
Expand Down
1 change: 1 addition & 0 deletions src/helpers/themes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export const theme = (currentTheme: any): DefaultTheme => ({
alt2: currentTheme.neutralA4,
alt3: currentTheme.neutral5,
alt4: currentTheme.neutral1,
alt5: currentTheme.primary1,
light1: currentTheme.light1,
},
form: {
Expand Down
5 changes: 5 additions & 0 deletions src/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,8 @@ export type UserBalancesType = {
};

export type GroupIndexType = { index: string; ids: string[] }[];

export type NotificationResponseType = {
status: boolean;
message: string | null;
};

0 comments on commit 249599f

Please sign in to comment.