@mohasinac/appkit 4.70.0 → 4.71.0
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/PROVIDERS.md +190 -0
- package/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/features/lottery/LotteryAdminSlotView.js +2 -1
- package/dist/_internal/server/features/checkout/actions.js +43 -8
- package/dist/_internal/server/features/orders/actions.js +2 -1
- package/dist/_internal/server/features/orders/adapters.js +18 -0
- package/dist/_internal/server/features/orders/balance.js +2 -1
- package/dist/_internal/server/features/orders/cancellation-refund.d.ts +0 -89
- package/dist/_internal/server/features/orders/cancellation-refund.js +3 -2
- package/dist/_internal/server/features/payouts/actions.js +2 -1
- package/dist/_internal/server/features/points/award.js +3 -2
- package/dist/_internal/server/features/protection-claims/actions.js +2 -1
- package/dist/_internal/server/features/refunds/actions.js +3 -2
- package/dist/_internal/server/features/shipping/courier-block.js +5 -2
- package/dist/_internal/server/features/shipping/reconcile.js +3 -2
- package/dist/_internal/server/features/shipping/reimbursement.js +3 -2
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +12 -2
- package/dist/_internal/server/jobs/core/dailyStatusDigest.js +7 -3
- package/dist/_internal/server/jobs/core/emiInstallmentReminder.js +2 -1
- package/dist/_internal/server/jobs/core/obligationSweep.js +5 -4
- package/dist/_internal/server/jobs/core/onScamReportCreate.js +2 -1
- package/dist/_internal/server/jobs/core/paymentReconcile.js +5 -4
- package/dist/_internal/shared/checkout/rules/auction.rule.js +25 -7
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +26 -8
- package/dist/_internal/shared/config/schema.d.ts +15 -0
- package/dist/_internal/shared/content/published-values.d.ts +2 -2
- package/dist/_internal/shared/content/published-values.js +31 -12
- package/dist/_internal/shared/features/auctions/errors.js +2 -1
- package/dist/_internal/shared/features/orders/refund-copy.js +3 -2
- package/dist/_internal/shared/features/promotions/errors.js +2 -1
- package/dist/constants/field-names.d.ts +23 -0
- package/dist/constants/field-names.js +23 -0
- package/dist/constants/storage-keys.d.ts +90 -0
- package/dist/constants/storage-keys.js +119 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/payment.d.ts +42 -0
- package/dist/contracts/registry.d.ts +8 -0
- package/dist/contracts/tax.d.ts +118 -0
- package/dist/contracts/tax.js +3 -0
- package/dist/core/brand-resolver.d.ts +92 -0
- package/dist/core/brand-resolver.js +120 -0
- package/dist/core/brand-text.d.ts +15 -0
- package/dist/core/brand-text.js +73 -0
- package/dist/core/hooks/useNewsletterSubscribe.d.ts +30 -0
- package/dist/core/hooks/useNewsletterSubscribe.js +121 -0
- package/dist/features/about/components/DeveloperView.js +2 -1
- package/dist/features/account/components/BuyerAccountGuideView.js +2 -1
- package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -3
- package/dist/features/account/components/BuyerOrdersGuideView.js +6 -5
- package/dist/features/account/components/BuyerShoppingGuideView.js +3 -2
- package/dist/features/account/components/UserOffersPanel.js +2 -1
- package/dist/features/account/hooks/useCollapsedSections.js +5 -3
- package/dist/features/account/schemas/index.js +1 -1
- package/dist/features/admin/actions/notification-actions.js +9 -2
- package/dist/features/admin/components/AdminAnalyticsGuideView.js +2 -1
- package/dist/features/admin/components/AdminBundlesView.js +2 -1
- package/dist/features/admin/components/AdminCatalogGuideView.js +2 -1
- package/dist/features/admin/components/AdminContentGuideView.js +2 -1
- package/dist/features/admin/components/AdminFaqEditorView.js +2 -1
- package/dist/features/admin/components/AdminGatewayEventsView.js +2 -1
- package/dist/features/admin/components/AdminGuideHubView.js +2 -1
- package/dist/features/admin/components/AdminMoneyCasesView.js +2 -1
- package/dist/features/admin/components/AdminOrdersGuideView.js +2 -1
- package/dist/features/admin/components/AdminProductsView.js +3 -2
- package/dist/features/admin/components/AdminSectionsView.js +3 -2
- package/dist/features/admin/components/AdminSiteSettingsView.js +8 -7
- package/dist/features/admin/components/AdminStoresGuideView.js +2 -1
- package/dist/features/admin/components/AdminSupportTicketDetailView.js +2 -1
- package/dist/features/admin/components/AdminTeamGuideView.js +2 -1
- package/dist/features/admin/components/AdminTrustGuideView.js +3 -2
- package/dist/features/admin/components/AdminUsersGuideView.js +2 -1
- package/dist/features/admin/components/sections/adminSectionsTypes.js +1 -1
- package/dist/features/auth/actions/user-slug.js +21 -2
- package/dist/features/auth/hooks/useAuth.js +11 -4
- package/dist/features/cart/utils/pending-ops.js +16 -3
- package/dist/features/catalogue/utils/product-from-item.d.ts +18 -2
- package/dist/features/catalogue/utils/product-from-item.js +23 -4
- package/dist/features/categories/components/BundleItemsPicker.js +2 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -1
- package/dist/features/categories/components/ContextualTrail.d.ts +9 -2
- package/dist/features/categories/components/ContextualTrail.js +11 -3
- package/dist/features/categories/components/LastCategoryBeacon.js +2 -2
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +9 -1
- package/dist/features/contact/email.js +11 -12
- package/dist/features/email/notification-templates.js +4 -5
- package/dist/features/email/render-static-markup.d.ts +34 -0
- package/dist/features/email/render-static-markup.js +4 -0
- package/dist/features/events/utils/event-charts.js +2 -1
- package/dist/features/homepage/components/AnnouncementBar.js +5 -3
- package/dist/features/homepage/components/WelcomeSection.d.ts +3 -1
- package/dist/features/homepage/components/WelcomeSection.js +2 -2
- package/dist/features/homepage/lib/section-renderer.d.ts +8 -0
- package/dist/features/homepage/lib/section-renderer.js +1 -1
- package/dist/features/layout/AppLayoutShell.d.ts +3 -1
- package/dist/features/layout/AppLayoutShell.js +2 -2
- package/dist/features/layout/TitleBarLayout.d.ts +8 -1
- package/dist/features/layout/TitleBarLayout.js +2 -2
- package/dist/features/media/MediaVideo.js +2 -1
- package/dist/features/orders/actions/refund-actions.js +3 -2
- package/dist/features/orders/components/RefundRequestView.js +15 -2
- package/dist/features/orders/repository/orders.repository.js +2 -1
- package/dist/features/orders/types/index.d.ts +14 -0
- package/dist/features/orders/utils/payment-proof-message.js +5 -2
- package/dist/features/price-index/components/PriceIndexToolView.js +6 -5
- package/dist/features/pricing/components/AdminPricingView.js +2 -1
- package/dist/features/print/components/labels.js +2 -1
- package/dist/features/products/components/CityCollectiblesView.js +2 -1
- package/dist/features/products/components/MakeOfferButton.js +2 -1
- package/dist/features/products/components/PrizeDrawCollage.js +3 -2
- package/dist/features/products/components/PrizeRevealModal.js +2 -1
- package/dist/features/products/repository/products.repository.js +2 -1
- package/dist/features/promotions/components/CouponCard.js +3 -2
- package/dist/features/promotions/components/CouponHelpDetails.js +2 -1
- package/dist/features/promotions/constants/coupon-help.d.ts +1 -1
- package/dist/features/promotions/constants/coupon-help.js +8 -1
- package/dist/features/scams/components/ScamAwarenessModal.js +2 -1
- package/dist/features/scams/components/ScamProfileView.js +5 -3
- package/dist/features/scams/components/ScamRegistryView.js +2 -1
- package/dist/features/scams/constants/scam-types.d.ts +13 -1
- package/dist/features/scams/constants/scam-types.js +67 -23
- package/dist/features/scams/schemas/firestore.js +1 -1
- package/dist/features/search/components/Search.js +5 -1
- package/dist/features/seller/actions/offer-actions.js +47 -9
- package/dist/features/seller/components/DispatchForm.js +3 -2
- package/dist/features/seller/components/PrintCenterView.js +4 -2
- package/dist/features/seller/components/SellerBundlesView.js +2 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +3 -2
- package/dist/features/seller/components/SellerProductShell.js +3 -2
- package/dist/features/seller/components/SellerProductsView.js +4 -3
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +30 -4
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +46 -7
- package/dist/features/store-extensions/schemas/firestore.d.ts +22 -3
- package/dist/features/store-extensions/schemas/firestore.js +20 -0
- package/dist/features/stores/components/StoreCapabilitiesGuideView.js +7 -6
- package/dist/features/stores/components/StoreFinanceGuideView.js +6 -5
- package/dist/features/stores/components/StoreGuideHubView.js +2 -1
- package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
- package/dist/features/stores/components/StoreOrdersGuideView.js +5 -4
- package/dist/features/stores/components/StoreSettingsGuideView.js +2 -1
- package/dist/features/support/schemas/index.d.ts +2 -2
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +1 -1
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +3 -2
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +5 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/providers/payment-manual/index.d.ts +11 -0
- package/dist/providers/payment-manual/index.js +13 -0
- package/dist/providers/payment-phonepe/index.d.ts +23 -7
- package/dist/providers/payment-phonepe/index.js +15 -0
- package/dist/providers/payment-razorpay/index.d.ts +13 -0
- package/dist/providers/payment-razorpay/index.js +21 -1
- package/dist/providers/tax-india-gst/index.d.ts +45 -0
- package/dist/providers/tax-india-gst/index.js +78 -0
- package/dist/react/hooks/useScrollToTop.d.ts +5 -0
- package/dist/react/hooks/useScrollToTop.js +70 -0
- package/dist/schemas/registry.d.ts +2 -2
- package/dist/seo/json-ld.js +25 -4
- package/dist/ui/components/SiteLogo.d.ts +20 -2
- package/dist/ui/components/SiteLogo.js +28 -6
- package/dist/ui/components/SiteMark.d.ts +1 -1
- package/dist/ui/components/SiteMark.js +21 -6
- package/dist/utils/date.formatter.d.ts +20 -0
- package/dist/utils/date.formatter.js +25 -0
- package/dist/utils/number.formatter.d.ts +47 -0
- package/dist/utils/number.formatter.js +79 -1
- package/dist/validation/phone.validator.d.ts +0 -1
- package/dist/validation/phone.validator.js +17 -3
- package/package.json +17 -3
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
* per-method override is needed (which is the point of the hooks — Pattern #9
|
|
11
11
|
* described overrides that never existed).
|
|
12
12
|
*
|
|
13
|
+
* 🛑 Reads are CIPHERTEXT-BY-DEFAULT since 2026-10-28. Those three fields are
|
|
14
|
+
* `Encrypted<string>` and `mapDoc` does not decrypt; use
|
|
15
|
+
* `maskedPayoutMethod(s)` (the default) or `revealPayoutMethod(s)` (opt-in and
|
|
16
|
+
* named). The interim state — encrypted at rest but decrypted on every read —
|
|
17
|
+
* was invisible to `audit-pii-coverage` purely because this file is named
|
|
18
|
+
* `.repositories.ts` and its filter read `.repository.ts`.
|
|
19
|
+
*
|
|
13
20
|
* The other ten hold no PII and use the BaseRepository public API only.
|
|
14
21
|
*/
|
|
15
22
|
import { BaseRepository } from "../../../providers/db-firebase";
|
|
16
|
-
import type
|
|
17
|
-
import { type AnalyticsAlertDocument, type AnalyticsCardDocument, type ItemRequestDocument, type ItemRequestReply, type ListingTemplateDocument, type ModerationQueueDocument, type PayoutMethodDocument, type ReportDocument, type ShippingConfigDocument, type StoreCategoryDocument, type StoreGoogleConfigDocument, type StoreWhatsAppConfigDocument } from "../schemas/firestore";
|
|
23
|
+
import { type AnalyticsAlertDocument, type AnalyticsCardDocument, type ItemRequestDocument, type ItemRequestReply, type ListingTemplateDocument, type ModerationQueueDocument, type PayoutMethodDocument, type PayoutMethodPlain, type ReportDocument, type ShippingConfigDocument, type StoreCategoryDocument, type StoreGoogleConfigDocument, type StoreWhatsAppConfigDocument } from "../schemas/firestore";
|
|
18
24
|
export declare class PayoutMethodsRepository extends BaseRepository<PayoutMethodDocument> {
|
|
19
25
|
/**
|
|
20
26
|
* Bank/UPI details, encrypted at rest by BaseRepository's write hooks.
|
|
@@ -25,8 +31,28 @@ export declare class PayoutMethodsRepository extends BaseRepository<PayoutMethod
|
|
|
25
31
|
*/
|
|
26
32
|
protected piiFields: readonly ["upiVpa", "accountNumber", "ifscCode"];
|
|
27
33
|
constructor();
|
|
28
|
-
/**
|
|
29
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Plaintext PII. **Opt-in and named** — the four `/api/store/payout-methods`
|
|
36
|
+
* response sites call it explicitly, which is the whole gain here: the
|
|
37
|
+
* decrypt is now four greppable call sites instead of implicit for every
|
|
38
|
+
* reader of this repository.
|
|
39
|
+
*
|
|
40
|
+
* 🛑 There is deliberately NO masked variant yet, and that is not an
|
|
41
|
+
* oversight. Masking payout methods properly means following
|
|
42
|
+
* `SellerPayoutSettingsView`'s convention — a separate `accountNumberMasked`
|
|
43
|
+
* key with the real field ABSENT, the form input starting empty, and "blank
|
|
44
|
+
* keeps existing" enforced server-side — because masking the real key
|
|
45
|
+
* in-place would be double-masked by
|
|
46
|
+
* `SellerPayoutMethodsView.getMaskedIdentifier` and, worse, saved over the
|
|
47
|
+
* real value by `.../[id]/edit`, which PATCHes its whole prefilled form
|
|
48
|
+
* back. When that change lands it is
|
|
49
|
+
* `maskPii(doc, PAYOUT_METHOD_PII_FIELDS)`, one import away; an unused
|
|
50
|
+
* accessor shipped ahead of it is the speculative surface `AK3-yagni-prune`
|
|
51
|
+
* exists to delete.
|
|
52
|
+
*/
|
|
53
|
+
revealPayoutMethod(doc: PayoutMethodDocument): PayoutMethodPlain;
|
|
54
|
+
/** As `revealPayoutMethod`, for a list read. */
|
|
55
|
+
revealPayoutMethods(docs: readonly PayoutMethodDocument[]): PayoutMethodPlain[];
|
|
30
56
|
listByStore(storeId: string): Promise<{
|
|
31
57
|
items: PayoutMethodDocument[];
|
|
32
58
|
}>;
|
|
@@ -10,12 +10,19 @@
|
|
|
10
10
|
* per-method override is needed (which is the point of the hooks — Pattern #9
|
|
11
11
|
* described overrides that never existed).
|
|
12
12
|
*
|
|
13
|
+
* 🛑 Reads are CIPHERTEXT-BY-DEFAULT since 2026-10-28. Those three fields are
|
|
14
|
+
* `Encrypted<string>` and `mapDoc` does not decrypt; use
|
|
15
|
+
* `maskedPayoutMethod(s)` (the default) or `revealPayoutMethod(s)` (opt-in and
|
|
16
|
+
* named). The interim state — encrypted at rest but decrypted on every read —
|
|
17
|
+
* was invisible to `audit-pii-coverage` purely because this file is named
|
|
18
|
+
* `.repositories.ts` and its filter read `.repository.ts`.
|
|
19
|
+
*
|
|
13
20
|
* The other ten hold no PII and use the BaseRepository public API only.
|
|
14
21
|
*/
|
|
15
22
|
import { BaseRepository } from "../../../providers/db-firebase/index.js";
|
|
16
23
|
import { arrayUnion, increment } from "../../../contracts/field-ops.js";
|
|
17
|
-
import {
|
|
18
|
-
import { ANALYTICS_ALERTS_COLLECTION, ANALYTICS_CARDS_COLLECTION, ITEM_REQUESTS_COLLECTION, LISTING_TEMPLATES_COLLECTION, MODERATION_QUEUE_COLLECTION, PAYOUT_METHODS_COLLECTION, REPORTS_COLLECTION, SHIPPING_CONFIGS_COLLECTION, STORE_CATEGORIES_COLLECTION, STORE_GOOGLE_CONFIG_COLLECTION, STORE_WHATSAPP_CONFIG_COLLECTION, } from "../schemas/firestore.js";
|
|
24
|
+
import { revealPii } from "../../../security/pii-accessors.js";
|
|
25
|
+
import { ANALYTICS_ALERTS_COLLECTION, ANALYTICS_CARDS_COLLECTION, ITEM_REQUESTS_COLLECTION, LISTING_TEMPLATES_COLLECTION, MODERATION_QUEUE_COLLECTION, PAYOUT_METHOD_PII_FIELDS, PAYOUT_METHODS_COLLECTION, REPORTS_COLLECTION, SHIPPING_CONFIGS_COLLECTION, STORE_CATEGORIES_COLLECTION, STORE_GOOGLE_CONFIG_COLLECTION, STORE_WHATSAPP_CONFIG_COLLECTION, } from "../schemas/firestore.js";
|
|
19
26
|
export class PayoutMethodsRepository extends BaseRepository {
|
|
20
27
|
constructor() {
|
|
21
28
|
super(PAYOUT_METHODS_COLLECTION);
|
|
@@ -26,12 +33,44 @@ export class PayoutMethodsRepository extends BaseRepository {
|
|
|
26
33
|
* (`listByStore` filters on `storeId`), so no read path depended on their
|
|
27
34
|
* plaintext.
|
|
28
35
|
*/
|
|
29
|
-
this.piiFields =
|
|
36
|
+
this.piiFields = PAYOUT_METHOD_PII_FIELDS;
|
|
37
|
+
}
|
|
38
|
+
/*
|
|
39
|
+
* 🛑 `mapDoc` no longer decrypts — reads return what Firestore holds.
|
|
40
|
+
*
|
|
41
|
+
* It used to override `mapDoc` and call `decryptPiiFields` on every read, so
|
|
42
|
+
* a plain `listByStore()` handed its caller a decrypted bank account number
|
|
43
|
+
* and IFSC code, and `GET /api/store/payout-methods` returned all three in
|
|
44
|
+
* full. That survived because `audit-pii-coverage` filtered
|
|
45
|
+
* `.endsWith(".repository.ts")` and this file is `.repositories.ts`, so the
|
|
46
|
+
* audit whose entire purpose is to find this reported `0 findings` — see the
|
|
47
|
+
* note on `isRepositoryModule` in that script.
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* Plaintext PII. **Opt-in and named** — the four `/api/store/payout-methods`
|
|
51
|
+
* response sites call it explicitly, which is the whole gain here: the
|
|
52
|
+
* decrypt is now four greppable call sites instead of implicit for every
|
|
53
|
+
* reader of this repository.
|
|
54
|
+
*
|
|
55
|
+
* 🛑 There is deliberately NO masked variant yet, and that is not an
|
|
56
|
+
* oversight. Masking payout methods properly means following
|
|
57
|
+
* `SellerPayoutSettingsView`'s convention — a separate `accountNumberMasked`
|
|
58
|
+
* key with the real field ABSENT, the form input starting empty, and "blank
|
|
59
|
+
* keeps existing" enforced server-side — because masking the real key
|
|
60
|
+
* in-place would be double-masked by
|
|
61
|
+
* `SellerPayoutMethodsView.getMaskedIdentifier` and, worse, saved over the
|
|
62
|
+
* real value by `.../[id]/edit`, which PATCHes its whole prefilled form
|
|
63
|
+
* back. When that change lands it is
|
|
64
|
+
* `maskPii(doc, PAYOUT_METHOD_PII_FIELDS)`, one import away; an unused
|
|
65
|
+
* accessor shipped ahead of it is the speculative surface `AK3-yagni-prune`
|
|
66
|
+
* exists to delete.
|
|
67
|
+
*/
|
|
68
|
+
revealPayoutMethod(doc) {
|
|
69
|
+
return revealPii(doc, PAYOUT_METHOD_PII_FIELDS);
|
|
30
70
|
}
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return decryptPiiFields(raw, [...this.piiFields]);
|
|
71
|
+
/** As `revealPayoutMethod`, for a list read. */
|
|
72
|
+
revealPayoutMethods(docs) {
|
|
73
|
+
return docs.map((d) => this.revealPayoutMethod(d));
|
|
35
74
|
}
|
|
36
75
|
async listByStore(storeId) {
|
|
37
76
|
const items = await this.findBy("storeId", storeId);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AlertMetric } from "../constants/alert-metrics";
|
|
2
2
|
import type { FirestoreDocument } from "@mohasinac/appkit";
|
|
3
3
|
import type { BaseDocument } from "../../../_internal/shared/types/base-document";
|
|
4
|
+
import type { Encrypted, Plaintext } from "../../../security/encrypted";
|
|
4
5
|
/**
|
|
5
6
|
* S-STORE Extensions — Firestore Document Types
|
|
6
7
|
*
|
|
@@ -29,13 +30,31 @@ export interface PayoutMethodDocument extends BaseDocument {
|
|
|
29
30
|
label: string;
|
|
30
31
|
isDefault: boolean;
|
|
31
32
|
isActive: boolean;
|
|
32
|
-
upiVpa?: string
|
|
33
|
-
accountNumber?: string
|
|
34
|
-
ifscCode?: string
|
|
33
|
+
upiVpa?: Encrypted<string>;
|
|
34
|
+
accountNumber?: Encrypted<string>;
|
|
35
|
+
ifscCode?: Encrypted<string>;
|
|
35
36
|
accountHolderName?: string;
|
|
36
37
|
bankName?: string;
|
|
37
38
|
details?: Record<string, string>;
|
|
38
39
|
}
|
|
40
|
+
/** `Plaintext<T>` is the one definition of this shape; see `security/encrypted.ts`. */
|
|
41
|
+
export type PayoutMethodPlain = Plaintext<PayoutMethodDocument>;
|
|
42
|
+
/**
|
|
43
|
+
* The three fields BaseRepository's write hooks encrypt at rest.
|
|
44
|
+
*
|
|
45
|
+
* 🛑 Exported rather than inlined in the repository, because `revealPii` /
|
|
46
|
+
* `maskPii` need the same list and two copies of a field list is Root Cause
|
|
47
|
+
* #75 — one money predicate hand-written in six places, two of which had
|
|
48
|
+
* silently dropped a field.
|
|
49
|
+
*
|
|
50
|
+
* `accountHolderName` is deliberately ABSENT: it is a person's name on the
|
|
51
|
+
* seller's own payout method, it is not matched by the audit's PII shape
|
|
52
|
+
* regex, and it was never encrypted. Adding it here would brand a field the
|
|
53
|
+
* write hooks have never encrypted, so `decryptValue` would pass the
|
|
54
|
+
* plaintext straight through and the brand would be a lie — exactly the
|
|
55
|
+
* half-change CLAUDE.md warns about.
|
|
56
|
+
*/
|
|
57
|
+
export declare const PAYOUT_METHOD_PII_FIELDS: readonly ["upiVpa", "accountNumber", "ifscCode"];
|
|
39
58
|
export declare const PAYOUT_METHODS_COLLECTION: "payoutMethods";
|
|
40
59
|
export declare const PAYOUT_METHOD_INDEXED_FIELDS: readonly ["sellerId", "storeId", "type", "isDefault", "isActive", "createdAt"];
|
|
41
60
|
export declare const DEFAULT_PAYOUT_METHOD_DATA: Partial<PayoutMethodDocument>;
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three fields BaseRepository's write hooks encrypt at rest.
|
|
3
|
+
*
|
|
4
|
+
* 🛑 Exported rather than inlined in the repository, because `revealPii` /
|
|
5
|
+
* `maskPii` need the same list and two copies of a field list is Root Cause
|
|
6
|
+
* #75 — one money predicate hand-written in six places, two of which had
|
|
7
|
+
* silently dropped a field.
|
|
8
|
+
*
|
|
9
|
+
* `accountHolderName` is deliberately ABSENT: it is a person's name on the
|
|
10
|
+
* seller's own payout method, it is not matched by the audit's PII shape
|
|
11
|
+
* regex, and it was never encrypted. Adding it here would brand a field the
|
|
12
|
+
* write hooks have never encrypted, so `decryptValue` would pass the
|
|
13
|
+
* plaintext straight through and the brand would be a lie — exactly the
|
|
14
|
+
* half-change CLAUDE.md warns about.
|
|
15
|
+
*/
|
|
16
|
+
export const PAYOUT_METHOD_PII_FIELDS = [
|
|
17
|
+
"upiVpa",
|
|
18
|
+
"accountNumber",
|
|
19
|
+
"ifscCode",
|
|
20
|
+
];
|
|
1
21
|
export const PAYOUT_METHODS_COLLECTION = "payoutMethods";
|
|
2
22
|
export const PAYOUT_METHOD_INDEXED_FIELDS = [
|
|
3
23
|
"sellerId",
|
|
@@ -8,6 +8,7 @@ import { Row, Stack } from "../../../ui/components/Layout.js";
|
|
|
8
8
|
import { TextLink } from "../../../ui/components/TextLink.js";
|
|
9
9
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
10
10
|
import { SellerStandingPanel } from "./SellerStandingPanel.js";
|
|
11
|
+
import { getBrandName, getBrandDomain, getBrandSupportEmail } from "../../../core/brand-resolver.js";
|
|
11
12
|
const SUBHEADING = "mb-1 font-semibold text-[var(--appkit-color-text)]";
|
|
12
13
|
const LIST_DISC = "list-disc leading-relaxed";
|
|
13
14
|
const SECTION_BODY = "space-y-4 text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)]";
|
|
@@ -17,7 +18,7 @@ function GuideSection({ title, children, }) {
|
|
|
17
18
|
}
|
|
18
19
|
// -- Section 1: What are capabilities -----------------------------------------
|
|
19
20
|
function WhatAreCapabilitiesSection() {
|
|
20
|
-
return (_jsxs(Div, { className: SECTION_BODY, children: [
|
|
21
|
+
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Text, { className: "leading-relaxed", children: ["Capabilities are per-store feature flags granted by the ", getBrandName(), " team. They are not self-serve \u2014 they must be requested and approved. This gives ", getBrandName(), " control over trust, quality, and platform safety as your store grows."] }), _jsxs(Text, { className: "leading-relaxed", children: ["Two capabilities are enabled for every store at creation by default:", _jsx(Code, { className: "mx-1", size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "suggest_brands" }), " and", _jsx(Code, { className: "mx-1", size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "create_coupons" }), ". All others must be requested. Some require contractual agreements before they can be granted."] })] }));
|
|
21
22
|
}
|
|
22
23
|
// -- Section 2: Capability table ----------------------------------------------
|
|
23
24
|
const CAPABILITY_GROUPS = [
|
|
@@ -62,7 +63,7 @@ const CAPABILITY_GROUPS = [
|
|
|
62
63
|
{
|
|
63
64
|
group: "Platform & Technical",
|
|
64
65
|
items: [
|
|
65
|
-
{ cap: "multiple_stores", unlocks:
|
|
66
|
+
{ cap: "multiple_stores", unlocks: `You (same owner) can create more than one store on ${getBrandName()}.`, defaultOn: false },
|
|
66
67
|
{ cap: "custom_store_slug", unlocks: "Change your store URL slug after the 7-day post-creation lock period.", defaultOn: false },
|
|
67
68
|
{ cap: "api_access", unlocks: "Programmatic API credentials for advanced integrations (future).", defaultOn: false },
|
|
68
69
|
{ cap: "lower_commission_rate", unlocks: "Negotiated reduced platform fee. Requires a signed agreement and minimum volume.", defaultOn: false },
|
|
@@ -80,7 +81,7 @@ const LOCKED_FEATURES = [
|
|
|
80
81
|
{
|
|
81
82
|
cap: "host_auctions",
|
|
82
83
|
missing: ["Create auction listings", "Set reserve prices and bid increments", "Auto-notify winner at auction end"],
|
|
83
|
-
howToUnlock:
|
|
84
|
+
howToUnlock: `Contact ${getBrandSupportEmail()} with at least 10 delivered orders and 3 months of store activity. Our team reviews within 5 business days.`,
|
|
84
85
|
contractRequired: false,
|
|
85
86
|
},
|
|
86
87
|
{
|
|
@@ -92,7 +93,7 @@ const LOCKED_FEATURES = [
|
|
|
92
93
|
{
|
|
93
94
|
cap: "lower_commission_rate",
|
|
94
95
|
missing: ["Reduced platform fee (custom rate)", "Higher net margin per order"],
|
|
95
|
-
howToUnlock:
|
|
96
|
+
howToUnlock: `Available to stores with ₹2L+ GMV per month. Contact your account manager or email partner@${getBrandDomain()} with your last 3 months' payout statements.`,
|
|
96
97
|
contractRequired: true,
|
|
97
98
|
},
|
|
98
99
|
{
|
|
@@ -104,7 +105,7 @@ const LOCKED_FEATURES = [
|
|
|
104
105
|
{
|
|
105
106
|
cap: "bulk_listing_import",
|
|
106
107
|
missing: ["CSV batch upload for products", "Template download for bulk edits"],
|
|
107
|
-
howToUnlock:
|
|
108
|
+
howToUnlock: `Contact ${getBrandSupportEmail()} — available to stores with verified status and 50+ published listings.`,
|
|
108
109
|
contractRequired: false,
|
|
109
110
|
},
|
|
110
111
|
];
|
|
@@ -125,7 +126,7 @@ function LockedFeatureGuide() {
|
|
|
125
126
|
* drift from the job that grants it.
|
|
126
127
|
*/
|
|
127
128
|
function VerifiedSellerSection() {
|
|
128
|
-
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Two different badges" }), _jsxs(Text, { className: "leading-relaxed", children: [_jsx(Span, { weight: "bold", children: "Verified" }), " means
|
|
129
|
+
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Two different badges" }), _jsxs(Text, { className: "leading-relaxed", children: [_jsx(Span, { weight: "bold", children: "Verified" }), " means ", getBrandName(), " has checked who you are. It is granted by an admin after an identity review, it is not earned by volume, and it does not expire on its own.", " ", _jsx(Span, { weight: "bold", children: "Trusted Seller" }), " is earned from your trading record and is recalculated every month \u2014 you can gain it and you can lose it. A store can hold either, both, or neither."] })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Getting Verified" }), _jsx(Text, { className: "leading-relaxed", children: "Open a support ticket from your store dashboard with category \"Account\" and subject \"Verified Seller Application\", and attach a government-issued photo ID. An admin reviews it and sets the badge on your store." })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Earning Trusted Seller" }), _jsx(Text, { className: "leading-relaxed", children: "Every criterion below is measured over the same rolling window, and all of them have to hold at the same time. Meeting them also changes when you are paid: your payout starts when you ship rather than when the parcel is delivered." })] }), _jsx(SellerStandingPanel, { heading: "The criteria" })] }));
|
|
129
130
|
}
|
|
130
131
|
// -- Section 5: Multiple stores -----------------------------------------------
|
|
131
132
|
function MultipleStoresSection() {
|
|
@@ -8,6 +8,7 @@ import { Heading, Span, Text } from "../../../ui/components/Typography.js";
|
|
|
8
8
|
import { Stack } from "../../../ui/components/Layout.js";
|
|
9
9
|
import { TextLink } from "../../../ui/components/TextLink.js";
|
|
10
10
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
11
|
+
import { getBrandName } from "../../../core/brand-resolver.js";
|
|
11
12
|
const SUBHEADING = "mb-1 font-semibold text-[var(--appkit-color-text)]";
|
|
12
13
|
const LIST_DISC = "list-disc leading-relaxed";
|
|
13
14
|
const SECTION_BODY = "space-y-4 text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)]";
|
|
@@ -18,7 +19,7 @@ function GuideSection({ title, children, }) {
|
|
|
18
19
|
}
|
|
19
20
|
// -- Section 1: Commission structure ------------------------------------------
|
|
20
21
|
function CommissionSection() {
|
|
21
|
-
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "How it works" }),
|
|
22
|
+
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "How it works" }), _jsxs(Text, { className: "leading-relaxed", children: [getBrandName(), " deducts a platform fee from every completed order before paying out to you. The current fee rate is shown in your store Settings \u2192 Finance page and in the platform site settings. Custom commission rates are available to high-volume sellers \u2014 see the Capabilities guide for details."] })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Worked example" }), _jsxs(Stack, { textSize: "xs", className: "border border-[var(--appkit-color-border)] font-mono", gap: "xs", rounded: "lg", padding: "md", children: [_jsx(Text, { className: MONO_MUTED, children: "Order total: \u20B91,000.00" }), _jsx(Text, { className: MONO_MUTED, children: "Platform fee (e.g. 5%): \u2212 \u20B950.00" }), _jsx(Text, { className: MONO_MUTED, children: "Payment processing (Razorpay ~2%): \u2212 \u20B920.00" }), _jsx(Div, { className: "border-t border-[var(--appkit-color-border)]", padding: "t-2xs", children: _jsx(Text, { className: "font-mono text-[var(--appkit-color-text)]", size: "xs", weight: "semibold", children: "Seller receives: \u2248 \u20B9930.00" }) })] }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] mt-2", size: "xs", children: "Exact amounts depend on the current platform fee configured in Site Settings. Razorpay charges vary by payment method (UPI is lowest)." })] }), _jsx(Alert, { variant: "info", title: "Shipping is separate", children: "Shipping costs you charge buyers are passed through in full \u2014 the platform fee applies only to the product price, not the shipping amount." })] }));
|
|
22
23
|
}
|
|
23
24
|
// -- Section 2: Payout cycle --------------------------------------------------
|
|
24
25
|
const PAYOUT_STATUSES = [
|
|
@@ -62,7 +63,7 @@ const COUPON_FIELDS = [
|
|
|
62
63
|
},
|
|
63
64
|
{
|
|
64
65
|
field: "First-time user only",
|
|
65
|
-
detail:
|
|
66
|
+
detail: `Restricts redemption to buyers with no prior completed orders on ${getBrandName()}.`,
|
|
66
67
|
},
|
|
67
68
|
{
|
|
68
69
|
field: "Combine with seller coupons",
|
|
@@ -70,15 +71,15 @@ const COUPON_FIELDS = [
|
|
|
70
71
|
},
|
|
71
72
|
];
|
|
72
73
|
function CouponsSection() {
|
|
73
|
-
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Admin coupons vs store coupons" }), _jsxs(Ul, { spacing: "tight", indent: "lg", children: [_jsxs(Li, { className: LIST_DISC, children: [_jsx(Span, { weight: "bold", children: "Admin coupons" }), " \u2014 created by
|
|
74
|
+
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Admin coupons vs store coupons" }), _jsxs(Ul, { spacing: "tight", indent: "lg", children: [_jsxs(Li, { className: LIST_DISC, children: [_jsx(Span, { weight: "bold", children: "Admin coupons" }), " \u2014 created by ", getBrandName(), " and apply platform-wide (all stores). You cannot create these."] }), _jsxs(Li, { className: LIST_DISC, children: [_jsx(Span, { weight: "bold", children: "Store coupons" }), " \u2014 created by you. Apply only to purchases from your store. You control the terms."] }), _jsx(Li, { className: LIST_DISC, children: "Admin coupons and store coupons can stack if the admin coupon has \"Combine with seller coupons\" enabled. If not, only the better coupon is applied automatically." })] })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Coupon field reference" }), _jsx(Stack, { gap: "3", children: COUPON_FIELDS.map(({ field, detail }) => (_jsxs(Row, { gap: "3", children: [_jsx(Text, { className: "shrink-0 w-32 text-[var(--appkit-color-text)]", size: "sm", weight: "semibold", children: field }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] leading-relaxed", size: "sm", children: detail })] }, field))) })] })] }));
|
|
74
75
|
}
|
|
75
76
|
// -- Section 4: Promoted & featured listings ----------------------------------
|
|
76
77
|
function PromotedSection() {
|
|
77
|
-
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "isPromoted \u2014 Deals & promoted sections" }),
|
|
78
|
+
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "isPromoted \u2014 Deals & promoted sections" }), _jsxs(Text, { className: "leading-relaxed", children: ["Promoted listings appear in the Deals carousel on the homepage and in search-result promoted slots. For most stores, this flag is set by a ", getBrandName(), " admin. If you want a product promoted, contact support with the product slug and reason."] })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "isFeatured \u2014 Featured badge & priority" }), _jsxs(Text, { className: "leading-relaxed", children: ["Featured listings get a \"Featured\" badge on their listing card and receive priority ordering in search results and category pages. Like isPromoted, this is admin-controlled for most stores. Stores with the ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "manage_promoted_listings" }), " capability can self-promote from their dashboard."] })] }), _jsxs(Alert, { variant: "info", title: "Promoted placement vs sponsored listings", children: [getBrandName(), " does not currently offer paid sponsored placements \u2014 promoted and featured flags are granted editorially by the ", getBrandName(), " team based on listing quality and seller performance."] })] }));
|
|
78
79
|
}
|
|
79
80
|
// -- Section 5: Taxes notice --------------------------------------------------
|
|
80
81
|
function TaxesSection() {
|
|
81
|
-
return (_jsxs(Div, { className: SECTION_BODY, children: [
|
|
82
|
+
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Alert, { variant: "warning", title: "Seller tax responsibility", children: [getBrandName(), " is a marketplace platform. Each seller operates independently and is individually responsible for applicable taxes on their sales, including GST where required by Indian tax law.", getBrandName(), " does not collect or remit GST on your behalf. Consult a qualified tax advisor to understand your obligations based on your turnover and registration status."] }), _jsxs(Text, { className: "leading-relaxed", children: ["TCS (Tax Collected at Source) deducted by ", getBrandName(), " under Section 194-O of the Income Tax Act will be reflected in your annual Form 26AS. Your gross payouts (before TCS) are visible in the Finance \u2192 Payouts dashboard."] })] }));
|
|
82
83
|
}
|
|
83
84
|
export function StoreFinanceGuideView(_props) {
|
|
84
85
|
return (_jsxs(Stack, { gap: "lg", padding: "b-2xl", children: [_jsxs(TextLink, { variant: "bare", href: String(ROUTES.STORE.GUIDE), size: "sm", layout: "inline-flex", align: "center", gap: "sm", className: "text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)] transition-colors", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Back to Seller Guide"] }), _jsxs(Div, { children: [_jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)]", size: "2xl", weight: "bold", children: "Finance Guide" }), _jsx(Text, { className: "mt-1 text-[var(--appkit-color-text-muted)]", size: "sm", children: "Payouts, commissions, coupons, and promoted listings." })] }), _jsx(GuideSection, { title: "1. Commission structure", children: _jsx(CommissionSection, {}) }), _jsx(GuideSection, { title: "2. Payout cycle", children: _jsx(PayoutCycleSection, {}) }), _jsx(GuideSection, { title: "3. Coupons", children: _jsx(CouponsSection, {}) }), _jsx(GuideSection, { title: "4. Promoted & featured listings", children: _jsx(PromotedSection, {}) }), _jsx(GuideSection, { title: "5. Taxes", children: _jsx(TaxesSection, {}) })] }));
|
|
@@ -8,6 +8,7 @@ import { Section } from "../../../ui/components/Semantic.js";
|
|
|
8
8
|
import { TextLink } from "../../../ui/components/TextLink.js";
|
|
9
9
|
import { SellerStandingPanel } from "./SellerStandingPanel.js";
|
|
10
10
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
11
|
+
import { getBrandName } from "../../../core/brand-resolver.js";
|
|
11
12
|
const CLS_CHECK_ICON = "w-5 h-5 flex-shrink-0 text-emerald-500";
|
|
12
13
|
const CLS_SUCCESS_BOX = "rounded-xl border border-emerald-200 bg-emerald-50 dark:bg-emerald-950/30 dark:border-emerald-800 px-[var(--appkit-space-5)] py-[var(--appkit-space-4)]";
|
|
13
14
|
const CLS_SUCCESS_BODY = "text-[length:var(--appkit-text-sm)] font-medium text-emerald-700 dark:text-emerald-400";
|
|
@@ -95,7 +96,7 @@ export function StoreGuideHubView({ store }) {
|
|
|
95
96
|
const storeName = store?.storeName ?? "Your Store";
|
|
96
97
|
const steps = buildChecklist(store);
|
|
97
98
|
const completedCount = steps.filter((s) => s.done).length;
|
|
98
|
-
return (_jsxs(Stack, { gap: "xl", padding: "b-2xl", children: [_jsx(Section, { overflow: "hidden", className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "2xl", shadow: "sm", children: _jsxs(Div, { paddingX: "x-md-xl", paddingY: "y-xl", className: "[background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_60%,var(--appkit-color-secondary-400)_100%)]", children: [_jsxs(Row, { className: "mb-3", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10", surface: "default", align: "center", justify: "center", rounded: "xl", children: _jsx(BookOpen, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { color: "inverse-muted", className: "tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Seller Guide" })] }), _jsx(Heading, { color: "inverse", level: 1, className: "mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: storeName }),
|
|
99
|
+
return (_jsxs(Stack, { gap: "xl", padding: "b-2xl", children: [_jsx(Section, { overflow: "hidden", className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "2xl", shadow: "sm", children: _jsxs(Div, { paddingX: "x-md-xl", paddingY: "y-xl", className: "[background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_60%,var(--appkit-color-secondary-400)_100%)]", children: [_jsxs(Row, { className: "mb-3", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10", surface: "default", align: "center", justify: "center", rounded: "xl", children: _jsx(BookOpen, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { color: "inverse-muted", className: "tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Seller Guide" })] }), _jsx(Heading, { color: "inverse", level: 1, className: "mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: storeName }), _jsxs(Text, { color: "inverse-muted", size: "base", children: ["Everything you need to know about selling on ", getBrandName(), "."] })] }) }), _jsxs(Div, { layout: "grid", gap: "8", className: "grid-cols-1 lg:grid-cols-3", children: [_jsxs(Stack, { className: "lg:col-span-2", gap: "md", children: [_jsx(Heading, { level: 2, className: "text-[var(--appkit-color-text)]", size: "lg", weight: "semibold", children: "Guides" }), _jsx(Grid, { cols: "cardsWide", gap: "md", children: GUIDE_CARDS.map(({ Icon, title, description, href }) => (_jsxs(TextLink, { variant: "bare", href: href, rounded: "xl", layout: "flex-col", gap: "md", paddingX: "lg", paddingY: "lg", shadow: "hover-md", className: "group border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] hover:border-[var(--appkit-color-primary)] transition-all", children: [_jsxs(Row, { align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-9 h-9 [background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_100%)]", align: "center", justify: "center", rounded: "lg", children: _jsx(Icon, { className: "w-4 h-4 text-white" }) }), _jsx(Text, { className: "text-[var(--appkit-color-text)] group-hover:text-[var(--appkit-color-primary)] transition-colors", weight: "semibold", children: title })] }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] leading-relaxed", size: "sm", children: description }), _jsx(Text, { className: "text-[var(--appkit-color-primary)] mt-auto", size: "xs", weight: "medium", children: "Read guide \u2192" })] }, title))) }), _jsx(SellerStandingPanel, { standing: store?.standing, tier: store?.sellerTier, showProgress: true, heading: "Your Trusted Seller standing" })] }), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 2, className: "text-[var(--appkit-color-text)]", size: "lg", weight: "semibold", children: "Getting started" }), _jsxs(Div, { overflow: "hidden", className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "xl", children: [_jsxs(Div, { className: "px-[1.25rem] pt-[1.25rem] border-b border-[var(--appkit-color-border)]", padding: "b-md", children: [_jsxs(Row, { className: "mb-2", align: "center", justify: "between", children: [_jsxs(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "medium", children: [completedCount, " of ", steps.length, " complete"] }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", children: [Math.round((completedCount / steps.length) * 100), "%"] })] }), _jsx(Progress, { value: completedCount, max: steps.length, size: "sm", variant: "gradient" })] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)]", children: steps.map(({ label, done, href }) => (_jsxs(TextLink, { variant: "bare", href: done ? "#" : href, "aria-disabled": done, layout: "flex", align: "center", gap: "sm", paddingX: "lg", paddingY: "sm-tall", size: "sm", className: `transition-colors ${done
|
|
99
100
|
? "cursor-default"
|
|
100
101
|
: "hover:bg-[var(--appkit-color-border)]/30"}`, children: [done ? (_jsx(CheckCircle2, { className: CLS_CHECK_ICON })) : (_jsx(Circle, { className: "w-5 h-5 flex-shrink-0 text-[var(--appkit-color-text-muted)]" })), _jsx(Text, { className: `leading-snug ${done
|
|
101
102
|
? "line-through text-[var(--appkit-color-text-muted)]"
|
|
@@ -8,6 +8,7 @@ import { Li, Section, Table, Tbody, Td, Th, Thead, Tr, Ul } from "../../../ui/co
|
|
|
8
8
|
import { Stack } from "../../../ui/components/Layout.js";
|
|
9
9
|
import { TextLink } from "../../../ui/components/TextLink.js";
|
|
10
10
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
11
|
+
import { getBrandName } from "../../../core/brand-resolver.js";
|
|
11
12
|
const SUBHEADING = "mb-1 font-semibold text-[var(--appkit-color-text)]";
|
|
12
13
|
const LIST_DISC = "list-disc leading-relaxed";
|
|
13
14
|
// -- Section wrapper -----------------------------------------------------------
|
|
@@ -134,7 +135,7 @@ const MEDIA_TIPS = [
|
|
|
134
135
|
{ label: "Image ratio", detail: "1:1 (square) for card thumbnails; 4:3 works well for product detail gallery." },
|
|
135
136
|
{ label: "Minimum size", detail: "800 × 800 px recommended. Below 400 × 400 px will look blurry on Retina screens." },
|
|
136
137
|
{ label: "Max file size", detail: "50 MB per image. Prefer JPG for photos; PNG for packaging or cards with text." },
|
|
137
|
-
{ label: "Watermark", detail:
|
|
138
|
+
{ label: "Watermark", detail: `${getBrandName()} adds a watermark automatically. Do not add your own — double watermarks look unprofessional.` },
|
|
138
139
|
{ label: "Order", detail: "Drag images to reorder after upload. The first image is the card thumbnail used in search results." },
|
|
139
140
|
{ label: "Video", detail: "Add a YouTube video ID to embed an unboxing or showcase. Not a replacement for photos." },
|
|
140
141
|
];
|
|
@@ -9,6 +9,7 @@ import { Row, Stack } from "../../../ui/components/Layout.js";
|
|
|
9
9
|
import { TextLink } from "../../../ui/components/TextLink.js";
|
|
10
10
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
11
11
|
import { resolvePublishedValues } from "../../../_internal/shared/content/published-values.js";
|
|
12
|
+
import { getBrandName } from "../../../core/brand-resolver.js";
|
|
12
13
|
/* Phase 9 — one definition of the refund timeline. */
|
|
13
14
|
const { refundBusinessDaysMin: REFUND_DAYS_MIN, refundBusinessDaysMax: REFUND_DAYS_MAX } = resolvePublishedValues();
|
|
14
15
|
const SUBHEADING = "mb-1 font-semibold text-[var(--appkit-color-text)]";
|
|
@@ -96,11 +97,11 @@ function CarriersSection() {
|
|
|
96
97
|
}
|
|
97
98
|
// -- Section 4: Cancellations -------------------------------------------------
|
|
98
99
|
function CancellationsSection() {
|
|
99
|
-
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Who can cancel" }), _jsxs(Ul, { spacing: "tight", indent: "lg", children: [_jsxs(Li, { className: LIST_DISC, children: [_jsx(Span, { weight: "bold", children: "Buyer" }), " \u2014 can self-cancel any time before the order reaches SHIPPED status."] }), _jsxs(Li, { className: LIST_DISC, children: [_jsx(Span, { weight: "bold", children: "Seller (you)" }), " \u2014 can cancel any time before SHIPPED (e.g. item found damaged during packaging)."] }), _jsxs(Li, { className: LIST_DISC, children: [
|
|
100
|
+
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Who can cancel" }), _jsxs(Ul, { spacing: "tight", indent: "lg", children: [_jsxs(Li, { className: LIST_DISC, children: [_jsx(Span, { weight: "bold", children: "Buyer" }), " \u2014 can self-cancel any time before the order reaches SHIPPED status."] }), _jsxs(Li, { className: LIST_DISC, children: [_jsx(Span, { weight: "bold", children: "Seller (you)" }), " \u2014 can cancel any time before SHIPPED (e.g. item found damaged during packaging)."] }), _jsxs(Li, { className: LIST_DISC, children: [_jsxs(Span, { weight: "bold", children: [getBrandName(), " admin"] }), " \u2014 can force-cancel at any status for policy violations or unresolved disputes."] })] })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Refund timeline" }), _jsxs(Text, { className: "leading-relaxed", children: ["Cancellation triggers an automatic Razorpay refund. Funds typically reach the buyer in", _jsx(Span, { weight: "bold", children: " 3\u20137 business days" }), " depending on the payment method (UPI is usually 1\u20132 business days)."] })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Already shipped?" }), _jsx(Text, { className: "leading-relaxed", children: "If you have already dispatched the item but the order is still PROCESSING (tracking not yet entered), enter the tracking number and mark it SHIPPED first \u2014 then contact the buyer via order notes. Do not cancel after shipping; work with the buyer to return the item through the normal return flow." })] }), _jsx(Alert, { variant: "warning", title: "Repeated cancellations affect your rating", children: "Cancelling more than 5% of your orders per month will reduce your store rating and may result in listing restrictions. Only cancel if you genuinely cannot fulfil \u2014 never as a price-change workaround." })] }));
|
|
100
101
|
}
|
|
101
102
|
// -- Section 5: Return requests -----------------------------------------------
|
|
102
103
|
function ReturnsSection() {
|
|
103
|
-
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "When a buyer can raise a return" }), _jsxs(Text, { className: "leading-relaxed", children: ["Buyers can open a return request within the ", _jsx(Span, { weight: "bold", children: "return window" }), " after the order is marked DELIVERED. The default return window is 7 days. Stores with the ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "extended_return_window" }), " capability can offer 30 days."] })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "What you see" }), _jsxs(Ul, { spacing: "tight", indent: "lg", children: [_jsx(Li, { className: LIST_DISC, children: "The buyer's reason for return (pre-defined category + optional description)." }), _jsx(Li, { className: LIST_DISC, children: "Photos of the item as received." }),
|
|
104
|
+
return (_jsxs(Div, { className: SECTION_BODY, children: [_jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "When a buyer can raise a return" }), _jsxs(Text, { className: "leading-relaxed", children: ["Buyers can open a return request within the ", _jsx(Span, { weight: "bold", children: "return window" }), " after the order is marked DELIVERED. The default return window is 7 days. Stores with the ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "extended_return_window" }), " capability can offer 30 days."] })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "What you see" }), _jsxs(Ul, { spacing: "tight", indent: "lg", children: [_jsx(Li, { className: LIST_DISC, children: "The buyer's reason for return (pre-defined category + optional description)." }), _jsx(Li, { className: LIST_DISC, children: "Photos of the item as received." }), _jsxs(Li, { className: LIST_DISC, children: ["A 3-day SLA to respond before ", getBrandName(), " mediates."] })] })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Accepting a return" }), _jsx(Text, { className: "leading-relaxed", children: "Click \"Accept return\" in the order detail. The buyer will receive a return shipping label or instructions. Once the item is received by you and you confirm it, the refund is triggered automatically via Razorpay. The payout for this order is withheld." })] }), _jsxs(Div, { children: [_jsx(Text, { className: SUBHEADING, children: "Rejecting a return" }), _jsxs(Text, { className: "leading-relaxed", children: ["You can reject a return request with a reason. If the buyer disagrees, they can escalate to ", getBrandName(), " support.", getBrandName(), " will mediate \u2014 if the evidence supports the buyer, the return will be approved regardless of your rejection. Unjustified rejections count against your seller rating."] })] })] }));
|
|
104
105
|
}
|
|
105
106
|
// -- Section 6: Auction orders ------------------------------------------------
|
|
106
107
|
function AuctionOrdersSection() {
|
|
@@ -110,7 +111,7 @@ function AuctionOrdersSection() {
|
|
|
110
111
|
const FAQS = [
|
|
111
112
|
{
|
|
112
113
|
q: "What if the buyer claims non-delivery but tracking shows delivered?",
|
|
113
|
-
a:
|
|
114
|
+
a: `Do not cancel immediately. Ask the buyer to check with neighbours and at the nearest post office or courier depot. If after 5 days the parcel is still missing, contact the carrier to file a trace. If the trace confirms loss, you may need to refund — document everything and contact ${getBrandName()} support if needed.`,
|
|
114
115
|
},
|
|
115
116
|
{
|
|
116
117
|
q: "Can I cancel an order after I've already shipped it?",
|
|
@@ -122,7 +123,7 @@ const FAQS = [
|
|
|
122
123
|
},
|
|
123
124
|
{
|
|
124
125
|
q: "How do I handle a partial order if I sold multiple items?",
|
|
125
|
-
a:
|
|
126
|
+
a: `Currently each product listing creates a separate order. If you sold a bundle, the bundle order must be fulfilled as a single shipment. If one item in a bundle is unavailable, contact the buyer and ${getBrandName()} support — partial fulfilment is not supported in the standard flow.`,
|
|
126
127
|
},
|
|
127
128
|
];
|
|
128
129
|
function CommonFAQs() {
|
|
@@ -8,6 +8,7 @@ import { Heading, Span, Text } from "../../../ui/components/Typography.js";
|
|
|
8
8
|
import { Stack } from "../../../ui/components/Layout.js";
|
|
9
9
|
import { TextLink } from "../../../ui/components/TextLink.js";
|
|
10
10
|
import { ROUTES } from "../../../next/routing/route-map.js";
|
|
11
|
+
import { getBrandSupportEmail } from "../../../core/brand-resolver.js";
|
|
11
12
|
const SUBHEADING = "mb-1 font-semibold text-[var(--appkit-color-text)]";
|
|
12
13
|
const LIST_DISC = "list-disc leading-relaxed";
|
|
13
14
|
const SECTION_BODY = "space-y-4 text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)]";
|
|
@@ -52,7 +53,7 @@ function VisibilityStatesSection() {
|
|
|
52
53
|
},
|
|
53
54
|
{
|
|
54
55
|
state: "Suspended",
|
|
55
|
-
note:
|
|
56
|
+
note: `Admin action. All listings hidden. Store profile shows 'Unavailable' banner. Contact ${getBrandSupportEmail()} to resolve.`,
|
|
56
57
|
},
|
|
57
58
|
];
|
|
58
59
|
return (_jsx(Stack, { gap: "3", children: STATES.map(({ state, note }) => (_jsxs(Row, { textSize: "sm", gap: "3", children: [_jsx(Text, { className: "shrink-0 w-32 text-[var(--appkit-color-text)]", weight: "semibold", children: state }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] leading-relaxed", children: note })] }, state))) }));
|
|
@@ -152,7 +152,7 @@ export declare const supportTicketFirestoreSchema: z.ZodObject<{
|
|
|
152
152
|
status: "open" | "closed" | "in_progress" | "waiting_on_user" | "resolved";
|
|
153
153
|
id: string;
|
|
154
154
|
description: string;
|
|
155
|
-
category: "general" | "
|
|
155
|
+
category: "general" | "order_issue" | "billing_payment" | "account" | "listing_dispute" | "scam_report" | "refund_request" | "auction_dispute" | "store_change_request" | "order_modification_request" | "unban_request";
|
|
156
156
|
createdAt: string | Date | z.objectOutputType<{
|
|
157
157
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
158
158
|
}, z.ZodTypeAny, "passthrough">;
|
|
@@ -195,7 +195,7 @@ export declare const supportTicketFirestoreSchema: z.ZodObject<{
|
|
|
195
195
|
status: "open" | "closed" | "in_progress" | "waiting_on_user" | "resolved";
|
|
196
196
|
id: string;
|
|
197
197
|
description: string;
|
|
198
|
-
category: "general" | "
|
|
198
|
+
category: "general" | "order_issue" | "billing_payment" | "account" | "listing_dispute" | "scam_report" | "refund_request" | "auction_dispute" | "store_change_request" | "order_modification_request" | "unban_request";
|
|
199
199
|
createdAt: string | Date | z.objectInputType<{
|
|
200
200
|
toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
|
|
201
201
|
}, z.ZodTypeAny, "passthrough">;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* 1. Almost no small seller will create a Meta app and mint a system user
|
|
13
13
|
* token. A setting nobody can complete is a feature nobody has.
|
|
14
|
-
* 2. It made
|
|
14
|
+
* 2. It made {getBrandName()} the custodian of a live Meta credential per seller.
|
|
15
15
|
* `StoreRepository.mapDoc` decrypted that token on EVERY read, so any
|
|
16
16
|
* code path that forgot to project was handling plaintext — a gap
|
|
17
17
|
* CLAUDE.md flags as deserving its own session.
|
|
@@ -13,7 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
13
13
|
*
|
|
14
14
|
* 1. Almost no small seller will create a Meta app and mint a system user
|
|
15
15
|
* token. A setting nobody can complete is a feature nobody has.
|
|
16
|
-
* 2. It made
|
|
16
|
+
* 2. It made {getBrandName()} the custodian of a live Meta credential per seller.
|
|
17
17
|
* `StoreRepository.mapDoc` decrypted that token on EVERY read, so any
|
|
18
18
|
* code path that forgot to project was handling plaintext — a gap
|
|
19
19
|
* CLAUDE.md flags as deserving its own session.
|
|
@@ -48,6 +48,7 @@ import { Toggle } from "../../../ui/components/Toggle.js";
|
|
|
48
48
|
import { useToast } from "../../../ui/components/toast-context.js";
|
|
49
49
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
50
50
|
import { formatCurrency } from "../../../utils/number.formatter.js";
|
|
51
|
+
import { getBrandName } from "../../../core/brand-resolver.js";
|
|
51
52
|
export function SellerWhatsAppSettingsView({ subscribed, monthlyFee, currency = "INR", channelEnabled, onToggleSubscription, hasCapability = true, }) {
|
|
52
53
|
const [isOn, setIsOn] = useState(subscribed);
|
|
53
54
|
const [saving, setSaving] = useState(false);
|
|
@@ -79,5 +80,5 @@ export function SellerWhatsAppSettingsView({ subscribed, monthlyFee, currency =
|
|
|
79
80
|
setSaving(false);
|
|
80
81
|
}
|
|
81
82
|
};
|
|
82
|
-
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" }),
|
|
83
|
+
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" }), _jsxs(Text, { variant: "secondary", className: "mb-6", size: "sm", children: ["Send your buyers order updates on WhatsApp \u2014 confirmed, shipped, delivered \u2014 from", getBrandName(), "'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."] }), _jsxs(Text, { color: "muted", size: "sm", children: ["Messages come from ", getBrandName(), "'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." })] })] }) }));
|
|
83
84
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { normalizeError } from "../../../errors/normalize.js";
|
|
2
2
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
3
3
|
import { getDefaultCurrencySymbol, getDefaultLocale, } from "../../../core/baseline-resolver.js";
|
|
4
|
+
import { formatMoney } from "../../../utils/number.formatter.js";
|
|
4
5
|
const META_GRAPH_BASE = "https://graph.facebook.com/v20.0";
|
|
5
6
|
export function buildCheckoutMessageURL(input) {
|
|
6
7
|
const { waNumber, cart, total, address, isPreorder = false } = input;
|
|
@@ -241,8 +242,10 @@ export async function syncProductsToCatalog(input) {
|
|
|
241
242
|
export function buildPurchaseAnnouncementMessage(input) {
|
|
242
243
|
const { buyerName, firstItemName, additionalItemCount, totalAmount, orderId } = input;
|
|
243
244
|
const extra = additionalItemCount > 0 ? ` + ${additionalItemCount} more item${additionalItemCount > 1 ? "s" : ""}` : "";
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
// `formatMoney` groups and places the symbol from the market profile; the
|
|
246
|
+
// intermediate `toLocaleString("en-IN")` this replaced hardcoded the locale.
|
|
247
|
+
const amount = formatMoney(totalAmount);
|
|
248
|
+
return `🛍️ New order! ${buyerName} purchased ${firstItemName}${extra} for ${amount}. Order #${orderId}`;
|
|
246
249
|
}
|
|
247
250
|
/**
|
|
248
251
|
* Build a wa.me share link with a pre-filled message.
|
package/dist/index.d.ts
CHANGED
|
@@ -286,7 +286,6 @@ export { isNumeric } from "./validation/input.validator";
|
|
|
286
286
|
export { isRequired } from "./validation/input.validator";
|
|
287
287
|
export { isValidEmail } from "./validation/email.validator";
|
|
288
288
|
export { isValidEmailDomain } from "./validation/email.validator";
|
|
289
|
-
export { isValidIndianPincode } from "./validation/phone.validator";
|
|
290
289
|
export { isValidMobile } from "./validation/phone.validator";
|
|
291
290
|
export { isValidPhone } from "./validation/phone.validator";
|
|
292
291
|
export { isValidUrl } from "./validation/url.validator";
|
package/dist/index.js
CHANGED
|
@@ -447,8 +447,6 @@ export { isValidEmail } from "./validation/email.validator.js";
|
|
|
447
447
|
// isValidEmailDomain - Shared export for is valid email domain.
|
|
448
448
|
export { isValidEmailDomain } from "./validation/email.validator.js";
|
|
449
449
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
450
|
-
// isValidIndianPincode - Shared export for is valid indian pincode.
|
|
451
|
-
export { isValidIndianPincode } from "./validation/phone.validator.js";
|
|
452
450
|
// [CLIENT-SSR]-Runs in both SSR and browser â€" React component or hook that does not depend on browser-only APIs.
|
|
453
451
|
// isValidMobile - Shared export for is valid mobile.
|
|
454
452
|
export { isValidMobile } from "./validation/phone.validator.js";
|
|
@@ -25,6 +25,17 @@ export declare class ManualPaymentProvider extends IPaymentProvider {
|
|
|
25
25
|
createOrder(amount: number, currency: string, metadata?: Record<string, JsonValue>): Promise<PaymentOrder>;
|
|
26
26
|
/** Manual payments have no webhook source — always false. */
|
|
27
27
|
verifyWebhook(_payload: string, _signature: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Manual payment has no gateway callback to verify, so there is nothing that
|
|
30
|
+
* could make this true.
|
|
31
|
+
*
|
|
32
|
+
* 🛑 `false`, never `true`. This is the check that decides whether a payment
|
|
33
|
+
* is genuine; a provider that cannot verify must REJECT, not wave through.
|
|
34
|
+
* Returning `true` here would mean a forged gateway callback is accepted on
|
|
35
|
+
* any install where the gateway flag is off — which is exactly the install
|
|
36
|
+
* where nobody is watching for one.
|
|
37
|
+
*/
|
|
38
|
+
verifyCheckoutSignature(_payload: Record<string, string>, _secret: string): boolean;
|
|
28
39
|
/**
|
|
29
40
|
* Manual payments are considered captured the moment the buyer's
|
|
30
41
|
* reference/proof is recorded — there is no separate auth+capture step.
|