check status to payment retries

This commit is contained in:
2026-01-16 18:08:07 -03:00
parent 61c01956b5
commit f593b21a73
4 changed files with 98 additions and 78 deletions

View File

@@ -36,8 +36,14 @@ def payment_retries(
with dyn.transact_writer() as transact: with dyn.transact_writer() as transact:
transact.condition( transact.condition(
key=KeyPair(order_id, '0'), key=KeyPair(order_id, '0'),
cond_expr='attribute_exists(sk) AND installments = :installments', cond_expr='attribute_exists(sk) \
AND installments = :installments \
AND #status = :pending',
expr_attr_names={
'#status': 'status',
},
expr_attr_values={ expr_attr_values={
':pending': 'PENDING',
':installments': installments, ':installments': installments,
}, },
exc_cls=OrderConflictError, exc_cls=OrderConflictError,

View File

@@ -28,7 +28,7 @@
// Seeds for Order // Seeds for Order
// file: tests/routes/orders/test_payment_retries.py // file: tests/routes/orders/test_payment_retries.py
{"id": "4b23f6f5-5377-476b-b1de-79427c0295f6", "sk": "0", "installments": 3} {"id": "4b23f6f5-5377-476b-b1de-79427c0295f6", "sk": "0", "installments": 3, "status": "PENDING"}
{"id": "4b23f6f5-5377-476b-b1de-79427c0295f6", "sk": "INVOICE", "invoice_id": "123"} {"id": "4b23f6f5-5377-476b-b1de-79427c0295f6", "sk": "INVOICE", "invoice_id": "123"}
{"id": "4b23f6f5-5377-476b-b1de-79427c0295f6", "sk": "TRANSACTION#STATS", "last_attempt_succeeded": false} {"id": "4b23f6f5-5377-476b-b1de-79427c0295f6", "sk": "TRANSACTION#STATS", "last_attempt_succeeded": false}

View File

@@ -490,10 +490,11 @@ function ActionMenu() {
}} }}
> >
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button variant="ghost" className="cursor-pointer"> <Button variant="ghost" className="cursor-pointer" size="icon-sm">
<EllipsisIcon /> <EllipsisIcon />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="end" className="p-0 overflow-hidden w-56"> <PopoverContent align="end" className="p-0 overflow-hidden w-56">
<div className="border-b p-2 text-xs text-muted-foreground font-medium"> <div className="border-b p-2 text-xs text-muted-foreground font-medium">
Envios recentes Envios recentes

View File

