mirror of
https://github.com/xtr-dev/payload-billing.git
synced 2025-12-08 00:33:23 +00:00
fix: add fallback for databases without transaction support
Some database adapters don't support transactions, causing payment updates to fail completely. This change adds graceful fallback to direct updates when transactions are unavailable. Changes: - Try to use transactions if supported - Fall back to direct update if beginTransaction() fails or returns null - Add debug logging to track which path is used - Maintain backward compatibility with transaction-supporting databases This fixes the "Failed to begin transaction" error in production. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xtr-dev/payload-billing",
|
||||
"version": "0.1.27",
|
||||
"version": "0.1.28",
|
||||
"description": "PayloadCMS plugin for billing and payment provider integrations with tracking and local testing",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -60,6 +60,7 @@ export async function updatePaymentStatus(
|
||||
pluginConfig: BillingPluginConfig
|
||||
): Promise<boolean> {
|
||||
const paymentsCollection = extractSlug(pluginConfig.collections?.payments, defaults.paymentsCollection)
|
||||
const logger = createContextLogger(payload, 'Payment Update')
|
||||
|
||||
try {
|
||||
// First, fetch the current payment to get the current version
|
||||
@@ -69,41 +70,65 @@ export async function updatePaymentStatus(
|
||||
}) as Payment
|
||||
|
||||
if (!currentPayment) {
|
||||
const logger = createContextLogger(payload, 'Payment Update')
|
||||
logger.error(`Payment ${paymentId} not found`)
|
||||
return false
|
||||
}
|
||||
|
||||
const currentVersion = currentPayment.version || 1
|
||||
|
||||
// Attempt to update with optimistic locking
|
||||
// We'll use a transaction to ensure atomicity
|
||||
const transactionID = await payload.db.beginTransaction()
|
||||
|
||||
if (!transactionID) {
|
||||
const logger = createContextLogger(payload, 'Payment Update')
|
||||
logger.error('Failed to begin transaction')
|
||||
return false
|
||||
// Try to use transactions if supported by the database adapter
|
||||
let transactionID: string | number | null = null
|
||||
try {
|
||||
transactionID = await payload.db.beginTransaction()
|
||||
} catch (error) {
|
||||
// Transaction support may not be available in all database adapters
|
||||
logger.debug('Transactions not supported, falling back to direct update')
|
||||
}
|
||||
|
||||
try {
|
||||
// Re-fetch within transaction to ensure consistency
|
||||
const paymentInTransaction = await payload.findByID({
|
||||
collection: paymentsCollection,
|
||||
id: toPayloadId(paymentId),
|
||||
req: { transactionID }
|
||||
}) as Payment
|
||||
if (transactionID) {
|
||||
// Use transactional update with optimistic locking
|
||||
try {
|
||||
// Re-fetch within transaction to ensure consistency
|
||||
const paymentInTransaction = await payload.findByID({
|
||||
collection: paymentsCollection,
|
||||
id: toPayloadId(paymentId),
|
||||
req: { transactionID }
|
||||
}) as Payment
|
||||
|
||||
// Check if version still matches
|
||||
if ((paymentInTransaction.version || 1) !== currentVersion) {
|
||||
// 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})`)
|
||||
// Check if version still matches
|
||||
if ((paymentInTransaction.version || 1) !== currentVersion) {
|
||||
// Version conflict detected - payment was modified by another process
|
||||
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)
|
||||
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({
|
||||
collection: paymentsCollection,
|
||||
id: toPayloadId(paymentId),
|
||||
@@ -114,18 +139,12 @@ export async function updatePaymentStatus(
|
||||
webhookProcessedAt: new Date().toISOString()
|
||||
},
|
||||
version: currentVersion + 1
|
||||
},
|
||||
req: { transactionID }
|
||||
}
|
||||
})
|
||||
|
||||
await payload.db.commitTransaction(transactionID)
|
||||
return true
|
||||
} catch (error) {
|
||||
await payload.db.rollbackTransaction(transactionID)
|
||||
throw error
|
||||
}
|
||||
} catch (error) {
|
||||
const logger = createContextLogger(payload, 'Payment Update')
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
const errorStack = error instanceof Error ? error.stack : undefined
|
||||
logger.error(`Failed to update payment ${paymentId}: ${errorMessage}`)
|
||||
|
||||
Reference in New Issue
Block a user