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',
|
plan: 'PRO',
|
||||||
billingPeriod: 'MONTHLY',
|
billingPeriod: 'MONTHLY',
|
||||||
currency: 'MAD',
|
currency: 'MAD',
|
||||||
method: 'STRIPE',
|
method: 'CASH',
|
||||||
idempotencyKey: '11111111-1111-4111-8111-111111111111',
|
idempotencyKey: '11111111-1111-4111-8111-111111111111',
|
||||||
}).success).toBe(false)
|
}).success).toBe(false)
|
||||||
expect(startTrialSchema.parse({ plan: 'STARTER', billingPeriod: 'ANNUAL' })).toMatchObject({ currency: 'MAD' })
|
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: 0, paymentMethod: 'CHECK' }).success).toBe(false)
|
||||||
expect(manualPaymentSchema.safeParse({ amount: 100, paymentMethod: 'PAYPAL' }).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: 'CASH' }).success).toBe(false)
|
||||||
expect(manualPaymentSchema.safeParse({ amount: 100, paymentMethod: 'STRIPE' as any }).success).toBe(false)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('validates refund and payment parameter payloads', () => {
|
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', () => {
|
it('accepts only bank transfer and check manual checkout', () => {
|
||||||
const base = { plan: 'GROWTH', billingPeriod: 'ANNUAL', currency: 'MAD', idempotencyKey: crypto.randomUUID() }
|
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: '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', () => {
|
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.parse(payload)).toEqual(payload)
|
||||||
expect(manualCheckoutSchema.safeParse({ ...payload, currency: 'USD' }).success).toBe(false)
|
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)
|
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',
|
plan: 'PRO',
|
||||||
billingPeriod: 'ANNUAL',
|
billingPeriod: 'ANNUAL',
|
||||||
currency: 'MAD',
|
currency: 'MAD',
|
||||||
method: 'STRIPE',
|
method: 'CASH',
|
||||||
idempotencyKey: '11111111-1111-4111-8111-111111111111',
|
idempotencyKey: '11111111-1111-4111-8111-111111111111',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const app = createApp()
|
|||||||
|
|
||||||
describe('subscription and team public boundary smoke', () => {
|
describe('subscription and team public boundary smoke', () => {
|
||||||
it('rejects malformed protected subscription checkout without leaking internals', async () => {
|
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([400, 401]).toContain(res.status)
|
||||||
expect(JSON.stringify(res.body)).not.toContain('stack')
|
expect(JSON.stringify(res.body)).not.toContain('stack')
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { parseBody, parseQuery } from '../../http/validate'
|
import { parseBody, parseQuery } from '../../http/validate'
|
||||||
import { carplaceReservationSchema, paginationSchema } from '../../modules/carplace/carplace.schemas'
|
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 = {}) {
|
function reqWith(body: unknown, query: unknown = {}) {
|
||||||
return { body, query, params: {} } as any
|
return { body, query, params: {} } as any
|
||||||
@@ -15,13 +15,14 @@ describe('public validation boundaries', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('blocks unsupported public payment providers before service orchestration', () => {
|
it('blocks unsupported payment methods before service orchestration', () => {
|
||||||
expect(() => parseBody(paySchema, reqWith({
|
expect(() => parseBody(manualCheckoutSchema, reqWith({
|
||||||
provider: 'STRIPE',
|
plan: 'PRO',
|
||||||
successUrl: 'https://ok.example.test',
|
billingPeriod: 'MONTHLY',
|
||||||
failureUrl: 'https://fail.example.test',
|
currency: 'MAD',
|
||||||
accessToken: 'booking-access-token-123',
|
method: 'CASH',
|
||||||
}))).toThrow('Invalid enum value')
|
idempotencyKey: '11111111-1111-4111-8111-111111111111',
|
||||||
|
}))).toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('requires enough anonymous carplace reservation identity to create a booking request', () => {
|
it('requires enough anonymous carplace reservation identity to create a booking request', () => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ describe('schema boundary integration markers', () => {
|
|||||||
plan: 'PRO',
|
plan: 'PRO',
|
||||||
billingPeriod: 'MONTHLY',
|
billingPeriod: 'MONTHLY',
|
||||||
currency: 'MAD',
|
currency: 'MAD',
|
||||||
method: 'STRIPE',
|
method: 'CASH',
|
||||||
idempotencyKey: '11111111-1111-4111-8111-111111111111',
|
idempotencyKey: '11111111-1111-4111-8111-111111111111',
|
||||||
}).success).toBe(false)
|
}).success).toBe(false)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user