How Credit Transaction Deduplication Works in hasTransactionWithMetadata

Credit transaction deduplication works by querying the credit_transactions table for existing records matching a specific user ID, source identifier, and metadata payload, returning true if any match exists to prevent duplicate credit grants.

The awesome-gpt-image-2 repository implements a robust idempotency guard within its billing system to ensure users never receive the same credits twice. This mechanism centers on the hasTransactionWithMetadata helper function defined in api/_lib/billing.js, which validates transaction uniqueness before inserting new credit records. Understanding this credit transaction deduplication strategy is essential when processing payment webhooks or retrying failed operations.

The Deduplication Logic in hasTransactionWithMetadata

The core implementation resides in api/_lib/billing.js and leverages Supabase's query builder to detect potential duplicates:

async function hasTransactionWithMetadata(client, userId, source, metadata) {
  const { data, error } = await client
    .from('credit_transactions')
    .select('id')
    .eq('user_id', userId)
    .eq('source', source)
    .contains('metadata', metadata)
    .limit(1);

  if (error) throw error;
  return Boolean(data?.length);
}

Breaking Down the Query Components

  • .eq('user_id', userId) – Scopes the search to the specific user receiving credits, ensuring cross-user isolation.
  • .eq('source', source) – Filters by the transaction source (e.g., "stripe_checkout" or "membership_grant"), distinguishing between different credit origins.
  • .contains('metadata', metadata) – Uses Supabase's JSON containment operator (@>) to verify that the stored metadata object includes all key-value pairs from the input. This approach ensures the check remains valid even if the stored record contains additional fields added after the original transaction.
  • .limit(1) – Optimizes performance by instructing the database to stop scanning after finding the first match, reducing query cost.

The function returns true when data?.length exists, indicating a matching transaction was found.

Preventing Duplicate Credits in grantMembershipCredits

The deduplication check serves as a critical guard clause within grantMembershipCredits, located in the same api/_lib/billing.js file:

if (await hasTransactionWithMetadata(client, userId, source, metadata)) {
  // A transaction with the same metadata already exists → skip the grant
  return null;
}

When the function detects an existing transaction with identical metadata, it aborts the credit grant and returns null. This prevents duplicate credits from webhook retries, race conditions, or manual operation reruns.

Why Idempotency Matters for Payment Processing

Payment providers like Stripe may resend webhook events during network timeouts or processing delays. Without credit transaction deduplication, these retries would create multiple credit records for a single purchase. The metadata-based check ensures that processing the same Stripe session ID twice results in only one credit grant, maintaining accurate user balances.

Practical Code Examples

Checking for Existing Transactions Manually

When building custom admin tools or migration scripts, you can leverage the same deduplication logic:

import { createClient } from '@supabase/supabase-js';
import { hasTransactionWithMetadata } from './api/_lib/billing.js';

const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY);

async function verifyNoDuplicate(userId, sessionId) {
  const exists = await hasTransactionWithMetadata(
    supabase,
    userId,
    'stripe_checkout',
    { stripeSessionId: sessionId, productId: 42 }
  );
  
  if (exists) {
    console.log('Transaction already processed');
    return true;
  }
  
  // Safe to proceed with credit grant
  return false;
}

Integrating the Guard in Custom Credit Flows

When extending the billing system, implement the same pattern used by grantMembershipCredits:

import { hasTransactionWithMetadata } from './api/_lib/billing.js';

async function grantBonusCredits(client, userId, bonusMetadata) {
  const source = 'manual_bonus';
  
  if (await hasTransactionWithMetadata(client, userId, source, bonusMetadata)) {
    console.log('Bonus already granted');
    return null;
  }
  
  // Insert new credit transaction only if no duplicate exists
  return await client.from('credit_transactions').insert({
    user_id: userId,
    source: source,
    metadata: bonusMetadata,
    credits: 100,
    created_at: new Date().toISOString()
  });
}

Database Performance Considerations

The credit transaction deduplication query is designed for scalability. The function queries indexed columns—specifically user_id and source—which should maintain database indexes in the credit_transactions table schema. The .limit(1) clause ensures that even as the table grows to millions of records, the database engine stops scanning after locating the first match, minimizing CPU and I/O overhead.

Summary

  • Credit transaction deduplication in hasTransactionWithMetadata checks for existing records matching user ID, source, and metadata to prevent double-grants.
  • The function lives in api/_lib/billing.js and returns true when a duplicate exists, causing calling functions like grantMembershipCredits to abort.
  • .contains('metadata', metadata) uses JSON containment to match records flexibly, accommodating schema evolution while maintaining strict deduplication.
  • The query utilizes .limit(1) and targets indexed columns to ensure minimal performance impact at scale.
  • This pattern ensures idempotency when processing payment webhooks, preventing credit inflation from retry mechanisms.

Frequently Asked Questions

How does hasTransactionWithMetadata detect duplicate transactions?

The function queries the credit_transactions table using equality filters on user_id and source, combined with a JSON containment check on the metadata column via .contains(). If any record matches all three criteria simultaneously, the function returns true, indicating the transaction already exists and should not be reprocessed.

Why use .contains() instead of exact equality for metadata comparison?

Supabase's .contains() operator verifies that the stored JSON object includes all key-value pairs from the supplied metadata argument. This approach accommodates future schema changes where the stored record might include additional tracking fields, while still correctly identifying duplicates based on the essential identifying information (such as Stripe session IDs).

Where is the deduplication logic implemented in the repository?

The deduplication logic is implemented in api/_lib/billing.js within the hasTransactionWithMetadata function (lines 81-89). It is primarily utilized by grantMembershipCredits (lines 56-78) in the same file to prevent duplicate membership credit grants before inserting new records into the database.

Does the deduplication check impact database performance?

No, the check is optimized for high-volume scenarios. The query uses .limit(1) to stop scanning after finding the first match, and it filters on user_id and source, which should be indexed columns in the credit_transactions table. This design ensures consistent query performance regardless of table growth.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →