Skip to content

Support session credentials for identity verification - #22

Merged
AngelPaella merged 7 commits into
mainfrom
angelcalderaro/id-693-em4-swift-checkout-support-the-session-credentials-shape
Oct 14, 2026
Merged

AngelPaella merged 7 commits into
mainfrom
angelcalderaro/id-693-em4-swift-checkout-support-the-session-credentials-shape

Conversation

@AngelPaella

@AngelPaella AngelPaella commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

This pull request lets the SDK read the identity verification credentials of orders on the new Crossmint KYC UI. Those orders carry { verificationId, clientSecret, deviceSessionKey } in payment.preparation.kyc instead of a Persona inquiry. Today the decoder rejects that shape and logs checkout.order.kyc.decodeError, so identityVerificationCredentials is nil. An app on identityVerificationHandling: .external never shows the step, and the order waits at requires-kyc.

Part of ID-693.

Usage

.onChange(of: controller.identityVerificationCredentials) { credentials in
    if let credentials, !kycClosed { kycCredentials = credentials }
}
.sheet(item: $kycCredentials, onDismiss: { kycClosed = true }) { credentials in
    CrossmintIdentityVerification(apiKey: "ck_production_...", credentials: credentials)
        .onComplete { _ in kycCredentials = nil }
        .onCancel { kycCredentials = nil }
}

Changes

  • IdentityVerificationCredentials decodes both shapes: the session shape when verificationId and clientSecret are non-empty, the Persona rules otherwise. It sends each shape to the hosted page as it came. The Persona JSON is unchanged.
  • New init(verificationId:clientSecret:deviceSessionKey:). init(inquiryId:sessionToken:) is unchanged.
  • id is the verification id for a session and the inquiry id for Persona, so a rotated clientSecret updates the open sheet instead of presenting a new one. Equality still compares every field.
  • Logs: credentialsId and credentialsShape (persona or session) replace inquiryId, and hasSessionToken is logged for Persona only. The scrubber now also redacts deviceSessionKey. Datadog queries on @inquiryId for this SDK need the new key.
  • README, demo and doc comments: present the sheet when the order first has credentials, close it in onComplete or onCancel, and keep it closed for that order, including after a swipe dismiss. With the session flow the order leaves requires-kyc as soon as the buyer submits, before the result screen, so the old example closed the sheet too early.

Breaking change

inquiryId and provider are now String?. Both are nil for session credentials, and so is sessionToken. Code that only passes the credentials to CrossmintIdentityVerification builds unchanged. The release that ships this needs a version that says so.

Tests

  • New tests: session decode and encode with and without deviceSessionKey, which shape wins when an order carries both sets of keys, invalid shapes, id and equality, session credentials in the hosted URL, the controller exposing session credentials from an order update, scrubber redaction, and the log attributes.
  • xcodebuild test on the iPhone 17 simulator: 146 tests pass. The CheckoutDemo builds for the simulator.
  • End to end on the iPhone 17 simulator against a staging order on the new KYC UI: the controller got session credentials, the sheet opened, the flow ended verified, and the order moved to awaiting-payment. The sheet stayed open after the order left requires-kyc and closed only from onComplete.

A rotated secret reloads the hosted page, the same way a new Persona sessionToken does today. ID-658 tracks keeping the page stable across rotations.

Orders on the Crossmint KYC UI send {verificationId, clientSecret, deviceSessionKey?}
in payment.preparation.kyc. Decode and encode that shape next to the Persona one,
make the Persona-only properties optional, log the stable id and the shape instead
of the inquiry id, and redact deviceSessionKey in logs.
With the session flow the order leaves requires-kyc when the buyer submits, before
the flow shows its result. The README and demo present the sheet when the order has
new credentials and close it from onComplete or onCancel.
The README and demo presented the sheet again on a rotated clientSecret or a new
sessionToken after the buyer cancelled or swiped it away. Latch the close in the
sheet's onDismiss and stop presenting for that order, and describe the full rule
in the view's documentation.
Test that complete session keys win over Persona keys and that an incomplete
session falls back to Persona. Keep a non-string deviceSessionKey from dropping
the whole session, and log hasSessionToken only for Persona credentials, as Kotlin does.
@AngelPaella
AngelPaella marked this pull request as ready for review October 13, 2026 18:54
@vmc-crossmint

Copy link
Copy Markdown

[codex] Can we preserve the original payment.preparation.kyc JSON inside the credentials wrapper and forward it to the hosted page, while keeping the existing constructors as convenience APIs?

The new Shape enum and CodingKeys fix the two current formats, but decoding/encoding still drops unknown fields and rejects an unrecognized future shape. That leaves the hosted flow coupled to native SDK releases for credential changes. An opaque JSON-backed wrapper is feasible in Swift; reading known fields for sheet identity or allowlisted logging does not require reconstructing the transport payload from them.

Please consider a round-trip test with an extra nested field and an unfamiliar credential shape. Preserve structural JSON equality and secret-rotation detection, and keep raw credentials out of logs. This is a forward-compatibility design concern; the current PR does address today's session format.

Comment thread README.md Resolved
The README and demo kept a single kycClosed flag, so after the first close the
sheet never presented again, even for a later order that needs verification.
Remember the id of the order whose sheet closed and present again for any other
order. Key it by the order id because a verification or inquiry id can repeat
across orders.
String(describing:), interpolation and String(reflecting:) printed the
clientSecret, sessionToken and deviceSessionKey. Describe the credentials by
their shape and stable id only.
@AngelPaella

Copy link
Copy Markdown
Contributor Author

Keeping typed parsing for now, reasoning in ID-692. We'll revisit with ID-658.

@AngelPaella
AngelPaella merged commit a9d186c into main Oct 14, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants