@aglyn/plugins-commerce 1.0.0-beta.289 → 1.0.0-beta.290
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/README.md +2 -2
- package/package.json +13 -13
- package/src/lib/components/console/commerce-pdf-download.d.ts +30 -0
- package/src/lib/components/console/commerce-pdf-download.js +53 -0
- package/src/lib/components/console/commerce-pdf-download.js.map +1 -0
- package/src/lib/components/console/host-quotes-card.component.js +20 -4
- package/src/lib/components/console/host-quotes-card.component.js.map +1 -1
- package/src/lib/components/console/order-invoice.component.d.ts +6 -4
- package/src/lib/components/console/order-invoice.component.js +67 -11
- package/src/lib/components/console/order-invoice.component.js.map +1 -1
- package/src/lib/components/console/quote-editor-dialog.component.js +53 -2
- package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/quote-proofs.component.d.ts +37 -0
- package/src/lib/components/console/quote-proofs.component.js +626 -0
- package/src/lib/components/console/quote-proofs.component.js.map +1 -0
- package/src/lib/components/quote-page-body.js +29 -1
- package/src/lib/components/quote-page-body.js.map +1 -1
- package/src/lib/components/quote-proof-panel.d.ts +42 -0
- package/src/lib/components/quote-proof-panel.js +436 -0
- package/src/lib/components/quote-proof-panel.js.map +1 -0
- package/src/lib/model/commerce-quote-pay.d.ts +27 -0
- package/src/lib/model/commerce-quote-pay.js +36 -3
- package/src/lib/model/commerce-quote-pay.js.map +1 -1
- package/src/lib/model/commerce-quote-proofs.d.ts +159 -0
- package/src/lib/model/commerce-quote-proofs.js +348 -0
- package/src/lib/model/commerce-quote-proofs.js.map +1 -0
- package/src/lib/model/commerce-quotes.d.ts +59 -3
- package/src/lib/model/commerce-quotes.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/server/commerce-pdf.d.ts +100 -0
- package/src/lib/server/commerce-pdf.js +283 -0
- package/src/lib/server/commerce-pdf.js.map +1 -0
- package/src/lib/server/order-print.d.ts +6 -0
- package/src/lib/server/order-print.js +22 -5
- package/src/lib/server/order-print.js.map +1 -1
- package/src/lib/server/pdf-response.d.ts +31 -0
- package/src/lib/server/pdf-response.js +30 -0
- package/src/lib/server/pdf-response.js.map +1 -0
- package/src/lib/server/pos-reader-splash.d.ts +2 -18
- package/src/lib/server/pos-reader-splash.js +8 -93
- package/src/lib/server/pos-reader-splash.js.map +1 -1
- package/src/lib/server/quote-pay.d.ts +31 -4
- package/src/lib/server/quote-pay.js +11 -11
- package/src/lib/server/quote-pay.js.map +1 -1
- package/src/lib/server/quote-proofs.d.ts +51 -0
- package/src/lib/server/quote-proofs.js +500 -0
- package/src/lib/server/quote-proofs.js.map +1 -0
- package/src/lib/server/quote-uploads.d.ts +33 -0
- package/src/lib/server/quote-uploads.js +29 -0
- package/src/lib/server/quote-uploads.js.map +1 -0
- package/src/lib/server/quote.d.ts +14 -0
- package/src/lib/server/quote.js +336 -3
- package/src/lib/server/quote.js.map +1 -1
- package/src/lib/server/site-image-source.d.ts +38 -0
- package/src/lib/server/site-image-source.js +130 -0
- package/src/lib/server/site-image-source.js.map +1 -0
- package/src/lib/server.js +19 -1
- package/src/lib/server.js.map +1 -1
- package/src/lib/utils/commerce-document-pdf.d.ts +77 -0
- package/src/lib/utils/commerce-document-pdf.js +608 -0
- package/src/lib/utils/commerce-document-pdf.js.map +1 -0
- package/src/lib/utils/order-invoice.d.ts +52 -3
- package/src/lib/utils/order-invoice.js +116 -64
- package/src/lib/utils/order-invoice.js.map +1 -1
|
@@ -15,10 +15,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/ import { createHash } from "node:crypto";
|
|
18
|
-
import {
|
|
19
|
-
import { formatMediaRef, isMediaCdnPath, MEDIA_CDN_ROUTE, mediaRefFromStorageUrl, parseMediaRef, resolveMediaSrc } from "@aglyn/aglyn/app-utils/media-ref";
|
|
18
|
+
import { formatMediaRef, resolveMediaSrc } from "@aglyn/aglyn/app-utils/media-ref";
|
|
20
19
|
import { checkEntitlement, resolveBrandingProfile } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
21
|
-
import { visibleToHost } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
22
20
|
import { firebaseAdmin, getOrgDoc, getOrgForHost } from "@aglyn/tenant-data-admin";
|
|
23
21
|
import { siteLockdownJobGate } from "@aglyn/tenant-data-admin/server/tenant-write-lockdown";
|
|
24
22
|
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";
|
|
@@ -26,6 +24,7 @@ import { authorizePosStaff, posRequestBody } from "./pos-auth.js";
|
|
|
26
24
|
import { PosReaderSplashRenderError, posReaderSplashImageFacts, renderPosReaderSplash } from "./pos-reader-splash-render.js";
|
|
27
25
|
import { POS_STRIPE_VERSION, posTerminalAvailable } from "./pos-stripe.js";
|
|
28
26
|
import { savePosReaderConfiguration } from "./pos-terminal.js";
|
|
27
|
+
import { locateImageSource, locateMediaImage, readImageSourceBytes } from "./site-image-source.js";
|
|
29
28
|
/*==========================================
|
|
30
29
|
* A STORE'S CARD READERS SHOW ITS START-UP SCREEN (AGL-4048).
|
|
31
30
|
*
|
|
@@ -107,96 +106,12 @@ function platformSplashVersion(doc) {
|
|
|
107
106
|
}
|
|
108
107
|
const digest = (value)=>createHash('sha256').update(value).digest('hex');
|
|
109
108
|
const cleanString = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const snapshot = await firestore().collection(scope.isOrg ? 'orgs' : 'hosts').doc(scope.scopeId).collection('media').doc(mediaId).get();
|
|
117
|
-
const doc = snapshot.exists ? snapshot.data() : null;
|
|
118
|
-
if (!doc || doc['deletedAt'] || doc['private']) return null;
|
|
119
|
-
if (scope.isOrg && !visibleToHost(doc['visibleTo'], hostId)) return null;
|
|
120
|
-
if (!/^image\//.test(String((_doc_contentType = doc['contentType']) != null ? _doc_contentType : ''))) return null;
|
|
121
|
-
if (typeof doc['sizeBytes'] === 'number' && doc['sizeBytes'] > POS_READER_SPLASH_SOURCE_MAX_BYTES) return null;
|
|
122
|
-
const version = cleanString(doc['contentSha256']) || cleanString(doc['contentHash']) || String((_ref = (_doc_updatedAtMs = doc['updatedAtMs']) != null ? _doc_updatedAtMs : doc['sizeBytes']) != null ? _ref : '');
|
|
123
|
-
return {
|
|
124
|
-
kind: 'asset',
|
|
125
|
-
scope,
|
|
126
|
-
scopeSegment,
|
|
127
|
-
mediaId,
|
|
128
|
-
doc,
|
|
129
|
-
version
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
/** Where a stored logo or image value lives, or null when it cannot be read. */ async function locateSource(value, hostId, orgId) {
|
|
133
|
-
const stored = parseMediaRef(value) ? value : mediaRefFromStorageUrl(value);
|
|
134
|
-
const ref = stored ? parseMediaRef(stored) : null;
|
|
135
|
-
if (ref) return locateMediaAsset(ref.scope, ref.mediaId, hostId, orgId);
|
|
136
|
-
if (isMediaCdnPath(value)) {
|
|
137
|
-
const rest = value.slice(`${MEDIA_CDN_ROUTE}/`.length).split('?')[0];
|
|
138
|
-
const slash = rest.indexOf('/');
|
|
139
|
-
if (slash <= 0) return null;
|
|
140
|
-
return locateMediaAsset(rest.slice(0, slash), rest.slice(slash + 1), hostId, orgId);
|
|
141
|
-
}
|
|
142
|
-
// `isMediaCdnPath` narrows a string it refuses to `never`; the value is still the string.
|
|
143
|
-
const address = value;
|
|
144
|
-
if (/^https:\/\//.test(address) && address.length <= 2000) return {
|
|
145
|
-
kind: 'url',
|
|
146
|
-
url: address,
|
|
147
|
-
version: address
|
|
148
|
-
};
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
async function readAssetBytes(asset) {
|
|
152
|
-
const { mediaCdnServeBlock, mediaStoragePathInScope } = await import("./pos-reader-splash-server.js");
|
|
153
|
-
const blocked = await mediaCdnServeBlock(asset.scope, {
|
|
154
|
-
contentSha256: typeof asset.doc['contentSha256'] === 'string' ? asset.doc['contentSha256'] : undefined,
|
|
155
|
-
contentHash: typeof asset.doc['contentHash'] === 'string' ? asset.doc['contentHash'] : undefined,
|
|
156
|
-
scopeSegment: asset.scopeSegment,
|
|
157
|
-
mediaId: asset.mediaId
|
|
158
|
-
});
|
|
159
|
-
if (blocked) return null;
|
|
160
|
-
const objectPath = mediaStoragePathInScope({
|
|
161
|
-
storagePath: asset.doc['storagePath'],
|
|
162
|
-
base: `${asset.scope.isOrg ? 'orgs' : 'hosts'}/${asset.scope.scopeId}`,
|
|
163
|
-
mediaId: asset.mediaId
|
|
164
|
-
});
|
|
165
|
-
const [buffer] = await posReaderSplashBucket().file(objectPath).download();
|
|
166
|
-
return buffer.length > POS_READER_SPLASH_SOURCE_MAX_BYTES ? null : buffer;
|
|
167
|
-
}
|
|
168
|
-
const FETCH_TIMEOUT_MS = 8000;
|
|
169
|
-
async function fetchPublicImage(url) {
|
|
170
|
-
const { createPinnedDispatcher, resolvePublicIp } = await import("./pos-reader-splash-server.js");
|
|
171
|
-
const pinnedIp = await resolvePublicIp(new URL(url).hostname);
|
|
172
|
-
if (!pinnedIp) return null;
|
|
173
|
-
const dispatcher = createPinnedDispatcher(pinnedIp);
|
|
174
|
-
const controller = new AbortController();
|
|
175
|
-
const timeout = setTimeout(()=>controller.abort(), FETCH_TIMEOUT_MS);
|
|
176
|
-
try {
|
|
177
|
-
var _response_headers_get, _response_headers_get1;
|
|
178
|
-
const init = {
|
|
179
|
-
redirect: 'error',
|
|
180
|
-
signal: controller.signal,
|
|
181
|
-
dispatcher
|
|
182
|
-
};
|
|
183
|
-
const response = await fetch(url, init);
|
|
184
|
-
if (!response.ok) return null;
|
|
185
|
-
if (!/^image\//.test((_response_headers_get = response.headers.get('content-type')) != null ? _response_headers_get : '')) return null;
|
|
186
|
-
const declared = Number((_response_headers_get1 = response.headers.get('content-length')) != null ? _response_headers_get1 : 0);
|
|
187
|
-
if (declared > POS_READER_SPLASH_SOURCE_MAX_BYTES) return null;
|
|
188
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
189
|
-
return buffer.length > POS_READER_SPLASH_SOURCE_MAX_BYTES ? null : buffer;
|
|
190
|
-
} catch (unused) {
|
|
191
|
-
return null;
|
|
192
|
-
} finally{
|
|
193
|
-
clearTimeout(timeout);
|
|
194
|
-
dispatcher.close == null ? void 0 : dispatcher.close.call(dispatcher).catch(()=>undefined);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
async function readSourceBytes(source) {
|
|
198
|
-
return source.kind === 'asset' ? readAssetBytes(source) : fetchPublicImage(source.url);
|
|
199
|
-
}
|
|
109
|
+
/*==========================================
|
|
110
|
+
* READING A SOURCE IMAGE: `site-image-source.ts`, with the reader screen's
|
|
111
|
+
* size cap.
|
|
112
|
+
*=========================================*/ const locateMediaAsset = (scopeSegment, mediaId, hostId, orgId)=>locateMediaImage(scopeSegment, mediaId, hostId, orgId, POS_READER_SPLASH_SOURCE_MAX_BYTES);
|
|
113
|
+
const locateSource = (value, hostId, orgId)=>locateImageSource(value, hostId, orgId, POS_READER_SPLASH_SOURCE_MAX_BYTES);
|
|
114
|
+
const readSourceBytes = (source)=>readImageSourceBytes(source, POS_READER_SPLASH_SOURCE_MAX_BYTES);
|
|
200
115
|
/*==========================================
|
|
201
116
|
* WHAT A STORE SHOWS.
|
|
202
117
|
*=========================================*/ /**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-reader-splash.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHash } from 'node:crypto'\nimport { parseMediaCdnScope, type MediaCdnScope } from '@aglyn/aglyn/app-utils/media-cdn-scope'\nimport {\n formatMediaRef,\n isMediaCdnPath,\n MEDIA_CDN_ROUTE,\n mediaRefFromStorageUrl,\n parseMediaRef,\n resolveMediaSrc,\n} from '@aglyn/aglyn/app-utils/media-ref'\nimport { checkEntitlement, resolveBrandingProfile } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport type { PluginEventPayloads } from '@aglyn/aglyn/plugin-manager/plugin-events'\nimport type { PluginJobHostGate } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getOrgDoc, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { siteLockdownJobGate } from '@aglyn/tenant-data-admin/server/tenant-write-lockdown'\nimport {\n isPosReaderSplashChoice,\n POS_READER_SPLASH_DEVICE_TYPES,\n POS_READER_SPLASH_FAMILIES,\n POS_READER_SPLASH_SOURCE_MAX_BYTES,\n POS_READER_SPLASH_SOURCE_TYPES,\n posReaderSplashArtworkProblem,\n posReaderSplashEffectiveChoice,\n type PosReaderSplashChoice,\n type PosReaderSplashFamily,\n type PosReaderSplashFamilyId,\n type PosReaderSplashSetting,\n type PosReaderSplashStatus,\n} from '../model/pos-reader-splash'\nimport { authorizePosStaff, posRequestBody } from './pos-auth'\nimport {\n PosReaderSplashRenderError,\n posReaderSplashImageFacts,\n renderPosReaderSplash,\n type RenderedPosReaderSplash,\n} from './pos-reader-splash-render'\nimport { POS_STRIPE_VERSION, posTerminalAvailable } from './pos-stripe'\nimport { savePosReaderConfiguration } from './pos-terminal'\n\n/*==========================================\n * A STORE'S CARD READERS SHOW ITS START-UP SCREEN (AGL-4048).\n *\n * ## Where it lives\n *\n * Readers and Locations are the PLATFORM's (the destination-charge model,\n * `pos-terminal.ts`), one Location per site. The screen is set on that\n * site's one Terminal Configuration, the one reader tipping already uses,\n * per device type (`stripe_s700`, `stripe_s710`, `bbpos_wisepos_e`), from a\n * File uploaded to Stripe with purpose `terminal_reader_splashscreen`.\n *\n * The platform's account-default configuration is NEVER given a screen. A\n * Location without an override inherits it, so an account default would put\n * the platform's brand on a white-label store's readers, which is the one\n * thing the white-label rule forbids. Staff's default screen is applied per\n * Location instead, to the stores that use it.\n *\n * ## What is applied\n *\n * `hosts/{hostId}/terminal/config.splash` holds the store's choice\n * (`model/pos-reader-splash.ts`); the screen applied is described by a KEY\n * built from cheap metadata only: the choice, the platform default's\n * version, the logo's address and its asset's content digest, the picked\n * image's. `splashAppliedKey` records the last key that reached Stripe, so\n * applying again with nothing changed makes no Stripe call at all, and a\n * changed logo, a new staff default or a new choice makes exactly one round\n * of uploads. Every upload and the first configuration write are keyed by\n * content, so a retried apply never makes a second file or configuration.\n *\n * ## When it is applied\n *\n * - when the store's Location is created (its first reader);\n * - when the POS devices card saves a choice, or opens and finds the key\n * stale (a logo changed since);\n * - when staff change the default, for every store with a Location\n * (`pos-reader-splash-admin.ts`);\n * - when a logo is saved on the organization profile or the brand, for\n * every store of that workspace with a Location, once the save has\n * answered: core's `org.logo.changed` event ({@link onOrgLogoChanged});\n * - on the commerce plugin's `pos-reader-splash-refresh` beat, for every\n * store with a Location whose key went stale with nobody opening the\n * card: the backstop for an event that failed, and the only path for a\n * logo's file replaced in the media library ({@link refreshPosReaderSplashes}).\n *\n * Never on the payment path, and never IN FRONT of a logo's save: drawing\n * and uploading takes a second or two, and a reader with an older screen\n * still takes the payment.\n *\n * ## Test mode first\n *\n * Gated by `posTerminalAvailable()`: always in test mode, in live mode only\n * once `STRIPE_TERMINAL_LIVE_ENABLED` is on, like every reader control.\n *=========================================*/\n\n/** Raised when the drawing or the Location changes how keys are built. */\nconst RENDER_VERSION = 'v1'\n\n/** `platformSettings/posReaderSplash`: staff's default screen. */\nexport const POS_READER_SPLASH_SETTINGS_ID = 'posReaderSplash'\n\n/** One family's default screen, as staff uploaded it. */\nexport interface PlatformSplashImage {\n storagePath: string\n sha256: string\n contentType: 'image/png' | 'image/jpeg'\n bytes: number\n uploadedAtMs: number\n uploadedBy?: string\n}\n\nexport interface PlatformSplashDoc {\n families?: Partial<Record<PosReaderSplashFamilyId, PlatformSplashImage>>\n updatedAtMs?: number\n}\n\nconst firestore = () => firebaseAdmin.app().firestore()\n\nexport function terminalConfigRef(hostId: string) {\n return firestore().collection('hosts').doc(hostId).collection('terminal').doc('config')\n}\n\nexport function platformSplashRef() {\n return firestore().collection('platformSettings').doc(POS_READER_SPLASH_SETTINGS_ID)\n}\n\nexport function posReaderSplashBucket() {\n return firebaseAdmin\n .app()\n .storage()\n .bucket(process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET || undefined)\n}\n\nexport async function readPlatformSplash(): Promise<PlatformSplashDoc> {\n const snapshot = await platformSplashRef().get()\n return (snapshot.exists ? snapshot.data() : {}) as PlatformSplashDoc\n}\n\n/** The default is usable once the S700/S710 screen is uploaded; the smaller one is drawn from it if absent. */\nexport function platformSplashReady(doc: PlatformSplashDoc): boolean {\n return Boolean(doc.families?.['portrait-large']?.storagePath)\n}\n\nfunction platformSplashVersion(doc: PlatformSplashDoc): string {\n return POS_READER_SPLASH_FAMILIES.map((family) => doc.families?.[family.id]?.sha256 ?? '-').join('.')\n}\n\nconst digest = (value: string | Buffer) => createHash('sha256').update(value).digest('hex')\n\nconst cleanString = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/*==========================================\n * READING A SOURCE IMAGE.\n *\n * A media value is read from STORAGE, never fetched, when it names this\n * site's library or its org's: the document must be live, not private,\n * visible to this site, its scope not locked and the asset not quarantined,\n * and the object path inside the scope's media prefix (the rules the media\n * CDN serves by). An https address outside the libraries is fetched only from\n * a public address, pinned against DNS rebinding, with no redirects, a\n * timeout and a size cap.\n *=========================================*/\n\ninterface LocatedAsset {\n kind: 'asset'\n scope: MediaCdnScope\n scopeSegment: string\n mediaId: string\n doc: Record<string, unknown>\n /** What changes when the asset's bytes do. */\n version: string\n}\n\ninterface LocatedUrl {\n kind: 'url'\n url: string\n version: string\n}\n\ntype LocatedSource = LocatedAsset | LocatedUrl\n\nasync function locateMediaAsset(\n scopeSegment: string,\n mediaId: string,\n hostId: string,\n orgId: string,\n): Promise<LocatedAsset | null> {\n const scope = parseMediaCdnScope(scopeSegment)\n if (!scope || !/^[A-Za-z0-9_-]{1,64}$/.test(mediaId)) return null\n const owned = scope.isOrg ? scope.scopeId === orgId : scope.scopeId === hostId\n if (!owned) return null\n const snapshot = await firestore()\n .collection(scope.isOrg ? 'orgs' : 'hosts')\n .doc(scope.scopeId)\n .collection('media')\n .doc(mediaId)\n .get()\n const doc = (snapshot.exists ? snapshot.data() : null) as Record<string, unknown> | null\n if (!doc || doc['deletedAt'] || doc['private']) return null\n if (scope.isOrg && !visibleToHost(doc['visibleTo'] as string[] | undefined, hostId)) return null\n if (!/^image\\//.test(String(doc['contentType'] ?? ''))) return null\n if (typeof doc['sizeBytes'] === 'number' && doc['sizeBytes'] > POS_READER_SPLASH_SOURCE_MAX_BYTES) return null\n const version =\n cleanString(doc['contentSha256']) ||\n cleanString(doc['contentHash']) ||\n String(doc['updatedAtMs'] ?? doc['sizeBytes'] ?? '')\n return { kind: 'asset', scope, scopeSegment, mediaId, doc, version }\n}\n\n/** Where a stored logo or image value lives, or null when it cannot be read. */\nasync function locateSource(value: string, hostId: string, orgId: string): Promise<LocatedSource | null> {\n const stored = parseMediaRef(value) ? value : mediaRefFromStorageUrl(value)\n const ref = stored ? parseMediaRef(stored) : null\n if (ref) return locateMediaAsset(ref.scope, ref.mediaId, hostId, orgId)\n if (isMediaCdnPath(value)) {\n const rest = value.slice(`${MEDIA_CDN_ROUTE}/`.length).split('?')[0]\n const slash = rest.indexOf('/')\n if (slash <= 0) return null\n return locateMediaAsset(rest.slice(0, slash), rest.slice(slash + 1), hostId, orgId)\n }\n // `isMediaCdnPath` narrows a string it refuses to `never`; the value is still the string.\n const address: string = value as string\n if (/^https:\\/\\//.test(address) && address.length <= 2000) return { kind: 'url', url: address, version: address }\n return null\n}\n\nasync function readAssetBytes(asset: LocatedAsset): Promise<Buffer | null> {\n const { mediaCdnServeBlock, mediaStoragePathInScope } = await import('./pos-reader-splash-server')\n const blocked = await mediaCdnServeBlock(asset.scope, {\n contentSha256: typeof asset.doc['contentSha256'] === 'string' ? (asset.doc['contentSha256'] as string) : undefined,\n contentHash: typeof asset.doc['contentHash'] === 'string' ? (asset.doc['contentHash'] as string) : undefined,\n scopeSegment: asset.scopeSegment,\n mediaId: asset.mediaId,\n })\n if (blocked) return null\n const objectPath = mediaStoragePathInScope({\n storagePath: asset.doc['storagePath'],\n base: `${asset.scope.isOrg ? 'orgs' : 'hosts'}/${asset.scope.scopeId}`,\n mediaId: asset.mediaId,\n })\n const [buffer] = (await posReaderSplashBucket().file(objectPath).download()) as [Buffer]\n return buffer.length > POS_READER_SPLASH_SOURCE_MAX_BYTES ? null : buffer\n}\n\nconst FETCH_TIMEOUT_MS = 8000\n\nasync function fetchPublicImage(url: string): Promise<Buffer | null> {\n const { createPinnedDispatcher, resolvePublicIp } = await import('./pos-reader-splash-server')\n const pinnedIp = await resolvePublicIp(new URL(url).hostname)\n if (!pinnedIp) return null\n const dispatcher = createPinnedDispatcher(pinnedIp)\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)\n try {\n const init = { redirect: 'error' as const, signal: controller.signal, dispatcher }\n const response = await fetch(url, init as RequestInit)\n if (!response.ok) return null\n if (!/^image\\//.test(response.headers.get('content-type') ?? '')) return null\n const declared = Number(response.headers.get('content-length') ?? 0)\n if (declared > POS_READER_SPLASH_SOURCE_MAX_BYTES) return null\n const buffer = Buffer.from(await response.arrayBuffer())\n return buffer.length > POS_READER_SPLASH_SOURCE_MAX_BYTES ? null : buffer\n } catch {\n return null\n } finally {\n clearTimeout(timeout)\n ;(dispatcher as { close?: () => Promise<void> }).close?.().catch(() => undefined)\n }\n}\n\nasync function readSourceBytes(source: LocatedSource): Promise<Buffer | null> {\n return source.kind === 'asset' ? readAssetBytes(source) : fetchPublicImage(source.url)\n}\n\n/*==========================================\n * WHAT A STORE SHOWS.\n *=========================================*/\n\n/**\n * The workspace's own logo: a white-label workspace's brand logo first,\n * then the logo on its organization profile. Never the platform's: the\n * resolved brand profile falls back to the platform's own logo for a\n * workspace without a brand, so its `logoUrl` is not read here.\n */\nexport function workspaceLogoValue(org: Record<string, any> | null, whiteLabel: boolean): string | null {\n const brand = whiteLabel ? cleanString(org?.['brandingProfile']?.['logoUrl']) : ''\n return brand || cleanString(org?.['logoUrl']) || null\n}\n\nexport type SplashSource =\n | { mode: 'platform'; doc: PlatformSplashDoc }\n | { mode: 'logo' | 'artwork'; located: LocatedSource }\n\nexport interface PosReaderSplashPlan {\n choice: PosReaderSplashChoice | null\n effectiveChoice: PosReaderSplashChoice\n whiteLabel: boolean\n productName: string\n platformReady: boolean\n logoValue: string | null\n image: { mediaScope: string; mediaId: string } | null\n /** `none` when the readers show Stripe's standard screen. */\n key: string\n problem: string | null\n source: SplashSource | null\n}\n\nconst NO_LOGO = 'Add a logo to your organization profile to show it on your card readers.'\nconst NO_IMAGE = 'The image picked for your card readers is no longer in your media library. Choose another.'\n\nexport async function planPosReaderSplash(\n hostId: string,\n org: Record<string, any> | null,\n config: Record<string, any> | undefined,\n platform?: PlatformSplashDoc,\n): Promise<PosReaderSplashPlan> {\n const orgId = String(org?.['id'] ?? '')\n const whiteLabel = checkEntitlement(org as any, 'whiteLabel')\n const productName = resolveBrandingProfile(org as any).productName\n const setting = (config?.['splash'] ?? null) as PosReaderSplashSetting | null\n const choice = isPosReaderSplashChoice(setting?.choice) ? setting.choice : null\n const effectiveChoice = posReaderSplashEffectiveChoice(choice, whiteLabel)\n const doc = platform ?? (await readPlatformSplash())\n const logoValue = workspaceLogoValue(org, whiteLabel)\n const image =\n setting?.mediaScope && setting?.mediaId ? { mediaScope: setting.mediaScope, mediaId: setting.mediaId } : null\n const base = {\n choice,\n effectiveChoice,\n whiteLabel,\n productName,\n platformReady: platformSplashReady(doc),\n logoValue,\n image,\n }\n if (effectiveChoice === 'platform') {\n // The white-label rule is in `posReaderSplashEffectiveChoice`; this is\n // the guard behind it, because this branch is the platform's brand.\n if (whiteLabel || !platformSplashReady(doc)) return { ...base, key: 'none', problem: null, source: null }\n return {\n ...base,\n key: `platform:${RENDER_VERSION}:${platformSplashVersion(doc)}`,\n problem: null,\n source: { mode: 'platform', doc },\n }\n }\n if (effectiveChoice === 'logo') {\n const located = logoValue ? await locateSource(logoValue, hostId, orgId) : null\n if (!located) return { ...base, key: 'none', problem: NO_LOGO, source: null }\n return {\n ...base,\n key: `logo:${RENDER_VERSION}:${digest(`${logoValue}|${located.version}`).slice(0, 24)}`,\n problem: null,\n source: { mode: 'logo', located },\n }\n }\n const located = image ? await locateMediaAsset(image.mediaScope, image.mediaId, hostId, orgId) : null\n if (!located) return { ...base, key: 'none', problem: NO_IMAGE, source: null }\n return {\n ...base,\n key: `image:${RENDER_VERSION}:${digest(`${image?.mediaScope}/${image?.mediaId}|${located.version}`).slice(0, 24)}`,\n problem: null,\n source: { mode: 'artwork', located },\n }\n}\n\nasync function loadSharpFactory(): Promise<any> {\n const { loadSharp } = await import('./pos-reader-splash-server')\n return loadSharp()\n}\n\n/** Each family's screen for the plan, ready for Stripe. */\nasync function drawScreens(\n source: SplashSource,\n): Promise<Record<PosReaderSplashFamilyId, RenderedPosReaderSplash>> {\n const sharp = await loadSharpFactory()\n const screens = {} as Record<PosReaderSplashFamilyId, RenderedPosReaderSplash>\n if (source.mode === 'platform') {\n const bucket = posReaderSplashBucket()\n const read = async (image: PlatformSplashImage) =>\n ((await bucket.file(image.storagePath).download()) as [Buffer])[0]\n const large = source.doc.families?.['portrait-large'] as PlatformSplashImage\n const largeBody = await read(large)\n for (const family of POS_READER_SPLASH_FAMILIES) {\n const own = source.doc.families?.[family.id]\n // Staff's own screen for the family is sent as uploaded: it was checked\n // against Stripe's rules for exactly this family then. A family staff\n // left empty is drawn from the S700/S710 screen, the same 9:16.\n screens[family.id] = own?.storagePath\n ? { body: own === large ? largeBody : await read(own), contentType: own.contentType }\n : await renderPosReaderSplash({ source: largeBody, family, mode: 'artwork', sharp })\n }\n return screens\n }\n const bytes = await readSourceBytes(source.located)\n if (!bytes) throw new PosReaderSplashRenderError('That image could not be read.')\n if (source.mode === 'artwork') {\n const facts = await posReaderSplashImageFacts(sharp, bytes)\n const problem = posReaderSplashArtworkProblem({\n contentType: facts?.contentType || null,\n bytes: bytes.length,\n width: facts?.width ?? 0,\n height: facts?.height ?? 0,\n })\n if (problem) throw new PosReaderSplashRenderError(problem)\n }\n for (const family of POS_READER_SPLASH_FAMILIES) {\n screens[family.id] = await renderPosReaderSplash({ source: bytes, family, mode: source.mode, sharp })\n }\n return screens\n}\n\n/** One screen uploaded to Stripe, keyed by its content so a retry makes no second file. */\nasync function uploadSplashFile(\n hostId: string,\n family: PosReaderSplashFamily,\n screen: RenderedPosReaderSplash,\n): Promise<string> {\n const form = new FormData()\n form.append('purpose', 'terminal_reader_splashscreen')\n const extension = screen.contentType === 'image/png' ? 'png' : 'jpg'\n form.append('file', new Blob([new Uint8Array(screen.body)], { type: screen.contentType }), `start-up-${family.id}.${extension}`)\n const response = await fetch('https://files.stripe.com/v1/files', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,\n 'Stripe-Version': POS_STRIPE_VERSION,\n 'Idempotency-Key': `pos-splash-file:${hostId}:${family.id}:${digest(screen.body).slice(0, 32)}`,\n },\n body: form,\n })\n const body = (await response.json().catch(() => ({}))) as { id?: string; error?: { message?: string } }\n if (!response.ok || !body.id) {\n throw new PosReaderSplashRenderError(body.error?.message || 'Stripe did not accept the start-up screen.')\n }\n return String(body.id)\n}\n\n/**\n * Draws each reader family's screen from the plan's source and uploads it to\n * Stripe: the image work, which needs `sharp`. Runs in the images function\n * on Vercel (`pos-reader-splash-images-route.ts`), and in any process off it.\n */\nexport async function drawAndUploadPosReaderSplash(\n hostId: string,\n source: SplashSource,\n): Promise<Record<PosReaderSplashFamilyId, string>> {\n const screens = await drawScreens(source)\n const fileIds = {} as Record<PosReaderSplashFamilyId, string>\n for (const family of POS_READER_SPLASH_FAMILIES) {\n fileIds[family.id] = await uploadSplashFile(hostId, family, screens[family.id])\n }\n return fileIds\n}\n\n/**\n * Each family's Stripe file for the plan (AGL-4048): drawn here where this\n * process can draw, else by the images function, which draws the plan with\n * the same key. A refusal meant for the store comes back as its own problem.\n */\nasync function posReaderSplashFileIds(\n hostId: string,\n key: string,\n source: SplashSource,\n): Promise<Record<PosReaderSplashFamilyId, string>> {\n const images = await import('./pos-reader-splash-images')\n const origin = images.posReaderSplashImagesOrigin()\n if (!origin) return drawAndUploadPosReaderSplash(hostId, source)\n try {\n const answer = await images.requestPosReaderSplashImages<{ fileIds: Record<PosReaderSplashFamilyId, string> }>(\n origin,\n { op: 'draw', hostId, key },\n )\n return answer.fileIds\n } catch (error) {\n if (error instanceof images.PosReaderSplashImagesRefusal) throw new PosReaderSplashRenderError(error.message)\n throw error\n }\n}\n\n/** The org that owns the site, with its id on it (`planPosReaderSplash` reads it). */\nexport async function ownerOrgOf(hostId: string): Promise<Record<string, any> | null> {\n const owner = await getOrgForHost(hostId).catch(() => null)\n if (!owner?.org) return null\n return { ...(owner.org as Record<string, any>), id: String((owner.org as any).id ?? owner.orgId ?? '') }\n}\n\nexport interface PosReaderSplashApplyResult {\n outcome: 'unchanged' | 'applied' | 'cleared' | 'skipped' | 'failed'\n plan: PosReaderSplashPlan | null\n problem: string | null\n}\n\n/**\n * Puts the store's start-up screen on its readers' configuration when it\n * changed since it was last applied. Never throws: a failure is recorded on\n * the store's terminal document and returned, and the readers keep their\n * previous screen.\n */\nexport async function applyPosReaderSplash(\n hostId: string,\n options: {\n org?: Record<string, any> | null\n platform?: PlatformSplashDoc\n force?: boolean\n /**\n * Leave a screen that failed to apply alone until this long after the\n * failure, when it is still the same screen: a background pass must not\n * draw and upload a logo Stripe keeps refusing on every beat. The card\n * passes none, so opening it always tries again.\n */\n retryFailedAfterMs?: number\n } = {},\n): Promise<PosReaderSplashApplyResult> {\n if (!posTerminalAvailable()) return { outcome: 'skipped', plan: null, problem: null }\n const ref = terminalConfigRef(hostId)\n const config = (await ref.get()).data() as Record<string, any> | undefined\n if (!config?.['stripeLocationId']) return { outcome: 'skipped', plan: null, problem: null }\n const org = options.org !== undefined ? options.org : await ownerOrgOf(hostId)\n const plan = await planPosReaderSplash(hostId, org, config, options.platform)\n const applied = typeof config['splashAppliedKey'] === 'string' ? (config['splashAppliedKey'] as string) : null\n // Nothing to show and nothing ever shown: the readers already show\n // Stripe's standard screen, and no Stripe call is needed to keep it.\n if (plan.key === 'none' && (applied === null || applied === 'none') && !options.force) {\n return { outcome: 'unchanged', plan, problem: plan.problem }\n }\n if (plan.key === applied && !options.force) return { outcome: 'unchanged', plan, problem: plan.problem }\n const failedAtMs = typeof config['splashFailedAtMs'] === 'number' ? (config['splashFailedAtMs'] as number) : 0\n if (\n options.retryFailedAfterMs &&\n !options.force &&\n config['splashFailedKey'] === plan.key &&\n Date.now() - failedAtMs < options.retryFailedAfterMs\n ) {\n const problem = typeof config['splashProblem'] === 'string' ? (config['splashProblem'] as string) : plan.problem\n return { outcome: 'skipped', plan, problem }\n }\n try {\n const params: Record<string, string> = {}\n if (plan.key === 'none' || !plan.source) {\n // An empty value unsets the device's screen on this configuration.\n for (const deviceType of POS_READER_SPLASH_DEVICE_TYPES) params[deviceType] = ''\n } else {\n const fileIds = await posReaderSplashFileIds(hostId, plan.key, plan.source)\n for (const family of POS_READER_SPLASH_FAMILIES) {\n for (const deviceType of family.deviceTypes) params[`${deviceType}[splashscreen]`] = fileIds[family.id]\n }\n }\n const saved = await savePosReaderConfiguration(hostId, params, `splash:${digest(plan.key).slice(0, 24)}`)\n if (!saved.ok) throw new PosReaderSplashRenderError('Stripe did not accept the start-up screen. Try again.')\n await ref.set(\n {\n splashAppliedKey: plan.key,\n splashAppliedAtMs: Date.now(),\n splashProblem: firebaseAdmin.firestore.FieldValue.delete(),\n },\n { merge: true },\n )\n return { outcome: plan.key === 'none' ? 'cleared' : 'applied', plan, problem: plan.problem }\n } catch (error) {\n const problem =\n error instanceof PosReaderSplashRenderError ? error.message : 'The start-up screen could not be applied. Try again.'\n if (!(error instanceof PosReaderSplashRenderError)) console.error('[pos-reader-splash]', hostId, error)\n await ref\n .set({ splashProblem: problem, splashFailedAtMs: Date.now(), splashFailedKey: plan.key }, { merge: true })\n .catch(() => undefined)\n return { outcome: 'failed', plan, problem }\n }\n}\n\n/** How many stores one pass reaches, and how many it works on at once. */\nexport const POS_READER_SPLASH_SWEEP_WINDOW = 400\nconst POS_READER_SPLASH_SWEEP_CONCURRENCY = 4\n\nexport interface PosReaderSplashSweep {\n applied: number\n unchanged: number\n failed: number\n /** Locked stores, and screens still inside their failure's retry wait. */\n skipped: number\n /** More stores have readers than one pass reaches. */\n more: boolean\n}\n\n/**\n * Brings every store with a Location up to date with\n * {@link applyPosReaderSplash}, the call the POS devices card makes: a store\n * whose screen key has not moved costs reads and no Stripe call, and one\n * that moved gets exactly one round of uploads. At most\n * {@link POS_READER_SPLASH_SWEEP_WINDOW} stores a pass.\n */\nexport async function applyPosReaderSplashToStores(\n options: {\n platform?: PlatformSplashDoc\n /** Asked before each store; a locked store is left as it is. */\n isLocked?: (hostId: string) => Promise<boolean>\n retryFailedAfterMs?: number\n } = {},\n): Promise<PosReaderSplashSweep> {\n const counts: PosReaderSplashSweep = { applied: 0, unchanged: 0, failed: 0, skipped: 0, more: false }\n const platform = options.platform ?? (await readPlatformSplash())\n const snapshot = await firestore().collectionGroup('terminal').limit(POS_READER_SPLASH_SWEEP_WINDOW + 1).get()\n const docs = snapshot.docs as Array<{ id: string; ref: { path: string }; get: (field: string) => unknown }>\n const hostIds = docs\n .filter((doc) => doc.id === 'config' && doc.get('stripeLocationId'))\n .map((doc) => doc.ref.path.split('/')[1])\n .filter(Boolean)\n await applyPosReaderSplashQueue(hostIds.slice(0, POS_READER_SPLASH_SWEEP_WINDOW), counts, {\n ...options,\n platform,\n orgOf: ownerOrgOf,\n })\n return { ...counts, more: docs.length > POS_READER_SPLASH_SWEEP_WINDOW }\n}\n\n/** Works through `hostIds` a few at a time, counting each store's outcome into `counts`. */\nasync function applyPosReaderSplashQueue(\n hostIds: string[],\n counts: PosReaderSplashSweep,\n options: {\n platform: PlatformSplashDoc\n orgOf: (hostId: string) => Promise<Record<string, any> | null>\n isLocked?: (hostId: string) => Promise<boolean>\n retryFailedAfterMs?: number\n },\n): Promise<void> {\n const queue = [...hostIds]\n await Promise.all(\n Array.from({ length: POS_READER_SPLASH_SWEEP_CONCURRENCY }, async () => {\n for (let hostId = queue.shift(); hostId; hostId = queue.shift()) {\n if (options.isLocked && (await options.isLocked(hostId))) {\n counts.skipped += 1\n continue\n }\n const result = await applyPosReaderSplash(hostId, {\n org: await options.orgOf(hostId),\n platform: options.platform,\n ...(options.retryFailedAfterMs ? { retryFailedAfterMs: options.retryFailedAfterMs } : {}),\n })\n if (result.outcome === 'applied' || result.outcome === 'cleared') counts.applied += 1\n else if (result.outcome === 'failed') counts.failed += 1\n else if (result.outcome === 'skipped') counts.skipped += 1\n else counts.unchanged += 1\n }\n }),\n )\n}\n\n/** How long a background pass leaves a screen Stripe refused before trying it again. */\nexport const POS_READER_SPLASH_RETRY_FAILED_AFTER_MS = 6 * 60 * 60 * 1000\n\n/**\n * The `pos-reader-splash-refresh` beat (AGL-4048): a logo changed on the\n * organization profile or the white-label brand, or the logo's file replaced\n * in the media library, reaches the store's readers without anybody opening\n * the POS devices card. The screen's key already moves with each of those,\n * so the beat only has to ask; nothing waits on it, and a save never waits\n * on Stripe. Asks the runner's lockdown gate for every store.\n */\nexport async function refreshPosReaderSplashes(gate: {\n isLocked: (hostId: string) => Promise<boolean>\n}): Promise<PosReaderSplashSweep> {\n const idle: PosReaderSplashSweep = { applied: 0, unchanged: 0, failed: 0, skipped: 0, more: false }\n if (!posTerminalAvailable()) return idle\n return applyPosReaderSplashToStores({\n isLocked: (hostId) => gate.isLocked(hostId),\n retryFailedAfterMs: POS_READER_SPLASH_RETRY_FAILED_AFTER_MS,\n })\n}\n\n/**\n * Core's `org.logo.changed` (AGL-4048): a logo saved on the organization\n * profile or the white-label brand reaches the readers of every store of\n * that workspace now, not on the next beat. Raised after the save has\n * answered, so nobody waits on it.\n *\n * {@link applyPosReaderSplash} for each store, the call the POS devices card\n * makes: a store with no Location, or whose screen does not show the logo\n * that moved (a dark-mode logo, a store showing its own image), costs reads\n * and no Stripe call. A store that is locked is left alone, and a screen\n * Stripe refused waits as it does on the beat; the beat, which stays, is\n * the backstop for anything this misses. Never throws.\n */\nexport async function onOrgLogoChanged(\n payload: Pick<PluginEventPayloads['org.logo.changed'], 'orgId'>,\n gate: PluginJobHostGate = siteLockdownJobGate(),\n): Promise<PosReaderSplashSweep> {\n const counts: PosReaderSplashSweep = { applied: 0, unchanged: 0, failed: 0, skipped: 0, more: false }\n const orgId = String(payload.orgId ?? '').trim()\n if (!orgId || !posTerminalAvailable()) return counts\n try {\n const org = await getOrgDoc(orgId)\n if (!org) return counts\n const snapshot = await firestore()\n .collection('hosts')\n .where('orgId', '==', orgId)\n .limit(POS_READER_SPLASH_SWEEP_WINDOW + 1)\n .get()\n const hostIds = (snapshot.docs as Array<{ id: string }>).map((doc) => String(doc.id))\n await applyPosReaderSplashQueue(hostIds.slice(0, POS_READER_SPLASH_SWEEP_WINDOW), counts, {\n platform: await readPlatformSplash(),\n orgOf: async () => ({ ...(org as Record<string, any>), id: orgId }),\n isLocked: (hostId) => gate.isLocked(hostId),\n retryFailedAfterMs: POS_READER_SPLASH_RETRY_FAILED_AFTER_MS,\n })\n return { ...counts, more: hostIds.length > POS_READER_SPLASH_SWEEP_WINDOW }\n } catch (error) {\n console.error('[pos-reader-splash] a changed logo could not reach the readers', orgId, error)\n return counts\n }\n}\n\n/*==========================================\n * `POST /api/commerce/pos-reader-splash`: the POS devices card's setting.\n *\n * status the store's choice and what it shows (re-applies a stale one)\n * save { choice, mediaScope?, mediaId? } — store and apply\n *\n * Gated like a sale (`authorizePosStaff`), as every reader control is.\n *=========================================*/\n\nfunction statusOf(\n plan: PosReaderSplashPlan | null,\n config: Record<string, any> | undefined,\n hostId: string,\n problem: string | null,\n): PosReaderSplashStatus {\n return {\n available: posTerminalAvailable(),\n choice: plan?.choice ?? null,\n effectiveChoice: plan?.effectiveChoice ?? 'platform',\n whiteLabel: plan?.whiteLabel ?? false,\n productName: plan?.productName ?? '',\n platformReady: plan?.platformReady ?? false,\n logoUrl: plan?.logoValue ? resolveMediaSrc(plan.logoValue, { hostId }) ?? null : null,\n image: plan?.image\n ? { ...plan.image, url: resolveMediaSrc(formatMediaRef(plan.image.mediaScope, plan.image.mediaId), { hostId }) ?? null }\n : null,\n locationReady: Boolean(config?.['stripeLocationId']),\n appliedAtMs: typeof config?.['splashAppliedAtMs'] === 'number' ? config['splashAppliedAtMs'] : null,\n problem: problem ?? (typeof config?.['splashProblem'] === 'string' ? config['splashProblem'] : null),\n }\n}\n\nasync function currentStatus(hostId: string, org: Record<string, any> | null): Promise<PosReaderSplashStatus> {\n const result = await applyPosReaderSplash(hostId, { org })\n const config = (await terminalConfigRef(hostId).get()).data() as Record<string, any> | undefined\n const plan = result.plan ?? (await planPosReaderSplash(hostId, org, config))\n return statusOf(plan, config, hostId, result.outcome === 'failed' ? result.problem : plan.problem)\n}\n\nexport const posReaderSplashHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const body = posRequestBody(req)\n const hostId = String(body['hostId'] ?? '')\n const action = String(body['action'] ?? '')\n // Changing what the readers show is setting them up (AGL-4047); reading it is not.\n const gate = await authorizePosStaff(req, hostId, action === 'save' ? 'posDevices' : undefined)\n if ('error' in gate) return res.status(gate.status).json({ error: gate.error })\n const staff = gate.staff\n const org = staff.org ? { ...staff.org, id: staff.orgId } : null\n try {\n if (action === 'status') return res.status(200).json(await currentStatus(hostId, org))\n if (action !== 'save') return res.status(400).json({ error: 'Unknown action' })\n if (!posTerminalAvailable()) return res.status(409).json({ error: 'Card readers are not available yet.' })\n const choice = body['choice']\n if (!isPosReaderSplashChoice(choice)) return res.status(400).json({ error: 'Choose what your readers show.' })\n const whiteLabel = checkEntitlement(staff.org as any, 'whiteLabel')\n if (choice === 'platform' && whiteLabel) {\n return res.status(400).json({ error: 'Choose your logo or your own image.' })\n }\n const setting: PosReaderSplashSetting = { choice, updatedAtMs: Date.now(), updatedBy: staff.uid }\n if (choice === 'image') {\n const mediaScope = String(body['mediaScope'] ?? '')\n const mediaId = String(body['mediaId'] ?? '')\n const located = await locateMediaAsset(mediaScope, mediaId, hostId, staff.orgId)\n if (!located) return res.status(400).json({ error: 'Choose an image from your media library.' })\n const contentType = String(located.doc['contentType'] ?? '')\n if (!POS_READER_SPLASH_SOURCE_TYPES.includes(contentType)) {\n return res.status(400).json({ error: 'Choose a PNG, JPEG or WebP image.' })\n }\n setting.mediaScope = mediaScope\n setting.mediaId = mediaId\n }\n await terminalConfigRef(hostId).set({ splash: setting }, { merge: true })\n return res.status(200).json(await currentStatus(hostId, org))\n } catch (error) {\n console.error('[pos-reader-splash]', action, error)\n return res.status(500).json({ error: 'The start-up screen could not be updated' })\n }\n}\n"],"names":["createHash","parseMediaCdnScope","formatMediaRef","isMediaCdnPath","MEDIA_CDN_ROUTE","mediaRefFromStorageUrl","parseMediaRef","resolveMediaSrc","checkEntitlement","resolveBrandingProfile","visibleToHost","firebaseAdmin","getOrgDoc","getOrgForHost","siteLockdownJobGate","isPosReaderSplashChoice","POS_READER_SPLASH_DEVICE_TYPES","POS_READER_SPLASH_FAMILIES","POS_READER_SPLASH_SOURCE_MAX_BYTES","POS_READER_SPLASH_SOURCE_TYPES","posReaderSplashArtworkProblem","posReaderSplashEffectiveChoice","authorizePosStaff","posRequestBody","PosReaderSplashRenderError","posReaderSplashImageFacts","renderPosReaderSplash","POS_STRIPE_VERSION","posTerminalAvailable","savePosReaderConfiguration","RENDER_VERSION","POS_READER_SPLASH_SETTINGS_ID","firestore","app","terminalConfigRef","hostId","collection","doc","platformSplashRef","posReaderSplashBucket","storage","bucket","process","env","NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET","undefined","readPlatformSplash","snapshot","get","exists","data","platformSplashReady","Boolean","families","storagePath","platformSplashVersion","map","family","id","sha256","join","digest","value","update","cleanString","trim","locateMediaAsset","scopeSegment","mediaId","orgId","scope","test","owned","isOrg","scopeId","String","version","kind","locateSource","stored","ref","rest","slice","length","split","slash","indexOf","address","url","readAssetBytes","asset","mediaCdnServeBlock","mediaStoragePathInScope","blocked","contentSha256","contentHash","objectPath","base","buffer","file","download","FETCH_TIMEOUT_MS","fetchPublicImage","createPinnedDispatcher","resolvePublicIp","pinnedIp","URL","hostname","dispatcher","controller","AbortController","timeout","setTimeout","abort","response","init","redirect","signal","fetch","ok","headers","declared","Number","Buffer","from","arrayBuffer","clearTimeout","close","catch","readSourceBytes","source","workspaceLogoValue","org","whiteLabel","brand","NO_LOGO","NO_IMAGE","planPosReaderSplash","config","platform","productName","setting","choice","effectiveChoice","logoValue","image","mediaScope","platformReady","key","problem","mode","located","loadSharpFactory","loadSharp","drawScreens","sharp","screens","read","large","largeBody","own","body","contentType","bytes","facts","width","height","uploadSplashFile","screen","form","FormData","append","extension","Blob","Uint8Array","type","method","Authorization","STRIPE_SECRET_KEY","json","error","message","drawAndUploadPosReaderSplash","fileIds","posReaderSplashFileIds","images","origin","posReaderSplashImagesOrigin","answer","requestPosReaderSplashImages","op","PosReaderSplashImagesRefusal","ownerOrgOf","owner","applyPosReaderSplash","options","outcome","plan","applied","force","failedAtMs","retryFailedAfterMs","Date","now","params","deviceType","deviceTypes","saved","set","splashAppliedKey","splashAppliedAtMs","splashProblem","FieldValue","delete","merge","console","splashFailedAtMs","splashFailedKey","POS_READER_SPLASH_SWEEP_WINDOW","POS_READER_SPLASH_SWEEP_CONCURRENCY","applyPosReaderSplashToStores","counts","unchanged","failed","skipped","more","collectionGroup","limit","docs","hostIds","filter","path","applyPosReaderSplashQueue","orgOf","queue","Promise","all","Array","shift","isLocked","result","POS_READER_SPLASH_RETRY_FAILED_AFTER_MS","refreshPosReaderSplashes","gate","idle","onOrgLogoChanged","payload","where","statusOf","available","logoUrl","locationReady","appliedAtMs","currentStatus","posReaderSplashHandler","req","res","status","action","staff","updatedAtMs","updatedBy","uid","includes","splash"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-reader-splash.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHash } from 'node:crypto'\nimport { formatMediaRef, resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport { checkEntitlement, resolveBrandingProfile } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport type { PluginEventPayloads } from '@aglyn/aglyn/plugin-manager/plugin-events'\nimport type { PluginJobHostGate } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getOrgDoc, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { siteLockdownJobGate } from '@aglyn/tenant-data-admin/server/tenant-write-lockdown'\nimport {\n isPosReaderSplashChoice,\n POS_READER_SPLASH_DEVICE_TYPES,\n POS_READER_SPLASH_FAMILIES,\n POS_READER_SPLASH_SOURCE_MAX_BYTES,\n POS_READER_SPLASH_SOURCE_TYPES,\n posReaderSplashArtworkProblem,\n posReaderSplashEffectiveChoice,\n type PosReaderSplashChoice,\n type PosReaderSplashFamily,\n type PosReaderSplashFamilyId,\n type PosReaderSplashSetting,\n type PosReaderSplashStatus,\n} from '../model/pos-reader-splash'\nimport { authorizePosStaff, posRequestBody } from './pos-auth'\nimport {\n PosReaderSplashRenderError,\n posReaderSplashImageFacts,\n renderPosReaderSplash,\n type RenderedPosReaderSplash,\n} from './pos-reader-splash-render'\nimport { POS_STRIPE_VERSION, posTerminalAvailable } from './pos-stripe'\nimport { savePosReaderConfiguration } from './pos-terminal'\nimport {\n locateImageSource,\n locateMediaImage,\n readImageSourceBytes,\n type LocatedImageSource,\n} from './site-image-source'\n\n/*==========================================\n * A STORE'S CARD READERS SHOW ITS START-UP SCREEN (AGL-4048).\n *\n * ## Where it lives\n *\n * Readers and Locations are the PLATFORM's (the destination-charge model,\n * `pos-terminal.ts`), one Location per site. The screen is set on that\n * site's one Terminal Configuration, the one reader tipping already uses,\n * per device type (`stripe_s700`, `stripe_s710`, `bbpos_wisepos_e`), from a\n * File uploaded to Stripe with purpose `terminal_reader_splashscreen`.\n *\n * The platform's account-default configuration is NEVER given a screen. A\n * Location without an override inherits it, so an account default would put\n * the platform's brand on a white-label store's readers, which is the one\n * thing the white-label rule forbids. Staff's default screen is applied per\n * Location instead, to the stores that use it.\n *\n * ## What is applied\n *\n * `hosts/{hostId}/terminal/config.splash` holds the store's choice\n * (`model/pos-reader-splash.ts`); the screen applied is described by a KEY\n * built from cheap metadata only: the choice, the platform default's\n * version, the logo's address and its asset's content digest, the picked\n * image's. `splashAppliedKey` records the last key that reached Stripe, so\n * applying again with nothing changed makes no Stripe call at all, and a\n * changed logo, a new staff default or a new choice makes exactly one round\n * of uploads. Every upload and the first configuration write are keyed by\n * content, so a retried apply never makes a second file or configuration.\n *\n * ## When it is applied\n *\n * - when the store's Location is created (its first reader);\n * - when the POS devices card saves a choice, or opens and finds the key\n * stale (a logo changed since);\n * - when staff change the default, for every store with a Location\n * (`pos-reader-splash-admin.ts`);\n * - when a logo is saved on the organization profile or the brand, for\n * every store of that workspace with a Location, once the save has\n * answered: core's `org.logo.changed` event ({@link onOrgLogoChanged});\n * - on the commerce plugin's `pos-reader-splash-refresh` beat, for every\n * store with a Location whose key went stale with nobody opening the\n * card: the backstop for an event that failed, and the only path for a\n * logo's file replaced in the media library ({@link refreshPosReaderSplashes}).\n *\n * Never on the payment path, and never IN FRONT of a logo's save: drawing\n * and uploading takes a second or two, and a reader with an older screen\n * still takes the payment.\n *\n * ## Test mode first\n *\n * Gated by `posTerminalAvailable()`: always in test mode, in live mode only\n * once `STRIPE_TERMINAL_LIVE_ENABLED` is on, like every reader control.\n *=========================================*/\n\n/** Raised when the drawing or the Location changes how keys are built. */\nconst RENDER_VERSION = 'v1'\n\n/** `platformSettings/posReaderSplash`: staff's default screen. */\nexport const POS_READER_SPLASH_SETTINGS_ID = 'posReaderSplash'\n\n/** One family's default screen, as staff uploaded it. */\nexport interface PlatformSplashImage {\n storagePath: string\n sha256: string\n contentType: 'image/png' | 'image/jpeg'\n bytes: number\n uploadedAtMs: number\n uploadedBy?: string\n}\n\nexport interface PlatformSplashDoc {\n families?: Partial<Record<PosReaderSplashFamilyId, PlatformSplashImage>>\n updatedAtMs?: number\n}\n\nconst firestore = () => firebaseAdmin.app().firestore()\n\nexport function terminalConfigRef(hostId: string) {\n return firestore().collection('hosts').doc(hostId).collection('terminal').doc('config')\n}\n\nexport function platformSplashRef() {\n return firestore().collection('platformSettings').doc(POS_READER_SPLASH_SETTINGS_ID)\n}\n\nexport function posReaderSplashBucket() {\n return firebaseAdmin\n .app()\n .storage()\n .bucket(process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET || undefined)\n}\n\nexport async function readPlatformSplash(): Promise<PlatformSplashDoc> {\n const snapshot = await platformSplashRef().get()\n return (snapshot.exists ? snapshot.data() : {}) as PlatformSplashDoc\n}\n\n/** The default is usable once the S700/S710 screen is uploaded; the smaller one is drawn from it if absent. */\nexport function platformSplashReady(doc: PlatformSplashDoc): boolean {\n return Boolean(doc.families?.['portrait-large']?.storagePath)\n}\n\nfunction platformSplashVersion(doc: PlatformSplashDoc): string {\n return POS_READER_SPLASH_FAMILIES.map((family) => doc.families?.[family.id]?.sha256 ?? '-').join('.')\n}\n\nconst digest = (value: string | Buffer) => createHash('sha256').update(value).digest('hex')\n\nconst cleanString = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/*==========================================\n * READING A SOURCE IMAGE: `site-image-source.ts`, with the reader screen's\n * size cap.\n *=========================================*/\n\nconst locateMediaAsset = (scopeSegment: string, mediaId: string, hostId: string, orgId: string) =>\n locateMediaImage(scopeSegment, mediaId, hostId, orgId, POS_READER_SPLASH_SOURCE_MAX_BYTES)\n\nconst locateSource = (value: string, hostId: string, orgId: string) =>\n locateImageSource(value, hostId, orgId, POS_READER_SPLASH_SOURCE_MAX_BYTES)\n\nconst readSourceBytes = (source: LocatedImageSource) => readImageSourceBytes(source, POS_READER_SPLASH_SOURCE_MAX_BYTES)\n\n/*==========================================\n * WHAT A STORE SHOWS.\n *=========================================*/\n\n/**\n * The workspace's own logo: a white-label workspace's brand logo first,\n * then the logo on its organization profile. Never the platform's: the\n * resolved brand profile falls back to the platform's own logo for a\n * workspace without a brand, so its `logoUrl` is not read here.\n */\nexport function workspaceLogoValue(org: Record<string, any> | null, whiteLabel: boolean): string | null {\n const brand = whiteLabel ? cleanString(org?.['brandingProfile']?.['logoUrl']) : ''\n return brand || cleanString(org?.['logoUrl']) || null\n}\n\nexport type SplashSource =\n | { mode: 'platform'; doc: PlatformSplashDoc }\n | { mode: 'logo' | 'artwork'; located: LocatedImageSource }\n\nexport interface PosReaderSplashPlan {\n choice: PosReaderSplashChoice | null\n effectiveChoice: PosReaderSplashChoice\n whiteLabel: boolean\n productName: string\n platformReady: boolean\n logoValue: string | null\n image: { mediaScope: string; mediaId: string } | null\n /** `none` when the readers show Stripe's standard screen. */\n key: string\n problem: string | null\n source: SplashSource | null\n}\n\nconst NO_LOGO = 'Add a logo to your organization profile to show it on your card readers.'\nconst NO_IMAGE = 'The image picked for your card readers is no longer in your media library. Choose another.'\n\nexport async function planPosReaderSplash(\n hostId: string,\n org: Record<string, any> | null,\n config: Record<string, any> | undefined,\n platform?: PlatformSplashDoc,\n): Promise<PosReaderSplashPlan> {\n const orgId = String(org?.['id'] ?? '')\n const whiteLabel = checkEntitlement(org as any, 'whiteLabel')\n const productName = resolveBrandingProfile(org as any).productName\n const setting = (config?.['splash'] ?? null) as PosReaderSplashSetting | null\n const choice = isPosReaderSplashChoice(setting?.choice) ? setting.choice : null\n const effectiveChoice = posReaderSplashEffectiveChoice(choice, whiteLabel)\n const doc = platform ?? (await readPlatformSplash())\n const logoValue = workspaceLogoValue(org, whiteLabel)\n const image =\n setting?.mediaScope && setting?.mediaId ? { mediaScope: setting.mediaScope, mediaId: setting.mediaId } : null\n const base = {\n choice,\n effectiveChoice,\n whiteLabel,\n productName,\n platformReady: platformSplashReady(doc),\n logoValue,\n image,\n }\n if (effectiveChoice === 'platform') {\n // The white-label rule is in `posReaderSplashEffectiveChoice`; this is\n // the guard behind it, because this branch is the platform's brand.\n if (whiteLabel || !platformSplashReady(doc)) return { ...base, key: 'none', problem: null, source: null }\n return {\n ...base,\n key: `platform:${RENDER_VERSION}:${platformSplashVersion(doc)}`,\n problem: null,\n source: { mode: 'platform', doc },\n }\n }\n if (effectiveChoice === 'logo') {\n const located = logoValue ? await locateSource(logoValue, hostId, orgId) : null\n if (!located) return { ...base, key: 'none', problem: NO_LOGO, source: null }\n return {\n ...base,\n key: `logo:${RENDER_VERSION}:${digest(`${logoValue}|${located.version}`).slice(0, 24)}`,\n problem: null,\n source: { mode: 'logo', located },\n }\n }\n const located = image ? await locateMediaAsset(image.mediaScope, image.mediaId, hostId, orgId) : null\n if (!located) return { ...base, key: 'none', problem: NO_IMAGE, source: null }\n return {\n ...base,\n key: `image:${RENDER_VERSION}:${digest(`${image?.mediaScope}/${image?.mediaId}|${located.version}`).slice(0, 24)}`,\n problem: null,\n source: { mode: 'artwork', located },\n }\n}\n\nasync function loadSharpFactory(): Promise<any> {\n const { loadSharp } = await import('./pos-reader-splash-server')\n return loadSharp()\n}\n\n/** Each family's screen for the plan, ready for Stripe. */\nasync function drawScreens(\n source: SplashSource,\n): Promise<Record<PosReaderSplashFamilyId, RenderedPosReaderSplash>> {\n const sharp = await loadSharpFactory()\n const screens = {} as Record<PosReaderSplashFamilyId, RenderedPosReaderSplash>\n if (source.mode === 'platform') {\n const bucket = posReaderSplashBucket()\n const read = async (image: PlatformSplashImage) =>\n ((await bucket.file(image.storagePath).download()) as [Buffer])[0]\n const large = source.doc.families?.['portrait-large'] as PlatformSplashImage\n const largeBody = await read(large)\n for (const family of POS_READER_SPLASH_FAMILIES) {\n const own = source.doc.families?.[family.id]\n // Staff's own screen for the family is sent as uploaded: it was checked\n // against Stripe's rules for exactly this family then. A family staff\n // left empty is drawn from the S700/S710 screen, the same 9:16.\n screens[family.id] = own?.storagePath\n ? { body: own === large ? largeBody : await read(own), contentType: own.contentType }\n : await renderPosReaderSplash({ source: largeBody, family, mode: 'artwork', sharp })\n }\n return screens\n }\n const bytes = await readSourceBytes(source.located)\n if (!bytes) throw new PosReaderSplashRenderError('That image could not be read.')\n if (source.mode === 'artwork') {\n const facts = await posReaderSplashImageFacts(sharp, bytes)\n const problem = posReaderSplashArtworkProblem({\n contentType: facts?.contentType || null,\n bytes: bytes.length,\n width: facts?.width ?? 0,\n height: facts?.height ?? 0,\n })\n if (problem) throw new PosReaderSplashRenderError(problem)\n }\n for (const family of POS_READER_SPLASH_FAMILIES) {\n screens[family.id] = await renderPosReaderSplash({ source: bytes, family, mode: source.mode, sharp })\n }\n return screens\n}\n\n/** One screen uploaded to Stripe, keyed by its content so a retry makes no second file. */\nasync function uploadSplashFile(\n hostId: string,\n family: PosReaderSplashFamily,\n screen: RenderedPosReaderSplash,\n): Promise<string> {\n const form = new FormData()\n form.append('purpose', 'terminal_reader_splashscreen')\n const extension = screen.contentType === 'image/png' ? 'png' : 'jpg'\n form.append('file', new Blob([new Uint8Array(screen.body)], { type: screen.contentType }), `start-up-${family.id}.${extension}`)\n const response = await fetch('https://files.stripe.com/v1/files', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,\n 'Stripe-Version': POS_STRIPE_VERSION,\n 'Idempotency-Key': `pos-splash-file:${hostId}:${family.id}:${digest(screen.body).slice(0, 32)}`,\n },\n body: form,\n })\n const body = (await response.json().catch(() => ({}))) as { id?: string; error?: { message?: string } }\n if (!response.ok || !body.id) {\n throw new PosReaderSplashRenderError(body.error?.message || 'Stripe did not accept the start-up screen.')\n }\n return String(body.id)\n}\n\n/**\n * Draws each reader family's screen from the plan's source and uploads it to\n * Stripe: the image work, which needs `sharp`. Runs in the images function\n * on Vercel (`pos-reader-splash-images-route.ts`), and in any process off it.\n */\nexport async function drawAndUploadPosReaderSplash(\n hostId: string,\n source: SplashSource,\n): Promise<Record<PosReaderSplashFamilyId, string>> {\n const screens = await drawScreens(source)\n const fileIds = {} as Record<PosReaderSplashFamilyId, string>\n for (const family of POS_READER_SPLASH_FAMILIES) {\n fileIds[family.id] = await uploadSplashFile(hostId, family, screens[family.id])\n }\n return fileIds\n}\n\n/**\n * Each family's Stripe file for the plan (AGL-4048): drawn here where this\n * process can draw, else by the images function, which draws the plan with\n * the same key. A refusal meant for the store comes back as its own problem.\n */\nasync function posReaderSplashFileIds(\n hostId: string,\n key: string,\n source: SplashSource,\n): Promise<Record<PosReaderSplashFamilyId, string>> {\n const images = await import('./pos-reader-splash-images')\n const origin = images.posReaderSplashImagesOrigin()\n if (!origin) return drawAndUploadPosReaderSplash(hostId, source)\n try {\n const answer = await images.requestPosReaderSplashImages<{ fileIds: Record<PosReaderSplashFamilyId, string> }>(\n origin,\n { op: 'draw', hostId, key },\n )\n return answer.fileIds\n } catch (error) {\n if (error instanceof images.PosReaderSplashImagesRefusal) throw new PosReaderSplashRenderError(error.message)\n throw error\n }\n}\n\n/** The org that owns the site, with its id on it (`planPosReaderSplash` reads it). */\nexport async function ownerOrgOf(hostId: string): Promise<Record<string, any> | null> {\n const owner = await getOrgForHost(hostId).catch(() => null)\n if (!owner?.org) return null\n return { ...(owner.org as Record<string, any>), id: String((owner.org as any).id ?? owner.orgId ?? '') }\n}\n\nexport interface PosReaderSplashApplyResult {\n outcome: 'unchanged' | 'applied' | 'cleared' | 'skipped' | 'failed'\n plan: PosReaderSplashPlan | null\n problem: string | null\n}\n\n/**\n * Puts the store's start-up screen on its readers' configuration when it\n * changed since it was last applied. Never throws: a failure is recorded on\n * the store's terminal document and returned, and the readers keep their\n * previous screen.\n */\nexport async function applyPosReaderSplash(\n hostId: string,\n options: {\n org?: Record<string, any> | null\n platform?: PlatformSplashDoc\n force?: boolean\n /**\n * Leave a screen that failed to apply alone until this long after the\n * failure, when it is still the same screen: a background pass must not\n * draw and upload a logo Stripe keeps refusing on every beat. The card\n * passes none, so opening it always tries again.\n */\n retryFailedAfterMs?: number\n } = {},\n): Promise<PosReaderSplashApplyResult> {\n if (!posTerminalAvailable()) return { outcome: 'skipped', plan: null, problem: null }\n const ref = terminalConfigRef(hostId)\n const config = (await ref.get()).data() as Record<string, any> | undefined\n if (!config?.['stripeLocationId']) return { outcome: 'skipped', plan: null, problem: null }\n const org = options.org !== undefined ? options.org : await ownerOrgOf(hostId)\n const plan = await planPosReaderSplash(hostId, org, config, options.platform)\n const applied = typeof config['splashAppliedKey'] === 'string' ? (config['splashAppliedKey'] as string) : null\n // Nothing to show and nothing ever shown: the readers already show\n // Stripe's standard screen, and no Stripe call is needed to keep it.\n if (plan.key === 'none' && (applied === null || applied === 'none') && !options.force) {\n return { outcome: 'unchanged', plan, problem: plan.problem }\n }\n if (plan.key === applied && !options.force) return { outcome: 'unchanged', plan, problem: plan.problem }\n const failedAtMs = typeof config['splashFailedAtMs'] === 'number' ? (config['splashFailedAtMs'] as number) : 0\n if (\n options.retryFailedAfterMs &&\n !options.force &&\n config['splashFailedKey'] === plan.key &&\n Date.now() - failedAtMs < options.retryFailedAfterMs\n ) {\n const problem = typeof config['splashProblem'] === 'string' ? (config['splashProblem'] as string) : plan.problem\n return { outcome: 'skipped', plan, problem }\n }\n try {\n const params: Record<string, string> = {}\n if (plan.key === 'none' || !plan.source) {\n // An empty value unsets the device's screen on this configuration.\n for (const deviceType of POS_READER_SPLASH_DEVICE_TYPES) params[deviceType] = ''\n } else {\n const fileIds = await posReaderSplashFileIds(hostId, plan.key, plan.source)\n for (const family of POS_READER_SPLASH_FAMILIES) {\n for (const deviceType of family.deviceTypes) params[`${deviceType}[splashscreen]`] = fileIds[family.id]\n }\n }\n const saved = await savePosReaderConfiguration(hostId, params, `splash:${digest(plan.key).slice(0, 24)}`)\n if (!saved.ok) throw new PosReaderSplashRenderError('Stripe did not accept the start-up screen. Try again.')\n await ref.set(\n {\n splashAppliedKey: plan.key,\n splashAppliedAtMs: Date.now(),\n splashProblem: firebaseAdmin.firestore.FieldValue.delete(),\n },\n { merge: true },\n )\n return { outcome: plan.key === 'none' ? 'cleared' : 'applied', plan, problem: plan.problem }\n } catch (error) {\n const problem =\n error instanceof PosReaderSplashRenderError ? error.message : 'The start-up screen could not be applied. Try again.'\n if (!(error instanceof PosReaderSplashRenderError)) console.error('[pos-reader-splash]', hostId, error)\n await ref\n .set({ splashProblem: problem, splashFailedAtMs: Date.now(), splashFailedKey: plan.key }, { merge: true })\n .catch(() => undefined)\n return { outcome: 'failed', plan, problem }\n }\n}\n\n/** How many stores one pass reaches, and how many it works on at once. */\nexport const POS_READER_SPLASH_SWEEP_WINDOW = 400\nconst POS_READER_SPLASH_SWEEP_CONCURRENCY = 4\n\nexport interface PosReaderSplashSweep {\n applied: number\n unchanged: number\n failed: number\n /** Locked stores, and screens still inside their failure's retry wait. */\n skipped: number\n /** More stores have readers than one pass reaches. */\n more: boolean\n}\n\n/**\n * Brings every store with a Location up to date with\n * {@link applyPosReaderSplash}, the call the POS devices card makes: a store\n * whose screen key has not moved costs reads and no Stripe call, and one\n * that moved gets exactly one round of uploads. At most\n * {@link POS_READER_SPLASH_SWEEP_WINDOW} stores a pass.\n */\nexport async function applyPosReaderSplashToStores(\n options: {\n platform?: PlatformSplashDoc\n /** Asked before each store; a locked store is left as it is. */\n isLocked?: (hostId: string) => Promise<boolean>\n retryFailedAfterMs?: number\n } = {},\n): Promise<PosReaderSplashSweep> {\n const counts: PosReaderSplashSweep = { applied: 0, unchanged: 0, failed: 0, skipped: 0, more: false }\n const platform = options.platform ?? (await readPlatformSplash())\n const snapshot = await firestore().collectionGroup('terminal').limit(POS_READER_SPLASH_SWEEP_WINDOW + 1).get()\n const docs = snapshot.docs as Array<{ id: string; ref: { path: string }; get: (field: string) => unknown }>\n const hostIds = docs\n .filter((doc) => doc.id === 'config' && doc.get('stripeLocationId'))\n .map((doc) => doc.ref.path.split('/')[1])\n .filter(Boolean)\n await applyPosReaderSplashQueue(hostIds.slice(0, POS_READER_SPLASH_SWEEP_WINDOW), counts, {\n ...options,\n platform,\n orgOf: ownerOrgOf,\n })\n return { ...counts, more: docs.length > POS_READER_SPLASH_SWEEP_WINDOW }\n}\n\n/** Works through `hostIds` a few at a time, counting each store's outcome into `counts`. */\nasync function applyPosReaderSplashQueue(\n hostIds: string[],\n counts: PosReaderSplashSweep,\n options: {\n platform: PlatformSplashDoc\n orgOf: (hostId: string) => Promise<Record<string, any> | null>\n isLocked?: (hostId: string) => Promise<boolean>\n retryFailedAfterMs?: number\n },\n): Promise<void> {\n const queue = [...hostIds]\n await Promise.all(\n Array.from({ length: POS_READER_SPLASH_SWEEP_CONCURRENCY }, async () => {\n for (let hostId = queue.shift(); hostId; hostId = queue.shift()) {\n if (options.isLocked && (await options.isLocked(hostId))) {\n counts.skipped += 1\n continue\n }\n const result = await applyPosReaderSplash(hostId, {\n org: await options.orgOf(hostId),\n platform: options.platform,\n ...(options.retryFailedAfterMs ? { retryFailedAfterMs: options.retryFailedAfterMs } : {}),\n })\n if (result.outcome === 'applied' || result.outcome === 'cleared') counts.applied += 1\n else if (result.outcome === 'failed') counts.failed += 1\n else if (result.outcome === 'skipped') counts.skipped += 1\n else counts.unchanged += 1\n }\n }),\n )\n}\n\n/** How long a background pass leaves a screen Stripe refused before trying it again. */\nexport const POS_READER_SPLASH_RETRY_FAILED_AFTER_MS = 6 * 60 * 60 * 1000\n\n/**\n * The `pos-reader-splash-refresh` beat (AGL-4048): a logo changed on the\n * organization profile or the white-label brand, or the logo's file replaced\n * in the media library, reaches the store's readers without anybody opening\n * the POS devices card. The screen's key already moves with each of those,\n * so the beat only has to ask; nothing waits on it, and a save never waits\n * on Stripe. Asks the runner's lockdown gate for every store.\n */\nexport async function refreshPosReaderSplashes(gate: {\n isLocked: (hostId: string) => Promise<boolean>\n}): Promise<PosReaderSplashSweep> {\n const idle: PosReaderSplashSweep = { applied: 0, unchanged: 0, failed: 0, skipped: 0, more: false }\n if (!posTerminalAvailable()) return idle\n return applyPosReaderSplashToStores({\n isLocked: (hostId) => gate.isLocked(hostId),\n retryFailedAfterMs: POS_READER_SPLASH_RETRY_FAILED_AFTER_MS,\n })\n}\n\n/**\n * Core's `org.logo.changed` (AGL-4048): a logo saved on the organization\n * profile or the white-label brand reaches the readers of every store of\n * that workspace now, not on the next beat. Raised after the save has\n * answered, so nobody waits on it.\n *\n * {@link applyPosReaderSplash} for each store, the call the POS devices card\n * makes: a store with no Location, or whose screen does not show the logo\n * that moved (a dark-mode logo, a store showing its own image), costs reads\n * and no Stripe call. A store that is locked is left alone, and a screen\n * Stripe refused waits as it does on the beat; the beat, which stays, is\n * the backstop for anything this misses. Never throws.\n */\nexport async function onOrgLogoChanged(\n payload: Pick<PluginEventPayloads['org.logo.changed'], 'orgId'>,\n gate: PluginJobHostGate = siteLockdownJobGate(),\n): Promise<PosReaderSplashSweep> {\n const counts: PosReaderSplashSweep = { applied: 0, unchanged: 0, failed: 0, skipped: 0, more: false }\n const orgId = String(payload.orgId ?? '').trim()\n if (!orgId || !posTerminalAvailable()) return counts\n try {\n const org = await getOrgDoc(orgId)\n if (!org) return counts\n const snapshot = await firestore()\n .collection('hosts')\n .where('orgId', '==', orgId)\n .limit(POS_READER_SPLASH_SWEEP_WINDOW + 1)\n .get()\n const hostIds = (snapshot.docs as Array<{ id: string }>).map((doc) => String(doc.id))\n await applyPosReaderSplashQueue(hostIds.slice(0, POS_READER_SPLASH_SWEEP_WINDOW), counts, {\n platform: await readPlatformSplash(),\n orgOf: async () => ({ ...(org as Record<string, any>), id: orgId }),\n isLocked: (hostId) => gate.isLocked(hostId),\n retryFailedAfterMs: POS_READER_SPLASH_RETRY_FAILED_AFTER_MS,\n })\n return { ...counts, more: hostIds.length > POS_READER_SPLASH_SWEEP_WINDOW }\n } catch (error) {\n console.error('[pos-reader-splash] a changed logo could not reach the readers', orgId, error)\n return counts\n }\n}\n\n/*==========================================\n * `POST /api/commerce/pos-reader-splash`: the POS devices card's setting.\n *\n * status the store's choice and what it shows (re-applies a stale one)\n * save { choice, mediaScope?, mediaId? } — store and apply\n *\n * Gated like a sale (`authorizePosStaff`), as every reader control is.\n *=========================================*/\n\nfunction statusOf(\n plan: PosReaderSplashPlan | null,\n config: Record<string, any> | undefined,\n hostId: string,\n problem: string | null,\n): PosReaderSplashStatus {\n return {\n available: posTerminalAvailable(),\n choice: plan?.choice ?? null,\n effectiveChoice: plan?.effectiveChoice ?? 'platform',\n whiteLabel: plan?.whiteLabel ?? false,\n productName: plan?.productName ?? '',\n platformReady: plan?.platformReady ?? false,\n logoUrl: plan?.logoValue ? resolveMediaSrc(plan.logoValue, { hostId }) ?? null : null,\n image: plan?.image\n ? { ...plan.image, url: resolveMediaSrc(formatMediaRef(plan.image.mediaScope, plan.image.mediaId), { hostId }) ?? null }\n : null,\n locationReady: Boolean(config?.['stripeLocationId']),\n appliedAtMs: typeof config?.['splashAppliedAtMs'] === 'number' ? config['splashAppliedAtMs'] : null,\n problem: problem ?? (typeof config?.['splashProblem'] === 'string' ? config['splashProblem'] : null),\n }\n}\n\nasync function currentStatus(hostId: string, org: Record<string, any> | null): Promise<PosReaderSplashStatus> {\n const result = await applyPosReaderSplash(hostId, { org })\n const config = (await terminalConfigRef(hostId).get()).data() as Record<string, any> | undefined\n const plan = result.plan ?? (await planPosReaderSplash(hostId, org, config))\n return statusOf(plan, config, hostId, result.outcome === 'failed' ? result.problem : plan.problem)\n}\n\nexport const posReaderSplashHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const body = posRequestBody(req)\n const hostId = String(body['hostId'] ?? '')\n const action = String(body['action'] ?? '')\n // Changing what the readers show is setting them up (AGL-4047); reading it is not.\n const gate = await authorizePosStaff(req, hostId, action === 'save' ? 'posDevices' : undefined)\n if ('error' in gate) return res.status(gate.status).json({ error: gate.error })\n const staff = gate.staff\n const org = staff.org ? { ...staff.org, id: staff.orgId } : null\n try {\n if (action === 'status') return res.status(200).json(await currentStatus(hostId, org))\n if (action !== 'save') return res.status(400).json({ error: 'Unknown action' })\n if (!posTerminalAvailable()) return res.status(409).json({ error: 'Card readers are not available yet.' })\n const choice = body['choice']\n if (!isPosReaderSplashChoice(choice)) return res.status(400).json({ error: 'Choose what your readers show.' })\n const whiteLabel = checkEntitlement(staff.org as any, 'whiteLabel')\n if (choice === 'platform' && whiteLabel) {\n return res.status(400).json({ error: 'Choose your logo or your own image.' })\n }\n const setting: PosReaderSplashSetting = { choice, updatedAtMs: Date.now(), updatedBy: staff.uid }\n if (choice === 'image') {\n const mediaScope = String(body['mediaScope'] ?? '')\n const mediaId = String(body['mediaId'] ?? '')\n const located = await locateMediaAsset(mediaScope, mediaId, hostId, staff.orgId)\n if (!located) return res.status(400).json({ error: 'Choose an image from your media library.' })\n const contentType = String(located.doc['contentType'] ?? '')\n if (!POS_READER_SPLASH_SOURCE_TYPES.includes(contentType)) {\n return res.status(400).json({ error: 'Choose a PNG, JPEG or WebP image.' })\n }\n setting.mediaScope = mediaScope\n setting.mediaId = mediaId\n }\n await terminalConfigRef(hostId).set({ splash: setting }, { merge: true })\n return res.status(200).json(await currentStatus(hostId, org))\n } catch (error) {\n console.error('[pos-reader-splash]', action, error)\n return res.status(500).json({ error: 'The start-up screen could not be updated' })\n }\n}\n"],"names":["createHash","formatMediaRef","resolveMediaSrc","checkEntitlement","resolveBrandingProfile","firebaseAdmin","getOrgDoc","getOrgForHost","siteLockdownJobGate","isPosReaderSplashChoice","POS_READER_SPLASH_DEVICE_TYPES","POS_READER_SPLASH_FAMILIES","POS_READER_SPLASH_SOURCE_MAX_BYTES","POS_READER_SPLASH_SOURCE_TYPES","posReaderSplashArtworkProblem","posReaderSplashEffectiveChoice","authorizePosStaff","posRequestBody","PosReaderSplashRenderError","posReaderSplashImageFacts","renderPosReaderSplash","POS_STRIPE_VERSION","posTerminalAvailable","savePosReaderConfiguration","locateImageSource","locateMediaImage","readImageSourceBytes","RENDER_VERSION","POS_READER_SPLASH_SETTINGS_ID","firestore","app","terminalConfigRef","hostId","collection","doc","platformSplashRef","posReaderSplashBucket","storage","bucket","process","env","NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET","undefined","readPlatformSplash","snapshot","get","exists","data","platformSplashReady","Boolean","families","storagePath","platformSplashVersion","map","family","id","sha256","join","digest","value","update","cleanString","trim","locateMediaAsset","scopeSegment","mediaId","orgId","locateSource","readSourceBytes","source","workspaceLogoValue","org","whiteLabel","brand","NO_LOGO","NO_IMAGE","planPosReaderSplash","config","platform","String","productName","setting","choice","effectiveChoice","logoValue","image","mediaScope","base","platformReady","key","problem","mode","located","version","slice","loadSharpFactory","loadSharp","drawScreens","sharp","screens","read","file","download","large","largeBody","own","body","contentType","bytes","facts","length","width","height","uploadSplashFile","screen","form","FormData","append","extension","Blob","Uint8Array","type","response","fetch","method","headers","Authorization","STRIPE_SECRET_KEY","json","catch","ok","error","message","drawAndUploadPosReaderSplash","fileIds","posReaderSplashFileIds","images","origin","posReaderSplashImagesOrigin","answer","requestPosReaderSplashImages","op","PosReaderSplashImagesRefusal","ownerOrgOf","owner","applyPosReaderSplash","options","outcome","plan","ref","applied","force","failedAtMs","retryFailedAfterMs","Date","now","params","deviceType","deviceTypes","saved","set","splashAppliedKey","splashAppliedAtMs","splashProblem","FieldValue","delete","merge","console","splashFailedAtMs","splashFailedKey","POS_READER_SPLASH_SWEEP_WINDOW","POS_READER_SPLASH_SWEEP_CONCURRENCY","applyPosReaderSplashToStores","counts","unchanged","failed","skipped","more","collectionGroup","limit","docs","hostIds","filter","path","split","applyPosReaderSplashQueue","orgOf","queue","Promise","all","Array","from","shift","isLocked","result","POS_READER_SPLASH_RETRY_FAILED_AFTER_MS","refreshPosReaderSplashes","gate","idle","onOrgLogoChanged","payload","where","statusOf","available","logoUrl","url","locationReady","appliedAtMs","currentStatus","posReaderSplashHandler","req","res","status","action","staff","updatedAtMs","updatedBy","uid","includes","splash"],"mappings":"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"}
|
|
@@ -55,13 +55,39 @@ export interface QuotePayDeps {
|
|
|
55
55
|
now(): number;
|
|
56
56
|
}
|
|
57
57
|
export declare function defaultQuotePayDeps(): QuotePayDeps;
|
|
58
|
-
|
|
58
|
+
/** A link route's answer: a status, a JSON body and any extra headers. */
|
|
59
|
+
export type QuoteLinkOutcome = {
|
|
59
60
|
status: number;
|
|
60
61
|
body: Record<string, unknown>;
|
|
61
62
|
headers?: Record<string, string>;
|
|
62
63
|
};
|
|
64
|
+
/** The rate limit a link route spends per address per site, BEFORE the token is checked. */
|
|
65
|
+
export declare function limitedQuoteLink(deps: QuotePayDeps, req: PluginApiRequest, key: string, limit: number): Promise<QuoteLinkOutcome | null>;
|
|
66
|
+
export interface LoadedQuote {
|
|
67
|
+
hostRef: FirebaseFirestore.DocumentReference;
|
|
68
|
+
host: Record<string, unknown>;
|
|
69
|
+
order: CommerceModel.HostOrder;
|
|
70
|
+
storeName: string;
|
|
71
|
+
store: FirebaseFirestore.DocumentSnapshot;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The quote a link names, after its token: the quote, the replaced answer for
|
|
75
|
+
* a link to an older version, or the one not-found answer for everything else.
|
|
76
|
+
*/
|
|
77
|
+
export declare function loadLinkedQuote(deps: QuotePayDeps, hostId: string, quoteId: string, token: string): Promise<{
|
|
78
|
+
loaded: LoadedQuote;
|
|
79
|
+
} | {
|
|
80
|
+
outcome: QuoteLinkOutcome;
|
|
81
|
+
}>;
|
|
82
|
+
/** The link's site, quote and token from a query or a body, and whether all three are there. */
|
|
83
|
+
export declare function quoteLinkParams(source: Record<string, unknown>): {
|
|
84
|
+
hostId: string;
|
|
85
|
+
quoteId: string;
|
|
86
|
+
token: string;
|
|
87
|
+
complete: boolean;
|
|
88
|
+
};
|
|
63
89
|
/** `GET /api/commerce/quote-view`: the quote page's data. */
|
|
64
|
-
export declare function handleQuoteView(deps: QuotePayDeps, req: PluginApiRequest): Promise<
|
|
90
|
+
export declare function handleQuoteView(deps: QuotePayDeps, req: PluginApiRequest): Promise<QuoteLinkOutcome>;
|
|
65
91
|
/** The Checkout Session a quote's Pay opens, as form params. Pure over its inputs. */
|
|
66
92
|
export declare function buildQuoteSessionParams(input: {
|
|
67
93
|
hostId: string;
|
|
@@ -75,9 +101,10 @@ export declare function buildQuoteSessionParams(input: {
|
|
|
75
101
|
paymentMethods: CommerceModel.StorefrontPaymentMethodControls;
|
|
76
102
|
}): URLSearchParams;
|
|
77
103
|
/** `POST /api/commerce/quote-pay`: a fresh Checkout Session for the quote. */
|
|
78
|
-
export declare function handleQuotePay(deps: QuotePayDeps, req: PluginApiRequest): Promise<
|
|
104
|
+
export declare function handleQuotePay(deps: QuotePayDeps, req: PluginApiRequest): Promise<QuoteLinkOutcome>;
|
|
105
|
+
/** Sends a link route's answer, never cached or indexed. */
|
|
106
|
+
export declare function sendQuoteLinkOutcome(res: Parameters<PluginApiHandler>[1], outcome: QuoteLinkOutcome): void;
|
|
79
107
|
/** `GET /api/commerce/quote-view`. */
|
|
80
108
|
export declare const quoteViewHandler: PluginApiHandler;
|
|
81
109
|
/** `POST /api/commerce/quote-pay`. */
|
|
82
110
|
export declare const quotePayHandler: PluginApiHandler;
|
|
83
|
-
export {};
|
|
@@ -113,7 +113,7 @@ function headerOf(req, name) {
|
|
|
113
113
|
const value = (_headers_name_toLowerCase = headers[name.toLowerCase()]) != null ? _headers_name_toLowerCase : headers[name];
|
|
114
114
|
return String(Array.isArray(value) ? (_value_ = value[0]) != null ? _value_ : '' : value != null ? value : '').trim();
|
|
115
115
|
}
|
|
116
|
-
async function
|
|
116
|
+
/** The rate limit a link route spends per address per site, BEFORE the token is checked. */ export async function limitedQuoteLink(deps, req, key, limit) {
|
|
117
117
|
var _readClientIp;
|
|
118
118
|
var _req_socket;
|
|
119
119
|
const ip = (_readClientIp = readClientIp(req.headers, {
|
|
@@ -137,7 +137,7 @@ async function limited(deps, req, key, limit) {
|
|
|
137
137
|
/**
|
|
138
138
|
* The quote a link names, after its token: the quote, the replaced answer for
|
|
139
139
|
* a link to an older version, or the one not-found answer for everything else.
|
|
140
|
-
*/ async function loadLinkedQuote(deps, hostId, quoteId, token) {
|
|
140
|
+
*/ export async function loadLinkedQuote(deps, hostId, quoteId, token) {
|
|
141
141
|
var _hostSnapshot_data, _resolveHostToken;
|
|
142
142
|
const version = verifyQuoteLinkToken(hostId, quoteId, token);
|
|
143
143
|
if (version === null) return {
|
|
@@ -185,7 +185,7 @@ async function limited(deps, req, key, limit) {
|
|
|
185
185
|
}
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
|
-
function
|
|
188
|
+
/** The link's site, quote and token from a query or a body, and whether all three are there. */ export function quoteLinkParams(source) {
|
|
189
189
|
var _source_hostId, _source_q, _source_t;
|
|
190
190
|
const hostId = String((_source_hostId = source['hostId']) != null ? _source_hostId : '');
|
|
191
191
|
const quoteId = String((_source_q = source['q']) != null ? _source_q : '').slice(0, 200);
|
|
@@ -206,14 +206,14 @@ function linkParams(req, source) {
|
|
|
206
206
|
error: 'Method not allowed'
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
|
-
const { hostId, quoteId, token, complete } =
|
|
209
|
+
const { hostId, quoteId, token, complete } = quoteLinkParams((_req_query = req.query) != null ? _req_query : {});
|
|
210
210
|
if (!complete) return {
|
|
211
211
|
status: 400,
|
|
212
212
|
body: {
|
|
213
213
|
error: 'This link is incomplete.'
|
|
214
214
|
}
|
|
215
215
|
};
|
|
216
|
-
const refused = await
|
|
216
|
+
const refused = await limitedQuoteLink(deps, req, `commerce-quote-view:${hostId}`, QUOTE_VIEW_LOOKUPS_PER_WINDOW);
|
|
217
217
|
if (refused) return refused;
|
|
218
218
|
const linked = await loadLinkedQuote(deps, hostId, quoteId, token);
|
|
219
219
|
if ('outcome' in linked) {
|
|
@@ -346,7 +346,7 @@ const refusal = (status, reason, error, extra = {})=>({
|
|
|
346
346
|
error: 'Payments are not configured.'
|
|
347
347
|
}
|
|
348
348
|
};
|
|
349
|
-
const { hostId, quoteId, token, complete } =
|
|
349
|
+
const { hostId, quoteId, token, complete } = quoteLinkParams(bodyOf(req));
|
|
350
350
|
if (!complete) return {
|
|
351
351
|
status: 400,
|
|
352
352
|
body: {
|
|
@@ -360,7 +360,7 @@ const refusal = (status, reason, error, extra = {})=>({
|
|
|
360
360
|
error: 'Paying needs an Idempotency-Key header.'
|
|
361
361
|
}
|
|
362
362
|
};
|
|
363
|
-
const refused = await
|
|
363
|
+
const refused = await limitedQuoteLink(deps, req, `commerce-quote-pay:${hostId}`, QUOTE_PAY_PRESSES_PER_WINDOW);
|
|
364
364
|
if (refused) return refused;
|
|
365
365
|
const linked = await loadLinkedQuote(deps, hostId, quoteId, token);
|
|
366
366
|
if ('outcome' in linked) return linked.outcome;
|
|
@@ -581,7 +581,7 @@ const refusal = (status, reason, error, extra = {})=>({
|
|
|
581
581
|
};
|
|
582
582
|
}
|
|
583
583
|
}
|
|
584
|
-
function
|
|
584
|
+
/** Sends a link route's answer, never cached or indexed. */ export function sendQuoteLinkOutcome(res, outcome) {
|
|
585
585
|
var _outcome_headers;
|
|
586
586
|
res.setHeader('Cache-Control', 'private, no-store');
|
|
587
587
|
res.setHeader('X-Robots-Tag', 'noindex, nofollow');
|
|
@@ -590,10 +590,10 @@ function send(res, outcome) {
|
|
|
590
590
|
}
|
|
591
591
|
/** `GET /api/commerce/quote-view`. */ export const quoteViewHandler = async (req, res)=>{
|
|
592
592
|
try {
|
|
593
|
-
return
|
|
593
|
+
return sendQuoteLinkOutcome(res, await handleQuoteView(defaultQuotePayDeps(), req));
|
|
594
594
|
} catch (error) {
|
|
595
595
|
console.error('quote view failed', error);
|
|
596
|
-
return
|
|
596
|
+
return sendQuoteLinkOutcome(res, {
|
|
597
597
|
status: 500,
|
|
598
598
|
body: {
|
|
599
599
|
error: 'This quote could not be loaded. Try again.'
|
|
@@ -601,6 +601,6 @@ function send(res, outcome) {
|
|
|
601
601
|
});
|
|
602
602
|
}
|
|
603
603
|
};
|
|
604
|
-
/** `POST /api/commerce/quote-pay`. */ export const quotePayHandler = async (req, res)=>
|
|
604
|
+
/** `POST /api/commerce/quote-pay`. */ export const quotePayHandler = async (req, res)=>sendQuoteLinkOutcome(res, await handleQuotePay(defaultQuotePayDeps(), req));
|
|
605
605
|
|
|
606
606
|
//# sourceMappingURL=quote-pay.js.map
|