All Docs
FeaturesCalmony PayUpdated March 15, 2026

Fixing the SDK's CreatePaymentMethodParams.card type mismatch

Fixing the SDK's CreatePaymentMethodParams.card type mismatch

Release: v1.0.69

What happened

A type definition in the Calmony Pay TypeScript SDK caused every call to paymentMethods.create() made via the typed SDK to fail with a 400 invalid_request_error at runtime.

The card field on CreatePaymentMethodParams was defined as:

card: {
  /** Cardstream hosted payment page token */
  token: string;
}

But POST /v1/payment_methods validates the request body expecting four raw card fields:

card: {
  number: string;    // PAN
  exp_month: number; // 1–12
  exp_year: number;  // e.g. 2027
  cvc: string;       // 3–4 digits
}

Because TypeScript types are erased at runtime, any developer who typed their payload as { card: { token: '...' } } and passed it through the SDK would compile without errors but hit a 400 immediately on the first real API call.

The fix

The CreatePaymentMethodParams interface in src/lib/calmony-pay/client.ts has been updated to exactly mirror the REST API validation schema:

export interface CreatePaymentMethodParams {
  type: "card";
  /**
   * Raw card details. The API tokenises the card via Cardstream and stores
   * only the last-4 digits and the resulting cross-reference token —
   * raw PAN is never persisted.
   */
  card: {
    /** Full card number (PAN), e.g. "4111111111111111" */
    number: string;
    /** Two-digit expiry month, e.g. 12 */
    exp_month: number;
    /** Four-digit expiry year, e.g. 2027 */
    exp_year: number;
    /** Card security code (3–4 digits) */
    cvc: string;
  };
  billing_details?: {
    name?: string;
    // ...
  };
}

The REST API route itself was already correct — only the SDK type needed updating.

Migration

If you were using the SDK and supplying { card: { token: '...' } }, update your code to pass the four raw card fields:

const pm = await pay.paymentMethods.create({
  type: "card",
  card: {
    number: "4111111111111111",
    exp_month: 12,
    exp_year: 2027,
    cvc: "123",
  },
});

No changes are required if you were calling POST /v1/payment_methods directly via HTTP — the API contract is unchanged.

Security note

Raw card details supplied to paymentMethods.create() are tokenised immediately via Cardstream. Only the last four digits of the PAN and the resulting Cardstream cross-reference token are persisted — the raw PAN, expiry, and CVC are never stored.

Also in this release: PII encryption test hardening

The tamper-detection test for PII encryption was updated to more reliably exercise the GCM authentication tag check. The previous test flipped the last character of the full encrypted string, which included the enc: prefix and could corrupt the prefix rather than the ciphertext. The updated test:

  1. Strips the enc: prefix.
  2. Corrupts a byte at the midpoint of the base64url payload (the GCM auth tag region).
  3. Re-attaches the prefix before asserting that decryptPii() throws.

This is an internal test-quality improvement and has no effect on the behaviour of the encryption utility itself.