Skip to content

Commit

Permalink
added user verification for card payments
Browse files Browse the repository at this point in the history
  • Loading branch information
erikrakuscek committed Mar 15, 2024
1 parent 7d274ec commit 487d4b8
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 42 deletions.
53 changes: 35 additions & 18 deletions packages/commerce/components/checkout/contact-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,49 @@ import type { UseFormReturn } from 'react-hook-form'

const ContactInfo: React.FC<{
form: UseFormReturn<{
name: string;
email: string;
firstName: string
lastName: string
email: string
}, any, {
name: string;
email: string;
firstName: string
lastName: string
email: string
}>,
}> = ({
form,
}) => {
return (
<Form {...form}>
<form className='text-left'>
<div className='flex flex-col sm:flex-row gap-4'>
<FormField
control={form.control}
name='name'
render={({ field }) => (
<FormItem className='space-y-1 w-full'>
<FormLabel>Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className='flex flex-col gap-4'>
<div className='flex flex-col sm:flex-row gap-4'>
<FormField
control={form.control}
name='firstName'
render={({ field }) => (
<FormItem className='space-y-1 w-full'>
<FormLabel>First name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='lastName'
render={({ field }) => (
<FormItem className='space-y-1 w-full'>
<FormLabel>Last name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name='email'
Expand Down
8 changes: 5 additions & 3 deletions packages/commerce/components/checkout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import { Cart } from '..'
import Payment from './payment'

const contactFormSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters.'),
firstName: z.string().min(1, 'Enter your first name.'),
lastName: z.string().min(1, 'Enter your last name.'),
email: z.string().email(),
})

Expand All @@ -33,14 +34,15 @@ const Checkout: React.FC<{toggleCheckout: () => void}> = observer(({toggleChecko
const contactForm = useForm<z.infer<typeof contactFormSchema>>({
resolver: zodResolver(contactFormSchema),
defaultValues: {
name: auth.user?.displayName ?? '',
firstName: auth.user?.displayName ?? '',
lastName: '',
email: auth.user?.email ?? '',
},
})

useEffect(() => {
if (auth.loggedIn) {
contactForm.setValue('name', auth.user?.displayName ?? '')
contactForm.setValue('firstName', auth.user?.displayName ?? '')
contactForm.setValue('email', auth.user?.email ?? '')
}
}, [auth.loggedIn])
Expand Down
15 changes: 9 additions & 6 deletions packages/commerce/components/checkout/payment/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ const Payment: React.FC<{
orderId?: string
setOrderId: (orderId?: string) => void
contactForm: UseFormReturn<{
name: string;
email: string;
firstName: string
lastName: string
email: string
}, any, {
name: string;
email: string;
firstName: string
lastName: string
email: string
}>,
}> = observer(({
setCurrentStep,
Expand All @@ -34,10 +36,10 @@ const Payment: React.FC<{

const storePaymentInfo = async (paymentInfo: any) => {
if (auth.user) {
const {name, email} = contactForm.getValues()
const {firstName, lastName, email} = contactForm.getValues()
let id
if (!orderId) {
id = await cmmc.createOrder(email ? email : auth.user.email, name)
id = await cmmc.createOrder(email ? email : auth.user.email, `${firstName} ${lastName}`)
setOrderId(id)
}
if (id) {
Expand Down Expand Up @@ -77,6 +79,7 @@ const Payment: React.FC<{
transactionStatus={transactionStatus}
setTransactionStatus={setTransactionStatus}
storePaymentInfo={storePaymentInfo}
contactForm={contactForm}
/>
</TabsContent>
<TabsContent value='crypto'>
Expand Down
70 changes: 57 additions & 13 deletions packages/commerce/components/checkout/payment/pay-with-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,64 @@

// @ts-ignore
import { CreditCard, PaymentForm } from 'react-square-web-payments-sdk'
import type { UseFormReturn } from 'react-hook-form'
import { ApplyTypography, Button } from '@hanzo/ui/primitives'
import { useCommerce, type TransactionStatus } from '../../..'
import PaymentMethods from './payment-methods'
import { ApplyTypography, Button } from '@hanzo/ui/primitives'
import { processSquareCardPayment } from '../../../util'

const PayWithCard: React.FC<{
setCurrentStep: (currentStep: number) => void
transactionStatus: TransactionStatus
setTransactionStatus: (status: TransactionStatus) => void
storePaymentInfo: (paymentInfo: any) => Promise<void>
contactForm: UseFormReturn<{
firstName: string
lastName: string
email: string
}, any, {
firstName: string
lastName: string
email: string
}>
}> = ({
setCurrentStep,
transactionStatus,
setTransactionStatus,
storePaymentInfo
storePaymentInfo,
contactForm,
}) => {
const c = useCommerce()
const cmmc = useCommerce()

const cardTokenizeResponseReceived = async (
token: { token: any },
verifiedBuyer: { token: string }
) => {
contactForm.handleSubmit(async () => {
setTransactionStatus('paid')
const res = await processSquareCardPayment(token.token, cmmc.cartTotal, verifiedBuyer.token)
if (res) {
await storePaymentInfo(res)
setTransactionStatus('confirmed')
} else {
setTransactionStatus('error')
}
})()
}

const createVerificationDetails = () => {
const {firstName, lastName, email} = contactForm.getValues()
return {
amount: cmmc.cartTotal.toFixed(2),
billingContact: {
givenName: firstName,
familyName: lastName,
email,
},
currencyCode: 'USD',
intent: 'CHARGE',
}
}

return (
<PaymentForm
Expand All @@ -31,16 +72,14 @@ const PayWithCard: React.FC<{
* Invoked when payment form receives the result of a tokenize generation
* request. The result will be a valid credit card or wallet token, or an error.
*/
cardTokenizeResponseReceived={async (token: { token: any }, verifiedBuyer: any) => {
setTransactionStatus('paid')
const res = await processSquareCardPayment(token.token, c.cartTotal)
if (res) {
await storePaymentInfo(res)
setTransactionStatus('confirmed')
} else {
setTransactionStatus('error')
}
}}
cardTokenizeResponseReceived={cardTokenizeResponseReceived}
/**
* This function enable the Strong Customer Authentication (SCA) flow
*
* We strongly recommend use this function to verify the buyer and reduce
* the chance of fraudulent transactions.
*/
createVerificationDetails={createVerificationDetails}
/**
* Identifies the location of the merchant that is taking the payment.
* Obtained from the Square Application Dashboard - Locations tab.
Expand Down Expand Up @@ -111,6 +150,11 @@ const PayWithCard: React.FC<{
},
}}
/>
{transactionStatus === 'error' && (
<ApplyTypography>
<p className='mx-auto text-destructive'>There was an error processing your payment.</p>
</ApplyTypography>
)}
</>
)}
</div>
Expand Down
5 changes: 3 additions & 2 deletions packages/commerce/util/square-payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const { paymentsApi } = new Client({
environment: process.env.SQUARE_ENVIRONMENT as Environment
})

const processPayment = async (sourceId: string, amount: number) => {
const processPayment = async (sourceId: string, amount: number, verificationToken: string) => {
// Square API accepts amount in cents
const amountInCents = amount * 100

Expand All @@ -28,7 +28,8 @@ const processPayment = async (sourceId: string, amount: number) => {
amountMoney: {
currency: 'USD',
amount: BigInt(amountInCents)
}
},
verificationToken
})
return result
} catch (error) {
Expand Down

0 comments on commit 487d4b8

Please sign in to comment.