add payment retries

This commit is contained in:
2026-01-16 14:13:58 -03:00
parent 466936acf4
commit 61c01956b5
6 changed files with 126 additions and 18 deletions

View File

@@ -50,6 +50,15 @@ def payment_retries(
}, },
exc_cls=InvoiceNotFoundError, exc_cls=InvoiceNotFoundError,
) )
transact.put(
item={
'id': order_id,
'sk': 'CREDIT_CARD',
'brand': credit_card.brand,
'last4': credit_card.last4,
'created_at': now_,
}
)
transact.put( transact.put(
item={ item={
'id': order_id, 'id': order_id,

View File

@@ -1,6 +1,10 @@
from http import HTTPMethod, HTTPStatus from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from layercake.dynamodb import (
DynamoDBPersistenceLayer,
SortKey,
TransactKey,
)
from ...conftest import HttpApiProxy, LambdaContext from ...conftest import HttpApiProxy, LambdaContext
@@ -33,12 +37,18 @@ def test_payment_retries(
assert r['statusCode'] == HTTPStatus.CREATED assert r['statusCode'] == HTTPStatus.CREATED
r = dynamodb_persistence_layer.collection.get_items( r = dynamodb_persistence_layer.collection.get_items(
KeyPair('4b23f6f5-5377-476b-b1de-79427c0295f6', 'TRANSACTION') TransactKey('4b23f6f5-5377-476b-b1de-79427c0295f6')
+ KeyPair( + SortKey('TRANSACTION')
'4b23f6f5-5377-476b-b1de-79427c0295f6', + SortKey(
'CREDIT_CARD',
rename_key='cc',
)
+ SortKey(
'TRANSACTION#STATS', 'TRANSACTION#STATS',
rename_key='stats', rename_key='stats',
) ),
) )
assert r['credit_card']['number'] == '4111111111111111' assert r['credit_card']['number'] == '4111111111111111'
assert 'last_attempt_succeeded' not in r['stats'] assert 'last_attempt_succeeded' not in r['stats']
assert r['cc']['last4'] == '1111'
assert r['cc']['brand'] == 'Visa'

View File

@@ -6,6 +6,9 @@ import { useRequest } from 'ahooks'
import { import {
AlertCircleIcon, AlertCircleIcon,
ArrowLeftRightIcon, ArrowLeftRightIcon,
CircleCheckIcon,
CircleXIcon,
EllipsisIcon,
HelpCircleIcon HelpCircleIcon
} from 'lucide-react' } from 'lucide-react'
import { useEffect } from 'react' import { useEffect } from 'react'
@@ -28,8 +31,16 @@ 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,
ItemContent, ItemContent,
ItemGroup, ItemGroup,
ItemTitle ItemTitle
@@ -46,16 +57,9 @@ import {
import { request as req } from '@repo/util/request' import { request as req } from '@repo/util/request'
import { Abbr } from '@repo/ui/components/abbr' import { Abbr } from '@repo/ui/components/abbr'
import { DateTime } from '@repo/ui/components/datetime'
import { Badge } from '@repo/ui/components/ui/badge' import { Badge } from '@repo/ui/components/ui/badge'
import { Button } from '@repo/ui/components/ui/button' import { Button } from '@repo/ui/components/ui/button'
import { Kbd } from '@repo/ui/components/ui/kbd'
import { cn } from '@repo/ui/lib/utils'
import {
labels,
statuses,
type Order as Order_
} from '@repo/ui/routes/orders/data'
import { import {
Dialog, Dialog,
DialogClose, DialogClose,
@@ -66,8 +70,20 @@ import {
DialogTitle, DialogTitle,
DialogTrigger DialogTrigger
} from '@repo/ui/components/ui/dialog' } from '@repo/ui/components/ui/dialog'
import { Kbd } from '@repo/ui/components/ui/kbd'
import {
Popover,
PopoverContent,
PopoverTrigger
} from '@repo/ui/components/ui/popover'
import { Separator } from '@repo/ui/components/ui/separator' import { Separator } from '@repo/ui/components/ui/separator'
import { Spinner } from '@repo/ui/components/ui/spinner' import { Spinner } from '@repo/ui/components/ui/spinner'
import { cn } from '@repo/ui/lib/utils'
import {
labels,
statuses,
type Order as Order_
} from '@repo/ui/routes/orders/data'
import { import {
CreditCard, CreditCard,
creditCardSchema, creditCardSchema,
@@ -107,6 +123,13 @@ type Invoice = {
secure_url: string secure_url: string
} }
type Attempts = {
sk: string
status: string
brand: string
last4: string
}
type Order = Order_ & { type Order = Order_ & {
items: Item[] items: Item[]
interest_amount: number interest_amount: number
@@ -115,6 +138,7 @@ type Order = Order_ & {
subtotal: number subtotal: number
discount: number discount: number
address: Address address: Address
payment_attempts: Attempts[]
credit_card?: CreditCardProps credit_card?: CreditCardProps
coupon?: string coupon?: string
installments?: number installments?: number
@@ -142,8 +166,9 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
interest_amount, interest_amount,
discount, discount,
invoice, invoice,
subtotal, payment_attempts = [],
items = [] items = [],
subtotal
} = order } = order
const Component = const Component =
@@ -203,6 +228,12 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
)} )}
</div> </div>
</ItemContent> </ItemContent>
{payment_attempts.length > 0 ? (
<ItemActions>
<PaymentAttemptsMenu payment_attempts={payment_attempts} />
</ItemActions>
) : null}
</Item> </Item>
</ItemGroup> </ItemGroup>
@@ -462,3 +493,60 @@ function CreditCardPaymentDialog({
</Dialog> </Dialog>
) )
} }
function PaymentAttemptsMenu({
payment_attempts
}: {
payment_attempts: Attempts[]
}) {
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="ghost" className="cursor-pointer">
<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) => {
const [, , created_at] = sk.split('#')
return (
<ul key={index} className="text-sm flex gap-1.5">
<li>
<Kbd>
<DateTime
options={{
year: '2-digit',
hour: '2-digit',
minute: '2-digit'
}}
>
{created_at}
</DateTime>
</Kbd>
</li>
<li>
<Abbr maxLen={6}>{brand}</Abbr>
</li>
<li className="ml-auto">**** {last4}</li>
<li className="flex items-center">
{status === 'FAILED' ? (
<CircleXIcon className="size-4 text-red-500" />
) : (
<CircleCheckIcon className="size-4 text-green-500" />
)}
</li>
</ul>
)
}
)}
</div>
</PopoverContent>
</Popover>
)
}

