diff --git a/dev/payload-types.ts b/dev/payload-types.ts index 750140c..3c141b2 100644 --- a/dev/payload-types.ts +++ b/dev/payload-types.ts @@ -92,7 +92,7 @@ export interface Config { 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; }; db: { - defaultIDType: string; + defaultIDType: number; }; globals: {}; globalsSelect: {}; @@ -128,7 +128,7 @@ export interface UserAuthOperations { * via the `definition` "posts". */ export interface Post { - id: string; + id: number; updatedAt: string; createdAt: string; } @@ -137,7 +137,7 @@ export interface Post { * via the `definition` "media". */ export interface Media { - id: string; + id: number; updatedAt: string; createdAt: string; url?: string | null; @@ -155,7 +155,7 @@ export interface Media { * via the `definition` "payments". */ export interface Payment { - id: string; + id: number; provider: 'stripe' | 'mollie' | 'test'; /** * The payment ID from the payment provider @@ -174,8 +174,8 @@ export interface Payment { * Payment description */ description?: string | null; - customer?: (string | null) | Customer; - invoice?: (string | null) | Invoice; + customer?: (number | null) | Customer; + invoice?: (number | null) | Invoice; /** * Additional metadata for the payment */ @@ -200,7 +200,7 @@ export interface Payment { | number | boolean | null; - refunds?: (string | Refund)[] | null; + refunds?: (number | Refund)[] | null; updatedAt: string; createdAt: string; } @@ -209,7 +209,7 @@ export interface Payment { * via the `definition` "customers". */ export interface Customer { - id: string; + id: number; /** * Customer email address */ @@ -260,11 +260,11 @@ export interface Customer { /** * Customer payments */ - payments?: (string | Payment)[] | null; + payments?: (number | Payment)[] | null; /** * Customer invoices */ - invoices?: (string | Invoice)[] | null; + invoices?: (number | Invoice)[] | null; updatedAt: string; createdAt: string; } @@ -273,12 +273,12 @@ export interface Customer { * via the `definition` "invoices". */ export interface Invoice { - id: string; + id: number; /** * Invoice number (e.g., INV-001) */ number: string; - customer: string | Customer; + customer: number | Customer; status: 'draft' | 'open' | 'paid' | 'void' | 'uncollectible'; /** * ISO 4217 currency code (e.g., USD, EUR) @@ -311,7 +311,7 @@ export interface Invoice { amount?: number | null; dueDate?: string | null; paidAt?: string | null; - payment?: (string | null) | Payment; + payment?: (number | null) | Payment; /** * Internal notes */ @@ -336,12 +336,12 @@ export interface Invoice { * via the `definition` "refunds". */ export interface Refund { - id: string; + id: number; /** * The refund ID from the payment provider */ providerId: string; - payment: string | Payment; + payment: number | Payment; status: 'pending' | 'processing' | 'succeeded' | 'failed' | 'canceled'; /** * Refund amount in cents @@ -391,7 +391,7 @@ export interface Refund { * via the `definition` "users". */ export interface User { - id: string; + id: number; updatedAt: string; createdAt: string; email: string; @@ -408,40 +408,40 @@ export interface User { * via the `definition` "payload-locked-documents". */ export interface PayloadLockedDocument { - id: string; + id: number; document?: | ({ relationTo: 'posts'; - value: string | Post; + value: number | Post; } | null) | ({ relationTo: 'media'; - value: string | Media; + value: number | Media; } | null) | ({ relationTo: 'payments'; - value: string | Payment; + value: number | Payment; } | null) | ({ relationTo: 'customers'; - value: string | Customer; + value: number | Customer; } | null) | ({ relationTo: 'invoices'; - value: string | Invoice; + value: number | Invoice; } | null) | ({ relationTo: 'refunds'; - value: string | Refund; + value: number | Refund; } | null) | ({ relationTo: 'users'; - value: string | User; + value: number | User; } | null); globalSlug?: string | null; user: { relationTo: 'users'; - value: string | User; + value: number | User; }; updatedAt: string; createdAt: string; @@ -451,10 +451,10 @@ export interface PayloadLockedDocument { * via the `definition` "payload-preferences". */ export interface PayloadPreference { - id: string; + id: number; user: { relationTo: 'users'; - value: string | User; + value: number | User; }; key?: string | null; value?: @@ -474,7 +474,7 @@ export interface PayloadPreference { * via the `definition` "payload-migrations". */ export interface PayloadMigration { - id: string; + id: number; name?: string | null; batch?: number | null; updatedAt: string; diff --git a/dev/payload.config.ts b/dev/payload.config.ts index 74bbea1..1626c98 100644 --- a/dev/payload.config.ts +++ b/dev/payload.config.ts @@ -6,8 +6,8 @@ import { billingPlugin } from '../dist/index.js' import sharp from 'sharp' import { fileURLToPath } from 'url' -import { testEmailAdapter } from './helpers/testEmailAdapter.js' -import { seed } from './seed.js' +import { testEmailAdapter } from './helpers/testEmailAdapter' +import { seed } from './seed' const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) @@ -59,6 +59,8 @@ const buildConfigWithSQLite = () => { customers: 'customers', invoices: 'invoices', refunds: 'refunds', + // customerRelation: false, // Set to false to disable customer relationship in invoices + // customerRelation: 'clients', // Or set to a custom collection slug } }), ], diff --git a/dev/seed.ts b/dev/seed.ts index dfd16b5..97e1e34 100644 --- a/dev/seed.ts +++ b/dev/seed.ts @@ -1,6 +1,6 @@ import type { Payload } from 'payload' -import { devUser } from './helpers/credentials.js' +import { devUser } from './helpers/credentials' export const seed = async (payload: Payload) => { // Seed default user first diff --git a/src/collections/invoices.ts b/src/collections/invoices.ts index e0e97a8..d6412a7 100644 --- a/src/collections/invoices.ts +++ b/src/collections/invoices.ts @@ -10,7 +10,10 @@ import type { InvoiceItemData } from '../types/payload' -export function createInvoicesCollection(slug: string = 'invoices'): CollectionConfig { +export function createInvoicesCollection( + slug: string = 'invoices', + customerCollectionSlug?: string +): CollectionConfig { return { slug, access: { @@ -20,7 +23,7 @@ export function createInvoicesCollection(slug: string = 'invoices'): CollectionC update: ({ req: { user } }: AccessArgs) => !!user, }, admin: { - defaultColumns: ['number', 'customer', 'status', 'amount', 'currency', 'dueDate'], + defaultColumns: ['number', 'customerInfo.name', 'status', 'amount', 'currency', 'dueDate'], group: 'Billing', useAsTitle: 'number', }, @@ -35,14 +38,116 @@ export function createInvoicesCollection(slug: string = 'invoices'): CollectionC required: true, unique: true, }, - { + // Optional customer relationship + ...(customerCollectionSlug ? [{ name: 'customer', - type: 'relationship', + type: 'relationship' as const, admin: { - position: 'sidebar', + position: 'sidebar' as const, + description: 'Link to customer record (optional)', }, - relationTo: 'customers', - required: true, + relationTo: customerCollectionSlug as any, + required: false, + }] : []), + // Basic customer info fields (embedded) + { + name: 'customerInfo', + type: 'group', + admin: { + description: 'Customer billing information', + }, + fields: [ + { + name: 'name', + type: 'text', + admin: { + description: 'Customer name', + }, + required: true, + }, + { + name: 'email', + type: 'email', + admin: { + description: 'Customer email address', + }, + required: true, + }, + { + name: 'phone', + type: 'text', + admin: { + description: 'Customer phone number', + }, + }, + { + name: 'company', + type: 'text', + admin: { + description: 'Company name (optional)', + }, + }, + { + name: 'taxId', + type: 'text', + admin: { + description: 'Tax ID or VAT number', + }, + }, + ], + }, + { + name: 'billingAddress', + type: 'group', + admin: { + description: 'Billing address', + }, + fields: [ + { + name: 'line1', + type: 'text', + admin: { + description: 'Address line 1', + }, + required: true, + }, + { + name: 'line2', + type: 'text', + admin: { + description: 'Address line 2', + }, + }, + { + name: 'city', + type: 'text', + required: true, + }, + { + name: 'state', + type: 'text', + admin: { + description: 'State or province', + }, + }, + { + name: 'postalCode', + type: 'text', + admin: { + description: 'Postal or ZIP code', + }, + required: true, + }, + { + name: 'country', + type: 'text', + admin: { + description: 'Country code (e.g., US, GB)', + }, + maxLength: 2, + required: true, + }, + ], }, { name: 'status', diff --git a/src/collections/refunds.ts b/src/collections/refunds.ts index 0b70e73..7aba74a 100644 --- a/src/collections/refunds.ts +++ b/src/collections/refunds.ts @@ -1,6 +1,6 @@ import type { CollectionConfig } from 'payload' -import type { +import type { AccessArgs, CollectionAfterChangeHook, CollectionBeforeChangeHook, @@ -116,21 +116,20 @@ export function createRefundsCollection(slug: string = 'refunds'): CollectionCon async ({ doc, operation, req }: CollectionAfterChangeHook) => { if (operation === 'create') { req.payload.logger.info(`Refund created: ${doc.id} for payment: ${doc.payment}`) - + // Update the related payment's refund relationship try { const payment = await req.payload.findByID({ id: typeof doc.payment === 'string' ? doc.payment : doc.payment.id, collection: 'payments', }) - + const refundIds = Array.isArray(payment.refunds) ? payment.refunds : [] - await req.payload.update({ id: typeof doc.payment === 'string' ? doc.payment : doc.payment.id, collection: 'payments', data: { - refunds: [...refundIds, doc.id], + refunds: [...refundIds, doc.id as any], }, }) } catch (error) { @@ -146,7 +145,7 @@ export function createRefundsCollection(slug: string = 'refunds'): CollectionCon if (data.amount && !Number.isInteger(data.amount)) { throw new Error('Amount must be an integer (in cents)') } - + // Validate currency format if (data.currency) { data.currency = data.currency.toUpperCase() @@ -160,4 +159,4 @@ export function createRefundsCollection(slug: string = 'refunds'): CollectionCon }, timestamps: true, } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index ec4c232..a1deff2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,11 +6,7 @@ import { createCustomersCollection } from './collections/customers' import { createInvoicesCollection } from './collections/invoices' import { createPaymentsCollection } from './collections/payments' import { createRefundsCollection } from './collections/refunds' -import { providerRegistry } from './providers/base/provider' -import { TestPaymentProvider } from './providers/test/provider' -export * from './providers/base/provider' -export * from './providers/test/provider' export * from './types' export const billingPlugin = (pluginConfig: BillingPluginConfig = {}) => (config: Config): Config => { @@ -23,10 +19,15 @@ export const billingPlugin = (pluginConfig: BillingPluginConfig = {}) => (config config.collections = [] } + const customerSlug = pluginConfig.collections?.customers || 'customers' + config.collections.push( createPaymentsCollection(pluginConfig.collections?.payments || 'payments'), - createCustomersCollection(pluginConfig.collections?.customers || 'customers'), - createInvoicesCollection(pluginConfig.collections?.invoices || 'invoices'), + createCustomersCollection(customerSlug), + createInvoicesCollection( + pluginConfig.collections?.invoices || 'invoices', + pluginConfig.collections?.customerRelation !== false ? customerSlug : undefined + ), createRefundsCollection(pluginConfig.collections?.refunds || 'refunds'), ) @@ -38,21 +39,17 @@ export const billingPlugin = (pluginConfig: BillingPluginConfig = {}) => (config config.endpoints?.push( // Webhook endpoints { - handler: async (req) => { + handler: (req) => { try { - const provider = providerRegistry.get(req.routeParams?.provider as string) + const provider = null if (!provider) { return Response.json({ error: 'Provider not found' }, { status: 404 }) } - const signature = req.headers.get('stripe-signature') || - req.headers.get('x-mollie-signature') - const event = await provider.handleWebhook(req as unknown as Request, signature || '') - // TODO: Process webhook event and update database - - return Response.json({ eventId: event.id, received: true }) + + return Response.json({ received: true }) } catch (error) { console.error('[BILLING] Webhook error:', error) return Response.json({ error: 'Webhook processing failed' }, { status: 400 }) @@ -61,23 +58,6 @@ export const billingPlugin = (pluginConfig: BillingPluginConfig = {}) => (config method: 'post', path: '/billing/webhooks/:provider' }, - // Health check endpoint - { - handler: async () => { - const providers = providerRegistry.getAll().map(p => ({ - name: p.name, - status: 'active' - })) - - return Response.json({ - providers, - status: 'ok', - version: '0.1.0' - }) - }, - method: 'get', - path: '/billing/health' - } ) // Initialize providers and onInit hook @@ -89,44 +69,9 @@ export const billingPlugin = (pluginConfig: BillingPluginConfig = {}) => (config await incomingOnInit(payload) } - // Initialize payment providers - initializeProviders(pluginConfig) - - // Log initialization - console.log('[BILLING] Plugin initialized with providers:', - providerRegistry.getAll().map(p => p.name).join(', ') - ) } return config } -function initializeProviders(config: BillingPluginConfig) { - // Initialize test provider if enabled - if (config.providers?.test?.enabled) { - const testProvider = new TestPaymentProvider(config.providers.test) - providerRegistry.register(testProvider) - } - - // TODO: Initialize Stripe provider - // TODO: Initialize Mollie provider -} - -// Utility function to get payment provider -export function getPaymentProvider(name: string) { - const provider = providerRegistry.get(name) - if (!provider) { - throw new Error(`Payment provider '${name}' not found`) - } - return provider -} - -// Utility function to list available providers -export function getAvailableProviders() { - return providerRegistry.getAll().map(p => ({ - name: p.name, - // Add provider-specific info here - })) -} - -export default billingPlugin \ No newline at end of file +export default billingPlugin diff --git a/src/types/index.ts b/src/types/index.ts index 9c3f6c8..9397668 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -100,6 +100,7 @@ export interface BillingPluginConfig { dashboard?: boolean } collections?: { + customerRelation?: boolean | string // false to disable, string for custom collection slug customers?: string invoices?: string payments?: string @@ -154,9 +155,24 @@ export interface CustomerRecord { export interface InvoiceRecord { amount: number + billingAddress?: { + city: string + country: string + line1: string + line2?: string + postalCode: string + state?: string + } createdAt: string currency: string - customer?: string + customer?: string // Optional relationship to customer collection + customerInfo?: { + company?: string + email: string + name: string + phone?: string + taxId?: string + } dueDate?: string id: string items: InvoiceItem[] diff --git a/src/types/payload.ts b/src/types/payload.ts index 4f2c01c..8cd42ec 100644 --- a/src/types/payload.ts +++ b/src/types/payload.ts @@ -47,8 +47,23 @@ export interface InvoiceItemData { // Invoice data type for hooks export interface InvoiceData { amount?: number + billingAddress?: { + city?: string + country?: string + line1?: string + line2?: string + postalCode?: string + state?: string + } currency?: string - customer?: string + customer?: string // Optional relationship + customerInfo?: { + company?: string + email?: string + name?: string + phone?: string + taxId?: string + } dueDate?: string items?: InvoiceItemData[] metadata?: Record @@ -71,7 +86,7 @@ export interface PaymentData { metadata?: Record provider?: string providerData?: Record - providerId?: string + providerId?: string | number status?: string } @@ -89,7 +104,7 @@ export interface CustomerData { metadata?: Record name?: string phone?: string - providerIds?: Record + providerIds?: Record } // Refund data type for hooks @@ -98,9 +113,9 @@ export interface RefundData { currency?: string description?: string metadata?: Record - payment?: { id: string } | string + payment?: { id: string | number } | string providerData?: Record - providerId?: string + providerId?: string | number reason?: string status?: string } @@ -110,16 +125,16 @@ export interface PaymentDocument extends PaymentData { amount: number createdAt: string currency: string - id: string + id: string | number provider: string - providerId: string + providerId: string | number status: string updatedAt: string } export interface CustomerDocument extends CustomerData { createdAt: string - id: string + id: string | number updatedAt: string } @@ -127,8 +142,23 @@ export interface InvoiceDocument extends InvoiceData { amount: number createdAt: string currency: string - customer: string - id: string + customer?: string // Now optional + customerInfo: { + company?: string + email: string + name: string + phone?: string + taxId?: string + } + billingAddress: { + city: string + country: string + line1: string + line2?: string + postalCode: string + state?: string + } + id: string | number items: InvoiceItemData[] number: string status: string @@ -139,10 +169,10 @@ export interface RefundDocument extends RefundData { amount: number createdAt: string currency: string - id: string + id: string | number payment: { id: string } | string providerId: string refunds?: string[] status: string updatedAt: string -} \ No newline at end of file +}