@amos.com/amos-js 0.9.8 → 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/apple-pay.ts CHANGED
@@ -10,10 +10,12 @@ import {
10
10
  sendParentReadyMessage,
11
11
  updateAmount as sendUpdateAmount,
12
12
  updateAppearance as sendUpdateAppearance,
13
+ updateApplePayButton as sendUpdateApplePayButton,
13
14
  updateMerchantName as sendUpdateMerchantName,
14
15
  } from "./messaging";
15
16
  import {
16
17
  type Appearance,
18
+ type ApplePayButtonElementProps,
17
19
  type ConfirmationResult,
18
20
  createMessage,
19
21
  type Message,
@@ -36,7 +38,7 @@ export function getApplePayButtonInitialHeight(): string {
36
38
  /**
37
39
  * Options accepted by {@link attachApplePayButtonListeners}.
38
40
  */
39
- export type ApplePayButtonListenerOptions = {
41
+ export type ApplePayButtonListenerOptions = ApplePayButtonElementProps & {
40
42
  /** The amount of the payment, in the same format passed in props. */
41
43
  amount: string;
42
44
  /** A user-visible merchant name. */
@@ -83,7 +85,9 @@ export type ApplePayButtonController = {
83
85
  /**
84
86
  * Update one or more listener options without re-attaching the
85
87
  * message listener. Pass `amount` or `merchantName` to push the new
86
- * value into the iframe; pass `appearance` to update theme variables.
88
+ * value into the iframe; pass `appearance` to update theme variables;
89
+ * pass `buttonstyle`, `type`, `locale`, or `style` to restyle the
90
+ * Apple Pay button.
87
91
  */
88
92
  update: (patch: Partial<ApplePayButtonListenerOptions>) => void;
89
93
  /**
@@ -126,6 +130,10 @@ export function attachApplePayButtonListeners(
126
130
  sendUpdateMerchantName({ iframe, merchantName: current.merchantName });
127
131
  }
128
132
 
133
+ function pushButtonProps() {
134
+ sendUpdateApplePayButton({ iframe, props: current });
135
+ }
136
+
129
137
  function handleMessage(event: MessageEvent<Message>) {
130
138
  // Ignore messages from other frames / windows.
131
139
  if (event.source !== iframe.contentWindow) {
@@ -138,6 +146,7 @@ export function attachApplePayButtonListeners(
138
146
  sendUpdateAppearance({ iframe, appearance: current.appearance });
139
147
  pushAmount();
140
148
  pushMerchantName();
149
+ pushButtonProps();
141
150
  break;
142
151
 
143
152
  case "UPDATE_HEIGHT":
@@ -199,6 +208,11 @@ export function attachApplePayButtonListeners(
199
208
  const hadAppearance = "appearance" in patch;
200
209
  const hadAmount = "amount" in patch;
201
210
  const hadMerchantName = "merchantName" in patch;
211
+ const hadButtonProps =
212
+ "buttonstyle" in patch ||
213
+ "type" in patch ||
214
+ "locale" in patch ||
215
+ "style" in patch;
202
216
  current = { ...current, ...patch };
203
217
  if (hadAppearance) {
204
218
  sendUpdateAppearance({ iframe, appearance: current.appearance });
@@ -209,6 +223,9 @@ export function attachApplePayButtonListeners(
209
223
  if (hadMerchantName) {
210
224
  pushMerchantName();
211
225
  }
226
+ if (hadButtonProps) {
227
+ pushButtonProps();
228
+ }
212
229
  },
213
230
  destroy() {
214
231
  window.removeEventListener("message", handleMessage);
package/src/google-pay.ts CHANGED
@@ -7,9 +7,15 @@ import {
7
7
  sendParentReadyMessage,
8
8
  updateAmount as sendUpdateAmount,
9
9
  updateAppearance as sendUpdateAppearance,
10
+ updateGooglePayButton as sendUpdateGooglePayButton,
10
11
  updateMerchantName as sendUpdateMerchantName,
11
12
  } from "./messaging";
12
- import type { Appearance, ConfirmationResult, Message } from "./types";
13
+ import type {
14
+ Appearance,
15
+ ConfirmationResult,
16
+ GooglePayButtonElementProps,
17
+ Message,
18
+ } from "./types";
13
19
 
14
20
  /**
15
21
  * Build the iframe `src` URL for the embedded Google Pay button.
@@ -28,7 +34,7 @@ export function getGooglePayButtonInitialHeight(): string {
28
34
  /**
29
35
  * Options accepted by {@link attachGooglePayButtonListeners}.
30
36
  */
31
- export type GooglePayButtonListenerOptions = {
37
+ export type GooglePayButtonListenerOptions = GooglePayButtonElementProps & {
32
38
  /** The amount of the payment, in the same format passed in props. */
33
39
  amount: string;
34
40
  /** A user-visible merchant name. */
@@ -75,7 +81,10 @@ export type GooglePayButtonController = {
75
81
  /**
76
82
  * Update one or more listener options without re-attaching the
77
83
  * message listener. Pass `amount` or `merchantName` to push the new
78
- * value into the iframe; pass `appearance` to update theme variables.
84
+ * value into the iframe; pass `appearance` to update theme variables;
85
+ * pass `buttonType`, `buttonColor`, `buttonRadius`, `buttonSizeMode`,
86
+ * `buttonLocale`, `buttonBorderType`, or `style` to restyle the
87
+ * Google Pay button.
79
88
  */
80
89
  update: (patch: Partial<GooglePayButtonListenerOptions>) => void;
81
90
  /**
@@ -106,6 +115,10 @@ export function attachGooglePayButtonListeners(
106
115
  sendUpdateMerchantName({ iframe, merchantName: current.merchantName });
107
116
  }
108
117
 
118
+ function pushButtonProps() {
119
+ sendUpdateGooglePayButton({ iframe, props: current });
120
+ }
121
+
109
122
  function handleMessage(event: MessageEvent<Message>) {
110
123
  if (event.source !== iframe.contentWindow) {
111
124
  return;
@@ -117,6 +130,7 @@ export function attachGooglePayButtonListeners(
117
130
  sendUpdateAppearance({ iframe, appearance: current.appearance });
118
131
  pushAmount();
119
132
  pushMerchantName();
133
+ pushButtonProps();
120
134
  break;
121
135
 
122
136
  case "UPDATE_HEIGHT":
@@ -163,6 +177,14 @@ export function attachGooglePayButtonListeners(
163
177
  const hadAppearance = "appearance" in patch;
164
178
  const hadAmount = "amount" in patch;
165
179
  const hadMerchantName = "merchantName" in patch;
180
+ const hadButtonProps =
181
+ "buttonType" in patch ||
182
+ "buttonColor" in patch ||
183
+ "buttonRadius" in patch ||
184
+ "buttonSizeMode" in patch ||
185
+ "buttonLocale" in patch ||
186
+ "buttonBorderType" in patch ||
187
+ "style" in patch;
166
188
  current = { ...current, ...patch };
167
189
  if (hadAppearance) {
168
190
  sendUpdateAppearance({ iframe, appearance: current.appearance });
@@ -173,6 +195,9 @@ export function attachGooglePayButtonListeners(
173
195
  if (hadMerchantName) {
174
196
  pushMerchantName();
175
197
  }
198
+ if (hadButtonProps) {
199
+ pushButtonProps();
200
+ }
176
201
  },
177
202
  destroy() {
178
203
  window.removeEventListener("message", handleMessage);
package/src/index.ts CHANGED
@@ -32,6 +32,8 @@ export {
32
32
  sendParentReadyMessage,
33
33
  updateAmount,
34
34
  updateAppearance,
35
+ updateApplePayButton,
36
+ updateGooglePayButton,
35
37
  updateMerchantName,
36
38
  validateForm,
37
39
  } from "./messaging";
@@ -66,9 +68,19 @@ export {
66
68
  export type {
67
69
  Appearance,
68
70
  AppearanceLabels,
71
+ ApplePayButtonElementProps,
72
+ ApplePayButtonStyle,
73
+ ApplePayButtonType,
69
74
  ConfirmationIncompleteReason,
70
75
  ConfirmationResult,
76
+ GooglePayButtonElementProps,
71
77
  Message,
72
78
  ThemeVariable,
79
+ WalletButtonStyle,
80
+ } from "./types";
81
+ export {
82
+ createMessage,
83
+ pickApplePayButtonElementProps,
84
+ pickGooglePayButtonElementProps,
85
+ serializeWalletButtonStyle,
73
86
  } from "./types";
74
- export { createMessage } from "./types";
package/src/messaging.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  import type { components } from "@amos.com/node";
2
2
  import { decodeJwt } from "./jwt";
3
- import { type Appearance, createMessage, type Message } from "./types";
3
+ import {
4
+ type Appearance,
5
+ type ApplePayButtonElementProps,
6
+ createMessage,
7
+ type GooglePayButtonElementProps,
8
+ type Message,
9
+ pickApplePayButtonElementProps,
10
+ pickGooglePayButtonElementProps,
11
+ } from "./types";
4
12
 
5
13
  type Iframe = HTMLIFrameElement | null | undefined;
6
14
 
@@ -55,6 +63,52 @@ export function updateAppearance({
55
63
  );
56
64
  }
57
65
 
66
+ /**
67
+ * Push Apple Pay button visual options into the embedded iframe.
68
+ */
69
+ export function updateApplePayButton({
70
+ iframe,
71
+ props,
72
+ }: {
73
+ iframe: Iframe;
74
+ props: ApplePayButtonElementProps;
75
+ }): void {
76
+ if (!iframe?.contentWindow) {
77
+ return;
78
+ }
79
+
80
+ iframe.contentWindow.postMessage(
81
+ createMessage({
82
+ type: "UPDATE_APPLE_PAY_BUTTON",
83
+ props: pickApplePayButtonElementProps(props),
84
+ }),
85
+ getIframeTargetOrigin(iframe),
86
+ );
87
+ }
88
+
89
+ /**
90
+ * Push Google Pay button visual options into the embedded iframe.
91
+ */
92
+ export function updateGooglePayButton({
93
+ iframe,
94
+ props,
95
+ }: {
96
+ iframe: Iframe;
97
+ props: GooglePayButtonElementProps;
98
+ }): void {
99
+ if (!iframe?.contentWindow) {
100
+ return;
101
+ }
102
+
103
+ iframe.contentWindow.postMessage(
104
+ createMessage({
105
+ type: "UPDATE_GOOGLE_PAY_BUTTON",
106
+ props: pickGooglePayButtonElementProps(props),
107
+ }),
108
+ getIframeTargetOrigin(iframe),
109
+ );
110
+ }
111
+
58
112
  /**
59
113
  * Push the express-checkout amount into the embedded Google Pay iframe.
60
114
  */
package/src/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ /// <reference types="googlepay" />
2
+
1
3
  import type { components } from "@amos.com/node";
2
4
 
3
5
  /**
@@ -92,6 +94,12 @@ export type ThemeVariable =
92
94
  * Default: oklch(0.205 0 0)
93
95
  */
94
96
  | "--secondary-foreground"
97
+ /*
98
+ * Muted surface color (e.g. chips, subdued fills).
99
+ *
100
+ * Default: oklch(0.97 0 0)
101
+ */
102
+ | "--muted"
95
103
  /*
96
104
  * Placeholder text, helper labels, and muted icons.
97
105
  *
@@ -112,17 +120,35 @@ export type ThemeVariable =
112
120
  */
113
121
  | "--accent-foreground"
114
122
  /*
115
- * Error/invalid state borders and icons.
123
+ * Error/invalid state borders, icons, and field error text.
116
124
  *
117
125
  * Default: oklch(0.577 0.245 27.325)
118
126
  */
119
127
  | "--destructive"
128
+ /*
129
+ * Text on destructive-colored surfaces (e.g. destructive buttons).
130
+ *
131
+ * Default: oklch(0.45 0.24 27.325)
132
+ */
133
+ | "--destructive-foreground"
120
134
  /*
121
135
  * General border color applied to all elements via the base layer.
122
136
  *
123
137
  * Default: oklch(0.922 0 0)
124
138
  */
125
139
  | "--border"
140
+ /*
141
+ * Dropdown / popover panel background.
142
+ *
143
+ * Default: oklch(1 0 0)
144
+ */
145
+ | "--popover"
146
+ /*
147
+ * Dropdown / popover panel text color.
148
+ *
149
+ * Default: oklch(0.145 0 0)
150
+ */
151
+ | "--popover-foreground"
126
152
  /*
127
153
  * Input field border color.
128
154
  *
@@ -147,6 +173,36 @@ export type ThemeVariable =
147
173
  * Default: 0.875rem
148
174
  */
149
175
  | "--input-font-size"
176
+ /*
177
+ * Font weight of typed input values and dropdown fields (e.g. `400`, `normal`).
178
+ *
179
+ * Default: 400
180
+ */
181
+ | "--input-font-weight"
182
+ /*
183
+ * Horizontal padding inside inputs and aligned floating labels (e.g. `0.75rem`, `12px`).
184
+ *
185
+ * Default: 0.75rem
186
+ */
187
+ | "--input-padding"
188
+ /*
189
+ * Input field border width (e.g. `1px`).
190
+ *
191
+ * Default: 1px
192
+ */
193
+ | "--input-border-width"
194
+ /*
195
+ * Input field box shadow (e.g. `none`, `0 1px 2px 0 rgb(0 0 0 / 0.05)`).
196
+ *
197
+ * Default: 0 1px 2px 0 rgb(0 0 0 / 0.05)
198
+ */
199
+ | "--input-shadow"
200
+ /*
201
+ * Height of text inputs when labels are floating (e.g. `3.25rem`, `52px`).
202
+ *
203
+ * Default: 3.25rem
204
+ */
205
+ | "--floating-input-height"
150
206
  /*
151
207
  * Font size of floating labels when the field is focused or filled
152
208
  * (e.g. `0.75rem`, `12px`). Does not affect the typed input value.
@@ -160,12 +216,66 @@ export type ThemeVariable =
160
216
  * Default: 500
161
217
  */
162
218
  | "--floating-label-font-weight"
219
+ /*
220
+ * Color of floating labels. Defaults to `--muted-foreground`.
221
+ *
222
+ * Default: var(--muted-foreground)
223
+ */
224
+ | "--floating-label-color"
225
+ /*
226
+ * Top offset of the shrunk floating label inside the control (e.g. `0.625rem`).
227
+ *
228
+ * Default: 0.625rem
229
+ */
230
+ | "--floating-label-offset"
231
+ /*
232
+ * Font size of above-style field labels and radio option labels.
233
+ *
234
+ * Default: 0.875rem
235
+ */
236
+ | "--label-font-size"
237
+ /*
238
+ * Font weight of above-style field labels and radio option labels.
239
+ *
240
+ * Default: 500
241
+ */
242
+ | "--label-font-weight"
243
+ /*
244
+ * Vertical gap between stacked form fields (e.g. `1rem`, `16px`).
245
+ *
246
+ * Default: 1rem
247
+ */
248
+ | "--field-gap"
249
+ /*
250
+ * Horizontal gap between side-by-side controls (e.g. expiry + CVC).
251
+ *
252
+ * Default: 0.5rem
253
+ */
254
+ | "--control-gap"
255
+ /*
256
+ * Font size of field-level error messages.
257
+ *
258
+ * Default: 0.875rem
259
+ */
260
+ | "--error-font-size"
261
+ /*
262
+ * Size of radio buttons on the bank account form (e.g. `1rem`, `16px`).
263
+ *
264
+ * Default: 1rem
265
+ */
266
+ | "--radio-size"
163
267
  /*
164
268
  * Focus ring and outline color for inputs and buttons.
165
269
  *
166
270
  * Default: oklch(0.708 0 0)
167
271
  */
168
272
  | "--ring"
273
+ /*
274
+ * Focus ring width for inputs (e.g. `3px`).
275
+ *
276
+ * Default: 3px
277
+ */
278
+ | "--ring-width"
169
279
  /*
170
280
  * Base border-radius; derived into --radius-sm/md/lg/xl.
171
281
  *
@@ -195,6 +305,193 @@ export type Appearance = {
195
305
  labels?: AppearanceLabels;
196
306
  };
197
307
 
308
+ /**
309
+ * Inline style bag sent through `postMessage` to the wallet-button iframe.
310
+ *
311
+ * Matches the serializable subset of a CSSStyleDeclaration / React
312
+ * `CSSProperties` object (string or number values). Custom properties such
313
+ * as `--apple-pay-button-height` are allowed.
314
+ */
315
+ export type WalletButtonStyle = {
316
+ [property: string]: string | number | undefined;
317
+ };
318
+
319
+ /**
320
+ * `buttonstyle` attribute on Apple's `<apple-pay-button>`.
321
+ *
322
+ * @see https://developer.apple.com/documentation/apple_pay_on_the_web/apple-pay-button
323
+ */
324
+ export type ApplePayButtonStyle = "black" | "white" | "white-outline";
325
+
326
+ /**
327
+ * `type` attribute on Apple's `<apple-pay-button>`.
328
+ *
329
+ * @see https://developer.apple.com/documentation/apple_pay_on_the_web/apple-pay-button
330
+ */
331
+ export type ApplePayButtonType =
332
+ | "plain"
333
+ | "buy"
334
+ | "set-up"
335
+ | "donate"
336
+ | "check-out"
337
+ | "book"
338
+ | "subscribe"
339
+ | "reload"
340
+ | "add-money"
341
+ | "top-up"
342
+ | "order"
343
+ | "rent"
344
+ | "support"
345
+ | "contribute"
346
+ | "tip";
347
+
348
+ /**
349
+ * Visual props for Apple's `<apple-pay-button>`, using Apple's attribute
350
+ * names. Applied inside the Amos embed iframe.
351
+ *
352
+ * @see https://developer.apple.com/documentation/apple_pay_on_the_web/apple-pay-button
353
+ */
354
+ export type ApplePayButtonElementProps = {
355
+ /**
356
+ * Apple Pay button color.
357
+ *
358
+ * @default "black"
359
+ */
360
+ buttonstyle?: ApplePayButtonStyle;
361
+ /**
362
+ * Apple Pay button label / verb.
363
+ *
364
+ * @default "plain"
365
+ */
366
+ type?: ApplePayButtonType;
367
+ /**
368
+ * BCP 47 locale for the button label (e.g. `"en-US"`).
369
+ *
370
+ * @default "en-US"
371
+ */
372
+ locale?: string;
373
+ /**
374
+ * Inline style for the `<apple-pay-button>`. Height is controlled with
375
+ * `--apple-pay-button-height` (Apple ignores CSS `height`). Width uses
376
+ * `--apple-pay-button-width` or CSS `width`.
377
+ */
378
+ style?: WalletButtonStyle;
379
+ };
380
+
381
+ /**
382
+ * Visual props for Google's Pay button, using `@google-pay/button-react`
383
+ * / Google Pay API names. Applied inside the Amos embed iframe.
384
+ *
385
+ * @see https://developers.google.com/pay/api/web/guides/resources/customize
386
+ */
387
+ export type GooglePayButtonElementProps = {
388
+ /**
389
+ * Button label / verb.
390
+ *
391
+ * @default "short"
392
+ */
393
+ buttonType?: google.payments.api.ButtonType;
394
+ /** Button color. */
395
+ buttonColor?: google.payments.api.ButtonColor;
396
+ /** Corner radius in pixels (0–20). */
397
+ buttonRadius?: number;
398
+ /**
399
+ * `"fill"` stretches the button to the container width; `"static"`
400
+ * uses Google's default size.
401
+ */
402
+ buttonSizeMode?: google.payments.api.ButtonSizeMode;
403
+ /** BCP 47 locale for the button label (e.g. `"en"`). */
404
+ buttonLocale?: string;
405
+ /** Whether the button draws a border. */
406
+ buttonBorderType?: google.payments.api.ButtonBorderType;
407
+ /**
408
+ * Inline style for the Google Pay button wrapper. Use `height` /
409
+ * `width` here (unlike Apple Pay, Google honors CSS `height`).
410
+ */
411
+ style?: WalletButtonStyle;
412
+ };
413
+
414
+ /**
415
+ * Keep only JSON-cloneable string/number declarations from a style object
416
+ * before sending it through `postMessage`.
417
+ */
418
+ export function serializeWalletButtonStyle(
419
+ style: WalletButtonStyle | undefined,
420
+ ): Record<string, string | number> | undefined {
421
+ if (!style || typeof style !== "object") {
422
+ return undefined;
423
+ }
424
+
425
+ const result: Record<string, string | number> = {};
426
+ for (const [key, value] of Object.entries(style)) {
427
+ if (typeof value === "string") {
428
+ if (value.length === 0) {
429
+ continue;
430
+ }
431
+ result[key] = value;
432
+ continue;
433
+ }
434
+ if (typeof value === "number" && Number.isFinite(value)) {
435
+ result[key] = value;
436
+ }
437
+ }
438
+
439
+ return Object.keys(result).length > 0 ? result : undefined;
440
+ }
441
+
442
+ export function pickApplePayButtonElementProps(
443
+ options: ApplePayButtonElementProps,
444
+ ): ApplePayButtonElementProps {
445
+ const props: ApplePayButtonElementProps = {};
446
+ if (options.buttonstyle !== undefined) {
447
+ props.buttonstyle = options.buttonstyle;
448
+ }
449
+ if (options.type !== undefined) {
450
+ props.type = options.type;
451
+ }
452
+ if (options.locale !== undefined) {
453
+ props.locale = options.locale;
454
+ }
455
+ if (options.style !== undefined) {
456
+ const style = serializeWalletButtonStyle(options.style);
457
+ if (style) {
458
+ props.style = style;
459
+ }
460
+ }
461
+ return props;
462
+ }
463
+
464
+ export function pickGooglePayButtonElementProps(
465
+ options: GooglePayButtonElementProps,
466
+ ): GooglePayButtonElementProps {
467
+ const props: GooglePayButtonElementProps = {};
468
+ if (options.buttonType !== undefined) {
469
+ props.buttonType = options.buttonType;
470
+ }
471
+ if (options.buttonColor !== undefined) {
472
+ props.buttonColor = options.buttonColor;
473
+ }
474
+ if (options.buttonRadius !== undefined) {
475
+ props.buttonRadius = options.buttonRadius;
476
+ }
477
+ if (options.buttonSizeMode !== undefined) {
478
+ props.buttonSizeMode = options.buttonSizeMode;
479
+ }
480
+ if (options.buttonLocale !== undefined) {
481
+ props.buttonLocale = options.buttonLocale;
482
+ }
483
+ if (options.buttonBorderType !== undefined) {
484
+ props.buttonBorderType = options.buttonBorderType;
485
+ }
486
+ if (options.style !== undefined) {
487
+ const style = serializeWalletButtonStyle(options.style);
488
+ if (style) {
489
+ props.style = style;
490
+ }
491
+ }
492
+ return props;
493
+ }
494
+
198
495
  /**
199
496
  * Typed `postMessage` payloads exchanged between the host page and the
200
497
  * embedded Amos iframe.
@@ -228,6 +525,20 @@ export type Message =
228
525
  type: "UPDATE_APPEARANCE";
229
526
  appearance: Appearance;
230
527
  }
528
+ | {
529
+ /**
530
+ * Parent → embed: visual options for the Apple Pay button. Nested
531
+ * under `props` so Apple's `type` attribute does not collide with
532
+ * this message discriminant.
533
+ */
534
+ type: "UPDATE_APPLE_PAY_BUTTON";
535
+ props: ApplePayButtonElementProps;
536
+ }
537
+ | {
538
+ /** Parent → embed: visual options for the Google Pay button. */
539
+ type: "UPDATE_GOOGLE_PAY_BUTTON";
540
+ props: GooglePayButtonElementProps;
541
+ }
231
542
  | {
232
543
  /**
233
544
  * Parent → embed: validate form inputs (`requestId` only).