fix stripe removal tests
Build & Push / Pipeline Tests (push) Failing after 1m26s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 49s
Test / API Unit Tests (push) Failing after 1m8s
Test / Homepage Unit Tests (push) Successful in 44s
Test / Carplace Unit Tests (push) Successful in 42s
Test / Admin Unit Tests (push) Successful in 39s
Test / Dashboard Unit Tests (push) Successful in 45s
Test / API Integration Tests (push) Failing after 1m7s
Build & Push / Pipeline Tests (push) Failing after 1m26s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 49s
Test / API Unit Tests (push) Failing after 1m8s
Test / Homepage Unit Tests (push) Successful in 44s
Test / Carplace Unit Tests (push) Successful in 42s
Test / Admin Unit Tests (push) Successful in 39s
Test / Dashboard Unit Tests (push) Successful in 45s
Test / API Integration Tests (push) Failing after 1m7s
This commit is contained in:
@@ -38,7 +38,7 @@ describe('operational schemas', () => {
|
||||
plan: 'PRO',
|
||||
billingPeriod: 'MONTHLY',
|
||||
currency: 'MAD',
|
||||
method: 'STRIPE',
|
||||
method: 'CASH',
|
||||
idempotencyKey: '11111111-1111-4111-8111-111111111111',
|
||||
}).success).toBe(false)
|
||||
expect(startTrialSchema.parse({ plan: 'STARTER', billingPeriod: 'ANNUAL' })).toMatchObject({ currency: 'MAD' })
|
||||
|
||||
@@ -7,7 +7,6 @@ describe('payment schemas edge cases', () => {
|
||||
expect(manualPaymentSchema.safeParse({ amount: 0, paymentMethod: 'CHECK' }).success).toBe(false)
|
||||
expect(manualPaymentSchema.safeParse({ amount: 100, paymentMethod: 'PAYPAL' }).success).toBe(false)
|
||||
expect(manualPaymentSchema.safeParse({ amount: 100, paymentMethod: 'CASH' }).success).toBe(false)
|
||||
expect(manualPaymentSchema.safeParse({ amount: 100, paymentMethod: 'STRIPE' as any }).success).toBe(false)
|
||||
})
|
||||
|
||||
it('validates refund and payment parameter payloads', () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ describe('manual subscription payment contracts', () => {
|
||||
it('accepts only bank transfer and check manual checkout', () => {
|
||||
const base = { plan: 'GROWTH', billingPeriod: 'ANNUAL', currency: 'MAD', idempotencyKey: crypto.randomUUID() }
|
||||
expect(manualCheckoutSchema.safeParse({ ...base, method: 'BANK_TRANSFER' }).success).toBe(true)
|
||||
expect(manualCheckoutSchema.safeParse({ ...base, method: 'STRIPE' }).success).toBe(false)
|
||||
expect(manualCheckoutSchema.safeParse({ ...base, method: 'CASH' }).success).toBe(false)
|
||||
})
|
||||
|
||||
it('normalizes references without discarding the display value', () => {
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('subscription.schemas edge contracts', () => {
|
||||
|
||||
expect(manualCheckoutSchema.parse(payload)).toEqual(payload)
|
||||
expect(manualCheckoutSchema.safeParse({ ...payload, currency: 'USD' }).success).toBe(false)
|
||||
expect(manualCheckoutSchema.safeParse({ ...payload, method: 'STRIPE' }).success).toBe(false)
|
||||
expect(manualCheckoutSchema.safeParse({ ...payload, method: 'CASH' }).success).toBe(false)
|
||||
expect(manualCheckoutSchema.safeParse({ ...payload, idempotencyKey: 'not-a-uuid' }).success).toBe(false)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
import Stripe from 'stripe'
|
||||
|
||||
const STRIPE_API_KEY = process.env.STRIPE_API_KEY ?? ''
|
||||
const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET ?? ''
|
||||
|
||||
let client: Stripe | null = null
|
||||
|
||||
function getClient() {
|
||||
if (!client) {
|
||||
client = new Stripe(STRIPE_API_KEY, {
|
||||
apiVersion: '2026-06-24.dahlia',
|
||||
appInfo: {
|
||||
name: 'RentalDriveGo',
|
||||
version: '1.0.0',
|
||||
},
|
||||
})
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
function integrationIdentifier() {
|
||||
const suffix = Math.random().toString(36).replace(/[^a-z]/g, '').slice(0, 8).padEnd(8, 'x')
|
||||
return `rentaldrivego_${suffix}`
|
||||
}
|
||||
|
||||
export interface StripeCheckoutParams {
|
||||
amount: number
|
||||
currency: string
|
||||
orderId: string
|
||||
description: string
|
||||
customerEmail?: string | null
|
||||
successUrl: string
|
||||
cancelUrl: string
|
||||
reservationId?: string
|
||||
companyId: string
|
||||
subscriptionId?: string
|
||||
type: 'CHARGE' | 'DEPOSIT' | 'SUBSCRIPTION'
|
||||
}
|
||||
|
||||
export interface StripeCheckoutResult {
|
||||
checkoutUrl: string
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
export async function createCheckoutSession(params: StripeCheckoutParams): Promise<StripeCheckoutResult> {
|
||||
const session = await getClient().checkout.sessions.create({
|
||||
mode: 'payment',
|
||||
success_url: params.successUrl,
|
||||
cancel_url: params.cancelUrl,
|
||||
customer_email: params.customerEmail ?? undefined,
|
||||
client_reference_id: params.orderId,
|
||||
line_items: [
|
||||
{
|
||||
quantity: 1,
|
||||
price_data: {
|
||||
currency: params.currency.toLowerCase(),
|
||||
unit_amount: params.amount,
|
||||
product_data: {
|
||||
name: params.description,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
...(params.reservationId ? { reservationId: params.reservationId } : {}),
|
||||
companyId: params.companyId,
|
||||
...(params.subscriptionId ? { subscriptionId: params.subscriptionId } : {}),
|
||||
type: params.type,
|
||||
orderId: params.orderId,
|
||||
},
|
||||
payment_intent_data: {
|
||||
metadata: {
|
||||
...(params.reservationId ? { reservationId: params.reservationId } : {}),
|
||||
companyId: params.companyId,
|
||||
...(params.subscriptionId ? { subscriptionId: params.subscriptionId } : {}),
|
||||
type: params.type,
|
||||
orderId: params.orderId,
|
||||
},
|
||||
},
|
||||
integration_identifier: integrationIdentifier(),
|
||||
})
|
||||
|
||||
if (!session.url) {
|
||||
throw new Error('Stripe checkout session did not include a checkout URL')
|
||||
}
|
||||
|
||||
return { checkoutUrl: session.url, sessionId: session.id }
|
||||
}
|
||||
|
||||
export function constructWebhookEvent(rawBody: string | Buffer, signature: string) {
|
||||
return getClient().webhooks.constructEvent(rawBody, signature, STRIPE_WEBHOOK_SECRET)
|
||||
}
|
||||
|
||||
export async function refundPaymentIntent(paymentIntentId: string, amount: number, reason?: string) {
|
||||
return getClient().refunds.create({
|
||||
payment_intent: paymentIntentId,
|
||||
amount,
|
||||
metadata: reason ? { reason } : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
export function isConfigured(): boolean {
|
||||
return getConfigurationStatus().configured
|
||||
}
|
||||
|
||||
export function getConfigurationStatus() {
|
||||
const problems: string[] = []
|
||||
|
||||
if (!STRIPE_API_KEY) {
|
||||
problems.push('STRIPE_API_KEY is missing')
|
||||
} else if (STRIPE_API_KEY.includes('placeholder')) {
|
||||
problems.push('STRIPE_API_KEY is still a placeholder')
|
||||
} else if (!STRIPE_API_KEY.startsWith('sk_') && !STRIPE_API_KEY.startsWith('rk_')) {
|
||||
problems.push('STRIPE_API_KEY must be a Stripe secret or restricted key')
|
||||
}
|
||||
|
||||
if (!STRIPE_WEBHOOK_SECRET) {
|
||||
problems.push('STRIPE_WEBHOOK_SECRET is missing')
|
||||
} else if (!STRIPE_WEBHOOK_SECRET.startsWith('whsec_')) {
|
||||
problems.push('STRIPE_WEBHOOK_SECRET must be a Stripe webhook signing secret')
|
||||
}
|
||||
|
||||
return {
|
||||
configured: problems.length === 0,
|
||||
problems,
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ describe('subscription and team API validation contracts', () => {
|
||||
plan: 'PRO',
|
||||
billingPeriod: 'ANNUAL',
|
||||
currency: 'MAD',
|
||||
method: 'STRIPE',
|
||||
method: 'CASH',
|
||||
idempotencyKey: '11111111-1111-4111-8111-111111111111',
|
||||
})
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ const app = createApp()
|
||||
|
||||
describe('subscription and team public boundary smoke', () => {
|
||||
it('rejects malformed protected subscription checkout without leaking internals', async () => {
|
||||
const res = await request(app).post('/api/v1/subscriptions/manual-checkout').send({ method: 'STRIPE' })
|
||||
const res = await request(app).post('/api/v1/subscriptions/manual-checkout').send({ method: 'CASH' })
|
||||
|
||||
expect([400, 401]).toContain(res.status)
|
||||
expect(JSON.stringify(res.body)).not.toContain('stack')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { parseBody, parseQuery } from '../../http/validate'
|
||||
import { carplaceReservationSchema, paginationSchema } from '../../modules/carplace/carplace.schemas'
|
||||
import { paySchema } from '../../modules/site/site.schemas'
|
||||
import { manualCheckoutSchema } from '../../modules/subscriptions/subscription.schemas'
|
||||
|
||||
function reqWith(body: unknown, query: unknown = {}) {
|
||||
return { body, query, params: {} } as any
|
||||
@@ -15,13 +15,14 @@ describe('public validation boundaries', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('blocks unsupported public payment providers before service orchestration', () => {
|
||||
expect(() => parseBody(paySchema, reqWith({
|
||||
provider: 'STRIPE',
|
||||
successUrl: 'https://ok.example.test',
|
||||
failureUrl: 'https://fail.example.test',
|
||||
accessToken: 'booking-access-token-123',
|
||||
}))).toThrow('Invalid enum value')
|
||||
it('blocks unsupported payment methods before service orchestration', () => {
|
||||
expect(() => parseBody(manualCheckoutSchema, reqWith({
|
||||
plan: 'PRO',
|
||||
billingPeriod: 'MONTHLY',
|
||||
currency: 'MAD',
|
||||
method: 'CASH',
|
||||
idempotencyKey: '11111111-1111-4111-8111-111111111111',
|
||||
}))).toThrow()
|
||||
})
|
||||
|
||||
it('requires enough anonymous carplace reservation identity to create a booking request', () => {
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('schema boundary integration markers', () => {
|
||||
plan: 'PRO',
|
||||
billingPeriod: 'MONTHLY',
|
||||
currency: 'MAD',
|
||||
method: 'STRIPE',
|
||||
method: 'CASH',
|
||||
idempotencyKey: '11111111-1111-4111-8111-111111111111',
|
||||
}).success).toBe(false)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user