@mohasinac/appkit 4.63.4 → 4.63.5
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/.build-stamp.json +3 -3
- package/dist/_internal/server/features/checkout/actions.d.ts +2 -0
- package/dist/_internal/server/features/checkout/actions.js +22 -5
- package/dist/_internal/server/features/orders/adapters.js +3 -1
- package/dist/_internal/server/features/site-settings/adapters.js +20 -2
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +1 -0
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +5 -2
- package/dist/_internal/server/jobs/core/jobRunners.js +5 -0
- package/dist/_internal/server/jobs/core/onOrderCreate.js +104 -10
- package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
- package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
- package/dist/_internal/server/jobs/core/onScamReportCreate.js +1 -0
- package/dist/_internal/server/jobs/core/onScamReportRejected.js +1 -0
- package/dist/_internal/server/jobs/core/onScamReportVerified.js +1 -0
- package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
- package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
- package/dist/_internal/server/jobs/core/whatsappNotify.js +140 -0
- package/dist/_internal/server/notifications/channel-health.d.ts +8 -6
- package/dist/_internal/server/notifications/channel-health.js +2 -2
- package/dist/_internal/server/notifications/send-guard.d.ts +1 -1
- package/dist/_internal/server/notifications/send-guard.js +4 -2
- package/dist/_internal/shared/actions/action-registry.js +28 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +1 -1
- package/dist/_internal/shared/checkout/order-math.js +1 -1
- package/dist/_internal/shared/features/checkout/config.d.ts +2 -0
- package/dist/_internal/shared/features/checkout/config.js +2 -0
- package/dist/_internal/shared/features/messaging/config.d.ts +5 -7
- package/dist/_internal/shared/features/messaging/config.js +4 -1
- package/dist/_internal/shared/features/site-settings/fees.d.ts +2 -0
- package/dist/_internal/shared/features/site-settings/fees.js +2 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +27 -2
- package/dist/_internal/shared/fees/calculator.d.ts +8 -0
- package/dist/_internal/shared/fees/calculator.js +7 -0
- package/dist/cli/index.js +2 -0
- package/dist/client.d.ts +2 -2
- package/dist/client.js +1 -1
- package/dist/constants/api-endpoints.d.ts +15 -0
- package/dist/constants/api-endpoints.js +8 -0
- package/dist/constants/notification-types.d.ts +1 -1
- package/dist/constants/notification-types.js +1 -1
- package/dist/core/integration-keys.d.ts +5 -0
- package/dist/core/integration-keys.js +10 -0
- package/dist/features/about/components/FeesView.js +6 -0
- package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
- package/dist/features/account/components/NotificationPreferencesPanel.js +4 -3
- package/dist/features/account/types/index.d.ts +2 -0
- package/dist/features/admin/actions/notification-actions.d.ts +17 -2
- package/dist/features/admin/actions/notification-actions.js +154 -9
- package/dist/features/admin/components/AdminOrderEditorView.d.ts +1 -1
- package/dist/features/admin/components/AdminOrdersView.js +2 -0
- package/dist/features/admin/components/AdminSiteSettingsView.js +66 -6
- package/dist/features/admin/components/AdminStoreEditorView.js +1 -0
- package/dist/features/admin/constants/site-settings-tabs.d.ts +12 -7
- package/dist/features/admin/constants/site-settings-tabs.js +12 -6
- package/dist/features/admin/repository/site-settings.repository.js +1 -1
- package/dist/features/admin/schemas/admin-user-form.d.ts +61 -0
- package/dist/features/admin/schemas/admin-user-form.js +44 -3
- package/dist/features/admin/schemas/firestore.d.ts +73 -0
- package/dist/features/admin/schemas/firestore.js +8 -3
- package/dist/features/auth/checkout-value-otp.d.ts +1 -1
- package/dist/features/auth/checkout-value-otp.js +1 -1
- package/dist/features/auth/permissions/constants.d.ts +2 -2
- package/dist/features/auth/permissions/constants.js +2 -1
- package/dist/features/cart/components/CartPriceBreakdown.d.ts +2 -0
- package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
- package/dist/features/cart/components/StoreAddonsPicker.d.ts +5 -2
- package/dist/features/cart/components/StoreAddonsPicker.js +5 -4
- package/dist/features/cart/repository/cart.repository.js +2 -1
- package/dist/features/cart/schemas/firestore.d.ts +2 -1
- package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +5 -10
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +88 -2
- package/dist/features/homepage/lib/section-renderer.js +1 -1
- package/dist/features/orders/actions/refund-actions.js +1 -0
- package/dist/features/orders/components/OrderAddonBadges.d.ts +3 -1
- package/dist/features/orders/components/OrderAddonBadges.js +4 -3
- package/dist/features/orders/repository/orders.repository.d.ts +8 -0
- package/dist/features/orders/repository/orders.repository.js +2 -0
- package/dist/features/orders/schemas/firestore.d.ts +4 -0
- package/dist/features/orders/types/index.d.ts +5 -2
- package/dist/features/search/action-index/settings-entries.js +9 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +6 -1
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +10 -1
- package/dist/features/store-extensions/schemas/firestore.d.ts +17 -1
- package/dist/features/store-extensions/schemas/firestore.js +10 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +1 -0
- package/dist/features/stores/components/StoreGuideHubView.js +1 -1
- package/dist/features/stores/components/StoreSettingsGuideView.js +6 -2
- package/dist/features/stores/repository/store.repository.d.ts +23 -1
- package/dist/features/stores/repository/store.repository.js +29 -1
- package/dist/features/stores/schemas/firestore.d.ts +16 -6
- package/dist/features/stores/schemas/firestore.js +3 -6
- package/dist/features/stores/schemas/index.d.ts +86 -0
- package/dist/features/stores/schemas/index.js +14 -0
- package/dist/features/whatsapp-bot/api/send-status/route.d.ts +12 -0
- package/dist/features/whatsapp-bot/api/send-status/route.js +38 -0
- package/dist/features/whatsapp-bot/api/webhook/route.d.ts +12 -0
- package/dist/features/whatsapp-bot/api/webhook/route.js +43 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +57 -0
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +76 -0
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +9 -0
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +7 -0
- package/dist/features/whatsapp-bot/components/index.d.ts +3 -0
- package/dist/features/whatsapp-bot/components/index.js +2 -0
- package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +52 -0
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +288 -0
- package/dist/features/whatsapp-bot/index.d.ts +3 -0
- package/dist/features/whatsapp-bot/index.js +3 -0
- package/dist/features/whatsapp-bot/manifest.d.ts +2 -0
- package/dist/features/whatsapp-bot/manifest.js +13 -0
- package/dist/features/whatsapp-bot/messages/en.json +7 -0
- package/dist/features/whatsapp-bot/server.d.ts +7 -0
- package/dist/features/whatsapp-bot/server.js +7 -0
- package/dist/features/whatsapp-bot/types/index.d.ts +126 -0
- package/dist/features/whatsapp-bot/types/index.js +1 -0
- package/dist/index.d.ts +23 -6
- package/dist/index.js +38 -12
- package/dist/next/routing/route-map.d.ts +3 -0
- package/dist/next/routing/route-map.js +1 -0
- package/dist/providers/db-firebase/base.repository.d.ts +1 -1
- package/dist/providers/db-firebase/base.repository.js +1 -1
- package/dist/repositories/index.d.ts +1 -1
- package/dist/repositories/index.js +1 -1
- package/dist/schemas/registry.d.ts +105 -0
- package/dist/schemas/registry.js +2 -1
- package/dist/security/pii-encrypt.js +1 -1
- package/dist/server.d.ts +10 -2
- package/dist/server.js +22 -2
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/NavigationLoader.d.ts +14 -1
- package/dist/ui/components/NavigationLoader.js +79 -30
- package/package.json +1 -1
- package/dist/features/orders/utils/order-announcement-message.d.ts +0 -18
- package/dist/features/orders/utils/order-announcement-message.js +0 -16
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* WHY: a seller's WhatsApp settings are now ONE decision — do they pay the
|
|
5
|
+
* monthly fee to have order updates sent to their buyers on the
|
|
6
|
+
* platform's WhatsApp number.
|
|
7
|
+
*
|
|
8
|
+
* 🛑 This replaced a 574-line credential form that asked every seller for a
|
|
9
|
+
* WhatsApp Business Account ID, a Catalogue ID and a **System User Access
|
|
10
|
+
* Token**, then stored that token encrypted on their store document.
|
|
11
|
+
*
|
|
12
|
+
* Three things were wrong with it, and they are why none of it is coming back:
|
|
13
|
+
*
|
|
14
|
+
* 1. Almost no small seller will create a Meta app and mint a system user
|
|
15
|
+
* token. A setting nobody can complete is a feature nobody has.
|
|
16
|
+
* 2. It made LetItRip the custodian of a live Meta credential per seller.
|
|
17
|
+
* `StoreRepository.mapDoc` decrypted that token on EVERY read, so any
|
|
18
|
+
* code path that forgot to project was handling plaintext — a gap
|
|
19
|
+
* CLAUDE.md flags as deserving its own session.
|
|
20
|
+
* 3. The catalogue half it existed for is gone: `/whatsapp/{slug}/feed.csv`
|
|
21
|
+
* is a public feed the seller points their own Commerce Manager at, so
|
|
22
|
+
* no credential crosses the boundary at all.
|
|
23
|
+
*
|
|
24
|
+
* WHAT: subscription status, the price, and the toggle.
|
|
25
|
+
*
|
|
26
|
+
* ## Presentational on purpose
|
|
27
|
+
*
|
|
28
|
+
* Takes the price and the current state as PROPS rather than reading site
|
|
29
|
+
* settings itself. Site settings are server-read and passed down — the
|
|
30
|
+
* monthly fee lives beside the platform's Meta credentials in a document that
|
|
31
|
+
* must never reach a browser, and a client hook here would be a second reader
|
|
32
|
+
* of it (Root Cause #70).
|
|
33
|
+
*
|
|
34
|
+
* EXPORTS: SellerWhatsAppSettingsView, SellerWhatsAppSettingsViewProps
|
|
35
|
+
*
|
|
36
|
+
* @tag domain:whatsapp
|
|
37
|
+
* @tag layer:component
|
|
38
|
+
* @tag access:client
|
|
39
|
+
*/
|
|
40
|
+
import { useState } from "react";
|
|
41
|
+
import { Alert, Badge, Div, Heading, Row, Section, Span, Stack, Text, Toggle, useToast, } from "../../../ui/index.js";
|
|
42
|
+
import { normalizeError } from "../../../errors/normalize.js";
|
|
43
|
+
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
44
|
+
export function SellerWhatsAppSettingsView({ subscribed, monthlyFee, currency = "INR", channelEnabled, onToggleSubscription, hasCapability = true, }) {
|
|
45
|
+
const [isOn, setIsOn] = useState(subscribed);
|
|
46
|
+
const [saving, setSaving] = useState(false);
|
|
47
|
+
const { showToast } = useToast();
|
|
48
|
+
const handleToggle = async (next) => {
|
|
49
|
+
if (saving)
|
|
50
|
+
return;
|
|
51
|
+
const previous = isOn;
|
|
52
|
+
setIsOn(next);
|
|
53
|
+
setSaving(true);
|
|
54
|
+
try {
|
|
55
|
+
await onToggleSubscription(next);
|
|
56
|
+
showToast(next
|
|
57
|
+
? "WhatsApp order updates are on. Your buyers will get them from the next order."
|
|
58
|
+
: "WhatsApp order updates are off. Buyers still get email and in-app updates.", "success");
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
/*
|
|
62
|
+
* Not swallowed, and the optimistic flip is REVERTED. A toggle that
|
|
63
|
+
* stays on after a failed write tells the seller they are subscribed
|
|
64
|
+
* when they are not — and the first they would hear of it is a buyer
|
|
65
|
+
* asking why no updates arrived.
|
|
66
|
+
*/
|
|
67
|
+
void normalizeError(err);
|
|
68
|
+
setIsOn(previous);
|
|
69
|
+
showToast("Could not change your subscription. Please try again.", "error");
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
setSaving(false);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
return (_jsx(Section, { children: _jsxs(Div, { className: "mx-auto max-w-2xl", children: [_jsx(Heading, { level: 1, className: "mb-1", color: "primary", size: "2xl", weight: "bold", children: "WhatsApp order updates" }), _jsx(Text, { variant: "secondary", className: "mb-6", size: "sm", children: "Send your buyers order updates on WhatsApp \u2014 confirmed, shipped, delivered \u2014 from LetItRip's verified business number. You don't need a WhatsApp Business account of your own, and nothing is installed on your phone." }), !channelEnabled && (_jsx(Alert, { variant: "warning", className: "mb-4", children: "WhatsApp updates are currently switched off platform-wide, so nothing will be sent even if you subscribe. We'll start sending as soon as it's back on." })), _jsxs(Stack, { gap: "md", surface: "card", padding: "md", children: [_jsxs(Row, { justify: "between", align: "center", gap: "md", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { gap: "sm", align: "center", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Subscription" }), _jsx(Badge, { variant: isOn ? "success" : "inactive", children: isOn ? "Active" : "Not subscribed" })] }), _jsxs(Text, { color: "muted", size: "sm", children: [formatCurrency(monthlyFee, currency), " per month. Covers every order update to every buyer \u2014 not charged per message."] })] }), _jsx(Toggle, { checked: isOn, onChange: handleToggle, disabled: saving || !hasCapability, size: "md" })] }), !hasCapability && (_jsx(Div, { textSize: "sm", className: "border border-warning/20", color: "warning", surface: "warning-surface", padding: "inlineSm", rounded: "lg", children: "Your store doesn't have this add-on enabled. Contact support to turn it on." }))] }), _jsx(Heading, { level: 2, className: "mt-8 mb-3", size: "lg", weight: "semibold", children: "What your buyers get" }), _jsxs(Stack, { gap: "sm", surface: "card", padding: "md", children: [_jsxs(Text, { color: "muted", size: "sm", children: ["A WhatsApp message when an order is ", _jsx(Span, { weight: "semibold", children: "confirmed" }), ",", " ", _jsx(Span, { weight: "semibold", children: "shipped" }), ", ", _jsx(Span, { weight: "semibold", children: "delivered" }), ",", " ", _jsx(Span, { weight: "semibold", children: "cancelled" }), " or ", _jsx(Span, { weight: "semibold", children: "refunded" }), ". Nothing promotional \u2014 buyers who opted out of WhatsApp never receive these."] }), _jsx(Text, { color: "muted", size: "sm", children: "Messages come from LetItRip's number, not yours, so your personal number stays private and replies land with our support team rather than on your phone." }), _jsx(Text, { color: "muted", size: "sm", children: "Turning this off changes nothing else: buyers keep getting the same updates by email and in their account." })] })] }) }));
|
|
76
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface WhatsAppChatButtonProps {
|
|
3
|
+
waNumber: string;
|
|
4
|
+
message?: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function WhatsAppChatButton({ waNumber, message, label, className, }: WhatsAppChatButtonProps): React.JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import Link from "next/link.js";
|
|
3
|
+
const CLS_WA_BTN = "inline-flex items-center gap-[var(--appkit-space-2)] rounded-lg bg-success-solid px-[var(--appkit-space-4)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-semibold text-success-on-solid transition-colors hover:brightness-110";
|
|
4
|
+
export function WhatsAppChatButton({ waNumber, message = "Hi! I have a question.", label = "Chat on WhatsApp", className, }) {
|
|
5
|
+
const href = `https://wa.me/${waNumber}?text=${encodeURIComponent(message)}`;
|
|
6
|
+
return (_jsxs(Link, { href: href, target: "_blank", rel: "noopener noreferrer", className: `${CLS_WA_BTN} ${className ?? ""}`, children: [_jsx("svg", { className: "h-4 w-4", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z" }) }), label] }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CheckoutMessageInput, StatusNotificationInput, WebhookVerifyInput, IncomingWebhookPayload, SendWhatsAppInput, StatusMessageInput, WaBusinessSendInput, WaBusinessTemplateSendInput, CatalogSyncInput, CatalogSyncResult, PurchaseAnnouncementInput } from "../types";
|
|
2
|
+
export declare function buildCheckoutMessageURL(input: CheckoutMessageInput): string;
|
|
3
|
+
export declare function buildStatusNotificationURL(input: StatusNotificationInput): string;
|
|
4
|
+
export declare function verifyWebhookSignature(input: WebhookVerifyInput): boolean;
|
|
5
|
+
export declare function isAdminNumber(incomingNumber: string, adminBotNumber: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Parse an incoming WhatsApp webhook payload (Twilio form-encoded or Wati.io JSON).
|
|
8
|
+
* Returns null if the payload cannot be parsed or is missing required fields.
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseIncomingWebhookPayload(rawBody: string, contentType: string): IncomingWebhookPayload | null;
|
|
11
|
+
/**
|
|
12
|
+
* Build a human-readable status notification message for an order.
|
|
13
|
+
* Pass a custom `statusMessages` map to override the defaults.
|
|
14
|
+
*/
|
|
15
|
+
export declare function buildStatusMessage(input: StatusMessageInput, statusMessages?: Record<string, string>): string;
|
|
16
|
+
/**
|
|
17
|
+
* Send a WhatsApp message via Meta WhatsApp Business Cloud API.
|
|
18
|
+
* Distinct from the Twilio-based sendWhatsAppMessage — used for server-initiated
|
|
19
|
+
* platform announcements. Returns true on success, false on any failure.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sendWhatsAppBusinessMessage(input: WaBusinessSendInput): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Send a pre-approved Meta message TEMPLATE via WhatsApp Business Cloud API.
|
|
24
|
+
* Required for business-initiated sends outside the 24h customer-service
|
|
25
|
+
* window (proactive order/status notifications) — Meta rejects free-form
|
|
26
|
+
* `sendWhatsAppBusinessMessage` `type: "text"` sends in that case. Returns
|
|
27
|
+
* true on success, false on any failure (including a missing/unapproved
|
|
28
|
+
* template — Meta returns a 4xx with an error body, not a network failure).
|
|
29
|
+
*/
|
|
30
|
+
export declare function sendWhatsAppTemplateMessage(input: WaBusinessTemplateSendInput): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Sync a store's products to a Meta Commerce Catalog via items_batch API.
|
|
33
|
+
* Batches at 50 items per call (Meta limit). Returns aggregate results.
|
|
34
|
+
*/
|
|
35
|
+
export declare function syncProductsToCatalog(input: CatalogSyncInput): Promise<CatalogSyncResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Build a plain-text purchase announcement message for WhatsApp.
|
|
38
|
+
* Sent to admin numbers and store owner when a new order is placed.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildPurchaseAnnouncementMessage(input: PurchaseAnnouncementInput): string;
|
|
41
|
+
/**
|
|
42
|
+
* Build a wa.me share link with a pre-filled message.
|
|
43
|
+
* Opens WhatsApp contact picker — user selects group manually.
|
|
44
|
+
* This is the only API-compliant way to target a WhatsApp group.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildGroupShareLink(message: string): string;
|
|
47
|
+
export { buildPaymentProofReviewMessage } from "../../orders/utils/payment-proof-message";
|
|
48
|
+
/**
|
|
49
|
+
* Send an outbound WhatsApp message via Twilio REST API.
|
|
50
|
+
* Returns true on success, false on failure.
|
|
51
|
+
*/
|
|
52
|
+
export declare function sendWhatsAppMessage(input: SendWhatsAppInput): Promise<boolean>;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { normalizeError } from "../../../errors/normalize.js";
|
|
2
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
3
|
+
import { getDefaultCurrencySymbol, getDefaultLocale, } from "../../../core/baseline-resolver.js";
|
|
4
|
+
const META_GRAPH_BASE = "https://graph.facebook.com/v20.0";
|
|
5
|
+
export function buildCheckoutMessageURL(input) {
|
|
6
|
+
const { waNumber, cart, total, address, isPreorder = false } = input;
|
|
7
|
+
const prefix = isPreorder ? "\uD83D\uDD16 *PRE-ORDER*\n\n" : "";
|
|
8
|
+
const lines = cart.map((i) => `• ${i.name} ×${i.qty} — ${getDefaultCurrencySymbol()}${(i.salePrice * i.qty).toLocaleString(getDefaultLocale())}`);
|
|
9
|
+
const body = [
|
|
10
|
+
`${prefix}Hi Hobson! I'd like to place an order:`,
|
|
11
|
+
"",
|
|
12
|
+
...lines,
|
|
13
|
+
"",
|
|
14
|
+
`*Total: ${getDefaultCurrencySymbol()}${total.toLocaleString(getDefaultLocale())}*`,
|
|
15
|
+
"",
|
|
16
|
+
`Deliver to: ${address.name}, ${address.line1}${address.line2 ? ", " + address.line2 : ""}, ${address.city} - ${address.pincode}`,
|
|
17
|
+
`Phone: ${address.phone}`,
|
|
18
|
+
"",
|
|
19
|
+
"Please share payment details.",
|
|
20
|
+
].join("\n");
|
|
21
|
+
return `https://wa.me/${waNumber}?text=${encodeURIComponent(body)}`;
|
|
22
|
+
}
|
|
23
|
+
export function buildStatusNotificationURL(input) {
|
|
24
|
+
const { userPhone, template, vars } = input;
|
|
25
|
+
const body = template.replace(/\{(\w+)\}/g, (_, key) => vars[key] ?? `{${key}}`);
|
|
26
|
+
return `https://wa.me/${userPhone}?text=${encodeURIComponent(body)}`;
|
|
27
|
+
}
|
|
28
|
+
export function verifyWebhookSignature(input) {
|
|
29
|
+
const { payload, signature, secret } = input;
|
|
30
|
+
const expected = createHmac("sha256", secret).update(payload).digest("hex");
|
|
31
|
+
const expectedBuf = Buffer.from(expected, "hex");
|
|
32
|
+
const sigBuf = Buffer.from(signature, "hex");
|
|
33
|
+
if (expectedBuf.length !== sigBuf.length)
|
|
34
|
+
return false;
|
|
35
|
+
return timingSafeEqual(expectedBuf, sigBuf);
|
|
36
|
+
}
|
|
37
|
+
export function isAdminNumber(incomingNumber, adminBotNumber) {
|
|
38
|
+
const clean = (n) => n.replace(/\D/g, "");
|
|
39
|
+
return clean(incomingNumber).endsWith(clean(adminBotNumber));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse an incoming WhatsApp webhook payload (Twilio form-encoded or Wati.io JSON).
|
|
43
|
+
* Returns null if the payload cannot be parsed or is missing required fields.
|
|
44
|
+
*/
|
|
45
|
+
export function parseIncomingWebhookPayload(rawBody, contentType) {
|
|
46
|
+
try {
|
|
47
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
48
|
+
const params = new URLSearchParams(rawBody);
|
|
49
|
+
const from = params.get("From") ?? "";
|
|
50
|
+
const body = params.get("Body") ?? "";
|
|
51
|
+
if (!from || !body)
|
|
52
|
+
return null;
|
|
53
|
+
return {
|
|
54
|
+
from: from.replace(/^whatsapp:/i, "").replace(/\D/g, ""),
|
|
55
|
+
body: body.trim(),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// JSON format (Wati.io or generic)
|
|
59
|
+
const json = JSON.parse(rawBody);
|
|
60
|
+
const from = typeof json.senderWaId === "string"
|
|
61
|
+
? json.senderWaId
|
|
62
|
+
: typeof json.from === "string"
|
|
63
|
+
? json.from
|
|
64
|
+
: "";
|
|
65
|
+
const body = typeof json.text?.body ===
|
|
66
|
+
"string"
|
|
67
|
+
? json.text.body
|
|
68
|
+
: typeof json.body === "string"
|
|
69
|
+
? json.body
|
|
70
|
+
: "";
|
|
71
|
+
if (!from || !body)
|
|
72
|
+
return null;
|
|
73
|
+
return { from: from.replace(/\D/g, ""), body: body.trim() };
|
|
74
|
+
}
|
|
75
|
+
catch (_err) {
|
|
76
|
+
void normalizeError(_err);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const DEFAULT_STATUS_MESSAGES = {
|
|
81
|
+
pending_payment: "🛒 Your order #{id} has been received and is awaiting payment confirmation.",
|
|
82
|
+
payment_confirmed: "✅ Payment confirmed for order #{id}! We're getting it ready.",
|
|
83
|
+
processing: "📦 Your order #{id} is being packed and prepared for dispatch.",
|
|
84
|
+
shipped: "🚚 Your order #{id} is on its way!{tracking}",
|
|
85
|
+
out_for_delivery: "🏃 Your order #{id} is out for delivery today!",
|
|
86
|
+
delivered: "🎉 Order #{id} delivered! Thank you for your order!",
|
|
87
|
+
cancelled: "❌ Your order #{id} has been cancelled. Contact us if you have any questions.",
|
|
88
|
+
refund_initiated: "💸 Refund initiated for order #{id}. It should reflect within 5–7 business days.",
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Build a human-readable status notification message for an order.
|
|
92
|
+
* Pass a custom `statusMessages` map to override the defaults.
|
|
93
|
+
*/
|
|
94
|
+
export function buildStatusMessage(input, statusMessages = DEFAULT_STATUS_MESSAGES) {
|
|
95
|
+
const { orderId, status, trackingNumber, courierName } = input;
|
|
96
|
+
const template = statusMessages[status] ??
|
|
97
|
+
`Your order #${orderId} status updated to: ${status}.`;
|
|
98
|
+
const trackingLine = trackingNumber
|
|
99
|
+
? `\nTracking: ${courierName ? courierName + " — " : ""}${trackingNumber}`
|
|
100
|
+
: "";
|
|
101
|
+
return template.replace("{id}", orderId).replace("{tracking}", trackingLine);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Send a WhatsApp message via Meta WhatsApp Business Cloud API.
|
|
105
|
+
* Distinct from the Twilio-based sendWhatsAppMessage — used for server-initiated
|
|
106
|
+
* platform announcements. Returns true on success, false on any failure.
|
|
107
|
+
*/
|
|
108
|
+
export async function sendWhatsAppBusinessMessage(input) {
|
|
109
|
+
const { toPhone, message, phoneNumberId, accessToken } = input;
|
|
110
|
+
const cleanPhone = toPhone.replace(/\D/g, "");
|
|
111
|
+
if (!cleanPhone || !phoneNumberId || !accessToken)
|
|
112
|
+
return false;
|
|
113
|
+
try {
|
|
114
|
+
const res = await fetch(`${META_GRAPH_BASE}/${phoneNumberId}/messages`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
"Content-Type": "application/json",
|
|
118
|
+
Authorization: `Bearer ${accessToken}`,
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
messaging_product: "whatsapp",
|
|
122
|
+
to: cleanPhone,
|
|
123
|
+
type: "text",
|
|
124
|
+
text: { body: message },
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
return res.ok;
|
|
128
|
+
}
|
|
129
|
+
catch (_err) {
|
|
130
|
+
void normalizeError(_err);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Send a pre-approved Meta message TEMPLATE via WhatsApp Business Cloud API.
|
|
136
|
+
* Required for business-initiated sends outside the 24h customer-service
|
|
137
|
+
* window (proactive order/status notifications) — Meta rejects free-form
|
|
138
|
+
* `sendWhatsAppBusinessMessage` `type: "text"` sends in that case. Returns
|
|
139
|
+
* true on success, false on any failure (including a missing/unapproved
|
|
140
|
+
* template — Meta returns a 4xx with an error body, not a network failure).
|
|
141
|
+
*/
|
|
142
|
+
export async function sendWhatsAppTemplateMessage(input) {
|
|
143
|
+
const { toPhone, phoneNumberId, accessToken, templateName, languageCode, bodyParams } = input;
|
|
144
|
+
const cleanPhone = toPhone.replace(/\D/g, "");
|
|
145
|
+
if (!cleanPhone || !phoneNumberId || !accessToken || !templateName)
|
|
146
|
+
return false;
|
|
147
|
+
try {
|
|
148
|
+
const res = await fetch(`${META_GRAPH_BASE}/${phoneNumberId}/messages`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
Authorization: `Bearer ${accessToken}`,
|
|
153
|
+
},
|
|
154
|
+
body: JSON.stringify({
|
|
155
|
+
messaging_product: "whatsapp",
|
|
156
|
+
to: cleanPhone,
|
|
157
|
+
type: "template",
|
|
158
|
+
template: {
|
|
159
|
+
name: templateName,
|
|
160
|
+
language: { code: languageCode },
|
|
161
|
+
...(bodyParams?.length
|
|
162
|
+
? {
|
|
163
|
+
components: [
|
|
164
|
+
{
|
|
165
|
+
type: "body",
|
|
166
|
+
parameters: bodyParams.map((text) => ({ type: "text", text })),
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
}
|
|
170
|
+
: {}),
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
174
|
+
return res.ok;
|
|
175
|
+
}
|
|
176
|
+
catch (_err) {
|
|
177
|
+
void normalizeError(_err);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Sync a store's products to a Meta Commerce Catalog via items_batch API.
|
|
183
|
+
* Batches at 50 items per call (Meta limit). Returns aggregate results.
|
|
184
|
+
*/
|
|
185
|
+
export async function syncProductsToCatalog(input) {
|
|
186
|
+
const { catalogId, accessToken, products } = input;
|
|
187
|
+
const result = {
|
|
188
|
+
handles: [],
|
|
189
|
+
successCount: 0,
|
|
190
|
+
failureCount: 0,
|
|
191
|
+
errors: [],
|
|
192
|
+
};
|
|
193
|
+
const BATCH_SIZE = 50;
|
|
194
|
+
for (let i = 0; i < products.length; i += BATCH_SIZE) {
|
|
195
|
+
const batch = products.slice(i, i + BATCH_SIZE);
|
|
196
|
+
const requests = batch.map((p) => ({
|
|
197
|
+
method: "UPDATE",
|
|
198
|
+
retailer_id: p.id,
|
|
199
|
+
data: {
|
|
200
|
+
name: p.title,
|
|
201
|
+
description: p.description,
|
|
202
|
+
// Meta expects "<amount> <ISO_CURRENCY>" e.g. "450.00 INR"
|
|
203
|
+
price: `${p.price.toFixed(2)} ${p.currency}`,
|
|
204
|
+
image_url: p.imageUrl,
|
|
205
|
+
availability: p.availability,
|
|
206
|
+
condition: p.condition,
|
|
207
|
+
...(p.link ? { url: p.link } : {}),
|
|
208
|
+
},
|
|
209
|
+
}));
|
|
210
|
+
try {
|
|
211
|
+
const res = await fetch(`${META_GRAPH_BASE}/${catalogId}/items_batch`, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: {
|
|
214
|
+
"Content-Type": "application/json",
|
|
215
|
+
Authorization: `Bearer ${accessToken}`,
|
|
216
|
+
},
|
|
217
|
+
body: JSON.stringify({ requests }),
|
|
218
|
+
});
|
|
219
|
+
const json = (await res.json());
|
|
220
|
+
if (res.ok && json.handles) {
|
|
221
|
+
result.handles.push(...json.handles);
|
|
222
|
+
result.successCount += batch.length;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
result.failureCount += batch.length;
|
|
226
|
+
result.errors.push(json.error?.message ?? `Batch ${i / BATCH_SIZE + 1} failed`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
void normalizeError(err);
|
|
231
|
+
result.failureCount += batch.length;
|
|
232
|
+
result.errors.push(String(err));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Build a plain-text purchase announcement message for WhatsApp.
|
|
239
|
+
* Sent to admin numbers and store owner when a new order is placed.
|
|
240
|
+
*/
|
|
241
|
+
export function buildPurchaseAnnouncementMessage(input) {
|
|
242
|
+
const { buyerName, firstItemName, additionalItemCount, totalAmount, orderId } = input;
|
|
243
|
+
const extra = additionalItemCount > 0 ? ` + ${additionalItemCount} more item${additionalItemCount > 1 ? "s" : ""}` : "";
|
|
244
|
+
const amount = totalAmount.toLocaleString("en-IN");
|
|
245
|
+
return `🛍️ New order! ${buyerName} purchased ${firstItemName}${extra} for ₹${amount}. Order #${orderId}`;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Build a wa.me share link with a pre-filled message.
|
|
249
|
+
* Opens WhatsApp contact picker — user selects group manually.
|
|
250
|
+
* This is the only API-compliant way to target a WhatsApp group.
|
|
251
|
+
*/
|
|
252
|
+
export function buildGroupShareLink(message) {
|
|
253
|
+
return `https://wa.me/?text=${encodeURIComponent(message)}`;
|
|
254
|
+
}
|
|
255
|
+
/*
|
|
256
|
+
* `buildPaymentProofReviewMessage` lives in `features/orders/utils/`, not
|
|
257
|
+
* beside these senders. It is a pure wa.me link-body builder with no crypto
|
|
258
|
+
* and no network, and it belongs to the orders domain — the move happened
|
|
259
|
+
* during the 2026-10-20 removal and is kept, because re-homing it here would
|
|
260
|
+
* drag this file's top-level `crypto` import into the client barrel's graph
|
|
261
|
+
* (Root Cause #24).
|
|
262
|
+
*/
|
|
263
|
+
export { buildPaymentProofReviewMessage } from "../../orders/utils/payment-proof-message.js";
|
|
264
|
+
/**
|
|
265
|
+
* Send an outbound WhatsApp message via Twilio REST API.
|
|
266
|
+
* Returns true on success, false on failure.
|
|
267
|
+
*/
|
|
268
|
+
export async function sendWhatsAppMessage(input) {
|
|
269
|
+
const { toPhone, message, accountSid, authToken, fromNumber } = input;
|
|
270
|
+
const cleanPhone = toPhone.replace(/\D/g, "");
|
|
271
|
+
if (!cleanPhone)
|
|
272
|
+
return false;
|
|
273
|
+
const url = `https://api.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`;
|
|
274
|
+
const body = new URLSearchParams({
|
|
275
|
+
From: fromNumber,
|
|
276
|
+
To: `whatsapp:+${cleanPhone}`,
|
|
277
|
+
Body: message,
|
|
278
|
+
});
|
|
279
|
+
const res = await fetch(url, {
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers: {
|
|
282
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
283
|
+
Authorization: `Basic ${Buffer.from(`${accountSid}:${authToken}`).toString("base64")}`,
|
|
284
|
+
},
|
|
285
|
+
body: body.toString(),
|
|
286
|
+
});
|
|
287
|
+
return res.ok;
|
|
288
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const manifest = {
|
|
2
|
+
name: "whatsappBot",
|
|
3
|
+
i18nNamespace: "whatsappBot",
|
|
4
|
+
envKeys: [
|
|
5
|
+
"WHATSAPP_BOT_NUMBER",
|
|
6
|
+
"WHATSAPP_WEBHOOK_SECRET",
|
|
7
|
+
],
|
|
8
|
+
routes: [],
|
|
9
|
+
apiRoutes: [
|
|
10
|
+
{ segment: "api/whatsapp/webhook", methods: ["POST"] },
|
|
11
|
+
{ segment: "api/whatsapp/send-status", methods: ["POST"] },
|
|
12
|
+
],
|
|
13
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export interface CheckoutMessageInput {
|
|
2
|
+
waNumber: string;
|
|
3
|
+
cart: Array<{
|
|
4
|
+
name: string;
|
|
5
|
+
qty: number;
|
|
6
|
+
salePrice: number;
|
|
7
|
+
}>;
|
|
8
|
+
total: number;
|
|
9
|
+
address: {
|
|
10
|
+
name: string;
|
|
11
|
+
phone: string;
|
|
12
|
+
line1: string;
|
|
13
|
+
line2?: string;
|
|
14
|
+
city: string;
|
|
15
|
+
pincode: string;
|
|
16
|
+
};
|
|
17
|
+
isPreorder?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface StatusNotificationInput {
|
|
20
|
+
userPhone: string;
|
|
21
|
+
template: string;
|
|
22
|
+
vars: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
export interface WebhookVerifyInput {
|
|
25
|
+
payload: string;
|
|
26
|
+
signature: string;
|
|
27
|
+
secret: string;
|
|
28
|
+
}
|
|
29
|
+
export interface IncomingWebhookPayload {
|
|
30
|
+
/** Digits-only phone number, e.g. "919876543210" */
|
|
31
|
+
from: string;
|
|
32
|
+
body: string;
|
|
33
|
+
}
|
|
34
|
+
export interface SendWhatsAppInput {
|
|
35
|
+
/** Digits-only phone number, e.g. "919876543210" */
|
|
36
|
+
toPhone: string;
|
|
37
|
+
message: string;
|
|
38
|
+
accountSid: string;
|
|
39
|
+
authToken: string;
|
|
40
|
+
/** Twilio from number, e.g. "whatsapp:+14155238886" */
|
|
41
|
+
fromNumber: string;
|
|
42
|
+
}
|
|
43
|
+
/** Input for sending a message via Meta WhatsApp Business Cloud API. */
|
|
44
|
+
export interface WaBusinessSendInput {
|
|
45
|
+
/** Digits-only phone number with country code, e.g. "919876543210" */
|
|
46
|
+
toPhone: string;
|
|
47
|
+
message: string;
|
|
48
|
+
/** Meta Phone Number ID (from Meta for Developers > App > WhatsApp > API Setup) */
|
|
49
|
+
phoneNumberId: string;
|
|
50
|
+
/** Meta system user access token */
|
|
51
|
+
accessToken: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Input for sending a pre-approved Meta message TEMPLATE. Required for any
|
|
55
|
+
* business-initiated WhatsApp send outside the 24h customer-service window —
|
|
56
|
+
* Meta rejects free-form `type: "text"` sends in that case.
|
|
57
|
+
*/
|
|
58
|
+
export interface WaBusinessTemplateSendInput {
|
|
59
|
+
/** Digits-only phone number with country code, e.g. "919876543210" */
|
|
60
|
+
toPhone: string;
|
|
61
|
+
/** Meta Phone Number ID (from Meta for Developers > App > WhatsApp > API Setup) */
|
|
62
|
+
phoneNumberId: string;
|
|
63
|
+
/** Meta system user access token */
|
|
64
|
+
accessToken: string;
|
|
65
|
+
/** Exact approved template name from Meta WhatsApp Manager (not the display label). */
|
|
66
|
+
templateName: string;
|
|
67
|
+
/** BCP-47 language code the template was approved in, e.g. "en" or "en_US". */
|
|
68
|
+
languageCode: string;
|
|
69
|
+
/** Positional {{1}}, {{2}}… body parameters, in order. */
|
|
70
|
+
bodyParams?: string[];
|
|
71
|
+
}
|
|
72
|
+
/** A product mapped to the Meta Commerce API catalog item format. */
|
|
73
|
+
export interface CatalogSyncProduct {
|
|
74
|
+
/** Firestore product ID — used as Meta `retailer_id` */
|
|
75
|
+
id: string;
|
|
76
|
+
title: string;
|
|
77
|
+
description: string;
|
|
78
|
+
/** Price in decimal rupees */
|
|
79
|
+
price: number;
|
|
80
|
+
currency: string;
|
|
81
|
+
/** URL of the first product image */
|
|
82
|
+
imageUrl: string;
|
|
83
|
+
availability: "in stock" | "out of stock";
|
|
84
|
+
condition: "new" | "used" | "refurbished";
|
|
85
|
+
/** Canonical product page URL, e.g. /products/{slug} */
|
|
86
|
+
link?: string;
|
|
87
|
+
}
|
|
88
|
+
export interface CatalogSyncInput {
|
|
89
|
+
catalogId: string;
|
|
90
|
+
/** Meta access token with catalog management permission */
|
|
91
|
+
accessToken: string;
|
|
92
|
+
products: CatalogSyncProduct[];
|
|
93
|
+
}
|
|
94
|
+
export interface CatalogSyncResult {
|
|
95
|
+
/** Meta batch request handles */
|
|
96
|
+
handles: string[];
|
|
97
|
+
successCount: number;
|
|
98
|
+
failureCount: number;
|
|
99
|
+
errors: string[];
|
|
100
|
+
}
|
|
101
|
+
/** Input for building a purchase announcement message. */
|
|
102
|
+
export interface PurchaseAnnouncementInput {
|
|
103
|
+
buyerName: string;
|
|
104
|
+
firstItemName: string;
|
|
105
|
+
/** 0 if only one item in the order */
|
|
106
|
+
additionalItemCount: number;
|
|
107
|
+
/** Total order amount in decimal rupees */
|
|
108
|
+
totalAmount: number;
|
|
109
|
+
orderId: string;
|
|
110
|
+
}
|
|
111
|
+
/** Tier PP — input for building the payment-proof fast-review WhatsApp message (admin push + buyer/seller share link). */
|
|
112
|
+
export interface PaymentProofReviewMessageInput {
|
|
113
|
+
orderId: string;
|
|
114
|
+
buyerName: string;
|
|
115
|
+
productTitle: string;
|
|
116
|
+
totalAmount: number;
|
|
117
|
+
reviewUrl: string;
|
|
118
|
+
}
|
|
119
|
+
/** Known order status values — extend as needed per project. */
|
|
120
|
+
export type OrderStatusKey = "pending_payment" | "payment_confirmed" | "processing" | "shipped" | "out_for_delivery" | "delivered" | "cancelled" | "refund_initiated" | string;
|
|
121
|
+
export interface StatusMessageInput {
|
|
122
|
+
orderId: string;
|
|
123
|
+
status: OrderStatusKey;
|
|
124
|
+
trackingNumber?: string;
|
|
125
|
+
courierName?: string;
|
|
126
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|