@jarwizz/create-jarshop 0.1.2 → 0.1.4

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.
Files changed (31) hide show
  1. package/README.md +2 -2
  2. package/dist/generator.js +4 -1
  3. package/dist/generator.js.map +1 -1
  4. package/dist/template/AGENTS.md +3 -3
  5. package/dist/template/CONTEXT.md +4 -0
  6. package/dist/template/README.md +5 -0
  7. package/dist/template/package.json +6 -1
  8. package/dist/template/pnpm-lock.yaml +2339 -79
  9. package/dist/template/pnpm-workspace.yaml +12 -1
  10. package/dist/template/src/plugins/jarshop-checkout/cod-payment.test.ts +44 -0
  11. package/dist/template/src/plugins/jarshop-checkout/cod-payment.ts +28 -0
  12. package/dist/template/src/plugins/jarshop-checkout/confirmation.ts +50 -4
  13. package/dist/template/src/plugins/jarshop-checkout/index.ts +5 -1
  14. package/dist/template/src/plugins/jarshop-checkout/jarshop-checkout.plugin.ts +95 -31
  15. package/dist/template/src/plugins/jarshop-checkout/order-email-delivery-core.ts +1 -1
  16. package/dist/template/src/seed.ts +41 -19
  17. package/dist/template/src/vendure-config.ts +5 -3
  18. package/dist/template/storefront/app/[locale]/checkout/confirmation/route.ts +21 -0
  19. package/dist/template/storefront/app/[locale]/checkout/success/page.tsx +6 -1
  20. package/dist/template/storefront/app/[locale]/page.tsx +2 -2
  21. package/dist/template/storefront/eslint.config.mjs +8 -1
  22. package/dist/template/storefront/lib/checkout.ts +14 -8
  23. package/dist/template/storefront/lib/commerce.ts +7 -3
  24. package/dist/template/storefront/lib/confirmation.ts +66 -18
  25. package/dist/template/storefront/package.json +7 -3
  26. package/dist/template/storefront/vitest.config.ts +8 -0
  27. package/dist/template/turbo.json +7 -0
  28. package/dist/template-manifest.json +1 -1
  29. package/package.json +1 -1
  30. package/dist/template/storefront/pnpm-lock.yaml +0 -4316
  31. package/dist/template/storefront/pnpm-workspace.yaml +0 -26
@@ -1,4 +1,15 @@
1
- packages: []
1
+ packages:
2
+ - .
3
+ - storefront
4
+
5
+ # The eslint-config-next range otherwise resolves a newer unreviewed resolver,
6
+ # which pnpm rejects as a trust downgrade. Keep the reviewed compatible
7
+ # resolver exact for the whole Client Project workspace.
8
+ overrides:
9
+ eslint-import-resolver-typescript: 3.8.7
10
+
11
+ minimumReleaseAgeExclude:
12
+ - "@jarwizz/commerce-sdk@0.3.0"
2
13
 
3
14
  allowBuilds:
