@aglyn/plugins-commerce 1.0.0-beta.278 → 1.0.0-beta.283
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/package.json +17 -14
- package/src/lib/components/commerce-console-page.js +33 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/payments-settings-card.component.js +1 -1
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-hardware-api.d.ts +122 -0
- package/src/lib/components/console/pos/pos-hardware-api.js +90 -0
- package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.d.ts +58 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.js +101 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.js.map +1 -0
- package/src/lib/components/console/pos-devices-card.component.js +5 -0
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-hardware-card.component.d.ts +26 -0
- package/src/lib/components/console/pos-hardware-card.component.js +456 -0
- package/src/lib/components/console/pos-hardware-card.component.js.map +1 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.d.ts +14 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +519 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.d.ts +27 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +444 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.d.ts +9 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js +37 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +24 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +346 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.d.ts +5 -1
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js +83 -4
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.d.ts +35 -0
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js +546 -0
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +2 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +20 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-ops-api.d.ts +1 -1
- package/src/lib/components/console/pos-ops/pos-ops-api.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +6 -3
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/pos-reader-splash-setting.component.d.ts +22 -0
- package/src/lib/components/console/pos-reader-splash-setting.component.js +275 -0
- package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.d.ts +14 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.js +301 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.js.map +1 -0
- package/src/lib/components/console/pos-zones.d.ts +37 -3
- package/src/lib/components/console/pos-zones.js +11 -1
- package/src/lib/components/console/pos-zones.js.map +1 -1
- package/src/lib/components/console/product-check-in-fields.component.d.ts +20 -0
- package/src/lib/components/console/product-check-in-fields.component.js +172 -0
- package/src/lib/components/console/product-check-in-fields.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.js +48 -9
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-register-fields.component.d.ts +12 -0
- package/src/lib/components/console/product-register-fields.component.js +39 -1
- package/src/lib/components/console/product-register-fields.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +21 -6
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/reservations-card.component.js +239 -70
- package/src/lib/components/console/reservations-card.component.js.map +1 -1
- package/src/lib/components/reservation-widget.js +16 -0
- package/src/lib/components/reservation-widget.js.map +1 -1
- package/src/lib/constants/operator-alerts.d.ts +6 -0
- package/src/lib/constants/operator-alerts.js +20 -1
- package/src/lib/constants/operator-alerts.js.map +1 -1
- package/src/lib/declarations.console-server.js +7 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +26 -0
- package/src/lib/declarations.js +34 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/model/commerce-check-in-product.d.ts +37 -0
- package/src/lib/model/commerce-check-in-product.js +49 -0
- package/src/lib/model/commerce-check-in-product.js.map +1 -0
- package/src/lib/model/commerce-check-in.d.ts +187 -0
- package/src/lib/model/commerce-check-in.js +468 -0
- package/src/lib/model/commerce-check-in.js.map +1 -0
- package/src/lib/model/commerce-pos-day-report.d.ts +209 -0
- package/src/lib/model/commerce-pos-day-report.js +572 -0
- package/src/lib/model/commerce-pos-day-report.js.map +1 -0
- package/src/lib/model/commerce-pos-offline.d.ts +65 -3
- package/src/lib/model/commerce-pos-offline.js +104 -7
- package/src/lib/model/commerce-pos-offline.js.map +1 -1
- package/src/lib/model/commerce-pos-ops.d.ts +3 -0
- package/src/lib/model/commerce-pos-ops.js +1 -0
- package/src/lib/model/commerce-pos-ops.js.map +1 -1
- package/src/lib/model/commerce-pos.d.ts +43 -3
- package/src/lib/model/commerce-pos.js +73 -5
- package/src/lib/model/commerce-pos.js.map +1 -1
- package/src/lib/model/commerce-reservations.d.ts +73 -7
- package/src/lib/model/commerce-reservations.js +90 -10
- package/src/lib/model/commerce-reservations.js.map +1 -1
- package/src/lib/model/commerce.d.ts +40 -0
- package/src/lib/model/commerce.js +22 -4
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/index.js +1 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/plugin-permissions.d.ts +1 -1
- package/src/lib/model/plugin-permissions.js +2 -1
- package/src/lib/model/plugin-permissions.js.map +1 -1
- package/src/lib/model/pos-hardware.d.ts +288 -0
- package/src/lib/model/pos-hardware.js +499 -0
- package/src/lib/model/pos-hardware.js.map +1 -0
- package/src/lib/model/pos-offline-card-limits.d.ts +20 -0
- package/src/lib/model/pos-offline-card-limits.js +28 -0
- package/src/lib/model/pos-offline-card-limits.js.map +1 -0
- package/src/lib/model/pos-reader-splash.d.ts +114 -0
- package/src/lib/model/pos-reader-splash.js +141 -0
- package/src/lib/model/pos-reader-splash.js.map +1 -0
- package/src/lib/model/product-write.d.ts +6 -0
- package/src/lib/plugin-config.d.ts +10 -0
- package/src/lib/plugin-config.js +40 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +28 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/cart-checkout.js +3 -1
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/cart.js +12 -1
- package/src/lib/server/cart.js.map +1 -1
- package/src/lib/server/catalog.js +49 -12
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout-extras.js +1 -1
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +7 -0
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +8 -1
- package/src/lib/server/person-eraser.js +23 -1
- package/src/lib/server/person-eraser.js.map +1 -1
- package/src/lib/server/pos-check-in.d.ts +49 -0
- package/src/lib/server/pos-check-in.js +652 -0
- package/src/lib/server/pos-check-in.js.map +1 -0
- package/src/lib/server/pos-day-report.d.ts +46 -0
- package/src/lib/server/pos-day-report.js +299 -0
- package/src/lib/server/pos-day-report.js.map +1 -0
- package/src/lib/server/pos-hardware-admin.d.ts +50 -0
- package/src/lib/server/pos-hardware-admin.js +366 -0
- package/src/lib/server/pos-hardware-admin.js.map +1 -0
- package/src/lib/server/pos-hardware-notify.d.ts +27 -0
- package/src/lib/server/pos-hardware-notify.js +142 -0
- package/src/lib/server/pos-hardware-notify.js.map +1 -0
- package/src/lib/server/pos-hardware-user-eraser.d.ts +26 -0
- package/src/lib/server/pos-hardware-user-eraser.js +67 -0
- package/src/lib/server/pos-hardware-user-eraser.js.map +1 -0
- package/src/lib/server/pos-hardware-webhook.d.ts +31 -0
- package/src/lib/server/pos-hardware-webhook.js +176 -0
- package/src/lib/server/pos-hardware-webhook.js.map +1 -0
- package/src/lib/server/pos-hardware.d.ts +58 -0
- package/src/lib/server/pos-hardware.js +234 -0
- package/src/lib/server/pos-hardware.js.map +1 -0
- package/src/lib/server/pos-offline-card-reconcile.d.ts +38 -0
- package/src/lib/server/pos-offline-card-reconcile.js +58 -0
- package/src/lib/server/pos-offline-card-reconcile.js.map +1 -0
- package/src/lib/server/pos-offline-sync.d.ts +32 -0
- package/src/lib/server/pos-offline-sync.js +191 -15
- package/src/lib/server/pos-offline-sync.js.map +1 -1
- package/src/lib/server/pos-ops-routes.d.ts +4 -2
- package/src/lib/server/pos-ops-routes.js +14 -2
- package/src/lib/server/pos-ops-routes.js.map +1 -1
- package/src/lib/server/pos-order.js +39 -51
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.js +8 -4
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/pos-reader-splash-admin.d.ts +36 -0
- package/src/lib/server/pos-reader-splash-admin.js +295 -0
- package/src/lib/server/pos-reader-splash-admin.js.map +1 -0
- package/src/lib/server/pos-reader-splash-render.d.ts +40 -0
- package/src/lib/server/pos-reader-splash-render.js +138 -0
- package/src/lib/server/pos-reader-splash-render.js.map +1 -0
- package/src/lib/server/pos-reader-splash-server.d.ts +32 -0
- package/src/lib/server/pos-reader-splash-server.js +32 -0
- package/src/lib/server/pos-reader-splash-server.js.map +1 -0
- package/src/lib/server/pos-reader-splash.d.ts +105 -0
- package/src/lib/server/pos-reader-splash.js +548 -0
- package/src/lib/server/pos-reader-splash.js.map +1 -0
- package/src/lib/server/pos-sale.d.ts +2 -1
- package/src/lib/server/pos-sale.js +3 -1
- package/src/lib/server/pos-sale.js.map +1 -1
- package/src/lib/server/pos-terminal.d.ts +67 -4
- package/src/lib/server/pos-terminal.js +239 -32
- package/src/lib/server/pos-terminal.js.map +1 -1
- package/src/lib/server/process-restock.js +4 -0
- package/src/lib/server/process-restock.js.map +1 -1
- package/src/lib/server/product-card.js +2 -2
- package/src/lib/server/product-card.js.map +1 -1
- package/src/lib/server/product-catalog.d.ts +2 -0
- package/src/lib/server/product-catalog.js +11 -4
- package/src/lib/server/product-catalog.js.map +1 -1
- package/src/lib/server/product.js +3 -1
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/related.js +8 -1
- package/src/lib/server/related.js.map +1 -1
- package/src/lib/server/reservation-availability-query.d.ts +35 -0
- package/src/lib/server/reservation-availability-query.js +34 -0
- package/src/lib/server/reservation-availability-query.js.map +1 -0
- package/src/lib/server/reservation-availability.d.ts +21 -0
- package/src/lib/server/reservation-availability.js +64 -10
- package/src/lib/server/reservation-availability.js.map +1 -1
- package/src/lib/server/reservation-walk-in.d.ts +28 -0
- package/src/lib/server/reservation-walk-in.js +166 -0
- package/src/lib/server/reservation-walk-in.js.map +1 -0
- package/src/lib/server/reserve.js +13 -10
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/reviews.js +3 -1
- package/src/lib/server/reviews.js.map +1 -1
- package/src/lib/server/site-page-resolver.js +5 -2
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +26 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/fake-firestore.js +72 -0
- package/src/lib/testing/fake-firestore.js.map +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type MediaCdnScope } from '@aglyn/aglyn/app-utils/media-cdn-scope';
|
|
18
|
+
import type { PluginApiHandler } from '@aglyn/aglyn/server';
|
|
19
|
+
import { type PosReaderSplashChoice, type PosReaderSplashFamilyId } from '../model/pos-reader-splash';
|
|
20
|
+
/** `platformSettings/posReaderSplash`: staff's default screen. */
|
|
21
|
+
export declare const POS_READER_SPLASH_SETTINGS_ID = "posReaderSplash";
|
|
22
|
+
/** One family's default screen, as staff uploaded it. */
|
|
23
|
+
export interface PlatformSplashImage {
|
|
24
|
+
storagePath: string;
|
|
25
|
+
sha256: string;
|
|
26
|
+
contentType: 'image/png' | 'image/jpeg';
|
|
27
|
+
bytes: number;
|
|
28
|
+
uploadedAtMs: number;
|
|
29
|
+
uploadedBy?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface PlatformSplashDoc {
|
|
32
|
+
families?: Partial<Record<PosReaderSplashFamilyId, PlatformSplashImage>>;
|
|
33
|
+
updatedAtMs?: number;
|
|
34
|
+
}
|
|
35
|
+
export declare function platformSplashRef(): FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;
|
|
36
|
+
export declare function posReaderSplashBucket(): import("@google-cloud/storage").Bucket;
|
|
37
|
+
export declare function readPlatformSplash(): Promise<PlatformSplashDoc>;
|
|
38
|
+
/** The default is usable once the S700/S710 screen is uploaded; the smaller one is drawn from it if absent. */
|
|
39
|
+
export declare function platformSplashReady(doc: PlatformSplashDoc): boolean;
|
|
40
|
+
interface LocatedAsset {
|
|
41
|
+
kind: 'asset';
|
|
42
|
+
scope: MediaCdnScope;
|
|
43
|
+
scopeSegment: string;
|
|
44
|
+
mediaId: string;
|
|
45
|
+
doc: Record<string, unknown>;
|
|
46
|
+
/** What changes when the asset's bytes do. */
|
|
47
|
+
version: string;
|
|
48
|
+
}
|
|
49
|
+
interface LocatedUrl {
|
|
50
|
+
kind: 'url';
|
|
51
|
+
url: string;
|
|
52
|
+
version: string;
|
|
53
|
+
}
|
|
54
|
+
type LocatedSource = LocatedAsset | LocatedUrl;
|
|
55
|
+
/**
|
|
56
|
+
* The workspace's own logo: a white-label workspace's brand logo first,
|
|
57
|
+
* then the logo on its organization profile. Never the platform's: the
|
|
58
|
+
* resolved brand profile falls back to the platform's own logo for a
|
|
59
|
+
* workspace without a brand, so its `logoUrl` is not read here.
|
|
60
|
+
*/
|
|
61
|
+
export declare function workspaceLogoValue(org: Record<string, any> | null, whiteLabel: boolean): string | null;
|
|
62
|
+
type SplashSource = {
|
|
63
|
+
mode: 'platform';
|
|
64
|
+
doc: PlatformSplashDoc;
|
|
65
|
+
} | {
|
|
66
|
+
mode: 'logo' | 'artwork';
|
|
67
|
+
located: LocatedSource;
|
|
68
|
+
};
|
|
69
|
+
export interface PosReaderSplashPlan {
|
|
70
|
+
choice: PosReaderSplashChoice | null;
|
|
71
|
+
effectiveChoice: PosReaderSplashChoice;
|
|
72
|
+
whiteLabel: boolean;
|
|
73
|
+
productName: string;
|
|
74
|
+
platformReady: boolean;
|
|
75
|
+
logoValue: string | null;
|
|
76
|
+
image: {
|
|
77
|
+
mediaScope: string;
|
|
78
|
+
mediaId: string;
|
|
79
|
+
} | null;
|
|
80
|
+
/** `none` when the readers show Stripe's standard screen. */
|
|
81
|
+
key: string;
|
|
82
|
+
problem: string | null;
|
|
83
|
+
source: SplashSource | null;
|
|
84
|
+
}
|
|
85
|
+
export declare function planPosReaderSplash(hostId: string, org: Record<string, any> | null, config: Record<string, any> | undefined, platform?: PlatformSplashDoc): Promise<PosReaderSplashPlan>;
|
|
86
|
+
/** The org that owns the site, with its id on it (`planPosReaderSplash` reads it). */
|
|
87
|
+
export declare function ownerOrgOf(hostId: string): Promise<Record<string, any> | null>;
|
|
88
|
+
export interface PosReaderSplashApplyResult {
|
|
89
|
+
outcome: 'unchanged' | 'applied' | 'cleared' | 'skipped' | 'failed';
|
|
90
|
+
plan: PosReaderSplashPlan | null;
|
|
91
|
+
problem: string | null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Puts the store's start-up screen on its readers' configuration when it
|
|
95
|
+
* changed since it was last applied. Never throws: a failure is recorded on
|
|
96
|
+
* the store's terminal document and returned, and the readers keep their
|
|
97
|
+
* previous screen.
|
|
98
|
+
*/
|
|
99
|
+
export declare function applyPosReaderSplash(hostId: string, options?: {
|
|
100
|
+
org?: Record<string, any> | null;
|
|
101
|
+
platform?: PlatformSplashDoc;
|
|
102
|
+
force?: boolean;
|
|
103
|
+
}): Promise<PosReaderSplashApplyResult>;
|
|
104
|
+
export declare const posReaderSplashHandler: PluginApiHandler;
|
|
105
|
+
export {};
|
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { createHash } from "node:crypto";
|
|
18
|
+
import { parseMediaCdnScope } from "@aglyn/aglyn/app-utils/media-cdn-scope";
|
|
19
|
+
import { formatMediaRef, isMediaCdnPath, MEDIA_CDN_ROUTE, mediaRefFromStorageUrl, parseMediaRef, resolveMediaSrc } from "@aglyn/aglyn/app-utils/media-ref";
|
|
20
|
+
import { checkEntitlement, resolveBrandingProfile } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
21
|
+
import { visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
22
|
+
import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
|
|
23
|
+
import { isPosReaderSplashChoice, POS_READER_SPLASH_DEVICE_TYPES, POS_READER_SPLASH_FAMILIES, POS_READER_SPLASH_SOURCE_MAX_BYTES, POS_READER_SPLASH_SOURCE_TYPES, posReaderSplashArtworkProblem, posReaderSplashEffectiveChoice } from "../model/pos-reader-splash.js";
|
|
24
|
+
import { authorizePosStaff, posRequestBody } from "./pos-auth.js";
|
|
25
|
+
import { PosReaderSplashRenderError, posReaderSplashImageFacts, renderPosReaderSplash } from "./pos-reader-splash-render.js";
|
|
26
|
+
import { POS_STRIPE_VERSION, posTerminalAvailable } from "./pos-stripe.js";
|
|
27
|
+
import { savePosReaderConfiguration } from "./pos-terminal.js";
|
|
28
|
+
/*==========================================
|
|
29
|
+
* A STORE'S CARD READERS SHOW ITS START-UP SCREEN (AGL-4048).
|
|
30
|
+
*
|
|
31
|
+
* ## Where it lives
|
|
32
|
+
*
|
|
33
|
+
* Readers and Locations are the PLATFORM's (the destination-charge model,
|
|
34
|
+
* `pos-terminal.ts`), one Location per site. The screen is set on that
|
|
35
|
+
* site's one Terminal Configuration, the one reader tipping already uses,
|
|
36
|
+
* per device type (`stripe_s700`, `stripe_s710`, `bbpos_wisepos_e`), from a
|
|
37
|
+
* File uploaded to Stripe with purpose `terminal_reader_splashscreen`.
|
|
38
|
+
*
|
|
39
|
+
* The platform's account-default configuration is NEVER given a screen. A
|
|
40
|
+
* Location without an override inherits it, so an account default would put
|
|
41
|
+
* the platform's brand on a white-label store's readers, which is the one
|
|
42
|
+
* thing the white-label rule forbids. Staff's default screen is applied per
|
|
43
|
+
* Location instead, to the stores that use it.
|
|
44
|
+
*
|
|
45
|
+
* ## What is applied
|
|
46
|
+
*
|
|
47
|
+
* `hosts/{hostId}/terminal/config.splash` holds the store's choice
|
|
48
|
+
* (`model/pos-reader-splash.ts`); the screen applied is described by a KEY
|
|
49
|
+
* built from cheap metadata only: the choice, the platform default's
|
|
50
|
+
* version, the logo's address and its asset's content digest, the picked
|
|
51
|
+
* image's. `splashAppliedKey` records the last key that reached Stripe, so
|
|
52
|
+
* applying again with nothing changed makes no Stripe call at all, and a
|
|
53
|
+
* changed logo, a new staff default or a new choice makes exactly one round
|
|
54
|
+
* of uploads. Every upload and the first configuration write are keyed by
|
|
55
|
+
* content, so a retried apply never makes a second file or configuration.
|
|
56
|
+
*
|
|
57
|
+
* ## When it is applied
|
|
58
|
+
*
|
|
59
|
+
* - when the store's Location is created (its first reader);
|
|
60
|
+
* - when the POS devices card saves a choice, or opens and finds the key
|
|
61
|
+
* stale (a logo changed since);
|
|
62
|
+
* - when staff change the default, for every store with a Location
|
|
63
|
+
* (`pos-reader-splash-admin.ts`).
|
|
64
|
+
*
|
|
65
|
+
* Never on the payment path: drawing and uploading takes a second or two,
|
|
66
|
+
* and a reader with an older screen still takes the payment.
|
|
67
|
+
*
|
|
68
|
+
* ## Test mode first
|
|
69
|
+
*
|
|
70
|
+
* Gated by `posTerminalAvailable()`: always in test mode, in live mode only
|
|
71
|
+
* once `STRIPE_TERMINAL_LIVE_ENABLED` is on, like every reader control.
|
|
72
|
+
*=========================================*/ /** Raised when the drawing or the Location changes how keys are built. */ const RENDER_VERSION = 'v1';
|
|
73
|
+
/** `platformSettings/posReaderSplash`: staff's default screen. */ export const POS_READER_SPLASH_SETTINGS_ID = 'posReaderSplash';
|
|
74
|
+
const firestore = ()=>firebaseAdmin.app().firestore();
|
|
75
|
+
function terminalConfigRef(hostId) {
|
|
76
|
+
return firestore().collection('hosts').doc(hostId).collection('terminal').doc('config');
|
|
77
|
+
}
|
|
78
|
+
export function platformSplashRef() {
|
|
79
|
+
return firestore().collection('platformSettings').doc(POS_READER_SPLASH_SETTINGS_ID);
|
|
80
|
+
}
|
|
81
|
+
export function posReaderSplashBucket() {
|
|
82
|
+
return firebaseAdmin.app().storage().bucket(process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET || undefined);
|
|
83
|
+
}
|
|
84
|
+
export async function readPlatformSplash() {
|
|
85
|
+
const snapshot = await platformSplashRef().get();
|
|
86
|
+
return snapshot.exists ? snapshot.data() : {};
|
|
87
|
+
}
|
|
88
|
+
/** The default is usable once the S700/S710 screen is uploaded; the smaller one is drawn from it if absent. */ export function platformSplashReady(doc) {
|
|
89
|
+
var _doc_families_portraitlarge, _doc_families;
|
|
90
|
+
return Boolean((_doc_families = doc.families) == null ? void 0 : (_doc_families_portraitlarge = _doc_families['portrait-large']) == null ? void 0 : _doc_families_portraitlarge.storagePath);
|
|
91
|
+
}
|
|
92
|
+
function platformSplashVersion(doc) {
|
|
93
|
+
return POS_READER_SPLASH_FAMILIES.map((family)=>{
|
|
94
|
+
var _ref;
|
|
95
|
+
var _doc_families_family_id, _doc_families;
|
|
96
|
+
return (_ref = (_doc_families = doc.families) == null ? void 0 : (_doc_families_family_id = _doc_families[family.id]) == null ? void 0 : _doc_families_family_id.sha256) != null ? _ref : '-';
|
|
97
|
+
}).join('.');
|
|
98
|
+
}
|
|
99
|
+
const digest = (value)=>createHash('sha256').update(value).digest('hex');
|
|
100
|
+
const cleanString = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
101
|
+
async function locateMediaAsset(scopeSegment, mediaId, hostId, orgId) {
|
|
102
|
+
var _doc_contentType, _ref, _doc_updatedAtMs;
|
|
103
|
+
const scope = parseMediaCdnScope(scopeSegment);
|
|
104
|
+
if (!scope || !/^[A-Za-z0-9_-]{1,64}$/.test(mediaId)) return null;
|
|
105
|
+
const owned = scope.isOrg ? scope.scopeId === orgId : scope.scopeId === hostId;
|
|
106
|
+
if (!owned) return null;
|
|
107
|
+
const snapshot = await firestore().collection(scope.isOrg ? 'orgs' : 'hosts').doc(scope.scopeId).collection('media').doc(mediaId).get();
|
|
108
|
+
const doc = snapshot.exists ? snapshot.data() : null;
|
|
109
|
+
if (!doc || doc['deletedAt'] || doc['private']) return null;
|
|
110
|
+
if (scope.isOrg && !visibleToHost(doc['visibleTo'], hostId)) return null;
|
|
111
|
+
if (!/^image\//.test(String((_doc_contentType = doc['contentType']) != null ? _doc_contentType : ''))) return null;
|
|
112
|
+
if (typeof doc['sizeBytes'] === 'number' && doc['sizeBytes'] > POS_READER_SPLASH_SOURCE_MAX_BYTES) return null;
|
|
113
|
+
const version = cleanString(doc['contentSha256']) || cleanString(doc['contentHash']) || String((_ref = (_doc_updatedAtMs = doc['updatedAtMs']) != null ? _doc_updatedAtMs : doc['sizeBytes']) != null ? _ref : '');
|
|
114
|
+
return {
|
|
115
|
+
kind: 'asset',
|
|
116
|
+
scope,
|
|
117
|
+
scopeSegment,
|
|
118
|
+
mediaId,
|
|
119
|
+
doc,
|
|
120
|
+
version
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** Where a stored logo or image value lives, or null when it cannot be read. */ async function locateSource(value, hostId, orgId) {
|
|
124
|
+
const stored = parseMediaRef(value) ? value : mediaRefFromStorageUrl(value);
|
|
125
|
+
const ref = stored ? parseMediaRef(stored) : null;
|
|
126
|
+
if (ref) return locateMediaAsset(ref.scope, ref.mediaId, hostId, orgId);
|
|
127
|
+
if (isMediaCdnPath(value)) {
|
|
128
|
+
const rest = value.slice(`${MEDIA_CDN_ROUTE}/`.length).split('?')[0];
|
|
129
|
+
const slash = rest.indexOf('/');
|
|
130
|
+
if (slash <= 0) return null;
|
|
131
|
+
return locateMediaAsset(rest.slice(0, slash), rest.slice(slash + 1), hostId, orgId);
|
|
132
|
+
}
|
|
133
|
+
// `isMediaCdnPath` narrows a string it refuses to `never`; the value is still the string.
|
|
134
|
+
const address = value;
|
|
135
|
+
if (/^https:\/\//.test(address) && address.length <= 2000) return {
|
|
136
|
+
kind: 'url',
|
|
137
|
+
url: address,
|
|
138
|
+
version: address
|
|
139
|
+
};
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
async function readAssetBytes(asset) {
|
|
143
|
+
const { mediaCdnServeBlock, mediaStoragePathInScope } = await import("./pos-reader-splash-server.js");
|
|
144
|
+
const blocked = await mediaCdnServeBlock(asset.scope, {
|
|
145
|
+
contentSha256: typeof asset.doc['contentSha256'] === 'string' ? asset.doc['contentSha256'] : undefined,
|
|
146
|
+
contentHash: typeof asset.doc['contentHash'] === 'string' ? asset.doc['contentHash'] : undefined,
|
|
147
|
+
scopeSegment: asset.scopeSegment,
|
|
148
|
+
mediaId: asset.mediaId
|
|
149
|
+
});
|
|
150
|
+
if (blocked) return null;
|
|
151
|
+
const objectPath = mediaStoragePathInScope({
|
|
152
|
+
storagePath: asset.doc['storagePath'],
|
|
153
|
+
base: `${asset.scope.isOrg ? 'orgs' : 'hosts'}/${asset.scope.scopeId}`,
|
|
154
|
+
mediaId: asset.mediaId
|
|
155
|
+
});
|
|
156
|
+
const [buffer] = await posReaderSplashBucket().file(objectPath).download();
|
|
157
|
+
return buffer.length > POS_READER_SPLASH_SOURCE_MAX_BYTES ? null : buffer;
|
|
158
|
+
}
|
|
159
|
+
const FETCH_TIMEOUT_MS = 8000;
|
|
160
|
+
async function fetchPublicImage(url) {
|
|
161
|
+
const { createPinnedDispatcher, resolvePublicIp } = await import("./pos-reader-splash-server.js");
|
|
162
|
+
const pinnedIp = await resolvePublicIp(new URL(url).hostname);
|
|
163
|
+
if (!pinnedIp) return null;
|
|
164
|
+
const dispatcher = createPinnedDispatcher(pinnedIp);
|
|
165
|
+
const controller = new AbortController();
|
|
166
|
+
const timeout = setTimeout(()=>controller.abort(), FETCH_TIMEOUT_MS);
|
|
167
|
+
try {
|
|
168
|
+
var _response_headers_get, _response_headers_get1;
|
|
169
|
+
const init = {
|
|
170
|
+
redirect: 'error',
|
|
171
|
+
signal: controller.signal,
|
|
172
|
+
dispatcher
|
|
173
|
+
};
|
|
174
|
+
const response = await fetch(url, init);
|
|
175
|
+
if (!response.ok) return null;
|
|
176
|
+
if (!/^image\//.test((_response_headers_get = response.headers.get('content-type')) != null ? _response_headers_get : '')) return null;
|
|
177
|
+
const declared = Number((_response_headers_get1 = response.headers.get('content-length')) != null ? _response_headers_get1 : 0);
|
|
178
|
+
if (declared > POS_READER_SPLASH_SOURCE_MAX_BYTES) return null;
|
|
179
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
180
|
+
return buffer.length > POS_READER_SPLASH_SOURCE_MAX_BYTES ? null : buffer;
|
|
181
|
+
} catch (unused) {
|
|
182
|
+
return null;
|
|
183
|
+
} finally{
|
|
184
|
+
clearTimeout(timeout);
|
|
185
|
+
dispatcher.close == null ? void 0 : dispatcher.close.call(dispatcher).catch(()=>undefined);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
async function readSourceBytes(source) {
|
|
189
|
+
return source.kind === 'asset' ? readAssetBytes(source) : fetchPublicImage(source.url);
|
|
190
|
+
}
|
|
191
|
+
/*==========================================
|
|
192
|
+
* WHAT A STORE SHOWS.
|
|
193
|
+
*=========================================*/ /**
|
|
194
|
+
* The workspace's own logo: a white-label workspace's brand logo first,
|
|
195
|
+
* then the logo on its organization profile. Never the platform's: the
|
|
196
|
+
* resolved brand profile falls back to the platform's own logo for a
|
|
197
|
+
* workspace without a brand, so its `logoUrl` is not read here.
|
|
198
|
+
*/ export function workspaceLogoValue(org, whiteLabel) {
|
|
199
|
+
var _org_brandingProfile;
|
|
200
|
+
const brand = whiteLabel ? cleanString(org == null ? void 0 : (_org_brandingProfile = org['brandingProfile']) == null ? void 0 : _org_brandingProfile['logoUrl']) : '';
|
|
201
|
+
return brand || cleanString(org == null ? void 0 : org['logoUrl']) || null;
|
|
202
|
+
}
|
|
203
|
+
const NO_LOGO = 'Add a logo to your organization profile to show it on your card readers.';
|
|
204
|
+
const NO_IMAGE = 'The image picked for your card readers is no longer in your media library. Choose another.';
|
|
205
|
+
export async function planPosReaderSplash(hostId, org, config, platform) {
|
|
206
|
+
var _ref, _ref1;
|
|
207
|
+
const orgId = String((_ref = org == null ? void 0 : org['id']) != null ? _ref : '');
|
|
208
|
+
const whiteLabel = checkEntitlement(org, 'whiteLabel');
|
|
209
|
+
const productName = resolveBrandingProfile(org).productName;
|
|
210
|
+
const setting = (_ref1 = config == null ? void 0 : config['splash']) != null ? _ref1 : null;
|
|
211
|
+
const choice = isPosReaderSplashChoice(setting == null ? void 0 : setting.choice) ? setting.choice : null;
|
|
212
|
+
const effectiveChoice = posReaderSplashEffectiveChoice(choice, whiteLabel);
|
|
213
|
+
const doc = platform != null ? platform : await readPlatformSplash();
|
|
214
|
+
const logoValue = workspaceLogoValue(org, whiteLabel);
|
|
215
|
+
const image = (setting == null ? void 0 : setting.mediaScope) && (setting == null ? void 0 : setting.mediaId) ? {
|
|
216
|
+
mediaScope: setting.mediaScope,
|
|
217
|
+
mediaId: setting.mediaId
|
|
218
|
+
} : null;
|
|
219
|
+
const base = {
|
|
220
|
+
choice,
|
|
221
|
+
effectiveChoice,
|
|
222
|
+
whiteLabel,
|
|
223
|
+
productName,
|
|
224
|
+
platformReady: platformSplashReady(doc),
|
|
225
|
+
logoValue,
|
|
226
|
+
image
|
|
227
|
+
};
|
|
228
|
+
if (effectiveChoice === 'platform') {
|
|
229
|
+
// The white-label rule is in `posReaderSplashEffectiveChoice`; this is
|
|
230
|
+
// the guard behind it, because this branch is the platform's brand.
|
|
231
|
+
if (whiteLabel || !platformSplashReady(doc)) return _extends({}, base, {
|
|
232
|
+
key: 'none',
|
|
233
|
+
problem: null,
|
|
234
|
+
source: null
|
|
235
|
+
});
|
|
236
|
+
return _extends({}, base, {
|
|
237
|
+
key: `platform:${RENDER_VERSION}:${platformSplashVersion(doc)}`,
|
|
238
|
+
problem: null,
|
|
239
|
+
source: {
|
|
240
|
+
mode: 'platform',
|
|
241
|
+
doc
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
if (effectiveChoice === 'logo') {
|
|
246
|
+
const located = logoValue ? await locateSource(logoValue, hostId, orgId) : null;
|
|
247
|
+
if (!located) return _extends({}, base, {
|
|
248
|
+
key: 'none',
|
|
249
|
+
problem: NO_LOGO,
|
|
250
|
+
source: null
|
|
251
|
+
});
|
|
252
|
+
return _extends({}, base, {
|
|
253
|
+
key: `logo:${RENDER_VERSION}:${digest(`${logoValue}|${located.version}`).slice(0, 24)}`,
|
|
254
|
+
problem: null,
|
|
255
|
+
source: {
|
|
256
|
+
mode: 'logo',
|
|
257
|
+
located
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const located = image ? await locateMediaAsset(image.mediaScope, image.mediaId, hostId, orgId) : null;
|
|
262
|
+
if (!located) return _extends({}, base, {
|
|
263
|
+
key: 'none',
|
|
264
|
+
problem: NO_IMAGE,
|
|
265
|
+
source: null
|
|
266
|
+
});
|
|
267
|
+
return _extends({}, base, {
|
|
268
|
+
key: `image:${RENDER_VERSION}:${digest(`${image == null ? void 0 : image.mediaScope}/${image == null ? void 0 : image.mediaId}|${located.version}`).slice(0, 24)}`,
|
|
269
|
+
problem: null,
|
|
270
|
+
source: {
|
|
271
|
+
mode: 'artwork',
|
|
272
|
+
located
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
async function loadSharpFactory() {
|
|
277
|
+
const { loadSharp } = await import("./pos-reader-splash-server.js");
|
|
278
|
+
return loadSharp();
|
|
279
|
+
}
|
|
280
|
+
/** Each family's screen for the plan, ready for Stripe. */ async function drawScreens(source) {
|
|
281
|
+
const sharp = await loadSharpFactory();
|
|
282
|
+
const screens = {};
|
|
283
|
+
if (source.mode === 'platform') {
|
|
284
|
+
var _source_doc_families;
|
|
285
|
+
const bucket = posReaderSplashBucket();
|
|
286
|
+
const read = async (image)=>(await bucket.file(image.storagePath).download())[0];
|
|
287
|
+
const large = (_source_doc_families = source.doc.families) == null ? void 0 : _source_doc_families['portrait-large'];
|
|
288
|
+
const largeBody = await read(large);
|
|
289
|
+
for (const family of POS_READER_SPLASH_FAMILIES){
|
|
290
|
+
var _source_doc_families1;
|
|
291
|
+
const own = (_source_doc_families1 = source.doc.families) == null ? void 0 : _source_doc_families1[family.id];
|
|
292
|
+
// Staff's own screen for the family is sent as uploaded: it was checked
|
|
293
|
+
// against Stripe's rules for exactly this family then. A family staff
|
|
294
|
+
// left empty is drawn from the S700/S710 screen, the same 9:16.
|
|
295
|
+
screens[family.id] = (own == null ? void 0 : own.storagePath) ? {
|
|
296
|
+
body: own === large ? largeBody : await read(own),
|
|
297
|
+
contentType: own.contentType
|
|
298
|
+
} : await renderPosReaderSplash({
|
|
299
|
+
source: largeBody,
|
|
300
|
+
family,
|
|
301
|
+
mode: 'artwork',
|
|
302
|
+
sharp
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return screens;
|
|
306
|
+
}
|
|
307
|
+
const bytes = await readSourceBytes(source.located);
|
|
308
|
+
if (!bytes) throw new PosReaderSplashRenderError('That image could not be read.');
|
|
309
|
+
if (source.mode === 'artwork') {
|
|
310
|
+
var _ref, _ref1;
|
|
311
|
+
const facts = await posReaderSplashImageFacts(sharp, bytes);
|
|
312
|
+
const problem = posReaderSplashArtworkProblem({
|
|
313
|
+
contentType: (facts == null ? void 0 : facts.contentType) || null,
|
|
314
|
+
bytes: bytes.length,
|
|
315
|
+
width: (_ref = facts == null ? void 0 : facts.width) != null ? _ref : 0,
|
|
316
|
+
height: (_ref1 = facts == null ? void 0 : facts.height) != null ? _ref1 : 0
|
|
317
|
+
});
|
|
318
|
+
if (problem) throw new PosReaderSplashRenderError(problem);
|
|
319
|
+
}
|
|
320
|
+
for (const family of POS_READER_SPLASH_FAMILIES){
|
|
321
|
+
screens[family.id] = await renderPosReaderSplash({
|
|
322
|
+
source: bytes,
|
|
323
|
+
family,
|
|
324
|
+
mode: source.mode,
|
|
325
|
+
sharp
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
return screens;
|
|
329
|
+
}
|
|
330
|
+
/** One screen uploaded to Stripe, keyed by its content so a retry makes no second file. */ async function uploadSplashFile(hostId, family, screen) {
|
|
331
|
+
const form = new FormData();
|
|
332
|
+
form.append('purpose', 'terminal_reader_splashscreen');
|
|
333
|
+
const extension = screen.contentType === 'image/png' ? 'png' : 'jpg';
|
|
334
|
+
form.append('file', new Blob([
|
|
335
|
+
new Uint8Array(screen.body)
|
|
336
|
+
], {
|
|
337
|
+
type: screen.contentType
|
|
338
|
+
}), `start-up-${family.id}.${extension}`);
|
|
339
|
+
const response = await fetch('https://files.stripe.com/v1/files', {
|
|
340
|
+
method: 'POST',
|
|
341
|
+
headers: {
|
|
342
|
+
Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
|
|
343
|
+
'Stripe-Version': POS_STRIPE_VERSION,
|
|
344
|
+
'Idempotency-Key': `pos-splash-file:${hostId}:${family.id}:${digest(screen.body).slice(0, 32)}`
|
|
345
|
+
},
|
|
346
|
+
body: form
|
|
347
|
+
});
|
|
348
|
+
const body = await response.json().catch(()=>({}));
|
|
349
|
+
if (!response.ok || !body.id) {
|
|
350
|
+
var _body_error;
|
|
351
|
+
throw new PosReaderSplashRenderError(((_body_error = body.error) == null ? void 0 : _body_error.message) || 'Stripe did not accept the start-up screen.');
|
|
352
|
+
}
|
|
353
|
+
return String(body.id);
|
|
354
|
+
}
|
|
355
|
+
/** The org that owns the site, with its id on it (`planPosReaderSplash` reads it). */ export async function ownerOrgOf(hostId) {
|
|
356
|
+
var _ref, _owner_org_id;
|
|
357
|
+
const owner = await getOrgForHost(hostId).catch(()=>null);
|
|
358
|
+
if (!(owner == null ? void 0 : owner.org)) return null;
|
|
359
|
+
return _extends({}, owner.org, {
|
|
360
|
+
id: String((_ref = (_owner_org_id = owner.org.id) != null ? _owner_org_id : owner.orgId) != null ? _ref : '')
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Puts the store's start-up screen on its readers' configuration when it
|
|
365
|
+
* changed since it was last applied. Never throws: a failure is recorded on
|
|
366
|
+
* the store's terminal document and returned, and the readers keep their
|
|
367
|
+
* previous screen.
|
|
368
|
+
*/ export async function applyPosReaderSplash(hostId, options = {}) {
|
|
369
|
+
if (!posTerminalAvailable()) return {
|
|
370
|
+
outcome: 'skipped',
|
|
371
|
+
plan: null,
|
|
372
|
+
problem: null
|
|
373
|
+
};
|
|
374
|
+
const ref = terminalConfigRef(hostId);
|
|
375
|
+
const config = (await ref.get()).data();
|
|
376
|
+
if (!(config == null ? void 0 : config['stripeLocationId'])) return {
|
|
377
|
+
outcome: 'skipped',
|
|
378
|
+
plan: null,
|
|
379
|
+
problem: null
|
|
380
|
+
};
|
|
381
|
+
const org = options.org !== undefined ? options.org : await ownerOrgOf(hostId);
|
|
382
|
+
const plan = await planPosReaderSplash(hostId, org, config, options.platform);
|
|
383
|
+
const applied = typeof config['splashAppliedKey'] === 'string' ? config['splashAppliedKey'] : null;
|
|
384
|
+
// Nothing to show and nothing ever shown: the readers already show
|
|
385
|
+
// Stripe's standard screen, and no Stripe call is needed to keep it.
|
|
386
|
+
if (plan.key === 'none' && (applied === null || applied === 'none') && !options.force) {
|
|
387
|
+
return {
|
|
388
|
+
outcome: 'unchanged',
|
|
389
|
+
plan,
|
|
390
|
+
problem: plan.problem
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
if (plan.key === applied && !options.force) return {
|
|
394
|
+
outcome: 'unchanged',
|
|
395
|
+
plan,
|
|
396
|
+
problem: plan.problem
|
|
397
|
+
};
|
|
398
|
+
try {
|
|
399
|
+
const params = {};
|
|
400
|
+
if (plan.key === 'none' || !plan.source) {
|
|
401
|
+
// An empty value unsets the device's screen on this configuration.
|
|
402
|
+
for (const deviceType of POS_READER_SPLASH_DEVICE_TYPES)params[deviceType] = '';
|
|
403
|
+
} else {
|
|
404
|
+
const screens = await drawScreens(plan.source);
|
|
405
|
+
for (const family of POS_READER_SPLASH_FAMILIES){
|
|
406
|
+
const fileId = await uploadSplashFile(hostId, family, screens[family.id]);
|
|
407
|
+
for (const deviceType of family.deviceTypes)params[`${deviceType}[splashscreen]`] = fileId;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const saved = await savePosReaderConfiguration(hostId, params, `splash:${digest(plan.key).slice(0, 24)}`);
|
|
411
|
+
if (!saved.ok) throw new PosReaderSplashRenderError('Stripe did not accept the start-up screen. Try again.');
|
|
412
|
+
await ref.set({
|
|
413
|
+
splashAppliedKey: plan.key,
|
|
414
|
+
splashAppliedAtMs: Date.now(),
|
|
415
|
+
splashProblem: firebaseAdmin.firestore.FieldValue.delete()
|
|
416
|
+
}, {
|
|
417
|
+
merge: true
|
|
418
|
+
});
|
|
419
|
+
return {
|
|
420
|
+
outcome: plan.key === 'none' ? 'cleared' : 'applied',
|
|
421
|
+
plan,
|
|
422
|
+
problem: plan.problem
|
|
423
|
+
};
|
|
424
|
+
} catch (error) {
|
|
425
|
+
const problem = error instanceof PosReaderSplashRenderError ? error.message : 'The start-up screen could not be applied. Try again.';
|
|
426
|
+
if (!(error instanceof PosReaderSplashRenderError)) console.error('[pos-reader-splash]', hostId, error);
|
|
427
|
+
await ref.set({
|
|
428
|
+
splashProblem: problem,
|
|
429
|
+
splashFailedAtMs: Date.now()
|
|
430
|
+
}, {
|
|
431
|
+
merge: true
|
|
432
|
+
}).catch(()=>undefined);
|
|
433
|
+
return {
|
|
434
|
+
outcome: 'failed',
|
|
435
|
+
plan,
|
|
436
|
+
problem
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/*==========================================
|
|
441
|
+
* `POST /api/commerce/pos-reader-splash`: the POS devices card's setting.
|
|
442
|
+
*
|
|
443
|
+
* status the store's choice and what it shows (re-applies a stale one)
|
|
444
|
+
* save { choice, mediaScope?, mediaId? } — store and apply
|
|
445
|
+
*
|
|
446
|
+
* Gated like a sale (`authorizePosStaff`), as every reader control is.
|
|
447
|
+
*=========================================*/ function statusOf(plan, config, hostId, problem) {
|
|
448
|
+
var _ref, _ref1, _ref2, _ref3, _ref4, _resolveMediaSrc, _resolveMediaSrc1;
|
|
449
|
+
return {
|
|
450
|
+
available: posTerminalAvailable(),
|
|
451
|
+
choice: (_ref = plan == null ? void 0 : plan.choice) != null ? _ref : null,
|
|
452
|
+
effectiveChoice: (_ref1 = plan == null ? void 0 : plan.effectiveChoice) != null ? _ref1 : 'platform',
|
|
453
|
+
whiteLabel: (_ref2 = plan == null ? void 0 : plan.whiteLabel) != null ? _ref2 : false,
|
|
454
|
+
productName: (_ref3 = plan == null ? void 0 : plan.productName) != null ? _ref3 : '',
|
|
455
|
+
platformReady: (_ref4 = plan == null ? void 0 : plan.platformReady) != null ? _ref4 : false,
|
|
456
|
+
logoUrl: (plan == null ? void 0 : plan.logoValue) ? (_resolveMediaSrc = resolveMediaSrc(plan.logoValue, {
|
|
457
|
+
hostId
|
|
458
|
+
})) != null ? _resolveMediaSrc : null : null,
|
|
459
|
+
image: (plan == null ? void 0 : plan.image) ? _extends({}, plan.image, {
|
|
460
|
+
url: (_resolveMediaSrc1 = resolveMediaSrc(formatMediaRef(plan.image.mediaScope, plan.image.mediaId), {
|
|
461
|
+
hostId
|
|
462
|
+
})) != null ? _resolveMediaSrc1 : null
|
|
463
|
+
}) : null,
|
|
464
|
+
locationReady: Boolean(config == null ? void 0 : config['stripeLocationId']),
|
|
465
|
+
appliedAtMs: typeof (config == null ? void 0 : config['splashAppliedAtMs']) === 'number' ? config['splashAppliedAtMs'] : null,
|
|
466
|
+
problem: problem != null ? problem : typeof (config == null ? void 0 : config['splashProblem']) === 'string' ? config['splashProblem'] : null
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
async function currentStatus(hostId, org) {
|
|
470
|
+
var _result_plan;
|
|
471
|
+
const result = await applyPosReaderSplash(hostId, {
|
|
472
|
+
org
|
|
473
|
+
});
|
|
474
|
+
const config = (await terminalConfigRef(hostId).get()).data();
|
|
475
|
+
const plan = (_result_plan = result.plan) != null ? _result_plan : await planPosReaderSplash(hostId, org, config);
|
|
476
|
+
return statusOf(plan, config, hostId, result.outcome === 'failed' ? result.problem : plan.problem);
|
|
477
|
+
}
|
|
478
|
+
export const posReaderSplashHandler = async (req, res)=>{
|
|
479
|
+
var _body_hostId, _body_action;
|
|
480
|
+
if (req.method !== 'POST') return res.status(405).json({
|
|
481
|
+
error: 'Method not allowed'
|
|
482
|
+
});
|
|
483
|
+
const body = posRequestBody(req);
|
|
484
|
+
const hostId = String((_body_hostId = body['hostId']) != null ? _body_hostId : '');
|
|
485
|
+
const gate = await authorizePosStaff(req, hostId);
|
|
486
|
+
if ('error' in gate) return res.status(gate.status).json({
|
|
487
|
+
error: gate.error
|
|
488
|
+
});
|
|
489
|
+
const staff = gate.staff;
|
|
490
|
+
const org = staff.org ? _extends({}, staff.org, {
|
|
491
|
+
id: staff.orgId
|
|
492
|
+
}) : null;
|
|
493
|
+
const action = String((_body_action = body['action']) != null ? _body_action : '');
|
|
494
|
+
try {
|
|
495
|
+
if (action === 'status') return res.status(200).json(await currentStatus(hostId, org));
|
|
496
|
+
if (action !== 'save') return res.status(400).json({
|
|
497
|
+
error: 'Unknown action'
|
|
498
|
+
});
|
|
499
|
+
if (!posTerminalAvailable()) return res.status(409).json({
|
|
500
|
+
error: 'Card readers are not available yet.'
|
|
501
|
+
});
|
|
502
|
+
const choice = body['choice'];
|
|
503
|
+
if (!isPosReaderSplashChoice(choice)) return res.status(400).json({
|
|
504
|
+
error: 'Choose what your readers show.'
|
|
505
|
+
});
|
|
506
|
+
const whiteLabel = checkEntitlement(staff.org, 'whiteLabel');
|
|
507
|
+
if (choice === 'platform' && whiteLabel) {
|
|
508
|
+
return res.status(400).json({
|
|
509
|
+
error: 'Choose your logo or your own image.'
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
const setting = {
|
|
513
|
+
choice,
|
|
514
|
+
updatedAtMs: Date.now(),
|
|
515
|
+
updatedBy: staff.uid
|
|
516
|
+
};
|
|
517
|
+
if (choice === 'image') {
|
|
518
|
+
var _body_mediaScope, _body_mediaId, _located_doc_contentType;
|
|
519
|
+
const mediaScope = String((_body_mediaScope = body['mediaScope']) != null ? _body_mediaScope : '');
|
|
520
|
+
const mediaId = String((_body_mediaId = body['mediaId']) != null ? _body_mediaId : '');
|
|
521
|
+
const located = await locateMediaAsset(mediaScope, mediaId, hostId, staff.orgId);
|
|
522
|
+
if (!located) return res.status(400).json({
|
|
523
|
+
error: 'Choose an image from your media library.'
|
|
524
|
+
});
|
|
525
|
+
const contentType = String((_located_doc_contentType = located.doc['contentType']) != null ? _located_doc_contentType : '');
|
|
526
|
+
if (!POS_READER_SPLASH_SOURCE_TYPES.includes(contentType)) {
|
|
527
|
+
return res.status(400).json({
|
|
528
|
+
error: 'Choose a PNG, JPEG or WebP image.'
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
setting.mediaScope = mediaScope;
|
|
532
|
+
setting.mediaId = mediaId;
|
|
533
|
+
}
|
|
534
|
+
await terminalConfigRef(hostId).set({
|
|
535
|
+
splash: setting
|
|
536
|
+
}, {
|
|
537
|
+
merge: true
|
|
538
|
+
});
|
|
539
|
+
return res.status(200).json(await currentStatus(hostId, org));
|
|
540
|
+
} catch (error) {
|
|
541
|
+
console.error('[pos-reader-splash]', action, error);
|
|
542
|
+
return res.status(500).json({
|
|
543
|
+
error: 'The start-up screen could not be updated'
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
//# sourceMappingURL=pos-reader-splash.js.map
|