diff --git a/src/collections/payments.ts b/src/collections/payments.ts index 797e140..64cea80 100644 --- a/src/collections/payments.ts +++ b/src/collections/payments.ts @@ -1,7 +1,7 @@ import type { AccessArgs, CollectionBeforeChangeHook, CollectionConfig, CollectionSlug, Field } from 'payload' import type { BillingPluginConfig} from '@/plugin/config'; import { defaults } from '@/plugin/config' -import { extractSlug } from '@/plugin/utils' +import { extractSlug, toPayloadId } from '@/plugin/utils' import { Payment } from '@/plugin/types/payments' import { initProviderPayment } from '@/collections/hooks' @@ -156,13 +156,29 @@ export function createPaymentsCollection(pluginConfig: BillingPluginConfig): Col await initProviderPayment(req.payload, data) } else if (operation === 'update') { - // Auto-increment version for updates (if not already set by optimistic locking) - if (!data.version) { - const currentDoc = await req.payload.findByID({ - collection: extractSlug(pluginConfig.collections?.payments || defaults.paymentsCollection), - id: req.id as any - }) - data.version = (currentDoc.version || 1) + 1 + // Handle version incrementing for manual updates + // Webhook updates from providers should already set the version via optimistic locking + if (!data.version && req.id) { + // Check if this is a webhook update by looking for webhook-specific fields + const isWebhookUpdate = data.providerData && + (data.providerData.webhookProcessedAt || + (typeof data.providerData === 'object' && 'webhookProcessedAt' in data.providerData)) + + if (!isWebhookUpdate) { + // This is a manual admin update, safely increment version + try { + const currentDoc = await req.payload.findByID({ + collection: extractSlug(pluginConfig.collections?.payments || defaults.paymentsCollection), + id: toPayloadId(req.id) + }) + data.version = (currentDoc?.version || 1) + 1 + } catch (error) { + // If we can't find the current document, start with version 1 + console.warn(`[Payment Hook] Could not fetch current version for payment ${req.id}, defaulting to version 1:`, error) + data.version = 1 + } + } + // If it's a webhook update without a version, let it proceed (optimistic locking already handled it) } } }, diff --git a/src/plugin/utils.ts b/src/plugin/utils.ts index 9ebdd01..9ac3710 100644 --- a/src/plugin/utils.ts +++ b/src/plugin/utils.ts @@ -1,6 +1,15 @@ import type { CollectionConfig, CollectionSlug, Field } from 'payload' +import type { Id } from '@/plugin/types' export type FieldsOverride = (args: { defaultFields: Field[] }) => Field[] export const extractSlug = (arg: string | Partial) => (typeof arg === 'string' ? arg : arg.slug!) as CollectionSlug + +/** + * Safely cast ID types for PayloadCMS operations + * This utility provides a typed way to handle the mismatch between our Id type and PayloadCMS expectations + */ +export function toPayloadId(id: Id): any { + return id as any +} diff --git a/src/providers/utils.ts b/src/providers/utils.ts index 1837504..24db926 100644 --- a/src/providers/utils.ts +++ b/src/providers/utils.ts @@ -1,8 +1,9 @@ import type { Payload } from 'payload' import type { Payment } from '@/plugin/types/payments' import type { BillingPluginConfig } from '@/plugin/config' +import type { ProviderData } from './types' import { defaults } from '@/plugin/config' -import { extractSlug } from '@/plugin/utils' +import { extractSlug, toPayloadId } from '@/plugin/utils' /** * Common webhook response utilities @@ -57,7 +58,7 @@ export async function updatePaymentStatus( // Get current payment to check version for atomic locking const currentPayment = await payload.findByID({ collection: paymentsCollection, - id: paymentId as any // Cast to avoid type mismatch between Id and PayloadCMS types + id: toPayloadId(paymentId) }) as Payment const now = new Date().toISOString() @@ -68,7 +69,7 @@ export async function updatePaymentStatus( const result = await payload.updateMany({ collection: paymentsCollection, where: { - id: { equals: paymentId as any }, // Cast to avoid type mismatch + id: { equals: toPayloadId(paymentId) }, version: { equals: currentPayment.version || 1 } }, data: { @@ -112,7 +113,7 @@ export async function updateInvoiceOnPaymentSuccess( await payload.update({ collection: invoicesCollection, - id: invoiceId as any, // Cast to avoid type mismatch between Id and PayloadCMS types + id: toPayloadId(invoiceId), data: { status: 'paid', payment: payment.id