sahab-utils 1.7.0 → 1.7.2
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/constants/types.d.ts +19 -0
- package/dist/ts/gifts.d.ts +11 -0
- package/dist/ts/gifts.js +23 -3
- package/dist/ts/loyalty.d.ts +13 -0
- package/dist/ts/loyalty.js +20 -0
- package/package.json +35 -35
|
@@ -655,6 +655,17 @@ export type Discount = {
|
|
|
655
655
|
gift?: {
|
|
656
656
|
giftId: string;
|
|
657
657
|
};
|
|
658
|
+
/**
|
|
659
|
+
* Which environment this offer belongs to. Absent means prod, matching every offer created
|
|
660
|
+
* before this field existed — mirrors `Client.isTest`/`Order.isTest`/`Provider.isTest`.
|
|
661
|
+
*
|
|
662
|
+
* Only meaningful for an offer that doesn't otherwise resolve to one environment (see
|
|
663
|
+
* `loyaltyOfferMatchesTestMode` in `ts/loyalty.ts`): a provider-scoped offer's environment is
|
|
664
|
+
* really the provider's own `isTest`, and a gift voucher's is really the gift's — this field
|
|
665
|
+
* is what lets a genuinely Sahab-wide, no-single-provider offer (or a promo code with no
|
|
666
|
+
* gift/provider scope at all) still declare one explicitly.
|
|
667
|
+
*/
|
|
668
|
+
isTest?: boolean;
|
|
658
669
|
};
|
|
659
670
|
/**
|
|
660
671
|
* "Buy N washes, get one free" configuration, carried on the `Discount` that represents the
|
|
@@ -762,6 +773,12 @@ export type Gift = {
|
|
|
762
773
|
expiresAt?: TimestampType;
|
|
763
774
|
notifiedAt?: TimestampType;
|
|
764
775
|
notifyChannel?: GiftNotifyChannel;
|
|
776
|
+
/**
|
|
777
|
+
* Whether the giver was a test client at purchase — mirrors `Client.isTest`. Stamped once at
|
|
778
|
+
* creation and never recomputed, same reasoning as `expiresAt`: a client's test/prod status
|
|
779
|
+
* changing later must not retroactively move a gift someone already paid for.
|
|
780
|
+
*/
|
|
781
|
+
isTest?: boolean;
|
|
765
782
|
};
|
|
766
783
|
/** One line of a gifted basket, priced when the gift was bought. */
|
|
767
784
|
export type GiftItem = {
|
|
@@ -800,6 +817,8 @@ export type GiftConfig = {
|
|
|
800
817
|
* Bounded on read: a 0 would expire every gift the moment it was sold.
|
|
801
818
|
*/
|
|
802
819
|
giftExpiryMonths?: number;
|
|
820
|
+
/** Charged when the giver adds a personal message to the SMS. Defaults to `GIFT_MESSAGE_SURCHARGE_DEFAULT` when unset. */
|
|
821
|
+
giftMessageSurcharge?: number;
|
|
803
822
|
};
|
|
804
823
|
export type OrderReview = {
|
|
805
824
|
ratingId: string;
|
package/dist/ts/gifts.d.ts
CHANGED
|
@@ -17,6 +17,17 @@ export declare const MIN_GIFT_EXPIRY_MONTHS = 1;
|
|
|
17
17
|
export declare const MAX_GIFT_EXPIRY_MONTHS = 36;
|
|
18
18
|
/** What the giver pays: every line in the basket. */
|
|
19
19
|
export declare function giftTotal(items?: GiftItem[]): number;
|
|
20
|
+
/** A message longer than this spans more than one Arabic (UCS-2) SMS segment on its own. */
|
|
21
|
+
export declare const GIFT_MESSAGE_MAX_CHARS = 70;
|
|
22
|
+
/** Used when the back office has not set `GiftConfig.giftMessageSurcharge`. */
|
|
23
|
+
export declare const GIFT_MESSAGE_SURCHARGE_DEFAULT = 3;
|
|
24
|
+
/**
|
|
25
|
+
* What a personal message, if any, adds to the gift's price.
|
|
26
|
+
*
|
|
27
|
+
* Zero for a blank or whitespace-only message — writing nothing costs nothing. Otherwise the
|
|
28
|
+
* back-office amount if one is configured, else the default, same shape as `giftExpiryMonths`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function giftMessageSurcharge(message?: string, config?: GiftConfig): number;
|
|
20
31
|
/** The gifted service ids. */
|
|
21
32
|
export declare function giftServiceIds(gift?: Pick<Gift, 'items'>): string[];
|
|
22
33
|
/** The gifted addon ids. */
|
package/dist/ts/gifts.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MAX_GIFT_EXPIRY_MONTHS = exports.MIN_GIFT_EXPIRY_MONTHS = exports.DEFAULT_GIFT_EXPIRY_MONTHS = void 0;
|
|
3
|
+
exports.GIFT_MESSAGE_SURCHARGE_DEFAULT = exports.GIFT_MESSAGE_MAX_CHARS = exports.MAX_GIFT_EXPIRY_MONTHS = exports.MIN_GIFT_EXPIRY_MONTHS = exports.DEFAULT_GIFT_EXPIRY_MONTHS = void 0;
|
|
4
4
|
exports.giftTotal = giftTotal;
|
|
5
|
+
exports.giftMessageSurcharge = giftMessageSurcharge;
|
|
5
6
|
exports.giftServiceIds = giftServiceIds;
|
|
6
7
|
exports.giftAddonIds = giftAddonIds;
|
|
7
8
|
exports.giftExpiryMonths = giftExpiryMonths;
|
|
@@ -36,6 +37,22 @@ exports.MAX_GIFT_EXPIRY_MONTHS = 36;
|
|
|
36
37
|
function giftTotal(items) {
|
|
37
38
|
return (items !== null && items !== void 0 ? items : []).reduce((sum, item) => sum + (0, utils_1.preventNaN)(item === null || item === void 0 ? void 0 : item.price), 0);
|
|
38
39
|
}
|
|
40
|
+
/** A message longer than this spans more than one Arabic (UCS-2) SMS segment on its own. */
|
|
41
|
+
exports.GIFT_MESSAGE_MAX_CHARS = 70;
|
|
42
|
+
/** Used when the back office has not set `GiftConfig.giftMessageSurcharge`. */
|
|
43
|
+
exports.GIFT_MESSAGE_SURCHARGE_DEFAULT = 3;
|
|
44
|
+
/**
|
|
45
|
+
* What a personal message, if any, adds to the gift's price.
|
|
46
|
+
*
|
|
47
|
+
* Zero for a blank or whitespace-only message — writing nothing costs nothing. Otherwise the
|
|
48
|
+
* back-office amount if one is configured, else the default, same shape as `giftExpiryMonths`.
|
|
49
|
+
*/
|
|
50
|
+
function giftMessageSurcharge(message, config) {
|
|
51
|
+
if (!(message !== null && message !== void 0 ? message : '').trim().length)
|
|
52
|
+
return 0;
|
|
53
|
+
const raw = config === null || config === void 0 ? void 0 : config.giftMessageSurcharge;
|
|
54
|
+
return raw === undefined || raw === null || !isFinite(raw) || raw < 0 ? exports.GIFT_MESSAGE_SURCHARGE_DEFAULT : raw;
|
|
55
|
+
}
|
|
39
56
|
/** The gifted service ids. */
|
|
40
57
|
function giftServiceIds(gift) {
|
|
41
58
|
var _a;
|
|
@@ -105,8 +122,10 @@ function giftDiscount(gift, dates) {
|
|
|
105
122
|
return {
|
|
106
123
|
isPercentage: true,
|
|
107
124
|
amount: 100,
|
|
108
|
-
// Bounds the discount to the
|
|
109
|
-
|
|
125
|
+
// Bounds the discount to what the basket is actually worth, whatever the recipient
|
|
126
|
+
// orders — never to `gift.amount`, which can include a message surcharge that bought
|
|
127
|
+
// no redeemable service and must not inflate how much the voucher can discount.
|
|
128
|
+
maxAmount: giftTotal(gift.items),
|
|
110
129
|
clientIds: gift.recipientUid ? [gift.recipientUid] : [],
|
|
111
130
|
providerUids: gift.providerUid ? [gift.providerUid] : [],
|
|
112
131
|
servicesIds: giftServiceIds(gift),
|
|
@@ -121,6 +140,7 @@ function giftDiscount(gift, dates) {
|
|
|
121
140
|
validFrom: dates.validFrom,
|
|
122
141
|
validTo: dates.validTo,
|
|
123
142
|
gift: { giftId: gift.giftId },
|
|
143
|
+
isTest: gift.isTest,
|
|
124
144
|
};
|
|
125
145
|
}
|
|
126
146
|
/** Every service and addon line on an order, flattened. */
|
package/dist/ts/loyalty.d.ts
CHANGED
|
@@ -20,6 +20,19 @@ export declare function isProviderLoyaltyOffer(offer?: Discount): boolean;
|
|
|
20
20
|
export declare function isSahabLoyaltyOffer(offer?: Discount): boolean;
|
|
21
21
|
/** Whether an offer's provider scope admits a given provider. */
|
|
22
22
|
export declare function loyaltyCoversProvider(offer: Discount | undefined, providerUid?: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Whether a loyalty offer belongs to the given test/prod environment.
|
|
25
|
+
*
|
|
26
|
+
* An offer's environment is decided in priority order:
|
|
27
|
+
* 1. `offer.isTest`, when the offer declares one explicitly.
|
|
28
|
+
* 2. `providerIsTest`, for an offer scoped to exactly one provider — resolving that (a
|
|
29
|
+
* Firestore read on the provider) is the caller's job, since this stays framework-agnostic.
|
|
30
|
+
* Pass it only when `loyaltyProviderScope(offer)` resolves to a single provider.
|
|
31
|
+
* 3. Prod, otherwise — a genuinely network-wide offer with no explicit flag is exactly the
|
|
32
|
+
* shape every offer had before this field existed, so it stays visible to prod and hidden
|
|
33
|
+
* from test rather than leaking into both.
|
|
34
|
+
*/
|
|
35
|
+
export declare function loyaltyOfferMatchesTestMode(offer: Discount | undefined, isTest: boolean, providerIsTest?: boolean): boolean;
|
|
23
36
|
/**
|
|
24
37
|
* Whether a finished order earns a stamp.
|
|
25
38
|
*
|
package/dist/ts/loyalty.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.loyaltyProviderScope = loyaltyProviderScope;
|
|
|
4
4
|
exports.isProviderLoyaltyOffer = isProviderLoyaltyOffer;
|
|
5
5
|
exports.isSahabLoyaltyOffer = isSahabLoyaltyOffer;
|
|
6
6
|
exports.loyaltyCoversProvider = loyaltyCoversProvider;
|
|
7
|
+
exports.loyaltyOfferMatchesTestMode = loyaltyOfferMatchesTestMode;
|
|
7
8
|
exports.orderEarnsLoyaltyStamp = orderEarnsLoyaltyStamp;
|
|
8
9
|
exports.giftEarnsLoyaltyStamp = giftEarnsLoyaltyStamp;
|
|
9
10
|
exports.emptyLoyaltyProgress = emptyLoyaltyProgress;
|
|
@@ -67,6 +68,25 @@ function loyaltyCoversProvider(offer, providerUid) {
|
|
|
67
68
|
return true;
|
|
68
69
|
return !!providerUid && scope.includes(providerUid);
|
|
69
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether a loyalty offer belongs to the given test/prod environment.
|
|
73
|
+
*
|
|
74
|
+
* An offer's environment is decided in priority order:
|
|
75
|
+
* 1. `offer.isTest`, when the offer declares one explicitly.
|
|
76
|
+
* 2. `providerIsTest`, for an offer scoped to exactly one provider — resolving that (a
|
|
77
|
+
* Firestore read on the provider) is the caller's job, since this stays framework-agnostic.
|
|
78
|
+
* Pass it only when `loyaltyProviderScope(offer)` resolves to a single provider.
|
|
79
|
+
* 3. Prod, otherwise — a genuinely network-wide offer with no explicit flag is exactly the
|
|
80
|
+
* shape every offer had before this field existed, so it stays visible to prod and hidden
|
|
81
|
+
* from test rather than leaking into both.
|
|
82
|
+
*/
|
|
83
|
+
function loyaltyOfferMatchesTestMode(offer, isTest, providerIsTest) {
|
|
84
|
+
if (typeof (offer === null || offer === void 0 ? void 0 : offer.isTest) === 'boolean')
|
|
85
|
+
return offer.isTest === isTest;
|
|
86
|
+
if (typeof providerIsTest === 'boolean')
|
|
87
|
+
return providerIsTest === isTest;
|
|
88
|
+
return !isTest;
|
|
89
|
+
}
|
|
70
90
|
/**
|
|
71
91
|
* Whether a finished order earns a stamp.
|
|
72
92
|
*
|
package/package.json
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
2
|
+
"name": "sahab-utils",
|
|
3
|
+
"version": "1.7.2",
|
|
4
|
+
"description": "This package contains types and utils functions",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"deploy": "npm run build && npm publish",
|
|
10
|
+
"test": "npm run build && node tests/loyalty.test.js && node tests/gifts.test.js"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/SahabApp/sahab-utils.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"sahab"
|
|
18
|
+
],
|
|
19
|
+
"author": "Sahab",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/SahabApp/sahab-utils/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/SahabApp/sahab-utils#readme",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/lodash": "^4.17.24",
|
|
30
|
+
"typescript": "^6.0.2"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"firebase": "^10.7.1",
|
|
34
|
+
"lodash": "^4.17.21"
|
|
35
|
+
},
|
|
36
|
+
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538"
|
|
37
37
|
}
|