remove stripe
Build & Push / Pipeline Tests (push) Successful in 1m28s
Test / Type Check (all packages) (push) Successful in 32s
Build & Push / Build & Push Docker Image (push) Failing after 1m23s
Test / API Unit Tests (push) Successful in 50s
Test / Homepage Unit Tests (push) Successful in 27s
Test / Carplace Unit Tests (push) Successful in 24s
Test / Admin Unit Tests (push) Successful in 24s
Test / Dashboard Unit Tests (push) Successful in 25s
Test / API Integration Tests (push) Successful in 49s

This commit is contained in:
root
2026-08-16 22:36:05 -04:00
parent 85ff9d492b
commit c9915a8315
11 changed files with 93 additions and 288 deletions
@@ -8,7 +8,7 @@ describe('payment schemas edge cases', () => {
type: 'CHARGE',
currency: 'MAD',
})
expect(chargeSchema.safeParse({ provider: 'STRIPE', successUrl: 'https://ok.example.test', failureUrl: 'https://fail.example.test' }).success).toBe(true)
expect(chargeSchema.safeParse({ provider: 'STRIPE', successUrl: 'https://ok.example.test', failureUrl: 'https://fail.example.test' }).success).toBe(false)
expect(chargeSchema.safeParse({ provider: 'UNKNOWN', successUrl: 'https://ok.example.test', failureUrl: 'https://fail.example.test' }).success).toBe(false)
expect(manualPaymentSchema.parse({ amount: 500, paymentMethod: 'CASH' })).toMatchObject({ amount: 500, currency: 'MAD', type: 'CHARGE' })
expect(manualPaymentSchema.safeParse({ amount: 0, paymentMethod: 'CASH' }).success).toBe(false)
@@ -1,7 +1,7 @@
import { z } from 'zod'
export const chargeSchema = z.object({
provider: z.enum(['AMANPAY', 'PAYPAL', 'STRIPE']),
provider: z.enum(['AMANPAY', 'PAYPAL']),
type: z.enum(['CHARGE', 'DEPOSIT']).default('CHARGE'),
currency: z.literal('MAD').default('MAD'),
successUrl: z.string().url(),
@@ -139,44 +139,6 @@ describe('payment.service', () => {
expect(repo.createPayment).not.toHaveBeenCalled()
})
it('creates a Stripe Checkout Session for an outstanding rental charge', async () => {
vi.mocked(repo.findReservationOrThrow).mockResolvedValue(reservation as never)
vi.mocked(stripe.isConfigured).mockReturnValue(true)
vi.mocked(stripe.createCheckoutSession).mockResolvedValue({ checkoutUrl: 'https://checkout.stripe.test/session', sessionId: 'cs_test_123' } as never)
vi.mocked(repo.createPayment).mockResolvedValue({ id: 'payment_1', status: 'PENDING' } as never)
const result = await initCharge('reservation_1', 'company_1', {
provider: 'STRIPE',
type: 'CHARGE',
currency: 'MAD',
successUrl: 'https://app.example/success',
failureUrl: 'https://app.example/failure',
})
expect(stripe.createCheckoutSession).toHaveBeenCalledWith(expect.objectContaining({
amount: 1000,
currency: 'MAD',
orderId: 'reservation_1-CHARGE-1780913700000',
description: 'Rental: Dacia Duster',
customerEmail: 'nora@example.com',
successUrl: 'https://app.example/success',
cancelUrl: 'https://app.example/failure',
reservationId: 'reservation_1',
companyId: 'company_1',
type: 'CHARGE',
}))
expect(repo.createPayment).toHaveBeenCalledWith(expect.objectContaining({
companyId: 'company_1',
reservationId: 'reservation_1',
amount: 1000,
status: 'PENDING',
type: 'CHARGE',
paymentProvider: 'STRIPE',
stripeCheckoutSessionId: 'cs_test_123',
}))
expect(result).toEqual({ payment: { id: 'payment_1', status: 'PENDING' }, checkoutUrl: 'https://checkout.stripe.test/session' })
})
it('allows an outstanding deposit when the rental invoice is fully paid', async () => {
vi.mocked(repo.findReservationOrThrow).mockResolvedValue({
...reservation,
@@ -106,7 +106,7 @@ export async function handleStripeWebhook(event: any, rawBody: string | Buffer =
}
export async function initCharge(reservationId: string, companyId: string, body: {
provider: 'AMANPAY' | 'PAYPAL' | 'STRIPE'; type: 'CHARGE' | 'DEPOSIT'
provider: 'AMANPAY' | 'PAYPAL'; type: 'CHARGE' | 'DEPOSIT'
currency: 'MAD'; successUrl: string; failureUrl: string
}) {
const reservation = await repo.findReservationOrThrow(reservationId, companyId)
@@ -132,7 +132,6 @@ export async function initCharge(reservationId: string, companyId: string, body:
let checkoutUrl: string
let amanpayTransactionId: string | null = null
let paypalCaptureId: string | null = null
let stripeCheckoutSessionId: string | null = null
if (body.provider === 'AMANPAY') {
if (!amanpay.isConfigured()) throw new ValidationError('AmanPay is not configured')
@@ -151,24 +150,10 @@ export async function initCharge(reservationId: string, companyId: string, body:
checkoutUrl = result.approveUrl
paypalCaptureId = result.orderId
} else {
if (!stripe.isConfigured()) throw new ValidationError('Stripe is not configured')
const result = await stripe.createCheckoutSession({
amount,
currency: body.currency,
orderId,
description,
customerEmail: reservation.customer.email,
successUrl: body.successUrl,
cancelUrl: body.failureUrl,
reservationId,
companyId,
type: body.type,
})
checkoutUrl = result.checkoutUrl
stripeCheckoutSessionId = result.sessionId
throw new ValidationError('Unsupported payment provider')
}
const payment = await repo.createPayment({ companyId, reservationId, amount, currency: body.currency, status: 'PENDING', type: body.type, paymentProvider: body.provider, amanpayTransactionId, paypalCaptureId, stripeCheckoutSessionId })
const payment = await repo.createPayment({ companyId, reservationId, amount, currency: body.currency, status: 'PENDING', type: body.type, paymentProvider: body.provider, amanpayTransactionId, paypalCaptureId })
return { payment, checkoutUrl }
}
@@ -1,10 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('../../services/stripeService', () => ({
getConfigurationStatus: vi.fn(),
}))
import { getConfigurationStatus } from '../../services/stripeService'
import { getPaymentOptions } from './subscription.payment-config'
const ENV_KEYS = [
@@ -26,7 +20,6 @@ describe('subscription payment configuration', () => {
const originalEnv = new Map<string, string | undefined>()
beforeEach(() => {
vi.mocked(getConfigurationStatus).mockReturnValue({ configured: true, problems: [] })
for (const key of ENV_KEYS) originalEnv.set(key, process.env[key])
})
@@ -44,7 +37,6 @@ describe('subscription payment configuration', () => {
process.env.NODE_ENV = 'development'
expect(getPaymentOptions('en').methods).toEqual([
{ method: 'STRIPE', enabled: true },
{
method: 'BANK_TRANSFER',
enabled: true,
@@ -74,6 +66,6 @@ describe('subscription payment configuration', () => {
process.env.BANK_TRANSFER_ENABLED = 'true'
process.env.CHECK_PAYMENT_ENABLED = 'true'
expect(getPaymentOptions('en').methods).toEqual([{ method: 'STRIPE', enabled: true }])
expect(getPaymentOptions('en').methods).toEqual([])
})
})
@@ -1,5 +1,4 @@
import { ValidationError } from '../../http/errors'
import { getConfigurationStatus as getStripeConfigurationStatus } from '../../services/stripeService'
import type { NotificationLocale } from '../../services/notificationLocalizationService'
export type ManualCollectionMethod = 'BANK_TRANSFER' | 'CHECK'
@@ -39,8 +38,7 @@ export function paymentEvidencePipelineReady() {
}
export function getPaymentOptions(locale: NotificationLocale = 'en') {
const stripeStatus = getStripeConfigurationStatus()
const methods: Array<Record<string, unknown>> = [{ method: 'STRIPE', enabled: stripeStatus.configured }]
const methods: Array<Record<string, unknown>> = []
// A manual method is unusable unless evidence can be scanned and submitted.
// Keeping it hidden also makes the rollout fail closed when the scanner is
@@ -134,14 +134,7 @@ describe('subscription.service operational edges', () => {
}))
})
it('creates Stripe checkout invoices with session metadata and due dates', async () => {
vi.mocked(prisma.pricingConfig.findUnique).mockResolvedValue({ amount: 19900 } as never)
vi.mocked(prisma.company.findUniqueOrThrow).mockResolvedValue({ email: 'owner@example.test', name: 'Atlas Cars' } as never)
vi.mocked(repo.findOrCreateSubscription).mockResolvedValue({ id: 'sub_1' } as never)
vi.mocked(stripe.isConfigured).mockReturnValue(true)
vi.mocked(stripe.createCheckoutSession).mockResolvedValue({ checkoutUrl: 'https://checkout.stripe.test/session', sessionId: 'cs_test_123' } as never)
vi.mocked(manualService.createCanonicalStripeCheckoutInvoice).mockResolvedValue({ id: 'invoice_1' } as never)
it('rejects Stripe subscription checkout before creating sessions or invoices', async () => {
await expect(service.checkout('company_1', {
plan: 'GROWTH',
billingPeriod: 'MONTHLY',
@@ -149,25 +142,10 @@ describe('subscription.service operational edges', () => {
provider: 'STRIPE',
successUrl: 'https://app.example.test/success',
failureUrl: 'https://app.example.test/failure',
})).resolves.toEqual({ invoice: { id: 'invoice_1' }, checkoutUrl: 'https://checkout.stripe.test/session' })
})).rejects.toThrow('Stripe subscription checkout is disabled')
expect(stripe.createCheckoutSession).toHaveBeenCalledWith(expect.objectContaining({
amount: 19900,
currency: 'MAD',
customerEmail: 'owner@example.test',
companyId: 'company_1',
subscriptionId: 'sub_1',
type: 'SUBSCRIPTION',
}))
expect(manualService.createCanonicalStripeCheckoutInvoice).toHaveBeenCalledWith(expect.objectContaining({
companyId: 'company_1',
subscriptionId: 'sub_1',
plan: 'GROWTH',
billingPeriod: 'MONTHLY',
amount: 19900,
stripeCheckoutSessionId: 'cs_test_123',
dueAt: new Date('2026-06-08T00:00:00.000Z'),
}))
expect(stripe.createCheckoutSession).not.toHaveBeenCalled()
expect(manualService.createCanonicalStripeCheckoutInvoice).not.toHaveBeenCalled()
})
it('activates the plan purchased through Stripe instead of keeping the previous subscription plan', async () => {
@@ -7,9 +7,7 @@ import * as stripe from '../../services/stripeService'
import * as repo from './subscription.repo'
import { SUBSCRIPTION_POLICY, getAccessLevel } from './subscription.policy'
import { getWebhookEventId, processWebhookOnce } from '../../security/webhookIdempotency'
import { assertAllowedPaymentRedirect } from '../../security/paymentRedirects'
import {
createCanonicalStripeCheckoutInvoice,
finalizeCanonicalOnlinePayment,
recordCanonicalOnlinePaymentFailure,
} from './subscription.manual.service'
@@ -36,10 +34,9 @@ export async function getPlans() {
}
export function getProviders() {
const stripeStatus = stripe.getConfigurationStatus()
return {
stripe: stripeStatus.configured,
stripeProblems: stripeStatus.problems,
stripe: false,
stripeProblems: [],
}
}
@@ -271,43 +268,7 @@ export async function checkout(companyId: string, body: {
currency: 'MAD'; provider: 'STRIPE'
successUrl: string; failureUrl: string
}) {
const dbPrice = await prisma.pricingConfig.findUnique({
where: { plan_billingPeriod: { plan: body.plan, billingPeriod: body.billingPeriod } },
})
const amount = dbPrice?.amount ?? (PLAN_PRICES[body.plan]?.[body.billingPeriod] as any)?.[body.currency]
if (!amount) throw new ValidationError('Invalid plan or billing period')
const company = await prisma.company.findUniqueOrThrow({ where: { id: companyId } })
const subscription = await repo.findOrCreateSubscription(companyId, body.plan, body.billingPeriod, body.currency)
assertAllowedPaymentRedirect(body.successUrl)
assertAllowedPaymentRedirect(body.failureUrl)
const orderId = `sub-${companyId}-${Date.now()}`
const description = `${body.plan} plan — ${body.billingPeriod}`
if (!stripe.isConfigured()) throw new ValidationError('Stripe is not configured on this platform')
const result = await stripe.createCheckoutSession({
amount, currency: body.currency, orderId, description,
customerEmail: company.email,
successUrl: body.successUrl,
cancelUrl: body.failureUrl,
companyId,
subscriptionId: subscription.id,
type: 'SUBSCRIPTION',
})
const dueAt = new Date(Date.now() + SUBSCRIPTION_POLICY.payment.paymentPendingTimeoutDays * 24 * 60 * 60 * 1000)
const invoice = await createCanonicalStripeCheckoutInvoice({
companyId,
subscriptionId: subscription.id,
plan: body.plan,
billingPeriod: body.billingPeriod,
amount,
currency: body.currency,
stripeCheckoutSessionId: result.sessionId,
dueAt,
})
return { invoice, checkoutUrl: result.checkoutUrl }
throw new ValidationError('Stripe subscription checkout is disabled')
}
export async function capturePaypal(companyId: string, paypalOrderId: string) {
@@ -378,24 +339,7 @@ export async function reactivate(companyId: string, body: {
currency: 'MAD'; provider: 'STRIPE'
successUrl: string; failureUrl: string
}) {
const sub = await repo.findByCompany(companyId)
if (!sub) throw new ValidationError('No subscription found')
const allowedStatuses = ['CANCELLED', 'EXPIRED', 'SUSPENDED', 'PAST_DUE', 'PAYMENT_PENDING']
if (!allowedStatuses.includes(sub.status)) {
throw new ValidationError(`Cannot reactivate a subscription with status ${sub.status}`)
}
await repo.setPaymentPending(sub.id)
await repo.createEvent({
subscriptionId: sub.id,
companyId,
eventType: 'subscription.reactivated',
source: 'user',
payload: { plan: body.plan, billingPeriod: body.billingPeriod },
})
return checkout(companyId, body)
throw new ValidationError('Stripe subscription checkout is disabled')
}
// ─── Scheduled job actions ────────────────────────────────────
@@ -50,18 +50,18 @@ const app = createApp()
describe('subscriptions public API', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(service.getProviders).mockReturnValue({ stripe: true, stripeProblems: [] })
vi.mocked(service.getProviders).mockReturnValue({ stripe: false, stripeProblems: [] })
vi.mocked(service.getPlans).mockResolvedValue({ STARTER: { MONTHLY: { MAD: 9900 } } } as never)
vi.mocked(service.getPlanFeatures).mockResolvedValue([
{ id: 'feature_1', plan: 'STARTER', label: 'Vehicles', sortOrder: 1 },
] as never)
})
it('GET /api/v1/subscriptions/providers exposes configured payment provider availability', async () => {
it('GET /api/v1/subscriptions/providers reports Stripe unavailable', async () => {
const res = await request(app).get('/api/v1/subscriptions/providers')
expect(res.status).toBe(200)
expect(res.body).toEqual({ data: { stripe: true, stripeProblems: [] } })
expect(res.body).toEqual({ data: { stripe: false, stripeProblems: [] } })
expect(service.getProviders).toHaveBeenCalledOnce()
})
@@ -53,7 +53,7 @@ describe('subscriptions public e2e smoke', () => {
expect(providers.body).toEqual({
data: {
stripe: false,
stripeProblems: ['STRIPE_API_KEY is missing', 'STRIPE_WEBHOOK_SECRET is missing'],
stripeProblems: [],
},
})