@mundogamernetwork/shared-ui 1.1.3 → 1.1.7

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.
@@ -1,85 +1,111 @@
1
- import { ref, reactive, provide, inject, computed } from "vue";
2
- import type { AxiosInstance } from "axios";
1
+ import { ref, computed, watch, nextTick, provide, inject } from "vue";
2
+ import { storeToRefs } from "pinia";
3
3
  import { usePaymentMethods } from "./usePaymentMethods";
4
- import type { PaymentMethod } from "./usePaymentMethods";
4
+ import type { AxiosInstance } from "axios";
5
+
6
+ // ─── Types ────────────────────────────────────────────────────────────────────
5
7
 
6
8
  export interface CartItem {
7
9
  id: number;
8
10
  item_id: number;
9
11
  item_type: string;
10
12
  quantity: number;
11
- price: number;
12
- price_with_discount: number;
13
- discount: number;
14
- currency_id: number;
15
- item: {
13
+ price: number | string;
14
+ price_with_discount?: number | string;
15
+ discount?: number;
16
+ currency_id?: number;
17
+ item?: {
16
18
  id: number;
17
- name: string;
19
+ name?: string;
20
+ localized_name?: string;
18
21
  description?: string;
19
22
  slug?: string;
20
23
  image_url?: string;
24
+ products?: any[];
21
25
  };
22
26
  }
23
27
 
24
28
  export interface CartData {
25
- items: CartItem[];
26
- original_total: number;
27
- total_with_discount: number;
29
+ items?: { data?: CartItem[] } | CartItem[];
30
+ original_total?: number;
31
+ total_with_discount?: number;
28
32
  total_discount?: number;
29
33
  formatted_original_total?: string;
30
34
  formatted_total_with_discount?: string;
35
+ formatted_total?: string;
31
36
  formatted_total_discount?: string;
32
- discount_coupon?: any;
37
+ discount_coupon?: { code: string; discount?: number };
33
38
  discount_coupon_code?: string;
39
+ currency?: { data?: { code?: string; symbol?: string } };
40
+ user_email?: string;
41
+ [key: string]: any;
34
42
  }
35
43
 
36
- export interface CheckoutResponse {
37
- order: { id: string; status: string };
38
- payer_action: string;
39
- payment_type: "redirect" | "pix" | "free";
40
- pix_data?: {
41
- qr_code: string;
42
- qr_code_base64: string;
43
- expires_at: string;
44
- };
44
+ export interface PlanSelection {
45
+ id: number;
46
+ name?: string;
47
+ slug?: string;
48
+ [key: string]: any;
49
+ }
50
+
51
+ export interface PlanPricingSelection {
52
+ id: number;
53
+ value?: number;
54
+ payment_period_id?: number;
55
+ [key: string]: any;
45
56
  }
46
57
 
47
58
  const CHECKOUT_KEY = "mg-checkout-instance";
48
59
 
49
- export function useMgCheckout(httpService: AxiosInstance) {
50
- // Payment methods (dynamic from backend)
60
+ export function provideMgCheckout(instance: ReturnType<typeof useMgCheckout>) {
61
+ provide(CHECKOUT_KEY, instance);
62
+ }
63
+
64
+ export function injectMgCheckout(): ReturnType<typeof useMgCheckout> | undefined {
65
+ return inject<ReturnType<typeof useMgCheckout>>(CHECKOUT_KEY);
66
+ }
67
+
68
+ // ─── Composable ───────────────────────────────────────────────────────────────
69
+
70
+ export function useMgCheckout(httpService: AxiosInstance, options?: {
71
+ buyerPageName?: string;
72
+ successPath?: string; // e.g. "/confirmation"
73
+ cancelPath?: string; // e.g. "/pricing"
74
+ }) {
75
+ const nuxtApp = useNuxtApp();
76
+ const { locale } = useI18n();
77
+ const router = useRouter();
78
+ const authStore = useAuthStore();
79
+ const { signedIn } = storeToRefs(authStore);
51
80
  const paymentMethods = usePaymentMethods(httpService);
52
81
 
53
- // Cart state
54
- const cart = ref<CartData | null>(null);
82
+ const buyerPageName = options?.buyerPageName ?? "";
83
+ const successPath = options?.successPath ?? "/confirmation";
84
+ const cancelPath = options?.cancelPath ?? "/pricing";
85
+
86
+ // ── Cart ──────────────────────────────────────────────────────────────────
87
+
88
+ const cart = ref<CartData>({});
55
89
  const cartLoading = ref(false);
56
90
  const cartError = ref<string | null>(null);
57
91
 
58
- // Checkout state
59
- const checkoutLoading = ref(false);
60
- const checkoutResponse = ref<CheckoutResponse | null>(null);
61
- const checkoutError = ref<string | null>(null);
92
+ const getCartItems = computed((): CartItem[] => {
93
+ const items = cart.value?.items;
94
+ if (!items) return [];
95
+ if (Array.isArray(items)) return items;
96
+ return items.data ?? [];
97
+ });
62
98
 
63
- // Guest state
64
- const guestEmail = ref("");
65
- const emailError = ref("");
99
+ const hasItems = computed(() => getCartItems.value.length > 0 || !!selectedPlan.value);
100
+ const hasDiscount = computed(() => !!cart.value?.total_discount && cart.value.total_discount !== 0);
66
101
 
67
- // Terms
68
- const termsAccepted = ref(false);
69
-
70
- // Coupon
71
- const couponCode = ref("");
72
- const couponLoading = ref(false);
73
- const couponError = ref<string | null>(null);
74
- const couponInfo = ref<any>(null);
75
-
76
- // Cart operations
77
102
  async function fetchCart() {
78
103
  cartLoading.value = true;
79
104
  cartError.value = null;
80
105
  try {
81
- const res = await httpService.get("/shopping-cart");
82
- cart.value = res.data?.data ?? null;
106
+ const res = await httpService.get("api/v1/shopping-cart");
107
+ cart.value = res.data?.data ?? {};
108
+ _syncCartState(cart.value);
83
109
  } catch (e: any) {
84
110
  cartError.value = e?.message ?? "Failed to load cart";
85
111
  } finally {
@@ -87,58 +113,85 @@ export function useMgCheckout(httpService: AxiosInstance) {
87
113
  }
88
114
  }
89
115
 
116
+ function _syncCartState(data: CartData) {
117
+ const items = Array.isArray(data.items) ? data.items : (data.items?.data ?? []);
118
+ orderCart.value = [...items].sort((a: any, b: any) => {
119
+ if (a.item_type < b.item_type) return -1;
120
+ if (a.item_type > b.item_type) return 1;
121
+ return (a.item_id ?? 0) - (b.item_id ?? 0);
122
+ });
123
+ orderCartDiscount.value = data.formatted_original_total ?? "";
124
+ orderCartTotal.value = data.formatted_total_with_discount ?? data.formatted_total ?? "";
125
+ hasDiscountFlag.value = (data.total_discount ?? 0) !== 0;
126
+ if (data.discount_coupon) couponInfo.value = data.discount_coupon;
127
+ }
128
+
129
+ // Raw ordered cart for display
130
+ const orderCart = ref<CartItem[]>([]);
131
+ const orderCartTotal = ref<string | number>(0);
132
+ const orderCartDiscount = ref<string | number>(0);
133
+ const hasDiscountFlag = ref(false);
134
+
90
135
  async function addItem(itemId: number, itemType: string, quantity = 1) {
136
+ if (cartLoading.value) return;
137
+ // Clear plan if adding product (different flows)
138
+ if (selectedPlan.value) { selectedPlan.value = null; selectedPlanPricing.value = null; }
91
139
  cartLoading.value = true;
92
140
  try {
93
- const res = await httpService.post("/shopping-cart/add", {
94
- item_id: itemId,
95
- item_type: itemType,
96
- quantity,
97
- });
98
- cart.value = res.data?.data ?? cart.value;
141
+ await httpService.post("api/v1/shopping-cart/add", { item_id: itemId, item_type: itemType, quantity });
142
+ await fetchCart();
99
143
  } finally {
100
144
  cartLoading.value = false;
101
145
  }
102
146
  }
103
147
 
104
148
  async function removeItem(itemId: number, itemType: string, quantity = 1) {
149
+ if (cartLoading.value) return;
105
150
  cartLoading.value = true;
106
151
  try {
107
- const res = await httpService.post("/shopping-cart/remove", {
108
- item_id: itemId,
109
- item_type: itemType,
110
- quantity,
111
- });
112
- cart.value = res.data?.data ?? cart.value;
152
+ await httpService.post("api/v1/shopping-cart/remove", { item_id: itemId, item_type: itemType, quantity });
153
+ await fetchCart();
113
154
  } finally {
114
155
  cartLoading.value = false;
115
156
  }
116
157
  }
117
158
 
118
- async function clearCart() {
159
+ async function clearCartItems() {
160
+ if (cartLoading.value) return;
119
161
  cartLoading.value = true;
120
162
  try {
121
- await httpService.post("/shopping-cart/clear");
122
- cart.value = null;
163
+ await httpService.post("api/v1/shopping-cart/clear");
164
+ cart.value = {};
165
+ orderCart.value = [];
166
+ orderCartTotal.value = 0;
167
+ orderCartDiscount.value = 0;
168
+ hasDiscountFlag.value = false;
123
169
  } finally {
124
170
  cartLoading.value = false;
125
171
  }
126
172
  }
127
173
 
128
- // Coupon
129
- async function applyCoupon(code: string) {
174
+ // ── Coupon ────────────────────────────────────────────────────────────────
175
+
176
+ const couponCode = ref("");
177
+ const couponLoading = ref(false);
178
+ const couponError = ref<string | null>(null);
179
+ const couponInfo = ref<any>(null);
180
+
181
+ async function applyCoupon(code?: string) {
182
+ const codeToApply = (code ?? couponCode.value).toUpperCase().trim();
183
+ if (!codeToApply) return;
130
184
  couponLoading.value = true;
131
185
  couponError.value = null;
132
186
  try {
133
- const res = await httpService.post("/shopping-cart/apply-coupon", {
134
- code: code.toUpperCase(),
135
- });
187
+ const res = await httpService.post("api/v1/shopping-cart/apply-coupon", { code: codeToApply });
136
188
  const data = res.data?.data ?? res.data;
137
189
  if (res.data?.warnings?.length) {
138
190
  couponError.value = res.data.warnings[0]?.message ?? "Invalid coupon";
139
191
  } else {
140
192
  cart.value = data;
141
- couponInfo.value = data?.discount_coupon ?? null;
193
+ _syncCartState(data);
194
+ couponInfo.value = data?.discount_coupon ?? { code: codeToApply };
142
195
  }
143
196
  } catch (e: any) {
144
197
  couponError.value = e?.response?.data?.message ?? "Invalid coupon code";
@@ -150,8 +203,10 @@ export function useMgCheckout(httpService: AxiosInstance) {
150
203
  async function removeCoupon() {
151
204
  couponLoading.value = true;
152
205
  try {
153
- const res = await httpService.post("/shopping-cart/remove-coupon");
154
- cart.value = res.data?.data ?? cart.value;
206
+ const res = await httpService.post("api/v1/shopping-cart/remove-coupon");
207
+ const data = res.data?.data ?? res.data;
208
+ cart.value = data;
209
+ _syncCartState(data);
155
210
  couponInfo.value = null;
156
211
  couponCode.value = "";
157
212
  } finally {
@@ -159,110 +214,279 @@ export function useMgCheckout(httpService: AxiosInstance) {
159
214
  }
160
215
  }
161
216
 
162
- // Checkout
163
- async function checkout(successUrl: string, cancelUrl: string) {
164
- if (!termsAccepted.value) {
165
- checkoutError.value = "You must accept the terms of use";
166
- return null;
217
+ // ── Plan / Subscription ────────────────────────────────────────────────────
218
+
219
+ const selectedPlan = ref<PlanSelection | null>(null);
220
+ const selectedPlanPricing = ref<PlanPricingSelection | null>(null);
221
+ const hasPlanInCart = computed(() => !!selectedPlan.value);
222
+
223
+ const isSubscriber = computed(() => {
224
+ const sub = (authStore as any).user?.subscription ?? (authStore as any).subscription;
225
+ return sub?.is_subscriber === true || sub?.active === true;
226
+ });
227
+
228
+ async function selectPlan(plan: PlanSelection, pricing: PlanPricingSelection) {
229
+ selectedPlan.value = plan;
230
+ selectedPlanPricing.value = pricing;
231
+ // Clear product cart when selecting a plan
232
+ if (orderCart.value.length > 0) {
233
+ try {
234
+ await httpService.post("api/v1/shopping-cart/clear");
235
+ orderCart.value = [];
236
+ orderCartTotal.value = 0;
237
+ orderCartDiscount.value = 0;
238
+ hasDiscountFlag.value = false;
239
+ } catch { /* silent */ }
167
240
  }
241
+ }
168
242
 
169
- // Guest email validation
170
- if (guestEmail.value && !isValidEmail(guestEmail.value)) {
171
- emailError.value = "Please enter a valid email";
172
- return null;
173
- }
243
+ function removePlan() {
244
+ selectedPlan.value = null;
245
+ selectedPlanPricing.value = null;
246
+ }
247
+
248
+ // ── Payment / Gateway ──────────────────────────────────────────────────────
249
+
250
+ const selectedPaymentGateway = ref<string>("");
251
+ const checkoutError = ref<string | null>(null);
252
+ const loading = ref(false);
174
253
 
175
- checkoutLoading.value = true;
254
+ function selectPaymentType(value: string) {
255
+ selectedPaymentGateway.value = value;
176
256
  checkoutError.value = null;
257
+ }
177
258
 
178
- try {
179
- const params: Record<string, string> = {
180
- payment_method: paymentMethods.selectedMethod.value,
181
- success_url: successUrl,
182
- cancel_url: cancelUrl,
183
- };
184
-
185
- if (guestEmail.value) {
186
- params.email = guestEmail.value;
187
- }
259
+ // ── Terms & Confirm Modal ──────────────────────────────────────────────────
260
+
261
+ const termUseAccepted = ref(false);
262
+ const sendError = ref(false);
263
+ const openConfirmModal = ref(false);
264
+
265
+ function changeTerm(value: boolean) {
266
+ termUseAccepted.value = value;
267
+ sendError.value = false;
268
+ }
269
+
270
+ // ── Guest Checkout ─────────────────────────────────────────────────────────
271
+
272
+ const offlineEmail = ref("");
273
+ const emailError = ref("");
274
+ const isInvalid = ref(false);
275
+
276
+ function isValidEmail(email: string): boolean {
277
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
278
+ }
188
279
 
189
- const res = await httpService.post("/shopping-cart/checkout", params);
190
- const data = res.data;
191
-
192
- checkoutResponse.value = {
193
- order: data.order,
194
- payer_action: data.payer_action,
195
- payment_type: data.payment_type ?? "redirect",
196
- pix_data: data.pix_data ?? undefined,
197
- };
198
-
199
- // Free order (100%-off coupon): the backend already marked the invoice
200
- // paid and returns payer_action: null (NO gateway). Send the user to the
201
- // confirmation/success URL with the invoice id appended, so the
202
- // confirmation page can read it — never redirect to a null payer_action.
203
- if (checkoutResponse.value.payment_type === "free") {
204
- const invId =
205
- (data.order?.invoice_id ?? data.order?.reference ?? "") as string;
206
- if (successUrl) {
207
- const sep = successUrl.includes("?") ? "&" : "?";
208
- window.location.href = invId
209
- ? `${successUrl}${sep}invoice_id=${encodeURIComponent(invId)}`
210
- : successUrl;
211
- }
212
- } else if (
213
- checkoutResponse.value.payment_type === "redirect" &&
214
- checkoutResponse.value.payer_action
215
- ) {
216
- // Stripe / PayPal — redirect to the gateway.
217
- window.location.href = checkoutResponse.value.payer_action;
280
+ // ── handlePurchase: gate check before opening modal ───────────────────────
281
+
282
+ function handlePurchase() {
283
+ if (!signedIn.value) {
284
+ if (!offlineEmail.value || !isValidEmail(offlineEmail.value)) {
285
+ emailError.value = _t("wallet.offline_order.error", "Please enter a valid email.");
286
+ isInvalid.value = true;
287
+ nextTick(() => {
288
+ document.getElementById("offlineEmail")?.scrollIntoView({ behavior: "smooth", block: "center" });
289
+ });
290
+ return;
218
291
  }
292
+ }
293
+ if (!selectedPaymentGateway.value) {
294
+ checkoutError.value = _t("checkout.select_payment_method", "Please select a payment method.");
295
+ return;
296
+ }
297
+ if (!termUseAccepted.value) {
298
+ sendError.value = true;
299
+ return;
300
+ }
301
+ openConfirmModal.value = true;
302
+ }
219
303
 
220
- // For Pix, the component will show the QR code
221
- return checkoutResponse.value;
222
- } catch (e: any) {
223
- checkoutError.value =
224
- e?.response?.data?.message ?? "Checkout failed. Please try again.";
225
- return null;
304
+ function _t(key: string, fallback: string): string {
305
+ try { return (nuxtApp.$i18n as any).t(key) || fallback; } catch { return fallback; }
306
+ }
307
+
308
+ // ── startValidation: the actual checkout submission ────────────────────────
309
+
310
+ async function startValidation() {
311
+ if (loading.value) return;
312
+ try {
313
+ loading.value = true;
314
+ if (buyerPageName) localStorage.setItem("buyerPage", buyerPageName);
315
+
316
+ const gatewayStr = selectedPaymentGateway.value || "stripe";
317
+ const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
318
+ const localeStr = locale.value || "en";
319
+
320
+ if (hasPlanInCart.value && selectedPlanPricing.value) {
321
+ // ── Subscription / plan flow ──────────────────────────────────────
322
+ await _doSubscriptionCheckout(gatewayStr, baseUrl, localeStr, false);
323
+
324
+ } else {
325
+ // ── Product / cart flow ────────────────────────────────────────────
326
+ await _doCartCheckout(gatewayStr, baseUrl, localeStr);
327
+ }
328
+ } catch (error: any) {
329
+ openConfirmModal.value = false;
330
+ await _handleCheckoutError(error, gatewayStr());
226
331
  } finally {
227
- checkoutLoading.value = false;
332
+ loading.value = false;
228
333
  }
229
334
  }
230
335
 
231
- // Computed
232
- const hasItems = computed(
233
- () => (cart.value?.items?.length ?? 0) > 0
234
- );
336
+ // helpers captured for error handler closure
337
+ let _lastGateway = "";
338
+ function gatewayStr() { return _lastGateway || selectedPaymentGateway.value || "stripe"; }
339
+
340
+ async function _doSubscriptionCheckout(gateway: string, baseUrl: string, loc: string, isUpgrade: boolean) {
341
+ _lastGateway = gateway;
342
+ const endpoint = isUpgrade
343
+ ? "api/v1/plans/subscriptions/upgrade"
344
+ : "api/v1/plans/subscriptions";
345
+
346
+ const payload: Record<string, any> = {
347
+ mg_network_plan_pricing_id: selectedPlanPricing.value!.id,
348
+ success_url: `${baseUrl}/${loc}${successPath}`,
349
+ cancel_url: `${baseUrl}/${loc}${cancelPath}`,
350
+ payment_gateway: gateway,
351
+ };
352
+ if (couponInfo.value?.code) payload.discount_coupon_code = couponInfo.value.code;
353
+ if (!signedIn.value && offlineEmail.value) payload.email = offlineEmail.value;
354
+
355
+ const res = await httpService.post(endpoint, payload);
356
+ const meta = res.data?.meta ?? res.data ?? {};
357
+ const payerAction = meta.payer_action ?? meta.checkout_url ?? null;
358
+ const invoiceId = meta.invoice_id ?? meta.id;
359
+
360
+ if (invoiceId) {
361
+ localStorage.setItem("invoiceUuid", String(invoiceId));
362
+ }
363
+ localStorage.setItem("paymentGateway", gateway);
364
+ localStorage.removeItem("checkoutType");
365
+ localStorage.removeItem("subscriptionId");
366
+
367
+ openConfirmModal.value = false;
368
+ removePlan();
369
+
370
+ if (payerAction) {
371
+ window.location.href = payerAction;
372
+ } else {
373
+ // Free / immediate — go straight to confirmation
374
+ router.push(`/${loc}${successPath}${invoiceId ? `?invoice_id=${invoiceId}` : ""}`);
375
+ }
376
+ }
235
377
 
236
- const hasDiscount = computed(
237
- () =>
238
- cart.value != null &&
239
- cart.value.original_total !== cart.value.total_with_discount
240
- );
378
+ async function _doCartCheckout(gateway: string, baseUrl: string, loc: string) {
379
+ _lastGateway = gateway;
380
+ localStorage.setItem("paymentGateway", gateway);
241
381
 
242
- const cartTotal = computed(() => cart.value?.total_with_discount ?? 0);
243
- const cartOriginalTotal = computed(() => cart.value?.original_total ?? 0);
382
+ const payload: Record<string, any> = {
383
+ payment_method: gateway,
384
+ success_url: `${baseUrl}/${loc}${successPath}`,
385
+ cancel_url: `${baseUrl}/${loc}${cancelPath}`,
386
+ };
387
+ if (!signedIn.value && offlineEmail.value) payload.email = offlineEmail.value;
244
388
 
245
- // Helpers
246
- function isValidEmail(email: string): boolean {
247
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
389
+ const res = await httpService.post("api/v1/shopping-cart/checkout", payload);
390
+ const data = res.data ?? {};
391
+ const payerAction = data.payer_action ?? data.checkout_url ?? null;
392
+ const invoiceId = data.order?.invoice_id ?? data.invoice_id;
393
+
394
+ if (invoiceId) localStorage.setItem("invoiceUuid", String(invoiceId));
395
+
396
+ openConfirmModal.value = false;
397
+
398
+ if (!payerAction) {
399
+ // Free order — confirmation directly
400
+ router.push(`/${loc}${successPath}${invoiceId ? `?invoice_id=${invoiceId}` : ""}`);
401
+ } else {
402
+ window.location.href = payerAction;
403
+ }
248
404
  }
249
405
 
250
- const instance = {
251
- // Payment methods
252
- paymentMethods,
406
+ async function _handleCheckoutError(error: any, currentGateway: string) {
407
+ openConfirmModal.value = false;
408
+ const status = error?.response?.status ?? 0;
409
+ const msg: string = error?.response?.data?.message ?? "";
410
+ const code: string = error?.response?.data?.code ?? "";
411
+
412
+ // 409 — already has subscription → auto-retry as upgrade
413
+ const isAlreadySubscribed =
414
+ status === 409 &&
415
+ (code === "ALREADY_SUBSCRIBED" ||
416
+ msg.toLowerCase().includes("already have an active") ||
417
+ msg.toLowerCase().includes("already subscribed"));
418
+
419
+ if (isAlreadySubscribed && !isSubscriber.value && hasPlanInCart.value && selectedPlanPricing.value) {
420
+ try {
421
+ const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
422
+ await _doSubscriptionCheckout(currentGateway, baseUrl, locale.value || "en", true);
423
+ return;
424
+ } catch (upgradeErr: any) {
425
+ checkoutError.value =
426
+ upgradeErr?.response?.data?.message ??
427
+ _t("pricing.error_upgrade_failed", "Unable to upgrade plan. Please contact support.");
428
+ return;
429
+ }
430
+ }
253
431
 
432
+ const isGatewayError =
433
+ msg.includes("GatewayPlan") ||
434
+ msg.includes("No payment credential") ||
435
+ status === 404;
436
+
437
+ // Auto-fallback to other gateway
438
+ if (isGatewayError && hasPlanInCart.value) {
439
+ const fallback = currentGateway === "paypal" ? "stripe" : "paypal";
440
+ const fallbackLabel = fallback === "paypal" ? "PayPal" : "Stripe";
441
+ checkoutError.value = _t("pricing.error_gateway_fallback", `Trying ${fallbackLabel}...`).replace("{gateway}", fallbackLabel);
442
+ selectedPaymentGateway.value = fallback;
443
+ setTimeout(() => {
444
+ checkoutError.value = null;
445
+ handlePurchase();
446
+ }, 2000);
447
+ return;
448
+ }
449
+
450
+ if (isGatewayError) {
451
+ checkoutError.value = _t("pricing.error_gateway_not_available", "Payment method unavailable. Please try another.");
452
+ } else if (msg.includes(".")) {
453
+ const translated = _t(`errors.${msg}`, msg);
454
+ checkoutError.value = translated;
455
+ } else if (msg) {
456
+ checkoutError.value = msg;
457
+ } else {
458
+ checkoutError.value = _t("checkout.generic_error", "Checkout failed. Please try again.");
459
+ }
460
+ }
461
+
462
+ // ── Watchers ───────────────────────────────────────────────────────────────
463
+
464
+ watch(offlineEmail, (val) => {
465
+ if (val && isValidEmail(val)) { emailError.value = ""; isInvalid.value = false; }
466
+ });
467
+
468
+ watch(sendError, (v) => {
469
+ if (v) openConfirmModal.value = false;
470
+ });
471
+
472
+ // ── Return ─────────────────────────────────────────────────────────────────
473
+
474
+ return {
254
475
  // Cart
255
476
  cart,
256
477
  cartLoading,
257
478
  cartError,
479
+ orderCart,
480
+ orderCartTotal,
481
+ orderCartDiscount,
482
+ hasDiscountFlag,
483
+ hasItems,
484
+ hasDiscount,
258
485
  fetchCart,
259
486
  addItem,
260
487
  removeItem,
261
- clearCart,
262
- hasItems,
263
- hasDiscount,
264
- cartTotal,
265
- cartOriginalTotal,
488
+ clearCartItems,
489
+ getCartItems,
266
490
 
267
491
  // Coupon
268
492
  couponCode,
@@ -272,29 +496,37 @@ export function useMgCheckout(httpService: AxiosInstance) {
272
496
  applyCoupon,
273
497
  removeCoupon,
274
498
 
275
- // Guest
276
- guestEmail,
277
- emailError,
499
+ // Plan
500
+ selectedPlan,
501
+ selectedPlanPricing,
502
+ hasPlanInCart,
503
+ isSubscriber,
504
+ selectPlan,
505
+ removePlan,
278
506
 
279
- // Terms
280
- termsAccepted,
281
-
282
- // Checkout
283
- checkout,
284
- checkoutLoading,
285
- checkoutResponse,
507
+ // Payment
508
+ paymentMethods,
509
+ selectedPaymentGateway,
286
510
  checkoutError,
287
- };
511
+ loading,
512
+ selectPaymentType,
288
513
 
289
- return instance;
290
- }
514
+ // Terms & modal
515
+ termUseAccepted,
516
+ sendError,
517
+ openConfirmModal,
518
+ changeTerm,
291
519
 
292
- export type MgCheckoutInstance = ReturnType<typeof useMgCheckout>;
520
+ // Guest
521
+ signedIn,
522
+ offlineEmail,
523
+ emailError,
524
+ isInvalid,
293
525
 
294
- export function provideMgCheckout(instance: MgCheckoutInstance) {
295
- provide(CHECKOUT_KEY, instance);
526
+ // Actions
527
+ handlePurchase,
528
+ startValidation,
529
+ };
296
530
  }
297
531
 
298
- export function injectMgCheckout(): MgCheckoutInstance | undefined {
299
- return inject<MgCheckoutInstance>(CHECKOUT_KEY);
300
- }
532
+ export type MgCheckoutInstance = ReturnType<typeof useMgCheckout>;