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
@@ -1,98 +1,20 @@
import { describe, it, expect } from 'vitest'
import { describe, expect, it } from 'vitest'
import { presentBrand } from './company.presenter'
const fullBrand = {
id: 'brand_1',
companyId: 'comp_1',
displayName: 'Test Rentals',
subdomain: 'test-rentals',
logoUrl: 'https://example.com/logo.jpg',
primaryColor: '#1A56DB',
defaultLocale: 'en',
defaultCurrency: 'MAD',
amanpayMerchantId: 'merchant-abc',
amanpaySecretKey: 'super-secret-key',
paypalEmail: 'paypal@example.com',
paypalMerchantId: 'paypal-merchant-xyz',
isListedOnCarplace: true,
}
describe('company.presenter', () => {
const fullBrand = {
displayName: 'Atlas Cars',
subdomain: 'atlas',
primaryColor: '#2563eb',
}
describe('presentBrand', () => {
it('strips amanpaySecretKey from the response', () => {
it('returns brand fields as-is', () => {
const result = presentBrand(fullBrand)
expect(result).not.toHaveProperty('amanpaySecretKey')
expect(result).toEqual(fullBrand)
})
it('strips amanpayMerchantId from the response', () => {
const result = presentBrand(fullBrand)
expect(result).not.toHaveProperty('amanpayMerchantId')
})
it('strips paypalEmail from the response', () => {
const result = presentBrand(fullBrand)
expect(result).not.toHaveProperty('paypalEmail')
})
it('strips paypalMerchantId from the response', () => {
const result = presentBrand(fullBrand)
expect(result).not.toHaveProperty('paypalMerchantId')
})
it('sets amanpayConfigured=true when both amanpay credentials are present', () => {
const result = presentBrand(fullBrand)
expect(result.amanpayConfigured).toBe(true)
})
it('sets amanpayConfigured=false when amanpaySecretKey is null', () => {
const result = presentBrand({ ...fullBrand, amanpaySecretKey: null })
expect(result.amanpayConfigured).toBe(false)
})
it('sets amanpayConfigured=false when amanpayMerchantId is null', () => {
const result = presentBrand({ ...fullBrand, amanpayMerchantId: null })
expect(result.amanpayConfigured).toBe(false)
})
it('sets amanpayConfigured=false when both amanpay fields are null', () => {
const result = presentBrand({ ...fullBrand, amanpayMerchantId: null, amanpaySecretKey: null })
expect(result.amanpayConfigured).toBe(false)
})
it('sets paypalConfigured=true when paypalEmail is set', () => {
const result = presentBrand({ ...fullBrand, paypalMerchantId: null })
expect(result.paypalConfigured).toBe(true)
})
it('sets paypalConfigured=true when paypalMerchantId is set', () => {
const result = presentBrand({ ...fullBrand, paypalEmail: null })
expect(result.paypalConfigured).toBe(true)
})
it('sets paypalConfigured=false when both paypal fields are null', () => {
const result = presentBrand({ ...fullBrand, paypalEmail: null, paypalMerchantId: null })
expect(result.paypalConfigured).toBe(false)
})
it('preserves all non-credential fields', () => {
const result = presentBrand(fullBrand)
expect(result).toMatchObject({
id: 'brand_1',
companyId: 'comp_1',
displayName: 'Test Rentals',
subdomain: 'test-rentals',
logoUrl: 'https://example.com/logo.jpg',
primaryColor: '#1A56DB',
defaultLocale: 'en',
defaultCurrency: 'MAD',
isListedOnCarplace: true,
})
})
it('returns null when brand is null', () => {
it('returns null/undefined brand unchanged', () => {
expect(presentBrand(null)).toBeNull()
})
it('returns undefined when brand is undefined', () => {
expect(presentBrand(undefined)).toBeUndefined()
})
})
@@ -3,11 +3,5 @@ export function presentCompany(company: any) {
}
export function presentBrand(brand: any) {
if (!brand) return brand
const { amanpaySecretKey, amanpayMerchantId, paypalEmail, paypalMerchantId, ...safe } = brand
return {
...safe,
amanpayConfigured: !!(amanpayMerchantId && amanpaySecretKey),
paypalConfigured: !!(paypalEmail || paypalMerchantId),
}
return brand
}
@@ -9,7 +9,6 @@ describe('company schemas edge cases', () => {
const brand = brandSchema.parse({
displayName: 'Atlas',
websiteUrl: 'https://atlas.example.test',
paypalEmail: 'paypal@example.test',
defaultCurrency: 'MAD',
homePageConfig: { showOffers: true, layout: { items: [{ id: 'hero-1', type: 'hero', x: 1, y: 1, w: 12, h: 4 }] } },
})
@@ -22,10 +22,6 @@ export const brandSchema = z.object({
whatsappNumber: z.string().optional(),
defaultLocale: z.string().optional(),
defaultCurrency: z.literal('MAD').optional(),
amanpayMerchantId: z.string().optional(),
amanpaySecretKey: z.string().optional(),
paypalEmail: optionalEmailField(),
paypalMerchantId: z.string().optional(),
isListedOnCarplace: z.boolean().optional(),
homePageConfig: z.object({
heroTitle: z.union([z.string(), z.null()]).optional(),
@@ -73,11 +73,6 @@ const currentBrand = {
companyId: 'company_1',
displayName: 'Atlas Cars',
subdomain: 'atlas',
amanpayMerchantId: 'merchant_1',
amanpaySecretKey: 'secret_1',
paypalEmail: null,
paypalMerchantId: null,
paymentMethodsEnabled: ['AMANPAY'],
}
beforeEach(() => {
@@ -87,40 +82,18 @@ beforeEach(() => {
})
describe('company.service edge behavior', () => {
it('preserves existing AmanPay credentials when recomputing enabled payment methods', async () => {
it('updates brand settings through the repository presenter', async () => {
vi.mocked(repo.findBrand).mockResolvedValue(currentBrand as any)
vi.mocked(repo.upsertBrand).mockResolvedValue({ ...currentBrand, paypalEmail: 'billing@example.test' } as any)
vi.mocked(repo.upsertBrand).mockResolvedValue({ ...currentBrand, tagline: 'Premium rentals' } as any)
const result = await service.updateBrand('company_1', { paypalEmail: 'billing@example.test' }, 'Atlas Cars', 'atlas')
const result = await service.updateBrand('company_1', { tagline: 'Premium rentals' }, 'Atlas Cars', 'atlas')
expect(repo.upsertBrand).toHaveBeenCalledWith(
'company_1',
expect.objectContaining({
paypalEmail: 'billing@example.test',
paymentMethodsEnabled: ['AMANPAY', 'PAYPAL'],
}),
expect.objectContaining({
displayName: 'Atlas Cars',
subdomain: 'atlas',
paypalEmail: 'billing@example.test',
paymentMethodsEnabled: ['AMANPAY', 'PAYPAL'],
}),
)
expect(result).not.toHaveProperty('paypalEmail')
expect(result.paypalConfigured).toBe(true)
})
it('does not report AmanPay enabled when only one credential is available', async () => {
vi.mocked(repo.findBrand).mockResolvedValue({ ...currentBrand, amanpaySecretKey: null } as any)
vi.mocked(repo.upsertBrand).mockResolvedValue({ ...currentBrand, amanpaySecretKey: null } as any)
await service.updateBrand('company_1', {}, 'Atlas Cars', 'atlas')
expect(repo.upsertBrand).toHaveBeenCalledWith(
'company_1',
expect.objectContaining({ paymentMethodsEnabled: [] }),
expect.objectContaining({ paymentMethodsEnabled: [] }),
expect.objectContaining({ tagline: 'Premium rentals' }),
expect.objectContaining({ displayName: 'Atlas Cars', subdomain: 'atlas', tagline: 'Premium rentals' }),
)
expect(result).toMatchObject({ tagline: 'Premium rentals' })
})
it('normalizes custom domains and marks them pending verification', async () => {
@@ -4,17 +4,6 @@ import { presentCompany, presentBrand } from './company.presenter'
import * as repo from './company.repo'
import { resolveSettingsEntitlements, SettingsFeatureKey } from './settingsEntitlements'
function buildPaymentMethodsEnabled(input: {
amanpayMerchantId?: string | null
amanpaySecretKey?: string | null
paypalEmail?: string | null
}) {
const methods: Array<'AMANPAY' | 'PAYPAL'> = []
if (input.amanpayMerchantId && input.amanpaySecretKey) methods.push('AMANPAY')
if (input.paypalEmail) methods.push('PAYPAL')
return methods
}
export async function getCompany(companyId: string) {
return presentCompany(await repo.findCompany(companyId))
}
@@ -30,20 +19,11 @@ export async function getBrand(companyId: string) {
export async function updateBrand(companyId: string, body: any, companyName: string, companySlug: string) {
await assertSettingsFeature(companyId, 'settings.branding_basic')
if (body.primaryColor || body.accentColor) await assertSettingsFeature(companyId, 'settings.branding_custom')
if (body.amanpayMerchantId || body.amanpaySecretKey || body.paypalEmail || body.paypalMerchantId) {
await assertSettingsFeature(companyId, 'settings.renter_payments')
}
const current = await repo.findBrand(companyId)
const paymentMethodsEnabled = buildPaymentMethodsEnabled({
amanpayMerchantId: body.amanpayMerchantId ?? current?.amanpayMerchantId,
amanpaySecretKey: body.amanpaySecretKey ?? current?.amanpaySecretKey,
paypalEmail: body.paypalEmail ?? current?.paypalEmail,
})
return presentBrand(await repo.upsertBrand(
companyId,
{ ...body, paymentMethodsEnabled },
{ displayName: body.displayName ?? companyName, subdomain: companySlug, paymentMethodsEnabled, ...body },
body,
{ displayName: body.displayName ?? companyName, subdomain: companySlug, ...body },
))
}
@@ -65,9 +65,6 @@ const mockBrand = {
heroImageUrl: null,
customDomain: null,
customDomainVerified: false,
amanpayMerchantId: null,
amanpaySecretKey: null,
paypalEmail: null,
}
beforeEach(() => {
@@ -52,7 +52,7 @@ const SECTION_COPY: Record<Locale, Record<SettingsSectionKey, { label: string; d
en: {
company: { label: 'Company Profile', description: 'Manage public company details and defaults.' },
carplace: { label: 'Branding and Carplace', description: 'Control Carplace listing, logo, colors, and media.' },
payments: { label: 'Payment Methods', description: 'Configure renter payment providers.' },
payments: { label: 'Payment Methods', description: 'Record renter payments by bank transfer or check.' },
'rental-policies': { label: 'Rental Policies', description: 'Set fuel, damage, and additional-driver policies.' },
insurance: { label: 'Insurance Policies', description: 'Manage optional and required insurance products.' },
pricing: { label: 'Pricing Rules', description: 'Automate surcharges, discounts, and driver-based pricing.' },
@@ -61,7 +61,7 @@ const SECTION_COPY: Record<Locale, Record<SettingsSectionKey, { label: string; d
fr: {
company: { label: 'Profil entreprise', description: 'Gérez les informations publiques et les valeurs par défaut.' },
carplace: { label: 'Marque et vitrine', description: 'Contrôlez la publication, le logo, les couleurs et les médias.' },
payments: { label: 'Méthodes de paiement', description: 'Configurez les prestataires de paiement des locataires.' },
payments: { label: 'Méthodes de paiement', description: 'Enregistrez les paiements locataires par virement ou chèque.' },
'rental-policies': { label: 'Politiques de location', description: 'Définissez les règles carburant, dommages et conducteurs.' },
insurance: { label: 'Polices dassurance', description: 'Gérez les assurances optionnelles et obligatoires.' },
pricing: { label: 'Règles tarifaires', description: 'Automatisez les suppléments, remises et règles conducteur.' },
@@ -70,7 +70,7 @@ const SECTION_COPY: Record<Locale, Record<SettingsSectionKey, { label: string; d
ar: {
company: { label: 'ملف الشركة', description: 'إدارة بيانات الشركة العامة والإعدادات الافتراضية.' },
carplace: { label: 'العلامة والواجهة', description: 'التحكم في الظهور والشعار والألوان ووسائط الواجهة.' },
payments: { label: 'طرق الدفع', description: 'إعداد مزودي دفع المستأجرين.' },
payments: { label: 'طرق الدفع', description: 'تسجيل دفعات المستأجرين بالتحويل البنكي أو الشيك.' },
'rental-policies': { label: 'سياسات الإيجار', description: 'ضبط سياسات الوقود والأضرار والسائق الإضافي.' },
insurance: { label: 'سياسات التأمين', description: 'إدارة منتجات التأمين الاختيارية والإلزامية.' },
pricing: { label: 'قواعد التسعير', description: 'أتمتة الرسوم والخصومات وقواعد السائق.' },