Silent Data Loss: How a Missing Schema Field Was Swallowing Trial Periods
Silent Data Loss: How a Missing Schema Field Was Swallowing Trial Periods
Release: v1.0.107 · Severity: High · Type: Spec Drift Bug Fix
What Happened
We identified and resolved a silent data-loss bug in the Calmony Pay SDK's subscriptions.create() method. When an SDK consumer passed a trial_end Unix timestamp, that value was never actually persisted — it was stripped silently before reaching the database.
No error was thrown. No warning was logged. The subscription was created successfully, just without the trial period.
The Root Cause
The bug was caused by a mismatch between two layers of the stack:
| Layer | trial_end defined? |
|---|---|
SDK CreateSubscriptionParams | ✅ Yes (trial_end?: number) |
REST API createSubscriptionSchema (Zod) | ❌ No |
The SDK correctly accepted trial_end as part of its parameter interface and forwarded it in the request body to the REST API. However, the Zod schema on the REST API side had no trial_end field. Zod's default behaviour is to strip unrecognised keys rather than reject them, so the value was quietly removed from the parsed body before any further processing.
The result: the subscription was created, the SDK consumer received a success response, but the trial end date was never stored.
Why This Matters
Trial periods are a billing-critical feature. A missed trial_end means:
- Customers are charged immediately instead of after their trial
- There is no visible error to alert the developer or the end user
- The bug is difficult to detect without explicitly inspecting the created subscription record
The Fix
The trial_end field has been added to createSubscriptionSchema in the REST API layer. The SDK interface and the API schema are now in alignment with the pinned Calmony Pay SDK Client Interface specification.
Affected file: src/lib/calmony-pay/client.ts
What You Should Do
If your integration calls subscriptions.create() with a trial_end value:
- Upgrade to Calmony Pay SDK v1.0.107 or later.
- Audit any subscriptions created before this release that were intended to include a trial period — they will have been created without one.
- For affected subscriptions, update the
trial_endon the subscription record directly, or cancel and recreate them as appropriate for your use case.
Example: Correct Usage After Fix
const subscription = await calmonyPay.subscriptions.create({
customer_id: 'cust_abc123',
plan_id: 'plan_monthly_pro',
trial_end: Math.floor(Date.now() / 1000) + 14 * 24 * 60 * 60, // 14 days from now
});
// trial_end is now correctly persisted
console.log(subscription.trial_end); // Unix timestamp
Lessons Learned
This bug is a textbook example of schema layer drift — when a client interface and a server schema evolve independently, undocumented strips or rejections at the boundary can cause data loss with no visible feedback. We are reviewing all SDK parameter fields against their corresponding API schemas to ensure this class of issue does not recur.