diff --git a/.changeset/google-loyalty-design.md b/.changeset/google-loyalty-design.md new file mode 100644 index 0000000..dfc358c --- /dev/null +++ b/.changeset/google-loyalty-design.md @@ -0,0 +1,5 @@ +--- +'@passmint/node': minor +--- + +Type `design.google` on templates: `loyaltyPoints` and `secondaryLoyaltyPoints` (`{ fieldKey, label }`) show a template field as the Google Wallet loyalty balance, and `accountNameFieldKey` picks the field shown as the card holder's name. New exported types: `TemplateDesignGoogle`, and `TemplateLocation` (already used by `TemplateDesign.locations`, now importable by name). diff --git a/README.md b/README.md index 35aae6f..628470e 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ const { data: scans } = await passmint.passes.redemptions(pass.id, { limit: 20 } for (const scan of scans) console.log(scan.decision, scan.reason, scan.scanned_at) ``` -Templates take a `redemptionPolicy` (`{ mode: 'single_use' | 'reusable' | 'count', maxUses? }`) and `requireDownloadLink` on `create` and `update`. Template responses include `warnings` when the saved design breaks a recommended rule, and template designs accept `issuerName`, `locations` and `relevantDate`. +Templates take a `redemptionPolicy` (`{ mode: 'single_use' | 'reusable' | 'count', maxUses? }`) and `requireDownloadLink` on `create` and `update`. Template responses include `warnings` when the saved design breaks a recommended rule, and template designs accept `issuerName`, `locations`, `relevantDate` and `google`. On storeCard templates, `google` maps template fields to the Google Wallet loyalty card: `loyaltyPoints` and `secondaryLoyaltyPoints` (`{ fieldKey, label }`) show a field as the native balance, and `accountNameFieldKey` picks the card holder name field. #### Per-pass images diff --git a/openapi.json b/openapi.json index 8428b2a..08256b8 100644 --- a/openapi.json +++ b/openapi.json @@ -430,6 +430,72 @@ ], "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2})$", "description": "ISO 8601 with offset. Apple Wallet surfaces the pass around this time. Apple only." + }, + "google": { + "type": [ + "object", + "null" + ], + "properties": { + "loyaltyPoints": { + "type": [ + "object", + "null" + ], + "properties": { + "fieldKey": { + "type": "string", + "maxLength": 48, + "pattern": "^[A-Za-z0-9_]+$", + "description": "Key of a field declared in this design. Its value on each pass (or the field's default) is the balance, sent to Google as text." + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "Shown next to the balance in Google Wallet, e.g. 'Stamps'." + } + }, + "required": [ + "fieldKey", + "label" + ] + }, + "secondaryLoyaltyPoints": { + "type": [ + "object", + "null" + ], + "properties": { + "fieldKey": { + "type": "string", + "maxLength": 48, + "pattern": "^[A-Za-z0-9_]+$", + "description": "Key of a field declared in this design. Its value on each pass (or the field's default) is the balance, sent to Google as text." + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "Shown next to the balance in Google Wallet, e.g. 'Stamps'." + } + }, + "required": [ + "fieldKey", + "label" + ] + }, + "accountNameFieldKey": { + "type": [ + "string", + "null" + ], + "maxLength": 48, + "pattern": "^[A-Za-z0-9_]+$", + "description": "Key of a field whose value is the card holder's name in Google Wallet. Omit to keep showing the description there, as cards always have." + } + }, + "description": "Google Wallet only, storeCard templates only: show template fields as the loyalty card's native balance and holder name. Apple Wallet is unaffected." } }, "required": [ @@ -872,6 +938,72 @@ ], "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2})$", "description": "ISO 8601 with offset. Apple Wallet surfaces the pass around this time. Apple only." + }, + "google": { + "type": [ + "object", + "null" + ], + "properties": { + "loyaltyPoints": { + "type": [ + "object", + "null" + ], + "properties": { + "fieldKey": { + "type": "string", + "maxLength": 48, + "pattern": "^[A-Za-z0-9_]+$", + "description": "Key of a field declared in this design. Its value on each pass (or the field's default) is the balance, sent to Google as text." + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "Shown next to the balance in Google Wallet, e.g. 'Stamps'." + } + }, + "required": [ + "fieldKey", + "label" + ] + }, + "secondaryLoyaltyPoints": { + "type": [ + "object", + "null" + ], + "properties": { + "fieldKey": { + "type": "string", + "maxLength": 48, + "pattern": "^[A-Za-z0-9_]+$", + "description": "Key of a field declared in this design. Its value on each pass (or the field's default) is the balance, sent to Google as text." + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "Shown next to the balance in Google Wallet, e.g. 'Stamps'." + } + }, + "required": [ + "fieldKey", + "label" + ] + }, + "accountNameFieldKey": { + "type": [ + "string", + "null" + ], + "maxLength": 48, + "pattern": "^[A-Za-z0-9_]+$", + "description": "Key of a field whose value is the card holder's name in Google Wallet. Omit to keep showing the description there, as cards always have." + } + }, + "description": "Google Wallet only, storeCard templates only: show template fields as the loyalty card's native balance and holder name. Apple Wallet is unaffected." } }, "required": [ @@ -3229,6 +3361,57 @@ "string", "null" ] + }, + "google": { + "type": [ + "object", + "null" + ], + "properties": { + "loyaltyPoints": { + "type": [ + "object", + "null" + ], + "properties": { + "fieldKey": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "fieldKey", + "label" + ] + }, + "secondaryLoyaltyPoints": { + "type": [ + "object", + "null" + ], + "properties": { + "fieldKey": { + "type": "string" + }, + "label": { + "type": "string" + } + }, + "required": [ + "fieldKey", + "label" + ] + }, + "accountNameFieldKey": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": {} } }, "additionalProperties": {}, diff --git a/src/generated/openapi.ts b/src/generated/openapi.ts index bdf0f3e..0a3d7fb 100644 --- a/src/generated/openapi.ts +++ b/src/generated/openapi.ts @@ -2827,6 +2827,23 @@ export interface components { }[] | null; /** @description ISO 8601 with offset. Apple Wallet surfaces the pass around this time. Apple only. */ relevantDate?: string | null; + /** @description Google Wallet only, storeCard templates only: show template fields as the loyalty card's native balance and holder name. Apple Wallet is unaffected. */ + google?: { + loyaltyPoints?: { + /** @description Key of a field declared in this design. Its value on each pass (or the field's default) is the balance, sent to Google as text. */ + fieldKey: string; + /** @description Shown next to the balance in Google Wallet, e.g. 'Stamps'. */ + label: string; + } | null; + secondaryLoyaltyPoints?: { + /** @description Key of a field declared in this design. Its value on each pass (or the field's default) is the balance, sent to Google as text. */ + fieldKey: string; + /** @description Shown next to the balance in Google Wallet, e.g. 'Stamps'. */ + label: string; + } | null; + /** @description Key of a field whose value is the card holder's name in Google Wallet. Omit to keep showing the description there, as cards always have. */ + accountNameFieldKey?: string | null; + } | null; }; platforms?: ("apple" | "google")[]; starter_template_id?: string; @@ -2926,6 +2943,23 @@ export interface components { }[] | null; /** @description ISO 8601 with offset. Apple Wallet surfaces the pass around this time. Apple only. */ relevantDate?: string | null; + /** @description Google Wallet only, storeCard templates only: show template fields as the loyalty card's native balance and holder name. Apple Wallet is unaffected. */ + google?: { + loyaltyPoints?: { + /** @description Key of a field declared in this design. Its value on each pass (or the field's default) is the balance, sent to Google as text. */ + fieldKey: string; + /** @description Shown next to the balance in Google Wallet, e.g. 'Stamps'. */ + label: string; + } | null; + secondaryLoyaltyPoints?: { + /** @description Key of a field declared in this design. Its value on each pass (or the field's default) is the balance, sent to Google as text. */ + fieldKey: string; + /** @description Shown next to the balance in Google Wallet, e.g. 'Stamps'. */ + label: string; + } | null; + /** @description Key of a field whose value is the card holder's name in Google Wallet. Omit to keep showing the description there, as cards always have. */ + accountNameFieldKey?: string | null; + } | null; }; archived?: boolean; platforms?: ("apple" | "google")[]; @@ -3493,6 +3527,19 @@ export interface components { [key: string]: unknown; }[] | null; relevantDate?: string | null; + google?: ({ + loyaltyPoints?: { + fieldKey: string; + label: string; + } | null; + secondaryLoyaltyPoints?: { + fieldKey: string; + label: string; + } | null; + accountNameFieldKey?: string | null; + } & { + [key: string]: unknown; + }) | null; } & { [key: string]: unknown; }; diff --git a/src/index.ts b/src/index.ts index 3beb2b4..e999889 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,10 +58,12 @@ export type { RequestOptions, Template, TemplateDesign, + TemplateDesignGoogle, TemplateField, TemplateImage, TemplateImageOptions, TemplateImageSlot, + TemplateLocation, TemplateType, UpdatePassParams, UpdateTemplateParams, diff --git a/src/types.ts b/src/types.ts index a9b64f9..92930ab 100644 --- a/src/types.ts +++ b/src/types.ts @@ -45,6 +45,11 @@ export type TemplateLocation = NonNullable< NonNullable['locations'] >[number] +/** Google Wallet loyalty mapping for storeCard templates, derived from the spec's design input. */ +export type TemplateDesignGoogle = NonNullable< + NonNullable['google'] +> + export interface TemplateDesign { description: string logoText: string | null @@ -72,6 +77,12 @@ export interface TemplateDesign { locations?: TemplateLocation[] | null /** ISO 8601 date the pass becomes relevant (lock-screen surfacing). */ relevantDate?: string | null + /** + * Google Wallet only, storeCard only: show template fields as the loyalty card's native + * balance (`loyaltyPoints`, `secondaryLoyaltyPoints`) and holder name + * (`accountNameFieldKey`). `null` clears it. + */ + google?: TemplateDesignGoogle | null } /** diff --git a/test/types.test-d.ts b/test/types.test-d.ts index 2253360..594a92b 100644 --- a/test/types.test-d.ts +++ b/test/types.test-d.ts @@ -200,3 +200,21 @@ describe('TemplateDesign.locations', () => { expectTypeOf<{ longitude: number }>().not.toExtend() }) }) + +describe('TemplateDesign.google', () => { + it('accepts the Google loyalty mapping', () => { + const design: Pick = { + google: { + loyaltyPoints: { fieldKey: 'count', label: 'Stamps' }, + secondaryLoyaltyPoints: { fieldKey: 'nextReward', label: 'Reward' }, + accountNameFieldKey: 'customerName', + }, + } + expectTypeOf(design.google?.loyaltyPoints?.fieldKey).toEqualTypeOf() + const bad: Pick = { + // @ts-expect-error label must be a string + google: { loyaltyPoints: { fieldKey: 'count', label: 1 } }, + } + void bad + }) +})