@crvouga/mockingbird-service-stripe 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog — @crvouga/mockingbird-service-stripe
2
2
 
3
+ ## 1.6.0 (2026-09-29)
4
+
5
+ ### Features
6
+
7
+ - per-option data-testids on the portal's plan and reason choices ([237e5ef](https://github.com/crvouga/mockingbird/commit/237e5efc5425be3062e75cdfbcf942e8590bcd90))
8
+
9
+ ### Fixes and improvements
10
+
11
+ - name a missing invoice item list cursor an invoice item ([7cfc811](https://github.com/crvouga/mockingbird/commit/7cfc81125e5e5ee23cfb03644bb7db4fa5473fa2))
12
+ - keep a customer address country as sent unless it is two letters ([6547c6c](https://github.com/crvouga/mockingbird/commit/6547c6c41e35dcfb469ebd53fbbb31dedbe34379))
13
+ - refuse a malformed preferred_locales before an empty invoice_settings on customer create ([65b18ed](https://github.com/crvouga/mockingbird/commit/65b18ed74f072b856a9c5334c3039c17f705bf45))
14
+ - resolve a payment method list's cursors before its customer_account ([8df4ce6](https://github.com/crvouga/mockingbird/commit/8df4ce63462b0192e32b8a14ce411c136ac55639))
15
+ - refuse both price amounts before looking up the product ([86700e2](https://github.com/crvouga/mockingbird/commit/86700e2e22576a4ebb79d5caa2790a2c86954da5))
16
+ - refuse an empty expand before looking up a Checkout Session ([4eb1010](https://github.com/crvouga/mockingbird/commit/4eb1010153a26f321dbc2870a537f8e0842bc945))
17
+ - refuse an empty cursor on a customer's balance transactions ([05fe354](https://github.com/crvouga/mockingbird/commit/05fe3549de39fb1a0208e79eba3c12d35641e5c7))
18
+ - name the dispute, not the cursor, in a missing-cursor error ([036ed1a](https://github.com/crvouga/mockingbird/commit/036ed1a249d015242e068c9c6796a320a9483f5c))
19
+ - name a missing portal configuration "configuration" ([d62e1df](https://github.com/crvouga/mockingbird/commit/d62e1df914536adef44a34414a0e42bfc726961a))
20
+ - leave portal configuration listing out of live parity walks ([ea11169](https://github.com/crvouga/mockingbird/commit/ea11169571ed61c2308ad5cb13fc07fa907ee9b2))
21
+ - refuse an empty search query before an empty expand ([2d0a11a](https://github.com/crvouga/mockingbird/commit/2d0a11aa1aab9e925597e63a1ec9df1744f8f9ef))
22
+ - show redacted startup output when stripe listen fails ([6c86bcc](https://github.com/crvouga/mockingbird/commit/6c86bccfe4654442cf20d2ab4e7114b63bd1ac04))
23
+
3
24
  ## 1.5.0 (2026-09-29)
4
25
 
5
26
  ### Features
package/README.md CHANGED
@@ -283,6 +283,11 @@ billing.stripe.com) where the customer manages their billing, as the session's c
283
283
  through a SetupIntent, which becomes the customer's and each subscription's default; **Make
284
284
  default**; **Delete** a non-default card); billing information (the configured `allowed_updates`,
285
285
  `customer.updated` with `previous_attributes`); invoice history with **Pay** for open invoices.
286
+ Repeated choices carry a per-option `data-testid` on the wrapping `<label class="option">`
287
+ (tapping it checks the radio), so a test-id-only driver can pick one: each plan is
288
+ `stripe-mock-portal-price-option-<price lookup_key, else price id>` and each cancellation reason
289
+ is `stripe-mock-portal-reason-<reason>` (e.g. `…-too_expensive`). The generic
290
+ `stripe-mock-portal-price-option` and `stripe-mock-portal-reason` ids stay on the radios.
286
291
  - **Deep links** open on the flow's page — cancel (with a retention offer to accept), update, update
287
292
  confirmation, payment method, billing details — and on completion honour `after_completion`:
288
293
  `redirect` (302), `hosted_confirmation` (its `custom_message`) or the homepage. Every action
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createRuntime,
3
3
  validateAccounts
4
- } from "./chunk-HLEW2SR7.js";
4
+ } from "./chunk-JMK5BHFP.js";
5
5
 
6
6
  // src/server.ts
7
7
  import { readFileSync } from "node:fs";
@@ -385,4 +385,4 @@ export {
385
385
  createServer2 as createServer,
386
386
  serveTarget
387
387
  };
388
- //# sourceMappingURL=chunk-L4SSW3ZW.js.map
388
+ //# sourceMappingURL=chunk-JLJVLLWF.js.map
@@ -1559,7 +1559,7 @@ var restoreNamespace = (sqlite, namespace, snapshot) => {
1559
1559
  };
1560
1560
 
1561
1561
  // ../core/dist/version.js
1562
- var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
1562
+ var PACKAGE_VERSION = true ? "1.6.0" : UNRELEASED_VERSION;
1563
1563
 
1564
1564
  // ../core/dist/signing.js
1565
1565
  var encoder = new TextEncoder();
@@ -5978,11 +5978,15 @@ var paginate = async (collection, params, options) => {
5978
5978
  const endingBefore = params.ending_before;
5979
5979
  const all = await collection.list({ order: "newest" });
5980
5980
  const exists = options.exists ?? (() => true);
5981
+ if (options.rejectEmptyCursors) {
5982
+ if (startingAfter === "") throw parameterInvalidEmpty("starting_after");
5983
+ if (endingBefore === "") throw parameterInvalidEmpty("ending_before");
5984
+ }
5981
5985
  const cursorIndex = (id, param) => {
5982
5986
  const index = all.findIndex(
5983
5987
  (entry) => entry.id === id && exists(entry.value)
5984
5988
  );
5985
- if (index === -1) throw resourceMissing(options.kind, id, param, 400);
5989
+ if (index === -1) throw resourceMissing(options.kind, id, options.cursorParam ?? param, 400);
5986
5990
  return index;
5987
5991
  };
5988
5992
  if (typeof startingAfter === "string" && startingAfter !== "")
@@ -9576,7 +9580,8 @@ var checkoutSessionHandlers = (services) => ({
9576
9580
  },
9577
9581
  GetCheckoutSessionsSession: async (context) => {
9578
9582
  const scope = requestScope(services, context);
9579
- queryParams(context);
9583
+ const params = queryParams(context);
9584
+ if (params.expand === "") throw parameterInvalidEmpty("expand");
9580
9585
  const id = context.params.session ?? "";
9581
9586
  const session = scope.account.checkoutSessions.get(id);
9582
9587
  if (!session)
@@ -9839,7 +9844,7 @@ var cancelPage = (context, subscriptionId, messages) => {
9839
9844
  const immediately = cancel.mode === "immediately";
9840
9845
  const when = immediately ? "Your subscription will be canceled immediately." : `Your subscription will be canceled, but is still available until the end of your billing period on ${formatDate(subscription.current_period_end)}.`;
9841
9846
  const reasons = cancel.cancellation_reason.enabled ? `<h2>Tell us why you're canceling</h2>${cancel.cancellation_reason.options.filter((option) => CANCELLATION_REASONS.includes(option)).map(
9842
- (option) => `<label class="option"><input type="radio" name="reason" value="${option}" data-testid="stripe-mock-portal-reason"${messages.values?.reason === option ? " checked" : ""}><span>${escapeHtml2(REASON_LABELS[option] ?? option)}</span></label>`
9847
+ (option) => `<label class="option" data-testid="stripe-mock-portal-reason-${escapeHtml2(option)}"><input type="radio" name="reason" value="${option}" data-testid="stripe-mock-portal-reason"${messages.values?.reason === option ? " checked" : ""}><span>${escapeHtml2(REASON_LABELS[option] ?? option)}</span></label>`
9843
9848
  ).join(
9844
9849
  ""
9845
9850
  )}<label class="field"><span>Anything else? (optional)</span><textarea name="comment" data-testid="stripe-mock-portal-comment">${escapeHtml2(messages.values?.comment ?? "")}</textarea></label>` : "";
@@ -9870,7 +9875,7 @@ var updatePage = (context, subscriptionId, messages) => {
9870
9875
  const options = allowed.includes("price") ? portalProducts(scope.account, config).flatMap((product) => product.prices).map((priceId) => {
9871
9876
  const price = scope.account.prices.get(priceId);
9872
9877
  if (!price?.active) return "";
9873
- return `<label class="option"><input type="radio" name="price" value="${price.id}" data-testid="stripe-mock-portal-price-option"${price.id === selected ? " checked" : ""}><span><strong>${escapeHtml2(productName(context, price))}</strong>${price.id === item?.price ? '<span class="chip">Current plan</span>' : ""}<div class="meta">${escapeHtml2(priceLabel(price))}</div></span></label>`;
9878
+ return `<label class="option" data-testid="stripe-mock-portal-price-option-${escapeHtml2(price.lookup_key ?? price.id)}"><input type="radio" name="price" value="${price.id}" data-testid="stripe-mock-portal-price-option"${price.id === selected ? " checked" : ""}><span><strong>${escapeHtml2(productName(context, price))}</strong>${price.id === item?.price ? '<span class="chip">Current plan</span>' : ""}<div class="meta">${escapeHtml2(priceLabel(price))}</div></span></label>`;
9874
9879
  }).join("") : `<input type="hidden" name="price" value="${item?.price ?? ""}">`;
9875
9880
  const quantity = allowed.includes("quantity") ? `<label class="field"><span>Quantity</span><input type="number" min="1" name="quantity" value="${escapeHtml2(messages.values?.quantity ?? String(item?.quantity ?? 1))}" data-testid="stripe-mock-portal-quantity"></label>` : "";
9876
9881
  return `<h1>Update your plan</h1>
@@ -11324,12 +11329,13 @@ var expanders4 = (scope) => {
11324
11329
  "data.invoice_settings.default_payment_method": defaultPaymentMethod
11325
11330
  };
11326
11331
  };
11332
+ var CUSTOMER_PARAM_ORDER = ["preferred_locales"];
11327
11333
  var customerHandlers = (services) => {
11328
11334
  const render3 = (scope, customer, params) => applyExpand(renderCustomer(customer), params.expand, expanders4(scope));
11329
11335
  return {
11330
11336
  PostCustomers: async (context) => {
11331
11337
  const scope = requestScope(services, context);
11332
- const params = bodyParams(context);
11338
+ const params = bodyParams(context, { order: CUSTOMER_PARAM_ORDER });
11333
11339
  const id = scope.ids.next("cus_");
11334
11340
  const base2 = {
11335
11341
  id,
@@ -11446,6 +11452,7 @@ var customerHandlers = (services) => {
11446
11452
  url: `/v1/customers/${id}/balance_transactions`,
11447
11453
  // Stripe names an unknown cursor on this list by its internal model.
11448
11454
  kind: "abstracttransaction",
11455
+ rejectEmptyCursors: true,
11449
11456
  where: (record) => record.customer === id && matchesCreated(record.created, params.created),
11450
11457
  render: renderCustomerBalanceTransaction
11451
11458
  });
@@ -11494,6 +11501,7 @@ var disputeHandlers = (services) => ({
11494
11501
  const page = await paginate(scope.account.disputes, params, {
11495
11502
  url: "/v1/disputes",
11496
11503
  kind: "dispute",
11504
+ cursorParam: "dispute",
11497
11505
  where: (record) => matchesCreated(record.created, params.created) && (charge === null || record.charge === charge) && (paymentIntent === null || record.payment_intent === paymentIntent),
11498
11506
  render: renderDispute
11499
11507
  });
@@ -12040,6 +12048,9 @@ var paymentMethodHandlers = (services) => ({
12040
12048
  where: (record) => (customer === null || record.customer === customer) && (type === null || record.type === type),
12041
12049
  render: renderPaymentMethod
12042
12050
  });
12051
+ const account = stringOf(params, "customer_account");
12052
+ if (account !== null && account !== "")
12053
+ throw resourceMissing("customer", account, "customer_account", 400);
12043
12054
  return jsonRes(200, page);
12044
12055
  },
12045
12056
  PostPaymentMethods: async (context) => {
@@ -17375,7 +17386,7 @@ var StripeAPI = class {
17375
17386
  "customer"
17376
17387
  ).init
17377
17388
  );
17378
- if (customerAccount !== void 0 && customerAccount !== "")
17389
+ if (customerAccount !== void 0 && customerAccount !== "" && context.operation.operationId !== "GetPaymentMethods")
17379
17390
  return this.errorResponse(
17380
17391
  resourceMissing("customer", customerAccount, "customer_account", 400).init
17381
17392
  );
@@ -17623,4 +17634,4 @@ export {
17623
17634
  createRuntime2 as createRuntime,
17624
17635
  StripeAPI
17625
17636
  };
17626
- //# sourceMappingURL=chunk-HLEW2SR7.js.map
17637
+ //# sourceMappingURL=chunk-JMK5BHFP.js.map