mirror of
https://github.com/xtr-dev/payload-billing.git
synced 2025-12-10 02:43:24 +00:00
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:
@@ -1,11 +1,13 @@
|
||||
import configPromise from '@payload-config'
|
||||
import { getPayload } from 'payload'
|
||||
import { useBillingPlugin } from '../../../src/plugin'
|
||||
|
||||
export const GET = async (request: Request) => {
|
||||
const payload = await getPayload({
|
||||
config: configPromise,
|
||||
})
|
||||
|
||||
|
||||
return Response.json({
|
||||
message: 'This is an example of a custom route.',
|
||||
})
|
||||
|
||||
627
dev/payload-types.ts
Normal file
627
dev/payload-types.ts
Normal file
@@ -0,0 +1,627 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* This file was automatically generated by Payload.
|
||||
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported timezones in IANA format.
|
||||
*
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "supportedTimezones".
|
||||
*/
|
||||
export type SupportedTimezones =
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Brisbane'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji';
|
||||
|
||||
export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
};
|
||||
blocks: {};
|
||||
collections: {
|
||||
posts: Post;
|
||||
media: Media;
|
||||
payments: Payment;
|
||||
invoices: Invoice;
|
||||
refunds: Refund;
|
||||
users: User;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
posts: PostsSelect<false> | PostsSelect<true>;
|
||||
media: MediaSelect<false> | MediaSelect<true>;
|
||||
payments: PaymentsSelect<false> | PaymentsSelect<true>;
|
||||
invoices: InvoicesSelect<false> | InvoicesSelect<true>;
|
||||
refunds: RefundsSelect<false> | RefundsSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: number;
|
||||
};
|
||||
globals: {};
|
||||
globalsSelect: {};
|
||||
locale: null;
|
||||
user: User & {
|
||||
collection: 'users';
|
||||
};
|
||||
jobs: {
|
||||
tasks: unknown;
|
||||
workflows: unknown;
|
||||
};
|
||||
}
|
||||
export interface UserAuthOperations {
|
||||
forgotPassword: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
login: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
registerFirstUser: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
unlock: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: number;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media".
|
||||
*/
|
||||
export interface Media {
|
||||
id: number;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
thumbnailURL?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
focalX?: number | null;
|
||||
focalY?: number | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payments".
|
||||
*/
|
||||
export interface Payment {
|
||||
id: number;
|
||||
provider: 'stripe' | 'mollie' | 'test';
|
||||
/**
|
||||
* The payment ID from the payment provider
|
||||
*/
|
||||
providerId: string;
|
||||
status: 'pending' | 'processing' | 'succeeded' | 'failed' | 'canceled' | 'refunded' | 'partially_refunded';
|
||||
/**
|
||||
* Amount in cents (e.g., 2000 = $20.00)
|
||||
*/
|
||||
amount: number;
|
||||
/**
|
||||
* ISO 4217 currency code (e.g., USD, EUR)
|
||||
*/
|
||||
currency: string;
|
||||
/**
|
||||
* Payment description
|
||||
*/
|
||||
description?: string | null;
|
||||
invoice?: (number | null) | Invoice;
|
||||
/**
|
||||
* Additional metadata for the payment
|
||||
*/
|
||||
metadata?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
/**
|
||||
* Raw data from the payment provider
|
||||
*/
|
||||
providerData?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
refunds?: (number | Refund)[] | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "invoices".
|
||||
*/
|
||||
export interface Invoice {
|
||||
id: number;
|
||||
/**
|
||||
* Invoice number (e.g., INV-001)
|
||||
*/
|
||||
number: string;
|
||||
/**
|
||||
* Customer billing information
|
||||
*/
|
||||
customerInfo: {
|
||||
/**
|
||||
* Customer name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Customer email address
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* Customer phone number
|
||||
*/
|
||||
phone?: string | null;
|
||||
/**
|
||||
* Company name (optional)
|
||||
*/
|
||||
company?: string | null;
|
||||
/**
|
||||
* Tax ID or VAT number
|
||||
*/
|
||||
taxId?: string | null;
|
||||
};
|
||||
/**
|
||||
* Billing address
|
||||
*/
|
||||
billingAddress: {
|
||||
/**
|
||||
* Address line 1
|
||||
*/
|
||||
line1: string;
|
||||
/**
|
||||
* Address line 2
|
||||
*/
|
||||
line2?: string | null;
|
||||
city: string;
|
||||
/**
|
||||
* State or province
|
||||
*/
|
||||
state?: string | null;
|
||||
/**
|
||||
* Postal or ZIP code
|
||||
*/
|
||||
postalCode: string;
|
||||
/**
|
||||
* Country code (e.g., US, GB)
|
||||
*/
|
||||
country: string;
|
||||
};
|
||||
status: 'draft' | 'open' | 'paid' | 'void' | 'uncollectible';
|
||||
/**
|
||||
* ISO 4217 currency code (e.g., USD, EUR)
|
||||
*/
|
||||
currency: string;
|
||||
items: {
|
||||
description: string;
|
||||
quantity: number;
|
||||
/**
|
||||
* Amount in cents
|
||||
*/
|
||||
unitAmount: number;
|
||||
/**
|
||||
* Calculated: quantity × unitAmount
|
||||
*/
|
||||
totalAmount?: number | null;
|
||||
id?: string | null;
|
||||
}[];
|
||||
/**
|
||||
* Sum of all line items
|
||||
*/
|
||||
subtotal?: number | null;
|
||||
/**
|
||||
* Tax amount in cents
|
||||
*/
|
||||
taxAmount?: number | null;
|
||||
/**
|
||||
* Total amount (subtotal + tax)
|
||||
*/
|
||||
amount?: number | null;
|
||||
dueDate?: string | null;
|
||||
paidAt?: string | null;
|
||||
payment?: (number | null) | Payment;
|
||||
/**
|
||||
* Internal notes
|
||||
*/
|
||||
notes?: string | null;
|
||||
/**
|
||||
* Additional invoice metadata
|
||||
*/
|
||||
metadata?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "refunds".
|
||||
*/
|
||||
export interface Refund {
|
||||
id: number;
|
||||
/**
|
||||
* The refund ID from the payment provider
|
||||
*/
|
||||
providerId: string;
|
||||
payment: number | Payment;
|
||||
status: 'pending' | 'processing' | 'succeeded' | 'failed' | 'canceled';
|
||||
/**
|
||||
* Refund amount in cents
|
||||
*/
|
||||
amount: number;
|
||||
/**
|
||||
* ISO 4217 currency code (e.g., USD, EUR)
|
||||
*/
|
||||
currency: string;
|
||||
/**
|
||||
* Reason for the refund
|
||||
*/
|
||||
reason?: ('duplicate' | 'fraudulent' | 'requested_by_customer' | 'other') | null;
|
||||
/**
|
||||
* Additional details about the refund
|
||||
*/
|
||||
description?: string | null;
|
||||
/**
|
||||
* Additional refund metadata
|
||||
*/
|
||||
metadata?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
/**
|
||||
* Raw data from the payment provider
|
||||
*/
|
||||
providerData?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: number;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string | null;
|
||||
resetPasswordExpiration?: string | null;
|
||||
salt?: string | null;
|
||||
hash?: string | null;
|
||||
loginAttempts?: number | null;
|
||||
lockUntil?: string | null;
|
||||
password?: string | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents".
|
||||
*/
|
||||
export interface PayloadLockedDocument {
|
||||
id: number;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: number | Post;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'media';
|
||||
value: number | Media;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'payments';
|
||||
value: number | Payment;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'invoices';
|
||||
value: number | Invoice;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'refunds';
|
||||
value: number | Refund;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-preferences".
|
||||
*/
|
||||
export interface PayloadPreference {
|
||||
id: number;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
};
|
||||
key?: string | null;
|
||||
value?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-migrations".
|
||||
*/
|
||||
export interface PayloadMigration {
|
||||
id: number;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts_select".
|
||||
*/
|
||||
export interface PostsSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "media_select".
|
||||
*/
|
||||
export interface MediaSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
url?: T;
|
||||
thumbnailURL?: T;
|
||||
filename?: T;
|
||||
mimeType?: T;
|
||||
filesize?: T;
|
||||
width?: T;
|
||||
height?: T;
|
||||
focalX?: T;
|
||||
focalY?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payments_select".
|
||||
*/
|
||||
export interface PaymentsSelect<T extends boolean = true> {
|
||||
provider?: T;
|
||||
providerId?: T;
|
||||
status?: T;
|
||||
amount?: T;
|
||||
currency?: T;
|
||||
description?: T;
|
||||
invoice?: T;
|
||||
metadata?: T;
|
||||
providerData?: T;
|
||||
refunds?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "invoices_select".
|
||||
*/
|
||||
export interface InvoicesSelect<T extends boolean = true> {
|
||||
number?: T;
|
||||
customerInfo?:
|
||||
| T
|
||||
| {
|
||||
name?: T;
|
||||
email?: T;
|
||||
phone?: T;
|
||||
company?: T;
|
||||
taxId?: T;
|
||||
};
|
||||
billingAddress?:
|
||||
| T
|
||||
| {
|
||||
line1?: T;
|
||||
line2?: T;
|
||||
city?: T;
|
||||
state?: T;
|
||||
postalCode?: T;
|
||||
country?: T;
|
||||
};
|
||||
status?: T;
|
||||
currency?: T;
|
||||
items?:
|
||||
| T
|
||||
| {
|
||||
description?: T;
|
||||
quantity?: T;
|
||||
unitAmount?: T;
|
||||
totalAmount?: T;
|
||||
id?: T;
|
||||
};
|
||||
subtotal?: T;
|
||||
taxAmount?: T;
|
||||
amount?: T;
|
||||
dueDate?: T;
|
||||
paidAt?: T;
|
||||
payment?: T;
|
||||
notes?: T;
|
||||
metadata?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "refunds_select".
|
||||
*/
|
||||
export interface RefundsSelect<T extends boolean = true> {
|
||||
providerId?: T;
|
||||
payment?: T;
|
||||
status?: T;
|
||||
amount?: T;
|
||||
currency?: T;
|
||||
reason?: T;
|
||||
description?: T;
|
||||
metadata?: T;
|
||||
providerData?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
*/
|
||||
export interface UsersSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
email?: T;
|
||||
resetPasswordToken?: T;
|
||||
resetPasswordExpiration?: T;
|
||||
salt?: T;
|
||||
hash?: T;
|
||||
loginAttempts?: T;
|
||||
lockUntil?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents_select".
|
||||
*/
|
||||
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
|
||||
document?: T;
|
||||
globalSlug?: T;
|
||||
user?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-preferences_select".
|
||||
*/
|
||||
export interface PayloadPreferencesSelect<T extends boolean = true> {
|
||||
user?: T;
|
||||
key?: T;
|
||||
value?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-migrations_select".
|
||||
*/
|
||||
export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
batch?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "auth".
|
||||
*/
|
||||
export interface Auth {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
|
||||
|
||||
declare module 'payload' {
|
||||
export interface GeneratedTypes extends Config {}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'url'
|
||||
import { testEmailAdapter } from './helpers/testEmailAdapter'
|
||||
import { seed } from './seed'
|
||||
import billingPlugin from '../src/plugin'
|
||||
import { mollieProvider } from '../src/providers'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
@@ -48,38 +49,16 @@ const buildConfigWithSQLite = () => {
|
||||
},
|
||||
plugins: [
|
||||
billingPlugin({
|
||||
providers: {
|
||||
test: {
|
||||
enabled: true,
|
||||
autoComplete: true,
|
||||
}
|
||||
},
|
||||
providers: [
|
||||
mollieProvider({
|
||||
apiKey: process.env.MOLLIE_KEY!
|
||||
})
|
||||
],
|
||||
collections: {
|
||||
payments: 'payments',
|
||||
invoices: 'invoices',
|
||||
refunds: 'refunds',
|
||||
},
|
||||
// // Customer relationship configuration
|
||||
// customerRelationSlug: 'customers', // Use 'customers' collection for relationship
|
||||
// // customerRelationSlug: false, // Or set to false to disable customer relationship
|
||||
// // customerRelationSlug: 'clients', // Or use a custom collection slug
|
||||
//
|
||||
// // Provide an extractor for your customer collection structure:
|
||||
// customerInfoExtractor: (customer) => ({
|
||||
// name: customer.name || '',
|
||||
// email: customer.email || '',
|
||||
// phone: customer.phone,
|
||||
// company: customer.company,
|
||||
// taxId: customer.taxId,
|
||||
// billingAddress: customer.address ? {
|
||||
// line1: customer.address.line1 || '',
|
||||
// line2: customer.address.line2,
|
||||
// city: customer.address.city || '',
|
||||
// state: customer.address.state,
|
||||
// postalCode: customer.address.postalCode || '',
|
||||
// country: customer.address.country || '',
|
||||
// } : undefined,
|
||||
// })
|
||||
}),
|
||||
],
|
||||
secret: process.env.PAYLOAD_SECRET || 'test-secret_key',
|
||||
|
||||
@@ -21,9 +21,9 @@ export const seed = async (payload: Payload) => {
|
||||
}
|
||||
|
||||
// Seed billing sample data
|
||||
await seedBillingData(payload)
|
||||
// await seedBillingData(payload)
|
||||
}
|
||||
|
||||
async function seedBillingData(payload: Payload): Promise<void> {
|
||||
payload.logger.info('Seeding billing sample data...')
|
||||
}
|
||||
// async function seedBillingData(payload: Payload): Promise<void> {
|
||||
// payload.logger.info('Seeding billing sample data...')
|
||||
// }
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@mollie/api-client": "^3.7.0",
|
||||
"@payloadcms/db-mongodb": "3.37.0",
|
||||
"@payloadcms/db-postgres": "3.37.0",
|
||||
"@payloadcms/db-sqlite": "3.37.0",
|
||||
@@ -104,11 +105,11 @@
|
||||
"vitest": "^3.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mollie/api-client": "^3.7.0",
|
||||
"payload": "^3.37.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"stripe": "^14.15.0",
|
||||
"@mollie/api-client": "^3.7.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -8,9 +8,6 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@mollie/api-client':
|
||||
specifier: ^3.7.0
|
||||
version: 3.7.0
|
||||
stripe:
|
||||
specifier: ^14.15.0
|
||||
version: 14.25.0
|
||||
@@ -24,6 +21,9 @@ importers:
|
||||
'@eslint/eslintrc':
|
||||
specifier: ^3.2.0
|
||||
version: 3.3.1
|
||||
'@mollie/api-client':
|
||||
specifier: ^3.7.0
|
||||
version: 3.7.0
|
||||
'@payloadcms/db-mongodb':
|
||||
specifier: 3.37.0
|
||||
version: 3.37.0(payload@3.37.0(graphql@16.11.0)(typescript@5.7.3))
|
||||
@@ -8875,7 +8875,7 @@ snapshots:
|
||||
eslint: 9.35.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import-x@4.4.2(eslint@9.35.0)(typescript@5.7.3))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.7.3))(eslint@9.35.0))(eslint@9.35.0)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.4.2(eslint@9.35.0)(typescript@5.7.3))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.7.3))(eslint@9.35.0))(eslint@9.35.0))(eslint@9.35.0)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.35.0)
|
||||
eslint-plugin-react: 7.37.5(eslint@9.35.0)
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.35.0)
|
||||
@@ -8909,7 +8909,7 @@ snapshots:
|
||||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.4.2(eslint@9.35.0)(typescript@5.7.3))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.7.3))(eslint@9.35.0))(eslint@9.35.0))(eslint@9.35.0)
|
||||
eslint-plugin-import-x: 4.4.2(eslint@9.35.0)(typescript@5.7.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -8960,7 +8960,7 @@ snapshots:
|
||||
- typescript
|
||||
optional: true
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import-x@4.4.2(eslint@9.35.0)(typescript@5.7.3))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0)(typescript@5.7.3))(eslint@9.35.0))(eslint@9.35.0))(eslint@9.35.0):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
|
||||
11
src/collections/hooks.ts
Normal file
11
src/collections/hooks.ts
Normal 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)
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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>[],
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
|
||||
|
||||
export { billingPlugin } from './plugin'
|
||||
export type { BillingPluginConfig, CustomerInfoExtractor } from './plugin/config'
|
||||
export type { Invoice, Payment, Refund } from './plugin/types'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { CollectionConfig } from 'payload'
|
||||
import { FieldsOverride } from '@/plugin/utils'
|
||||
import { PaymentProvider } from '@/plugin/types'
|
||||
|
||||
export const defaults = {
|
||||
paymentsCollection: 'payments',
|
||||
@@ -63,11 +64,7 @@ export interface BillingPluginConfig {
|
||||
customerInfoExtractor?: CustomerInfoExtractor // Callback to extract customer info from relationship
|
||||
customerRelationSlug?: string // Customer collection slug for relationship
|
||||
disabled?: boolean
|
||||
providers?: {
|
||||
mollie?: MollieConfig
|
||||
stripe?: StripeConfig
|
||||
test?: TestProviderConfig
|
||||
}
|
||||
providers?: PaymentProvider[]
|
||||
webhooks?: {
|
||||
basePath?: string
|
||||
cors?: boolean
|
||||
|
||||
@@ -1,62 +1,49 @@
|
||||
import type { Config } from 'payload'
|
||||
import { createInvoicesCollection, createPaymentsCollection, createRefundsCollection } from '@/collections'
|
||||
import type { BillingPluginConfig } from '@/plugin/config'
|
||||
import type { Config, Payload } from 'payload'
|
||||
import { createSingleton } from '@/plugin/singleton'
|
||||
import type { PaymentProvider } from '@/providers'
|
||||
|
||||
const singleton = createSingleton(Symbol('billingPlugin'))
|
||||
|
||||
type BillingPlugin = {
|
||||
config: BillingPluginConfig
|
||||
providerConfig: {
|
||||
[key: string]: PaymentProvider
|
||||
}
|
||||
}
|
||||
|
||||
export const useBillingPlugin = (payload: Payload) => singleton.get(payload) as BillingPlugin
|
||||
|
||||
export const billingPlugin = (pluginConfig: BillingPluginConfig = {}) => (config: Config): Config => {
|
||||
if (pluginConfig.disabled) {
|
||||
return config
|
||||
}
|
||||
|
||||
// Initialize collections
|
||||
if (!config.collections) {
|
||||
config.collections = []
|
||||
}
|
||||
|
||||
config.collections.push(
|
||||
config.collections = [
|
||||
...(config.collections || []),
|
||||
createPaymentsCollection(pluginConfig),
|
||||
createInvoicesCollection(pluginConfig),
|
||||
createRefundsCollection(pluginConfig),
|
||||
)
|
||||
]
|
||||
|
||||
// Initialize endpoints
|
||||
if (!config.endpoints) {
|
||||
config.endpoints = []
|
||||
}
|
||||
|
||||
config.endpoints?.push(
|
||||
// Webhook endpoints
|
||||
{
|
||||
handler: (_req) => {
|
||||
try {
|
||||
const provider = null
|
||||
if (!provider) {
|
||||
return Response.json({ error: 'Provider not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
// TODO: Process webhook event and update database
|
||||
|
||||
return Response.json({ received: true })
|
||||
} catch (error) {
|
||||
// TODO: Use proper logger instead of console
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[BILLING] Webhook error:', error)
|
||||
return Response.json({ error: 'Webhook processing failed' }, { status: 400 })
|
||||
}
|
||||
},
|
||||
method: 'post',
|
||||
path: '/billing/webhooks/:provider'
|
||||
},
|
||||
)
|
||||
|
||||
// Initialize providers and onInit hook
|
||||
const incomingOnInit = config.onInit
|
||||
|
||||
config.onInit = async (payload) => {
|
||||
// Execute any existing onInit functions first
|
||||
if (incomingOnInit) {
|
||||
await incomingOnInit(payload)
|
||||
}
|
||||
|
||||
singleton.set(payload, {
|
||||
config: pluginConfig,
|
||||
providerConfig: (pluginConfig.providers || []).reduce(
|
||||
(acc, val) => {
|
||||
acc[val.key] = val
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, PaymentProvider>
|
||||
)
|
||||
} satisfies BillingPlugin)
|
||||
console.log('Billing plugin initialized', singleton.get(payload))
|
||||
await Promise.all((pluginConfig.providers || []).map(p => p.onInit(payload)))
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
11
src/plugin/singleton.ts
Normal file
11
src/plugin/singleton.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const createSingleton = <T>(s?: symbol | string) => {
|
||||
const symbol = !s ? Symbol() : s
|
||||
return {
|
||||
get(container: any) {
|
||||
return container[symbol] as T
|
||||
},
|
||||
set(container: any, value: T) {
|
||||
container[symbol] = value
|
||||
},
|
||||
}
|
||||
}
|
||||
1
src/plugin/types/id.ts
Normal file
1
src/plugin/types/id.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type Id = string | number
|
||||
5
src/plugin/types/index.ts
Normal file
5
src/plugin/types/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './id'
|
||||
export * from './invoices'
|
||||
export * from './payments'
|
||||
export * from './refunds'
|
||||
export * from '../../providers/types'
|
||||
@@ -1,107 +1,6 @@
|
||||
import { Payment } from '@/plugin/types/payments'
|
||||
|
||||
export type Id = string | number
|
||||
|
||||
export interface Refund {
|
||||
id: number;
|
||||
/**
|
||||
* The refund ID from the payment provider
|
||||
*/
|
||||
providerId: string;
|
||||
payment: number | Payment;
|
||||
status: 'pending' | 'processing' | 'succeeded' | 'failed' | 'canceled';
|
||||
/**
|
||||
* Refund amount in cents
|
||||
*/
|
||||
amount: number;
|
||||
/**
|
||||
* ISO 4217 currency code (e.g., USD, EUR)
|
||||
*/
|
||||
currency: string;
|
||||
/**
|
||||
* Reason for the refund
|
||||
*/
|
||||
reason?: ('duplicate' | 'fraudulent' | 'requested_by_customer' | 'other') | null;
|
||||
/**
|
||||
* Additional details about the refund
|
||||
*/
|
||||
description?: string | null;
|
||||
/**
|
||||
* Additional refund metadata
|
||||
*/
|
||||
metadata?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
/**
|
||||
* Raw data from the payment provider
|
||||
*/
|
||||
providerData?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Payment {
|
||||
id: Id;
|
||||
provider: 'stripe' | 'mollie' | 'test';
|
||||
/**
|
||||
* The payment ID from the payment provider
|
||||
*/
|
||||
providerId: Id;
|
||||
status: 'pending' | 'processing' | 'succeeded' | 'failed' | 'canceled' | 'refunded' | 'partially_refunded';
|
||||
/**
|
||||
* Amount in cents (e.g., 2000 = $20.00)
|
||||
*/
|
||||
amount: number;
|
||||
/**
|
||||
* ISO 4217 currency code (e.g., USD, EUR)
|
||||
*/
|
||||
currency: string;
|
||||
/**
|
||||
* Payment description
|
||||
*/
|
||||
description?: string | null;
|
||||
invoice?: (Id | null) | Invoice;
|
||||
/**
|
||||
* Additional metadata for the payment
|
||||
*/
|
||||
metadata?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
/**
|
||||
* Raw data from the payment provider
|
||||
*/
|
||||
providerData?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
refunds?: (number | Refund)[] | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
import { Id } from '@/plugin/types/id'
|
||||
|
||||
export interface Invoice<TCustomer = unknown> {
|
||||
id: Id;
|
||||
@@ -216,4 +115,3 @@ export interface Invoice<TCustomer = unknown> {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
53
src/plugin/types/payments.ts
Normal file
53
src/plugin/types/payments.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Refund } from '@/plugin/types/refunds'
|
||||
import { Invoice } from '@/plugin/types/invoices'
|
||||
import { Id } from '@/plugin/types/id'
|
||||
|
||||
export interface Payment {
|
||||
id: Id;
|
||||
provider: 'stripe' | 'mollie' | 'test';
|
||||
/**
|
||||
* The payment ID from the payment provider
|
||||
*/
|
||||
providerId: Id;
|
||||
status: 'pending' | 'processing' | 'succeeded' | 'failed' | 'canceled' | 'refunded' | 'partially_refunded';
|
||||
/**
|
||||
* Amount in cents (e.g., 2000 = $20.00)
|
||||
*/
|
||||
amount: number;
|
||||
/**
|
||||
* ISO 4217 currency code (e.g., USD, EUR)
|
||||
*/
|
||||
currency: string;
|
||||
/**
|
||||
* Payment description
|
||||
*/
|
||||
description?: string | null;
|
||||
invoice?: (Id | null) | Invoice;
|
||||
/**
|
||||
* Additional metadata for the payment
|
||||
*/
|
||||
metadata?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
/**
|
||||
* Raw data from the payment provider
|
||||
*/
|
||||
providerData?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
refunds?: (number | Refund)[] | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
53
src/plugin/types/refunds.ts
Normal file
53
src/plugin/types/refunds.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Payment } from '@/plugin/types/payments'
|
||||
|
||||
export interface Refund {
|
||||
id: number;
|
||||
/**
|
||||
* The refund ID from the payment provider
|
||||
*/
|
||||
providerId: string;
|
||||
payment: number | Payment;
|
||||
status: 'pending' | 'processing' | 'succeeded' | 'failed' | 'canceled';
|
||||
/**
|
||||
* Refund amount in cents
|
||||
*/
|
||||
amount: number;
|
||||
/**
|
||||
* ISO 4217 currency code (e.g., USD, EUR)
|
||||
*/
|
||||
currency: string;
|
||||
/**
|
||||
* Reason for the refund
|
||||
*/
|
||||
reason?: ('duplicate' | 'fraudulent' | 'requested_by_customer' | 'other') | null;
|
||||
/**
|
||||
* Additional details about the refund
|
||||
*/
|
||||
description?: string | null;
|
||||
/**
|
||||
* Additional refund metadata
|
||||
*/
|
||||
metadata?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
/**
|
||||
* Raw data from the payment provider
|
||||
*/
|
||||
providerData?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { CollectionConfig, Field } from 'payload'
|
||||
import type { CollectionConfig, CollectionSlug, Field } from 'payload'
|
||||
|
||||
export type FieldsOverride = (args: { defaultFields: Field[] }) => Field[]
|
||||
|
||||
export const extractSlug = (arg: string | Partial<CollectionConfig>) => typeof arg === 'string' ? arg : arg.slug!
|
||||
export const extractSlug =
|
||||
(arg: string | Partial<CollectionConfig>) => (typeof arg === 'string' ? arg : arg.slug!) as CollectionSlug
|
||||
|
||||
2
src/providers/index.ts
Normal file
2
src/providers/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './mollie'
|
||||
export * from './types'
|
||||
40
src/providers/mollie.ts
Normal file
40
src/providers/mollie.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { Payment } from '@/plugin/types/payments'
|
||||
import type { InitPayment, PaymentProvider } from '@/plugin/types'
|
||||
import type { Payload } from 'payload'
|
||||
import { createSingleton } from '@/plugin/singleton'
|
||||
import type { createMollieClient, MollieClient } from '@mollie/api-client'
|
||||
|
||||
const symbol = Symbol('mollie')
|
||||
export type MollieProviderConfig = Parameters<typeof createMollieClient>[0]
|
||||
|
||||
export const mollieProvider = (config: MollieProviderConfig) => {
|
||||
const singleton = createSingleton<MollieClient>(symbol)
|
||||
return {
|
||||
key: 'mollie',
|
||||
onInit: async (payload: Payload) => {
|
||||
const createMollieClient = (await import('@mollie/api-client')).default
|
||||
const mollieClient = createMollieClient(config)
|
||||
singleton.set(payload, mollieClient)
|
||||
},
|
||||
initPayment: async (payload, payment) => {
|
||||
if (!payment.amount) {
|
||||
throw new Error('Amount is required')
|
||||
}
|
||||
if (!payment.currency) {
|
||||
throw new Error('Currency is required')
|
||||
}
|
||||
const molliePayment = await singleton.get(payload).payments.create({
|
||||
amount: {
|
||||
value: (payment.amount / 100).toFixed(2),
|
||||
currency: payment.currency
|
||||
},
|
||||
description: payment.description || '',
|
||||
redirectUrl: 'https://localhost:3000/payment/success',
|
||||
webhookUrl: 'https://localhost:3000',
|
||||
});
|
||||
payment.providerId = molliePayment.id
|
||||
payment.providerData = molliePayment.toPlainObject()
|
||||
return payment
|
||||
},
|
||||
} satisfies PaymentProvider
|
||||
}
|
||||
10
src/providers/types.ts
Normal file
10
src/providers/types.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { Payment } from '@/plugin/types/payments'
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
export type InitPayment = (payload: Payload, payment: Partial<Payment>) => Promise<Partial<Payment>>
|
||||
|
||||
export type PaymentProvider = {
|
||||
key: string
|
||||
onInit: (payload: Payload) => Promise<void> | void
|
||||
initPayment: InitPayment
|
||||
}
|
||||
Reference in New Issue
Block a user