4
15
  bcrypt: true
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type {
3
+ Order,
4
+ Payment,
5
+ PaymentMethod,
6
+ RequestContext,
7
+ } from "@vendure/core";
8
+ import { jarShopCodPaymentHandler } from "./cod-payment.js";
9
+
10
+ const context = {} as RequestContext;
11
+ const order = { code: "ORDER-123" } as Order;
12
+ const method = {} as PaymentMethod;
13
+
14
+ describe("jarShopCodPaymentHandler", () => {
15
+ it("authorizes COD without pretending that money was collected", async () => {
16
+ const result = await jarShopCodPaymentHandler.createPayment(
17
+ context,
18
+ order,
19
+ 12_345,
20
+ [],
21
+ { attemptId: "attempt-1" },
22
+ method,
23
+ );
24
+
25
+ expect(result).toMatchObject({
26
+ amount: 12_345,
27
+ state: "Authorized",
28
+ transactionId: "cod:ORDER-123",
29
+ method: "jarshop-cod-handler",
30
+ });
31
+ });
32
+
33
+ it("allows an operator-driven settlement after collection", async () => {
34
+ const result = await jarShopCodPaymentHandler.settlePayment(
35
+ context,
36
+ order,
37
+ {} as Payment,
38
+ [],
39
+ method,
40
+ );
41
+
42
+ expect(result).toEqual({ success: true });
43
+ });
44
+ });
@@ -0,0 +1,28 @@
1
+ import { LanguageCode, PaymentMethodHandler } from "@vendure/core";
2
+
3
+ /**
4
+ * Cash on delivery is a two-step payment: checkout authorizes collection, and
5
+ * an authenticated operator settles it only after money is actually received.
6
+ */
7
+ export const jarShopCodPaymentHandler = new PaymentMethodHandler({
8
+ code: "jarshop-cod-handler",
9
+ description: [
10
+ {
11
+ languageCode: LanguageCode.sk,
12
+ value: "Dobierka s manuálnym potvrdením prijatia platby",
13
+ },
14
+ {
15
+ languageCode: LanguageCode.en,
16
+ value: "Cash on delivery with manual payment collection",
17
+ },
18
+ ],
19
+ args: {},
20
+ createPayment: async (_ctx, order, amount, _args, metadata) => ({
21
+ amount,
22
+ state: "Authorized" as const,
23
+ transactionId: `cod:${order.code}`,
24
+ metadata,
25
+ }),
26
+ settlePayment: async () => ({ success: true }),
27
+ cancelPayment: async () => ({ success: true }),
28
+ });
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
2
 
3
3
  export const CONFIRMATION_TTL_MS = 2 * 60 * 60 * 1000;
4
+ export const CONFIRMATION_COOKIE_NAME = "jarshop-confirmation";
4
5
 
5
6
  export function hashConfirmationToken(token: string): string {
6
7
  return createHash("sha256").update(token).digest("hex");
@@ -15,12 +16,57 @@ export function createConfirmationToken(now = Date.now()) {
15
16
  };
16
17
  }
17
18
 
