add stripe
Build & Push / Pipeline Tests (push) Failing after 1m6s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Failing after 56s
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-07-22 20:17:12 -04:00
parent 7ecd85e9b7
commit bcabd17220
38 changed files with 1674 additions and 96 deletions
@@ -13,9 +13,15 @@ vi.mock('../../services/paypalService', () => ({
verifyWebhookEvent: vi.fn(),
}))
vi.mock('../../services/stripeService', () => ({
isConfigured: vi.fn(),
constructWebhookEvent: vi.fn(),
}))
vi.mock('../../modules/payments/payment.service', () => ({
handleAmanpayWebhook: vi.fn(),
handlePaypalWebhook: vi.fn(),
handleStripeWebhook: vi.fn(),
listByCompany: vi.fn(),
listByReservation: vi.fn(),
initCharge: vi.fn(),
@@ -28,6 +34,7 @@ import request from 'supertest'
import { createApp } from '../../app'
import * as amanpay from '../../services/amanpayService'
import * as paypal from '../../services/paypalService'
import * as stripe from '../../services/stripeService'
import * as service from '../../modules/payments/payment.service'
const app = createApp()
@@ -96,6 +103,37 @@ describe('payments API contract', () => {
expect(service.handlePaypalWebhook).toHaveBeenCalledWith(payload, JSON.stringify(payload))
})
it('rejects Stripe webhooks when signature validation fails', async () => {
vi.mocked(stripe.isConfigured).mockReturnValue(true)
vi.mocked(stripe.constructWebhookEvent).mockImplementation(() => {
throw new Error('bad signature')
})
const res = await request(app)
.post('/api/v1/payments/webhooks/stripe')
.set('stripe-signature', 'bad')
.send({ id: 'evt_1', type: 'checkout.session.completed' })
expect(res.status).toBe(401)
expect(res.body).toEqual({ error: 'invalid_signature' })
expect(service.handleStripeWebhook).not.toHaveBeenCalled()
})
it('accepts verified Stripe webhooks and delegates handling', async () => {
vi.mocked(stripe.isConfigured).mockReturnValue(true)
vi.mocked(stripe.constructWebhookEvent).mockReturnValue({ id: 'evt_1', type: 'checkout.session.completed' } as never)
const payload = { id: 'evt_1', type: 'checkout.session.completed' }
const res = await request(app)
.post('/api/v1/payments/webhooks/stripe')
.set('stripe-signature', 'good')
.send(payload)
expect(res.status).toBe(200)
expect(res.body).toEqual({ received: true })
expect(service.handleStripeWebhook).toHaveBeenCalledWith({ id: 'evt_1', type: 'checkout.session.completed' }, JSON.stringify(payload))
})
it('keeps authenticated payment routes behind auth before any payment service call', async () => {
const res = await request(app).get('/api/v1/payments/company')
@@ -24,18 +24,25 @@ vi.mock('../../services/paypalService', () => ({
verifyWebhookEvent: vi.fn(),
}))
vi.mock('../../services/stripeService', () => ({
isConfigured: vi.fn(),
constructWebhookEvent: vi.fn(),
}))
vi.mock('../../modules/subscriptions/subscription.service', () => ({
getPlans: vi.fn(),
getProviders: vi.fn(),
getPlanFeatures: vi.fn(),
handleAmanpayWebhook: vi.fn(),
handlePaypalWebhook: vi.fn(),
handleStripeWebhook: vi.fn(),
}))
import request from 'supertest'
import { createApp } from '../../app'
import * as amanpay from '../../services/amanpayService'
import * as paypal from '../../services/paypalService'
import * as stripe from '../../services/stripeService'
import * as service from '../../modules/subscriptions/subscription.service'
const app = createApp()
@@ -43,7 +50,7 @@ const app = createApp()
describe('subscriptions public API', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(service.getProviders).mockReturnValue({ amanpay: false, paypal: true })
vi.mocked(service.getProviders).mockReturnValue({ stripe: true, 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 },
@@ -54,7 +61,7 @@ describe('subscriptions public API', () => {
const res = await request(app).get('/api/v1/subscriptions/providers')
expect(res.status).toBe(200)
expect(res.body).toEqual({ data: { amanpay: false, paypal: true } })
expect(res.body).toEqual({ data: { stripe: true, stripeProblems: [] } })
expect(service.getProviders).toHaveBeenCalledOnce()
})
@@ -102,4 +109,21 @@ describe('subscriptions public API', () => {
expect(res.body).toEqual({ received: true })
expect(service.handlePaypalWebhook).toHaveBeenCalledWith(payload, JSON.stringify(payload))
})
it('accepts verified Stripe webhooks and delegates handling to the subscription service', async () => {
vi.mocked(stripe.isConfigured).mockReturnValue(true)
vi.mocked(stripe.constructWebhookEvent).mockReturnValue({ id: 'evt_1', type: 'checkout.session.completed' } as never)
const res = await request(app)
.post('/api/v1/subscriptions/webhooks/stripe')
.set('stripe-signature', 'good')
.send({ id: 'evt_1' })
expect(res.status).toBe(200)
expect(res.body).toEqual({ received: true })
expect(service.handleStripeWebhook).toHaveBeenCalledWith(
{ id: 'evt_1', type: 'checkout.session.completed' },
JSON.stringify({ id: 'evt_1' }),
)
})
})
@@ -7,6 +7,6 @@ describe('schema boundary integration markers', () => {
it('keeps public commercial payloads constrained before database-backed flows execute', () => {
expect(companySignupSchema.safeParse({}).success).toBe(false)
expect(reservationCreateSchema.safeParse({ vehicleId: 'bad', customerId: 'bad', startDate: 'bad', endDate: 'bad' }).success).toBe(false)
expect(subscriptionCheckoutSchema.safeParse({ plan: 'PRO', billingPeriod: 'MONTHLY', currency: 'MAD', provider: 'PAYPAL', successUrl: 'https://ok.example.test', failureUrl: 'https://fail.example.test' }).success).toBe(true)
expect(subscriptionCheckoutSchema.safeParse({ plan: 'PRO', billingPeriod: 'MONTHLY', currency: 'MAD', provider: 'STRIPE', successUrl: 'https://ok.example.test', failureUrl: 'https://fail.example.test' }).success).toBe(true)
})
})
@@ -99,8 +99,7 @@ describe('Subscriptions API', () => {
const res = await request(app).get('/api/v1/subscriptions/providers')
expect(res.status).toBe(200)
expect(typeof res.body.data.amanpay).toBe('boolean')
expect(typeof res.body.data.paypal).toBe('boolean')
expect(typeof res.body.data.stripe).toBe('boolean')
})
})