4 Commits

Author SHA1 Message Date
4fde492e0f feat: add checkoutUrl field to payment collection
- Add checkoutUrl field to Payment type and collection
- Mollie provider now sets checkoutUrl from _links.checkout.href
- Test provider sets checkoutUrl to interactive payment UI
- Stripe provider doesn't use checkoutUrl (uses client_secret instead)
- Update README with checkoutUrl examples and clarifications
- Make it easier to redirect users to payment pages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 23:01:43 +01:00
a37757ffa1 fix: add better error handling for uninitialized billing plugin
- Fix TypeError when accessing providerConfig on undefined billing plugin
- Add proper type safety: useBillingPlugin now returns BillingPlugin | undefined
- Add clear error message when plugin hasn't been initialized
- Update README quickstart with concise provider response examples

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 22:31:51 +01:00
1867bb2f96 docs: restructure and update README for clarity
- Revise features list for precision and enhanced readability
- Expand and reorganize table of contents for better navigation
- Add detailed configurations and examples for Stripe, Mollie, and test providers
- Include new sections like customer management, payment flows, and webhook setup
- Refine descriptions of automatic behaviors and status synchronization
- Fix minor grammar inconsistencies and improve overall formatting

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 21:43:47 +01:00
89578aeba2 fix: replace path aliases with relative imports to fix published package
- Replace @/ path aliases with relative imports in invoices collection
- This fixes the 'Cannot find package @/plugin' error in published package
- Path aliases don't resolve correctly in the transpiled dist folder

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 16:35:21 +01:00
10 changed files with 1145 additions and 316 deletions

1367
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@xtr-dev/payload-billing",
"version": "0.1.13",
"version": "0.1.16",
"description": "PayloadCMS plugin for billing and payment provider integrations with tracking and local testing",
"license": "MIT",
"type": "module",
@@ -81,6 +81,7 @@
"@playwright/test": "^1.52.0",
"@swc-node/register": "1.10.9",
"@swc/cli": "0.6.0",
"@swc/plugin-transform-imports": "^11.0.0",
"@tailwindcss/postcss": "^4.1.17",
"@types/node": "^22.5.4",
"@types/react": "19.1.8",
@@ -105,6 +106,7 @@
"sort-package-json": "^2.10.0",
"stripe": "^18.5.0",
"tailwindcss": "^4.1.17",
"tsc-alias": "^1.8.16",
"typescript": "5.7.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.1.2"

54
pnpm-lock.yaml generated
View File

@@ -51,6 +51,9 @@ importers:
'@swc/cli':
specifier: 0.6.0
version: 0.6.0(@swc/core@1.13.5)
'@swc/plugin-transform-imports':
specifier: ^11.0.0
version: 11.0.0
'@tailwindcss/postcss':
specifier: ^4.1.17
version: 4.1.17
@@ -123,6 +126,9 @@ importers:
tailwindcss:
specifier: ^4.1.17
version: 4.1.17
tsc-alias:
specifier: ^1.8.16
version: 1.8.16
typescript:
specifier: 5.7.3
version: 5.7.3
@@ -1990,6 +1996,9 @@ packages:
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@swc/plugin-transform-imports@11.0.0':
resolution: {integrity: sha512-vYxPeZd8GpsdO4RWu9h1sYUVj/3yMwdvZaHRTUjN+AcUKcTr+OMl4hK2iNk6n6UzMlpURcLvibfl1HkxZkCCLQ==}
'@swc/types@0.1.25':
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
@@ -2820,6 +2829,10 @@ packages:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
commander@9.5.0:
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
engines: {node: ^12.20.0 || >=14}
comment-parser@1.4.1:
resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
engines: {node: '>= 12.0.0'}
@@ -4515,6 +4528,10 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
mylas@2.1.14:
resolution: {integrity: sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==}
engines: {node: '>=16.0.0'}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -4838,6 +4855,10 @@ packages:
engines: {node: '>=18'}
hasBin: true
plimit-lit@1.6.1:
resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==}
engines: {node: '>=12'}
pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
@@ -4944,6 +4965,10 @@ packages:
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
queue-lit@1.5.2:
resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==}
engines: {node: '>=12'}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@@ -5535,6 +5560,11 @@ packages:
ts-pattern@5.8.0:
resolution: {integrity: sha512-kIjN2qmWiHnhgr5DAkAafF9fwb0T5OhMVSWrm8XEdTFnX6+wfXwYOFjeF86UZ54vduqiR7BfqScFmXSzSaH8oA==}
tsc-alias@1.8.16:
resolution: {integrity: sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==}
engines: {node: '>=16.20.2'}
hasBin: true
tsconfck@3.1.6:
resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==}
engines: {node: ^18 || >=20}
@@ -7810,6 +7840,10 @@ snapshots:
dependencies:
tslib: 2.8.1
'@swc/plugin-transform-imports@11.0.0':
dependencies:
'@swc/counter': 0.1.3
'@swc/types@0.1.25':
dependencies:
'@swc/counter': 0.1.3
@@ -8774,6 +8808,8 @@ snapshots:
commander@8.3.0: {}
commander@9.5.0: {}
comment-parser@1.4.1: {}
commondir@1.0.1: {}
@@ -10815,6 +10851,8 @@ snapshots:
ms@2.1.3: {}
mylas@2.1.14: {}
nanoid@3.3.11: {}
napi-postinstall@0.3.3: {}
@@ -11194,6 +11232,10 @@ snapshots:
optionalDependencies:
fsevents: 2.3.2
plimit-lit@1.6.1:
dependencies:
queue-lit: 1.5.2
pluralize@8.0.0: {}
possible-typed-array-names@1.1.0: {}
@@ -11274,6 +11316,8 @@ snapshots:
quansync@0.2.11: {}
queue-lit@1.5.2: {}
queue-microtask@1.2.3: {}
quick-format-unescaped@4.0.4: {}
@@ -11976,6 +12020,16 @@ snapshots:
ts-pattern@5.8.0: {}
tsc-alias@1.8.16:
dependencies:
chokidar: 3.6.0
commander: 9.5.0
get-tsconfig: 4.10.1
globby: 11.1.0
mylas: 2.1.14
normalize-path: 3.0.0
plimit-lit: 1.6.1
tsconfck@3.1.6(typescript@5.7.3):
optionalDependencies:
typescript: 5.7.3

