fix: add idempotency keys to all Stripe mutation calls #49

Merged
hodlbod merged 1 commits from userAdityaa/caravel:stripe-keys into master 2026-04-25 12:34:35 +00:00
Contributor

Problem

All non-read Stripe API calls —> create customer, create/update subscription item, create subscription, pay invoice, and pay invoice out-of-band, were sent without an Idempotency-Key header. On network uncertainty or an upstream retry, Stripe would treat each attempt as a new request, creating duplicate subscriptions, subscription items, or applying multiple payment side effects.

Solution

Added a private Billing::idempotency_key(&[&str]) -> String helper that produces a deterministic, 64-char hex HMAC-SHA256 fingerprint. The key is derived from a colon-separated tuple of an operation label and the business identifiers that uniquely describe "this specific state transition" (e.g., create_subscription:{customer_id}:{price_id}). The HMAC is keyed with STRIPE_SECRET_KEY so the output is unguessable externally, and it stays well within Stripe's 255-character limit.

The Idempotency-Key header was added to 6 mutation methods:

Method Key scope
stripe_create_customer create_customer:{tenant_pubkey}
stripe_create_subscription create_subscription:{customer_id}:{price_id}
stripe_create_subscription_item create_subscription_item:{subscription_id}:{price_id}
stripe_update_subscription_item update_subscription_item:{item_id}:{price_id}
stripe_pay_invoice pay_invoice:{invoice_id}
stripe_pay_invoice_out_of_band pay_invoice_oob:{invoice_id}

closes #47

### Problem All non-read Stripe API calls —> create customer, create/update subscription item, create subscription, pay invoice, and pay invoice out-of-band, were sent without an `Idempotency-Key` header. On network uncertainty or an upstream retry, Stripe would treat each attempt as a new request, creating duplicate subscriptions, subscription items, or applying multiple payment side effects. ### Solution Added a private `Billing::idempotency_key(&[&str]) -> String` helper that produces a deterministic, 64-char hex HMAC-SHA256 fingerprint. The key is derived from a colon-separated tuple of an operation label and the business identifiers that uniquely describe "this specific state transition" (e.g., `create_subscription:{customer_id}:{price_id}`). The HMAC is keyed with `STRIPE_SECRET_KEY` so the output is unguessable externally, and it stays well within Stripe's 255-character limit. The `Idempotency-Key` header was added to 6 mutation methods: | Method | Key scope | |---|---| | `stripe_create_customer` | `create_customer:{tenant_pubkey}` | | `stripe_create_subscription` | `create_subscription:{customer_id}:{price_id}` | | `stripe_create_subscription_item` | `create_subscription_item:{subscription_id}:{price_id}` | | `stripe_update_subscription_item` | `update_subscription_item:{item_id}:{price_id}` | | `stripe_pay_invoice` | `pay_invoice:{invoice_id}` | | `stripe_pay_invoice_out_of_band` | `pay_invoice_oob:{invoice_id}` | closes #47
userAdityaa added 1 commit 2026-04-25 05:20:57 +00:00
hodlbod merged commit 9f8fe7261f into master 2026-04-25 12:34:35 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: coracle/caravel#49