@unifiedcommerce/plugin-pos 0.0.1
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/hooks/checkout-pos.d.ts +29 -0
- package/dist/hooks/checkout-pos.d.ts.map +1 -0
- package/dist/hooks/checkout-pos.js +69 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/payment-adapter.d.ts +33 -0
- package/dist/payment-adapter.d.ts.map +1 -0
- package/dist/payment-adapter.js +37 -0
- package/dist/routes/lookup.d.ts +9 -0
- package/dist/routes/lookup.d.ts.map +1 -0
- package/dist/routes/lookup.js +37 -0
- package/dist/routes/payments.d.ts +10 -0
- package/dist/routes/payments.d.ts.map +1 -0
- package/dist/routes/payments.js +62 -0
- package/dist/routes/receipts.d.ts +9 -0
- package/dist/routes/receipts.d.ts.map +1 -0
- package/dist/routes/receipts.js +28 -0
- package/dist/routes/returns.d.ts +21 -0
- package/dist/routes/returns.d.ts.map +1 -0
- package/dist/routes/returns.js +83 -0
- package/dist/routes/shifts.d.ts +9 -0
- package/dist/routes/shifts.d.ts.map +1 -0
- package/dist/routes/shifts.js +91 -0
- package/dist/routes/terminals.d.ts +9 -0
- package/dist/routes/terminals.d.ts.map +1 -0
- package/dist/routes/terminals.js +55 -0
- package/dist/routes/transactions.d.ts +19 -0
- package/dist/routes/transactions.d.ts.map +1 -0
- package/dist/routes/transactions.js +175 -0
- package/dist/schema.d.ts +1337 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +123 -0
- package/dist/services/lookup-service.d.ts +38 -0
- package/dist/services/lookup-service.d.ts.map +1 -0
- package/dist/services/lookup-service.js +104 -0
- package/dist/services/payment-service.d.ts +40 -0
- package/dist/services/payment-service.d.ts.map +1 -0
- package/dist/services/payment-service.js +99 -0
- package/dist/services/receipt-service.d.ts +45 -0
- package/dist/services/receipt-service.d.ts.map +1 -0
- package/dist/services/receipt-service.js +119 -0
- package/dist/services/return-service.d.ts +27 -0
- package/dist/services/return-service.d.ts.map +1 -0
- package/dist/services/return-service.js +51 -0
- package/dist/services/shift-service.d.ts +36 -0
- package/dist/services/shift-service.d.ts.map +1 -0
- package/dist/services/shift-service.js +198 -0
- package/dist/services/terminal-service.d.ts +21 -0
- package/dist/services/terminal-service.d.ts.map +1 -0
- package/dist/services/terminal-service.js +59 -0
- package/dist/services/transaction-service.d.ts +30 -0
- package/dist/services/transaction-service.d.ts.map +1 -0
- package/dist/services/transaction-service.js +202 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/package.json +40 -0
- package/src/hooks/checkout-pos.ts +93 -0
- package/src/index.ts +131 -0
- package/src/payment-adapter.ts +53 -0
- package/src/routes/lookup.ts +44 -0
- package/src/routes/payments.ts +82 -0
- package/src/routes/receipts.ts +35 -0
- package/src/routes/returns.ts +116 -0
- package/src/routes/shifts.ts +100 -0
- package/src/routes/terminals.ts +62 -0
- package/src/routes/transactions.ts +192 -0
- package/src/schema.ts +136 -0
- package/src/services/lookup-service.ts +136 -0
- package/src/services/payment-service.ts +133 -0
- package/src/services/receipt-service.ts +169 -0
- package/src/services/return-service.ts +65 -0
- package/src/services/shift-service.ts +260 -0
- package/src/services/terminal-service.ts +76 -0
- package/src/services/transaction-service.ts +248 -0
- package/src/types.ts +49 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POS-specific checkout hooks.
|
|
3
|
+
*
|
|
4
|
+
* checkout.beforePayment: Sets shippingTotal = 0 for POS transactions (no shipping).
|
|
5
|
+
* checkout.afterCreate: Updates POS transaction with orderId, increments shift counters.
|
|
6
|
+
*/
|
|
7
|
+
import type { Db } from "../types";
|
|
8
|
+
interface CheckoutData {
|
|
9
|
+
metadata?: Record<string, unknown> | null;
|
|
10
|
+
shippingTotal: number;
|
|
11
|
+
shippingAddress?: unknown;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Before payment hook: zero out shipping for POS transactions.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildPOSShippingHook(): {
|
|
18
|
+
key: string;
|
|
19
|
+
handler: (...args: unknown[]) => Promise<CheckoutData>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* After create hook: finalize POS transaction with order details.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildPOSFinalizationHook(getDb: () => Db): {
|
|
25
|
+
key: string;
|
|
26
|
+
handler: (...args: unknown[]) => Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=checkout-pos.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkout-pos.d.ts","sourceRoot":"","sources":["../../src/hooks/checkout-pos.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,UAAU,CAAC;AAEnC,UAAU,YAAY;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAaD;;GAEG;AACH,wBAAgB,oBAAoB;;uBAGP,OAAO,EAAE;EAWrC;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE;;uBAG3B,OAAO,EAAE;EAsCrC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POS-specific checkout hooks.
|
|
3
|
+
*
|
|
4
|
+
* checkout.beforePayment: Sets shippingTotal = 0 for POS transactions (no shipping).
|
|
5
|
+
* checkout.afterCreate: Updates POS transaction with orderId, increments shift counters.
|
|
6
|
+
*/
|
|
7
|
+
import { eq, sql } from "drizzle-orm";
|
|
8
|
+
import { posTransactions, posShifts } from "../schema";
|
|
9
|
+
/**
|
|
10
|
+
* Before payment hook: zero out shipping for POS transactions.
|
|
11
|
+
*/
|
|
12
|
+
export function buildPOSShippingHook() {
|
|
13
|
+
return {
|
|
14
|
+
key: "checkout.beforePayment",
|
|
15
|
+
handler: async (...args) => {
|
|
16
|
+
const hook = args[0];
|
|
17
|
+
const posTransactionId = hook.data.metadata?.posTransactionId;
|
|
18
|
+
if (!posTransactionId)
|
|
19
|
+
return hook.data;
|
|
20
|
+
// POS transactions have no shipping
|
|
21
|
+
hook.data.shippingTotal = 0;
|
|
22
|
+
hook.data.shippingAddress = undefined;
|
|
23
|
+
return hook.data;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* After create hook: finalize POS transaction with order details.
|
|
29
|
+
*/
|
|
30
|
+
export function buildPOSFinalizationHook(getDb) {
|
|
31
|
+
return {
|
|
32
|
+
key: "checkout.afterCreate",
|
|
33
|
+
handler: async (...args) => {
|
|
34
|
+
const hook = args[0];
|
|
35
|
+
const result = hook.result;
|
|
36
|
+
if (!result)
|
|
37
|
+
return;
|
|
38
|
+
// Check if this checkout was initiated by a POS transaction
|
|
39
|
+
// The metadata is on the order, not directly on the hook result
|
|
40
|
+
const metadata = result.metadata;
|
|
41
|
+
const posTransactionId = metadata?.posTransactionId;
|
|
42
|
+
if (!posTransactionId)
|
|
43
|
+
return;
|
|
44
|
+
const db = getDb();
|
|
45
|
+
// Update transaction with orderId and status
|
|
46
|
+
await db
|
|
47
|
+
.update(posTransactions)
|
|
48
|
+
.set({
|
|
49
|
+
orderId: result.id,
|
|
50
|
+
status: "completed",
|
|
51
|
+
completedAt: new Date(),
|
|
52
|
+
updatedAt: new Date(),
|
|
53
|
+
})
|
|
54
|
+
.where(eq(posTransactions.id, posTransactionId));
|
|
55
|
+
// Increment shift sales counters
|
|
56
|
+
const posShiftId = metadata?.posShiftId;
|
|
57
|
+
if (posShiftId) {
|
|
58
|
+
await db
|
|
59
|
+
.update(posShifts)
|
|
60
|
+
.set({
|
|
61
|
+
salesCount: sql `${posShifts.salesCount} + 1`,
|
|
62
|
+
salesTotal: sql `${posShifts.salesTotal} + ${result.grandTotal ?? 0}`,
|
|
63
|
+
updatedAt: new Date(),
|
|
64
|
+
})
|
|
65
|
+
.where(eq(posShifts.id, posShiftId));
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { POSPluginOptions } from "./types";
|
|
2
|
+
export type { POSPluginOptions } from "./types";
|
|
3
|
+
export { TerminalService } from "./services/terminal-service";
|
|
4
|
+
export { ShiftService } from "./services/shift-service";
|
|
5
|
+
export { TransactionService } from "./services/transaction-service";
|
|
6
|
+
export { PaymentService } from "./services/payment-service";
|
|
7
|
+
export { ReturnService } from "./services/return-service";
|
|
8
|
+
export { LookupService } from "./services/lookup-service";
|
|
9
|
+
export { ReceiptService } from "./services/receipt-service";
|
|
10
|
+
export { createPOSPaymentAdapter } from "./payment-adapter";
|
|
11
|
+
/**
|
|
12
|
+
* POS Plugin — Tier 0 Core Primitives (RFC-023)
|
|
13
|
+
*
|
|
14
|
+
* Provides:
|
|
15
|
+
* - Terminal management (register, tablet, mobile, kiosk)
|
|
16
|
+
* - Shift management (open/close with cash tracking, Z-report)
|
|
17
|
+
* - Transaction lifecycle (create, hold/recall, void, complete)
|
|
18
|
+
* - Split payment support (cash, card, gift card, store credit)
|
|
19
|
+
* - Returns with original order linkage
|
|
20
|
+
* - Barcode/SKU lookup via indexed queries
|
|
21
|
+
* - Receipt data assembly + email
|
|
22
|
+
* - POS payment adapter for checkout pipeline
|
|
23
|
+
* - Checkout hooks (zero shipping, transaction finalization)
|
|
24
|
+
*/
|
|
25
|
+
export declare function posPlugin(userOptions?: POSPluginOptions): import("@unifiedcommerce/core").CommercePlugin;
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,gBAAgB,EAAM,MAAM,SAAS,CAAC;AAGpD,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,WAAW,GAAE,gBAAqB,kDA+E3D"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { defineCommercePlugin } from "@unifiedcommerce/core";
|
|
2
|
+
import { posTerminals, posShifts, posCashEvents, posTransactions, posPayments, posReturnItems, } from "./schema";
|
|
3
|
+
import { TerminalService } from "./services/terminal-service";
|
|
4
|
+
import { ShiftService } from "./services/shift-service";
|
|
5
|
+
import { TransactionService } from "./services/transaction-service";
|
|
6
|
+
import { PaymentService } from "./services/payment-service";
|
|
7
|
+
import { ReturnService } from "./services/return-service";
|
|
8
|
+
import { LookupService } from "./services/lookup-service";
|
|
9
|
+
import { ReceiptService } from "./services/receipt-service";
|
|
10
|
+
import { buildTerminalRoutes } from "./routes/terminals";
|
|
11
|
+
import { buildShiftRoutes } from "./routes/shifts";
|
|
12
|
+
import { buildTransactionRoutes } from "./routes/transactions";
|
|
13
|
+
import { buildPaymentRoutes } from "./routes/payments";
|
|
14
|
+
import { buildReturnRoutes } from "./routes/returns";
|
|
15
|
+
import { buildLookupRoutes } from "./routes/lookup";
|
|
16
|
+
import { buildReceiptRoutes } from "./routes/receipts";
|
|
17
|
+
import { buildPOSShippingHook, buildPOSFinalizationHook } from "./hooks/checkout-pos";
|
|
18
|
+
import { DEFAULT_POS_OPTIONS } from "./types";
|
|
19
|
+
export { TerminalService } from "./services/terminal-service";
|
|
20
|
+
export { ShiftService } from "./services/shift-service";
|
|
21
|
+
export { TransactionService } from "./services/transaction-service";
|
|
22
|
+
export { PaymentService } from "./services/payment-service";
|
|
23
|
+
export { ReturnService } from "./services/return-service";
|
|
24
|
+
export { LookupService } from "./services/lookup-service";
|
|
25
|
+
export { ReceiptService } from "./services/receipt-service";
|
|
26
|
+
export { createPOSPaymentAdapter } from "./payment-adapter";
|
|
27
|
+
/**
|
|
28
|
+
* POS Plugin — Tier 0 Core Primitives (RFC-023)
|
|
29
|
+
*
|
|
30
|
+
* Provides:
|
|
31
|
+
* - Terminal management (register, tablet, mobile, kiosk)
|
|
32
|
+
* - Shift management (open/close with cash tracking, Z-report)
|
|
33
|
+
* - Transaction lifecycle (create, hold/recall, void, complete)
|
|
34
|
+
* - Split payment support (cash, card, gift card, store credit)
|
|
35
|
+
* - Returns with original order linkage
|
|
36
|
+
* - Barcode/SKU lookup via indexed queries
|
|
37
|
+
* - Receipt data assembly + email
|
|
38
|
+
* - POS payment adapter for checkout pipeline
|
|
39
|
+
* - Checkout hooks (zero shipping, transaction finalization)
|
|
40
|
+
*/
|
|
41
|
+
export function posPlugin(userOptions = {}) {
|
|
42
|
+
const options = {
|
|
43
|
+
...DEFAULT_POS_OPTIONS,
|
|
44
|
+
...userOptions,
|
|
45
|
+
};
|
|
46
|
+
// Shared DB reference for hooks (populated in routes())
|
|
47
|
+
const dbRef = { current: null };
|
|
48
|
+
return defineCommercePlugin({
|
|
49
|
+
id: "pos",
|
|
50
|
+
version: "1.0.0",
|
|
51
|
+
permissions: [
|
|
52
|
+
{
|
|
53
|
+
scope: "pos:admin",
|
|
54
|
+
description: "Register terminals, view all shifts, Z-reports, configure POS settings.",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
scope: "pos:manage",
|
|
58
|
+
description: "Void transactions, apply discounts, process returns, override price.",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
scope: "pos:operate",
|
|
62
|
+
description: "Open/close shifts, ring up sales, accept payment, hold/recall, reprint receipts.",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
schema: () => ({
|
|
66
|
+
posTerminals,
|
|
67
|
+
posShifts,
|
|
68
|
+
posCashEvents,
|
|
69
|
+
posTransactions,
|
|
70
|
+
posPayments,
|
|
71
|
+
posReturnItems,
|
|
72
|
+
}),
|
|
73
|
+
hooks: () => [
|
|
74
|
+
buildPOSShippingHook(),
|
|
75
|
+
buildPOSFinalizationHook(() => {
|
|
76
|
+
if (!dbRef.current)
|
|
77
|
+
throw new Error("POS plugin DB not initialized — hooks ran before routes()");
|
|
78
|
+
return dbRef.current;
|
|
79
|
+
}),
|
|
80
|
+
],
|
|
81
|
+
routes: (ctx) => {
|
|
82
|
+
const db = ctx.database.db;
|
|
83
|
+
if (!db)
|
|
84
|
+
return [];
|
|
85
|
+
// Populate the shared DB reference for hooks
|
|
86
|
+
dbRef.current = db;
|
|
87
|
+
const transactionFn = ctx.database.transaction;
|
|
88
|
+
// Initialize services
|
|
89
|
+
const terminalService = new TerminalService(db);
|
|
90
|
+
const shiftService = new ShiftService(db, transactionFn);
|
|
91
|
+
const transactionService = new TransactionService(db, transactionFn);
|
|
92
|
+
const paymentService = new PaymentService(db, transactionFn);
|
|
93
|
+
const returnService = new ReturnService(db);
|
|
94
|
+
const lookupService = new LookupService(db, ctx.services);
|
|
95
|
+
const receiptService = new ReceiptService(db, ctx.services);
|
|
96
|
+
// Cart service from core (for creating POS transaction carts)
|
|
97
|
+
const cartService = ctx.services.cart;
|
|
98
|
+
return [
|
|
99
|
+
...buildTerminalRoutes(terminalService, ctx),
|
|
100
|
+
...buildShiftRoutes(shiftService, ctx),
|
|
101
|
+
...buildTransactionRoutes(transactionService, cartService, ctx),
|
|
102
|
+
...buildPaymentRoutes(paymentService, transactionService, ctx),
|
|
103
|
+
...buildReturnRoutes(returnService, transactionService, paymentService, cartService, ctx),
|
|
104
|
+
...buildLookupRoutes(lookupService, ctx),
|
|
105
|
+
...buildReceiptRoutes(receiptService, ctx),
|
|
106
|
+
];
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POS Payment Adapter for the checkout pipeline.
|
|
3
|
+
*
|
|
4
|
+
* POS payments are collected at the terminal before checkout is called.
|
|
5
|
+
* The adapter acts as a pass-through: payment has already been tendered,
|
|
6
|
+
* so authorize/capture are no-ops.
|
|
7
|
+
*/
|
|
8
|
+
export interface PaymentAdapter {
|
|
9
|
+
providerId: string;
|
|
10
|
+
createPaymentIntent(params: {
|
|
11
|
+
amount: number;
|
|
12
|
+
currency: string;
|
|
13
|
+
orderId?: string;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
id: string;
|
|
17
|
+
status: string;
|
|
18
|
+
amount: number;
|
|
19
|
+
clientSecret?: string;
|
|
20
|
+
}>;
|
|
21
|
+
capturePayment(intentId: string, amount: number): Promise<{
|
|
22
|
+
id: string;
|
|
23
|
+
status: string;
|
|
24
|
+
amountCaptured: number;
|
|
25
|
+
}>;
|
|
26
|
+
refundPayment(paymentId: string, amount: number): Promise<{
|
|
27
|
+
id: string;
|
|
28
|
+
status: string;
|
|
29
|
+
amountRefunded: number;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export declare function createPOSPaymentAdapter(): PaymentAdapter;
|
|
33
|
+
//# sourceMappingURL=payment-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-adapter.d.ts","sourceRoot":"","sources":["../src/payment-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,MAAM,EAAE;QAC1B,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnF,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnH;AAED,wBAAgB,uBAAuB,IAAI,cAAc,CAgCxD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POS Payment Adapter for the checkout pipeline.
|
|
3
|
+
*
|
|
4
|
+
* POS payments are collected at the terminal before checkout is called.
|
|
5
|
+
* The adapter acts as a pass-through: payment has already been tendered,
|
|
6
|
+
* so authorize/capture are no-ops.
|
|
7
|
+
*/
|
|
8
|
+
export function createPOSPaymentAdapter() {
|
|
9
|
+
return {
|
|
10
|
+
providerId: "pos",
|
|
11
|
+
async createPaymentIntent(params) {
|
|
12
|
+
// POS payments are already collected at the terminal.
|
|
13
|
+
// The "intent" represents the sum of pos_payments rows.
|
|
14
|
+
return {
|
|
15
|
+
id: `pos_${params.orderId ?? crypto.randomUUID()}`,
|
|
16
|
+
status: "requires_capture",
|
|
17
|
+
amount: params.amount,
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
async capturePayment(intentId, amount) {
|
|
21
|
+
// POS payments are already collected. Capture is a no-op.
|
|
22
|
+
return {
|
|
23
|
+
id: intentId,
|
|
24
|
+
status: "succeeded",
|
|
25
|
+
amountCaptured: amount,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
async refundPayment(paymentId, amount) {
|
|
29
|
+
// POS refunds are handled by the return transaction flow.
|
|
30
|
+
return {
|
|
31
|
+
id: `ref_${paymentId}`,
|
|
32
|
+
status: "succeeded",
|
|
33
|
+
amountRefunded: amount,
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LookupService } from "../services/lookup-service";
|
|
2
|
+
import type { PluginRouteRegistration } from "@unifiedcommerce/core";
|
|
3
|
+
export declare function buildLookupRoutes(service: LookupService, ctx: {
|
|
4
|
+
services?: Record<string, unknown>;
|
|
5
|
+
database?: {
|
|
6
|
+
db: unknown;
|
|
7
|
+
};
|
|
8
|
+
}): PluginRouteRegistration[];
|
|
9
|
+
//# sourceMappingURL=lookup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lookup.d.ts","sourceRoot":"","sources":["../../src/routes/lookup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CAmC3B"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { router } from "@unifiedcommerce/core";
|
|
2
|
+
import { z } from "@hono/zod-openapi";
|
|
3
|
+
export function buildLookupRoutes(service, ctx) {
|
|
4
|
+
const r = router("POS Lookup", "/pos/lookup", ctx);
|
|
5
|
+
r.get("/barcode/{code}")
|
|
6
|
+
.summary("Lookup by barcode")
|
|
7
|
+
.permission("pos:operate")
|
|
8
|
+
.handler(async ({ params, orgId }) => {
|
|
9
|
+
const result = await service.byBarcode(orgId, params.code);
|
|
10
|
+
if (!result.ok)
|
|
11
|
+
throw new Error(result.error);
|
|
12
|
+
return result.value;
|
|
13
|
+
});
|
|
14
|
+
r.get("/sku/{sku}")
|
|
15
|
+
.summary("Lookup by SKU")
|
|
16
|
+
.permission("pos:operate")
|
|
17
|
+
.handler(async ({ params, orgId }) => {
|
|
18
|
+
const result = await service.bySku(orgId, params.sku);
|
|
19
|
+
if (!result.ok)
|
|
20
|
+
throw new Error(result.error);
|
|
21
|
+
return result.value;
|
|
22
|
+
});
|
|
23
|
+
r.get("/search")
|
|
24
|
+
.summary("Search items")
|
|
25
|
+
.permission("pos:operate")
|
|
26
|
+
.query(z.object({
|
|
27
|
+
q: z.string().min(1).max(200),
|
|
28
|
+
}))
|
|
29
|
+
.handler(async ({ query, orgId }) => {
|
|
30
|
+
const q = query;
|
|
31
|
+
const result = await service.search(orgId, q.q);
|
|
32
|
+
if (!result.ok)
|
|
33
|
+
throw new Error(result.error);
|
|
34
|
+
return result.value;
|
|
35
|
+
});
|
|
36
|
+
return r.routes();
|
|
37
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PaymentService } from "../services/payment-service";
|
|
2
|
+
import type { TransactionService } from "../services/transaction-service";
|
|
3
|
+
import type { PluginRouteRegistration } from "@unifiedcommerce/core";
|
|
4
|
+
export declare function buildPaymentRoutes(paymentService: PaymentService, transactionService: TransactionService, ctx: {
|
|
5
|
+
services?: Record<string, unknown>;
|
|
6
|
+
database?: {
|
|
7
|
+
db: unknown;
|
|
8
|
+
};
|
|
9
|
+
}): PluginRouteRegistration[];
|
|
10
|
+
//# sourceMappingURL=payments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payments.d.ts","sourceRoot":"","sources":["../../src/routes/payments.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,cAAc,EAC9B,kBAAkB,EAAE,kBAAkB,EACtC,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CAuE3B"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { router } from "@unifiedcommerce/core";
|
|
2
|
+
import { z } from "@hono/zod-openapi";
|
|
3
|
+
export function buildPaymentRoutes(paymentService, transactionService, ctx) {
|
|
4
|
+
const r = router("POS Payments", "/pos/transactions", ctx);
|
|
5
|
+
r.post("/{id}/payments")
|
|
6
|
+
.summary("Add payment")
|
|
7
|
+
.permission("pos:operate")
|
|
8
|
+
.input(z.object({
|
|
9
|
+
method: z.enum(["cash", "card", "gift_card", "store_credit", "other"]),
|
|
10
|
+
amount: z.number().int().positive(),
|
|
11
|
+
changeGiven: z.number().int().min(0).optional(),
|
|
12
|
+
reference: z.string().max(200).optional(),
|
|
13
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
14
|
+
}))
|
|
15
|
+
.handler(async ({ params, input, orgId }) => {
|
|
16
|
+
const body = input;
|
|
17
|
+
const result = await paymentService.addPayment(orgId, params.id, body);
|
|
18
|
+
if (!result.ok)
|
|
19
|
+
throw new Error(result.error);
|
|
20
|
+
return result.value;
|
|
21
|
+
});
|
|
22
|
+
r.post("/{id}/complete")
|
|
23
|
+
.summary("Complete transaction")
|
|
24
|
+
.description("Validates total payments >= total, then calls checkout pipeline.")
|
|
25
|
+
.permission("pos:operate")
|
|
26
|
+
.handler(async ({ params, services, orgId }) => {
|
|
27
|
+
// Validate payments cover the total
|
|
28
|
+
const validation = await paymentService.validateForCompletion(orgId, params.id);
|
|
29
|
+
if (!validation.ok)
|
|
30
|
+
throw new Error(validation.error);
|
|
31
|
+
const { transaction } = validation.value;
|
|
32
|
+
// Call checkout pipeline via the checkout service
|
|
33
|
+
// The POS payment adapter handles the payment side
|
|
34
|
+
const checkout = services;
|
|
35
|
+
// Build checkout request — POS goes through the same pipeline as online
|
|
36
|
+
const checkoutBody = {
|
|
37
|
+
cartId: transaction.cartId,
|
|
38
|
+
currency: "USD",
|
|
39
|
+
paymentMethodId: "pos",
|
|
40
|
+
metadata: {
|
|
41
|
+
posTransactionId: transaction.id,
|
|
42
|
+
posShiftId: transaction.shiftId,
|
|
43
|
+
posTerminalId: transaction.terminalId,
|
|
44
|
+
},
|
|
45
|
+
...(transaction.customerId != null ? { customerId: transaction.customerId } : {}),
|
|
46
|
+
};
|
|
47
|
+
// We need to make an internal call to the checkout pipeline
|
|
48
|
+
// This is handled by POSCheckoutAdapter in hooks/checkout-pos.ts
|
|
49
|
+
// For now, return the transaction with payment status
|
|
50
|
+
// The plugin entry point wires the checkout hook
|
|
51
|
+
// Mark transaction as completed (the afterCreate hook will set orderId)
|
|
52
|
+
const result = await transactionService.complete(params.id, null);
|
|
53
|
+
if (!result.ok)
|
|
54
|
+
throw new Error(result.error);
|
|
55
|
+
return {
|
|
56
|
+
transaction: result.value,
|
|
57
|
+
checkoutRequest: checkoutBody,
|
|
58
|
+
message: "Transaction completed. Use POST /api/checkout with the checkoutRequest body to finalize.",
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
return r.routes();
|
|
62
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReceiptService } from "../services/receipt-service";
|
|
2
|
+
import type { PluginRouteRegistration } from "@unifiedcommerce/core";
|
|
3
|
+
export declare function buildReceiptRoutes(service: ReceiptService, ctx: {
|
|
4
|
+
services?: Record<string, unknown>;
|
|
5
|
+
database?: {
|
|
6
|
+
db: unknown;
|
|
7
|
+
};
|
|
8
|
+
}): PluginRouteRegistration[];
|
|
9
|
+
//# sourceMappingURL=receipts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receipts.d.ts","sourceRoot":"","sources":["../../src/routes/receipts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CA0B3B"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { router } from "@unifiedcommerce/core";
|
|
2
|
+
import { z } from "@hono/zod-openapi";
|
|
3
|
+
export function buildReceiptRoutes(service, ctx) {
|
|
4
|
+
const r = router("POS Receipts", "/pos/transactions", ctx);
|
|
5
|
+
r.get("/{id}/receipt")
|
|
6
|
+
.summary("Get receipt")
|
|
7
|
+
.permission("pos:operate")
|
|
8
|
+
.handler(async ({ params, orgId }) => {
|
|
9
|
+
const result = await service.getReceipt(orgId, params.id);
|
|
10
|
+
if (!result.ok)
|
|
11
|
+
throw new Error(result.error);
|
|
12
|
+
return result.value;
|
|
13
|
+
});
|
|
14
|
+
r.post("/{id}/receipt/email")
|
|
15
|
+
.summary("Email receipt")
|
|
16
|
+
.permission("pos:operate")
|
|
17
|
+
.input(z.object({
|
|
18
|
+
email: z.string().email(),
|
|
19
|
+
}))
|
|
20
|
+
.handler(async ({ params, input, orgId }) => {
|
|
21
|
+
const body = input;
|
|
22
|
+
const result = await service.emailReceipt(orgId, params.id, body.email);
|
|
23
|
+
if (!result.ok)
|
|
24
|
+
throw new Error(result.error);
|
|
25
|
+
return result.value;
|
|
26
|
+
});
|
|
27
|
+
return r.routes();
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReturnService } from "../services/return-service";
|
|
2
|
+
import type { TransactionService } from "../services/transaction-service";
|
|
3
|
+
import type { PaymentService } from "../services/payment-service";
|
|
4
|
+
import type { PluginRouteRegistration } from "@unifiedcommerce/core";
|
|
5
|
+
export declare function buildReturnRoutes(returnService: ReturnService, transactionService: TransactionService, paymentService: PaymentService, cartService: {
|
|
6
|
+
create: (input: {
|
|
7
|
+
currency?: string;
|
|
8
|
+
metadata?: Record<string, unknown>;
|
|
9
|
+
}, actor: unknown) => Promise<{
|
|
10
|
+
ok: boolean;
|
|
11
|
+
value?: {
|
|
12
|
+
id: string;
|
|
13
|
+
};
|
|
14
|
+
}>;
|
|
15
|
+
}, ctx: {
|
|
16
|
+
services?: Record<string, unknown>;
|
|
17
|
+
database?: {
|
|
18
|
+
db: unknown;
|
|
19
|
+
};
|
|
20
|
+
}): PluginRouteRegistration[];
|
|
21
|
+
//# sourceMappingURL=returns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"returns.d.ts","sourceRoot":"","sources":["../../src/routes/returns.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,aAAa,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE;IAAE,MAAM,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAA;CAAE,EAC/J,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CAsG3B"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { router } from "@unifiedcommerce/core";
|
|
2
|
+
import { z } from "@hono/zod-openapi";
|
|
3
|
+
export function buildReturnRoutes(returnService, transactionService, paymentService, cartService, ctx) {
|
|
4
|
+
const r = router("POS Returns", "/pos/returns", ctx);
|
|
5
|
+
r.post("/")
|
|
6
|
+
.summary("Create return")
|
|
7
|
+
.permission("pos:manage")
|
|
8
|
+
.input(z.object({
|
|
9
|
+
shiftId: z.string().uuid(),
|
|
10
|
+
terminalId: z.string().uuid(),
|
|
11
|
+
originalOrderId: z.string().uuid(),
|
|
12
|
+
items: z.array(z.object({
|
|
13
|
+
originalLineItemId: z.string().uuid(),
|
|
14
|
+
quantity: z.number().int().positive(),
|
|
15
|
+
reason: z.enum(["defective", "wrong_item", "changed_mind", "other"]),
|
|
16
|
+
restockingFee: z.number().int().min(0).optional(),
|
|
17
|
+
refundAmount: z.number().int().positive(),
|
|
18
|
+
})).min(1),
|
|
19
|
+
}))
|
|
20
|
+
.handler(async ({ input, actor, orgId }) => {
|
|
21
|
+
const body = input;
|
|
22
|
+
// Create a cart for the return transaction
|
|
23
|
+
const cartResult = await cartService.create({ currency: "USD", metadata: { posReturn: true } }, actor);
|
|
24
|
+
if (!cartResult.ok || !cartResult.value) {
|
|
25
|
+
throw new Error("Failed to create cart for return");
|
|
26
|
+
}
|
|
27
|
+
// Create return transaction
|
|
28
|
+
const txnResult = await transactionService.create(orgId, {
|
|
29
|
+
shiftId: body.shiftId,
|
|
30
|
+
terminalId: body.terminalId,
|
|
31
|
+
operatorId: actor.userId,
|
|
32
|
+
cartId: cartResult.value.id,
|
|
33
|
+
type: "return",
|
|
34
|
+
});
|
|
35
|
+
if (!txnResult.ok)
|
|
36
|
+
throw new Error(txnResult.error);
|
|
37
|
+
// Record return items
|
|
38
|
+
const itemsResult = await returnService.addReturnItems(txnResult.value.id, body.items.map((item) => ({
|
|
39
|
+
...item,
|
|
40
|
+
originalOrderId: body.originalOrderId,
|
|
41
|
+
})));
|
|
42
|
+
if (!itemsResult.ok)
|
|
43
|
+
throw new Error(itemsResult.error);
|
|
44
|
+
// Update transaction totals
|
|
45
|
+
const refundTotal = body.items.reduce((sum, i) => sum + i.refundAmount, 0);
|
|
46
|
+
await transactionService.updateTotals(txnResult.value.id, {
|
|
47
|
+
subtotal: refundTotal,
|
|
48
|
+
taxTotal: 0,
|
|
49
|
+
total: refundTotal,
|
|
50
|
+
discountTotal: 0,
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
transaction: txnResult.value,
|
|
54
|
+
returnItems: itemsResult.value,
|
|
55
|
+
refundTotal,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
r.post("/{id}/payments")
|
|
59
|
+
.summary("Add refund payment")
|
|
60
|
+
.permission("pos:operate")
|
|
61
|
+
.input(z.object({
|
|
62
|
+
method: z.enum(["cash", "card", "gift_card", "store_credit", "other"]),
|
|
63
|
+
amount: z.number().int().positive(),
|
|
64
|
+
reference: z.string().max(200).optional(),
|
|
65
|
+
}))
|
|
66
|
+
.handler(async ({ params, input, orgId }) => {
|
|
67
|
+
const body = input;
|
|
68
|
+
const result = await paymentService.addPayment(orgId, params.id, body);
|
|
69
|
+
if (!result.ok)
|
|
70
|
+
throw new Error(result.error);
|
|
71
|
+
return result.value;
|
|
72
|
+
});
|
|
73
|
+
r.post("/{id}/complete")
|
|
74
|
+
.summary("Complete return")
|
|
75
|
+
.permission("pos:operate")
|
|
76
|
+
.handler(async ({ params, actor }) => {
|
|
77
|
+
const result = await transactionService.complete(params.id, null);
|
|
78
|
+
if (!result.ok)
|
|
79
|
+
throw new Error(result.error);
|
|
80
|
+
return result.value;
|
|
81
|
+
});
|
|
82
|
+
return r.routes();
|
|
83
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ShiftService } from "../services/shift-service";
|
|
2
|
+
import type { PluginRouteRegistration } from "@unifiedcommerce/core";
|
|
3
|
+
export declare function buildShiftRoutes(service: ShiftService, ctx: {
|
|
4
|
+
services?: Record<string, unknown>;
|
|
5
|
+
database?: {
|
|
6
|
+
db: unknown;
|
|
7
|
+
};
|
|
8
|
+
}): PluginRouteRegistration[];
|
|
9
|
+
//# sourceMappingURL=shifts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shifts.d.ts","sourceRoot":"","sources":["../../src/routes/shifts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CA2F3B"}
|