@@ -2,7 +2,7 @@ import type { Route } from './+types/route'
import { formatCEP } from '@brazilian-utils/brazilian-utils' import { formatCEP } from '@brazilian-utils/brazilian-utils'
import { zodResolver } from '@hookform/resolvers/zod' import { zodResolver } from '@hookform/resolvers/zod'
import { useRequest } from 'ahooks' import { useRequest, useToggle } from 'ahooks'
import { import {
AlertCircleIcon, AlertCircleIcon,
ArrowLeftRightIcon, ArrowLeftRightIcon,
@@ -13,7 +13,7 @@ import {
} from 'lucide-react' } from 'lucide-react'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import { Link } from 'react-router' import { Link, useRevalidator } from 'react-router'
import { z } from 'zod' import { z } from 'zod'
import { Currency } from '@repo/ui/components/currency' import { Currency } from '@repo/ui/components/currency'
@@ -31,13 +31,6 @@ import {
CardHeader, CardHeader,
CardTitle CardTitle
} from '@repo/ui/components/ui/card' } from '@repo/ui/components/ui/card'
import {
Command,
CommandEmpty,
CommandGroup,
CommandItem,
CommandList
} from '@repo/ui/components/ui/command'
import { import {
Item, Item,
ItemActions, ItemActions,
@@ -121,6 +114,13 @@ type User = {
type Invoice = { type Invoice = {
invoice_id: string invoice_id: string
secure_url: string secure_url: string
bank_slip?: {
bank_slip_pdf_url: string
}
pix?: {
qrcode: string
qrcode_text: string
}
} }
type Attempts = { type Attempts = {
@@ -180,6 +180,8 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
reset() reset()
}, []) }, [])
console.log(order)
return ( return (
<div className="space-y-2.5"> <div className="space-y-2.5">
<Breadcrumb> <Breadcrumb>
@@ -203,7 +205,8 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<ItemGroup className="grid lg:grid-cols-2 gap-4"> <ItemGroup className="grid lg:grid-cols-2 gap-4">
<Item variant="outline"> <Item variant="outline" className="items-start">
{/* Billing address */}
<ItemContent> <ItemContent>
<ItemTitle>Endereço de cobrança</ItemTitle> <ItemTitle>Endereço de cobrança</ItemTitle>
<ul className="text-muted-foreground text-sm leading-normal font-normal text-balance"> <ul className="text-muted-foreground text-sm leading-normal font-normal text-balance">
@@ -218,14 +221,12 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
</ul> </ul>
</ItemContent> </ItemContent>
</Item> </Item>
{/* Payment method */}
<Item variant="outline" className="items-start"> <Item variant="outline" className="items-start">
<ItemContent> <ItemContent>
<ItemTitle>Forma de pagamento</ItemTitle> <ItemTitle>Forma de pagamento</ItemTitle>
<div className="text-muted-foreground text-sm leading-normal font-normal text-balance"> <div className="text-muted-foreground text-sm leading-normal font-normal text-balance">
{Component && ( {Component && <Component {...order} invoice={invoice} />}
<Component {...order} invoice_id={invoice['invoice_id']} />
)}
</div> </div>
</ItemContent> </ItemContent>
@@ -315,8 +316,6 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
</Table> </Table>
</CardContent> </CardContent>
</Card> </Card>
{/*<pre>{JSON.stringify(order, null, 2)}</pre>*/}
</div> </div>
) )
} }
@@ -326,7 +325,7 @@ function Status({ status: s }: { status: string }) {
const { icon: Icon, color } = statuses?.[s] ?? { icon: HelpCircleIcon } const { icon: Icon, color } = statuses?.[s] ?? { icon: HelpCircleIcon }
return ( return (
<Badge variant="outline" className={cn(color, ' px-1.5')}> <Badge variant="outline" className={cn(color, 'px-1.5')}>
<Icon className={cn('stroke-2', color)} /> <Icon className={cn('stroke-2', color)} />
{status} {status}
</Badge> </Badge>
@@ -337,7 +336,7 @@ type PaymentMethodProps = {
id: string id: string
status: string status: string
total: number total: number
invoice_id: string invoice: Invoice
installments: number installments: number
} }
@@ -345,7 +344,7 @@ type BankSlipPaymentMethodProps = PaymentMethodProps & {}
function BankSlipPaymentMethod({ status }: BankSlipPaymentMethodProps) { function BankSlipPaymentMethod({ status }: BankSlipPaymentMethodProps) {
return ( return (
<ul className="flex max-lg:flex-col gap-x-1.5"> <ul className="flex gap-x-1.5">
<li>Boleto bancário</li> <li>Boleto bancário</li>
<li> <li>
<Status status={status} /> <Status status={status} />
@@ -356,8 +355,27 @@ function BankSlipPaymentMethod({ status }: BankSlipPaymentMethodProps) {
type PixPaymentMethodrops = PaymentMethodProps & {} type PixPaymentMethodrops = PaymentMethodProps & {}
function PixPaymentMethod({}: PixPaymentMethodrops) { function PixPaymentMethod({ invoice, status }: PixPaymentMethodrops) {
return <>Pix</> return (
<div className="space-y-2">
<ul className="flex gap-x-1.5">
<li>Pix</li>
<li>
<Status status={status} />
</li>
</ul>
{invoice?.pix ? (
<div
className="font-mono text-xs break-all p-2.5 border
rounded-md text-red-900 dark:text-yellow-600
bg-gray-50 dark:bg-muted/50 select-all"
>
{invoice.pix.qrcode_text}
</div>
) : null}
</div>
)
} }
function UnknownPaymentMethod() { function UnknownPaymentMethod() {
@@ -365,7 +383,7 @@ function UnknownPaymentMethod() {
} }
type CreditCardPaymentMethodProps = PaymentMethodProps & { type CreditCardPaymentMethodProps = PaymentMethodProps & {
stats: { last_attempt_succeeded: boolean } stats?: { last_attempt_succeeded: boolean }
credit_card: { last4: string; brand: string } credit_card: { last4: string; brand: string }
} }
@@ -375,22 +393,20 @@ function CreditCardPaymentMethod({
total, total,
credit_card, credit_card,
installments, installments,
invoice_id, invoice,
stats stats
}: CreditCardPaymentMethodProps) { }: CreditCardPaymentMethodProps) {
return ( return (
<> <>
<ul className="flex max-lg:flex-col gap-x-1.5"> <ul className="lg:flex gap-x-1">
<li> <li>
{credit_card.brand} (Crédito) **** {credit_card.last4} <Abbr maxLen={6}>{credit_card.brand}</Abbr>
</li> </li>
<li>(Crédito) **** {credit_card.last4}</li>
<li> <li>
{stats.last_attempt_succeeded === false ? ( {stats?.last_attempt_succeeded === false ? (
<Badge <Badge variant="outline" className="text-red-400 px-1.5">
variant="outline" <AlertCircleIcon /> Negado
className="text-red-400 border-red-400 px-1.5"
>
<AlertCircleIcon /> Pagamento não aprovado
</Badge> </Badge>
) : ( ) : (
<Status status={status} /> <Status status={status} />
@@ -402,13 +418,13 @@ function CreditCardPaymentMethod({
{installments}x <Currency>{total / Number(installments)}</Currency> {installments}x <Currency>{total / Number(installments)}</Currency>
</p> </p>
{stats.last_attempt_succeeded === false ? ( {stats?.last_attempt_succeeded === false && invoice?.invoice_id ? (
<div className="flex justify-center mt-2"> <div className="lg:flex justify-center mt-2">
<CreditCardPaymentDialog <CreditCardPaymentDialog
id={id} id={id}
total={total} total={total}
installments={installments} installments={installments}
invoice_id={invoice_id} invoice_id={invoice.invoice_id}
> >
<Button size="sm" variant="secondary" className="cursor-pointer"> <Button size="sm" variant="secondary" className="cursor-pointer">
<ArrowLeftRightIcon /> Pagar com outro cartão <ArrowLeftRightIcon /> Pagar com outro cartão
@@ -433,6 +449,8 @@ function CreditCardPaymentDialog({
invoice_id, invoice_id,
total total
}) { }) {
const revalidator = useRevalidator()
const [open, { set: setOpen, toggle }] = useToggle()
const { runAsync } = useRequest( const { runAsync } = useRequest(
async ({ credit_card }) => { async ({ credit_card }) => {
return await fetch(`/~/api/orders/${id}/payment-retries`, { return await fetch(`/~/api/orders/${id}/payment-retries`, {
@@ -448,13 +466,13 @@ function CreditCardPaymentDialog({
}) })
const onSubmit = async ({ credit_card }: Schema) => { const onSubmit = async ({ credit_card }: Schema) => {
const r = await runAsync({ credit_card }) await runAsync({ credit_card })
console.log(r.ok) revalidator.revalidate()
console.log(await r.json()) setOpen(false)
} }
return ( return (
<Dialog> <Dialog open={open} onOpenChange={toggle}>
<DialogTrigger asChild>{children}</DialogTrigger> <DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="sm:max-w-[425px]"> <DialogContent className="sm:max-w-[425px]">
@@ -502,17 +520,13 @@ function PaymentAttemptsMenu({
return ( return (
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button variant="ghost" className="cursor-pointer"> <Button variant="ghost" className="cursor-pointer" size="icon-sm">
<EllipsisIcon /> <EllipsisIcon />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent align="end" className="w-76"> <PopoverContent align="end" className="w-76">
{/* <div className="border-b p-2 text-xs text-muted-foreground font-medium">
Transações
</div>*/}
<div className="p-2 space-y-1.5"> <div className="p-2 space-y-1.5">
{payment_attempts.map( {payment_attempts.map(({ sk, brand, last4, status }, index) => {
({ sk, brand, last4, status, ...props }, index) => {
const [, , created_at] = sk.split('#') const [, , created_at] = sk.split('#')
return ( return (
@@ -536,15 +550,14 @@ function PaymentAttemptsMenu({
<li className="ml-auto">**** {last4}</li> <li className="ml-auto">**** {last4}</li>
<li className="flex items-center"> <li className="flex items-center">
{status === 'FAILED' ? ( {status === 'FAILED' ? (
<CircleXIcon className="size-4 text-red-500" /> <CircleXIcon className="size-4 text-red-400" />
) : ( ) : (
<CircleCheckIcon className="size-4 text-green-500" /> <CircleCheckIcon className="size-4 text-green-400" />
)} )}
</li> </li>
</ul> </ul>
) )
} })}
)}
</div> </div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>