feat(api): add customer blocklist and manual payment retry - #149
Conversation
Spec 1.113.6 -> 1.113.13 (+5 paths, +12 schemas, none removed). Adds a new blocklist resource exposing blocklist.customers with list/create/retrieve/delete and a nested notes subresource (create/update), plus two manual retry methods on payments (retry, retrieve_retry_state). The blocklist list endpoint takes page_number/page_size and returns an items array, matching default_page_number_pagination, so it auto-detects like the other page-number lists and needs no explicit paginated flag. SubscriptionWebhookPayload is reachable through OutgoingWebhookData and the subscription.* webhooks, so it needs no explicit model mapping. Validated: 148 configured endpoints all resolve against the spec, 113 config schema refs resolve, and all 295 distinct spec $refs resolve with none dangling. The 18 uncovered spec paths are the pre-existing intentional exclusions (Swagger UI /docs routes, internal connectors and discount-customer endpoints); no newly added path is uncovered. Stainless-Generated-From: cb8f543
There was a problem hiding this comment.
Review: generated SDK update for spec 1.113.6 → 1.113.13
I verified the generated output against the OpenAPI spec embedded in scripts/mock (decoded and diffed old vs. new). The spec delta is exactly +5 paths / +12 schemas, none removed, and configured_endpoints: 137 → 145 matches the 8 new operations (4 blocklist customer + 2 notes + 2 payment retry).
Verification performed
- Compiled
dodo-payments-java-core(main + test) cleanly on JDK 21. - Ran the suite against the Steady mock server: 1,889 tests, 1,860 passing. The 29 failures are all
Could not initialize plugin: org.mockito.plugins.MockMakerinClientOptionsTest,AutoPagerAsyncTestandAsyncStreamResponseTest— I reproduced them onmain, so they are a sandbox self-attach limitation, unrelated to this PR. All 426 tests covering files touched by this PR pass. - Upstream CI
buildandlintare both green.
What checks out
- Paths, HTTP verbs and path-parameter ordering match the spec for every new operation, including the two-segment
PATCH /blocklist/customers/{entry_id}/notes/{note_id}. - Required vs. optional fields on
BlockedCustomer,BlockedCustomerNote,ManualRetry,ManualRetryStateandCustomerListPageResponsematch the spec exactly, and numeric widths are right (retry_attemptint32 →Int;sends_used/sends_allowed/totalint64 →Long). - The
allOf(BlockIdentifier, {reason, source})request body flattens correctly into two union variants that each carryreasonandsource, and the deserializer triescustomer_idfirst, matching the spec's stated "customer_idwins" tie-break. - All six
listquery parameters are serialized with the correct wire names and ISO-8601 date-time formatting; pagination is consistent with every other paginated resource in the SDK. - Sync/async method parity holds for all three new services, client wiring and the ProGuard smoke test are updated, and the
scripts/mockchange is confined to the embedded spec blob (the script logic is byte-identical).
Main concern: an undisclosed breaking change rides along with this PR
The 12th new schema, SubscriptionWebhookPayload (= SubscriptionResponse + past_due_ends_at), is not mentioned in the PR description, but it is responsible for most of the 38.6k-line diff and it breaks source and binary compatibility for webhook consumers.
Across 12 event classes (SubscriptionActiveWebhookEvent, …Cancelled, …Expired, …Failed, …OnHold, …PastDue, …Paused, …PlanChanged, …Renewed, …Unpaused, …UpdatePaymentMethod, …Updated), the data field changes type:
- fun data(): Subscription // com.dodopayments.api.models.subscriptions.Subscription
+ fun data(): Data // a new class nested inside each event
Any code doing Subscription s = event.data();, or builder().data(subscription), stops compiling — and pre-compiled callers break at runtime. Two consequences worth deciding on before merge:
-
Release type. The commit is
feat(api):, which release-please turns into a minor bump (1.114.0 → 1.115.0) with no breaking-change note in the changelog. A compile break shipping silently in a minor release is likely to surprise users. Please either add aBREAKING CHANGE:footer /!so the bump and changelog reflect reality, or call it out prominently in the release notes. -
Code duplication. The generator inlined
SubscriptionWebhookPayloadonce per event rather than emitting a shared model. I confirmed the 12 nestedDataclasses are byte-for-byte identical after normalizing the enclosing class name — 26,460 lines of exact duplication for a single schema, roughly 2,200 lines each. Beyond review burden, this inflates jar size, compile time, and R8/ProGuard output for Android consumers, and it means the 12 payload types are mutually incompatible even though they represent the same thing (you can't write one helper that accepts the payload from any subscription event).Adding
SubscriptionWebhookPayloadto the sharedmodelssection of the Stainless config would collapse these into one type. That is still a type change relative toSubscription, but it is a much smaller diff, keeps a single shared payload type, and mirrors howSubscriptionitself was previously shared. NoteWebhookPayload.ktalready handles the same spec change additively on its existing nestedSubscriptionclass, so only the standalone event classes are affected.
Everything else in the change is additive and backward compatible: new past_due / subscription.past_due enum members, optional Customer.blocked_at / blocklist_entry_id, and optional BalanceLedgerEntry.payout_id.
Minor notes (non-blocking)
models/blocklist/customers/BlockIdentifier.ktis generated but referenced nowhere in the SDK — the request body uses the flattenedCreateBlockedCustomerRequestvariants instead. It is dead public API surface that will need to be supported forever; consider dropping it from the config unless it is intentionally exported.UnwrapWebhookEvent.VisitorgainsvisitSubscriptionPastDuewith no default implementation, so anyone implementing that interface directly must update. This is the established pattern in this SDK for new union variants, just worth a changelog mention alongside thedatachange.
I'd suggest REQUEST_CHANGES only on the release-metadata point — the generated code itself is correct and faithful to the spec. Happy to approve once the breaking change is either reflected in the version bump/changelog or the shared-model config change is made.
| * | ||
| * @throws DodoPaymentsInvalidDataException if the JSON field has an unexpected type or is | ||
| * unexpectedly missing or null (e.g. if the server responded with an unexpected value). | ||
| */ | ||
| fun data(): Subscription = data.getRequired("data") | ||
| fun data(): Data = data.getRequired("data") |
There was a problem hiding this comment.
data changes from the shared com.dodopayments.api.models.subscriptions.Subscription to this per-event nested Data class. This is source- and binary-incompatible for anyone calling event.data() or builder().data(subscription), and it repeats identically across 12 subscription event classes.
The underlying spec change is legitimate (SubscriptionWebhookPayload = SubscriptionResponse + past_due_ends_at), but promoting it to a shared model in the Stainless config would keep a single payload type across all 12 events instead of 12 mutually incompatible copies.
Spec 1.113.6 -> 1.113.13 (+5 paths, +12 schemas, none removed).
Adds a new
blocklistresource exposingblocklist.customerswithlist/create/retrieve/delete and a nested
notessubresource (create/update),plus two manual retry methods on
payments(retry,retrieve_retry_state).Generated by stlc run 33521978397 from dodopayments-sdk-config@ae64a0b.