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:
transact.condition(
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={
':pending': 'PENDING',
':installments': installments,
},
exc_cls=OrderConflictError,

View File

@@ -28,7 +28,7 @@
// Seeds for Order
// 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": "TRANSACTION#STATS", "last_attempt_succeeded": false}

View File

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

View File

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