Skip to content

[FEAT] Exactly-Once Payment Processing with Conflict Resolution #14

Description

@oomokaro1

[FEAT] Exactly-Once Payment Processing with Conflict Resolution

Priority: High

Difficulty: Hard
Estimated Effort: 2-3 days
Relevant Packages: OrbitStream_backend/
Labels: enhancement, data-integrity, priority:high

Requirements

1. Serializable Transaction

Wrap the entire processPayment in a Drizzle transaction with the highest isolation level:

await db.transaction(async (tx) => {
  // Lock the session row
  const [session] = await tx.execute(
    sql`SELECT * FROM checkout_sessions WHERE id = ${sessionId} FOR UPDATE`
  );

  if (session.status !== 'pending') {
    return; // Another payment already processed this session
  }

  // Update session
  await tx.update(checkoutSessions).set({ status: 'paid' })...

  // Insert payment
  await tx.insert(payments).values({ ... })...
});

2. Idempotency Check

Before processing, check if a payment with the same tx_hash already exists:

const existing = await db.query.payments.findFirst({
  where: eq(payments.txHash, op.transaction_hash),
});
if (existing) {
  this.logger.warn(`Duplicate payment ${op.transaction_hash} — skipping`);
  return;
}

3. Recovery for Stale Sessions

Implement a periodic job that finds and repairs inconsistent state:

  • Sessions stuck in processing for > 5 minutes → check Horizon, recover or rollback
  • Sessions marked paid with no payment record → check Horizon, insert missing payment
  • Sessions with payment records but still pending → mark as paid

Run every 5 minutes using @nestjs/schedule.

4. Duplicate Payment Handling

If a payment arrives for an already-paid session:

  • Log a warning
  • Don't update the session
  • Don't insert a duplicate payment
  • Don't dispatch a duplicate webhook
  • Return gracefully

5. Testing

  • Concurrent payment submissions (2 payments for same session simultaneously)
  • Verify only one succeeds
  • Test idempotency: same tx hash submitted twice
  • Test recovery job: orphaned sessions are repaired
  • Test crash simulation: session stuck in processing

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions