From 62ce241479b28566f3a9d5f319ab701d51dd7551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Rafael=20Siqueira?= Date: Mon, 29 Dec 2025 13:34:33 -0300 Subject: [PATCH] add interest --- .../app/routes/orders/checkout.py | 2 + .../_.$orgid.enrollments.buy/payment.tsx | 116 ++++++++++++++---- .../_.$orgid.enrollments.buy/review.tsx | 27 +++- .../routes/_.$orgid.enrollments.buy/route.tsx | 3 + 4 files changed, 125 insertions(+), 23 deletions(-) diff --git a/api.saladeaula.digital/app/routes/orders/checkout.py b/api.saladeaula.digital/app/routes/orders/checkout.py index 959f2fd..b02184f 100644 --- a/api.saladeaula.digital/app/routes/orders/checkout.py +++ b/api.saladeaula.digital/app/routes/orders/checkout.py @@ -19,6 +19,7 @@ from pydantic import ( from api_gateway import JSONResponse from boto3clients import dynamodb_client from config import ORDER_TABLE +from routes.enrollments.enroll import Enrollment router = Router() dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) @@ -66,6 +67,7 @@ class Checkout(BaseModel): address: Address payment_method: Literal['PIX', 'CREDIT_CARD', 'BANK_SLIP', 'MANUAL'] items: tuple[Item, ...] + enrollments: tuple[Enrollment, ...] | None = None coupon: Coupon | None = None user: User | None = None org_id: UUID4 | str | None = None diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/payment.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/payment.tsx index f689ad0..409fc06 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/payment.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/payment.tsx @@ -29,10 +29,13 @@ import { NativeSelect, NativeSelectOption } from '@repo/ui/components/ui/native-select' +import { Currency } from '@repo/ui/components/currency' import { useWizard } from '@/components/wizard' import { isName } from '../_.$orgid.users.add/data' import type { PaymentMethod } from '@repo/ui/routes/orders/data' +import type { WizardState } from './route' +import { applyDiscount } from './discount' const creditCard = z.object({ holder_name: z @@ -52,31 +55,32 @@ const creditCard = z.object({ cvv: z.string().min(3).max(4) }) -const formSchema = z.discriminatedUnion( - 'payment_method', - [ - z.object({ - payment_method: z.literal('PIX') - }), - z.object({ - payment_method: z.literal('BANK_SLIP') - }), - z.object({ - payment_method: z.literal('MANUAL') - }), - z.object({ - payment_method: z.literal('CREDIT_CARD'), - credit_card: creditCard - }) - ], - { error: 'Escolha uma forma de pagamento' } -) +const formSchema = z.discriminatedUnion('payment_method', [ + z.object({ + payment_method: z.literal('PIX') + }), -type Schema = z.infer + z.object({ + payment_method: z.literal('BANK_SLIP') + }), + + z.object({ + payment_method: z.literal('MANUAL') + }), + + z.object({ + payment_method: z.literal('CREDIT_CARD'), + credit_card: creditCard, + installments: z.coerce.number().int().min(1).max(12) + }) +]) + +type Schema = z.input export type CreditCard = z.infer type PaymentProps = { + state: WizardState onSubmit: (value: any) => void | Promise payment_method?: PaymentMethod credit_card?: CreditCard @@ -84,6 +88,7 @@ type PaymentProps = { export function Payment({ onSubmit, + state, payment_method: paymentMethodInit, credit_card: creditCardInit = undefined }: PaymentProps) { @@ -91,11 +96,23 @@ export function Payment({ const { control, handleSubmit } = useForm({ defaultValues: { payment_method: paymentMethodInit, + installments: state?.installments ?? 1, credit_card: creditCardInit }, resolver: zodResolver(formSchema) }) const paymentMethod = useWatch({ control, name: 'payment_method' }) + const subtotal = state.items.reduce( + (acc, { course, quantity }) => + acc + + (course?.unit_price || 0) * + (Number.isFinite(quantity) && quantity > 0 ? quantity : 1), + 0 + ) + const discount = state.coupon + ? applyDiscount(subtotal, state.coupon.amount, state.coupon.type) * -1 + : 0 + const total = subtotal > 0 ? subtotal + discount : 0 const onSubmit_ = async (data: Schema) => { await onSubmit({ credit_card: undefined, ...data }) @@ -147,7 +164,7 @@ export function Payment({ /> {paymentMethod === 'CREDIT_CARD' ? ( - + ) : null} @@ -171,7 +188,13 @@ export function Payment({ ) } -export function CreditCard({ control }: { control: Control }) { +export function CreditCard({ + total, + control +}: { + total: number + control: Control +}) { const currentYear = new Date().getFullYear() const years = Array.from({ length: 10 }, (_, i) => currentYear + i) @@ -325,9 +348,58 @@ export function CreditCard({ control }: { control: Control }) { )} /> + + ( + + Parcelas + + {Array.from({ length: 12 }, (_, index) => { + const installment = index + 1 // 1 -> 12 + + if (installment === 1) { + return ( + + {total} à vista + + ) + } + + const value = + calcInterest(total, installment) / installment + + return ( + + {installment}x {value} com juros + + ) + })} + + + )} + /> ) } + +export const calcInterest = (total: number, installment: number) => { + const rate2to6 = 0.055 + const rate7to12 = 0.0608 + const rate = installment >= 7 ? rate7to12 : rate2to6 + + return total * ((1 - 0.0382) / (1 - rate)) +} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/review.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/review.tsx index c76678f..ced263e 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/review.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/review.tsx @@ -57,6 +57,7 @@ import { InputGroupButton, InputGroupInput } from '@repo/ui/components/ui/input-group' +import { calcInterest } from './payment' type ReviewProps = { state: WizardState @@ -79,6 +80,13 @@ export function Review({ state, onSubmit }: ReviewProps) { ? applyDiscount(subtotal, coupon.amount, coupon.type) * -1 : 0 const total = subtotal > 0 ? subtotal + discount : 0 + const installments = state.installments + const interest_amount = + state.payment_method === 'CREDIT_CARD' && + typeof installments === 'number' && + installments > 1 + ? calcInterest(total, installments) - total + : 0 return ( <> @@ -117,6 +125,8 @@ export function Review({ state, onSubmit }: ReviewProps) { ) })} + + {/* Summary */} @@ -126,6 +136,7 @@ export function Review({ state, onSubmit }: ReviewProps) { {subtotal} + {/* Discount */} Descontos @@ -134,12 +145,26 @@ export function Review({ state, onSubmit }: ReviewProps) { {discount} + {/* Interest */} + {interest_amount ? ( + + + Juros + + + {interest_amount} + + + ) : ( + <> + )} + {/* Total */} Total - {total} + {total + interest_amount} diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/route.tsx index 025d6f9..494fdee 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.enrollments.buy/route.tsx @@ -43,6 +43,7 @@ export type WizardState = { items: Item[] enrollments: Enrollment[] coupon?: Coupon + installments?: number payment_method?: PaymentMethod credit_card?: CreditCard } @@ -54,6 +55,7 @@ const emptyWizard: WizardState = { enrollments: [], coupon: undefined, payment_method: undefined, + installments: undefined, credit_card: undefined } @@ -204,6 +206,7 @@ export default function Route({ {/* Payment */} {