|
|
|
@@ -16,7 +16,7 @@ export const webhookResponses = {
|
|
|
|
// Log error internally but don't expose details
|
|
|
|
// Log error internally but don't expose details
|
|
|
|
if (payload) {
|
|
|
|
if (payload) {
|
|
|
|
const logger = createContextLogger(payload, 'Webhook')
|
|
|
|
const logger = createContextLogger(payload, 'Webhook')
|
|
|
|
logger.error('Error:', message)
|
|
|
|
logger.error(`Error: ${message}`)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
console.error('[Webhook] Error:', message)
|
|
|
|
console.error('[Webhook] Error:', message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -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,23 +70,23 @@ 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
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (transactionID) {
|
|
|
|
|
|
|
|
// Use transactional update with optimistic locking
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
// Re-fetch within transaction to ensure consistency
|
|
|
|
// Re-fetch within transaction to ensure consistency
|
|
|
|
const paymentInTransaction = await payload.findByID({
|
|
|
|
const paymentInTransaction = await payload.findByID({
|
|
|
|
@@ -97,7 +98,6 @@ export async function updatePaymentStatus(
|
|
|
|
// 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)
|
|
|
|
await payload.db.rollbackTransaction(transactionID)
|
|
|
|
return false
|
|
|
|
return false
|
|
|
|
@@ -124,9 +124,33 @@ export async function updatePaymentStatus(
|
|
|
|
await payload.db.rollbackTransaction(transactionID)
|
|
|
|
await payload.db.rollbackTransaction(transactionID)
|
|
|
|
throw error
|
|
|
|
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')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await payload.update({
|
|
|
|
|
|
|
|
collection: paymentsCollection,
|
|
|
|
|
|
|
|
id: toPayloadId(paymentId),
|
|
|
|
|
|
|
|
data: {
|
|
|
|
|
|
|
|
status,
|
|
|
|
|
|
|
|
providerData: {
|
|
|
|
|
|
|
|
...providerData,
|
|
|
|
|
|
|
|
webhookProcessedAt: new Date().toISOString()
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
version: currentVersion + 1
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
const logger = createContextLogger(payload, 'Payment Update')
|
|
|
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
|
|
logger.error(`Failed to update payment ${paymentId}:`, error)
|
|
|
|
const errorStack = error instanceof Error ? error.stack : undefined
|
|
|
|
|
|
|
|
logger.error(`Failed to update payment ${paymentId}: ${errorMessage}`)
|
|
|
|
|
|
|
|
if (errorStack) {
|
|
|
|
|
|
|
|
logger.error(`Stack trace: ${errorStack}`)
|
|
|
|
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -165,15 +189,22 @@ export function handleWebhookError(
|
|
|
|
context?: string,
|
|
|
|
context?: string,
|
|
|
|
payload?: Payload
|
|
|
|
payload?: Payload
|
|
|
|
): Response {
|
|
|
|
): Response {
|
|
|
|
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
|
|
|
|
|
|
const stack = error instanceof Error ? error.stack : undefined
|
|
|
|
const fullContext = context ? `${provider} Webhook - ${context}` : `${provider} Webhook`
|
|
|
|
const fullContext = context ? `${provider} Webhook - ${context}` : `${provider} Webhook`
|
|
|
|
|
|
|
|
|
|
|
|
// Log detailed error internally for debugging
|
|
|
|
// Log detailed error internally for debugging
|
|
|
|
if (payload) {
|
|
|
|
if (payload) {
|
|
|
|
const logger = createContextLogger(payload, fullContext)
|
|
|
|
const logger = createContextLogger(payload, fullContext)
|
|
|
|
logger.error('Error:', error)
|
|
|
|
logger.error(`Error: ${message}`)
|
|
|
|
|
|
|
|
if (stack) {
|
|
|
|
|
|
|
|
logger.error(`Stack trace: ${stack}`)
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
console.error(`[${fullContext}] Error:`, error)
|
|
|
|
console.error(`[${fullContext}] Error: ${message}`)
|
|
|
|
|
|
|
|
if (stack) {
|
|
|
|
|
|
|
|
console.error(`[${fullContext}] Stack trace:`, stack)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Return generic response to avoid information disclosure
|
|
|
|
// Return generic response to avoid information disclosure
|
|
|
|
|