View File

@@ -4,6 +4,13 @@ import { useBillingPlugin } from '../plugin/index'
export const initProviderPayment = async (payload: Payload, payment: Partial<Payment>): Promise<Partial<Payment>> => {
const billing = useBillingPlugin(payload)
if (!billing) {
throw new Error(
'Billing plugin not initialized. Make sure the billingPlugin is properly configured in your Payload config and that Payload has finished initializing.'
)
}
if (!payment.provider || !billing.providerConfig[payment.provider]) {
throw new Error(`Provider ${payment.provider} not found.`)
}

View File

@@ -7,11 +7,11 @@ import type {
CollectionSlug,
Field,
} from 'payload'
import type { BillingPluginConfig} from '@/plugin/config';
import { defaults } from '@/plugin/config'
import { extractSlug } from '@/plugin/utils'
import { createContextLogger } from '@/utils/logger'
import type { Invoice } from '@/plugin/types'
import type { BillingPluginConfig} from '../plugin/config.js';
import { defaults } from '../plugin/config.js'
import { extractSlug } from '../plugin/utils.js'
import { createContextLogger } from '../utils/logger.js'
import type { Invoice } from '../plugin/types/index.js'
export function createInvoicesCollection(pluginConfig: BillingPluginConfig): CollectionConfig {
const {customerRelationSlug, customerInfoExtractor} = pluginConfig

View File

@@ -78,6 +78,14 @@ export function createPaymentsCollection(pluginConfig: BillingPluginConfig): Col
description: 'Payment description',
},
},
{
name: 'checkoutUrl',
type: 'text',
admin: {
description: 'Checkout URL where user can complete payment (if applicable)',
readOnly: true,
},
},
{
name: 'invoice',
type: 'relationship',

View File

@@ -13,7 +13,7 @@ type BillingPlugin = {
}
}
export const useBillingPlugin = (payload: Payload) => singleton.get(payload) as BillingPlugin
export const useBillingPlugin = (payload: Payload) => singleton.get(payload) as BillingPlugin | undefined
export const billingPlugin = (pluginConfig: BillingPluginConfig = {}) => (config: Config): Config => {
if (pluginConfig.disabled) {

View File

@@ -22,6 +22,10 @@ export interface Payment {
* Payment description
*/
description?: string | null;
/**
* Checkout URL where user can complete payment (if applicable)
*/
checkoutUrl?: string | null;
invoice?: (Id | null) | Invoice;
/**
* Additional metadata for the payment

View File

@@ -155,6 +155,7 @@ export const mollieProvider = (mollieConfig: MollieProviderConfig & {
});
payment.providerId = molliePayment.id
payment.providerData = molliePayment.toPlainObject()
payment.checkoutUrl = molliePayment._links?.checkout?.href || null
return payment
},
} satisfies PaymentProvider

View File

@@ -492,6 +492,7 @@ export const testProvider = (testConfig: TestProviderConfig) => {
// Set provider ID and data
payment.providerId = testPaymentId
const paymentUrl = `${baseUrl}/api/payload-billing/test/payment/${testPaymentId}`
const providerData: ProviderData = {
raw: {
id: testPaymentId,
@@ -500,7 +501,7 @@ export const testProvider = (testConfig: TestProviderConfig) => {
description: payment.description,
status: 'pending',
testMode: true,
paymentUrl: `${baseUrl}/api/payload-billing/test/payment/${testPaymentId}`,
paymentUrl,
scenarios: scenarios.map(s => ({ id: s.id, name: s.name, description: s.description })),
methods: Object.entries(PAYMENT_METHODS).map(([key, value]) => ({
id: key,
@@ -512,6 +513,7 @@ export const testProvider = (testConfig: TestProviderConfig) => {
provider: 'test'
}
payment.providerData = providerData
payment.checkoutUrl = paymentUrl
return payment
},