View File

@@ -3,8 +3,8 @@ import type { Route } from './+types/route'
import { MeiliSearchFilterBuilder } from 'meilisearch-helper' import { MeiliSearchFilterBuilder } from 'meilisearch-helper'
import { data } from 'react-router' import { data } from 'react-router'
import { createSearch } from '@repo/util/meili'
import { cloudflareContext } from '@repo/auth/context' import { cloudflareContext } from '@repo/auth/context'
import { createSearch } from '@repo/util/meili'
export async function loader({ params, context, request }: Route.LoaderArgs) { export async function loader({ params, context, request }: Route.LoaderArgs) {
const cloudflare = context.get(cloudflareContext) const cloudflare = context.get(cloudflareContext)

View File

@@ -133,6 +133,7 @@ def _generate_cert(
return None return None
try: try:
logger.info('Sending data to Paperforge API')
# Send template URI and data to Paperforge API to generate a PDF # Send template URI and data to Paperforge API to generate a PDF
r = requests.post( r = requests.post(
PAPERFORGE_API, PAPERFORGE_API,
@@ -165,7 +166,7 @@ def _generate_cert(
ContentType='application/pdf', ContentType='application/pdf',
) )
logger.debug(f'PDF uploaded successfully to {s3_uri}') logger.info(f'PDF uploaded successfully to {s3_uri}')
except requests.exceptions.RequestException as exc: except requests.exceptions.RequestException as exc:
logger.exception(exc) logger.exception(exc)
raise raise

View File

@@ -394,7 +394,7 @@ Resources:
Properties: Properties:
Handler: events.issue_cert.lambda_handler Handler: events.issue_cert.lambda_handler
Tracing: Active Tracing: Active
# Timeout: 12 Timeout: 10
LoggingConfig: LoggingConfig:
LogGroup: !Ref EventLog LogGroup: !Ref EventLog
Policies: Policies: