@final-commerce/command-frame 0.7.0-staging.9 → 0.8.0-online.2
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/dist/CommonTypes.d.ts +1 -26
- package/dist/actions/get-context/mock.js +0 -4
- package/dist/actions/get-refund-plan/types.d.ts +2 -5
- package/dist/actions/get-remaining-refundable-quantities/mock.js +0 -1
- package/dist/actions/get-remaining-refundable-quantities/types.d.ts +0 -8
- package/dist/actions/process-partial-refund/types.d.ts +2 -2
- package/dist/actions/resume-checkout/action.d.ts +6 -0
- package/dist/actions/resume-checkout/action.js +8 -0
- package/dist/actions/resume-checkout/mock.d.ts +16 -0
- package/dist/actions/resume-checkout/mock.js +51 -0
- package/dist/actions/resume-checkout/types.d.ts +59 -0
- package/dist/actions/resume-checkout/types.js +13 -0
- package/dist/actions/start-checkout/action.d.ts +6 -0
- package/dist/actions/start-checkout/action.js +8 -0
- package/dist/actions/start-checkout/mock.d.ts +16 -0
- package/dist/actions/start-checkout/mock.js +80 -0
- package/dist/actions/start-checkout/types.d.ts +81 -0
- package/dist/actions/start-checkout/types.js +10 -0
- package/dist/demo/database.d.ts +1 -12
- package/dist/demo/database.js +0 -127
- package/dist/index.d.ts +6 -16
- package/dist/index.js +5 -15
- package/dist/projects/render/mocks.js +4 -14
- package/dist/projects/render/types.d.ts +13 -8
- package/dist/pubsub/topics/checkout/checkout-started/types.d.ts +26 -0
- package/dist/pubsub/topics/checkout/index.d.ts +7 -0
- package/dist/pubsub/topics/checkout/index.js +28 -0
- package/dist/pubsub/topics/checkout/payment-completed/types.d.ts +31 -0
- package/dist/pubsub/topics/checkout/payment-failed/types.d.ts +20 -0
- package/dist/pubsub/topics/checkout/types.d.ts +12 -0
- package/dist/pubsub/topics/checkout/types.js +8 -0
- package/dist/pubsub/topics/index.d.ts +17 -17
- package/dist/pubsub/topics/index.js +17 -17
- package/dist/pubsub/topics/types.d.ts +8 -2
- package/package.json +2 -2
- package/dist/actions/add-booking-to-cart/action.d.ts +0 -6
- package/dist/actions/add-booking-to-cart/action.js +0 -8
- package/dist/actions/add-booking-to-cart/mock.d.ts +0 -2
- package/dist/actions/add-booking-to-cart/mock.js +0 -42
- package/dist/actions/add-booking-to-cart/types.d.ts +0 -23
- package/dist/actions/cancel-booking/action.d.ts +0 -6
- package/dist/actions/cancel-booking/action.js +0 -8
- package/dist/actions/cancel-booking/mock.d.ts +0 -2
- package/dist/actions/cancel-booking/mock.js +0 -10
- package/dist/actions/cancel-booking/types.d.ts +0 -10
- package/dist/actions/get-booking-availability/action.d.ts +0 -6
- package/dist/actions/get-booking-availability/action.js +0 -8
- package/dist/actions/get-booking-availability/mock.d.ts +0 -2
- package/dist/actions/get-booking-availability/mock.js +0 -6
- package/dist/actions/get-booking-availability/types.d.ts +0 -18
- package/dist/actions/get-booking-resources/action.d.ts +0 -6
- package/dist/actions/get-booking-resources/action.js +0 -8
- package/dist/actions/get-booking-resources/mock.d.ts +0 -2
- package/dist/actions/get-booking-resources/mock.js +0 -7
- package/dist/actions/get-booking-resources/types.d.ts +0 -12
- package/dist/actions/get-booking-resources/types.js +0 -1
- package/dist/actions/get-bookings/action.d.ts +0 -6
- package/dist/actions/get-bookings/action.js +0 -8
- package/dist/actions/get-bookings/mock.d.ts +0 -2
- package/dist/actions/get-bookings/mock.js +0 -20
- package/dist/actions/get-bookings/types.d.ts +0 -18
- package/dist/actions/get-bookings/types.js +0 -1
- package/dist/actions/hold-booking/action.d.ts +0 -6
- package/dist/actions/hold-booking/action.js +0 -8
- package/dist/actions/hold-booking/mock.d.ts +0 -2
- package/dist/actions/hold-booking/mock.js +0 -33
- package/dist/actions/hold-booking/types.d.ts +0 -24
- package/dist/actions/hold-booking/types.js +0 -1
- package/dist/actions/remove-booking-from-cart/action.d.ts +0 -6
- package/dist/actions/remove-booking-from-cart/action.js +0 -8
- package/dist/actions/remove-booking-from-cart/mock.d.ts +0 -2
- package/dist/actions/remove-booking-from-cart/mock.js +0 -26
- package/dist/actions/remove-booking-from-cart/types.d.ts +0 -12
- package/dist/actions/remove-booking-from-cart/types.js +0 -1
- package/dist/pubsub/topics/bookings/booking-created/types.d.ts +0 -6
- package/dist/pubsub/topics/bookings/booking-created/types.js +0 -1
- package/dist/pubsub/topics/bookings/booking-setup-changed/types.d.ts +0 -15
- package/dist/pubsub/topics/bookings/booking-setup-changed/types.js +0 -1
- package/dist/pubsub/topics/bookings/booking-updated/types.d.ts +0 -6
- package/dist/pubsub/topics/bookings/booking-updated/types.js +0 -1
- package/dist/pubsub/topics/bookings/index.d.ts +0 -3
- package/dist/pubsub/topics/bookings/index.js +0 -27
- package/dist/pubsub/topics/bookings/types.d.ts +0 -8
- package/dist/pubsub/topics/bookings/types.js +0 -3
- /package/dist/{actions/add-booking-to-cart → pubsub/topics/checkout/checkout-started}/types.js +0 -0
- /package/dist/{actions/cancel-booking → pubsub/topics/checkout/payment-completed}/types.js +0 -0
- /package/dist/{actions/get-booking-availability → pubsub/topics/checkout/payment-failed}/types.js +0 -0
package/dist/CommonTypes.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './common-types';
|
|
2
|
-
import type { Tax, Tip, Address, MetadataItem, PosDataItem, CartFeeTaxEntry, CartFeeItem, CartDiscountItem, OrderNote, NonRevenueItem, DiscountDetail, FeeDetail, DiscountLineItem, FeeLineItem, LineItem, TipPayment, RefundedTipPayment, PaymentMethod, Discount, CustomFee, Summary, CustomSale, RefundedCustomSale, RefundItem, RefundedLineItem, ActiveStation, ActiveSession, ActiveOutlet, ActiveUser, ActiveUserRole, ActiveOrder, ActiveCart, ActivePark, ActiveCompany, ActiveCustomSales, ActiveProduct, ActiveCustomer, FullProduct, ProductVariant, Inventory, CustomerNote, Attribute, AttributeOption, Category, Transaction, ActiveSplitPayment
|
|
2
|
+
import type { Tax, Tip, Address, MetadataItem, PosDataItem, CartFeeTaxEntry, CartFeeItem, CartDiscountItem, OrderNote, NonRevenueItem, DiscountDetail, FeeDetail, DiscountLineItem, FeeLineItem, LineItem, TipPayment, RefundedTipPayment, PaymentMethod, Discount, CustomFee, Summary, CustomSale, RefundedCustomSale, RefundItem, RefundedLineItem, ActiveStation, ActiveSession, ActiveOutlet, ActiveUser, ActiveUserRole, ActiveOrder, ActiveCart, ActivePark, ActiveCompany, ActiveCustomSales, ActiveProduct, ActiveCustomer, FullProduct, ProductVariant, Inventory, CustomerNote, Attribute, AttributeOption, Category, Transaction, ActiveSplitPayment } from '@final-commerce/common/pos-types';
|
|
3
3
|
export { CurrencyCode, ProductType as CFProductType, UserTypes as CFUserTypes } from '@final-commerce/common';
|
|
4
4
|
/** Open record used as a base for active entities. */
|
|
5
5
|
export type CFActiveEntity = Record<string, unknown>;
|
|
@@ -18,16 +18,6 @@ export type CFTransaction = Transaction;
|
|
|
18
18
|
export type CFCategory = Category;
|
|
19
19
|
export type CFProductVariant = ProductVariant;
|
|
20
20
|
export type CFProduct = FullProduct;
|
|
21
|
-
export type CFBookingAvailability = BookingAvailability;
|
|
22
|
-
export type CFBookingSlot = BookingSlot;
|
|
23
|
-
export type CFBookingSlotResource = BookingSlotResource;
|
|
24
|
-
export type CFBookingResource = BookingResource;
|
|
25
|
-
export type CFBooking = Booking;
|
|
26
|
-
/** A service sitting in the cart, and the same sale once it is on an order. A flow that draws a
|
|
27
|
-
* cart or a receipt needs both by name — reaching into the package's dist for them is not an
|
|
28
|
-
* interface. */
|
|
29
|
-
export type CFCartReservation = CartReservation;
|
|
30
|
-
export type CFOrderReservation = OrderReservation;
|
|
31
21
|
export type CFActiveProduct = ActiveProduct;
|
|
32
22
|
export type CFCustomer = ActiveCustomer;
|
|
33
23
|
export type CFActiveCustomer = ActiveCustomer;
|
|
@@ -116,21 +106,6 @@ export interface CFContextRender {
|
|
|
116
106
|
stationName: string | null;
|
|
117
107
|
outletId: string | null;
|
|
118
108
|
outletName: string | null;
|
|
119
|
-
/**
|
|
120
|
-
* IANA zone the SHOP keeps, resolved as the outlet's own zone, else the
|
|
121
|
-
* company's — never the device's. A booking is stored as an instant, so which
|
|
122
|
-
* hour and which DAY it reads as is decided entirely by the zone it is printed
|
|
123
|
-
* in, and the screen's own machine is the one zone that is certainly wrong: a
|
|
124
|
-
* 17:00 Monday appointment in Vancouver is 21:30 for a till in St John's, and
|
|
125
|
-
* for some viewers it lands on Tuesday. Without this an app cannot label a
|
|
126
|
-
* slot or an appointment honestly — it can only guess, and it guesses wrong
|
|
127
|
-
* for every business that does not sit in the same zone as its screen.
|
|
128
|
-
*
|
|
129
|
-
* `null` when neither the outlet nor the company has one configured. Say so
|
|
130
|
-
* rather than falling back to the device: a missing timezone is a setup
|
|
131
|
-
* problem, and printing the machine's clock hides it.
|
|
132
|
-
*/
|
|
133
|
-
timeZone: string | null;
|
|
134
109
|
buildId: string | null;
|
|
135
110
|
buildName: string | null;
|
|
136
111
|
buildVersion: string | null;
|
|
@@ -11,10 +11,6 @@ export const mockGetContext = () => {
|
|
|
11
11
|
stationName: MOCK_STATION.name,
|
|
12
12
|
outletId: MOCK_OUTLET.id,
|
|
13
13
|
outletName: MOCK_OUTLET.name || null,
|
|
14
|
-
// Deliberately NOT the developer's own zone: an app that quietly formats with
|
|
15
|
-
// the machine's clock looks correct in preview and is wrong on every till that
|
|
16
|
-
// sits in a different city. Here it cannot look correct by accident.
|
|
17
|
-
timeZone: "America/Vancouver",
|
|
18
14
|
buildId: "mock_build_id",
|
|
19
15
|
buildName: "Mock Build",
|
|
20
16
|
buildVersion: "1.0.0-mock",
|
|
@@ -18,14 +18,11 @@ export interface GetRefundPlanParams {
|
|
|
18
18
|
* nothing staged, no `allocation` comes back.
|
|
19
19
|
*/
|
|
20
20
|
items?: {
|
|
21
|
-
/**
|
|
22
|
-
* `internalId` / `variantId` for a product, `customSaleId`, cart-fee id, tip
|
|
23
|
-
* `transactionId`, or a booking's own `internalId` on `order.reservations[]`.
|
|
24
|
-
*/
|
|
21
|
+
/** `internalId` / `variantId` for a product, `customSaleId`, cart-fee id, or tip `transactionId`. */
|
|
25
22
|
itemKey: string;
|
|
26
23
|
quantity: number;
|
|
27
24
|
/** Optional hint; inferred from the order when omitted. */
|
|
28
|
-
type?: 'product' | 'customSale' | 'fee' | 'tip'
|
|
25
|
+
type?: 'product' | 'customSale' | 'fee' | 'tip';
|
|
29
26
|
}[];
|
|
30
27
|
}
|
|
31
28
|
export interface RefundPlanSource {
|
|
@@ -18,14 +18,6 @@ export interface GetRemainingRefundableQuantitiesResponse {
|
|
|
18
18
|
* 0/1 semantics: `1` = still refundable, `0` = already refunded.
|
|
19
19
|
*/
|
|
20
20
|
tips: Record<string, number>;
|
|
21
|
-
/**
|
|
22
|
-
* Remaining refundable bookings, keyed by `order.reservations[].internalId` —
|
|
23
|
-
* the same key `processPartialRefund` takes for `type: 'reservation'` items.
|
|
24
|
-
* A service is sold as a reservation rather than a line item, so without this
|
|
25
|
-
* map a refund screen can show everything on the order EXCEPT the appointment.
|
|
26
|
-
* 0/1 semantics: `1` = still refundable, `0` = already refunded.
|
|
27
|
-
*/
|
|
28
|
-
reservations: Record<string, number>;
|
|
29
21
|
timestamp: string;
|
|
30
22
|
}
|
|
31
23
|
export type GetRemainingRefundableQuantities = (params?: GetRemainingRefundableQuantitiesParams) => Promise<GetRemainingRefundableQuantitiesResponse>;
|
|
@@ -131,10 +131,10 @@ export interface ProcessPartialRefundParams {
|
|
|
131
131
|
};
|
|
132
132
|
/** Optional items to refund. */
|
|
133
133
|
items?: {
|
|
134
|
-
/** internalId or variantId
|
|
134
|
+
/** internalId or variantId or customSaleId. */
|
|
135
135
|
itemKey: string;
|
|
136
136
|
quantity: number;
|
|
137
|
-
type?: 'product' | 'customSale' | 'fee' | 'tip'
|
|
137
|
+
type?: 'product' | 'customSale' | 'fee' | 'tip';
|
|
138
138
|
/**
|
|
139
139
|
* Per-item stock disposition for a refunded **product** line — the
|
|
140
140
|
* headless equivalent of the old refund popup's per-row restock/damaged
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resume Checkout action
|
|
3
|
+
* Calls the resumeCheckout action on the parent window
|
|
4
|
+
*/
|
|
5
|
+
import { commandFrameClient } from '../../client';
|
|
6
|
+
export const resumeCheckout = async (params) => {
|
|
7
|
+
return await commandFrameClient.call('resumeCheckout', params ?? {});
|
|
8
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ResumeCheckout } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Standalone mock of the checkout return leg.
|
|
4
|
+
*
|
|
5
|
+
* It reads the SAME query parameters the real provider sends a shopper back
|
|
6
|
+
* with, so a builder can exercise the redirect path in preview by appending
|
|
7
|
+
* them by hand:
|
|
8
|
+
*
|
|
9
|
+
* ?sessionId=CS_MOCK&redirectResult=MOCK
|
|
10
|
+
*
|
|
11
|
+
* Without them it answers `{ resumed: false }` and does nothing, which is what
|
|
12
|
+
* the real command does on an ordinary page load. That is the branch a
|
|
13
|
+
* checkout screen hits on every normal visit, so it is the one worth getting
|
|
14
|
+
* right in a mock.
|
|
15
|
+
*/
|
|
16
|
+
export declare const mockResumeCheckout: ResumeCheckout;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mockPublishEvent } from '../../demo/database';
|
|
2
|
+
/**
|
|
3
|
+
* Standalone mock of the checkout return leg.
|
|
4
|
+
*
|
|
5
|
+
* It reads the SAME query parameters the real provider sends a shopper back
|
|
6
|
+
* with, so a builder can exercise the redirect path in preview by appending
|
|
7
|
+
* them by hand:
|
|
8
|
+
*
|
|
9
|
+
* ?sessionId=CS_MOCK&redirectResult=MOCK
|
|
10
|
+
*
|
|
11
|
+
* Without them it answers `{ resumed: false }` and does nothing, which is what
|
|
12
|
+
* the real command does on an ordinary page load. That is the branch a
|
|
13
|
+
* checkout screen hits on every normal visit, so it is the one worth getting
|
|
14
|
+
* right in a mock.
|
|
15
|
+
*/
|
|
16
|
+
export const mockResumeCheckout = async (params) => {
|
|
17
|
+
console.log('[Mock] resumeCheckout called', params);
|
|
18
|
+
const href = params?.url ?? (typeof window === 'undefined' ? '' : window.location.href);
|
|
19
|
+
let redirectResult = null;
|
|
20
|
+
try {
|
|
21
|
+
redirectResult = new URL(href).searchParams.get('redirectResult');
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
redirectResult = null;
|
|
25
|
+
}
|
|
26
|
+
if (!redirectResult) {
|
|
27
|
+
return {
|
|
28
|
+
success: true,
|
|
29
|
+
timestamp: new Date().toISOString(),
|
|
30
|
+
checkout: { resumed: false },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const suffix = String(Date.now()).slice(-6);
|
|
34
|
+
const checkout = {
|
|
35
|
+
resumed: true,
|
|
36
|
+
orderId: `mock-order-${suffix}`,
|
|
37
|
+
receiptId: `ON-001-${suffix}`,
|
|
38
|
+
// `Authorised` is what the real provider reports for a completed redirect.
|
|
39
|
+
// It is NOT settlement — see the checkout topic docs.
|
|
40
|
+
resultCode: 'Authorised',
|
|
41
|
+
sessionResult: `mock-session-result-${suffix}`,
|
|
42
|
+
orderPassword: `mock-pw-${suffix}`,
|
|
43
|
+
};
|
|
44
|
+
mockPublishEvent('checkout', 'payment-completed', {
|
|
45
|
+
orderId: checkout.orderId,
|
|
46
|
+
receiptId: checkout.receiptId,
|
|
47
|
+
resultCode: checkout.resultCode,
|
|
48
|
+
sessionResult: checkout.sessionResult,
|
|
49
|
+
});
|
|
50
|
+
return { success: true, timestamp: new Date().toISOString(), checkout };
|
|
51
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface ResumeCheckoutParams {
|
|
2
|
+
/**
|
|
3
|
+
* Where to read the provider's return data from. Defaults to the current
|
|
4
|
+
* page URL, which is where the shopper has just landed.
|
|
5
|
+
*
|
|
6
|
+
* Only override this if you moved the query string somewhere else before
|
|
7
|
+
* calling — for example if your router strips it on mount.
|
|
8
|
+
*/
|
|
9
|
+
url?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* What the return leg produced. Every field past `resumed` is absent when
|
|
13
|
+
* `resumed` is `false`.
|
|
14
|
+
*/
|
|
15
|
+
export interface ResumeCheckoutResult {
|
|
16
|
+
/**
|
|
17
|
+
* `false` means THIS WAS AN ORDINARY PAGE LOAD — no provider return data was
|
|
18
|
+
* in the URL, so there was nothing to finish and nothing happened. It is not
|
|
19
|
+
* an error, and it is the answer on every normal visit to the page. Call this
|
|
20
|
+
* unconditionally when your checkout or confirmation screen mounts and branch
|
|
21
|
+
* on this field.
|
|
22
|
+
*/
|
|
23
|
+
resumed: boolean;
|
|
24
|
+
orderId?: string;
|
|
25
|
+
receiptId?: string;
|
|
26
|
+
/**
|
|
27
|
+
* The provider's result code for the completed attempt, e.g. `Authorised` or
|
|
28
|
+
* `Refused`. As everywhere else in this topic, `Authorised` is an
|
|
29
|
+
* authorisation and NOT a settlement.
|
|
30
|
+
*/
|
|
31
|
+
resultCode?: string;
|
|
32
|
+
/** Opaque proof of the payment — see `PaymentCompletedPayload.sessionResult`. */
|
|
33
|
+
sessionResult?: string;
|
|
34
|
+
/**
|
|
35
|
+
* The one-time order password from the original checkout, recovered from
|
|
36
|
+
* browser storage. The page that held it in memory is gone, so this is the
|
|
37
|
+
* only way back to the order's status after a redirect.
|
|
38
|
+
*/
|
|
39
|
+
orderPassword?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface ResumeCheckoutResponse {
|
|
42
|
+
success: boolean;
|
|
43
|
+
timestamp: string;
|
|
44
|
+
checkout: ResumeCheckoutResult;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Finish a checkout the shopper was redirected away from.
|
|
48
|
+
*
|
|
49
|
+
* SAFE TO CALL ON EVERY PAGE LOAD, and that is how it is meant to be used: on
|
|
50
|
+
* an ordinary visit it finds no return data and answers `{ resumed: false }`
|
|
51
|
+
* without touching the network. Calling it twice for one return is also safe —
|
|
52
|
+
* the return data is consumed the first time.
|
|
53
|
+
*
|
|
54
|
+
* The outcome is ALSO published on the `checkout` topic (`payment-completed` or
|
|
55
|
+
* `payment-failed`), exactly as it would have been had the shopper never left,
|
|
56
|
+
* so a page that already subscribes needs no second code path. Subscribe first,
|
|
57
|
+
* then call this, or the event fires before you are listening.
|
|
58
|
+
*/
|
|
59
|
+
export type ResumeCheckout = (params?: ResumeCheckoutParams) => Promise<ResumeCheckoutResponse>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Resume Checkout Types
|
|
2
|
+
//
|
|
3
|
+
// The RETURN LEG of an online checkout. Some payment methods — and 3-D Secure
|
|
4
|
+
// on a card — take the shopper away to their bank or wallet and send them back
|
|
5
|
+
// to `returnUrl` on a FRESH PAGE LOAD. The payment is not finished at that
|
|
6
|
+
// point: the provider hands the browser a one-time result in the URL, and it
|
|
7
|
+
// has to be handed back to the provider to complete the charge.
|
|
8
|
+
//
|
|
9
|
+
// `startCheckout` cannot do this. It ran in the page that is now gone.
|
|
10
|
+
//
|
|
11
|
+
// Served ONLY by the storefront runtime (a published website), like
|
|
12
|
+
// `startCheckout`. On a register it does not exist.
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StartCheckout } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Standalone mock of the storefront checkout.
|
|
4
|
+
*
|
|
5
|
+
* It mirrors the REFUSALS of the real command (empty cart, missing email,
|
|
6
|
+
* container not on the page) in the same order, because those are what a
|
|
7
|
+
* builder actually hits first — a mock that accepted anything would let a
|
|
8
|
+
* checkout screen look finished and fail on the published site.
|
|
9
|
+
*
|
|
10
|
+
* The mounted box is deliberately labelled as a mock. The real command mounts
|
|
11
|
+
* the provider's hosted card fields (iframes owned by the payment provider);
|
|
12
|
+
* nothing here takes a card number, and the placeholder must never look like
|
|
13
|
+
* it does. Its button publishes `payment-completed` so a flow's subscriber —
|
|
14
|
+
* the only way the outcome ever arrives — can be exercised in preview.
|
|
15
|
+
*/
|
|
16
|
+
export declare const mockStartCheckout: StartCheckout;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { MOCK_CART, MOCK_COMPANY, mockPublishEvent } from '../../demo/database';
|
|
2
|
+
/**
|
|
3
|
+
* Standalone mock of the storefront checkout.
|
|
4
|
+
*
|
|
5
|
+
* It mirrors the REFUSALS of the real command (empty cart, missing email,
|
|
6
|
+
* container not on the page) in the same order, because those are what a
|
|
7
|
+
* builder actually hits first — a mock that accepted anything would let a
|
|
8
|
+
* checkout screen look finished and fail on the published site.
|
|
9
|
+
*
|
|
10
|
+
* The mounted box is deliberately labelled as a mock. The real command mounts
|
|
11
|
+
* the provider's hosted card fields (iframes owned by the payment provider);
|
|
12
|
+
* nothing here takes a card number, and the placeholder must never look like
|
|
13
|
+
* it does. Its button publishes `payment-completed` so a flow's subscriber —
|
|
14
|
+
* the only way the outcome ever arrives — can be exercised in preview.
|
|
15
|
+
*/
|
|
16
|
+
export const mockStartCheckout = async (params) => {
|
|
17
|
+
console.log('[Mock] startCheckout called', params);
|
|
18
|
+
if (!params)
|
|
19
|
+
throw new Error('Params required');
|
|
20
|
+
if (!params.contact?.email || !params.contact.email.trim()) {
|
|
21
|
+
throw new Error('startCheckout: contact.email is required — it is where the receipt goes');
|
|
22
|
+
}
|
|
23
|
+
if (!params.container || typeof params.container !== 'string') {
|
|
24
|
+
throw new Error('startCheckout: container must be a CSS selector string (e.g. "#card-fields") — a DOM element cannot cross the command boundary');
|
|
25
|
+
}
|
|
26
|
+
const lineCount = (MOCK_CART.products?.length ?? 0) + (MOCK_CART.customSales?.length ?? 0);
|
|
27
|
+
if (!lineCount) {
|
|
28
|
+
throw new Error('startCheckout: cannot check out an empty cart');
|
|
29
|
+
}
|
|
30
|
+
// Resolved BEFORE the order is "created", exactly as the real command does:
|
|
31
|
+
// finding out the selector is wrong after an order exists would leave an
|
|
32
|
+
// unpaid order behind for a typo that cost nothing to catch first.
|
|
33
|
+
const container = typeof document === 'undefined' ? null : document.querySelector(params.container);
|
|
34
|
+
if (!container) {
|
|
35
|
+
throw new Error(`startCheckout: checkout container "${params.container}" was not found on the page`);
|
|
36
|
+
}
|
|
37
|
+
const serverTotal = Math.round(MOCK_CART.amountToBeCharged || MOCK_CART.total);
|
|
38
|
+
const currency = MOCK_COMPANY.settings?.currency ?? 'USD';
|
|
39
|
+
const suffix = String(Date.now()).slice(-6);
|
|
40
|
+
const order = {
|
|
41
|
+
orderId: `mock-order-${suffix}`,
|
|
42
|
+
receiptId: `ON-001-${suffix}`,
|
|
43
|
+
serverTotal,
|
|
44
|
+
currency,
|
|
45
|
+
paymentStatus: 'ready',
|
|
46
|
+
orderPassword: `mock-pw-${suffix}`,
|
|
47
|
+
};
|
|
48
|
+
mockPublishEvent('checkout', 'checkout-started', { ...order });
|
|
49
|
+
container.replaceChildren();
|
|
50
|
+
const box = document.createElement('div');
|
|
51
|
+
box.setAttribute('data-mock-checkout', 'true');
|
|
52
|
+
box.style.cssText =
|
|
53
|
+
'border:1px dashed #9aa;border-radius:8px;padding:16px;font:14px system-ui,sans-serif;text-align:center;color:#334';
|
|
54
|
+
const label = document.createElement('div');
|
|
55
|
+
label.textContent = 'Mock payment fields — the published site shows the real hosted card fields here.';
|
|
56
|
+
label.style.cssText = 'margin-bottom:12px;opacity:.75';
|
|
57
|
+
const button = document.createElement('button');
|
|
58
|
+
button.type = 'button';
|
|
59
|
+
button.textContent = `Simulate paying ${(serverTotal / 100).toFixed(2)} ${currency}`;
|
|
60
|
+
button.style.cssText =
|
|
61
|
+
'padding:10px 16px;border:0;border-radius:6px;background:#111;color:#fff;font-size:14px;cursor:pointer';
|
|
62
|
+
button.addEventListener('click', () => {
|
|
63
|
+
button.disabled = true;
|
|
64
|
+
// `Authorised` is what the real provider reports for a successful card
|
|
65
|
+
// entry. It is NOT settlement — see the checkout topic docs.
|
|
66
|
+
mockPublishEvent('checkout', 'payment-completed', {
|
|
67
|
+
orderId: order.orderId,
|
|
68
|
+
receiptId: order.receiptId,
|
|
69
|
+
resultCode: 'Authorised',
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
box.appendChild(label);
|
|
73
|
+
box.appendChild(button);
|
|
74
|
+
container.appendChild(box);
|
|
75
|
+
return {
|
|
76
|
+
success: true,
|
|
77
|
+
timestamp: new Date().toISOString(),
|
|
78
|
+
order,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guest contact for the order. There is no shopper account, so this is the only
|
|
3
|
+
* way to reach them — `email` is where the receipt goes and is required.
|
|
4
|
+
*/
|
|
5
|
+
export interface StartCheckoutContact {
|
|
6
|
+
email: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
phone?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface StartCheckoutParams {
|
|
11
|
+
contact: StartCheckoutContact;
|
|
12
|
+
/**
|
|
13
|
+
* CSS SELECTOR for the element the payment fields mount into, e.g.
|
|
14
|
+
* `'#card-fields'`. The element must already be in the DOM when this is
|
|
15
|
+
* called.
|
|
16
|
+
*
|
|
17
|
+
* A SELECTOR, NOT AN ELEMENT — and this is not a style preference. Command
|
|
18
|
+
* params cross a `postMessage` boundary, which serializes them with the
|
|
19
|
+
* structured clone algorithm; a DOM node is not cloneable, so passing
|
|
20
|
+
* `ref.current` throws `DataCloneError: HTMLDivElement object could not be
|
|
21
|
+
* cloned` before the command is ever sent. The string is resolved on the
|
|
22
|
+
* runtime's side against the same document the page rendered.
|
|
23
|
+
*/
|
|
24
|
+
container: string;
|
|
25
|
+
/**
|
|
26
|
+
* Where the provider returns the shopper after a redirect payment method
|
|
27
|
+
* (bank apps, wallets). Defaults to the current URL.
|
|
28
|
+
*
|
|
29
|
+
* Must be HTTPS and on the SAME ORIGIN the storefront token was minted for;
|
|
30
|
+
* the server rejects anything else, since this URL is handed to a payment
|
|
31
|
+
* provider and an open redirect on a checkout is a phishing primitive.
|
|
32
|
+
*/
|
|
33
|
+
returnUrl?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Overrides the cart-derived idempotency key. Leave unset: the default is
|
|
36
|
+
* derived from the cart, which is what makes a shopper's double-tap on "Pay"
|
|
37
|
+
* return the SAME order instead of creating a second, abandoned one.
|
|
38
|
+
*/
|
|
39
|
+
idempotencyKey?: string;
|
|
40
|
+
/** Defaults to the outlet the storefront booted against. */
|
|
41
|
+
outletId?: string;
|
|
42
|
+
/** Forwarded verbatim to the provider's Drop-in (`showPayButton`, `locale`, field styling, …). */
|
|
43
|
+
dropinConfiguration?: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The created order. Deliberately says nothing about money having moved — see
|
|
47
|
+
* `paymentStatus` and the `checkout` topic.
|
|
48
|
+
*/
|
|
49
|
+
export interface StartCheckoutOrder {
|
|
50
|
+
orderId: string;
|
|
51
|
+
receiptId: string;
|
|
52
|
+
/** The SERVER-computed total in integer minor units — the only total that matters. */
|
|
53
|
+
serverTotal: number;
|
|
54
|
+
currency: string;
|
|
55
|
+
/**
|
|
56
|
+
* `ready` — the payment fields are mounted and the shopper can pay.
|
|
57
|
+
* `unavailable` — THE ORDER EXISTS but no payment could be started, so the
|
|
58
|
+
* shopper has NOT been charged. Never report this as a failed order.
|
|
59
|
+
*/
|
|
60
|
+
paymentStatus: 'ready' | 'unavailable';
|
|
61
|
+
/**
|
|
62
|
+
* One-time password for reading this order back, returned only on the first
|
|
63
|
+
* create. It is the ONLY route to this order's status — the guest token
|
|
64
|
+
* grants order creation and deliberately not order reads — so a caller that
|
|
65
|
+
* drops it cannot check whether the payment settled.
|
|
66
|
+
*/
|
|
67
|
+
orderPassword?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface StartCheckoutResponse {
|
|
70
|
+
success: boolean;
|
|
71
|
+
timestamp: string;
|
|
72
|
+
order: StartCheckoutOrder;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Creates the server-priced order and mounts the provider's hosted card fields.
|
|
76
|
+
*
|
|
77
|
+
* RESOLVES WHEN THE SHOPPER *CAN* PAY, NOT WHEN THEY HAVE. The shopper has not
|
|
78
|
+
* typed a card yet when this returns. The outcome arrives on the `checkout`
|
|
79
|
+
* topic instead.
|
|
80
|
+
*/
|
|
81
|
+
export type StartCheckout = (params: StartCheckoutParams) => Promise<StartCheckoutResponse>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Start Checkout Types
|
|
2
|
+
//
|
|
3
|
+
// The ONLINE counterpart of the POS tenders: a shopper on a published website
|
|
4
|
+
// pays for the current cart with their own card. Mirrors kaching's storefront
|
|
5
|
+
// `startCheckout` input and result. All money is integer MINOR currency units.
|
|
6
|
+
//
|
|
7
|
+
// This command is served ONLY by the storefront runtime (a published website).
|
|
8
|
+
// On a register it does not exist, exactly as the till tenders (`cashPayment`,
|
|
9
|
+
// `terminalPayment`, `tapToPayPayment`, `chargeMoto`) do not exist online.
|
|
10
|
+
export {};
|
package/dist/demo/database.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Mock Database for Standalone/Demo Mode
|
|
3
3
|
* Stores mock data that mimics the Render environment
|
|
4
4
|
*/
|
|
5
|
-
import { CFActiveCompany, CFActiveUser, CFActiveStation, CFActiveOutlet, CFActiveOrder, CFCustomer, CFProduct, CFActiveCart, CFCategory, CFActiveProduct, CFSession, CFActiveRefundDetails, CFSmartGridLayout
|
|
5
|
+
import { CFActiveCompany, CFActiveUser, CFActiveStation, CFActiveOutlet, CFActiveOrder, CFCustomer, CFProduct, CFActiveCart, CFCategory, CFActiveProduct, CFSession, CFActiveRefundDetails, CFSmartGridLayout } from '../CommonTypes';
|
|
6
6
|
export * from './mocks';
|
|
7
7
|
/** Replace mock catalog / context data in place (same array references mock handlers use). */
|
|
8
8
|
export interface MockDatabaseConfig {
|
|
@@ -15,10 +15,6 @@ export interface MockDatabaseConfig {
|
|
|
15
15
|
products?: CFProduct[];
|
|
16
16
|
orders?: CFActiveOrder[];
|
|
17
17
|
parkedOrders?: CFActiveOrder[];
|
|
18
|
-
/** Who is scarce: the staff, rooms or machines the dataset's services are booked against. */
|
|
19
|
-
bookingResources?: CFBookingResource[];
|
|
20
|
-
/** Windows already taken when the dataset loads, so a calendar does not open empty. */
|
|
21
|
-
bookings?: CFBooking[];
|
|
22
18
|
}
|
|
23
19
|
export declare const MOCK_COMPANY: CFActiveCompany;
|
|
24
20
|
export declare const MOCK_OUTLET_MAIN: CFActiveOutlet;
|
|
@@ -63,7 +59,6 @@ export declare const MOCK_STATIONS: import("@final-commerce/common/pos-types").A
|
|
|
63
59
|
export declare const MOCK_OUTLETS: import("@final-commerce/common/pos-types").ActiveOutlet[];
|
|
64
60
|
export declare const MOCK_CUSTOMERS: import("@final-commerce/common/pos-types").ActiveCustomer[];
|
|
65
61
|
export declare const MOCK_CATEGORIES: import("@final-commerce/common/pos-types").Category[];
|
|
66
|
-
export declare const MOCK_PRODUCT_HAIRCUT: CFProduct;
|
|
67
62
|
export declare const MOCK_PRODUCTS: import("@final-commerce/common/pos-types").FullProduct[];
|
|
68
63
|
export declare const MOCK_ORDERS: import("@final-commerce/common/pos-types").ActiveOrder[];
|
|
69
64
|
export declare const MOCK_PARKED_ORDERS: CFActiveOrder[];
|
|
@@ -92,9 +87,3 @@ export declare const createOrderFromCart: (paymentType: string, amount: number,
|
|
|
92
87
|
* open with `amountToBeCharged` reset to what's left, and returns null.
|
|
93
88
|
*/
|
|
94
89
|
export declare const applyMockPayment: (amount: number, paymentType: string, processor?: string) => CFActiveOrder | null;
|
|
95
|
-
export declare const MOCK_BOOKING_RULES_ID = "rule_salon_30";
|
|
96
|
-
export declare const MOCK_BOOKING_RESOURCES: CFBookingResource[];
|
|
97
|
-
export declare const MOCK_BOOKINGS: CFBooking[];
|
|
98
|
-
/** Live = confirmed, or held and not yet expired. An expired hold occupies nothing. */
|
|
99
|
-
export declare const mockLiveBookings: () => CFBooking[];
|
|
100
|
-
export declare const mockBookingAvailability: (productId: string, from: Date, to: Date, resourceId?: string) => CFBookingAvailability;
|