import type { Coupon, Prisma } from '../generated/prisma/client'; import type { Principal } from '../identity/identity.types'; import { AppError } from '../common/errors/app-error'; import { minor, discountFor } from '../checkout/money'; export function assertCoupon( coupon: Coupon | null, currency: string, subtotal: bigint, now: Date, uses: number, userUses: number, ): asserts coupon is Coupon { if (!coupon) throw new AppError('COUPON_INELIGIBLE', 'COUPON_UNKNOWN'); const failures: [boolean, string][] = [ [!coupon.active, 'COUPON_DISABLED'], [coupon.currency !== currency, 'COUPON_CURRENCY'], [coupon.startsAt > now, 'COUPON_NOT_STARTED'], [coupon.endsAt <= now, 'COUPON_EXPIRED'], [minor(coupon.minimumSubtotal.toString()) > subtotal, 'COUPON_MINIMUM'], [uses >= coupon.maxUses, 'COUPON_TOTAL_LIMIT'], [userUses >= coupon.perUserLimit, 'COUPON_USER_LIMIT'], ]; const failure = failures.find(([failed]) => failed); if (failure) throw new AppError('COUPON_INELIGIBLE', failure[1]); } export async function priceCoupon( tx: Prisma.TransactionClient, actor: Principal, code: string | undefined, currency: string, subtotal: bigint, now: Date, ) { if (!code) return { discount: 0n, coupon: null }; const coupon = await tx.coupon.findUnique({ where: { organizationId_code: { organizationId: actor.organizationId, code }, }, }); const where = { couponId: coupon?.id ?? '00000000-0000-0000-0000-000000000000', status: 'PENDING_PAYMENT' as const, expiresAt: { gt: now }, }; const uses = await tx.order.count({ where }); const userUses = await tx.order.count({ where: { ...where, userId: actor.userId }, }); assertCoupon(coupon, currency, subtotal, now, uses, userUses); return { coupon, discount: discountFor(subtotal, { kind: coupon.kind, amount: coupon.amount?.toString() ?? null, percentBps: coupon.percentBps, }), }; }