add interest
This commit is contained in:
@@ -19,6 +19,7 @@ from pydantic import (
|
|||||||
from api_gateway import JSONResponse
|
from api_gateway import JSONResponse
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client
|
||||||
from config import ORDER_TABLE
|
from config import ORDER_TABLE
|
||||||
|
from routes.enrollments.enroll import Enrollment
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
dyn = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
||||||
@@ -66,6 +67,7 @@ class Checkout(BaseModel):
|
|||||||
address: Address
|
address: Address
|
||||||
payment_method: Literal['PIX', 'CREDIT_CARD', 'BANK_SLIP', 'MANUAL']
|
payment_method: Literal['PIX', 'CREDIT_CARD', 'BANK_SLIP', 'MANUAL']
|
||||||
items: tuple[Item, ...]
|
items: tuple[Item, ...]
|
||||||
|
enrollments: tuple[Enrollment, ...] | None = None
|
||||||
coupon: Coupon | None = None
|
coupon: Coupon | None = None
|
||||||
user: User | None = None
|
user: User | None = None
|
||||||
org_id: UUID4 | str | None = None
|
org_id: UUID4 | str | None = None
|
||||||
|
|||||||
@@ -29,10 +29,13 @@ import {
|
|||||||
NativeSelect,
|
NativeSelect,
|
||||||
NativeSelectOption
|
NativeSelectOption
|
||||||
} from '@repo/ui/components/ui/native-select'
|
} from '@repo/ui/components/ui/native-select'
|
||||||
|
import { Currency } from '@repo/ui/components/currency'
|
||||||
|
|
||||||
import { useWizard } from '@/components/wizard'
|
import { useWizard } from '@/components/wizard'
|
||||||
import { isName } from '../_.$orgid.users.add/data'
|
import { isName } from '../_.$orgid.users.add/data'
|
||||||
import type { PaymentMethod } from '@repo/ui/routes/orders/data'
|
import type { PaymentMethod } from '@repo/ui/routes/orders/data'
|
||||||
|
import type { WizardState } from './route'
|
||||||
|
import { applyDiscount } from './discount'
|
||||||
|
|
||||||
const creditCard = z.object({
|
const creditCard = z.object({
|
||||||
holder_name: z
|
holder_name: z
|
||||||
@@ -52,31 +55,32 @@ const creditCard = z.object({
|
|||||||
cvv: z.string().min(3).max(4)
|
cvv: z.string().min(3).max(4)
|
||||||
})
|
})
|
||||||
|
|
||||||
const formSchema = z.discriminatedUnion(
|
const formSchema = z.discriminatedUnion('payment_method', [
|
||||||
'payment_method',
|
|
||||||
[
|
|
||||||
z.object({
|
z.object({
|
||||||
payment_method: z.literal('PIX')
|
payment_method: z.literal('PIX')
|
||||||
}),
|
}),
|
||||||
|
|
||||||
z.object({
|
z.object({
|
||||||
payment_method: z.literal('BANK_SLIP')
|
payment_method: z.literal('BANK_SLIP')
|
||||||
}),
|
}),
|
||||||
|
|
||||||
z.object({
|
z.object({
|
||||||
payment_method: z.literal('MANUAL')
|
payment_method: z.literal('MANUAL')
|
||||||
}),
|
}),
|
||||||
|
|
||||||
z.object({
|
z.object({
|
||||||
payment_method: z.literal('CREDIT_CARD'),
|
payment_method: z.literal('CREDIT_CARD'),
|
||||||
credit_card: creditCard
|
credit_card: creditCard,
|
||||||
|
installments: z.coerce.number().int().min(1).max(12)
|
||||||
})
|
})
|
||||||
],
|
])
|
||||||
{ error: 'Escolha uma forma de pagamento' }
|
|
||||||
)
|
|
||||||
|
|
||||||
type Schema = z.infer<typeof formSchema>
|
type Schema = z.input<typeof formSchema>
|
||||||
|
|
||||||
export type CreditCard = z.infer<typeof creditCard>
|
export type CreditCard = z.infer<typeof creditCard>
|
||||||
|
|
||||||
type PaymentProps = {
|
type PaymentProps = {
|
||||||
|
state: WizardState
|
||||||
onSubmit: (value: any) => void | Promise<void>
|
onSubmit: (value: any) => void | Promise<void>
|
||||||
payment_method?: PaymentMethod
|
payment_method?: PaymentMethod
|
||||||
credit_card?: CreditCard
|
credit_card?: CreditCard
|
||||||
@@ -84,6 +88,7 @@ type PaymentProps = {
|
|||||||
|
|
||||||
export function Payment({
|
export function Payment({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
state,
|
||||||
payment_method: paymentMethodInit,
|
payment_method: paymentMethodInit,
|
||||||
credit_card: creditCardInit = undefined
|
credit_card: creditCardInit = undefined
|
||||||
}: PaymentProps) {
|
}: PaymentProps) {
|
||||||
@@ -91,11 +96,23 @@ export function Payment({
|
|||||||
const { control, handleSubmit } = useForm<Schema>({
|
const { control, handleSubmit } = useForm<Schema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
payment_method: paymentMethodInit,
|
payment_method: paymentMethodInit,
|
||||||
|
installments: state?.installments ?? 1,
|
||||||
credit_card: creditCardInit
|
credit_card: creditCardInit
|
||||||
},
|
},
|
||||||
resolver: zodResolver(formSchema)
|
resolver: zodResolver(formSchema)
|
||||||
})
|
})
|
||||||
const paymentMethod = useWatch({ control, name: 'payment_method' })
|
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) => {
|
const onSubmit_ = async (data: Schema) => {
|
||||||
await onSubmit({ credit_card: undefined, ...data })
|
await onSubmit({ credit_card: undefined, ...data })
|
||||||
@@ -147,7 +164,7 @@ export function Payment({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{paymentMethod === 'CREDIT_CARD' ? (
|
{paymentMethod === 'CREDIT_CARD' ? (
|
||||||
<CreditCard control={control} />
|
<CreditCard control={control} total={total} />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
@@ -171,7 +188,13 @@ export function Payment({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CreditCard({ control }: { control: Control<Schema> }) {
|
export function CreditCard({
|
||||||
|
total,
|
||||||
|
control
|
||||||
|
}: {
|
||||||
|
total: number
|
||||||
|
control: Control<Schema>
|
||||||
|
}) {
|
||||||
const currentYear = new Date().getFullYear()
|
const currentYear = new Date().getFullYear()
|
||||||
const years = Array.from({ length: 10 }, (_, i) => currentYear + i)
|
const years = Array.from({ length: 10 }, (_, i) => currentYear + i)
|
||||||
|
|
||||||
@@ -325,9 +348,58 @@ export function CreditCard({ control }: { control: Control<Schema> }) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="installments"
|
||||||
|
defaultValue={1}
|
||||||
|
render={({ field, fieldState }) => (
|
||||||
|
<Field data-invalid={fieldState.invalid}>
|
||||||
|
<FieldLabel htmlFor={field.name}>Parcelas</FieldLabel>
|
||||||
|
<NativeSelect
|
||||||
|
id={field.name}
|
||||||
|
value={String(field.value)}
|
||||||
|
onChange={field.onChange}
|
||||||
|
aria-invalid={fieldState.invalid}
|
||||||
|
>
|
||||||
|
{Array.from({ length: 12 }, (_, index) => {
|
||||||
|
const installment = index + 1 // 1 -> 12
|
||||||
|
|
||||||
|
if (installment === 1) {
|
||||||
|
return (
|
||||||
|
<NativeSelectOption key={installment} value={1}>
|
||||||
|
<Currency>{total}</Currency> à vista
|
||||||
|
</NativeSelectOption>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const value =
|
||||||
|
calcInterest(total, installment) / installment
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NativeSelectOption
|
||||||
|
key={installment}
|
||||||
|
value={installment}
|
||||||
|
>
|
||||||
|
{installment}x <Currency>{value}</Currency> com juros
|
||||||
|
</NativeSelectOption>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</NativeSelect>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import {
|
|||||||
InputGroupButton,
|
InputGroupButton,
|
||||||
InputGroupInput
|
InputGroupInput
|
||||||
} from '@repo/ui/components/ui/input-group'
|
} from '@repo/ui/components/ui/input-group'
|
||||||
|
import { calcInterest } from './payment'
|
||||||
|
|
||||||
type ReviewProps = {
|
type ReviewProps = {
|
||||||
state: WizardState
|
state: WizardState
|
||||||
@@ -79,6 +80,13 @@ export function Review({ state, onSubmit }: ReviewProps) {
|
|||||||
? applyDiscount(subtotal, coupon.amount, coupon.type) * -1
|
? applyDiscount(subtotal, coupon.amount, coupon.type) * -1
|
||||||
: 0
|
: 0
|
||||||
const total = subtotal > 0 ? subtotal + discount : 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -117,6 +125,8 @@ export function Review({ state, onSubmit }: ReviewProps) {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
||||||
|
{/* Summary */}
|
||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell className="text-right" colSpan={3}>
|
<TableCell className="text-right" colSpan={3}>
|
||||||
@@ -126,6 +136,7 @@ export function Review({ state, onSubmit }: ReviewProps) {
|
|||||||
<Currency>{subtotal}</Currency>
|
<Currency>{subtotal}</Currency>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
{/* Discount */}
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell className="text-right" colSpan={3}>
|
<TableCell className="text-right" colSpan={3}>
|
||||||
Descontos
|
Descontos
|
||||||
@@ -134,12 +145,26 @@ export function Review({ state, onSubmit }: ReviewProps) {
|
|||||||
<Currency>{discount}</Currency>
|
<Currency>{discount}</Currency>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
{/* Interest */}
|
||||||
|
{interest_amount ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell className="text-right" colSpan={3}>
|
||||||
|
Juros
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Currency>{interest_amount}</Currency>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
{/* Total */}
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell className="text-right" colSpan={3}>
|
<TableCell className="text-right" colSpan={3}>
|
||||||
Total
|
Total
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Currency>{total}</Currency>
|
<Currency>{total + interest_amount}</Currency>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableFooter>
|
</TableFooter>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export type WizardState = {
|
|||||||
items: Item[]
|
items: Item[]
|
||||||
enrollments: Enrollment[]
|
enrollments: Enrollment[]
|
||||||
coupon?: Coupon
|
coupon?: Coupon
|
||||||
|
installments?: number
|
||||||
payment_method?: PaymentMethod
|
payment_method?: PaymentMethod
|
||||||
credit_card?: CreditCard
|
credit_card?: CreditCard
|
||||||
}
|
}
|
||||||
@@ -54,6 +55,7 @@ const emptyWizard: WizardState = {
|
|||||||
enrollments: [],
|
enrollments: [],
|
||||||
coupon: undefined,
|
coupon: undefined,
|
||||||
payment_method: undefined,
|
payment_method: undefined,
|
||||||
|
installments: undefined,
|
||||||
credit_card: undefined
|
credit_card: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +206,7 @@ export default function Route({
|
|||||||
{/* Payment */}
|
{/* Payment */}
|
||||||
<WizardStep name="payment">
|
<WizardStep name="payment">
|
||||||
<Payment
|
<Payment
|
||||||
|
state={state}
|
||||||
payment_method={state.payment_method}
|
payment_method={state.payment_method}
|
||||||
credit_card={state.credit_card}
|
credit_card={state.credit_card}
|
||||||
onSubmit={(data: any) => {
|
onSubmit={(data: any) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user