@aglyn/plugins-commerce 1.0.0-beta.268 → 1.0.0-beta.270
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -13
- package/src/lib/components/console/payment-methods-card.component.js +7 -0
- package/src/lib/components/console/payment-methods-card.component.js.map +1 -1
- package/src/lib/components/console/payments-settings-card.component.d.ts +2 -0
- package/src/lib/components/console/payments-settings-card.component.js +2 -0
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-kiosk-readiness.component.d.ts +58 -0
- package/src/lib/components/console/pos/pos-kiosk-readiness.component.js +146 -0
- package/src/lib/components/console/pos/pos-kiosk-readiness.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-pairing.component.d.ts +12 -2
- package/src/lib/components/console/pos/pos-pairing.component.js +14 -1
- package/src/lib/components/console/pos/pos-pairing.component.js.map +1 -1
- package/src/lib/components/console/pos-devices-card.component.js +13 -0
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/contracts.d.ts +237 -0
- package/src/lib/contracts.js +46 -0
- package/src/lib/contracts.js.map +1 -0
- package/src/lib/declarations.console-server.js +8 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/model/commerce-record-routes.d.ts +7 -0
- package/src/lib/model/commerce-record-routes.js +7 -0
- package/src/lib/model/commerce-record-routes.js.map +1 -1
- package/src/lib/model/commerce-server-search-sources.d.ts +30 -0
- package/src/lib/model/commerce-server-search-sources.js +57 -0
- package/src/lib/model/commerce-server-search-sources.js.map +1 -0
- package/src/lib/model/order-events.d.ts +8 -127
- package/src/lib/model/order-events.js +11 -24
- package/src/lib/model/order-events.js.map +1 -1
- package/src/lib/model/order-history.d.ts +7 -40
- package/src/lib/model/order-history.js +7 -5
- package/src/lib/model/order-history.js.map +1 -1
- package/src/lib/native-nav.d.ts +30 -0
- package/src/lib/native-nav.js +127 -0
- package/src/lib/native-nav.js.map +1 -0
- package/src/lib/server/order-event-triggers.d.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-commerce",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.270",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,18 +25,18 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
37
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
38
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
39
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.270",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.270",
|
|
30
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.270",
|
|
31
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.270",
|
|
32
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.270",
|
|
33
|
+
"@aglyn/shared-util-email": "1.0.0-beta.270",
|
|
34
|
+
"@aglyn/shared-util-http": "1.0.0-beta.270",
|
|
35
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.270",
|
|
36
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.270",
|
|
37
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.270",
|
|
38
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.270",
|
|
39
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.270",
|
|
40
40
|
"@stripe/react-stripe-js": "^6.12.0",
|
|
41
41
|
"@stripe/stripe-js": "^9.17.0",
|
|
42
42
|
"@swc/helpers": "0.5.23",
|
|
@@ -18,12 +18,14 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
18
18
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
19
|
import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
20
20
|
import { CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { PrerequisiteNotice } from "@aglyn/shared-ui-jsx/components/prerequisite-notice.component";
|
|
21
22
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
22
23
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
23
24
|
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
24
25
|
import { Alert, Button, Chip, Divider, Stack, Switch, Typography } from "@mui/material";
|
|
25
26
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
26
27
|
import { STOREFRONT_PAYMENT_METHODS } from "../../model/commerce-payment-methods.js";
|
|
28
|
+
import { PAYMENTS_SETTINGS_ANCHOR } from "./payments-settings-card.component.js";
|
|
27
29
|
/**
|
|
28
30
|
* What a method's Stripe state means to the merchant, or null for nothing to
|
|
29
31
|
* say. Only an ON method gets a verdict: an off one is the merchant's choice.
|
|
@@ -274,6 +276,11 @@ import { STOREFRONT_PAYMENT_METHODS } from "../../model/commerce-payment-methods
|
|
|
274
276
|
severity: "info",
|
|
275
277
|
children: 'Only a site admin can change which payment methods your store offers.'
|
|
276
278
|
}),
|
|
279
|
+
view.accountConnected ? null : /*#__PURE__*/ _jsx(PrerequisiteNotice, {
|
|
280
|
+
href: `#${PAYMENTS_SETTINGS_ANCHOR}`,
|
|
281
|
+
actionLabel: "Connect Stripe",
|
|
282
|
+
children: 'Connect Stripe in the Payments card above to offer these methods. ' + 'Methods you turn on show at checkout once Stripe is connected and approves them.'
|
|
283
|
+
}),
|
|
277
284
|
view.warnings.map((warning)=>/*#__PURE__*/ _jsx(Alert, {
|
|
278
285
|
severity: "warning",
|
|
279
286
|
children: warning
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/payment-methods-card.component.tsx"],"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'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Chip,\n Divider,\n Stack,\n Switch,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n STOREFRONT_PAYMENT_METHODS,\n type StorefrontPaymentMethodId,\n type StorefrontPaymentMethodSettings,\n type StorefrontPaymentMethodSpec,\n} from '../../model/commerce-payment-methods'\n\nexport interface PaymentMethodsCardProps {\n hostId: string\n}\n\n/** One method as the route reports it (`server/payment-methods.ts`). */\nexport interface PaymentMethodRowView {\n id: StorefrontPaymentMethodId\n on: boolean\n platform: 'on' | 'unavailable' | null\n capability: 'active' | 'pending' | 'inactive' | 'unrequested' | null\n}\n\nexport interface PaymentMethodDomainView {\n domain: string\n enabled: boolean\n stripeId: string | null\n status: { applePay: string | null; googlePay: string | null }\n lastError: string | null\n}\n\nexport interface PaymentMethodsView {\n methods: PaymentMethodRowView[]\n domains: PaymentMethodDomainView[]\n accountConnected: boolean\n /** Absent from an older answer: read as allowed, and the route still judges. */\n canEdit?: boolean\n canFinish?: boolean\n warnings: string[]\n}\n\n/**\n * What a method's Stripe state means to the merchant, or null for nothing to\n * say. Only an ON method gets a verdict: an off one is the merchant's choice.\n */\nexport function capabilityVerdict(\n row: PaymentMethodRowView,\n accountConnected: boolean,\n): { label: string; color: 'success' | 'warning' | 'default' } | null {\n if (!row.on) return null\n if (!accountConnected) return { label: 'Connect Stripe first', color: 'default' }\n switch (row.capability) {\n case 'active':\n return { label: 'Ready', color: 'success' }\n case 'pending':\n return { label: 'Stripe is reviewing', color: 'warning' }\n case 'inactive':\n return { label: 'Stripe needs more details', color: 'warning' }\n case 'unrequested':\n return { label: 'Save to request', color: 'default' }\n default:\n return null\n }\n}\n\n/** The domain chip: what Apple Pay can do on this name right now. */\nexport function domainVerdict(domain: PaymentMethodDomainView): {\n label: string\n color: 'success' | 'warning' | 'default'\n} {\n if (domain.enabled && domain.status.applePay === 'active') {\n return { label: 'Apple Pay ready', color: 'success' }\n }\n if (domain.enabled) return { label: 'Registered', color: 'default' }\n if (domain.lastError) return { label: 'Retrying', color: 'warning' }\n return { label: 'Registers on first checkout', color: 'default' }\n}\n\n/**\n * The methods a merchant can see: everything the platform offers, and every\n * method we could not ask Stripe about except crypto, which is shown only once\n * the platform positively offers it (AGL-3629: hidden where unavailable).\n */\nexport function visibleMethods(\n rows: readonly PaymentMethodRowView[],\n): Array<{ row: PaymentMethodRowView; spec: StorefrontPaymentMethodSpec }> {\n return rows\n .map((row) => ({\n row,\n spec: STOREFRONT_PAYMENT_METHODS.find((method) => method.id === row.id),\n }))\n .filter(\n (entry): entry is { row: PaymentMethodRowView; spec: StorefrontPaymentMethodSpec } =>\n Boolean(entry.spec) &&\n entry.row.platform !== 'unavailable' &&\n !(entry.row.platform === null && entry.spec?.kind === 'crypto'),\n )\n}\n\n/**\n * Payment methods (AGL-3629): which wallets, buy-now-pay-later and crypto\n * options the storefront offers, what each needs, and whether Stripe is ready\n * for it. Cards are always on and are not listed.\n *\n * Read and saved through `/api/commerce/payment-methods` rather than a\n * Firestore listen, because two of the three facts per row — what the\n * platform offers and the payout account's capability — live at Stripe, and a\n * save is a Stripe request as well as a settings write.\n */\nexport function PaymentMethodsCard(props: PaymentMethodsCardProps) {\n const { hostId } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [view, setView] = useState<PaymentMethodsView | null>(null)\n const [state, setState] = useState<'loading' | 'loaded' | 'error' | 'unconfigured'>(\n 'loading',\n )\n const [draft, setDraft] = useState<StorefrontPaymentMethodSettings>({})\n const [saving, setSaving] = useState(false)\n const [nonce, setNonce] = useState(0)\n // The load keys on the uid, not the user object: a refreshed token hands\n // back a new object, and reloading on it would throw away unsaved toggles.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid\n\n useEffect(() => {\n const user = userRef.current\n if (!uid || !user || !hostId) return\n let cancelled = false\n setState('loading')\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/commerce/payment-methods?hostId=${encodeURIComponent(hostId)}`,\n )\n if (cancelled) return\n if (response.status === 501) return void setState('unconfigured')\n if (!response.ok) return void setState('error')\n const payload = (await response.json()) as PaymentMethodsView\n if (cancelled) return\n setView(payload)\n setDraft({})\n setState('loaded')\n } catch {\n if (!cancelled) setState('error')\n }\n })()\n return () => {\n cancelled = true\n }\n }, [uid, hostId, nonce])\n\n const dirty = Object.keys(draft).length > 0\n\n const handleSave = useCallback(async () => {\n if (!dirty) return\n setSaving(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/payment-methods', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, methods: draft }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'Payment methods were not saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n setView(payload as PaymentMethodsView)\n setDraft({})\n enqueueSnackbar('Payment methods saved', { variant: 'success', persist: false })\n } catch {\n enqueueSnackbar('Payment methods were not saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setSaving(false)\n }\n }, [dirty, draft, enqueueSnackbar, hostId, user])\n\n const handleFinish = useCallback(async () => {\n setSaving(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/payment-methods', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, action: 'finish' }),\n })\n const payload = await response.json().catch(() => ({}))\n if (response.ok && typeof payload?.url === 'string') {\n return void window.location.assign(payload.url)\n }\n enqueueSnackbar(payload?.error ?? 'Stripe could not be opened', {\n variant: 'error',\n allowDuplicate: true,\n })\n } catch {\n enqueueSnackbar('Stripe could not be opened', { variant: 'error', allowDuplicate: true })\n } finally {\n setSaving(false)\n }\n }, [enqueueSnackbar, hostId, user])\n\n const rows = view ? visibleMethods(view.methods) : []\n const canEdit = view?.canEdit !== false\n const canFinish = view?.canFinish !== false\n\n return (\n <CardDisplay\n header={'Payment methods'}\n help={pluginDocsHelp('commerce', { anchor: '#payment-methods' })}\n HeaderProps={{\n action:\n state === 'loaded' && canEdit ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={!dirty || saving}\n onClick={handleSave}\n >\n {saving ? 'Saving…' : 'Save'}\n </Button>\n ) : null,\n }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5} data-testid=\"payment-methods-card\">\n {state === 'loading' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking your payment methods…'}\n </Typography>\n ) : null}\n {state === 'unconfigured' ? (\n <Alert severity=\"info\">{'Payments are not configured on this deployment.'}</Alert>\n ) : null}\n {state === 'error' ? (\n <Alert\n severity=\"error\"\n action={\n <Button color=\"inherit\" size=\"small\" onClick={() => setNonce((n) => n + 1)}>\n {'Retry'}\n </Button>\n }\n >\n {'We couldn’t load your payment methods. Nothing has changed — ' +\n 'shoppers still see what they saw before.'}\n </Alert>\n ) : null}\n {state === 'loaded' && view ? (\n <>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Cards are always on. Turn off anything you don’t want to offer. ' +\n 'Stripe shows each method only on orders and to shoppers it suits, ' +\n 'using the limits below.'}\n </Typography>\n {canEdit ? null : (\n <Alert severity=\"info\">\n {'Only a site admin can change which payment methods your store offers.'}\n </Alert>\n )}\n {view.warnings.map((warning) => (\n <Alert key={warning} severity=\"warning\">\n {warning}\n </Alert>\n ))}\n {rows.map(({ row, spec }) => {\n const on = draft[row.id] ?? row.on\n const verdict = capabilityVerdict({ ...row, on }, view.accountConnected)\n return (\n <Stack key={row.id} spacing={0.5} data-testid={`payment-method-${row.id}`}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Switch\n checked={on}\n disabled={!canEdit || saving}\n onChange={(event) => {\n const next = event.target.checked\n setDraft((current) => {\n const copy = { ...current }\n if (next === row.on) delete copy[row.id]\n else copy[row.id] = next\n return copy\n })\n }}\n slotProps={{ input: { 'aria-label': spec.label } }}\n />\n <Typography variant=\"subtitle2\" sx={{ flex: 1 }}>\n {spec.label}\n </Typography>\n {spec.capability && verdict ? (\n <Chip size=\"small\" variant=\"outlined\" label={verdict.label} color={verdict.color} />\n ) : null}\n {on && row.capability === 'inactive' && canFinish ? (\n <Button size=\"small\" disabled={saving} onClick={handleFinish}>\n {'Finish in Stripe'}\n </Button>\n ) : null}\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {spec.requirements}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {spec.limits}\n </Typography>\n </Stack>\n )\n })}\n <Divider />\n <Typography variant=\"subtitle2\">{'Wallet domains'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Apple Pay shows only on a domain registered with Stripe. Your ' +\n 'site’s domains are registered for you when you connect them and ' +\n 'on the first in-page checkout.'}\n </Typography>\n {view.domains.map((domain) => {\n const verdict = domainVerdict(domain)\n return (\n <Stack\n key={domain.domain}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n data-testid={`payment-domain-${domain.domain}`}\n >\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {domain.domain}\n </Typography>\n <Chip size=\"small\" variant=\"outlined\" label={verdict.label} color={verdict.color} />\n </Stack>\n )\n })}\n </>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nPaymentMethodsCard.displayName = 'PaymentMethodsCard'\n\nexport default PaymentMethodsCard\n"],"names":["pluginDocsHelp","CardDisplay","useSnackbar","authorizedFetch","useUser","Alert","Button","Chip","Divider","Stack","Switch","Typography","useCallback","useEffect","useRef","useState","STOREFRONT_PAYMENT_METHODS","capabilityVerdict","row","accountConnected","on","label","color","capability","domainVerdict","domain","enabled","status","applePay","lastError","visibleMethods","rows","map","spec","find","method","id","filter","entry","Boolean","platform","kind","PaymentMethodsCard","props","hostId","data","user","enqueueSnackbar","view","setView","state","setState","draft","setDraft","saving","setSaving","nonce","setNonce","userRef","current","uid","cancelled","response","encodeURIComponent","ok","payload","json","dirty","Object","keys","length","handleSave","headers","body","JSON","stringify","methods","catch","error","variant","allowDuplicate","persist","handleFinish","action","url","window","location","assign","canEdit","canFinish","header","help","anchor","HeaderProps","size","disabled","onClick","contentGutterX","contentGutterY","spacing","data-testid","severity","n","warnings","warning","verdict","direction","sx","alignItems","checked","onChange","event","next","target","copy","slotProps","input","flex","requirements","limits","domains","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/payment-methods-card.component.tsx"],"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'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { PrerequisiteNotice } from '@aglyn/shared-ui-jsx/components/prerequisite-notice.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Chip,\n Divider,\n Stack,\n Switch,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n STOREFRONT_PAYMENT_METHODS,\n type StorefrontPaymentMethodId,\n type StorefrontPaymentMethodSettings,\n type StorefrontPaymentMethodSpec,\n} from '../../model/commerce-payment-methods'\nimport { PAYMENTS_SETTINGS_ANCHOR } from './payments-settings-card.component'\n\nexport interface PaymentMethodsCardProps {\n hostId: string\n}\n\n/** One method as the route reports it (`server/payment-methods.ts`). */\nexport interface PaymentMethodRowView {\n id: StorefrontPaymentMethodId\n on: boolean\n platform: 'on' | 'unavailable' | null\n capability: 'active' | 'pending' | 'inactive' | 'unrequested' | null\n}\n\nexport interface PaymentMethodDomainView {\n domain: string\n enabled: boolean\n stripeId: string | null\n status: { applePay: string | null; googlePay: string | null }\n lastError: string | null\n}\n\nexport interface PaymentMethodsView {\n methods: PaymentMethodRowView[]\n domains: PaymentMethodDomainView[]\n accountConnected: boolean\n /** Absent from an older answer: read as allowed, and the route still judges. */\n canEdit?: boolean\n canFinish?: boolean\n warnings: string[]\n}\n\n/**\n * What a method's Stripe state means to the merchant, or null for nothing to\n * say. Only an ON method gets a verdict: an off one is the merchant's choice.\n */\nexport function capabilityVerdict(\n row: PaymentMethodRowView,\n accountConnected: boolean,\n): { label: string; color: 'success' | 'warning' | 'default' } | null {\n if (!row.on) return null\n if (!accountConnected) return { label: 'Connect Stripe first', color: 'default' }\n switch (row.capability) {\n case 'active':\n return { label: 'Ready', color: 'success' }\n case 'pending':\n return { label: 'Stripe is reviewing', color: 'warning' }\n case 'inactive':\n return { label: 'Stripe needs more details', color: 'warning' }\n case 'unrequested':\n return { label: 'Save to request', color: 'default' }\n default:\n return null\n }\n}\n\n/** The domain chip: what Apple Pay can do on this name right now. */\nexport function domainVerdict(domain: PaymentMethodDomainView): {\n label: string\n color: 'success' | 'warning' | 'default'\n} {\n if (domain.enabled && domain.status.applePay === 'active') {\n return { label: 'Apple Pay ready', color: 'success' }\n }\n if (domain.enabled) return { label: 'Registered', color: 'default' }\n if (domain.lastError) return { label: 'Retrying', color: 'warning' }\n return { label: 'Registers on first checkout', color: 'default' }\n}\n\n/**\n * The methods a merchant can see: everything the platform offers, and every\n * method we could not ask Stripe about except crypto, which is shown only once\n * the platform positively offers it (AGL-3629: hidden where unavailable).\n */\nexport function visibleMethods(\n rows: readonly PaymentMethodRowView[],\n): Array<{ row: PaymentMethodRowView; spec: StorefrontPaymentMethodSpec }> {\n return rows\n .map((row) => ({\n row,\n spec: STOREFRONT_PAYMENT_METHODS.find((method) => method.id === row.id),\n }))\n .filter(\n (entry): entry is { row: PaymentMethodRowView; spec: StorefrontPaymentMethodSpec } =>\n Boolean(entry.spec) &&\n entry.row.platform !== 'unavailable' &&\n !(entry.row.platform === null && entry.spec?.kind === 'crypto'),\n )\n}\n\n/**\n * Payment methods (AGL-3629): which wallets, buy-now-pay-later and crypto\n * options the storefront offers, what each needs, and whether Stripe is ready\n * for it. Cards are always on and are not listed.\n *\n * Read and saved through `/api/commerce/payment-methods` rather than a\n * Firestore listen, because two of the three facts per row — what the\n * platform offers and the payout account's capability — live at Stripe, and a\n * save is a Stripe request as well as a settings write.\n */\nexport function PaymentMethodsCard(props: PaymentMethodsCardProps) {\n const { hostId } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [view, setView] = useState<PaymentMethodsView | null>(null)\n const [state, setState] = useState<'loading' | 'loaded' | 'error' | 'unconfigured'>(\n 'loading',\n )\n const [draft, setDraft] = useState<StorefrontPaymentMethodSettings>({})\n const [saving, setSaving] = useState(false)\n const [nonce, setNonce] = useState(0)\n // The load keys on the uid, not the user object: a refreshed token hands\n // back a new object, and reloading on it would throw away unsaved toggles.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid\n\n useEffect(() => {\n const user = userRef.current\n if (!uid || !user || !hostId) return\n let cancelled = false\n setState('loading')\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/commerce/payment-methods?hostId=${encodeURIComponent(hostId)}`,\n )\n if (cancelled) return\n if (response.status === 501) return void setState('unconfigured')\n if (!response.ok) return void setState('error')\n const payload = (await response.json()) as PaymentMethodsView\n if (cancelled) return\n setView(payload)\n setDraft({})\n setState('loaded')\n } catch {\n if (!cancelled) setState('error')\n }\n })()\n return () => {\n cancelled = true\n }\n }, [uid, hostId, nonce])\n\n const dirty = Object.keys(draft).length > 0\n\n const handleSave = useCallback(async () => {\n if (!dirty) return\n setSaving(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/payment-methods', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, methods: draft }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'Payment methods were not saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n setView(payload as PaymentMethodsView)\n setDraft({})\n enqueueSnackbar('Payment methods saved', { variant: 'success', persist: false })\n } catch {\n enqueueSnackbar('Payment methods were not saved', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setSaving(false)\n }\n }, [dirty, draft, enqueueSnackbar, hostId, user])\n\n const handleFinish = useCallback(async () => {\n setSaving(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/payment-methods', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId, action: 'finish' }),\n })\n const payload = await response.json().catch(() => ({}))\n if (response.ok && typeof payload?.url === 'string') {\n return void window.location.assign(payload.url)\n }\n enqueueSnackbar(payload?.error ?? 'Stripe could not be opened', {\n variant: 'error',\n allowDuplicate: true,\n })\n } catch {\n enqueueSnackbar('Stripe could not be opened', { variant: 'error', allowDuplicate: true })\n } finally {\n setSaving(false)\n }\n }, [enqueueSnackbar, hostId, user])\n\n const rows = view ? visibleMethods(view.methods) : []\n const canEdit = view?.canEdit !== false\n const canFinish = view?.canFinish !== false\n\n return (\n <CardDisplay\n header={'Payment methods'}\n help={pluginDocsHelp('commerce', { anchor: '#payment-methods' })}\n HeaderProps={{\n action:\n state === 'loaded' && canEdit ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={!dirty || saving}\n onClick={handleSave}\n >\n {saving ? 'Saving…' : 'Save'}\n </Button>\n ) : null,\n }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5} data-testid=\"payment-methods-card\">\n {state === 'loading' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking your payment methods…'}\n </Typography>\n ) : null}\n {state === 'unconfigured' ? (\n <Alert severity=\"info\">{'Payments are not configured on this deployment.'}</Alert>\n ) : null}\n {state === 'error' ? (\n <Alert\n severity=\"error\"\n action={\n <Button color=\"inherit\" size=\"small\" onClick={() => setNonce((n) => n + 1)}>\n {'Retry'}\n </Button>\n }\n >\n {'We couldn’t load your payment methods. Nothing has changed — ' +\n 'shoppers still see what they saw before.'}\n </Alert>\n ) : null}\n {state === 'loaded' && view ? (\n <>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Cards are always on. Turn off anything you don’t want to offer. ' +\n 'Stripe shows each method only on orders and to shoppers it suits, ' +\n 'using the limits below.'}\n </Typography>\n {canEdit ? null : (\n <Alert severity=\"info\">\n {'Only a site admin can change which payment methods your store offers.'}\n </Alert>\n )}\n {/* What every \"Connect Stripe first\" chip below waits on, and where\n it is done: the Payments card above (AGL-3982). */}\n {view.accountConnected ? null : (\n <PrerequisiteNotice href={`#${PAYMENTS_SETTINGS_ANCHOR}`} actionLabel=\"Connect Stripe\">\n {'Connect Stripe in the Payments card above to offer these methods. ' +\n 'Methods you turn on show at checkout once Stripe is connected and approves them.'}\n </PrerequisiteNotice>\n )}\n {view.warnings.map((warning) => (\n <Alert key={warning} severity=\"warning\">\n {warning}\n </Alert>\n ))}\n {rows.map(({ row, spec }) => {\n const on = draft[row.id] ?? row.on\n const verdict = capabilityVerdict({ ...row, on }, view.accountConnected)\n return (\n <Stack key={row.id} spacing={0.5} data-testid={`payment-method-${row.id}`}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Switch\n checked={on}\n disabled={!canEdit || saving}\n onChange={(event) => {\n const next = event.target.checked\n setDraft((current) => {\n const copy = { ...current }\n if (next === row.on) delete copy[row.id]\n else copy[row.id] = next\n return copy\n })\n }}\n slotProps={{ input: { 'aria-label': spec.label } }}\n />\n <Typography variant=\"subtitle2\" sx={{ flex: 1 }}>\n {spec.label}\n </Typography>\n {spec.capability && verdict ? (\n <Chip size=\"small\" variant=\"outlined\" label={verdict.label} color={verdict.color} />\n ) : null}\n {on && row.capability === 'inactive' && canFinish ? (\n <Button size=\"small\" disabled={saving} onClick={handleFinish}>\n {'Finish in Stripe'}\n </Button>\n ) : null}\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {spec.requirements}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {spec.limits}\n </Typography>\n </Stack>\n )\n })}\n <Divider />\n <Typography variant=\"subtitle2\">{'Wallet domains'}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Apple Pay shows only on a domain registered with Stripe. Your ' +\n 'site’s domains are registered for you when you connect them and ' +\n 'on the first in-page checkout.'}\n </Typography>\n {view.domains.map((domain) => {\n const verdict = domainVerdict(domain)\n return (\n <Stack\n key={domain.domain}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n data-testid={`payment-domain-${domain.domain}`}\n >\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {domain.domain}\n </Typography>\n <Chip size=\"small\" variant=\"outlined\" label={verdict.label} color={verdict.color} />\n </Stack>\n )\n })}\n </>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nPaymentMethodsCard.displayName = 'PaymentMethodsCard'\n\nexport default PaymentMethodsCard\n"],"names":["pluginDocsHelp","CardDisplay","PrerequisiteNotice","useSnackbar","authorizedFetch","useUser","Alert","Button","Chip","Divider","Stack","Switch","Typography","useCallback","useEffect","useRef","useState","STOREFRONT_PAYMENT_METHODS","PAYMENTS_SETTINGS_ANCHOR","capabilityVerdict","row","accountConnected","on","label","color","capability","domainVerdict","domain","enabled","status","applePay","lastError","visibleMethods","rows","map","spec","find","method","id","filter","entry","Boolean","platform","kind","PaymentMethodsCard","props","hostId","data","user","enqueueSnackbar","view","setView","state","setState","draft","setDraft","saving","setSaving","nonce","setNonce","userRef","current","uid","cancelled","response","encodeURIComponent","ok","payload","json","dirty","Object","keys","length","handleSave","headers","body","JSON","stringify","methods","catch","error","variant","allowDuplicate","persist","handleFinish","action","url","window","location","assign","canEdit","canFinish","header","help","anchor","HeaderProps","size","disabled","onClick","contentGutterX","contentGutterY","spacing","data-testid","severity","n","href","actionLabel","warnings","warning","verdict","direction","sx","alignItems","checked","onChange","event","next","target","copy","slotProps","input","flex","requirements","limits","domains","displayName"],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface PaymentsSettingsCardProps {
|
|
2
2
|
hostId: string;
|
|
3
3
|
}
|
|
4
|
+
/** The element id the Payments card carries, for a link on the same page that lands on it (AGL-3982). */
|
|
5
|
+
export declare const PAYMENTS_SETTINGS_ANCHOR = "store-payments";
|
|
4
6
|
/**
|
|
5
7
|
* Merchant payments card (AGL-284): Stripe Connect Express onboarding
|
|
6
8
|
* status for the org owner + the plan's transaction-fee ladder so the
|
|
@@ -29,6 +29,7 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
|
29
29
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
30
30
|
import { PrerequisiteNotice } from "@aglyn/shared-ui-jsx/components/prerequisite-notice.component";
|
|
31
31
|
import { useCommerceEntitlement } from "./entitlement-gate.component.js";
|
|
32
|
+
/** The element id the Payments card carries, for a link on the same page that lands on it (AGL-3982). */ export const PAYMENTS_SETTINGS_ANCHOR = 'store-payments';
|
|
32
33
|
/**
|
|
33
34
|
* Merchant payments card (AGL-284): Stripe Connect Express onboarding
|
|
34
35
|
* status for the org owner + the plan's transaction-fee ladder so the
|
|
@@ -173,6 +174,7 @@ import { useCommerceEntitlement } from "./entitlement-gate.component.js";
|
|
|
173
174
|
enqueueSnackbar
|
|
174
175
|
]);
|
|
175
176
|
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
177
|
+
id: PAYMENTS_SETTINGS_ANCHOR,
|
|
176
178
|
header: 'Payments',
|
|
177
179
|
help: pluginDocsHelp('commerceEndToEnd', {
|
|
178
180
|
anchor: '#1-connect-payments'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/payments-settings-card.component.tsx"],"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'use client'\n\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport * as Aglyn from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Alert, Button, Chip, Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { useOrgPlan } from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { PrerequisiteNotice } from '@aglyn/shared-ui-jsx/components/prerequisite-notice.component'\nimport { useCommerceEntitlement } from './entitlement-gate.component'\n\nexport interface PaymentsSettingsCardProps {\n hostId: string\n}\n\n/**\n * Merchant payments card (AGL-284): Stripe Connect Express onboarding\n * status for the org owner + the plan's transaction-fee ladder so the\n * \"upgrade to kill fees\" motion (AGL-278) is visible where it matters.\n */\nexport function PaymentsSettingsCard(props: PaymentsSettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [busy, setBusy] = useState(false)\n /**\n * A one-time Stripe Express dashboard link, minted by the connect route on\n * request. Not persisted: login links are single-use and short-lived, so a\n * stored one would be a button that fails the second time it is pressed.\n */\n const [dashboardUrl, setDashboardUrl] = useState<string | null>(null)\n const { org, ready: planReady } = useOrgPlan(hostId)\n /** Bumped by Retry to re-subscribe the profile listen (AGL-1380). */\n const [retryNonce, setRetryNonce] = useState(0)\n const { upgradeHref } = useCommerceEntitlement(hostId, 'commerce')\n /**\n * The OWNER's profile: the Stripe account every money route charges\n * against is the one on the workspace owner's profile. Reading the\n * signed-in member's instead told an admin who is not the owner that a\n * connected store was not set up.\n */\n const ownerUid =\n typeof org?.ownerUid === 'string' && org.ownerUid ? org.ownerUid : undefined\n const { data: profile, status: profileStatus } = useFirestoreDoc<any>(\n // Held at null rather than addressed as `profiles/-pending-` — that\n // sentinel issued a guaranteed-denied read on every pre-auth mount, and\n // a denial lands in `status: 'error'`, which now means something.\n () => (ownerUid ? doc(firestore, 'profiles', ownerUid) : null),\n [firestore, ownerUid, retryNonce],\n )\n const isOwner = Boolean(\n org?.ownerUid && (user as any)?.uid === org.ownerUid,\n )\n /**\n * Three outcomes, not two (AGL-1380). An absent profile doc is the value\n * BEFORE an answer as much as it is the value of a merchant who never\n * connected Stripe, so \"Not set up\" off a falsy `stripeChargesEnabled` is a\n * claim about this org's payment setup made without having heard back. It\n * is wrong in the direction that provokes action: the same falsy value\n * turns the button into \"Set up payments\", which sends a merchant who is\n * already taking money into Stripe onboarding.\n */\n const stripeState: 'pending' | 'error' | 'loaded' =\n !planReady || (ownerUid && profileStatus === 'loading')\n ? 'pending'\n : profileStatus === 'error'\n ? 'error'\n : 'loaded'\n /**\n * WHICH STRIPE WORLD THE STORED ACCOUNT BELONGS TO (AGL-2471).\n *\n * `stripeChargesEnabled: true` was the whole readiness claim, and a\n * production profile carrying it named a TEST-mode account: this card said\n * \"Charges enabled\" about three storefronts that could not take a single\n * payment. Only a literal boolean counts as recorded — the same\n * three-valued reading `merchantAccountIsReady` uses on the server, so the\n * chip and the checkout cannot disagree.\n */\n const modeVerified = typeof profile?.stripeAccountLivemode === 'boolean'\n const chargesConnected =\n stripeState === 'loaded' && Boolean(profile?.stripeChargesEnabled)\n const modeUnverified = chargesConnected && !modeVerified\n const chargesEnabled = chargesConnected && modeVerified\n const physicalPct = Aglyn.resolveTransactionFeePct(org, 'physical')\n const digitalPct = Aglyn.resolveTransactionFeePct(org, 'digital')\n const commerceEnabled = Aglyn.checkEntitlement(org, 'commerce')\n\n const handleConnect = useCallback(async () => {\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/connect', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId }),\n })\n const payload = await response.json()\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'Payments are not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'Payment setup failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n if (payload.chargesEnabled) {\n // \"% who connect Stripe\" — a GTM plan §6 activation metric\n // (AGL-1561). Gated on the TRANSITION, not the state: this handler\n // is a re-entrant create-or-resume, so it answers `chargesEnabled:\n // true` on every subsequent visit to a connected account, and a bare\n // event here would count one merchant once per click forever.\n // `chargesEnabled` is the state read from the profile BEFORE this\n // request, so the event fires only when onboarding actually completed.\n if (!chargesEnabled) trackEvent('stripe_connected', {})\n // CHARGES AND PAYOUTS ARE TWO FLAGS, AND ONLY ONE OF THEM IS THIS\n // SENTENCE (AGL-1997). `chargesEnabled` says Stripe will let the\n // account take money; whether the money can LEAVE is `payoutsEnabled`,\n // which the route answers alongside it. Charges-yes/payouts-no is an\n // ordinary Stripe state — verification pending or lapsed, no payout\n // method — and announcing \"Payments are enabled\" in it is false in the\n // direction that costs the merchant: they sell believing they are\n // paid, and the funds sit in a Connect account that cannot release\n // them. Read three-valued, so only a literal `false` from Stripe\n // downgrades the claim; an absent flag means unasked, not off.\n const payoutsBlocked = payload.payoutsEnabled === false\n // A WARNING THAT NAMES A PROBLEM MUST OFFER THE FIX.\n //\n // Stripe is the only place payouts can be released — an Express\n // account has no password and no direct login — so telling the\n // merchant their funds are stranded and then keeping them here is half\n // an answer. The route now mints the remediation link for exactly this\n // state; going there is the whole point of pressing the button.\n if (payoutsBlocked && payload.url) {\n return void window.location.assign(payload.url)\n }\n // Payouts are flowing, so the link is the Express dashboard: balance,\n // payout schedule, and the reason a payout failed. Held in state\n // rather than followed, because this click was a status check and\n // navigating away from one nobody asked to leave is its own defect.\n if (!payoutsBlocked && typeof payload.dashboardUrl === 'string') {\n setDashboardUrl(payload.dashboardUrl)\n }\n return void enqueueSnackbar(\n payoutsBlocked\n ? 'Connected — payouts are not released yet'\n : 'Payments are enabled',\n {\n variant: payoutsBlocked ? 'warning' : 'success',\n persist: false,\n },\n )\n }\n if (payload.url) window.location.assign(payload.url)\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [user, hostId, chargesEnabled, enqueueSnackbar])\n\n return (\n <CardDisplay\n header={'Payments'}\n help={pluginDocsHelp('commerceEndToEnd', {\n anchor: '#1-connect-payments',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {'Stripe account'}\n </Typography>\n {/*\n No chip at all on error: both labels it could carry — \"Charges\n enabled\" and \"Not set up\" — assert something we failed to find\n out. Whatever Stripe is doing right now it carries on doing.\n */}\n {stripeState === 'error' ? null : (\n <Chip\n size=\"small\"\n label={\n stripeState === 'pending'\n ? 'Checking…'\n : chargesEnabled\n ? 'Charges enabled'\n : modeUnverified\n ? 'Not verified'\n : 'Not set up'\n }\n color={\n stripeState === 'pending'\n ? 'default'\n : chargesEnabled\n ? 'success'\n : modeUnverified\n ? 'error'\n : 'warning'\n }\n variant=\"outlined\"\n />\n )}\n </Stack>\n {stripeState === 'error' ? (\n <Alert\n severity=\"error\"\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n onClick={() => setRetryNonce((n) => n + 1)}\n >\n {'Retry'}\n </Button>\n }\n >\n {'We couldn’t load your payment setup. This says nothing about ' +\n 'whether payments are on — we could not reach the setting to ' +\n 'find out. Nothing has changed.'}\n </Alert>\n ) : null}\n {/*\n Every line below reads the plan, and an org doc still in flight\n resolves to the FREE tier (AGL-1064) — which would flash \"Selling\n requires a paid plan\" and the wrong fee percentages at a paying\n seller, then correct itself. Say nothing until it is known.\n */}\n {!planReady ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking your plan…'}\n </Typography>\n ) : (\n <>\n {!commerceEnabled ? (\n <PrerequisiteNotice href={upgradeHref} actionLabel=\"See plans\">\n {'Selling needs a paid plan. Choose one on Billing, then connect Stripe here.'}\n </PrerequisiteNotice>\n ) : null}\n {/*\n THE LEGAL NAME, not the org's resolved `productName`\n (AGL-2351). This sentence says who takes money off the\n merchant's own Stripe account, and the application fee lands in\n the platform Stripe account — held by the operating entity, not\n by a white-label agency reselling the console. Naming the\n agency here would assert a fee relationship that does not\n exist. `PLATFORM_BRAND_LEGAL_NAME` still configures cleanly for\n a self-host operator, who really is the entity collecting it.\n */}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Buyers pay on your own Stripe account; ' +\n `${Aglyn.PLATFORM_BRAND_LEGAL_NAME} collects a platform fee ` +\n 'per sale based on your plan:'}\n </Typography>\n {/* The DIGITAL rate is not products-only (AGL-2315). A paid\n booking is a service sale and resolves through the same\n `'service'`/digital axis, so a bookings-only merchant pays this\n exact number — and this card was the one place they could read\n their own rate while naming only \"products\". Stated here rather\n than adding a third figure: it is one rate, not two. */}\n <Typography variant=\"body2\">\n {`Physical products: ${physicalPct}% · Digital products, ` +\n `services & bookings: ${digitalPct}%`}\n {physicalPct > 0 || digitalPct > 0 ? (\n <Typography\n component=\"span\"\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {' — higher plans reduce fees to 0%'}\n </Typography>\n ) : null}\n </Typography>\n {/* CARD PROCESSING, PASSED THROUGH AT COST (AGL-2152).\n\n Unconditional, including on the 0% plans, because it is\n exactly those plans where the merchant would otherwise read\n \"0%\" and be surprised by a deduction. Their PLATFORM fee\n really is zero — the figure below is Stripe's own charge for\n taking the payment, which every storefront sale incurs and\n which used to be absorbed silently out of the platform's\n balance rather than passed through.\n\n Named as a range rather than one number because the buyer\n chooses the payment method after the session is created:\n cards settle at the low end, buy-now-pay-later at the high\n end, and the deduction is sized for the dearest so a sale can\n never cost more to process than it collects. */}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Card processing is passed through at cost on top of that: ' +\n `${Aglyn.CARD_PROCESSING_PERCENT}–` +\n `${Aglyn.BNPL_PROCESSING_PERCENT}% + ` +\n `${Aglyn.PROCESSING_FIXED_CENTS}¢ per online ` +\n 'sale, what Stripe charges to take the payment. It is not a ' +\n `${Aglyn.PLATFORM_BRAND_LEGAL_NAME} margin.`}\n </Typography>\n {/* EVERY TENDER, AND HOW EACH IS COLLECTED (AGL-2111).\n\n Until now this card said \"per sale\" and meant it for online and\n card sales only: cash and room-charge sales at the register\n recorded no fee at all, so a merchant could read this sentence,\n ring everything as cash, and pay nothing. The fee now attaches\n to the sale on every tender, which makes the sentence above\n true — and creates a second, visible collection route the\n merchant has to be told about BEFORE it appears on their\n invoice, because it is money leaving by a door they have never\n seen it leave by.\n\n Shown only when a rate actually applies: on a 0% plan there is\n nothing to collect either way, and the paragraph would be noise\n on the card of the merchants who upgraded to escape fees. */}\n {physicalPct > 0 || digitalPct > 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The fee is charged on the sale, not on how it was paid — ' +\n 'in-person cash and room-charge sales carry it too. On card ' +\n 'sales it is deducted from your Stripe payout; on cash and ' +\n 'room-charge sales there is no payout to deduct from, so it ' +\n `is added to your monthly ${Aglyn.PLATFORM_BRAND_LEGAL_NAME} ` +\n 'invoice instead.'}\n </Typography>\n ) : null}\n </>\n )}\n {/*\n AGL-2471: connected, and refused by every money door because the\n account was never verified against this deployment's Stripe mode.\n \"Not set up\" would be a lie in the other direction — the merchant\n did set something up — so the card names what is actually wrong and\n what to do about it.\n */}\n {modeUnverified ? (\n <Alert severity=\"error\">\n {'Your Stripe account has not been verified for payments on ' +\n 'this site, so sales will be refused. Set up payments again ' +\n 'to verify it — if it was connected against Stripe test mode, ' +\n 'you will need to connect a live account.'}\n </Alert>\n ) : null}\n {/*\n The button's own label is a claim — \"Set up payments\" tells the\n owner they have none. Withheld until the answer is in, for the same\n reason as the chip (AGL-1380); on error the Retry above is the only\n action offered, because acting on a setup we could not read is how\n a connected merchant gets sent back through onboarding.\n */}\n {/*\n WHERE THE MONEY STOPPED. Mirrored onto this profile by the\n `payout.failed` handler and retired by the next successful payout, so\n it names a live problem rather than a scar. Without it a merchant sold\n into an account that could not release the funds and the card said\n \"Payments are enabled\" the whole time — the storefront looked healthy\n from every angle available to them.\n */}\n {stripeState === 'loaded' && profile?.lastPayoutFailureAtMs ? (\n <Alert severity=\"warning\" variant=\"outlined\">\n {`A payout of $${(\n Number(profile.lastPayoutFailureCents ?? 0) / 100\n ).toFixed(2)} did not reach your bank on ${new Date(\n Number(profile.lastPayoutFailureAtMs),\n ).toLocaleDateString()} — ${String(\n profile.lastPayoutFailureReason ?? 'Stripe did not say why',\n )} The money is still in your Stripe account. Open Stripe to fix your payout details.`}\n </Alert>\n ) : null}\n {!planReady || stripeState !== 'loaded' ? null : isOwner ? (\n <>\n <Button\n size=\"small\"\n variant={chargesEnabled ? 'text' : 'contained'}\n color=\"primary\"\n disabled={busy}\n onClick={handleConnect}\n sx={{ alignSelf: 'flex-start' }}\n >\n {chargesEnabled\n ? 'Refresh status'\n : busy\n ? 'Opening Stripe…'\n : 'Set up payments'}\n </Button>\n {/*\n Where the money actually is. Aglyn records none of it —\n no balance, no payout schedule, and no `payout.failed` handling\n anywhere — so a merchant whose payout bounced has no way to learn\n that from this console. An Express account has no direct login\n either, which leaves a link minted by the platform as the only\n route in. `target=\"_blank\"` because it leaves the app entirely.\n */}\n {dashboardUrl ? (\n <Button\n size=\"small\"\n variant=\"text\"\n color=\"primary\"\n href={dashboardUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n sx={{ alignSelf: 'flex-start' }}\n >\n {'View payouts in Stripe'}\n </Button>\n ) : null}\n </>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Only the organization owner can set up payments.'}\n </Typography>\n )}\n </Stack>\n </CardDisplay>\n )\n}\nPaymentsSettingsCard.displayName = 'PaymentsSettingsCard'\n\nexport default PaymentsSettingsCard\n"],"names":["trackEvent","Aglyn","CardDisplay","useSnackbar","Alert","Button","Chip","Stack","Typography","doc","useCallback","useState","useFirestore","useUser","useFirestoreDoc","useOrgPlan","pluginDocsHelp","authorizedFetch","PrerequisiteNotice","useCommerceEntitlement","PaymentsSettingsCard","props","profile","hostId","firestore","data","user","enqueueSnackbar","busy","setBusy","dashboardUrl","setDashboardUrl","org","ready","planReady","retryNonce","setRetryNonce","upgradeHref","ownerUid","undefined","status","profileStatus","isOwner","Boolean","uid","stripeState","modeVerified","stripeAccountLivemode","chargesConnected","stripeChargesEnabled","modeUnverified","chargesEnabled","physicalPct","resolveTransactionFeePct","digitalPct","commerceEnabled","checkEntitlement","handleConnect","response","method","headers","body","JSON","stringify","payload","json","variant","persist","ok","error","allowDuplicate","payoutsBlocked","payoutsEnabled","url","window","location","assign","console","header","help","anchor","contentGutterX","contentGutterY","spacing","direction","sx","alignItems","flex","size","label","color","severity","action","onClick","n","href","actionLabel","PLATFORM_BRAND_LEGAL_NAME","component","CARD_PROCESSING_PERCENT","BNPL_PROCESSING_PERCENT","PROCESSING_FIXED_CENTS","lastPayoutFailureAtMs","Number","lastPayoutFailureCents","toFixed","Date","toLocaleDateString","String","lastPayoutFailureReason","disabled","alignSelf","target","rel","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/payments-settings-card.component.tsx"],"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'use client'\n\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport * as Aglyn from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Alert, Button, Chip, Stack, Typography } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport { useFirestore, useUser } from '@aglyn/tenant-feature-instance'\nimport { useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { useOrgPlan } from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { PrerequisiteNotice } from '@aglyn/shared-ui-jsx/components/prerequisite-notice.component'\nimport { useCommerceEntitlement } from './entitlement-gate.component'\n\nexport interface PaymentsSettingsCardProps {\n hostId: string\n}\n\n/** The element id the Payments card carries, for a link on the same page that lands on it (AGL-3982). */\nexport const PAYMENTS_SETTINGS_ANCHOR = 'store-payments'\n\n/**\n * Merchant payments card (AGL-284): Stripe Connect Express onboarding\n * status for the org owner + the plan's transaction-fee ladder so the\n * \"upgrade to kill fees\" motion (AGL-278) is visible where it matters.\n */\nexport function PaymentsSettingsCard(props: PaymentsSettingsCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [busy, setBusy] = useState(false)\n /**\n * A one-time Stripe Express dashboard link, minted by the connect route on\n * request. Not persisted: login links are single-use and short-lived, so a\n * stored one would be a button that fails the second time it is pressed.\n */\n const [dashboardUrl, setDashboardUrl] = useState<string | null>(null)\n const { org, ready: planReady } = useOrgPlan(hostId)\n /** Bumped by Retry to re-subscribe the profile listen (AGL-1380). */\n const [retryNonce, setRetryNonce] = useState(0)\n const { upgradeHref } = useCommerceEntitlement(hostId, 'commerce')\n /**\n * The OWNER's profile: the Stripe account every money route charges\n * against is the one on the workspace owner's profile. Reading the\n * signed-in member's instead told an admin who is not the owner that a\n * connected store was not set up.\n */\n const ownerUid =\n typeof org?.ownerUid === 'string' && org.ownerUid ? org.ownerUid : undefined\n const { data: profile, status: profileStatus } = useFirestoreDoc<any>(\n // Held at null rather than addressed as `profiles/-pending-` — that\n // sentinel issued a guaranteed-denied read on every pre-auth mount, and\n // a denial lands in `status: 'error'`, which now means something.\n () => (ownerUid ? doc(firestore, 'profiles', ownerUid) : null),\n [firestore, ownerUid, retryNonce],\n )\n const isOwner = Boolean(\n org?.ownerUid && (user as any)?.uid === org.ownerUid,\n )\n /**\n * Three outcomes, not two (AGL-1380). An absent profile doc is the value\n * BEFORE an answer as much as it is the value of a merchant who never\n * connected Stripe, so \"Not set up\" off a falsy `stripeChargesEnabled` is a\n * claim about this org's payment setup made without having heard back. It\n * is wrong in the direction that provokes action: the same falsy value\n * turns the button into \"Set up payments\", which sends a merchant who is\n * already taking money into Stripe onboarding.\n */\n const stripeState: 'pending' | 'error' | 'loaded' =\n !planReady || (ownerUid && profileStatus === 'loading')\n ? 'pending'\n : profileStatus === 'error'\n ? 'error'\n : 'loaded'\n /**\n * WHICH STRIPE WORLD THE STORED ACCOUNT BELONGS TO (AGL-2471).\n *\n * `stripeChargesEnabled: true` was the whole readiness claim, and a\n * production profile carrying it named a TEST-mode account: this card said\n * \"Charges enabled\" about three storefronts that could not take a single\n * payment. Only a literal boolean counts as recorded — the same\n * three-valued reading `merchantAccountIsReady` uses on the server, so the\n * chip and the checkout cannot disagree.\n */\n const modeVerified = typeof profile?.stripeAccountLivemode === 'boolean'\n const chargesConnected =\n stripeState === 'loaded' && Boolean(profile?.stripeChargesEnabled)\n const modeUnverified = chargesConnected && !modeVerified\n const chargesEnabled = chargesConnected && modeVerified\n const physicalPct = Aglyn.resolveTransactionFeePct(org, 'physical')\n const digitalPct = Aglyn.resolveTransactionFeePct(org, 'digital')\n const commerceEnabled = Aglyn.checkEntitlement(org, 'commerce')\n\n const handleConnect = useCallback(async () => {\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/commerce/connect', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ hostId }),\n })\n const payload = await response.json()\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'Payments are not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'Payment setup failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n if (payload.chargesEnabled) {\n // \"% who connect Stripe\" — a GTM plan §6 activation metric\n // (AGL-1561). Gated on the TRANSITION, not the state: this handler\n // is a re-entrant create-or-resume, so it answers `chargesEnabled:\n // true` on every subsequent visit to a connected account, and a bare\n // event here would count one merchant once per click forever.\n // `chargesEnabled` is the state read from the profile BEFORE this\n // request, so the event fires only when onboarding actually completed.\n if (!chargesEnabled) trackEvent('stripe_connected', {})\n // CHARGES AND PAYOUTS ARE TWO FLAGS, AND ONLY ONE OF THEM IS THIS\n // SENTENCE (AGL-1997). `chargesEnabled` says Stripe will let the\n // account take money; whether the money can LEAVE is `payoutsEnabled`,\n // which the route answers alongside it. Charges-yes/payouts-no is an\n // ordinary Stripe state — verification pending or lapsed, no payout\n // method — and announcing \"Payments are enabled\" in it is false in the\n // direction that costs the merchant: they sell believing they are\n // paid, and the funds sit in a Connect account that cannot release\n // them. Read three-valued, so only a literal `false` from Stripe\n // downgrades the claim; an absent flag means unasked, not off.\n const payoutsBlocked = payload.payoutsEnabled === false\n // A WARNING THAT NAMES A PROBLEM MUST OFFER THE FIX.\n //\n // Stripe is the only place payouts can be released — an Express\n // account has no password and no direct login — so telling the\n // merchant their funds are stranded and then keeping them here is half\n // an answer. The route now mints the remediation link for exactly this\n // state; going there is the whole point of pressing the button.\n if (payoutsBlocked && payload.url) {\n return void window.location.assign(payload.url)\n }\n // Payouts are flowing, so the link is the Express dashboard: balance,\n // payout schedule, and the reason a payout failed. Held in state\n // rather than followed, because this click was a status check and\n // navigating away from one nobody asked to leave is its own defect.\n if (!payoutsBlocked && typeof payload.dashboardUrl === 'string') {\n setDashboardUrl(payload.dashboardUrl)\n }\n return void enqueueSnackbar(\n payoutsBlocked\n ? 'Connected — payouts are not released yet'\n : 'Payments are enabled',\n {\n variant: payoutsBlocked ? 'warning' : 'success',\n persist: false,\n },\n )\n }\n if (payload.url) window.location.assign(payload.url)\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [user, hostId, chargesEnabled, enqueueSnackbar])\n\n return (\n <CardDisplay\n id={PAYMENTS_SETTINGS_ANCHOR}\n header={'Payments'}\n help={pluginDocsHelp('commerceEndToEnd', {\n anchor: '#1-connect-payments',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flex: 1 }}>\n {'Stripe account'}\n </Typography>\n {/*\n No chip at all on error: both labels it could carry — \"Charges\n enabled\" and \"Not set up\" — assert something we failed to find\n out. Whatever Stripe is doing right now it carries on doing.\n */}\n {stripeState === 'error' ? null : (\n <Chip\n size=\"small\"\n label={\n stripeState === 'pending'\n ? 'Checking…'\n : chargesEnabled\n ? 'Charges enabled'\n : modeUnverified\n ? 'Not verified'\n : 'Not set up'\n }\n color={\n stripeState === 'pending'\n ? 'default'\n : chargesEnabled\n ? 'success'\n : modeUnverified\n ? 'error'\n : 'warning'\n }\n variant=\"outlined\"\n />\n )}\n </Stack>\n {stripeState === 'error' ? (\n <Alert\n severity=\"error\"\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n onClick={() => setRetryNonce((n) => n + 1)}\n >\n {'Retry'}\n </Button>\n }\n >\n {'We couldn’t load your payment setup. This says nothing about ' +\n 'whether payments are on — we could not reach the setting to ' +\n 'find out. Nothing has changed.'}\n </Alert>\n ) : null}\n {/*\n Every line below reads the plan, and an org doc still in flight\n resolves to the FREE tier (AGL-1064) — which would flash \"Selling\n requires a paid plan\" and the wrong fee percentages at a paying\n seller, then correct itself. Say nothing until it is known.\n */}\n {!planReady ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Checking your plan…'}\n </Typography>\n ) : (\n <>\n {!commerceEnabled ? (\n <PrerequisiteNotice href={upgradeHref} actionLabel=\"See plans\">\n {'Selling needs a paid plan. Choose one on Billing, then connect Stripe here.'}\n </PrerequisiteNotice>\n ) : null}\n {/*\n THE LEGAL NAME, not the org's resolved `productName`\n (AGL-2351). This sentence says who takes money off the\n merchant's own Stripe account, and the application fee lands in\n the platform Stripe account — held by the operating entity, not\n by a white-label agency reselling the console. Naming the\n agency here would assert a fee relationship that does not\n exist. `PLATFORM_BRAND_LEGAL_NAME` still configures cleanly for\n a self-host operator, who really is the entity collecting it.\n */}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Buyers pay on your own Stripe account; ' +\n `${Aglyn.PLATFORM_BRAND_LEGAL_NAME} collects a platform fee ` +\n 'per sale based on your plan:'}\n </Typography>\n {/* The DIGITAL rate is not products-only (AGL-2315). A paid\n booking is a service sale and resolves through the same\n `'service'`/digital axis, so a bookings-only merchant pays this\n exact number — and this card was the one place they could read\n their own rate while naming only \"products\". Stated here rather\n than adding a third figure: it is one rate, not two. */}\n <Typography variant=\"body2\">\n {`Physical products: ${physicalPct}% · Digital products, ` +\n `services & bookings: ${digitalPct}%`}\n {physicalPct > 0 || digitalPct > 0 ? (\n <Typography\n component=\"span\"\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {' — higher plans reduce fees to 0%'}\n </Typography>\n ) : null}\n </Typography>\n {/* CARD PROCESSING, PASSED THROUGH AT COST (AGL-2152).\n\n Unconditional, including on the 0% plans, because it is\n exactly those plans where the merchant would otherwise read\n \"0%\" and be surprised by a deduction. Their PLATFORM fee\n really is zero — the figure below is Stripe's own charge for\n taking the payment, which every storefront sale incurs and\n which used to be absorbed silently out of the platform's\n balance rather than passed through.\n\n Named as a range rather than one number because the buyer\n chooses the payment method after the session is created:\n cards settle at the low end, buy-now-pay-later at the high\n end, and the deduction is sized for the dearest so a sale can\n never cost more to process than it collects. */}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Card processing is passed through at cost on top of that: ' +\n `${Aglyn.CARD_PROCESSING_PERCENT}–` +\n `${Aglyn.BNPL_PROCESSING_PERCENT}% + ` +\n `${Aglyn.PROCESSING_FIXED_CENTS}¢ per online ` +\n 'sale, what Stripe charges to take the payment. It is not a ' +\n `${Aglyn.PLATFORM_BRAND_LEGAL_NAME} margin.`}\n </Typography>\n {/* EVERY TENDER, AND HOW EACH IS COLLECTED (AGL-2111).\n\n Until now this card said \"per sale\" and meant it for online and\n card sales only: cash and room-charge sales at the register\n recorded no fee at all, so a merchant could read this sentence,\n ring everything as cash, and pay nothing. The fee now attaches\n to the sale on every tender, which makes the sentence above\n true — and creates a second, visible collection route the\n merchant has to be told about BEFORE it appears on their\n invoice, because it is money leaving by a door they have never\n seen it leave by.\n\n Shown only when a rate actually applies: on a 0% plan there is\n nothing to collect either way, and the paragraph would be noise\n on the card of the merchants who upgraded to escape fees. */}\n {physicalPct > 0 || digitalPct > 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The fee is charged on the sale, not on how it was paid — ' +\n 'in-person cash and room-charge sales carry it too. On card ' +\n 'sales it is deducted from your Stripe payout; on cash and ' +\n 'room-charge sales there is no payout to deduct from, so it ' +\n `is added to your monthly ${Aglyn.PLATFORM_BRAND_LEGAL_NAME} ` +\n 'invoice instead.'}\n </Typography>\n ) : null}\n </>\n )}\n {/*\n AGL-2471: connected, and refused by every money door because the\n account was never verified against this deployment's Stripe mode.\n \"Not set up\" would be a lie in the other direction — the merchant\n did set something up — so the card names what is actually wrong and\n what to do about it.\n */}\n {modeUnverified ? (\n <Alert severity=\"error\">\n {'Your Stripe account has not been verified for payments on ' +\n 'this site, so sales will be refused. Set up payments again ' +\n 'to verify it — if it was connected against Stripe test mode, ' +\n 'you will need to connect a live account.'}\n </Alert>\n ) : null}\n {/*\n The button's own label is a claim — \"Set up payments\" tells the\n owner they have none. Withheld until the answer is in, for the same\n reason as the chip (AGL-1380); on error the Retry above is the only\n action offered, because acting on a setup we could not read is how\n a connected merchant gets sent back through onboarding.\n */}\n {/*\n WHERE THE MONEY STOPPED. Mirrored onto this profile by the\n `payout.failed` handler and retired by the next successful payout, so\n it names a live problem rather than a scar. Without it a merchant sold\n into an account that could not release the funds and the card said\n \"Payments are enabled\" the whole time — the storefront looked healthy\n from every angle available to them.\n */}\n {stripeState === 'loaded' && profile?.lastPayoutFailureAtMs ? (\n <Alert severity=\"warning\" variant=\"outlined\">\n {`A payout of $${(\n Number(profile.lastPayoutFailureCents ?? 0) / 100\n ).toFixed(2)} did not reach your bank on ${new Date(\n Number(profile.lastPayoutFailureAtMs),\n ).toLocaleDateString()} — ${String(\n profile.lastPayoutFailureReason ?? 'Stripe did not say why',\n )} The money is still in your Stripe account. Open Stripe to fix your payout details.`}\n </Alert>\n ) : null}\n {!planReady || stripeState !== 'loaded' ? null : isOwner ? (\n <>\n <Button\n size=\"small\"\n variant={chargesEnabled ? 'text' : 'contained'}\n color=\"primary\"\n disabled={busy}\n onClick={handleConnect}\n sx={{ alignSelf: 'flex-start' }}\n >\n {chargesEnabled\n ? 'Refresh status'\n : busy\n ? 'Opening Stripe…'\n : 'Set up payments'}\n </Button>\n {/*\n Where the money actually is. Aglyn records none of it —\n no balance, no payout schedule, and no `payout.failed` handling\n anywhere — so a merchant whose payout bounced has no way to learn\n that from this console. An Express account has no direct login\n either, which leaves a link minted by the platform as the only\n route in. `target=\"_blank\"` because it leaves the app entirely.\n */}\n {dashboardUrl ? (\n <Button\n size=\"small\"\n variant=\"text\"\n color=\"primary\"\n href={dashboardUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n sx={{ alignSelf: 'flex-start' }}\n >\n {'View payouts in Stripe'}\n </Button>\n ) : null}\n </>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Only the organization owner can set up payments.'}\n </Typography>\n )}\n </Stack>\n </CardDisplay>\n )\n}\nPaymentsSettingsCard.displayName = 'PaymentsSettingsCard'\n\nexport default PaymentsSettingsCard\n"],"names":["trackEvent","Aglyn","CardDisplay","useSnackbar","Alert","Button","Chip","Stack","Typography","doc","useCallback","useState","useFirestore","useUser","useFirestoreDoc","useOrgPlan","pluginDocsHelp","authorizedFetch","PrerequisiteNotice","useCommerceEntitlement","PAYMENTS_SETTINGS_ANCHOR","PaymentsSettingsCard","props","profile","hostId","firestore","data","user","enqueueSnackbar","busy","setBusy","dashboardUrl","setDashboardUrl","org","ready","planReady","retryNonce","setRetryNonce","upgradeHref","ownerUid","undefined","status","profileStatus","isOwner","Boolean","uid","stripeState","modeVerified","stripeAccountLivemode","chargesConnected","stripeChargesEnabled","modeUnverified","chargesEnabled","physicalPct","resolveTransactionFeePct","digitalPct","commerceEnabled","checkEntitlement","handleConnect","response","method","headers","body","JSON","stringify","payload","json","variant","persist","ok","error","allowDuplicate","payoutsBlocked","payoutsEnabled","url","window","location","assign","console","id","header","help","anchor","contentGutterX","contentGutterY","spacing","direction","sx","alignItems","flex","size","label","color","severity","action","onClick","n","href","actionLabel","PLATFORM_BRAND_LEGAL_NAME","component","CARD_PROCESSING_PERCENT","BNPL_PROCESSING_PERCENT","PROCESSING_FIXED_CENTS","lastPayoutFailureAtMs","Number","lastPayoutFailureCents","toFixed","Date","toLocaleDateString","String","lastPayoutFailureReason","disabled","alignSelf","target","rel","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { posReadersCall } from './pos-api';
|
|
2
|
+
type User = Parameters<typeof posReadersCall>[0];
|
|
3
|
+
/** The element id the POS devices card carries, for a link that lands on its card readers. */
|
|
4
|
+
export declare const POS_DEVICES_ANCHOR = "pos-devices";
|
|
5
|
+
/**
|
|
6
|
+
* One thing that keeps a kiosk from taking payment, or narrows how:
|
|
7
|
+
*
|
|
8
|
+
* - `no-payment` — pay at the counter is off and no card path is ready, so
|
|
9
|
+
* the shopper is told "Payment is not set up on this kiosk yet";
|
|
10
|
+
* - `stripe` — card payments wait on the workspace's Stripe account;
|
|
11
|
+
* - `reader` — no card reader is assigned to the register;
|
|
12
|
+
* - `reader-choice` — several are, and the kiosk sends cards to the one its
|
|
13
|
+
* staff menu picks.
|
|
14
|
+
*/
|
|
15
|
+
export type PosKioskGap = 'no-payment' | 'stripe' | 'reader' | 'reader-choice';
|
|
16
|
+
export interface PosKioskReadinessFacts {
|
|
17
|
+
/**
|
|
18
|
+
* The deployment takes card payments at a register at all (the readers
|
|
19
|
+
* route's `available`). Where it does not, nothing about readers or Stripe
|
|
20
|
+
* is the merchant's to fix, and only pay at the counter is left.
|
|
21
|
+
*/
|
|
22
|
+
cardsOffered: boolean;
|
|
23
|
+
/** Card readers assigned to this register. A reader on "Any register" is not one the kiosk uses. */
|
|
24
|
+
registerReaders: number;
|
|
25
|
+
/** "Kiosk: let customers pay at the counter", as the site resolves it. */
|
|
26
|
+
payAtCounter: boolean;
|
|
27
|
+
/** The workspace's Stripe account can not take card payments yet (settled, never while pending). */
|
|
28
|
+
stripeBlocked: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* What stands between a kiosk on this register and taking payment, most
|
|
32
|
+
* serious first. It mirrors the kiosk's own answer (`server/pos-kiosk.ts`
|
|
33
|
+
* `context`): a reader is offered only where the deployment takes card
|
|
34
|
+
* payments and the register has one, and pay at the counter only where it is
|
|
35
|
+
* switched on. Pure.
|
|
36
|
+
*/
|
|
37
|
+
export declare function posKioskGaps(facts: PosKioskReadinessFacts): PosKioskGap[];
|
|
38
|
+
export interface PosKioskReadinessProps {
|
|
39
|
+
user: User | null | undefined;
|
|
40
|
+
hostId: string;
|
|
41
|
+
registerId: string;
|
|
42
|
+
registerName?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Says, while a kiosk is being paired, what it still needs to take payment
|
|
46
|
+
* (AGL-3982). The shopper at a kiosk with no way to pay reads "Payment is not
|
|
47
|
+
* set up on this kiosk yet"; this is where the merchant learns why, with a
|
|
48
|
+
* link to each fix: Stripe on the Payments page, a reader on the POS devices
|
|
49
|
+
* card, and pay at the counter under Admin → Plugins → Commerce.
|
|
50
|
+
*
|
|
51
|
+
* Nothing is said until every answer is in: a notice that flashes while the
|
|
52
|
+
* readers or the settings load would tell a ready store to set up.
|
|
53
|
+
*/
|
|
54
|
+
export declare function PosKioskReadiness({ user, hostId, registerId, registerName }: PosKioskReadinessProps): import("react").JSX.Element;
|
|
55
|
+
export declare namespace PosKioskReadiness {
|
|
56
|
+
var displayName: string;
|
|
57
|
+
}
|
|
58
|
+
export default PosKioskReadiness;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
18
|
+
import { buildRoute, Route } from "@aglyn/aglyn";
|
|
19
|
+
import { PrerequisiteNotice } from "@aglyn/shared-ui-jsx/components/prerequisite-notice.component";
|
|
20
|
+
import { useConsoleHostRoute, useHostOrgId, useMerchantPayments, useSitePluginConfig, useStoreSettingsHref } from "@aglyn/tenant-feature-instance";
|
|
21
|
+
import { Stack } from "@mui/material";
|
|
22
|
+
import { useEffect, useState } from "react";
|
|
23
|
+
import { BUNDLE_ID } from "../../../constants/bundle-common.js";
|
|
24
|
+
import { posReadersCall } from "./pos-api.js";
|
|
25
|
+
/** The element id the POS devices card carries, for a link that lands on its card readers. */ export const POS_DEVICES_ANCHOR = 'pos-devices';
|
|
26
|
+
/**
|
|
27
|
+
* What stands between a kiosk on this register and taking payment, most
|
|
28
|
+
* serious first. It mirrors the kiosk's own answer (`server/pos-kiosk.ts`
|
|
29
|
+
* `context`): a reader is offered only where the deployment takes card
|
|
30
|
+
* payments and the register has one, and pay at the counter only where it is
|
|
31
|
+
* switched on. Pure.
|
|
32
|
+
*/ export function posKioskGaps(facts) {
|
|
33
|
+
const gaps = [];
|
|
34
|
+
const cardReady = facts.cardsOffered && facts.registerReaders > 0 && !facts.stripeBlocked;
|
|
35
|
+
if (!facts.payAtCounter && !cardReady) gaps.push('no-payment');
|
|
36
|
+
if (!facts.cardsOffered) return gaps;
|
|
37
|
+
if (facts.stripeBlocked) gaps.push('stripe');
|
|
38
|
+
if (facts.registerReaders === 0) gaps.push('reader');
|
|
39
|
+
else if (facts.registerReaders > 1) gaps.push('reader-choice');
|
|
40
|
+
return gaps;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Says, while a kiosk is being paired, what it still needs to take payment
|
|
44
|
+
* (AGL-3982). The shopper at a kiosk with no way to pay reads "Payment is not
|
|
45
|
+
* set up on this kiosk yet"; this is where the merchant learns why, with a
|
|
46
|
+
* link to each fix: Stripe on the Payments page, a reader on the POS devices
|
|
47
|
+
* card, and pay at the counter under Admin → Plugins → Commerce.
|
|
48
|
+
*
|
|
49
|
+
* Nothing is said until every answer is in: a notice that flashes while the
|
|
50
|
+
* readers or the settings load would tell a ready store to set up.
|
|
51
|
+
*/ export function PosKioskReadiness({ user, hostId, registerId, registerName }) {
|
|
52
|
+
var _ref;
|
|
53
|
+
const payments = useMerchantPayments(hostId);
|
|
54
|
+
const orgId = useHostOrgId(hostId);
|
|
55
|
+
const { config, ready: configReady } = useSitePluginConfig(orgId, hostId, BUNDLE_ID);
|
|
56
|
+
const { orgSlug, subdomain } = useConsoleHostRoute(hostId);
|
|
57
|
+
const posSettingsHref = useStoreSettingsHref(hostId, 'pos');
|
|
58
|
+
// `error` when the readers could not be listed: then nothing is said, since
|
|
59
|
+
// "no reader" would be a guess.
|
|
60
|
+
const [readers, setReaders] = useState(null);
|
|
61
|
+
// The person, for the load's key: a signed-in session carries its uid.
|
|
62
|
+
const uid = (_ref = user == null ? void 0 : user.uid) != null ? _ref : '';
|
|
63
|
+
useEffect(()=>{
|
|
64
|
+
if (!user || !registerId) return;
|
|
65
|
+
let active = true;
|
|
66
|
+
void posReadersCall(user, {
|
|
67
|
+
action: 'list',
|
|
68
|
+
hostId
|
|
69
|
+
}).then((answer)=>{
|
|
70
|
+
var _ref;
|
|
71
|
+
if (!active) return;
|
|
72
|
+
setReaders({
|
|
73
|
+
available: Boolean(answer == null ? void 0 : answer.available),
|
|
74
|
+
count: ((_ref = answer == null ? void 0 : answer.readers) != null ? _ref : []).filter((reader)=>reader.registerId === registerId).length
|
|
75
|
+
});
|
|
76
|
+
}).catch(()=>{
|
|
77
|
+
if (active) setReaders('error');
|
|
78
|
+
});
|
|
79
|
+
return ()=>{
|
|
80
|
+
active = false;
|
|
81
|
+
};
|
|
82
|
+
// Keyed on the person, not the session object (see the register page).
|
|
83
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
84
|
+
}, [
|
|
85
|
+
uid,
|
|
86
|
+
hostId,
|
|
87
|
+
registerId
|
|
88
|
+
]);
|
|
89
|
+
if (!readers || readers === 'error' || !configReady || !payments.settled) return null;
|
|
90
|
+
// `posKioskSettings`'s own reading (pos-kiosk-config.ts), spelled out here:
|
|
91
|
+
// importing it puts the kiosk's settings module in the plugin's eager
|
|
92
|
+
// chunk, and this card is drawn only on the pairing dialog.
|
|
93
|
+
const payAtCounter = config['posKioskPayAtCounter'] !== false;
|
|
94
|
+
const gaps = posKioskGaps({
|
|
95
|
+
cardsOffered: readers.available,
|
|
96
|
+
registerReaders: readers.count,
|
|
97
|
+
payAtCounter,
|
|
98
|
+
stripeBlocked: payments.blocked
|
|
99
|
+
});
|
|
100
|
+
if (!gaps.length) return null;
|
|
101
|
+
const register = registerName || 'this register';
|
|
102
|
+
const devicesHref = posSettingsHref ? `${posSettingsHref}#${POS_DEVICES_ANCHOR}` : undefined;
|
|
103
|
+
const kioskSettingsHref = orgSlug && subdomain ? buildRoute(Route.HOST_ADMIN_PLUGIN, {
|
|
104
|
+
orgSlug,
|
|
105
|
+
host: subdomain,
|
|
106
|
+
pluginRef: BUNDLE_ID
|
|
107
|
+
}) : undefined;
|
|
108
|
+
return /*#__PURE__*/ _jsx(Stack, {
|
|
109
|
+
spacing: 1,
|
|
110
|
+
"data-testid": "pos-kiosk-readiness",
|
|
111
|
+
children: gaps.map((gap)=>{
|
|
112
|
+
switch(gap){
|
|
113
|
+
case 'no-payment':
|
|
114
|
+
return /*#__PURE__*/ _jsx(PrerequisiteNotice, {
|
|
115
|
+
severity: "warning",
|
|
116
|
+
href: kioskSettingsHref,
|
|
117
|
+
actionLabel: "Open Commerce settings",
|
|
118
|
+
children: 'This kiosk can’t take payment yet: customers will see “Payment is not set up on this kiosk yet.” ' + 'Turn on “Kiosk: let customers pay at the counter” under Admin → Plugins → Commerce' + (readers.available ? ', or finish card payments below.' : '.')
|
|
119
|
+
}, gap);
|
|
120
|
+
case 'stripe':
|
|
121
|
+
var _payments_href, _payments_reason;
|
|
122
|
+
return /*#__PURE__*/ _jsx(PrerequisiteNotice, {
|
|
123
|
+
href: (_payments_href = payments.href) != null ? _payments_href : undefined,
|
|
124
|
+
actionLabel: "Open Payments",
|
|
125
|
+
children: `${(_payments_reason = payments.reason) != null ? _payments_reason : ''} Until then the kiosk can’t take cards` + (payAtCounter ? '; customers pay at the counter.' : '.')
|
|
126
|
+
}, gap);
|
|
127
|
+
case 'reader':
|
|
128
|
+
return /*#__PURE__*/ _jsx(PrerequisiteNotice, {
|
|
129
|
+
href: devicesHref,
|
|
130
|
+
actionLabel: "Open POS devices",
|
|
131
|
+
children: `No card reader is assigned to ${register}. Assign one under POS devices so customers can pay by card at the kiosk.`
|
|
132
|
+
}, gap);
|
|
133
|
+
case 'reader-choice':
|
|
134
|
+
return /*#__PURE__*/ _jsx(PrerequisiteNotice, {
|
|
135
|
+
children: `${register === 'this register' ? 'This register' : register} has ${readers.count} card readers. ` + 'Once the kiosk is paired, choose the one it uses from its staff menu.'
|
|
136
|
+
}, gap);
|
|
137
|
+
default:
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
PosKioskReadiness.displayName = 'PosKioskReadiness';
|
|
144
|
+
export default PosKioskReadiness;
|
|
145
|
+
|
|
146
|
+
//# sourceMappingURL=pos-kiosk-readiness.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-kiosk-readiness.component.tsx"],"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'use client'\n\nimport { buildRoute, Route } from '@aglyn/aglyn'\nimport { PrerequisiteNotice } from '@aglyn/shared-ui-jsx/components/prerequisite-notice.component'\nimport {\n useConsoleHostRoute,\n useHostOrgId,\n useMerchantPayments,\n useSitePluginConfig,\n useStoreSettingsHref,\n} from '@aglyn/tenant-feature-instance'\nimport { Stack } from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { BUNDLE_ID } from '../../../constants/bundle-common'\nimport { posReadersCall } from './pos-api'\n\ntype User = Parameters<typeof posReadersCall>[0]\n\n/** The element id the POS devices card carries, for a link that lands on its card readers. */\nexport const POS_DEVICES_ANCHOR = 'pos-devices'\n\n/**\n * One thing that keeps a kiosk from taking payment, or narrows how:\n *\n * - `no-payment` — pay at the counter is off and no card path is ready, so\n * the shopper is told \"Payment is not set up on this kiosk yet\";\n * - `stripe` — card payments wait on the workspace's Stripe account;\n * - `reader` — no card reader is assigned to the register;\n * - `reader-choice` — several are, and the kiosk sends cards to the one its\n * staff menu picks.\n */\nexport type PosKioskGap = 'no-payment' | 'stripe' | 'reader' | 'reader-choice'\n\nexport interface PosKioskReadinessFacts {\n /**\n * The deployment takes card payments at a register at all (the readers\n * route's `available`). Where it does not, nothing about readers or Stripe\n * is the merchant's to fix, and only pay at the counter is left.\n */\n cardsOffered: boolean\n /** Card readers assigned to this register. A reader on \"Any register\" is not one the kiosk uses. */\n registerReaders: number\n /** \"Kiosk: let customers pay at the counter\", as the site resolves it. */\n payAtCounter: boolean\n /** The workspace's Stripe account can not take card payments yet (settled, never while pending). */\n stripeBlocked: boolean\n}\n\n/**\n * What stands between a kiosk on this register and taking payment, most\n * serious first. It mirrors the kiosk's own answer (`server/pos-kiosk.ts`\n * `context`): a reader is offered only where the deployment takes card\n * payments and the register has one, and pay at the counter only where it is\n * switched on. Pure.\n */\nexport function posKioskGaps(facts: PosKioskReadinessFacts): PosKioskGap[] {\n const gaps: PosKioskGap[] = []\n const cardReady = facts.cardsOffered && facts.registerReaders > 0 && !facts.stripeBlocked\n if (!facts.payAtCounter && !cardReady) gaps.push('no-payment')\n if (!facts.cardsOffered) return gaps\n if (facts.stripeBlocked) gaps.push('stripe')\n if (facts.registerReaders === 0) gaps.push('reader')\n else if (facts.registerReaders > 1) gaps.push('reader-choice')\n return gaps\n}\n\nexport interface PosKioskReadinessProps {\n user: User | null | undefined\n hostId: string\n registerId: string\n registerName?: string\n}\n\n/**\n * Says, while a kiosk is being paired, what it still needs to take payment\n * (AGL-3982). The shopper at a kiosk with no way to pay reads \"Payment is not\n * set up on this kiosk yet\"; this is where the merchant learns why, with a\n * link to each fix: Stripe on the Payments page, a reader on the POS devices\n * card, and pay at the counter under Admin → Plugins → Commerce.\n *\n * Nothing is said until every answer is in: a notice that flashes while the\n * readers or the settings load would tell a ready store to set up.\n */\nexport function PosKioskReadiness({ user, hostId, registerId, registerName }: PosKioskReadinessProps) {\n const payments = useMerchantPayments(hostId)\n const orgId = useHostOrgId(hostId)\n const { config, ready: configReady } = useSitePluginConfig(orgId, hostId, BUNDLE_ID)\n const { orgSlug, subdomain } = useConsoleHostRoute(hostId)\n const posSettingsHref = useStoreSettingsHref(hostId, 'pos')\n // `error` when the readers could not be listed: then nothing is said, since\n // \"no reader\" would be a guess.\n const [readers, setReaders] = useState<{ available: boolean; count: number } | 'error' | null>(null)\n // The person, for the load's key: a signed-in session carries its uid.\n const uid = (user as { uid?: string } | null | undefined)?.uid ?? ''\n\n useEffect(() => {\n if (!user || !registerId) return\n let active = true\n void posReadersCall<{ available?: boolean; readers?: Array<{ registerId: string | null }> }>(user, {\n action: 'list',\n hostId,\n })\n .then((answer) => {\n if (!active) return\n setReaders({\n available: Boolean(answer?.available),\n count: (answer?.readers ?? []).filter((reader) => reader.registerId === registerId).length,\n })\n })\n .catch(() => {\n if (active) setReaders('error')\n })\n return () => {\n active = false\n }\n // Keyed on the person, not the session object (see the register page).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [uid, hostId, registerId])\n\n if (!readers || readers === 'error' || !configReady || !payments.settled) return null\n // `posKioskSettings`'s own reading (pos-kiosk-config.ts), spelled out here:\n // importing it puts the kiosk's settings module in the plugin's eager\n // chunk, and this card is drawn only on the pairing dialog.\n const payAtCounter = config['posKioskPayAtCounter'] !== false\n const gaps = posKioskGaps({\n cardsOffered: readers.available,\n registerReaders: readers.count,\n payAtCounter,\n stripeBlocked: payments.blocked,\n })\n if (!gaps.length) return null\n\n const register = registerName || 'this register'\n const devicesHref = posSettingsHref ? `${posSettingsHref}#${POS_DEVICES_ANCHOR}` : undefined\n const kioskSettingsHref =\n orgSlug && subdomain\n ? buildRoute(Route.HOST_ADMIN_PLUGIN, { orgSlug, host: subdomain, pluginRef: BUNDLE_ID })\n : undefined\n return (\n <Stack spacing={1} data-testid=\"pos-kiosk-readiness\">\n {gaps.map((gap) => {\n switch (gap) {\n case 'no-payment':\n return (\n <PrerequisiteNotice key={gap} severity=\"warning\" href={kioskSettingsHref} actionLabel=\"Open Commerce settings\">\n {'This kiosk can’t take payment yet: customers will see “Payment is not set up on this kiosk yet.” ' +\n 'Turn on “Kiosk: let customers pay at the counter” under Admin → Plugins → Commerce' +\n (readers.available ? ', or finish card payments below.' : '.')}\n </PrerequisiteNotice>\n )\n case 'stripe':\n return (\n <PrerequisiteNotice key={gap} href={payments.href ?? undefined} actionLabel=\"Open Payments\">\n {`${payments.reason ?? ''} Until then the kiosk can’t take cards` +\n (payAtCounter ? '; customers pay at the counter.' : '.')}\n </PrerequisiteNotice>\n )\n case 'reader':\n return (\n <PrerequisiteNotice key={gap} href={devicesHref} actionLabel=\"Open POS devices\">\n {`No card reader is assigned to ${register}. Assign one under POS devices so customers can pay by card at the kiosk.`}\n </PrerequisiteNotice>\n )\n case 'reader-choice':\n return (\n <PrerequisiteNotice key={gap}>\n {`${register === 'this register' ? 'This register' : register} has ${readers.count} card readers. ` +\n 'Once the kiosk is paired, choose the one it uses from its staff menu.'}\n </PrerequisiteNotice>\n )\n default:\n return null\n }\n })}\n </Stack>\n )\n}\nPosKioskReadiness.displayName = 'PosKioskReadiness'\n\nexport default PosKioskReadiness\n"],"names":["buildRoute","Route","PrerequisiteNotice","useConsoleHostRoute","useHostOrgId","useMerchantPayments","useSitePluginConfig","useStoreSettingsHref","Stack","useEffect","useState","BUNDLE_ID","posReadersCall","POS_DEVICES_ANCHOR","posKioskGaps","facts","gaps","cardReady","cardsOffered","registerReaders","stripeBlocked","payAtCounter","push","PosKioskReadiness","user","hostId","registerId","registerName","payments","orgId","config","ready","configReady","orgSlug","subdomain","posSettingsHref","readers","setReaders","uid","active","action","then","answer","available","Boolean","count","filter","reader","length","catch","settled","blocked","register","devicesHref","undefined","kioskSettingsHref","HOST_ADMIN_PLUGIN","host","pluginRef","spacing","data-testid","map","gap","severity","href","actionLabel","reason","displayName"],"mappings":"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"}
|