Completely remove all race condition and optimistic locking logic

- Remove webhook context tracking system (context.ts file)
- Eliminate updatePaymentFromWebhook wrapper function
- Simplify payment providers to use updatePaymentStatus directly
- Remove all version-based optimistic locking references
- Clean up webhook context parameters and metadata
- Streamline codebase assuming providers don't send duplicate webhooks

The payment system now operates with simple, direct updates without any
race condition handling, as payment providers typically don't send
duplicate webhook requests for the same event.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-18 18:51:23 +02:00
parent b6c27ff3a3
commit a25111444a
4 changed files with 9 additions and 89 deletions

View File

@@ -1,50 +0,0 @@
/**
* Request context utilities for tracking webhook vs manual operations
*/
// Symbol for storing webhook context in the request object
const WEBHOOK_CONTEXT_SYMBOL = Symbol('billingWebhookContext')
export interface WebhookContext {
isWebhookUpdate: boolean
provider?: string
webhookType?: string
timestamp: string
metadata?: Record<string, any>
}
/**
* Mark a request as coming from a webhook
*/
export function markRequestAsWebhook(
req: any,
provider: string,
webhookType: string = 'payment_update',
metadata?: Record<string, any>
): void {
const context: WebhookContext = {
isWebhookUpdate: true,
provider,
webhookType,
timestamp: new Date().toISOString(),
metadata
}
// Store context in request object using symbol to avoid conflicts
req[WEBHOOK_CONTEXT_SYMBOL] = context
}
/**
* Check if a request is from a webhook
*/
export function isWebhookRequest(req: any): boolean {
const context = req[WEBHOOK_CONTEXT_SYMBOL] as WebhookContext | undefined
return context?.isWebhookUpdate === true
}
/**
* Get webhook context from request
*/
export function getWebhookContext(req: any): WebhookContext | null {
return req[WEBHOOK_CONTEXT_SYMBOL] as WebhookContext || null
}

View File

@@ -6,7 +6,7 @@ import type { createMollieClient, MollieClient } from '@mollie/api-client'
import { import {
webhookResponses, webhookResponses,
findPaymentByProviderId, findPaymentByProviderId,
updatePaymentFromWebhook, updatePaymentStatus,
updateInvoiceOnPaymentSuccess, updateInvoiceOnPaymentSuccess,
handleWebhookError, handleWebhookError,
validateProductionUrl validateProductionUrl
@@ -83,15 +83,13 @@ export const mollieProvider = (mollieConfig: MollieProviderConfig & {
// Map Mollie status to our status using proper type-safe mapping // Map Mollie status to our status using proper type-safe mapping
const status = mapMollieStatusToPaymentStatus(molliePayment.status) const status = mapMollieStatusToPaymentStatus(molliePayment.status)
// Update the payment status and provider data with webhook context // Update the payment status and provider data
const updateSuccess = await updatePaymentFromWebhook( const updateSuccess = await updatePaymentStatus(
payload, payload,
payment.id, payment.id,
status, status,
molliePayment.toPlainObject(), molliePayment.toPlainObject(),
pluginConfig, pluginConfig
'mollie',
`payment.${molliePayment.status}`
) )
// If payment is successful and update succeeded, update the invoice // If payment is successful and update succeeded, update the invoice

View File

@@ -6,7 +6,7 @@ import type Stripe from 'stripe'
import { import {
webhookResponses, webhookResponses,
findPaymentByProviderId, findPaymentByProviderId,
updatePaymentFromWebhook, updatePaymentStatus,
updateInvoiceOnPaymentSuccess, updateInvoiceOnPaymentSuccess,
handleWebhookError, handleWebhookError,
logWebhookEvent logWebhookEvent
@@ -117,14 +117,12 @@ export const stripeProvider = (stripeConfig: StripeProviderConfig) => {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
provider: 'stripe' provider: 'stripe'
} }
const updateSuccess = await updatePaymentFromWebhook( const updateSuccess = await updatePaymentStatus(
payload, payload,
payment.id, payment.id,
status, status,
providerData, providerData,
pluginConfig, pluginConfig
'stripe',
event.type
) )
// If payment is successful and update succeeded, update the invoice // If payment is successful and update succeeded, update the invoice
@@ -165,14 +163,12 @@ export const stripeProvider = (stripeConfig: StripeProviderConfig) => {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
provider: 'stripe' provider: 'stripe'
} }
const updateSuccess = await updatePaymentFromWebhook( const updateSuccess = await updatePaymentStatus(
payload, payload,
payment.id, payment.id,
isFullyRefunded ? 'refunded' : 'partially_refunded', isFullyRefunded ? 'refunded' : 'partially_refunded',
providerData, providerData,
pluginConfig, pluginConfig
'stripe',
event.type
) )
if (!updateSuccess) { if (!updateSuccess) {

View File

@@ -4,7 +4,6 @@ import type { BillingPluginConfig } from '@/plugin/config'
import type { ProviderData } from './types' import type { ProviderData } from './types'
import { defaults } from '@/plugin/config' import { defaults } from '@/plugin/config'
import { extractSlug, toPayloadId } from '@/plugin/utils' import { extractSlug, toPayloadId } from '@/plugin/utils'
import { markRequestAsWebhook } from './context'
/** /**
* Common webhook response utilities * Common webhook response utilities
@@ -44,29 +43,6 @@ export async function findPaymentByProviderId(
return payments.docs.length > 0 ? payments.docs[0] as Payment : null return payments.docs.length > 0 ? payments.docs[0] as Payment : null
} }
/**
* Update payment status from webhook with proper context tracking
*/
export async function updatePaymentFromWebhook(
payload: Payload,
paymentId: string | number,
status: Payment['status'],
providerData: ProviderData<any>,
pluginConfig: BillingPluginConfig,
provider: string,
eventType?: string
): Promise<boolean> {
// Mark the request context as webhook before updating with metadata
markRequestAsWebhook((payload as any).req, provider, 'payment_status_update', {
paymentId: paymentId.toString(),
newStatus: status,
eventType,
timestamp: new Date().toISOString()
})
return updatePaymentStatus(payload, paymentId, status, providerData, pluginConfig)
}
/** /**
* Update payment status and provider data * Update payment status and provider data
*/ */