@@ -3,23 +3,48 @@ import { z } from "zod";
33import { CheckoutSchema } from "../schemas/checkout" ;
44
55/**
6- * Schema for customer metadata values .
7- * Strings only, max 500 chars per value .
6+ * Helper to treat empty strings as undefined (not provided) .
7+ * This allows clients to pass empty strings without validation errors .
88 */
9- const CustomerMetadataValueSchema = z . string ( ) . max ( 500 ) ;
9+ const emptyStringToUndefined = z . string ( ) . transform ( ( val ) => val . trim ( ) === "" ? undefined : val ) ;
1010
1111/**
12- * Schema for customer metadata object .
13- * Max 50 keys, string values only (max 500 chars each).
14- * This is only accepted at create time (server-side), NOT at confirm time (browser-callable)
15- * to prevent malicious payers from overwriting merchant-set metadata.
12+ * Standard customer fields .
13+ * - 'name' → "Name"
14+ * - 'email' → "Email"
15+ * - 'externalId' → "External Id"
1616 */
17- const CustomerMetadataSchema = z
18- . record ( z . string ( ) , CustomerMetadataValueSchema )
19- . refine ( ( obj ) => Object . keys ( obj ) . length <= 50 , {
20- message : "Customer metadata cannot have more than 50 keys" ,
17+ export const StandardCustomerFieldSchema = z . enum ( [ "email" , "name" , "externalId" ] ) ;
18+ export type StandardCustomerField = z . infer < typeof StandardCustomerFieldSchema > ;
19+
20+ /**
21+ * Valid fields that can be required at checkout time.
22+ * - Standard fields: 'email', 'name' (checked against customer.email/name)
23+ * - Any other string is a custom field (checked against customer[field])
24+ *
25+ * @example ['email'] - require email
26+ * @example ['email', 'name'] - require both email and name
27+ * @example ['email', 'company'] - require email and company
28+ */
29+ export const CustomerFieldSchema = z . string ( ) . min ( 1 ) ;
30+ export type CustomerField = string ;
31+
32+ /**
33+ * Customer data object for checkout.
34+ * Flat structure - standard fields (name, email, externalId) plus any custom string fields.
35+ * Empty strings are treated as undefined (not provided).
36+ *
37+ * @example { name: "John", email: "john@example.com", externalId: "user_123", company: "Acme" }
38+ */
39+ export const CustomerInputSchema = z
40+ . object ( {
41+ name : emptyStringToUndefined . optional ( ) ,
42+ email : z . string ( ) . email ( ) . optional ( ) ,
43+ externalId : emptyStringToUndefined . optional ( ) ,
2144 } )
22- . optional ( ) ;
45+ . catchall ( z . string ( ) ) ;
46+
47+ export type CustomerInput = z . infer < typeof CustomerInputSchema > ;
2348
2449export const CreateCheckoutInputSchema = z . object ( {
2550 nodeId : z . string ( ) ,
@@ -29,25 +54,39 @@ export const CreateCheckoutInputSchema = z.object({
2954 successUrl : z . string ( ) . optional ( ) ,
3055 allowDiscountCodes : z . boolean ( ) . optional ( ) ,
3156 metadata : z . record ( z . string ( ) , z . any ( ) ) . optional ( ) ,
32- customerMetadata : CustomerMetadataSchema ,
33- customerName : z . string ( ) . nonempty ( ) . optional ( ) ,
34- customerEmail : z . string ( ) . email ( ) . optional ( ) ,
35- customerIpAddress : z . string ( ) . ip ( ) . optional ( ) ,
36- customerExternalId : z . string ( ) . nonempty ( ) . optional ( ) ,
37- requireCustomerFields : z
38- . object ( {
39- customerName : z . boolean ( ) . optional ( ) ,
40- customerEmail : z . boolean ( ) . optional ( ) ,
41- } )
42- . optional ( ) ,
57+ /**
58+ * Customer data for this checkout.
59+ */
60+ customer : CustomerInputSchema . optional ( ) ,
61+ /**
62+ * Array of customer fields to require at checkout.
63+ * If a field is listed here and not provided, the checkout UI will prompt for it.
64+ * @example ['email'] - require email
65+ * @example ['email', 'name'] - require both
66+ */
67+ requireCustomerData : z . array ( CustomerFieldSchema ) . optional ( ) ,
4368} ) ;
4469
70+ /**
71+ * Customer data for confirm.
72+ * Accepts standard fields (name, email, externalId) plus any custom fields from the form.
73+ * Empty strings are treated as undefined (not provided).
74+ */
75+ export const CustomerConfirmInputSchema = z
76+ . object ( {
77+ name : emptyStringToUndefined . optional ( ) ,
78+ email : z . string ( ) . email ( ) . optional ( ) ,
79+ externalId : emptyStringToUndefined . optional ( ) ,
80+ } )
81+ . catchall ( z . string ( ) ) ;
82+
4583export const ConfirmCheckoutInputSchema = z . object ( {
4684 checkoutId : z . string ( ) ,
47- customerName : z . string ( ) . nonempty ( ) . optional ( ) ,
48- customerEmail : z . string ( ) . email ( ) . optional ( ) ,
49- customerIpAddress : z . string ( ) . ip ( ) . optional ( ) ,
50- customerExternalId : z . string ( ) . nonempty ( ) . optional ( ) ,
85+ /**
86+ * Customer data provided at confirm time.
87+ * Note: metadata is NOT accepted here (browser-callable) to prevent tampering.
88+ */
89+ customer : CustomerConfirmInputSchema . optional ( ) ,
5190 products : z
5291 . array (
5392 z . object ( {
0 commit comments