mirror of
https://github.com/xtr-dev/payload-billing.git
synced 2025-12-10 10:53:23 +00:00
Compare commits
3 Commits
v0.1.25
...
f2ab50214b
| Author | SHA1 | Date | |
|---|---|---|---|
| f2ab50214b | |||
| 20030b435c | |||
| 1eb9d282b3 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@xtr-dev/payload-billing",
|
"name": "@xtr-dev/payload-billing",
|
||||||
"version": "0.1.25",
|
"version": "0.1.28",
|
||||||
"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",
|
||||||
|
|||||||
@@ -17,13 +17,6 @@ import { createContextLogger } from '../utils/logger'
|
|||||||
const symbol = Symbol.for('@xtr-dev/payload-billing/mollie')
|
const symbol = Symbol.for('@xtr-dev/payload-billing/mollie')
|
||||||
export type MollieProviderConfig = Parameters<typeof createMollieClient>[0]
|
export type MollieProviderConfig = Parameters<typeof createMollieClient>[0]
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine the Mollie mode based on API key prefix
|
|
||||||
*/
|
|
||||||
function getMollieMode(apiKey: string): 'test' | 'live' {
|
|
||||||
return apiKey.startsWith('test_') ? 'test' : 'live'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type-safe mapping of Mollie payment status to internal status
|
* Type-safe mapping of Mollie payment status to internal status
|
||||||
*/
|
*/
|
||||||
@@ -168,9 +161,6 @@ export const mollieProvider = (mollieConfig: MollieProviderConfig & {
|
|||||||
validateProductionUrl(redirectUrl, 'Redirect')
|
validateProductionUrl(redirectUrl, 'Redirect')
|
||||||
validateProductionUrl(webhookUrl, 'Webhook')
|
validateProductionUrl(webhookUrl, 'Webhook')
|
||||||
|
|
||||||
// Determine mode from API key (test_ or live_)
|
|
||||||
const mode = getMollieMode(mollieConfig.apiKey)
|
|
||||||
|
|
||||||
const molliePayment = await singleton.get(payload).payments.create({
|
const molliePayment = await singleton.get(payload).payments.create({
|
||||||
amount: {
|
amount: {
|
||||||
value: formatAmountForProvider(payment.amount, payment.currency),
|
value: formatAmountForProvider(payment.amount, payment.currency),
|
||||||
@@ -179,8 +169,7 @@ export const mollieProvider = (mollieConfig: MollieProviderConfig & {
|
|||||||
description: payment.description || '',
|
description: payment.description || '',
|
||||||
redirectUrl,
|
redirectUrl,
|
||||||
webhookUrl,
|
webhookUrl,
|
||||||
mode,
|
});
|
||||||
} as any);
|
|
||||||
payment.providerId = molliePayment.id
|
payment.providerId = molliePayment.id
|
||||||
// Use toPlainObject if available, otherwise spread the object (for compatibility with different Mollie client versions)
|
// Use toPlainObject if available, otherwise spread the object (for compatibility with different Mollie client versions)
|
||||||
payment.providerData = typeof molliePayment.toPlainObject === 'function'
|
payment.providerData = typeof molliePayment.toPlainObject === 'function'
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export async function updatePaymentStatus(
|
|||||||
pluginConfig: BillingPluginConfig
|
pluginConfig: BillingPluginConfig
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const paymentsCollection = extractSlug(pluginConfig.collections?.payments, defaults.paymentsCollection)
|
const paymentsCollection = extractSlug(pluginConfig.collections?.payments, defaults.paymentsCollection)
|
||||||
|
const logger = createContextLogger(payload, 'Payment Update')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// First, fetch the current payment to get the current version
|
// First, fetch the current payment to get the current version
|
||||||
@@ -69,41 +70,65 @@ export async function updatePaymentStatus(
|
|||||||
}) as Payment
|
}) as Payment
|
||||||
|
|
||||||
if (!currentPayment) {
|
if (!currentPayment) {
|
||||||
const logger = createContextLogger(payload, 'Payment Update')
|
|
||||||
logger.error(`Payment ${paymentId} not found`)
|
logger.error(`Payment ${paymentId} not found`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentVersion = currentPayment.version || 1
|
const currentVersion = currentPayment.version || 1
|
||||||
|
|
||||||
// Attempt to update with optimistic locking
|
// Try to use transactions if supported by the database adapter
|
||||||
// We'll use a transaction to ensure atomicity
|
let transactionID: string | number | null = null
|
||||||
const transactionID = await payload.db.beginTransaction()
|
try {
|
||||||
|
transactionID = await payload.db.beginTransaction()
|
||||||
if (!transactionID) {
|
} catch (error) {
|
||||||
const logger = createContextLogger(payload, 'Payment Update')
|
// Transaction support may not be available in all database adapters
|
||||||
logger.error('Failed to begin transaction')
|
logger.debug('Transactions not supported, falling back to direct update')
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (transactionID) {
|
||||||
// Re-fetch within transaction to ensure consistency
|
// Use transactional update with optimistic locking
|
||||||
const paymentInTransaction = await payload.findByID({
|
try {
|
||||||
collection: paymentsCollection,
|
// Re-fetch within transaction to ensure consistency
|
||||||
id: toPayloadId(paymentId),
|
const paymentInTransaction = await payload.findByID({
|
||||||
req: { transactionID }
|
collection: paymentsCollection,
|
||||||
}) as Payment
|
id: toPayloadId(paymentId),
|
||||||
|
req: { transactionID }
|
||||||
|
}) as Payment
|
||||||
|
|
||||||
// Check if version still matches
|
// Check if version still matches
|
||||||
if ((paymentInTransaction.version || 1) !== currentVersion) {
|
if ((paymentInTransaction.version || 1) !== currentVersion) {
|
||||||
// Version conflict detected - payment was modified by another process
|
// Version conflict detected - payment was modified by another process
|
||||||
const logger = createContextLogger(payload, 'Payment Update')
|
logger.warn(`Version conflict for payment ${paymentId} (expected version: ${currentVersion}, got: ${paymentInTransaction.version})`)
|
||||||
logger.warn(`Version conflict for payment ${paymentId} (expected version: ${currentVersion}, got: ${paymentInTransaction.version})`)
|
await payload.db.rollbackTransaction(transactionID)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update with new version
|
||||||
|
await payload.update({
|
||||||
|
collection: paymentsCollection,
|
||||||
|
id: toPayloadId(paymentId),
|
||||||
|
data: {
|
||||||
|
status,
|
||||||
|
providerData: {
|
||||||
|
...providerData,
|
||||||
|
webhookProcessedAt: new Date().toISOString()
|
||||||
|
},
|
||||||
|
version: currentVersion + 1
|
||||||
|
},
|
||||||
|
req: { transactionID }
|
||||||
|
})
|
||||||
|
|
||||||
|
await payload.db.commitTransaction(transactionID)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
await payload.db.rollbackTransaction(transactionID)
|
await payload.db.rollbackTransaction(transactionID)
|
||||||
return false
|
throw error
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback: Direct update without transaction support
|
||||||
|
// This is less safe but allows payment updates on databases without transaction support
|
||||||
|
logger.debug('Using direct update without transaction')
|
||||||
|
|
||||||
// Update with new version
|
|
||||||
await payload.update({
|
await payload.update({
|
||||||
collection: paymentsCollection,
|
collection: paymentsCollection,
|
||||||
id: toPayloadId(paymentId),
|
id: toPayloadId(paymentId),
|
||||||
@@ -114,18 +139,12 @@ export async function updatePaymentStatus(
|
|||||||
webhookProcessedAt: new Date().toISOString()
|
webhookProcessedAt: new Date().toISOString()
|
||||||
},
|
},
|
||||||
version: currentVersion + 1
|
version: currentVersion + 1
|
||||||
},
|
}
|
||||||
req: { transactionID }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await payload.db.commitTransaction(transactionID)
|
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
|
||||||
await payload.db.rollbackTransaction(transactionID)
|
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const logger = createContextLogger(payload, 'Payment Update')
|
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||||
const errorStack = error instanceof Error ? error.stack : undefined
|
const errorStack = error instanceof Error ? error.stack : undefined
|
||||||
logger.error(`Failed to update payment ${paymentId}: ${errorMessage}`)
|
logger.error(`Failed to update payment ${paymentId}: ${errorMessage}`)
|
||||||
|
|||||||
Reference in New Issue
Block a user