Skip to main content

ID and Object Modeling

Payment processors can't agree on how to name their IDs. Stripe uses pi_3MqSCR2eZvKYlo2C1. Adyen uses 7914073381344578. Razorpay uses pay_ABCdef123. This inconsistency breaks code completion, confuses LLMs, and forces you to maintain different ID handling logic for each connector.

Prism solves this with a unified ID system that uses strongly-typed, self-describing identifiers regardless of the underlying processor.

The ID Problem

Without unified IDs, your code looks like this:

// Which ID goes where? Easy to mix up.
const stripePaymentId = 'pi_3MqSCR2eZvKYlo2C1';
const adyenPaymentId = '7914073381344578';
const razorpayPaymentId = 'pay_ABCdef123';

// Oops—passed the wrong ID type
await client.refunds.create({
paymentId: adyenPaymentId, // But we're using Stripe!
amount: 1000
});
// Error: Payment not found (because IDs aren't interchangeable)

LLMs hallucinate valid IDs when they see inconsistent patterns. Ask an LLM to "create a Stripe PaymentIntent ID" and it might generate pay_123abc (Razorpay format) or just make up a random number.

Connector ID Chaos

ConnectorPayment ID ExampleCustomer ID ExampleRefund ID Example
Stripepi_3MqSCR2eZvKYlo2C1cus_ABC123def456re_3NabcDEF456
Adyen791407338134457812345678901234568814073381344578
Razorpaypay_ABCdef123ghicust_ABC123ref_ABCdef123
PayPalPAYID-ABC123DEF456CUST-123-456-789REFUND-ABC-123
Braintreef2ekdhg3sk5mcustomer_id_123refund_id_456

Each has different length constraints, prefixes, and validation rules. Your validation logic becomes a nightmare of regex patterns.

The entity_domain_id Pattern

Prism uses typed IDs with a consistent format: entity_domain_id

ID TypeFormatExample
PaymentIdpay_{nano_id}pay_abc123def456ghi
CustomerIdcus_{nano_id}cus_xyz789uvw012jkl
RefundIdref_{nano_id}ref_def456ghi789mno
MandateIdman_{nano_id}man_ghi789mno345pqr

The prefix tells you exactly what the ID represents. The NanoID provides collision-resistant unique identifiers.

Typed ID Safety

Prism implements strongly-typed IDs at the language level:

// These are distinct types, not just Strings
struct PaymentId(LengthId<64, 1>);
struct CustomerId(String);
struct RefundId(LengthId<64, 1>);

// Compiler prevents mixing them up
fn process_payment(id: PaymentId) { ... }
fn process_customer(id: CustomerId) { ... }

process_payment(customer_id); // Compile error!
process_customer(payment_id); // Compile error!

In your SDK:

// TypeScript knows the difference
type PaymentId = `pay_${string}`;
type CustomerId = `cus_${string}`;

function authorize(paymentId: PaymentId): Promise<AuthorizeResponse>;
function createCustomer(customerId: CustomerId): Promise<Customer>;

// IDE autocomplete and type checking prevent mistakes
client.payments.authorize({ paymentId: 'cus_abc123' });
// Error: Argument of type 'CustomerId' is not assignable to parameter of type 'PaymentId'

Global IDs for Distributed Systems

For multi-instance deployments, Prism uses Global IDs with a cell prefix:

Format: {cell_id}_{entity_prefix}_{uuid}

Example: cell1_pay_uu1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p
│ │ │
│ │ └─ Time-ordered UUID (enables DB indexing)
│ └──── Entity type (pay, cus, ref, etc.)
└────────── Cell identifier (for distributed deployments)

This lets you route requests to the correct data center and maintain uniqueness across regions.

ID Types in Key Operations

Authorization Flow IDs

const response = await client.payments.authorize({
amount: { minorAmount: 1000, currency: 'USD' },
paymentMethod: { card: { ... } }
});

// Returns:
{
paymentId: 'pay_abc123def456ghi', // Prism unified ID
connectorPaymentId: 'pi_3MqSCR...', // Stripe's native ID (for reference)
status: 'AUTHORIZED'
}

Use paymentId for all follow-up operations. Prism tracks the mapping to Stripe's pi_ ID internally.

Refund Flow IDs

const refund = await client.payments.refund({
paymentId: 'pay_abc123def456ghi',
amount: { minorAmount: 500, currency: 'USD' }
});

// Returns:
{
refundId: 'ref_def456ghi789mno', // Prism unified ID
connectorRefundId: 're_3Nabc...', // Stripe's refund ID
status: 'PENDING'
}

Customer IDs

const customer = await client.customers.create({
name: 'John Doe'
});

// Returns:
{
customerId: 'cus_xyz789uvw012jkl', // Prism unified ID
connectorCustomerId: 'cus_ABC123...', // Stripe's customer ID
}

// Use unified ID for future operations
await client.payments.authorize({
customerId: 'cus_xyz789uvw012jkl', // Not 'cus_ABC123...'
amount: { ... }
});

Connector ID Mapping

Prism maintains a mapping table internally:

Unified IDConnectorConnector-Specific ID
pay_abc123Stripepi_3MqSCR2eZvKYlo2C1
pay_abc123Adyen7914073381344578
cus_xyz789Stripecus_ABC123def456
cus_xyz789Adyen1234567890123456

You never deal with connector-specific IDs directly. The unified ID works across all connectors.

Benefits

Without Unified IDsWith Unified IDs
Regex validation per connectorSingle format validation
Type confusion bugsCompile-time type safety
LLM hallucinationPredictable ID patterns
Connector-specific code pathsOne code path for all connectors
Difficult debuggingClear ID semantics

ID Constraints

ID TypeMax LengthMin LengthPrefix
PaymentId6417pay_
CustomerId6417cus_
RefundId6417ref_
MandateId6417man_
GlobalId6432{cell}_{entity}_

These constraints prevent buffer overflows and ensure IDs fit in database indexes.

Best Practices

  1. Always use unified IDs in your application code
  2. Store connector-specific IDs only for reconciliation with processor dashboards
  3. Validate ID format at API boundaries (prefix + length checks)
  4. Use type-safe wrappers in strongly-typed languages
  5. Never construct IDs manually—always get them from Prism responses

Your ID handling becomes simple, safe, and portable across all 50+ connectors.