Skip to content

Commit 5284fd4

Browse files
committed
Create new component to setup card details
1 parent c6e8f64 commit 5284fd4

File tree

9 files changed

+301
-225
lines changed

9 files changed

+301
-225
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { LoadingButton } from '@mui/lab';
2+
import { Box, Grid, Link, Typography } from '@mui/material';
3+
import {
4+
PaymentElement,
5+
useElements,
6+
useStripe,
7+
} from '@stripe/react-stripe-js';
8+
import { useState } from 'react';
9+
import { useSnackbar } from '../../providers/SnackProvider';
10+
import * as paymentService from '../../services/payment';
11+
import { useAppDispatch } from '../../state';
12+
import { fetchUserBalanceAsync } from '../../state/auth/reducer';
13+
14+
interface CardSetupFormProps {
15+
onCardSetup: () => void; // Prop para notificar cuando la tarjeta está lista
16+
}
17+
18+
export const CardSetupForm: React.FC<CardSetupFormProps> = ({
19+
onCardSetup,
20+
}) => {
21+
const stripe = useStripe();
22+
const elements = useElements();
23+
const [isLoading, setIsLoading] = useState(false);
24+
const dispatch = useAppDispatch();
25+
const { showError } = useSnackbar();
26+
27+
const handleCardSetup = async () => {
28+
if (!stripe || !elements) {
29+
showError('Stripe.js has not yet loaded.');
30+
return;
31+
}
32+
33+
// Trigger form validation and card details collection
34+
const { error: submitError } = await elements.submit();
35+
if (submitError) {
36+
showError(submitError);
37+
return;
38+
}
39+
40+
setIsLoading(true);
41+
try {
42+
const clientSecret = await paymentService.createSetupIntent();
43+
44+
if (!clientSecret) {
45+
throw new Error('Failed to create SetupIntent.');
46+
}
47+
48+
const { error: stripeError, setupIntent } = await stripe.confirmSetup({
49+
elements,
50+
clientSecret,
51+
confirmParams: {
52+
return_url: window.location.href,
53+
},
54+
redirect: 'if_required',
55+
});
56+
57+
if (stripeError) {
58+
throw stripeError;
59+
}
60+
61+
const success = await paymentService.confirmSetupIntent(
62+
setupIntent?.id ?? '',
63+
);
64+
65+
if (!success) {
66+
throw new Error('Card setup confirmation failed.');
67+
}
68+
69+
dispatch(fetchUserBalanceAsync());
70+
onCardSetup();
71+
} catch (err: any) {
72+
showError(err.message || 'An error occurred while setting up the card.');
73+
}
74+
setIsLoading(false);
75+
};
76+
77+
return (
78+
<Box>
79+
<Grid container spacing={4}>
80+
<Grid item xs={12}>
81+
<PaymentElement
82+
options={{ fields: { billingDetails: { name: 'auto' } } }}
83+
/>
84+
</Grid>
85+
<Grid item xs={12}>
86+
<LoadingButton
87+
color="primary"
88+
variant="contained"
89+
fullWidth
90+
size="large"
91+
onClick={handleCardSetup}
92+
loading={isLoading}
93+
>
94+
Save card details
95+
</LoadingButton>
96+
</Grid>
97+
<Grid item xs={12}>
98+
<Link
99+
href="https://humanprotocol.org/app/terms-and-conditions"
100+
target="_blank"
101+
>
102+
<Typography variant="caption" component="p" textAlign="center">
103+
Terms & conditions
104+
</Typography>
105+
</Link>
106+
</Grid>
107+
</Grid>
108+
</Box>
109+
);
110+
};

packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx

