feat: Add Mollie payment provider support

- Introduce `mollieProvider` for handling Mollie payments
- Add configurable payment hooks for initialization and processing
- Implement `initPayment` logic to create Mollie payments and update metadata
- Include types for Mollie integration in payments and refunds
- Update `package.json` to include `@mollie/api-client` dependency
- Refactor existing payment-related types into modular files for better maintainability
This commit is contained in:
2025-09-16 22:10:47 +02:00
parent 0308e30ebd
commit e3a58fe6bc
23 changed files with 890 additions and 207 deletions

11
src/collections/hooks.ts Normal file
View File

@@ -0,0 +1,11 @@
import type { Payment } from '@/plugin/types'
import type { Payload } from 'payload'
import { useBillingPlugin } from '@/plugin'
export const initProviderPayment = (payload: Payload, payment: Partial<Payment>) => {
const billing = useBillingPlugin(payload)
if (!payment.provider || !billing.providerConfig[payment.provider]) {
throw new Error(`Provider ${payment.provider} not found.`)
}
return billing.providerConfig[payment.provider].initPayment(payload, payment)
}

View File

@@ -5,9 +5,10 @@ import {
CollectionBeforeValidateHook,
CollectionConfig, Field,
} from 'payload'
import { BillingPluginConfig, CustomerInfoExtractor, defaults } from '@/plugin/config'
import { Invoice } from '@/plugin/types'
import type { BillingPluginConfig} from '@/plugin/config';
import { defaults } from '@/plugin/config'
import { extractSlug } from '@/plugin/utils'
import type { Invoice } from '@/plugin/types/invoices'
export function createInvoicesCollection(pluginConfig: BillingPluginConfig): CollectionConfig {
const {customerRelationSlug, customerInfoExtractor} = pluginConfig
@@ -31,7 +32,7 @@ export function createInvoicesCollection(pluginConfig: BillingPluginConfig): Col
position: 'sidebar' as const,
description: 'Link to customer record (optional)',
},
relationTo: pluginConfig.customerRelationSlug as never,
relationTo: extractSlug(customerRelationSlug),
required: false,
}] : []),
// Basic customer info fields (embedded)
@@ -275,7 +276,7 @@ export function createInvoicesCollection(pluginConfig: BillingPluginConfig): Col
condition: (data) => data.status === 'paid',
position: 'sidebar',
},
relationTo: 'payments',
relationTo: extractSlug(pluginConfig.collections?.payments || defaults.paymentsCollection),
},
{
name: 'notes',

View File

@@ -1,8 +1,9 @@
import type { AccessArgs, CollectionBeforeChangeHook, CollectionConfig, Field } from 'payload'
import type { Payment } from '@/plugin/types'
import type { AccessArgs, CollectionBeforeChangeHook, CollectionConfig, CollectionSlug, Field } from 'payload'
import type { BillingPluginConfig} from '@/plugin/config';
import { defaults } from '@/plugin/config'
import { extractSlug } from '@/plugin/utils'
import { Payment } from '@/plugin/types/payments'
import { initProviderPayment } from '@/collections/hooks'
export function createPaymentsCollection(pluginConfig: BillingPluginConfig): CollectionConfig {
const overrides = typeof pluginConfig.collections?.payments === 'object' ? pluginConfig.collections?.payments : {}
@@ -27,7 +28,6 @@ export function createPaymentsCollection(pluginConfig: BillingPluginConfig): Col
description: 'The payment ID from the payment provider',
},
label: 'Provider Payment ID',
required: true,
unique: true,
},
{
@@ -78,7 +78,7 @@ export function createPaymentsCollection(pluginConfig: BillingPluginConfig): Col
admin: {
position: 'sidebar',
},
relationTo: 'invoices',
relationTo: extractSlug(pluginConfig.collections?.invoices || defaults.invoicesCollection) as CollectionSlug,
},
{
name: 'metadata',
@@ -103,7 +103,7 @@ export function createPaymentsCollection(pluginConfig: BillingPluginConfig): Col
readOnly: true,
},
hasMany: true,
relationTo: 'refunds',
relationTo: extractSlug(pluginConfig.collections?.refunds || defaults.refundsCollection) as CollectionSlug,
},
]
if (overrides?.fields) {
@@ -126,7 +126,7 @@ export function createPaymentsCollection(pluginConfig: BillingPluginConfig): Col
fields,
hooks: {
beforeChange: [
({ data, operation }) => {
async ({ data, operation, req }) => {
if (operation === 'create') {
// Validate amount format
if (data.amount && !Number.isInteger(data.amount)) {
@@ -140,6 +140,8 @@ export function createPaymentsCollection(pluginConfig: BillingPluginConfig): Col
throw new Error('Currency must be a 3-letter ISO code')
}
}
await initProviderPayment(req.payload, data)
}
},
] satisfies CollectionBeforeChangeHook<Payment>[],

View File

@@ -1,9 +1,9 @@
import type { AccessArgs, CollectionConfig } from 'payload'
import { BillingPluginConfig, defaults } from '@/plugin/config'
import { extractSlug } from '@/plugin/utils'
import { Payment } from '@/plugin/types'
export function createRefundsCollection(pluginConfig: BillingPluginConfig): CollectionConfig {
const overrides = typeof pluginConfig.collections?.invoices === 'object' ? pluginConfig.collections?.invoices : {}
// TODO: finish collection overrides
return {
slug: extractSlug(pluginConfig.collections?.refunds || defaults.refundsCollection),
@@ -35,7 +35,7 @@ export function createRefundsCollection(pluginConfig: BillingPluginConfig): Coll
admin: {
position: 'sidebar',
},
relationTo: 'payments',
relationTo: extractSlug(pluginConfig.collections?.payments || defaults.paymentsCollection),
required: true,
},
{
@@ -117,13 +117,13 @@ export function createRefundsCollection(pluginConfig: BillingPluginConfig): Coll
try {
const payment = await req.payload.findByID({
id: typeof doc.payment === 'string' ? doc.payment : doc.payment.id,
collection: 'payments',
})
collection: extractSlug(pluginConfig.collections?.payments || defaults.paymentsCollection),
}) as Payment
const refundIds = Array.isArray(payment.refunds) ? payment.refunds : []
await req.payload.update({
id: typeof doc.payment === 'string' ? doc.payment : doc.payment.id,
collection: 'payments',
collection: extractSlug(pluginConfig.collections?.payments || defaults.paymentsCollection),
data: {
refunds: [...refundIds, doc.id],
},