update payment method, remove stripe, paypal, amanapay
Build & Push / Pipeline Tests (push) Failing after 59s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 48s
Test / API Unit Tests (push) Has been skipped
Test / Homepage Unit Tests (push) Has been skipped
Test / Carplace Unit Tests (push) Has been skipped
Test / Admin Unit Tests (push) Has been skipped
Test / Dashboard Unit Tests (push) Has been skipped
Test / API Integration Tests (push) Has been skipped

This commit is contained in:
root
2026-08-17 21:05:29 -04:00
parent c9915a8315
commit fe8ffbeb9f
109 changed files with 568 additions and 2662 deletions
@@ -63,7 +63,6 @@ function fmtDate(value?: Date | string | null) {
function paymentMethodLabel(method?: string | null) {
if (method === 'BANK_TRANSFER') return 'Bank transfer'
if (method === 'CHECK') return 'Check'
if (method === 'STRIPE') return 'Online card payment'
return method ?? 'Manual payment'
}
@@ -410,7 +409,7 @@ export async function createManualCheckout(companyId: string, employeeId: string
if (scheduled.legacySubscriptionInvoice) {
await tx.subscriptionInvoice.update({
where: { id: scheduled.legacySubscriptionInvoice.id },
data: { paymentProvider: 'MANUAL', stripeCheckoutSessionId: null },
data: { paymentProvider: 'MANUAL' },
})
}
await createBillingEvent(tx, {
@@ -538,163 +537,6 @@ export async function createManualCheckout(companyId: string, employeeId: string
})
}
export async function createCanonicalStripeCheckoutInvoice(data: {
companyId: string
subscriptionId: string
plan: 'STARTER' | 'GROWTH' | 'PRO' | 'ENTERPRISE'
billingPeriod: 'MONTHLY' | 'ANNUAL'
amount: number
currency: 'MAD'
stripeCheckoutSessionId: string
dueAt: Date
}) {
const account = await ensurePrimaryBillingAccount(data.companyId)
const platformBillingSettings = await getPlatformBillingSettings()
const tax = calculateTaxForAccount(data.amount, account, platformBillingSettings.taxRate)
return prisma.$transaction(async (tx: any) => {
const duplicate = await tx.billingInvoice.findFirst({
where: { metadata: { path: ['stripeCheckoutSessionId'], equals: data.stripeCheckoutSessionId } },
})
if (duplicate) return duplicate
const subscription = await tx.subscription.findUniqueOrThrow({ where: { id: data.subscriptionId } })
const now = new Date()
const isRenewal = subscription.status === 'ACTIVE' && subscription.currentPeriodEnd && subscription.currentPeriodEnd > now
const renewalKey = isRenewal ? `${subscription.id}:${subscription.currentPeriodEnd!.toISOString()}` : null
if (renewalKey) {
const scheduled = await tx.billingInvoice.findUnique({
where: { renewalKey },
include: { legacySubscriptionInvoice: true },
})
if (scheduled) {
if (!PAYABLE_INVOICE_STATUSES.includes(scheduled.status)) {
throw new ConflictError('The renewal invoice is not payable')
}
if (scheduled.amountDue !== tax.totalAmount || scheduled.requestedPlan !== data.plan || scheduled.requestedBillingPeriod !== data.billingPeriod) {
throw new ConflictError('An existing renewal invoice must be resolved before changing the renewal terms')
}
const invoice = await tx.billingInvoice.update({
where: { id: scheduled.id },
data: {
collectionMethod: 'STRIPE',
paymentProvider: 'STRIPE',
metadata: {
...((scheduled.metadata as Record<string, unknown>) ?? {}),
source: 'stripe_checkout',
stripeCheckoutSessionId: data.stripeCheckoutSessionId,
},
},
})
if (scheduled.legacySubscriptionInvoice) {
await tx.subscriptionInvoice.update({
where: { id: scheduled.legacySubscriptionInvoice.id },
data: { paymentProvider: 'STRIPE', stripeCheckoutSessionId: data.stripeCheckoutSessionId },
})
} else {
await tx.subscriptionInvoice.create({
data: {
companyId: data.companyId,
subscriptionId: data.subscriptionId,
requestedPlan: data.plan,
requestedBillingPeriod: data.billingPeriod,
amount: tax.totalAmount,
currency: data.currency,
status: 'PENDING',
paymentProvider: 'STRIPE',
stripeCheckoutSessionId: data.stripeCheckoutSessionId,
billingInvoiceId: invoice.id,
dueAt: scheduled.dueAt ?? data.dueAt,
},
})
}
await createBillingEvent(tx, {
billingAccountId: account.id,
invoiceId: invoice.id,
subscriptionId: data.subscriptionId,
companyId: data.companyId,
eventType: 'stripe_checkout.created',
source: 'customer',
payload: { stripeCheckoutSessionId: data.stripeCheckoutSessionId, reusedRenewalInvoice: true },
})
return invoice
}
}
const invoiceSequence = await getNextInvoiceSequence(tx)
const invoiceNumber = buildSequentialInvoiceNumber(invoiceSequence, now)
const invoice = await tx.billingInvoice.create({
data: {
billingAccountId: account.id,
companyId: data.companyId,
subscriptionId: data.subscriptionId,
invoiceNumber,
invoiceSequence,
invoiceType: isRenewal ? 'SUBSCRIPTION_RENEWAL' : 'SUBSCRIPTION_INITIAL',
status: 'OPEN',
currency: data.currency,
subtotalAmount: data.amount,
taxAmount: tax.taxAmount,
totalAmount: tax.totalAmount,
amountDue: tax.totalAmount,
invoiceDate: now,
dueAt: data.dueAt,
finalizedAt: now,
billingName: account.legalName,
billingEmail: account.billingEmail,
billingAddress: account.billingAddress ?? undefined,
paymentProvider: 'STRIPE',
collectionMethod: 'STRIPE',
requestedPlan: data.plan,
requestedBillingPeriod: data.billingPeriod,
renewalKey,
isSubscriptionBlocking: true,
metadata: { source: 'stripe_checkout', stripeCheckoutSessionId: data.stripeCheckoutSessionId },
lineItems: {
create: [
{
subscriptionId: data.subscriptionId,
plan: data.plan,
type: 'SUBSCRIPTION_FEE',
description: `${data.plan} subscription — ${data.billingPeriod}`,
quantity: 1,
unitAmount: data.amount,
amount: data.amount,
currency: data.currency,
periodStart: isRenewal ? subscription.currentPeriodEnd : now,
periodEnd: isRenewal ? addBillingPeriod(subscription.currentPeriodEnd!, data.billingPeriod) : addBillingPeriod(now, data.billingPeriod),
},
...taxLineItem(tax, data.currency),
],
},
...(taxRecordCreate(account, tax) ? { taxRecords: taxRecordCreate(account, tax) } : {}),
},
})
await tx.subscriptionInvoice.create({
data: {
companyId: data.companyId,
subscriptionId: data.subscriptionId,
requestedPlan: data.plan,
requestedBillingPeriod: data.billingPeriod,
amount: tax.totalAmount,
currency: data.currency,
status: 'PENDING',
paymentProvider: 'STRIPE',
stripeCheckoutSessionId: data.stripeCheckoutSessionId,
billingInvoiceId: invoice.id,
dueAt: data.dueAt,
},
})
await createBillingEvent(tx, {
billingAccountId: account.id,
invoiceId: invoice.id,
subscriptionId: data.subscriptionId,
companyId: data.companyId,
eventType: 'stripe_checkout.created',
source: 'customer',
payload: { stripeCheckoutSessionId: data.stripeCheckoutSessionId },
})
return invoice
})
}
export async function finalizeCanonicalOnlinePayment(legacyInvoiceId: string, providerPaymentId?: string) {
let collectionsCaseId: string | null = null
const handled = await prisma.$transaction(async (tx: any) => {
@@ -711,7 +553,7 @@ export async function finalizeCanonicalOnlinePayment(legacyInvoiceId: string, pr
data: {
invoiceId: invoice.id,
billingAccountId: invoice.billingAccountId,
providerPaymentId: providerPaymentId ?? legacy.stripeCheckoutSessionId ?? legacy.providerInvoiceId,
providerPaymentId: providerPaymentId ?? legacy.providerInvoiceId,
channel: 'ONLINE',
status: 'SUCCEEDED',
amount: invoice.amountDue,
@@ -833,7 +675,7 @@ export async function finalizeCanonicalOnlinePayment(legacyInvoiceId: string, pr
invoice: invoiceLabel,
amountPaid: invoice.amountPaid || paymentAttempt?.amount || invoice.totalAmount,
currency: invoice.currency,
paymentType: invoice.collectionMethod ?? invoice.paymentProvider ?? 'STRIPE',
paymentType: invoice.collectionMethod ?? invoice.paymentProvider ?? 'BANK_TRANSFER',
paymentReference: paymentAttempt?.providerPaymentId ?? null,
paidAt: invoice.paidAt,
plan: invoice.requestedPlan ?? invoice.subscription?.plan ?? null,
@@ -854,7 +696,7 @@ export async function finalizeCanonicalOnlinePayment(legacyInvoiceId: string, pr
invoiceId: invoice.id,
amountPaid: invoice.amountPaid || paymentAttempt?.amount || invoice.totalAmount,
currency: invoice.currency,
paymentType: invoice.collectionMethod ?? invoice.paymentProvider ?? 'STRIPE',
paymentType: invoice.collectionMethod ?? invoice.paymentProvider ?? 'BANK_TRANSFER',
paymentReference: paymentAttempt?.providerPaymentId ?? null,
subscriptionStart: periodStart,
subscriptionEnd: periodEnd,
@@ -889,7 +731,7 @@ export async function recordCanonicalOnlinePaymentFailure(
data: {
invoiceId: invoice.id,
billingAccountId: invoice.billingAccountId,
providerPaymentId: legacy.stripeCheckoutSessionId ?? legacy.providerInvoiceId,
providerPaymentId: legacy.providerInvoiceId,
channel: 'ONLINE',
status: 'FAILED',
amount: invoice.amountDue,
@@ -997,7 +839,7 @@ export async function getCanonicalInvoices(companyId: string) {
currency: invoice.currency,
status: invoice.status,
paymentProvider: invoice.paymentProvider,
collectionMethod: invoice.paymentProvider === 'STRIPE' ? 'STRIPE' : 'BANK_TRANSFER',
collectionMethod: 'BANK_TRANSFER',
requestedPlan: invoice.requestedPlan,
requestedBillingPeriod: invoice.requestedBillingPeriod,
dueAt: invoice.dueAt,