@porulle/core 0.10.5 → 0.10.8

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.
@@ -42,6 +42,8 @@ export interface CheckoutLineItem {
42
42
  }
43
43
  export interface CheckoutData {
44
44
  checkoutId: string;
45
+ /** Server-owned order id allocated before payment authorization. */
46
+ orderId?: string;
45
47
  cartId: string;
46
48
  customerId?: string;
47
49
  customerGroupIds?: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../../src/hooks/checkout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../../src/hooks/checkout.ts"],"names":[],"mappings":"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"}
@@ -219,6 +219,8 @@ export const authorizePayment = async ({ data, context, }) => {
219
219
  ? { idempotencyKey: data.idempotencyKey }
220
220
  : {}),
221
221
  metadata: {
222
+ ...(data.orderId !== undefined ? { orderId: data.orderId } : {}),
223
+ organizationId: resolveOrgId(context.actor),
222
224
  checkoutId: data.checkoutId,
223
225
  cartId: data.cartId,
224
226
  },
@@ -1 +1 @@
1
- {"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../../../../src/interfaces/rest/routes/checkout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAoBzD,OAAO,EAAE,KAAK,MAAM,EAAwC,MAAM,aAAa,CAAC;AAKhF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAEpD;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,WAAW,CAAC,EAC1C,KAAK,EAAE,KAAK,GAAG,IAAI,EACnB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmC7B;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,gCAgU5C"}
1
+ {"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../../../../src/interfaces/rest/routes/checkout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAoBzD,OAAO,EAAE,KAAK,MAAM,EAAwC,MAAM,aAAa,CAAC;AAMhF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAEpD;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,WAAW,CAAC,EAC1C,KAAK,EAAE,KAAK,GAAG,IAAI,EACnB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmC7B;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,gCAgV5C"}
@@ -6,7 +6,8 @@ import { createHookContext } from "../../../kernel/hooks/create-context.js";
6
6
  import { mapErrorToResponse, mapErrorToStatus } from "../utils.js";
7
7
  import { isCommerceError } from "../../../kernel/errors.js";
8
8
  import { assertPermission } from "../../../auth/permissions.js";
9
- import { makeId } from "../../../utils/id.js";
9
+ import { resolveOrgId } from "../../../auth/org.js";
10
+ import { makeDeterministicId, makeId } from "../../../utils/id.js";
10
11
  /**
11
12
  * SEC-07 — resolve the customer profile a checkout order is attributed to.
12
13
  * A self-service actor may ONLY attribute the order to its own customer
@@ -93,8 +94,12 @@ export function checkoutRoutes(kernel) {
93
94
  }, 409);
94
95
  }
95
96
  }
97
+ const orderId = body.idempotencyKey
98
+ ? await makeDeterministicId(`checkout:${resolveOrgId(actor)}:${body.idempotencyKey}`)
99
+ : makeId();
96
100
  const checkoutData = {
97
101
  checkoutId: makeId(),
102
+ orderId,
98
103
  cartId: body.cartId,
99
104
  currency: body.currency ?? "USD",
100
105
  paymentMethodId: body.paymentMethodId,
@@ -162,6 +167,7 @@ export function checkoutRoutes(kernel) {
162
167
  origin: "rest",
163
168
  database: { db: kernel.database.db },
164
169
  });
170
+ let pipelineStage = "validate-and-calculate";
165
171
  try {
166
172
  // Phase 1: DB transaction for validation — releases connection immediately after
167
173
  const validated = await kernel.database.transaction(async (_tx) => {
@@ -170,13 +176,16 @@ export function checkoutRoutes(kernel) {
170
176
  });
171
177
  // Phase 2: Payment authorization — NO DB connection held while calling Stripe/etc.
172
178
  // If Stripe takes 5s, the DB connection pool is not affected.
179
+ pipelineStage = "authorize-payment";
173
180
  context.tx = null;
174
181
  const processed = await runBeforeHooks(paymentHooks, validated, "create", context);
175
182
  // SEC-07: resolve the order's customer server-side. A self-service actor
176
183
  // can only attribute the order to its own profile; a client-supplied
177
184
  // foreign customerId is ignored unless the actor is staff.
185
+ pipelineStage = "resolve-customer";
178
186
  const customerUuid = await resolveCheckoutCustomerUuid(kernel.services.customers, actor, processed.customerId);
179
187
  const orderPayload = {
188
+ id: processed.orderId ?? orderId,
180
189
  ...(body.idempotencyKey !== undefined
181
190
  ? { idempotencyKey: body.idempotencyKey }
182
191
  : {}),
@@ -225,6 +234,7 @@ export function checkoutRoutes(kernel) {
225
234
  // Checkout is a trusted, already-server-priced pipeline (resolveCurrentPrices
226
235
  // + promotions/tax) and reserves stock in its own after-hooks — so it hands
227
236
  // the order primitive precomputed totals rather than re-deriving them.
237
+ pipelineStage = "create-order";
228
238
  const order = await kernel.services.orders.create(orderPayload, actor, undefined, {
229
239
  trustedPricing: true,
230
240
  });
@@ -232,6 +242,7 @@ export function checkoutRoutes(kernel) {
232
242
  return c.json(mapErrorToResponse(order.error), mapErrorToStatus(order.error));
233
243
  }
234
244
  if (order.ok && (processed.appliedPromotions?.length ?? 0) > 0) {
245
+ pipelineStage = "record-promotion-usage";
235
246
  await kernel.services.promotions.recordUsage({
236
247
  promotions: processed.appliedPromotions ?? [],
237
248
  organizationId: order.value.organizationId,
@@ -242,6 +253,7 @@ export function checkoutRoutes(kernel) {
242
253
  });
243
254
  }
244
255
  if (order.ok) {
256
+ pipelineStage = "report-tax-transaction";
245
257
  await kernel.services.tax.reportTransaction({
246
258
  transactionId: order.value.id,
247
259
  transactionDate: new Date(),
@@ -271,7 +283,9 @@ export function checkoutRoutes(kernel) {
271
283
  }
272
284
  // Stash paymentMethodId for completeCheckout compensation chain
273
285
  context.context.paymentMethodId = processed.paymentMethodId;
286
+ pipelineStage = "complete-checkout";
274
287
  const afterReport = await runAfterHooks(afterHooks, null, order.value, "create", context);
288
+ pipelineStage = "mark-cart-checked-out";
275
289
  await kernel.services.cart.markAsCheckedOut(body.cartId, actor);
276
290
  return c.json({
277
291
  data: {
@@ -297,7 +311,10 @@ export function checkoutRoutes(kernel) {
297
311
  });
298
312
  });
299
313
  // Always log the real error — hidden errors in checkout are unacceptable
300
- const realMessage = error instanceof Error ? error.message : String(error);
314
+ const rawMessage = error instanceof Error ? error.message : String(error);
315
+ const realMessage = rawMessage === "[object ErrorEvent]"
316
+ ? `Checkout stage "${pipelineStage}" failed: ${rawMessage}`
317
+ : rawMessage;
301
318
  const realStack = error instanceof Error ? error.stack : undefined;
302
319
  console.error("[checkout] Pipeline failed:", { message: realMessage, stack: realStack, code: error?.code });
303
320
  const message = isCommerceError(error)
@@ -1 +1 @@
1
- {"version":3,"file":"payments.d.ts","sourceRoot":"","sources":["../../../../src/interfaces/rest/routes/payments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,KAAK,MAAM,EAAwC,MAAM,aAAa,CAAC;AAKhF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,gCAmD3C"}
1
+ {"version":3,"file":"payments.d.ts","sourceRoot":"","sources":["../../../../src/interfaces/rest/routes/payments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,KAAK,MAAM,EAAwC,MAAM,aAAa,CAAC;AAOhF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,gCAqE3C"}
@@ -1,6 +1,7 @@
1
1
  import { OpenAPIHono } from "@hono/zod-openapi";
2
2
  import { mapErrorToResponse, mapErrorToStatus } from "../utils.js";
3
3
  import { processedWebhookEvents } from "../../../modules/webhooks/schema.js";
4
+ import { toCommerceError } from "../../../kernel/errors.js";
4
5
  export function paymentRoutes(kernel) {
5
6
  const router = new OpenAPIHono();
6
7
  router.post("/webhook", async (c) => {
@@ -9,37 +10,54 @@ export function paymentRoutes(kernel) {
9
10
  return c.json(mapErrorToResponse(result.error), mapErrorToStatus(result.error));
10
11
  }
11
12
  const event = result.value;
12
- const db = kernel.database.db;
13
- // Atomic idempotency: INSERT ... ON CONFLICT DO NOTHING ... RETURNING
14
- // If the row already exists, RETURNING yields zero rows → duplicate.
15
- // If the row is new, RETURNING yields one row process the event.
16
- // No TOCTOU race: the UNIQUE constraint on event_id is the single source of truth.
17
- const [inserted] = await db
18
- .insert(processedWebhookEvents)
19
- .values({
20
- eventId: event.id,
21
- provider: "stripe",
22
- eventType: event.type,
23
- })
24
- .onConflictDoNothing()
25
- .returning({ id: processedWebhookEvents.id });
26
- if (!inserted) {
27
- // Row already existed — this is a duplicate delivery
28
- return c.json({ data: { received: true, duplicate: true } });
29
- }
30
- // Process the event (first time only)
31
- if (event.type === "payment_intent.succeeded") {
32
- const data = event.data;
33
- const metadata = data?.metadata;
34
- if (typeof metadata?.orderId === "string") {
35
- await kernel.services.orders.changeStatus({
36
- orderId: metadata.orderId,
37
- newStatus: "confirmed",
38
- reason: "stripe_webhook_payment_intent_succeeded",
39
- }, null);
13
+ const outcome = await kernel.database.transaction(async (transaction) => {
14
+ const db = transaction;
15
+ // Claim and process in one transaction. If reconciliation throws, the
16
+ // claim rolls back so Stripe can safely retry instead of receiving a
17
+ // false 2xx for an event that never changed commerce state.
18
+ const [inserted] = await db
19
+ .insert(processedWebhookEvents)
20
+ .values({
21
+ eventId: event.id,
22
+ provider: "stripe",
23
+ eventType: event.type,
24
+ })
25
+ .onConflictDoNothing()
26
+ .returning({ id: processedWebhookEvents.id });
27
+ if (!inserted)
28
+ return { duplicate: true };
29
+ if (event.type === "payment_intent.succeeded") {
30
+ const data = event.data;
31
+ const metadata = data?.metadata;
32
+ if (typeof metadata?.orderId === "string") {
33
+ const actor = {
34
+ type: "api_key",
35
+ userId: "stripe-webhook",
36
+ email: null,
37
+ name: "Stripe webhook",
38
+ vendorId: null,
39
+ organizationId: typeof metadata.organizationId === "string"
40
+ ? metadata.organizationId
41
+ : null,
42
+ role: "system",
43
+ permissions: ["orders:update"],
44
+ };
45
+ const changed = await kernel.services.orders.changeStatus({
46
+ orderId: metadata.orderId,
47
+ newStatus: "confirmed",
48
+ reason: "stripe_webhook_payment_intent_succeeded",
49
+ }, actor, {
50
+ tx: transaction,
51
+ actor,
52
+ requestId: c.get("requestId"),
53
+ });
54
+ if (!changed.ok)
55
+ throw toCommerceError(changed.error);
56
+ }
40
57
  }
41
- }
42
- return c.json({ data: { received: true } });
58
+ return { duplicate: false };
59
+ });
60
+ return c.json({ data: { received: true, ...(outcome.duplicate ? { duplicate: true } : {}) } });
43
61
  });
44
62
  return router;
45
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"error-mapper.d.ts","sourceRoot":"","sources":["../../src/kernel/error-mapper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAYnD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAGrE;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IACnD,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AAaD,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,OAAO,EACf,MAAM,CAAC,EAAE,MAAM,GACd,YAAY,CA8Ed"}
1
+ {"version":3,"file":"error-mapper.d.ts","sourceRoot":"","sources":["../../src/kernel/error-mapper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAenD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAGrE;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IACnD,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AAaD,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,OAAO,EACf,MAAM,CAAC,EAAE,MAAM,GACd,YAAY,CA8Ed"}
@@ -8,6 +8,9 @@ const statusByCode = {
8
8
  CONFLICT: 409,
9
9
  INVALID_TRANSITION: 422,
10
10
  ORG_RESOLUTION_FAILED: 503,
11
+ WEBHOOK_SIGNATURE_MISSING: 400,
12
+ WEBHOOK_VERIFICATION_FAILED: 400,
13
+ WEBHOOK_SECRET_MISSING: 503,
11
14
  };
12
15
  export function mapErrorToStatus(error) {
13
16
  const normalized = toCommerceError(error);
@@ -1 +1 @@
1
- {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/kernel/hooks/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEpF,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,CAKzE;AAmBD,wBAAsB,cAAc,CAAC,CAAC,EACpC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EACtB,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,CAAC,CAAC,CAmBZ;AAED,wBAAsB,aAAa,CAAC,CAAC,EACnC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,EACrB,YAAY,EAAE,CAAC,GAAG,IAAI,EACtB,eAAe,EAAE,CAAC,EAClB,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CA0BrB"}
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../../src/kernel/hooks/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEpF,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,CAKzE;AAqCD,wBAAsB,cAAc,CAAC,CAAC,EACpC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,EACtB,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,CAAC,CAAC,CAmBZ;AAED,wBAAsB,aAAa,CAAC,CAAC,EACnC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,EACrB,YAAY,EAAE,CAAC,GAAG,IAAI,EACtB,eAAe,EAAE,CAAC,EAClB,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,CAAC,CA0BrB"}
@@ -13,6 +13,23 @@ function withTimeout(promiseOrValue, timeoutMs, hookName) {
13
13
  promise.then((val) => { clearTimeout(timer); resolve(val); }, (err) => { clearTimeout(timer); reject(err); });
14
14
  });
15
15
  }
16
+ function actionableHookError(error, hookName) {
17
+ if (error instanceof Error && error.message && error.message !== "[object ErrorEvent]") {
18
+ return error;
19
+ }
20
+ const candidate = error;
21
+ const nested = candidate?.error ?? candidate?.cause;
22
+ const nestedMessage = nested instanceof Error
23
+ ? nested.message
24
+ : nested && typeof nested === "object" && typeof nested.message === "string"
25
+ ? String(nested.message)
26
+ : undefined;
27
+ const directMessage = typeof candidate?.message === "string" && candidate.message !== "[object ErrorEvent]"
28
+ ? candidate.message
29
+ : undefined;
30
+ const message = nestedMessage || directMessage || String(error);
31
+ return new Error(`Before-hook "${hookName}" failed: ${message}`, { cause: error });
32
+ }
16
33
  export async function runBeforeHooks(hooks, data, operation, context) {
17
34
  let current = data;
18
35
  for (const hook of hooks) {
@@ -25,7 +42,7 @@ export async function runBeforeHooks(hooks, data, operation, context) {
25
42
  error: error instanceof Error ? error.message : String(error),
26
43
  requestId: context.requestId,
27
44
  });
28
- throw error; // Re-throw beforeHooks MUST succeed
45
+ throw actionableHookError(error, hookName); // Before hooks MUST succeed with actionable context.
29
46
  }
30
47
  }
31
48
  return current;
@@ -7,6 +7,8 @@ import { type TxContext } from "../../kernel/database/tx-context.js";
7
7
  import type { DatabaseAdapter } from "../../kernel/database/adapter.js";
8
8
  import { OrdersRepository, type Order, type OrderLineItem, type OrderNote, type OrderRefund, type OrderStatusHistory } from "./repository/index.js";
9
9
  export interface CreateOrderInput {
10
+ /** Server-allocated id for trusted pipelines that must reference the order before persistence. */
11
+ id?: string;
10
12
  customerId?: string;
11
13
  /** Client-supplied retry key — a repeat create with the same key returns the original order. */
12
14
  idempotencyKey?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/orders/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/orders/service.ts"],"names":[],"mappings":"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"}
@@ -154,6 +154,7 @@ export class OrderService {
154
154
  let order;
155
155
  try {
156
156
  order = await this.repo.create({
157
+ ...(opts?.trustedPricing === true && processed.id != null ? { id: processed.id } : {}),
157
158
  organizationId: orgId,
158
159
  orderNumber,
159
160
  status: "pending",
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/runtime/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAQhD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAOzD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAItE,KAAK,SAAS,GAAG;IACf,SAAS,EAAE;QACT,IAAI,EAAE,YAAY,CAAC;QACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAMvC;AAeD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,cAAc;;;;;sBAyVxB,MAAM,UAAU,MAAM;GA2FtD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/runtime/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAQhD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAOzD,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAItE,KAAK,SAAS,GAAG;IACf,SAAS,EAAE;QACT,IAAI,EAAE,YAAY,CAAC;QACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAcvC;AAeD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,cAAc;;;;;sBAyVxB,MAAM,UAAU,MAAM;GA2FtD"}
@@ -22,7 +22,15 @@ import { mapErrorToResponse } from "../kernel/error-mapper.js";
22
22
  * Exposed for direct unit testing — see test/server-edge-runtime.test.ts.
23
23
  */
24
24
  export function isNodeRuntime() {
25
+ const workerUserAgent = typeof navigator !== "undefined"
26
+ ? navigator.userAgent
27
+ : undefined;
28
+ const hasWorkersSocketPair = "WebSocketPair" in globalThis;
29
+ if (workerUserAgent === "Cloudflare-Workers" || hasWorkersSocketPair) {
30
+ return false;
31
+ }
25
32
  return (typeof process !== "undefined" &&
33
+ typeof process.versions?.node === "string" &&
26
34
  typeof process.on === "function" &&
27
35
  typeof process.exit === "function");
28
36
  }
@@ -1,2 +1,3 @@
1
1
  export declare function makeId(): string;
2
+ export declare function makeDeterministicId(value: string): Promise<string>;
2
3
  //# sourceMappingURL=id.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,IAAI,MAAM,CAE/B"}
1
+ {"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASxE"}
package/dist/utils/id.js CHANGED
@@ -1,3 +1,11 @@
1
1
  export function makeId() {
2
2
  return crypto.randomUUID();
3
3
  }
4
+ export async function makeDeterministicId(value) {
5
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)));
6
+ const bytes = digest.slice(0, 16);
7
+ bytes[6] = (bytes[6] & 0x0f) | 0x80;
8
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
9
+ const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
10
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/core",
3
- "version": "0.10.5",
3
+ "version": "0.10.8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -57,6 +57,8 @@ export interface CheckoutLineItem {
57
57
 
58
58
  export interface CheckoutData {
59
59
  checkoutId: string;
60
+ /** Server-owned order id allocated before payment authorization. */
61
+ orderId?: string;
60
62
  cartId: string;
61
63
  customerId?: string;
62
64
  customerGroupIds?: string[];
@@ -543,6 +545,8 @@ export const authorizePayment: BeforeHook<CheckoutData> = async ({
543
545
  ? { idempotencyKey: data.idempotencyKey }
544
546
  : {}),
545
547
  metadata: {
548
+ ...(data.orderId !== undefined ? { orderId: data.orderId } : {}),
549
+ organizationId: resolveOrgId(context.actor),
546
550
  checkoutId: data.checkoutId,
547
551
  cartId: data.cartId,
548
552
  },
@@ -22,7 +22,8 @@ import type { PluginDb } from "../../../kernel/database/plugin-types.js";
22
22
  import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
23
23
  import { isCommerceError } from "../../../kernel/errors.js";
24
24
  import { assertPermission } from "../../../auth/permissions.js";
25
- import { makeId } from "../../../utils/id.js";
25
+ import { resolveOrgId } from "../../../auth/org.js";
26
+ import { makeDeterministicId, makeId } from "../../../utils/id.js";
26
27
  import type { ShippingAddress } from "../../../modules/shipping/calculator.js";
27
28
  import type { Actor } from "../../../auth/types.js";
28
29
 
@@ -126,8 +127,12 @@ export function checkoutRoutes(kernel: Kernel) {
126
127
  }
127
128
  }
128
129
 
130
+ const orderId = body.idempotencyKey
131
+ ? await makeDeterministicId(`checkout:${resolveOrgId(actor)}:${body.idempotencyKey}`)
132
+ : makeId();
129
133
  const checkoutData: CheckoutData = {
130
134
  checkoutId: makeId(),
135
+ orderId,
131
136
  cartId: body.cartId,
132
137
  currency: body.currency ?? "USD",
133
138
  paymentMethodId: body.paymentMethodId,
@@ -200,6 +205,7 @@ export function checkoutRoutes(kernel: Kernel) {
200
205
  database: { db: kernel.database.db as PluginDb },
201
206
  });
202
207
 
208
+ let pipelineStage = "validate-and-calculate";
203
209
  try {
204
210
  // Phase 1: DB transaction for validation — releases connection immediately after
205
211
  const validated = await kernel.database.transaction(async (_tx) => {
@@ -214,6 +220,7 @@ export function checkoutRoutes(kernel: Kernel) {
214
220
 
215
221
  // Phase 2: Payment authorization — NO DB connection held while calling Stripe/etc.
216
222
  // If Stripe takes 5s, the DB connection pool is not affected.
223
+ pipelineStage = "authorize-payment";
217
224
  context.tx = null;
218
225
  const processed = await runBeforeHooks(
219
226
  paymentHooks,
@@ -225,6 +232,7 @@ export function checkoutRoutes(kernel: Kernel) {
225
232
  // SEC-07: resolve the order's customer server-side. A self-service actor
226
233
  // can only attribute the order to its own profile; a client-supplied
227
234
  // foreign customerId is ignored unless the actor is staff.
235
+ pipelineStage = "resolve-customer";
228
236
  const customerUuid = await resolveCheckoutCustomerUuid(
229
237
  kernel.services.customers,
230
238
  actor,
@@ -232,6 +240,7 @@ export function checkoutRoutes(kernel: Kernel) {
232
240
  );
233
241
 
234
242
  const orderPayload = {
243
+ id: processed.orderId ?? orderId,
235
244
  ...(body.idempotencyKey !== undefined
236
245
  ? { idempotencyKey: body.idempotencyKey }
237
246
  : {}),
@@ -281,6 +290,7 @@ export function checkoutRoutes(kernel: Kernel) {
281
290
  // Checkout is a trusted, already-server-priced pipeline (resolveCurrentPrices
282
291
  // + promotions/tax) and reserves stock in its own after-hooks — so it hands
283
292
  // the order primitive precomputed totals rather than re-deriving them.
293
+ pipelineStage = "create-order";
284
294
  const order = await kernel.services.orders.create(orderPayload, actor, undefined, {
285
295
  trustedPricing: true,
286
296
  });
@@ -293,6 +303,7 @@ export function checkoutRoutes(kernel: Kernel) {
293
303
  }
294
304
 
295
305
  if (order.ok && (processed.appliedPromotions?.length ?? 0) > 0) {
306
+ pipelineStage = "record-promotion-usage";
296
307
  await kernel.services.promotions.recordUsage({
297
308
  promotions: processed.appliedPromotions ?? [],
298
309
  organizationId: order.value.organizationId,
@@ -304,6 +315,7 @@ export function checkoutRoutes(kernel: Kernel) {
304
315
  }
305
316
 
306
317
  if (order.ok) {
318
+ pipelineStage = "report-tax-transaction";
307
319
  await kernel.services.tax.reportTransaction({
308
320
  transactionId: order.value.id,
309
321
  transactionDate: new Date(),
@@ -336,6 +348,7 @@ export function checkoutRoutes(kernel: Kernel) {
336
348
  // Stash paymentMethodId for completeCheckout compensation chain
337
349
  context.context.paymentMethodId = processed.paymentMethodId;
338
350
 
351
+ pipelineStage = "complete-checkout";
339
352
  const afterReport = await runAfterHooks(
340
353
  afterHooks,
341
354
  null,
@@ -344,6 +357,7 @@ export function checkoutRoutes(kernel: Kernel) {
344
357
  context,
345
358
  );
346
359
 
360
+ pipelineStage = "mark-cart-checked-out";
347
361
  await kernel.services.cart.markAsCheckedOut(body.cartId, actor);
348
362
 
349
363
  return c.json(
@@ -374,7 +388,10 @@ export function checkoutRoutes(kernel: Kernel) {
374
388
  },
375
389
  );
376
390
  // Always log the real error — hidden errors in checkout are unacceptable
377
- const realMessage = error instanceof Error ? error.message : String(error);
391
+ const rawMessage = error instanceof Error ? error.message : String(error);
392
+ const realMessage = rawMessage === "[object ErrorEvent]"
393
+ ? `Checkout stage "${pipelineStage}" failed: ${rawMessage}`
394
+ : rawMessage;
378
395
  const realStack = error instanceof Error ? error.stack : undefined;
379
396
  console.error("[checkout] Pipeline failed:", { message: realMessage, stack: realStack, code: (error as Record<string, unknown>)?.code });
380
397
 
@@ -3,6 +3,8 @@ import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core";
3
3
  import type { Kernel } from "../../../runtime/kernel.js";
4
4
  import { type AppEnv, mapErrorToResponse, mapErrorToStatus } from "../utils.js";
5
5
  import { processedWebhookEvents } from "../../../modules/webhooks/schema.js";
6
+ import type { Actor } from "../../../auth/types.js";
7
+ import { toCommerceError } from "../../../kernel/errors.js";
6
8
 
7
9
  type Db = PgDatabase<PgQueryResultHKT, Record<string, unknown>>;
8
10
 
@@ -16,44 +18,62 @@ export function paymentRoutes(kernel: Kernel) {
16
18
  }
17
19
 
18
20
  const event = result.value;
19
- const db = kernel.database.db as Db;
20
-
21
- // Atomic idempotency: INSERT ... ON CONFLICT DO NOTHING ... RETURNING
22
- // If the row already exists, RETURNING yields zero rows → duplicate.
23
- // If the row is new, RETURNING yields one row process the event.
24
- // No TOCTOU race: the UNIQUE constraint on event_id is the single source of truth.
25
- const [inserted] = await db
26
- .insert(processedWebhookEvents)
27
- .values({
28
- eventId: event.id,
29
- provider: "stripe",
30
- eventType: event.type,
31
- })
32
- .onConflictDoNothing()
33
- .returning({ id: processedWebhookEvents.id });
34
-
35
- if (!inserted) {
36
- // Row already existed — this is a duplicate delivery
37
- return c.json({ data: { received: true, duplicate: true } });
38
- }
21
+ const outcome = await kernel.database.transaction(async (transaction) => {
22
+ const db = transaction as Db;
23
+
24
+ // Claim and process in one transaction. If reconciliation throws, the
25
+ // claim rolls back so Stripe can safely retry instead of receiving a
26
+ // false 2xx for an event that never changed commerce state.
27
+ const [inserted] = await db
28
+ .insert(processedWebhookEvents)
29
+ .values({
30
+ eventId: event.id,
31
+ provider: "stripe",
32
+ eventType: event.type,
33
+ })
34
+ .onConflictDoNothing()
35
+ .returning({ id: processedWebhookEvents.id });
36
+
37
+ if (!inserted) return { duplicate: true };
39
38
 
40
- // Process the event (first time only)
41
- if (event.type === "payment_intent.succeeded") {
42
- const data = event.data as Record<string, unknown> | undefined;
43
- const metadata = data?.metadata as Record<string, unknown> | undefined;
44
- if (typeof metadata?.orderId === "string") {
45
- await kernel.services.orders.changeStatus(
46
- {
47
- orderId: metadata.orderId,
48
- newStatus: "confirmed",
49
- reason: "stripe_webhook_payment_intent_succeeded",
50
- },
51
- null,
52
- );
39
+ if (event.type === "payment_intent.succeeded") {
40
+ const data = event.data as Record<string, unknown> | undefined;
41
+ const metadata = data?.metadata as Record<string, unknown> | undefined;
42
+ if (typeof metadata?.orderId === "string") {
43
+ const actor: Actor = {
44
+ type: "api_key",
45
+ userId: "stripe-webhook",
46
+ email: null,
47
+ name: "Stripe webhook",
48
+ vendorId: null,
49
+ organizationId:
50
+ typeof metadata.organizationId === "string"
51
+ ? metadata.organizationId
52
+ : null,
53
+ role: "system",
54
+ permissions: ["orders:update"],
55
+ };
56
+ const changed = await kernel.services.orders.changeStatus(
57
+ {
58
+ orderId: metadata.orderId,
59
+ newStatus: "confirmed",
60
+ reason: "stripe_webhook_payment_intent_succeeded",
61
+ },
62
+ actor,
63
+ {
64
+ tx: transaction,
65
+ actor,
66
+ requestId: c.get("requestId"),
67
+ },
68
+ );
69
+ if (!changed.ok) throw toCommerceError(changed.error);
70
+ }
53
71
  }
54
- }
55
72
 
56
- return c.json({ data: { received: true } });
73
+ return { duplicate: false };
74
+ });
75
+
76
+ return c.json({ data: { received: true, ...(outcome.duplicate ? { duplicate: true } : {}) } });
57
77
  });
58
78
 
59
79
  return router;
@@ -11,6 +11,9 @@ const statusByCode: Record<string, ContentfulStatusCode> = {
11
11
  CONFLICT: 409,
12
12
  INVALID_TRANSITION: 422,
13
13
  ORG_RESOLUTION_FAILED: 503,
14
+ WEBHOOK_SIGNATURE_MISSING: 400,
15
+ WEBHOOK_VERIFICATION_FAILED: 400,
16
+ WEBHOOK_SECRET_MISSING: 503,
14
17
  };
15
18
 
16
19
  export function mapErrorToStatus(error: unknown): ContentfulStatusCode {
@@ -34,6 +34,24 @@ function withTimeout<T>(promiseOrValue: Promise<T> | T, timeoutMs: number, hookN
34
34
  });
35
35
  }
36
36
 
37
+ function actionableHookError(error: unknown, hookName: string): Error {
38
+ if (error instanceof Error && error.message && error.message !== "[object ErrorEvent]") {
39
+ return error;
40
+ }
41
+ const candidate = error as { error?: unknown; cause?: unknown; message?: unknown } | null;
42
+ const nested = candidate?.error ?? candidate?.cause;
43
+ const nestedMessage = nested instanceof Error
44
+ ? nested.message
45
+ : nested && typeof nested === "object" && typeof (nested as { message?: unknown }).message === "string"
46
+ ? String((nested as { message: string }).message)
47
+ : undefined;
48
+ const directMessage = typeof candidate?.message === "string" && candidate.message !== "[object ErrorEvent]"
49
+ ? candidate.message
50
+ : undefined;
51
+ const message = nestedMessage || directMessage || String(error);
52
+ return new Error(`Before-hook "${hookName}" failed: ${message}`, { cause: error });
53
+ }
54
+
37
55
  export async function runBeforeHooks<T>(
38
56
  hooks: BeforeHook<T>[],
39
57
  data: T,
@@ -54,7 +72,7 @@ export async function runBeforeHooks<T>(
54
72
  error: error instanceof Error ? error.message : String(error),
55
73
  requestId: context.requestId,
56
74
  });
57
- throw error; // Re-throw beforeHooks MUST succeed
75
+ throw actionableHookError(error, hookName); // Before hooks MUST succeed with actionable context.
58
76
  }
59
77
  }
60
78
  return current;
@@ -42,6 +42,8 @@ import {
42
42
  } from "./repository/index.js";
43
43
 
44
44
  export interface CreateOrderInput {
45
+ /** Server-allocated id for trusted pipelines that must reference the order before persistence. */
46
+ id?: string;
45
47
  customerId?: string;
46
48
  /** Client-supplied retry key — a repeat create with the same key returns the original order. */
47
49
  idempotencyKey?: string | undefined;
@@ -379,6 +381,7 @@ export class OrderService {
379
381
  try {
380
382
  order = await this.repo.create(
381
383
  {
384
+ ...(opts?.trustedPricing === true && processed.id != null ? { id: processed.id } : {}),
382
385
  organizationId: orgId,
383
386
  orderNumber,
384
387
  status: "pending",
@@ -39,8 +39,16 @@ type ServerEnv = {
39
39
  * Exposed for direct unit testing — see test/server-edge-runtime.test.ts.
40
40
  */
41
41
  export function isNodeRuntime(): boolean {
42
+ const workerUserAgent = typeof navigator !== "undefined"
43
+ ? navigator.userAgent
44
+ : undefined;
45
+ const hasWorkersSocketPair = "WebSocketPair" in globalThis;
46
+ if (workerUserAgent === "Cloudflare-Workers" || hasWorkersSocketPair) {
47
+ return false;
48
+ }
42
49
  return (
43
50
  typeof process !== "undefined" &&
51
+ typeof process.versions?.node === "string" &&
44
52
  typeof process.on === "function" &&
45
53
  typeof process.exit === "function"
46
54
  );
package/src/utils/id.ts CHANGED
@@ -1,3 +1,14 @@
1
1
  export function makeId(): string {
2
2
  return crypto.randomUUID();
3
3
  }
4
+
5
+ export async function makeDeterministicId(value: string): Promise<string> {
6
+ const digest = new Uint8Array(
7
+ await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)),
8
+ );
9
+ const bytes = digest.slice(0, 16);
10
+ bytes[6] = (bytes[6]! & 0x0f) | 0x80;
11
+ bytes[8] = (bytes[8]! & 0x3f) | 0x80;
12
+ const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
13
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
14
+ }