Design study · proposed · System Architecture · Financial Tech
Keeping SDKs consistent across iOS, Android, and web
Changing one API across several public SDKs is where maintenance debt shows up. This design study walks a hypothetical change through a shared contract, generated clients, and platform packs, and when separate implementations stay simpler.
4 min read · Updated 2026-04-23
Reader takeaway
- Four kinds of “shared” kept separate (runtime, contract, clients, wrappers)
- One hypothetical API change walked from OpenAPI to release
- When a shared core is worth the coordination cost, and when it is not
The maintenance tax
When partners embed checkout, ads, or rewards, you often ship SDKs for iOS, Android, and web. Change one public method and you may touch three codebases, three release trains, and three sets of samples. Without a shared source of truth, APIs drift, bugs fork, and every platform fix costs another pass.
Status: proposed design for a commerce SDK stack. Not a shipped multi-platform release with measured maintenance outcomes.
Four kinds of “shared”
Teams blur these together. Keep them separate:
| Kind | What it is | Example |
|---|---|---|
| Shared runtime | Code that runs on the device in every pack | Session validation, checkout state machine in a Core SDK |
| Shared contract | Machine-readable API surface | OpenAPI for POST /intents |
| Generated clients | Code produced from the contract | TypeScript / Swift / Kotlin stubs |
| Platform wrappers | Thin idiomatic bindings over the core | Swift Checkout.start() calling Core |
The proposal mixes them: a Core SDK owns shared namespaces (session, ads, checkout, rewards); platform packs import the core; storefront connectors sit above when a partner needs a plugin shape; cloud APIs stay behind OpenAPI; webhooks close payment and reward events.
Day-to-day integration behavior is in How checkout fits into a partner app. This piece is about keeping platforms aligned over time.
Worked example: add `metadata` to checkout start
Hypothetical change: partners need to pass a metadata map when starting checkout so support can find the cart later.
- Contract: add an optional
metadataobject onCheckoutStartRequestin OpenAPI. - Generate: regenerate clients so each language gets the field.
- Core: accept and forward
metadatain the shared checkout start path; do not re-implement per platform. - Wrappers: expose idiomatic setters only if the language needs them (Swift dictionary vs Kotlin map). No forked validation rules.
- Tests: contract test that unexpected keys are rejected or stripped per policy; one platform integration test that the field round-trips.
- Review: human review if the field is customer-visible or changes breaking semantics.
- Release: version bump per platform policy; changelog names the field.
# OpenAPI sketch (hypothetical)
CheckoutStartRequest:
properties:
metadata:
type: object
additionalProperties:
type: string
Deterministic generation from the schema is different from asking a coding agent to “update all SDKs.” Agents can open the PR, draft the changelog, and regenerate clients. People still decide breaking changes. If generation is wrong, fix the contract or generator, not only one platform pack.
What humans still review
Did the contract change intentionally? Do platform packs only add bindings, not forked business rules? Do tests cover double-submit and missing webhook cases you care about? Is the changelog honest about breaks?
Shared code reduces duplicated logic. Platform differences, release coordination, and dependency constraints remain. Test platform-specific behavior on purpose.
When sharing is worth it
Share a core when the same product rules must stay aligned across platforms and you can afford contract discipline. Prefer separate implementations when platforms diverge heavily, the team is small, or a shared core would become an abstraction that slows every change.
A sensible first slice of this design: one Core plus one platform pack shipping from the same contract, a documented human gate for breaking changes, and a regenerate-and-review path you can run twice without inventing a new process.