18
- export function canUseConfirmationToken(input: {
19
+ /**
20
+ * Independent one-time credentials for the browser hand-off and e-mail link.
21
+ *
22
+ * Checkout exchanges the browser token immediately. Reusing it in the outbox
23
+ * would make the e-mail link a replay before the customer receives it.
24
+ */
25
+ export function createConfirmationTokenPair(now = Date.now()) {
26
+ return {
27
+ browser: createConfirmationToken(now),
28
+ email: createConfirmationToken(now),
29
+ };
30
+ }
31
+
32
+ export function canExchangeConfirmationToken(input: {
33
+ expiresAt: Date;
34
+ consumedAt: Date | null;
35
+ now?: number;
36
+ }): boolean {
37
+ return (
38
+ input.consumedAt === null &&
39
+ input.expiresAt.getTime() > (input.now ?? Date.now())
40
+ );
41
+ }
42
+
43
+ export function canReadConfirmationCredential(input: {
19
44
  expiresAt: Date;
20
45
  consumedAt: Date | null;
21
- cookieMatches: boolean;
22
46
  now?: number;
23
47
  }): boolean {
24
- if (input.expiresAt.getTime() <= (input.now ?? Date.now())) return false;
25
- return input.consumedAt === null || input.cookieMatches;
48
+ return (
49
+ input.consumedAt !== null &&
50
+ input.expiresAt.getTime() > (input.now ?? Date.now())
51
+ );
52
+ }
53
+
54
+ export function readCookieValue(
55
+ cookieHeader: string,
56
+ cookieName: string,
57
+ ): string | undefined {
58
+ for (const part of cookieHeader.split(";")) {
59
+ const separator = part.indexOf("=");
60
+ if (separator < 0 || part.slice(0, separator).trim() !== cookieName) {
61
+ continue;
62
+ }
63
+ const value = part.slice(separator + 1).trim();
64
+ if (!value) return undefined;
65
+ try {
66
+ return decodeURIComponent(value);
67
+ } catch {
68
+ return undefined;
69
+ }
70
+ }
71
+ return undefined;
26
72
  }
@@ -1,11 +1,15 @@
1
1
  export { CheckoutAttempt } from "./checkout-attempt.entity.js";
2
2
  export type { CheckoutAttemptStatus } from "./checkout-attempt.entity.js";
3
+ export { jarShopCodPaymentHandler } from "./cod-payment.js";
3
4
  export { JarShopConfirmationToken } from "./confirmation-token.entity.js";
4
5
  export {
5
6
  CONFIRMATION_TTL_MS,
6
- canUseConfirmationToken,
7
+ CONFIRMATION_COOKIE_NAME,
8
+ canExchangeConfirmationToken,
9
+ canReadConfirmationCredential,
7
10
  createConfirmationToken,
8
11
  hashConfirmationToken,
12
+ readCookieValue,
9
13
  } from "./confirmation.js";
10
14
  export { JarShopLegalConfiguration } from "./legal.entity.js";
11
15
  export { JarShopOrderEmailDelivery } from "./order-email-delivery.entity.js";
@@ -4,7 +4,7 @@ import { Inject, Injectable } from "@nestjs/common";
4
4
  import { Allow, Permission } from "@vendure/core";
5
5
  import { createHash } from "node:crypto";
6
6
  import { parse } from "graphql";
7
- import type { Repository } from "typeorm";
7
+ import { IsNull, MoreThan, type Repository } from "typeorm";
8
8
  import {
9
9
  ActiveOrderService,
10
10
  Ctx,
@@ -23,9 +23,13 @@ import {
23
23
  import { CheckoutAttempt } from "./checkout-attempt.entity.js";
24
24
  import { JarShopConfirmationToken } from "./confirmation-token.entity.js";
25
25
  import {
26
- canUseConfirmationToken,
26
+ CONFIRMATION_COOKIE_NAME,
27
+ canExchangeConfirmationToken,
28
+ canReadConfirmationCredential,
27
29
  createConfirmationToken,
30
+ createConfirmationTokenPair,
28
31
  hashConfirmationToken,
32
+ readCookieValue,
29
33
  } from "./confirmation.js";
30
34
  import { JarShopLegalConfiguration } from "./legal.entity.js";
31
35
  import { JarShopOrderEmailDelivery } from "./order-email-delivery.entity.js";
@@ -111,6 +115,10 @@ const schema = parse(`
111
115
  totalWithTax: Int!
112
116
  currencyCode: String!
113
117
  }
118
+ type JarShopOrderConfirmationExchange {
119
+ credential: String!
120
+ confirmation: JarShopOrderConfirmation!
121
+ }
114
122
  input JarShopLegalConfigurationInput {
115
123
  languageCode: String!
116
124
  termsVersion: String!
@@ -120,10 +128,11 @@ const schema = parse(`
120
128
  }
121
129
  extend type Query {
122
130
  jarShopLegalConfiguration(languageCode: String!): JarShopLegalConfiguration
123
- jarShopOrderConfirmation(token: String!): JarShopOrderConfirmation
131
+ jarShopProtectedOrderConfirmation(orderCode: String!): JarShopOrderConfirmation
124
132
  }
125
133
  extend type Mutation {
126
134
  placeJarShopOrder(input: JarShopPlaceOrderInput!): JarShopPlaceOrderResult!
135
+ exchangeJarShopOrderConfirmation(token: String!): JarShopOrderConfirmationExchange
127
136
  updateJarShopLegalConfiguration(input: JarShopLegalConfigurationInput!): JarShopLegalConfiguration!
128
137
  }
129
138
  `);
@@ -222,8 +231,8 @@ class JarShopCheckoutResolver {
222
231
  return configuration ? toLegalConfiguration(configuration) : null;
223
232
  }
224
233
 
225
- @Query()
226
- async jarShopOrderConfirmation(
234
+ @Mutation()
235
+ async exchangeJarShopOrderConfirmation(
227
236
  @Ctx() ctx: RequestContext,
228
237
  @Args("token") token: string,
229
238
  ) {
@@ -234,31 +243,70 @@ class JarShopCheckoutResolver {
234
243
  const record = await repository.findOne({
235
244
  where: { tokenHash: hashConfirmationToken(token) },
236
245
  });
237
- if (!record || record.expiresAt.getTime() <= Date.now()) return null;
238
- const cookie = ctx.req?.headers.cookie ?? "";
239
- const exchanged = cookie.includes(`jarshop-confirmation=${token}`);
246
+ const now = new Date();
240
247
  if (
241
- !canUseConfirmationToken({
248
+ !record ||
249
+ !canExchangeConfirmationToken({
242
250
  expiresAt: record.expiresAt,
243
251
  consumedAt: record.consumedAt,
244
- cookieMatches: exchanged,
252
+ now: now.getTime(),
245
253
  })
246
254
  ) {
247
255
  return null;
248
256
  }
249
- if (!record.consumedAt) {
250
- record.consumedAt = new Date();
251
- await repository.save(record);
257
+ const order = await this.orderService.findOne(ctx, record.orderId);
258
+ if (!order) return null;
259
+
260
+ const credential = createConfirmationToken(now.getTime());
261
+ const update = await repository.update(
262
+ {
263
+ id: record.id,
264
+ tokenHash: record.tokenHash,
265
+ consumedAt: IsNull(),
266
+ expiresAt: MoreThan(now),
267
+ },
268
+ {
269
+ tokenHash: credential.tokenHash,
270
+ consumedAt: now,
271
+ },
272
+ );
273
+ if (update.affected !== 1) return null;
274
+
275
+ return {
276
+ credential: credential.token,
277
+ confirmation: toOrderConfirmation(order),
278
+ };
279
+ }
280
+
281
+ @Query()
282
+ async jarShopProtectedOrderConfirmation(
283
+ @Ctx() ctx: RequestContext,
284
+ @Args("orderCode") orderCode: string,
285
+ ) {
286
+ const credential = readCookieValue(
287
+ ctx.req?.headers.cookie ?? "",
288
+ CONFIRMATION_COOKIE_NAME,
289
+ );
290
+ if (!credential) return null;
291
+ const repository = this.connection.getRepository(
292
+ ctx,
293
+ JarShopConfirmationToken,
294
+ );
295
+ const record = await repository.findOne({
296
+ where: { tokenHash: hashConfirmationToken(credential) },
297
+ });
298
+ if (
299
+ !record ||
300
+ !canReadConfirmationCredential({
301
+ expiresAt: record.expiresAt,
302
+ consumedAt: record.consumedAt,
303
+ })
304
+ ) {
305
+ return null;
252
306
  }
253
307
  const order = await this.orderService.findOne(ctx, record.orderId);
254
- return order
255
- ? {
256
- orderId: order.id,
257
- orderCode: order.code,
258
- state: order.state,
259
- totalWithTax: order.totalWithTax,
260
- currencyCode: order.currencyCode,
261
- }
308
+ return order && order.code === orderCode
309
+ ? toOrderConfirmation(order)
262
310
  : null;
263
311
  }
264
312
 
@@ -536,8 +584,8 @@ class JarShopCheckoutResolver {
536
584
  ctx,
537
585
  activeOrder.id,
538
586
  {
539
- method: "dummy-payment-method",
540
- metadata: { attemptId: input.attemptId, method: "cod" },
587
+ method: "cod",
588
+ metadata: { attemptId: input.attemptId },
541
589
  },
542
590
  );
543
591
  if (isGraphQlErrorResult(paymentResult)) {
@@ -566,19 +614,25 @@ class JarShopCheckoutResolver {
566
614
  acceptedAt: new Date(),
567
615
  }),
568
616
  );
569
- const confirmation = createConfirmationToken();
617
+ const confirmations = createConfirmationTokenPair();
570
618
  const tokenRepository = this.connection.getRepository(
571
619
  ctx,
572
620
  JarShopConfirmationToken,
573
621
  );
574
- await tokenRepository.save(
622
+ await tokenRepository.save([
575
623
  tokenRepository.create({
576
- tokenHash: confirmation.tokenHash,
624
+ tokenHash: confirmations.browser.tokenHash,
577
625
  orderId: Number(paymentResult.id),
578
- expiresAt: confirmation.expiresAt,
626
+ expiresAt: confirmations.browser.expiresAt,
579
627
  consumedAt: null,
580
628
  }),
581
- );
629
+ tokenRepository.create({
630
+ tokenHash: confirmations.email.tokenHash,
631
+ orderId: Number(paymentResult.id),
632
+ expiresAt: confirmations.email.expiresAt,
633
+ consumedAt: null,
634
+ }),
635
+ ]);
582
636
  const emailDeliveryRepository = this.connection.getRepository(
583
637
  ctx,
584
638
  JarShopOrderEmailDelivery,
@@ -589,7 +643,7 @@ class JarShopCheckoutResolver {
589
643
  orderId: Number(paymentResult.id),
590
644
  checkoutAttemptId: attempt.attemptId,
591
645
  languageCode: input.languageCode,
592
- confirmationToken: confirmation.token,
646
+ confirmationToken: confirmations.email.token,
593
647
  }),
594
648
  ),
595
649
  );
@@ -603,13 +657,13 @@ class JarShopCheckoutResolver {
603
657
  placedOrder,
604
658
  activeOrder.totalWithTax,
605
659
  activeOrder.currencyCode,
606
- confirmation.token,
660
+ confirmations.browser.token,
607
661
  )
608
662
  : success(
609
663
  paymentResult,
610
664
  activeOrder.totalWithTax,
611
665
  activeOrder.currencyCode,
612
- confirmation.token,
666
+ confirmations.browser.token,
613
667
  );
614
668
  }
615
669
 
@@ -783,6 +837,16 @@ function toLegalConfiguration(configuration: JarShopLegalConfiguration) {
783
837
  };
784
838
  }
785
839
 
840
+ function toOrderConfirmation(order: Order) {
841
+ return {
842
+ orderId: order.id,
843
+ orderCode: order.code,
844
+ state: order.state,
845
+ totalWithTax: order.totalWithTax,
846
+ currencyCode: order.currencyCode,
847
+ };
848
+ }
849
+
786
850
  /**
787
851
  * JarShop's server-owned checkout seam.
788
852
  *
@@ -275,7 +275,7 @@ export function createOrderConfirmationUrl(
275
275
  const storefrontUrl =
276
276
  process.env.JARSHOP_STOREFRONT_URL ?? defaultStorefrontUrl;
277
277
  return new URL(
278
- `/${languageCode}/checkout/success?token=${encodeURIComponent(token)}`,
278
+ `/${languageCode}/checkout/confirmation?token=${encodeURIComponent(token)}`,
279
279
  storefrontUrl,
280
280
  ).toString();
281
281
  }
@@ -156,26 +156,48 @@ async function seed(): Promise<void> {
156
156
  channel.id,
157
157
  ]);
158
158
 
159
- const paymentMethods =
160
- await paymentMethodService.getActivePaymentMethods(ctx);
161
- const cod =
162
- paymentMethods.find((method) => method.code === "dummy-payment-method") ??
163
- (await paymentMethodService.create(ctx, {
164
- code: "dummy-payment-method",
165
- enabled: true,
166
- handler: {
167
- code: "dummy-payment-handler",
168
- arguments: [{ name: "automaticSettle", value: "false" }],
169
- },
170
- translations: [
171
- { languageCode: LanguageCode.sk, name: "Dobierka", description: "" },
172
- {
173
- languageCode: LanguageCode.en,
174
- name: "Cash on delivery",
175
- description: "",
159
+ const paymentMethods = await paymentMethodService.findAll(ctx);
160
+ const legacyDummy = paymentMethods.items.find(
161
+ (method) => method.code === "dummy-payment-method",
162
+ );
163
+ if (legacyDummy?.enabled) {
164
+ await paymentMethodService.update(ctx, {
165
+ id: legacyDummy.id,
166
+ enabled: false,
167
+ });
168
+ }
169
+ const existingCod = paymentMethods.items.find(
170
+ (method) => method.code === "cod",
171
+ );
172
+ const cod = existingCod
173
+ ? await paymentMethodService.update(ctx, {
174
+ id: existingCod.id,
175
+ enabled: true,
176
+ handler: {
177
+ code: "jarshop-cod-handler",
178
+ arguments: [],
176
179
  },
177
- ],
178
- }));
180
+ })
181
+ : await paymentMethodService.create(ctx, {
182
+ code: "cod",
183
+ enabled: true,
184
+ handler: {
185
+ code: "jarshop-cod-handler",
186
+ arguments: [],
187
+ },
188
+ translations: [
189
+ {
190
+ languageCode: LanguageCode.sk,
191
+ name: "Dobierka",
192
+ description: "",
193
+ },
194
+ {
195
+ languageCode: LanguageCode.en,
196
+ name: "Cash on delivery",
197
+ description: "",
198
+ },
199
+ ],
200
+ });
179
201
  await channelService.assignToChannels(ctx, PaymentMethod, cod.id, [
180
202
  channel.id,
181
203
  ]);
@@ -3,7 +3,6 @@ import {
3
3
  DefaultSchedulerPlugin,
4
4
  DefaultSearchPlugin,
5
5
  VendureConfig,
6
- dummyPaymentHandler,
7
6
  } from "@vendure/core";
8
7
  import { AssetServerPlugin } from "@vendure/asset-server-plugin";
9
8
  import { DashboardPlugin } from "@vendure/dashboard/plugin";
@@ -13,7 +12,10 @@ import {
13
12
  defaultEmailHandlers,
14
13
  } from "@vendure/email-plugin";
15
14
  import { GraphiqlPlugin } from "@vendure/graphiql-plugin";
16
- import { JarShopCheckoutPlugin } from "./plugins/jarshop-checkout/index.js";
15
+ import {
16
+ JarShopCheckoutPlugin,
17
+ jarShopCodPaymentHandler,
18
+ } from "./plugins/jarshop-checkout/index.js";
17
19
  import "dotenv/config";
18
20
  import path from "node:path";
19
21
 
@@ -76,7 +78,7 @@ export const config: VendureConfig = {
76
78
  username: process.env.DB_USERNAME,
77
79
  password: process.env.DB_PASSWORD,
78
80
  },
79
- paymentOptions: { paymentMethodHandlers: [dummyPaymentHandler] },
81
+ paymentOptions: { paymentMethodHandlers: [jarShopCodPaymentHandler] },
80
82
  plugins: [
81
83
  JarShopCheckoutPlugin,
82
84
  GraphiqlPlugin.init(),
@@ -0,0 +1,21 @@
1
+ import { NextResponse, type NextRequest } from "next/server";
2
+
3
+ import { exchangeOrderConfirmation } from "../../../../lib/confirmation";
4
+ import { parseLocale } from "../../../../lib/i18n";
5
+
6
+ export async function GET(
7
+ request: NextRequest,
8
+ context: { params: Promise<{ locale: string }> },
9
+ ) {
10
+ const { locale } = await context.params;
11
+ const language = parseLocale(locale);
12
+ const token = request.nextUrl.searchParams.get("token");
13
+ const confirmation = token
14
+ ? await exchangeOrderConfirmation(language, token)
15
+ : null;
16
+ const target = new URL(`/${language}/checkout/success`, request.url);
17
+ if (confirmation) {
18
+ target.searchParams.set("order", confirmation.orderCode);
19
+ }
20
+ return NextResponse.redirect(target);
21
+ }
@@ -3,15 +3,20 @@ import { getOrderConfirmation } from "../../../../lib/confirmation";
3
3
 
4
4
  interface CheckoutSuccessProps {
5
5
  params: Promise<{ locale: string }>;
6
+ searchParams: Promise<{ order?: string }>;
6
7
  }
7
8
 
8
9
  export default async function CheckoutSuccess({
9
10
  params,
11
+ searchParams,
10
12
  }: CheckoutSuccessProps) {
11
13
  const { locale } = await params;
14
+ const { order } = await searchParams;
12
15
  const language = parseLocale(locale);
13
16
  const t = createTranslator(language);
14
- const confirmation = await getOrderConfirmation(language);
17
+ const confirmation = order
18
+ ? await getOrderConfirmation(language, order)
19
+ : null;
15
20
  return (
16
21
  <main lang={language}>
17
22
  <h1>{t("success.title")}</h1>
@@ -17,11 +17,11 @@ export default async function LocalePage({ params }: LocalePageProps) {
17
17
  <p className="eyebrow">JarShop starter</p>
18
18
  <h1>{t("catalog.title")}</h1>
19
19
  </div>
20
- {!result.ok || result.data.length === 0 ? (
20
+ {!result.ok || result.data.items.length === 0 ? (
21
21
  <p>{t("catalog.empty")}</p>
22
22
  ) : (
23
23
  <ul className="product-grid">
24
- {result.data.map((product) => (
24
+ {result.data.items.map((product) => (
25
25
  <li key={product.id}>
26
26
  <a href={`/${language}/${product.slug}`}>{product.name}</a>
27
27
  <p>{product.description}</p>
@@ -5,5 +5,12 @@ import nextTypeScript from "eslint-config-next/typescript";
5
5
  export default defineConfig([
6
6
  ...nextVitals,
7
7
  ...nextTypeScript,
8
- globalIgnores([".next/**", "dist/**", "out/**", "build/**", "next-env.d.ts"]),
8
+ globalIgnores([
9
+ ".next/**",
10
+ "dist/**",
11
+ "out/**",
12
+ "build/**",
13
+ "src/gql/**",
14
+ "next-env.d.ts",
15
+ ]),
9
16
  ]);
@@ -8,11 +8,8 @@ import {
8
8
  commerceSessionAuthorization,
9
9
  persistCommerceSession,
10
10
  } from "./commerce-session";
11
- import {
12
- confirmationCookieOptions,
13
- CONFIRMATION_COOKIE_NAME,
14
- } from "./confirmation-cookie";
15
11
  import { PLACE_JARSHOP_ORDER_MUTATION } from "./checkout-contract";
12
+ import { exchangeOrderConfirmation } from "./confirmation";
16
13
 
17
14
  export interface CheckoutCartSnapshot {
18
15
  currencyCode: string;
@@ -133,11 +130,20 @@ export async function placeOrder(
133
130
  affectedLineIds: result?.affectedLineIds ?? [],
134
131
  };
135
132
  }
136
- store.set(
137
- CONFIRMATION_COOKIE_NAME,
133
+ const confirmation = await exchangeOrderConfirmation(
134
+ locale,
138
135
  result.confirmationToken,
139
- confirmationCookieOptions,
140
136
  );
141
- redirect(`/${locale}/checkout/success`);
137
+ if (!confirmation || confirmation.orderCode !== result.orderCode) {
138
+ return {
139
+ ok: false,
140
+ errorCode: "ORDER_NOT_FOUND",
141
+ message: "Order confirmation is not available",
142
+ affectedLineIds: [],
143
+ };
144
+ }
145
+ redirect(
146
+ `/${locale}/checkout/success?order=${encodeURIComponent(result.orderCode)}`,
147
+ );
142
148
  return { ok: true };
143
149
  }
@@ -6,9 +6,12 @@ import type {
6
6
  AccountAddressInput,
7
7
  AccountProfile,
8
8
  AccountOrdersOptions,
9
+ CatalogPage,
9
10
  ChangePasswordInput,
10
11
  CommerceResult,
11
12
  LoginInput,
13
+ ProductDetail,
14
+ ProductListOptions,
12
15
  ProductSummary,
13
16
  RegisterAccountInput,
14
17
  ResetPasswordInput,
@@ -37,14 +40,15 @@ function client(locale: CommerceLocale, sessionStore?: SessionStore) {
37
40
 
38
41
  export function getCatalog(
39
42
  locale: CommerceLocale,
40
- ): Promise<CommerceResult<ProductSummary[]>> {
41
- return client(locale).listProducts();
43
+ options?: ProductListOptions,
44
+ ): Promise<CommerceResult<CatalogPage<ProductSummary>>> {
45
+ return client(locale).listProducts(options);
42
46
  }
43
47
 
44
48
  export function getProduct(
45
49
  slug: string,
46
50
  locale: CommerceLocale,
47
- ): Promise<CommerceResult<ProductSummary | null>> {
51
+ ): Promise<CommerceResult<ProductDetail | null>> {
48
52
  return client(locale).getProductBySlug(slug);
49
53
  }
50
54