check status to payment retries
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,49 +520,44 @@ 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 (
|
||||||
<ul key={index} className="text-sm flex gap-1.5">
|
<ul key={index} className="text-sm flex gap-1.5">
|
||||||
<li>
|
<li>
|
||||||
<Kbd>
|
<Kbd>
|
||||||
<DateTime
|
<DateTime
|
||||||
options={{
|
options={{
|
||||||
year: '2-digit',
|
year: '2-digit',
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit'
|
minute: '2-digit'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{created_at}
|
{created_at}
|
||||||
</DateTime>
|
</DateTime>
|
||||||
</Kbd>
|
</Kbd>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Abbr maxLen={6}>{brand}</Abbr>
|
<Abbr maxLen={6}>{brand}</Abbr>
|
||||||
</li>
|
</li>
|
||||||
<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>
|
||||||
|
|||||||
Reference in New Issue
Block a user