Lines changed: 11 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { KVStoreKeys, NETWORKS } from '@human-protocol/sdk';
33
import { LoadingButton } from '@mui/lab';
44
import {
55
Box,
6-
BoxProps,
76
Button,
87
Checkbox,
98
FormControl,
@@ -14,39 +13,19 @@ import {
1413
TextField,
1514
Typography,
1615
} from '@mui/material';
17-
import { styled } from '@mui/material/styles';
18-
import {
19-
CardCvcElement,
20-
CardExpiryElement,
21-
CardNumberElement,
22-
useElements,
23-
useStripe,
24-
} from '@stripe/react-stripe-js';
16+
import { useElements, useStripe } from '@stripe/react-stripe-js';
2517
import { useEffect, useMemo, useState } from 'react';
2618
import { Address } from 'viem';
2719
import { useReadContract } from 'wagmi';
2820
import { CURRENCY } from '../../../constants/payment';
21+
2922
import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider';
3023
import * as jobService from '../../../services/job';
3124
import * as paymentService from '../../../services/payment';
3225
import { useAppDispatch, useAppSelector } from '../../../state';
3326
import { fetchUserBalanceAsync } from '../../../state/auth/reducer';
3427
import { JobType } from '../../../types';
3528

36-
const StripeElement = styled(Box)<BoxProps & { disabled?: boolean }>(
37-
(props) => ({
38-
border: '1px solid rgba(50,10,141,0.5)',
39-
borderRadius: '4px',
40-
height: '56px',
41-
padding: '18px 16px',
42-
pointerEvents: props.disabled ? 'none' : 'auto',
43-
opacity: props.disabled ? 0.2 : 1,
44-
'&:focus-within': {
45-
borderColor: '#32108D',
46-
},
47-
}),
48-
);
49-
5029
export const FiatPayForm = ({
5130
onStart,
5231
onFinish,
@@ -64,10 +43,7 @@ export const FiatPayForm = ({
6443

6544
const [payWithAccountBalance, setPayWithAccountBalance] = useState(false);
6645
const [isLoading, setIsLoading] = useState(false);
67-
const [paymentData, setPaymentData] = useState({
68-
amount: '',
69-
name: '',
70-
});
46+
const [amount, setAmount] = useState<string>('');
7147
const [jobLauncherAddress, setJobLauncherAddress] = useState<string>();
7248
const [minFee, setMinFee] = useState<number>(0.01);
7349

@@ -94,7 +70,7 @@ export const FiatPayForm = ({
9470
},
9571
});
9672

97-
const fundAmount = paymentData.amount ? Number(paymentData.amount) : 0;
73+
const fundAmount = amount ? Number(amount) : 0;
9874
const feeAmount = Math.max(
9975
minFee,
10076
fundAmount * (Number(jobLauncherFee) / 100),
@@ -114,78 +90,27 @@ export const FiatPayForm = ({
11490
return totalAmount - accountAmount;
11591
}, [payWithAccountBalance, totalAmount, accountAmount]);
11692

117-
const cardElementsDisabled = useMemo(() => {
118-
if (paymentData.amount) {
119-
return creditCardPayAmount <= 0;
120-
} else {
121-
if (payWithAccountBalance) return true;
122-
return false;
123-
}
124-
}, [paymentData, payWithAccountBalance, creditCardPayAmount]);
125-
126-
const handlePaymentDataFormFieldChange = (
127-
fieldName: string,
128-
fieldValue: any,
129-
) => {
130-
setPaymentData({ ...paymentData, [fieldName]: fieldValue });
131-
};
132-
13393
const handlePay = async () => {
13494
if (!stripe || !elements) {
135-
// Stripe.js has not yet loaded.
136-
// Make sure to disable form submission until Stripe.js has loaded.
137-
// eslint-disable-next-line no-console
138-
console.error('Stripe.js has not yet loaded.');
95+
onError('Stripe.js has not yet loaded.');
13996
return;
14097
}
14198

14299
setIsLoading(true);
143-
144100
try {
145101
if (creditCardPayAmount > 0) {
146-
if (!paymentData.name) {
147-
throw new Error('Please enter name on card.');
148-
}
149-
150-
// Stripe elements validation
151-
const cardNumber = elements.getElement(CardNumberElement) as any;
152-
const cardExpiry = elements.getElement(CardExpiryElement) as any;
153-
const cardCvc = elements.getElement(CardCvcElement) as any;
154-
155-
if (!cardNumber || !cardExpiry || !cardCvc) {
156-
throw new Error('Card elements are not initialized');
157-
}
158-
if (cardNumber._invalid || cardNumber._empty) {
159-
throw new Error('Your card number is incomplete.');
160-
}
161-
if (cardExpiry._invalid || cardExpiry._empty) {
162-
throw new Error("Your card's expiration date is incomplete.");
163-
}
164-
if (cardCvc._invalid || cardCvc._empty) {
165-
throw new Error("Your card's security code is incomplete.");
166-
}
167-
168-
// send payment if creditCardPayment > 0
169102
const clientSecret = await paymentService.createFiatPayment({
170103
amount: creditCardPayAmount,
171-
currency: CURRENCY.usd,
104+
currency: 'usd',
172105
});
173106

174-
// stripe payment
175107
const { error: stripeError, paymentIntent } =
176-
await stripe.confirmCardPayment(clientSecret, {
177-
payment_method: {
178-
card: cardNumber,
179-
billing_details: {
180-
name: paymentData.name,
181-
},
182-
},
183-
});
108+
await stripe.confirmCardPayment(clientSecret);
109+
184110
if (stripeError) {
185111
throw stripeError;
186112
}
187113

188-
// confirm payment
189114
const success = await paymentService.confirmFiatPayment(
190115
paymentIntent.id,
191116
);
@@ -254,44 +179,15 @@ export const FiatPayForm = ({
254179
/>
255180
</Box>
256181
</Grid>
257-
<Grid item xs={12}>
258-
<StripeElement disabled={cardElementsDisabled}>
259-
<CardNumberElement id="card-number" />
260-
</StripeElement>
261-
</Grid>
262-
<Grid item xs={12} sm={6}>
263-
<StripeElement disabled={cardElementsDisabled}>
264-
<CardExpiryElement id="card-expiry" />
265-
</StripeElement>
266-
</Grid>
267-
<Grid item xs={12} sm={6}>
268-
<StripeElement disabled={cardElementsDisabled}>
269-
<CardCvcElement id="card-cvc" />
270-
</StripeElement>
271-
</Grid>
272-
<Grid item xs={12}>
273-
<TextField
274-
fullWidth
275-
variant="outlined"
276-
placeholder="Name on Card"
277-
value={paymentData.name}
278-
onChange={(e) =>
279-
handlePaymentDataFormFieldChange('name', e.target.value)
280-
}
281-
disabled={cardElementsDisabled}
282-
/>
283-
</Grid>
284182
<Grid item xs={12}>
285183
<FormControl fullWidth>
286184
<TextField
287185
fullWidth
288186
placeholder="Amount USD"
289187
variant="outlined"
290-
value={paymentData.amount}
188+
value={amount}
291189
type="number"
292-
onChange={(e) =>
293-
handlePaymentDataFormFieldChange('amount', e.target.value)
294-
}
190+
onChange={(e) => setAmount(e.target.value)}
295191
/>
296192
</FormControl>
297193
</Grid>
@@ -324,18 +220,6 @@ export const FiatPayForm = ({
324220
</Typography>
325221
)}
326222
</Box>
327-
{/* <Box
328-
sx={{
329-
display: 'flex',
330-
alignItems: 'center',
331-
justifyContent: 'space-between',
332-
py: 2,
333-
borderBottom: '1px solid #E5E7EB',
334-
}}
335-
>
336-
<Typography>Amount due</Typography>
337-
<Typography color="text.secondary">{totalAmount} USD</Typography>
338-
</Box> */}
339223
<Box
340224
sx={{
341225
display: 'flex',
@@ -373,16 +257,6 @@ export const FiatPayForm = ({
373257
{creditCardPayAmount.toFixed(2)} USD
374258
</Typography>
375259
</Stack>
376-
{/* <Stack
377-
direction="row"
378-
justifyContent="space-between"
379-
alignItems="center"
380-
>
381-
<Typography color="text.secondary">Fees</Typography>
382-
<Typography color="text.secondary">
383-
({JOB_LAUNCHER_FEE}%) {feeAmount} USD
384-
</Typography>
385-
</Stack> */}
386260
<Stack
387261
direction="row"
388262
justifyContent="space-between"
@@ -412,7 +286,7 @@ export const FiatPayForm = ({
412286
size="large"
413287
onClick={handlePay}
414288
loading={isLoading}
415-
disabled={!paymentData.amount}
289+
disabled={!amount}
416290
>
417291
Pay now
418292
</LoadingButton>

0 commit comments

Comments
 (0)