1 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
6 changed files with 24 additions and 6 deletions

View File

@@ -111,9 +111,9 @@ const payment = await payload.create({
**What you get back:** **What you get back:**
- **Stripe**: `providerId` = PaymentIntent ID, `providerData.raw.client_secret` for Stripe.js - **Stripe**: `providerId` = PaymentIntent ID, use `providerData.raw.client_secret` with Stripe.js on frontend
- **Mollie**: `providerId` = Transaction ID, `providerData.raw._links.checkout.href` for redirect URL - **Mollie**: `providerId` = Transaction ID, redirect user to `checkoutUrl` to complete payment
- **Test**: `providerId` = Test payment ID, `providerData.raw.paymentUrl` for interactive test UI - **Test**: `providerId` = Test payment ID, navigate to `checkoutUrl` for interactive test UI
## Payment Providers ## Payment Providers
@@ -407,6 +407,7 @@ Tracks payment transactions with provider integration.
amount: number // Amount in cents amount: number // Amount in cents
currency: string // ISO 4217 currency code currency: string // ISO 4217 currency code
description?: string description?: string
checkoutUrl?: string // Checkout URL (if applicable)
invoice?: Invoice | string // Linked invoice invoice?: Invoice | string // Linked invoice
metadata?: Record<string, any> // Custom metadata metadata?: Record<string, any> // Custom metadata
providerData?: ProviderData // Raw provider response (read-only) providerData?: ProviderData // Raw provider response (read-only)
@@ -639,12 +640,14 @@ const payment = await payload.create({
} }
}) })
// Get client secret for Stripe.js // Get client secret for Stripe.js (Stripe doesn't use checkoutUrl)
const clientSecret = payment.providerData.raw.client_secret const clientSecret = payment.providerData.raw.client_secret
// Frontend: Confirm payment with Stripe.js // Frontend: Confirm payment with Stripe.js
// const stripe = Stripe('pk_...') // const stripe = Stripe('pk_...')
// await stripe.confirmCardPayment(clientSecret, { ... }) // await stripe.confirmCardPayment(clientSecret, { ... })
// For Mollie/Test: redirect to payment.checkoutUrl instead
``` ```
### Creating an Invoice with Line Items ### Creating an Invoice with Line Items

View File

@@ -1,6 +1,6 @@
{ {
"name": "@xtr-dev/payload-billing", "name": "@xtr-dev/payload-billing",
"version": "0.1.15", "version": "0.1.16",
"description": "PayloadCMS plugin for billing and payment provider integrations with tracking and local testing", "description": "PayloadCMS plugin for billing and payment provider integrations with tracking and local testing",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",

View File

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

View File

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

View File

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

View File

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