add retries to order

This commit is contained in:
2026-01-15 19:58:57 -03:00
parent ca52384b53
commit 466936acf4
9 changed files with 402 additions and 273 deletions

View File

@@ -1,13 +1,17 @@
import type { Route } from './+types/route'
import { formatCEP } from '@brazilian-utils/brazilian-utils'
import { zodResolver } from '@hookform/resolvers/zod'
import { useRequest } from 'ahooks'
import {
AlertCircleIcon,
ArrowLeftRightIcon,
HelpCircleIcon
} from 'lucide-react'
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { Link } from 'react-router'
import { z } from 'zod'
import { Currency } from '@repo/ui/components/currency'
import {
@@ -52,7 +56,23 @@ import {
type Order as Order_
} from '@repo/ui/routes/orders/data'
import type { CreditCard as CreditCard_ } from '../_.$orgid.enrollments.buy/payment'
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger
} from '@repo/ui/components/ui/dialog'
import { Separator } from '@repo/ui/components/ui/separator'
import { Spinner } from '@repo/ui/components/ui/spinner'
import {
CreditCard,
creditCardSchema,
type CreditCard as CreditCardProps
} from '../_.$orgid.enrollments.buy/payment'
import type { Address } from '../_.$orgid.enrollments.buy/review'
import { useWizardStore } from '../_.$orgid.enrollments.buy/store'
@@ -65,12 +85,12 @@ export function meta() {
}
const PaymentMethodComponent = {
PIX: Pix,
BANK_SLIP: BankSlip,
CREDIT_CARD: CreditCard
PIX: PixPaymentMethod,
BANK_SLIP: BankSlipPaymentMethod,
CREDIT_CARD: CreditCardPaymentMethod
}
type Item_ = {
type Item = {
id: string
name: string
unit_price: number
@@ -82,18 +102,24 @@ type User = {
name: string
}
type Invoice = {
invoice_id: string
secure_url: string
}
type Order = Order_ & {
items: Item_[]
items: Item[]
interest_amount: number
due_date: string
created_at: string
subtotal: number
discount: number
address: Address
credit_card?: CreditCard_
credit_card?: CreditCardProps
coupon?: string
installments?: number
created_by?: User
invoice: Invoice
}
export async function loader({ context, request, params }: Route.LoaderArgs) {
@@ -115,11 +141,15 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
payment_method,
interest_amount,
discount,
invoice,
subtotal,
items = []
} = order
const Component = PaymentMethodComponent[payment_method]
const Component =
(PaymentMethodComponent as Record<string, React.ComponentType<any>>)[
payment_method
] ?? UnknownPaymentMethod
useEffect(() => {
reset()
@@ -168,7 +198,9 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
<ItemContent>
<ItemTitle>Forma de pagamento</ItemTitle>
<div className="text-muted-foreground text-sm leading-normal font-normal text-balance">
{Component && <Component {...order} />}
{Component && (
<Component {...order} invoice_id={invoice['invoice_id']} />
)}
</div>
</ItemContent>
</Item>
@@ -271,23 +303,50 @@ function Status({ status: s }: { status: string }) {
}
type PaymentMethodProps = {
id: string
status: string
total: number
invoice_id: string
installments: number
}
type CreditCardProps = PaymentMethodProps & {
type BankSlipPaymentMethodProps = PaymentMethodProps & {}
function BankSlipPaymentMethod({ status }: BankSlipPaymentMethodProps) {
return (
<ul className="flex max-lg:flex-col gap-x-1.5">
<li>Boleto bancário</li>
<li>
<Status status={status} />
</li>
</ul>
)
}
type PixPaymentMethodrops = PaymentMethodProps & {}
function PixPaymentMethod({}: PixPaymentMethodrops) {
return <>Pix</>
}
function UnknownPaymentMethod() {
return <>Deposito bancário</>
}
type CreditCardPaymentMethodProps = PaymentMethodProps & {
stats: { last_attempt_succeeded: boolean }
credit_card: { last4: string; brand: string }
}
function CreditCard({
function CreditCardPaymentMethod({
id,
status,
total,
credit_card,
installments,
invoice_id,
stats
}: CreditCardProps) {
}: CreditCardPaymentMethodProps) {
return (
<>
<ul className="flex max-lg:flex-col gap-x-1.5">
@@ -295,12 +354,12 @@ function CreditCard({
{credit_card.brand} (Crédito) **** {credit_card.last4}
</li>
<li>
{!stats.last_attempt_succeeded ? (
{stats.last_attempt_succeeded === false ? (
<Badge
variant="outline"
className="text-red-400 border-red-400 px-1.5"
>
<AlertCircleIcon /> Transação negada
<AlertCircleIcon /> Pagamento não aprovado
</Badge>
) : (
<Status status={status} />
@@ -312,32 +371,94 @@ function CreditCard({
{installments}x <Currency>{total / Number(installments)}</Currency>
</p>
{!stats.last_attempt_succeeded ? (
{stats.last_attempt_succeeded === false ? (
<div className="flex justify-center mt-2">
<Button size="sm" variant="secondary" className="cursor-pointer">
<ArrowLeftRightIcon /> Tentar com outro cartão
</Button>
<CreditCardPaymentDialog
id={id}
total={total}
installments={installments}
invoice_id={invoice_id}
>
<Button size="sm" variant="secondary" className="cursor-pointer">
<ArrowLeftRightIcon /> Pagar com outro cartão
</Button>
</CreditCardPaymentDialog>
</div>
) : null}
</>
)
}
type BankSlipProps = PaymentMethodProps & {}
const formSchema = z.object({
credit_card: creditCardSchema
})
type Schema = z.input<typeof formSchema>
function CreditCardPaymentDialog({
children,
id,
installments,
invoice_id,
total
}) {
const { runAsync } = useRequest(
async ({ credit_card }) => {
return await fetch(`/~/api/orders/${id}/payment-retries`, {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify({ credit_card, installments, invoice_id })
})
},
{ manual: true }
)
const { control, handleSubmit, formState } = useForm<Schema>({
resolver: zodResolver(formSchema)
})
const onSubmit = async ({ credit_card }: Schema) => {
const r = await runAsync({ credit_card })
console.log(r.ok)
console.log(await r.json())
}
function BankSlip({ status }: BankSlipProps) {
return (
<ul className="flex max-lg:flex-col gap-x-1.5">
<li>Boleto bancário</li>
<li>
<Status status={status} />
</li>
</ul>
<Dialog>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Novo cartão de crédito</DialogTitle>
<DialogDescription>
Use um novo cartão para concluir o pagamento. Nenhuma cobrança foi
realizada no cartão anterior.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<CreditCard control={control} />
<Separator />
<DialogFooter className="*:cursor-pointer">
<DialogClose asChild>
<Button
type="button"
variant="link"
className="text-black dark:text-white"
tabIndex={-1}
>
Cancelar
</Button>
</DialogClose>
<Button type="submit" disabled={formState.isSubmitting}>
{formState.isSubmitting && <Spinner />}
Pagar <Currency>{total}</Currency>
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}
type PixProps = PaymentMethodProps & {}
function Pix({}: PixProps) {
return <>Pix</>
}