diff --git a/.changeset/pmm-mode-and-new-card.md b/.changeset/pmm-mode-and-new-card.md
new file mode 100644
index 000000000..c16a249e7
--- /dev/null
+++ b/.changeset/pmm-mode-and-new-card.md
@@ -0,0 +1,10 @@
+---
+"@crossmint/client-sdk-base": minor
+"@crossmint/client-sdk-react-ui": minor
+---
+
+`CrossmintPaymentMethodManagement`: add `mode` (`"select-or-add" | "add-only"`) and `allowedPaymentMethodTypes` props. Make `jwt` optional — when omitted, the component tokenizes the card without persisting a saved payment method and emits a `card-token` selection event.
+
+Introduces `CrossmintNewCard`, a tokenize-only wrapper around `CrossmintPaymentMethodManagement` for use cases that don't need a signed-in user (e.g., embedded checkout).
+
+The `onPaymentMethodSelected` callback now receives a discriminated union: `{ type: "card", paymentMethod }` when a saved method is selected/created, or `{ type: "card-token", cardToken }` when tokenizing without JWT.
diff --git a/apps/payments/nextjs/app/new-card/page.tsx b/apps/payments/nextjs/app/new-card/page.tsx
new file mode 100644
index 000000000..82802b67a
--- /dev/null
+++ b/apps/payments/nextjs/app/new-card/page.tsx
@@ -0,0 +1,36 @@
+"use client";
+
+import { CrossmintProvider, CrossmintNewCard } from "@crossmint/client-sdk-react-ui";
+
+export default function NewCardPage() {
+ return (
+
+
+
+ {
+ console.log("card tokenized", cardToken);
+ }}
+ />
+
+
+
+ );
+}
diff --git a/packages/client/base/src/types/payment-method-management/CrossmintPaymentMethodManagementProps.ts b/packages/client/base/src/types/payment-method-management/CrossmintPaymentMethodManagementProps.ts
index a666a2b3c..444104041 100644
--- a/packages/client/base/src/types/payment-method-management/CrossmintPaymentMethodManagementProps.ts
+++ b/packages/client/base/src/types/payment-method-management/CrossmintPaymentMethodManagementProps.ts
@@ -1,12 +1,17 @@
import type { EmbeddedCheckoutV3Appearance } from "../embed";
+export type PaymentMethodManagementMode = "select-or-add" | "add-only";
+export type PaymentMethodManagementAllowedType = "card";
+
export interface CrossmintPaymentMethodManagementProps {
- jwt: string;
- appearance?: PaymentMethodManagementAppearance;
+ jwt?: string;
+ mode?: PaymentMethodManagementMode;
+ allowedPaymentMethodTypes?: PaymentMethodManagementAllowedType[];
+ appearance?: EmbeddedCheckoutV3Appearance;
onPaymentMethodSelected?: (paymentMethod: CrossmintPaymentMethod) => void | Promise;
}
-export type CrossmintPaymentMethod = {
+export type CrossmintCardPaymentMethod = {
type: "card";
paymentMethodId: string;
card: {
@@ -29,7 +34,13 @@ export type CrossmintPaymentMethod = {
};
};
-export type PaymentMethodManagementAppearance = {
- fonts?: EmbeddedCheckoutV3Appearance["fonts"];
- variables?: EmbeddedCheckoutV3Appearance["variables"];
+export type CrossmintCardToken = {
+ id: string;
+ billing?: {
+ name?: string;
+ };
};
+
+export type CrossmintPaymentMethod =
+ | { type: "card"; paymentMethod: CrossmintCardPaymentMethod }
+ | { type: "card-token"; cardToken: CrossmintCardToken };
diff --git a/packages/client/base/src/types/payment-method-management/events/incoming.ts b/packages/client/base/src/types/payment-method-management/events/incoming.ts
index 8cdd5489f..67ff68da7 100644
--- a/packages/client/base/src/types/payment-method-management/events/incoming.ts
+++ b/packages/client/base/src/types/payment-method-management/events/incoming.ts
@@ -1,9 +1,47 @@
import { z } from "zod";
+const cardTokenSelectedSchema = z.object({
+ type: z.literal("card-token"),
+ cardToken: z.object({
+ id: z.string(),
+ billing: z
+ .object({
+ name: z.string().optional(),
+ })
+ .optional(),
+ }),
+});
+
+const cardPaymentMethodSelectedSchema = z.object({
+ type: z.literal("card"),
+ paymentMethod: z.object({
+ type: z.literal("card"),
+ paymentMethodId: z.string(),
+ card: z.object({
+ source: z.object({
+ type: z.literal("basis-theory-token"),
+ id: z.string(),
+ }),
+ brand: z.string(),
+ last4: z.string(),
+ expiration: z.object({
+ month: z.string(),
+ year: z.string(),
+ }),
+ }),
+ default: z.boolean().optional(),
+ display: z
+ .object({
+ imageUrl: z.string().optional(),
+ })
+ .optional(),
+ }),
+});
+
export const paymentMethodManagementIncomingEvents = {
"ui:height.changed": z.object({
height: z.number(),
}),
- "payment-method:selected": z.any(),
+ "payment-method:selected": z.discriminatedUnion("type", [cardPaymentMethodSelectedSchema, cardTokenSelectedSchema]),
};
export type PaymentMethodManagementIncomingEventMap = typeof paymentMethodManagementIncomingEvents;
diff --git a/packages/client/ui/react-ui/src/components/card-management/CrossmintNewCard.tsx b/packages/client/ui/react-ui/src/components/card-management/CrossmintNewCard.tsx
new file mode 100644
index 000000000..f140b64de
--- /dev/null
+++ b/packages/client/ui/react-ui/src/components/card-management/CrossmintNewCard.tsx
@@ -0,0 +1,30 @@
+import type {
+ CrossmintCardPaymentMethod,
+ CrossmintCardToken,
+ EmbeddedCheckoutV3Appearance,
+} from "@crossmint/client-sdk-base";
+import { CrossmintPaymentMethodManagement } from "./CrossmintPaymentMethodManagement";
+
+export interface CrossmintNewCardProps {
+ jwt?: string;
+ appearance?: EmbeddedCheckoutV3Appearance;
+ onCardTokenized?: (cardToken: CrossmintCardToken) => void | Promise;
+ onPaymentMethodAdded?: (paymentMethod: CrossmintCardPaymentMethod) => void | Promise;
+}
+
+export function CrossmintNewCard(props: CrossmintNewCardProps) {
+ return (
+ {
+ if (result.type === "card-token") {
+ return props.onCardTokenized?.(result.cardToken);
+ }
+ return props.onPaymentMethodAdded?.(result.paymentMethod);
+ }}
+ />
+ );
+}
diff --git a/packages/client/ui/react-ui/src/components/card-management/index.ts b/packages/client/ui/react-ui/src/components/card-management/index.ts
index ea4d02023..f1f46bba9 100644
--- a/packages/client/ui/react-ui/src/components/card-management/index.ts
+++ b/packages/client/ui/react-ui/src/components/card-management/index.ts
@@ -1 +1,2 @@
export * from "./CrossmintPaymentMethodManagement";
+export * from "./CrossmintNewCard";