feat: PMM mode/allowedPaymentMethodTypes + NewCard wrapper#1807
feat: PMM mode/allowedPaymentMethodTypes + NewCard wrapper#1807VeniaminC wants to merge 3 commits into
Conversation
…d wrapper Makes jwt optional, adds mode and allowedPaymentMethodTypes props, and introduces CrossmintNewCard as a tokenize-only wrapper. onPaymentMethodSelected now emits a discriminated union distinguishing saved-method selection from card tokenization. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: d8e10ce The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Prompt To Fix All With AIThis is a comment left during a code review.
Path: packages/client/base/src/types/payment-method-management/events/incoming.ts
Line: 15-23
Comment:
**Zod schema inferred type won't satisfy `CrossmintCardPaymentMethod`**
`cardPaymentMethodSelectedSchema` only declares `type` and `paymentMethodId` inside `paymentMethod`, relying on `.passthrough()` to preserve the remaining runtime fields. However, `.passthrough()` does not add the undeclared fields to Zod's inferred TypeScript output type — it only prevents them from being stripped at runtime. The inferred type for `paymentMethod` is therefore `{ type: "card"; paymentMethodId: string }`, which is missing the required `card: { source, brand, last4, expiration }` field of `CrossmintCardPaymentMethod`.
This causes a structural type mismatch: when the validated event data is passed to `props.onPaymentMethodSelected?.(data)` in `CrossmintPaymentMethodManagementIFrame.tsx`, TypeScript sees the Zod-inferred union rather than `CrossmintPaymentMethod`, and they are not mutually assignable. This will surface as a compile error on the unchanged IFrame component.
To fix, either extend the schema to include all required `CrossmintCardPaymentMethod` fields, or cast the validated data to `CrossmintPaymentMethod` at the call-site in the IFrame component.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat: PaymentMethodManagement mode/allow..." | Re-trigger Greptile |
| const cardPaymentMethodSelectedSchema = z.object({ | ||
| type: z.literal("card"), | ||
| paymentMethod: z | ||
| .object({ | ||
| type: z.literal("card"), | ||
| paymentMethodId: z.string(), | ||
| }) | ||
| .passthrough(), | ||
| }); |
There was a problem hiding this comment.
Zod schema inferred type won't satisfy
CrossmintCardPaymentMethod
cardPaymentMethodSelectedSchema only declares type and paymentMethodId inside paymentMethod, relying on .passthrough() to preserve the remaining runtime fields. However, .passthrough() does not add the undeclared fields to Zod's inferred TypeScript output type — it only prevents them from being stripped at runtime. The inferred type for paymentMethod is therefore { type: "card"; paymentMethodId: string }, which is missing the required card: { source, brand, last4, expiration } field of CrossmintCardPaymentMethod.
This causes a structural type mismatch: when the validated event data is passed to props.onPaymentMethodSelected?.(data) in CrossmintPaymentMethodManagementIFrame.tsx, TypeScript sees the Zod-inferred union rather than CrossmintPaymentMethod, and they are not mutually assignable. This will surface as a compile error on the unchanged IFrame component.
To fix, either extend the schema to include all required CrossmintCardPaymentMethod fields, or cast the validated data to CrossmintPaymentMethod at the call-site in the IFrame component.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/client/base/src/types/payment-method-management/events/incoming.ts
Line: 15-23
Comment:
**Zod schema inferred type won't satisfy `CrossmintCardPaymentMethod`**
`cardPaymentMethodSelectedSchema` only declares `type` and `paymentMethodId` inside `paymentMethod`, relying on `.passthrough()` to preserve the remaining runtime fields. However, `.passthrough()` does not add the undeclared fields to Zod's inferred TypeScript output type — it only prevents them from being stripped at runtime. The inferred type for `paymentMethod` is therefore `{ type: "card"; paymentMethodId: string }`, which is missing the required `card: { source, brand, last4, expiration }` field of `CrossmintCardPaymentMethod`.
This causes a structural type mismatch: when the validated event data is passed to `props.onPaymentMethodSelected?.(data)` in `CrossmintPaymentMethodManagementIFrame.tsx`, TypeScript sees the Zod-inferred union rather than `CrossmintPaymentMethod`, and they are not mutually assignable. This will surface as a compile error on the unchanged IFrame component.
To fix, either extend the schema to include all required `CrossmintCardPaymentMethod` fields, or cast the validated data to `CrossmintPaymentMethod` at the call-site in the IFrame component.
How can I resolve this? If you propose a fix, please make it concise.- Adds apps/payments/nextjs/app/new-card exercising CrossmintNewCard - Tightens payment-method:selected schema to validate the full card shape, so the TS callback type matches CrossmintPaymentMethod Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Prompt To Fix All With AIThis is a comment left during a code review.
Path: .changeset/pmm-mode-and-new-card.md
Line: 1-10
Comment:
**Removed public export not mentioned in changeset**
`PaymentMethodManagementAppearance` was a named export from `@crossmint/client-sdk-base` (via `CrossmintPaymentMethodManagementProps.ts` → `types/payment-method-management/index.ts`) and has been deleted entirely in this PR. The changeset describes the appearance prop as widened to `EmbeddedCheckoutV3Appearance`, but any consumer who imported `PaymentMethodManagementAppearance` as a type will get a compile error on upgrade. Consider adding a line to the changeset noting the removal, e.g. "The `PaymentMethodManagementAppearance` type has been removed — use `EmbeddedCheckoutV3Appearance` directly."
How can I resolve this? If you propose a fix, please make it concise.Reviews (2): Last reviewed commit: "demo: add /new-card page and tighten inc..." | Re-trigger Greptile |
When jwt is provided the card is both tokenized and saved as a UserPaymentMethod (onPaymentMethodAdded fires); without a jwt only the BT token is emitted (onCardTokenized fires). Either or both callbacks can be wired independently. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Reviews (3): Last reviewed commit: "feat: CrossmintNewCard accepts optional ..." | Re-trigger Greptile |
| appearance?: PaymentMethodManagementAppearance; | ||
| jwt?: string; | ||
| mode?: PaymentMethodManagementMode; | ||
| allowedPaymentMethodTypes?: PaymentMethodManagementAllowedType[]; |
There was a problem hiding this comment.
can u make this an object instead
i think its like:
allowedPaymentMethodTypes?: Partial<Record<PaymentMethodManagementAllowedType, boolean>>
| export interface CrossmintPaymentMethodManagementProps { | ||
| jwt: string; | ||
| appearance?: PaymentMethodManagementAppearance; | ||
| jwt?: string; |
There was a problem hiding this comment.
for now lets just keep jwt as required, we will tackl non-authenticated cases later
| export type CrossmintCardToken = { | ||
| id: string; | ||
| billing?: { | ||
| name?: string; | ||
| }; | ||
| }; | ||
|
|
||
| export type CrossmintPaymentMethod = | ||
| | { type: "card"; paymentMethod: CrossmintCardPaymentMethod } | ||
| | { type: "card-token"; cardToken: CrossmintCardToken }; |
There was a problem hiding this comment.
since jwt required, remove card-token case
| export interface CrossmintNewCardProps { | ||
| jwt?: string; | ||
| appearance?: EmbeddedCheckoutV3Appearance; | ||
| onCardTokenized?: (cardToken: CrossmintCardToken) => void | Promise<void>; |
| import { CrossmintPaymentMethodManagement } from "./CrossmintPaymentMethodManagement"; | ||
|
|
||
| export interface CrossmintNewCardProps { | ||
| jwt?: string; |
| jwt?: string; | ||
| appearance?: EmbeddedCheckoutV3Appearance; | ||
| onCardTokenized?: (cardToken: CrossmintCardToken) => void | Promise<void>; | ||
| onPaymentMethodAdded?: (paymentMethod: CrossmintCardPaymentMethod) => void | Promise<void>; |
| if (result.type === "card-token") { | ||
| return props.onCardTokenized?.(result.cardToken); | ||
| } |
Description
Updates
CrossmintPaymentMethodManagementto match the new iframe contract from crossbit-main#24854, and addsCrossmintNewCardas a thin wrapper per the payments team's Option 2 (one underlying component, multiple named SDK entry points).Props changes on
CrossmintPaymentMethodManagement:jwtis now optional. When omitted, the component tokenizes a card client-side without saving aUserPaymentMethod.mode: "select-or-add" | "add-only"— replaces the previousappearance.rules.SavedPaymentMethodsSection.display = "hidden"workaround.allowedPaymentMethodTypes: ("card")[]— future-proofs for bank-account etc.appearancewidened to the fullEmbeddedCheckoutV3Appearanceso consumers can passrules.PrimaryButtonfor styling.onPaymentMethodSelectednow receives a discriminated union:{ type: "card", paymentMethod }— a saved method was selected/created{ type: "card-token", cardToken: { id, billing? } }— tokenize-only path (no JWT)New component
CrossmintNewCard:Internally just renders
<CrossmintPaymentMethodManagement mode="add-only" allowedPaymentMethodTypes={["card"]} />and narrows the callback toonCardTokenized.Test plan
apps/payments/nextjsexisting PaymentMethodManagement page still loads (backwards compatible, defaultmodeisselect-or-add)CrossmintNewCardin isolation, tokenize a card, confirmonCardTokenizedfires with{ id, billing? }CrossmintPaymentMethodManagementwithmode="add-only"+ JWT, confirm Continue creates a saved payment method andonPaymentMethodSelectedreceives{ type: "card", paymentMethod }CrossmintPaymentMethodManagementwithappearance.rules.PrimaryButton.colors.background+borderRadiusand verify styling appliespnpm --filter @crossmint/client-sdk-base --filter @crossmint/client-sdk-react-ui tsc --noEmitPackage updates
@crossmint/client-sdk-base: minor (new props, discriminated union callback — breaking-ish but pre-1.0 style additive shape)@crossmint/client-sdk-react-ui: minor (newCrossmintNewCardexport)Changeset:
.changeset/pmm-mode-and-new-card.md🤖 Generated with Claude Code