@cloudcommerce/app-loyalty-points 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +1 -0
  3. package/LICENSE.md +230 -0
  4. package/README.md +1 -0
  5. package/events.js +1 -0
  6. package/lib/functions-lib/get-program-id.d.ts +5 -0
  7. package/lib/functions-lib/get-program-id.js +20 -0
  8. package/lib/functions-lib/get-program-id.js.map +1 -0
  9. package/lib/functions-lib/handle-loyalty-points-event.d.ts +3 -0
  10. package/lib/functions-lib/handle-loyalty-points-event.js +172 -0
  11. package/lib/functions-lib/handle-loyalty-points-event.js.map +1 -0
  12. package/lib/index.d.ts +1 -0
  13. package/lib/index.js +2 -0
  14. package/lib/index.js.map +1 -0
  15. package/lib/loyalty-create-transaction.d.ts +76 -0
  16. package/lib/loyalty-create-transaction.js +210 -0
  17. package/lib/loyalty-create-transaction.js.map +1 -0
  18. package/lib/loyalty-list-payments.d.ts +3 -0
  19. package/lib/loyalty-list-payments.js +44 -0
  20. package/lib/loyalty-list-payments.js.map +1 -0
  21. package/lib/loyalty-points-events.d.ts +4 -0
  22. package/lib/loyalty-points-events.js +28 -0
  23. package/lib/loyalty-points-events.js.map +1 -0
  24. package/lib/loyalty-points.d.ts +77 -0
  25. package/lib/loyalty-points.js +12 -0
  26. package/lib/loyalty-points.js.map +1 -0
  27. package/package.json +32 -0
  28. package/src/functions-lib/get-program-id.ts +23 -0
  29. package/src/functions-lib/handle-loyalty-points-event.ts +207 -0
  30. package/src/index.ts +1 -0
  31. package/src/loyalty-create-transaction.ts +241 -0
  32. package/src/loyalty-list-payments.ts +55 -0
  33. package/src/loyalty-points-events.ts +41 -0
  34. package/src/loyalty-points.ts +12 -0
  35. package/tsconfig.json +6 -0
@@ -0,0 +1,210 @@
1
+ import api from '@cloudcommerce/api';
2
+ import { getFirestore } from 'firebase-admin/firestore';
3
+ import logger from 'firebase-functions/logger';
4
+ import getProgramId from './functions-lib/get-program-id.js';
5
+
6
+ const collectionRef = getFirestore().collection('billedPoints');
7
+ const updateTransactionStatus = (orderId) => {
8
+ setTimeout(async () => {
9
+ const order = (await api.get(`orders/${orderId}`)).data;
10
+ // appSdk.apiRequest(storeId, `/orders/${orderId}.json`).then(({ response }) => {
11
+ const { transactions } = order;
12
+ if (transactions) {
13
+ const transaction = transactions.find((transactionFound) => {
14
+ return transactionFound.payment_method.code === 'loyalty_points';
15
+ });
16
+ if (transaction) {
17
+ const bodyPaymentHistory = {
18
+ transaction_id: transaction._id,
19
+ date_time: new Date().toISOString(),
20
+ status: 'paid',
21
+ customer_notified: true,
22
+ }; // TODO: incompatible type=> amount and status;
23
+ await api.post(`orders/${orderId}/payments_history`, bodyPaymentHistory);
24
+ }
25
+ }
26
+ }, 500);
27
+ };
28
+ const handleUpdatedPoints = (
29
+ endpoint, // TODO: endpoint type not string compatible
30
+ body,
31
+ orderId,
32
+ usedPointsEntries,
33
+ timeout = 400,
34
+ isLastRequest = false,
35
+ ) => {
36
+ setTimeout(async () => {
37
+ await api.patch(endpoint, body);
38
+ // appSdk.apiRequest(storeId, endpoint, 'PATCH', data).then(() => {
39
+ logger.log(`#(App Loyalty Points): ${orderId} ${endpoint} => ${JSON.stringify(body)}`);
40
+ if (isLastRequest) {
41
+ updateTransactionStatus(orderId);
42
+ collectionRef.doc(orderId).set({ usedPointsEntries });
43
+ }
44
+ // });
45
+ }, timeout);
46
+ };
47
+
48
+ export default async (appData) => {
49
+ const { application, storeId } = appData;
50
+ const { params } = appData;
51
+ // app configured options
52
+ const appConfig = { ...application.data, ...application.hidden_data };
53
+ const { lang, amount } = params;
54
+ // setup required `transaction` response object
55
+ const transaction = {
56
+ amount: amount.total,
57
+ };
58
+ if (params.payment_method.code === 'loyalty_points') {
59
+ const pointsApplied = params.loyalty_points_applied;
60
+ const programsRules = appConfig.programs_rules;
61
+ if (pointsApplied && Array.isArray(programsRules) && programsRules.length) {
62
+ // for (const programId in pointsApplied) {
63
+ Object.keys(pointsApplied).forEach((programId) => {
64
+ const pointsValue = pointsApplied[programId];
65
+ if (pointsValue > 0) {
66
+ const programRule = programsRules.find((programRuleFound, index) => {
67
+ if (programRuleFound) {
68
+ programRuleFound.program_id = getProgramId(programRuleFound, index);
69
+ return programId === programRuleFound.program_id;
70
+ }
71
+ return false;
72
+ });
73
+ if (programRule) {
74
+ const ratio = programRule.ratio || 1;
75
+ transaction.loyalty_points = {
76
+ name: programRule.name,
77
+ program_id: programRule.program_id,
78
+ ratio,
79
+ points_value: programRule.max_points < pointsValue
80
+ ? programRule.max_points
81
+ : pointsValue,
82
+ };
83
+ transaction.amount = pointsValue * ratio;
84
+ }
85
+ }
86
+ });
87
+ }
88
+ }
89
+ if (transaction.amount) {
90
+ const loyaltyPoints = transaction.loyalty_points;
91
+ const customerId = params.buyer.customer_id;
92
+ const orderId = params.order_id;
93
+ const usedPointsEntries = [];
94
+ try {
95
+ const customer = (await api.get(`customers/${customerId}`)).data;
96
+ // return appSdk.apiRequest(storeId, `/customers/${customerId}.json`)
97
+ // .then(({ response }) => {
98
+ const pointsEntries = customer.loyalty_points_entries;
99
+ let pointsToConsume = loyaltyPoints?.points_value;
100
+ if (pointsEntries && pointsToConsume) {
101
+ pointsEntries.sort((a, b) => {
102
+ let value = 0;
103
+ if (a.valid_thru && b.valid_thru) {
104
+ if (a.valid_thru < b.valid_thru) {
105
+ value = -1;
106
+ } else if (a.valid_thru > b.valid_thru) {
107
+ value = 1;
108
+ }
109
+ }
110
+ return value;
111
+ });
112
+ for (let i = 0; i < pointsEntries.length; i++) {
113
+ if (pointsToConsume <= 0) {
114
+ break;
115
+ }
116
+ const pointsEntry = pointsEntries[i];
117
+ if (pointsEntry.program_id === loyaltyPoints?.program_id) {
118
+ const validThru = pointsEntry.valid_thru;
119
+ const activePoints = pointsEntry.active_points;
120
+ if (activePoints > 0 && (!validThru || new Date(validThru).getTime() >= Date.now())) {
121
+ const pointsDiff = activePoints - pointsToConsume;
122
+ if (pointsDiff > 0 && pointsDiff < 0.01) {
123
+ pointsToConsume = activePoints;
124
+ }
125
+ if (pointsToConsume >= activePoints) {
126
+ pointsToConsume -= activePoints;
127
+ pointsEntry.active_points = 0;
128
+ if (pointsToConsume < 0.02) {
129
+ pointsToConsume = 0;
130
+ }
131
+ } else {
132
+ pointsEntry.active_points -= pointsToConsume;
133
+ pointsToConsume = 0;
134
+ }
135
+ usedPointsEntries.push({
136
+ ...pointsEntry,
137
+ original_active_points: activePoints,
138
+ });
139
+ }
140
+ }
141
+ }
142
+ if (pointsToConsume <= 0) {
143
+ if (usedPointsEntries.length <= 3) {
144
+ usedPointsEntries.forEach((pointsEntry, i) => {
145
+ /* Obs:
146
+ Store api v2 => loyalty_points_entries do not have id,
147
+ but change can be done by index
148
+ */
149
+ const endpoint = `customers/${customerId}/loyalty_points_entries/${i}`;
150
+ const body = {
151
+ active_points: pointsEntry.active_points,
152
+ };
153
+ handleUpdatedPoints(endpoint, body, orderId, usedPointsEntries, (i + 1) * 400, i + 1 === usedPointsEntries.length);
154
+ });
155
+ } else {
156
+ const endpoint = `customers/${customerId}`;
157
+ const body = {
158
+ loyalty_points_entries: pointsEntries,
159
+ };
160
+ handleUpdatedPoints(endpoint, body, orderId, usedPointsEntries, 400, true);
161
+ }
162
+ }
163
+ }
164
+ transaction.status = {
165
+ current: pointsToConsume && pointsToConsume <= 0 ? 'authorized' : 'unauthorized',
166
+ };
167
+ return {
168
+ status: 200,
169
+ redirect_to_payment: false,
170
+ transaction,
171
+ };
172
+ } catch (error) {
173
+ // try to debug request error
174
+ const errCode = 'POINTS_TRANSACTION_ERR';
175
+ let { message } = error;
176
+ const err = {
177
+ message: `CREATE_TRANSACTION_ERR #${storeId} - ${orderId} => ${message}`,
178
+ payment: '',
179
+ status: 0,
180
+ response: '',
181
+ usedPointsEntries,
182
+ };
183
+ if (error.response) {
184
+ const { status, data } = error.response;
185
+ err.status = status;
186
+ if (status !== 401 && status !== 403) {
187
+ if (typeof data === 'object' && data) {
188
+ err.response = JSON.stringify(data);
189
+ } else {
190
+ err.response = data;
191
+ }
192
+ }
193
+ if (data && data.user_message) {
194
+ message = lang ? data.user_message[lang] : data.user_message.en_us;
195
+ }
196
+ }
197
+ logger.error(err);
198
+ return {
199
+ error: errCode,
200
+ message,
201
+ };
202
+ }
203
+ }
204
+ return {
205
+ status: 200,
206
+ redirect_to_payment: false,
207
+ transaction,
208
+ };
209
+ };
210
+ // # sourceMappingURL=loyalty-create-transaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loyalty-create-transaction.js","sourceRoot":"","sources":["../src/loyalty-create-transaction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { AppModuleBody, ListPaymentsResponse } from '@cloudcommerce/types';
2
+ declare const _default: (data: AppModuleBody) => ListPaymentsResponse;
3
+ export default _default;
@@ -0,0 +1,44 @@
1
+ import getProgramId from './functions-lib/get-program-id.js';
2
+
3
+ export default (data) => {
4
+ const { application } = data;
5
+ const { params } = data;
6
+ const appData = {
7
+ ...application.data,
8
+ ...application.hidden_data,
9
+ };
10
+ // const { storeId } = req
11
+ // setup basic required response object
12
+ const response = {
13
+ payment_gateways: [],
14
+ };
15
+ const label = params.lang === 'pt_br' || !params.lang
16
+ ? 'Pontos de fidelidade'
17
+ : 'Loyalty points';
18
+ response.payment_gateways.push({
19
+ type: 'payment',
20
+ payment_method: {
21
+ code: 'loyalty_points',
22
+ name: label,
23
+ },
24
+ label,
25
+ });
26
+ if (Array.isArray(appData.programs_rules)) {
27
+ const pointsPrograms = {};
28
+ appData.programs_rules.forEach((programRule, index) => {
29
+ const programId = getProgramId(programRule, index);
30
+ pointsPrograms[programId] = {
31
+ name: programRule.name,
32
+ ratio: programRule.ratio || 1,
33
+ max_points: programRule.max_points,
34
+ min_subtotal_to_earn: programRule.min_subtotal_to_earn,
35
+ earn_percentage: programRule.earn_percentage,
36
+ };
37
+ });
38
+ if (Object.keys(pointsPrograms).length) {
39
+ response.loyalty_points_programs = pointsPrograms;
40
+ }
41
+ }
42
+ return response;
43
+ };
44
+ // # sourceMappingURL=loyalty-list-payments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loyalty-list-payments.js","sourceRoot":"","sources":["../src/loyalty-list-payments.ts"],"names":[],"mappings":"AAKA,OAAO,YAAY,MAAM,gCAAgC,CAAC;AAE1D,eAAe,CAAC,IAAmB,EAAE,EAAE;IACrC,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,MAA4B,CAAC;IAEjD,MAAM,OAAO,GAAG;QACd,GAAG,WAAW,CAAC,IAAI;QACnB,GAAG,WAAW,CAAC,WAAW;KAC3B,CAAC;IAEF,0BAA0B;IAC1B,uCAAuC;IACvC,MAAM,QAAQ,GAAyB;QACrC,gBAAgB,EAAE,EAAE;KACrB,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI;QACnD,CAAC,CAAC,sBAAsB;QACxB,CAAC,CAAC,gBAAgB,CAAC;IAErB,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC;QAC7B,IAAI,EAAE,SAAS;QACf,cAAc,EAAE;YACd,IAAI,EAAE,gBAAgB;YACtB,IAAI,EAAE,KAAK;SACZ;QACD,KAAK;KACN,CAAC,CAAC;IAEH,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE;QACzC,MAAM,cAAc,GAAG,EAAE,CAAC;QAC1B,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE;YACpD,MAAM,SAAS,GAAG,YAAY,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;YACnD,cAAc,CAAC,SAAS,CAAC,GAAG;gBAC1B,IAAI,EAAE,WAAW,CAAC,IAAI;gBACtB,KAAK,EAAE,WAAW,CAAC,KAAK,IAAI,CAAC;gBAC7B,UAAU,EAAE,WAAW,CAAC,UAAU;gBAClC,oBAAoB,EAAE,WAAW,CAAC,oBAAoB;gBACtD,eAAe,EAAE,WAAW,CAAC,eAAe;aAC7C,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,EAAE;YACtC,QAAQ,CAAC,uBAAuB,GAAG,cAAc,CAAC;SACnD;KACF;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import '@cloudcommerce/firebase/lib/init';
2
+ export declare const loyaltypoints: {
3
+ onStoreEvent: any;
4
+ };
@@ -0,0 +1,28 @@
1
+ /* eslint-disable import/prefer-default-export */
2
+ import '@cloudcommerce/firebase/lib/init';
3
+ import { createAppEventsFunction } from '@cloudcommerce/firebase/lib/helpers/pubsub';
4
+ import logger from 'firebase-functions/logger';
5
+ import handleLoyaltyPointsEvent from './functions-lib/handle-loyalty-points-event.js';
6
+
7
+ const handleApiEvent = async ({
8
+ evName, apiEvent, apiDoc, app,
9
+ }) => {
10
+ const resourceId = apiEvent.resource_id;
11
+ logger.info('>> ', resourceId, ' - Action: ', apiEvent.action);
12
+ const key = `${evName}_${resourceId}`;
13
+ const appData = { ...app.data, ...app.hidden_data };
14
+ const programRules = appData.programs_rules;
15
+ if ((Array.isArray(appData.ignore_events)
16
+ && appData.ignore_events.includes(evName))
17
+ || (!Array.isArray(programRules) || !programRules.length)) {
18
+ logger.info('>> ', key, ' - Ignored event');
19
+ return null;
20
+ }
21
+ logger.info(`> Webhook ${resourceId} [${evName}]`);
22
+ return handleLoyaltyPointsEvent(apiDoc, programRules);
23
+ };
24
+
25
+ export const loyaltypoints = {
26
+ onStoreEvent: createAppEventsFunction('loyaltyPoints', handleApiEvent),
27
+ };
28
+ // # sourceMappingURL=loyalty-points-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loyalty-points-events.js","sourceRoot":"","sources":["../src/loyalty-points-events.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,OAAO,kCAAkC,CAAC;AAE1C,OAAO,EACL,uBAAuB,GAExB,MAAM,4CAA4C,CAAC;AACpD,OAAO,MAAM,MAAM,2BAA2B,CAAC;AAC/C,OAAO,wBAAwB,MAAM,6CAA6C,CAAC;AAEnF,MAAM,cAAc,GAAoB,KAAK,EAAE,EAC7C,MAAM,EACN,QAAQ,EACR,MAAM,EACN,GAAG,GACJ,EAAE,EAAE;IACH,MAAM,UAAU,GAAG,QAAQ,CAAC,WAAW,CAAC;IACxC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/D,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,UAAU,EAAE,CAAC;IACtC,MAAM,OAAO,GAAG,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IACpD,MAAM,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC;IAE5C,IACE,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;WAChC,OAAO,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;WACzC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EACzD;QACA,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC;KACb;IACD,MAAM,CAAC,IAAI,CAAC,aAAa,UAAU,KAAK,MAAM,GAAG,CAAC,CAAC;IAEnD,OAAO,wBAAwB,CAAC,MAAgB,EAAE,YAAY,CAAC,CAAC;AAClE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,YAAY,EAAE,uBAAuB,CACnC,eAAe,EACf,cAAc,CACR;CACT,CAAC"}
@@ -0,0 +1,77 @@
1
+ import '@cloudcommerce/firebase/lib/init';
2
+ import type { AppModuleBody } from '@cloudcommerce/types';
3
+ export declare const listPayments: (modBody: AppModuleBody) => Promise<import("@cloudcommerce/types").ListPaymentsResponse>;
4
+ export declare const createTransaction: (modBody: AppModuleBody) => Promise<{
5
+ status: number;
6
+ redirect_to_payment: boolean;
7
+ transaction: {
8
+ payment_link?: string | undefined;
9
+ payment_instructions?: string | undefined;
10
+ intermediator?: {
11
+ transaction_id?: string | undefined;
12
+ transaction_code?: string | undefined;
13
+ transaction_reference?: string | undefined;
14
+ payment_method?: {
15
+ code: string;
16
+ name?: string | undefined;
17
+ } | undefined;
18
+ buyer_id?: string | undefined;
19
+ } | undefined;
20
+ credit_card?: {
21
+ holder_name?: string | undefined;
22
+ avs_result_code?: string | null | undefined;
23
+ cvv_result_code?: string | null | undefined;
24
+ bin?: number | undefined;
25
+ company?: string | undefined;
26
+ last_digits?: string | undefined;
27
+ token?: string | undefined;
28
+ error_code?: "incorrect_number" | "invalid_number" | "invalid_expiry_date" | "invalid_cvc" | "expired_card" | "incorrect_cvc" | "incorrect_zip" | "incorrect_address" | "card_declined" | "processing_error" | "call_issuer" | "pick_up_card" | undefined;
29
+ } | undefined;
30
+ banking_billet?: {
31
+ code?: string | undefined;
32
+ valid_thru?: string | undefined;
33
+ text_lines?: string[] | undefined;
34
+ link?: string | undefined;
35
+ } | undefined;
36
+ loyalty_points?: {
37
+ name?: string | undefined;
38
+ program_id: string;
39
+ points_value: number;
40
+ ratio?: number | undefined;
41
+ } | undefined;
42
+ currency_id?: string | undefined;
43
+ currency_symbol?: string | undefined;
44
+ discount?: number | undefined;
45
+ amount: number;
46
+ installments?: {
47
+ number: number;
48
+ value?: number | undefined;
49
+ tax?: boolean | undefined;
50
+ total?: number | undefined;
51
+ } | undefined;
52
+ creditor_fees?: {
53
+ installment?: number | undefined;
54
+ operational?: number | undefined;
55
+ intermediation?: number | undefined;
56
+ other?: number | undefined;
57
+ } | undefined;
58
+ status?: {
59
+ updated_at?: string | undefined;
60
+ current: "authorized" | "unauthorized" | "pending" | "under_analysis" | "paid" | "in_dispute" | "refunded" | "voided" | "unknown";
61
+ } | undefined;
62
+ flags?: string[] | undefined;
63
+ custom_fields?: {
64
+ field: string;
65
+ value: string;
66
+ }[] | undefined;
67
+ notes?: string | undefined;
68
+ };
69
+ error?: undefined;
70
+ message?: undefined;
71
+ } | {
72
+ error: string;
73
+ message: any;
74
+ status?: undefined;
75
+ redirect_to_payment?: undefined;
76
+ transaction?: undefined;
77
+ }>;
@@ -0,0 +1,12 @@
1
+ import '@cloudcommerce/firebase/lib/init';
2
+ import handleListPayments from './loyalty-list-payments.js';
3
+ import handleCreateTransaction from './loyalty-create-transaction.js';
4
+
5
+ export const listPayments = async (modBody) => {
6
+ return handleListPayments(modBody);
7
+ };
8
+
9
+ export const createTransaction = async (modBody) => {
10
+ return handleCreateTransaction(modBody);
11
+ };
12
+ // # sourceMappingURL=loyalty-points.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loyalty-points.js","sourceRoot":"","sources":["../src/loyalty-points.ts"],"names":[],"mappings":"AAAA,OAAO,kCAAkC,CAAC;AAE1C,OAAO,kBAAkB,MAAM,yBAAyB,CAAC;AACzD,OAAO,uBAAuB,MAAM,8BAA8B,CAAC;AAEnE,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,OAAsB,EAAE,EAAE;IAC3D,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAC;AACrC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EAAE,OAAsB,EAAE,EAAE;IAChE,OAAO,uBAAuB,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@cloudcommerce/app-loyalty-points",
3
+ "type": "module",
4
+ "version": "0.1.1",
5
+ "description": "E-Com Plus Cloud Commerce app to handle simple loyalty points programs",
6
+ "main": "lib/loyalty-points.js",
7
+ "exports": {
8
+ ".": "./lib/loyalty-points.js",
9
+ "./events": "./lib/loyalty-points-events.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/ecomplus/cloud-commerce.git",
14
+ "directory": "packages/loyalty-points"
15
+ },
16
+ "author": "E-Com Club Softwares para E-commerce <ti@e-com.club>",
17
+ "license": "Apache 2.0 with Commons Clause",
18
+ "bugs": {
19
+ "url": "https://github.com/ecomplus/cloud-commerce/issues"
20
+ },
21
+ "homepage": "https://github.com/ecomplus/cloud-commerce/tree/main/packages/apps/loyalty-points#readme",
22
+ "dependencies": {
23
+ "@cloudcommerce/api": "0.1.1",
24
+ "@cloudcommerce/firebase": "0.1.1"
25
+ },
26
+ "devDependencies": {
27
+ "@cloudcommerce/types": "0.1.1"
28
+ },
29
+ "scripts": {
30
+ "build": "sh ../../../scripts/build-lib.sh"
31
+ }
32
+ }
@@ -0,0 +1,23 @@
1
+ export default (
2
+ programRule: { program_id: string; name: string; },
3
+ index: number,
4
+ ) => {
5
+ let programId = `lpt__${index}`;
6
+ if (programRule.program_id) {
7
+ programId = programRule.program_id;
8
+ } else if (programRule.name && typeof programRule.name === 'string') {
9
+ programId = (`p0_${programRule.name.toLowerCase()}`)
10
+ .replace(/\n\s/g, '_')
11
+ .replace(/__/g, '_')
12
+ .replace(/áàãâ/g, 'a')
13
+ .replace(/éê/g, 'e')
14
+ .replace(/óõô/g, 'o')
15
+ .replace(/í/g, 'e')
16
+ .replace(/ú/g, 'u')
17
+ .replace(/ç/g, 'c')
18
+ .replace(/[^a-z0-9_]/g, '')
19
+ .substring(0, 30);
20
+ }
21
+
22
+ return programId;
23
+ };
@@ -0,0 +1,207 @@
1
+ import type { Orders, Customers } from '@cloudcommerce/types';
2
+ import api from '@cloudcommerce/api';
3
+ import { getFirestore } from 'firebase-admin/firestore';
4
+ import logger from 'firebase-functions/logger';
5
+ import getProgramId from './get-program-id';
6
+
7
+ const ECHO_SUCCESS = 'SUCCESS';
8
+ const ECHO_SKIP = 'SKIP';
9
+
10
+ type UsedPointsEntries = Exclude<Customers['loyalty_points_entries'], undefined>[number]
11
+ & { original_active_points: number }
12
+
13
+ const responsePubSub = (response: string) => {
14
+ logger.log('(App: Loyalty Points): ', response);
15
+ return null;
16
+ };
17
+
18
+ const haveEarnedPoints = (pointsList: Customers['loyalty_points_entries'], orderId: string) => {
19
+ if (pointsList) {
20
+ let hasEarned = false;
21
+ for (let i = 0; i < pointsList.length; i++) {
22
+ const point = pointsList[i];
23
+ if (point.order_id === orderId) {
24
+ hasEarned = true;
25
+ break;
26
+ }
27
+ }
28
+ return hasEarned;
29
+ }
30
+ return false;
31
+ };
32
+
33
+ const findPointIndex = (pointsList: Customers['loyalty_points_entries'], pointEntry: UsedPointsEntries) => {
34
+ if (pointsList) {
35
+ for (let i = 0; i < pointsList.length; i++) {
36
+ const point = pointsList[i];
37
+ if (point.name === pointEntry.name) {
38
+ return i;
39
+ }
40
+ }
41
+ }
42
+ return null;
43
+ };
44
+
45
+ const handleLoyaltyPointsEvent = async (
46
+ order: Orders,
47
+ programRules: any[],
48
+ ) => {
49
+ const orderId = order._id;
50
+ const currentStatus = order.financial_status && order.financial_status.current;
51
+ let isPaid: boolean = false;
52
+ let isCancelled: boolean = false;
53
+
54
+ switch (currentStatus) {
55
+ case 'paid':
56
+ isPaid = true;
57
+ break;
58
+ case 'unauthorized':
59
+ case 'partially_refunded':
60
+ case 'refunded':
61
+ case 'voided':
62
+ isCancelled = true;
63
+ break;
64
+ case 'partially_paid':
65
+ if (order.transactions) {
66
+ order.transactions.forEach((transaction) => {
67
+ if (transaction.payment_method.code !== 'loyalty_points' && transaction.status) {
68
+ switch (transaction.status.current) {
69
+ case 'unauthorized':
70
+ case 'refunded':
71
+ case 'voided':
72
+ isCancelled = true;
73
+ break;
74
+ default:
75
+ break;
76
+ }
77
+ }
78
+ });
79
+ }
80
+ break;
81
+ default:
82
+ break;
83
+ }
84
+
85
+ try {
86
+ if (isPaid || isCancelled) {
87
+ // get app configured options
88
+ const { amount, buyers } = order;
89
+ const customerId = buyers && buyers[0] && buyers[0]._id;
90
+ if (customerId) {
91
+ const pointsList: Customers['loyalty_points_entries'] = (await api.get(
92
+ `customers/${customerId}/loyalty_points_entries`,
93
+ )
94
+ ).data;
95
+
96
+ if (pointsList) {
97
+ const hasEarnedPoints = haveEarnedPoints(pointsList, orderId);
98
+
99
+ if (isPaid && !hasEarnedPoints) {
100
+ for (let i = 0; i < programRules.length; i++) {
101
+ const rule = programRules[i];
102
+ if (amount.subtotal) {
103
+ if (!rule.min_subtotal_to_earn || rule.min_subtotal_to_earn <= amount.subtotal) {
104
+ const pointsValue = ((rule.earn_percentage || 1) / 100)
105
+ * (amount.subtotal - (amount.discount || 0));
106
+
107
+ let validThru: string | undefined;
108
+ if (rule.expiration_days > 0) {
109
+ const d = new Date();
110
+ d.setDate(d.getDate() + rule.expiration_days);
111
+ validThru = d.toISOString();
112
+ }
113
+
114
+ const data = {
115
+ name: rule.name,
116
+ program_id: getProgramId(rule, i),
117
+ earned_points: pointsValue,
118
+ active_points: pointsValue,
119
+ ratio: rule.ratio || 1,
120
+ valid_thru: validThru,
121
+ order_id: orderId,
122
+ } as any; // TODO: set the correct type
123
+
124
+ // eslint-disable-next-line no-await-in-loop
125
+ await api.post(`customers/${customerId}/loyalty_points_entries`, data);
126
+
127
+ return responsePubSub(ECHO_SUCCESS);
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ if (isCancelled && hasEarnedPoints) {
134
+ for (let i = 0; i < pointsList.length; i++) {
135
+ if (pointsList[i].order_id === orderId) {
136
+ // eslint-disable-next-line no-await-in-loop
137
+ await api.delete(`customers/${customerId}/loyalty_points_entries/${i}`);
138
+ }
139
+ }
140
+
141
+ return responsePubSub(ECHO_SUCCESS);
142
+ }
143
+ }
144
+
145
+ if (isCancelled) {
146
+ const documentRef = getFirestore().doc(`billedPoints/${orderId}`);
147
+ const documentSnapshot = await documentRef.get();
148
+
149
+ if (documentSnapshot.exists) {
150
+ const usedPointsEntries = documentSnapshot.get('usedPointsEntries') as UsedPointsEntries[];
151
+ documentRef.delete();
152
+
153
+ if (Array.isArray(usedPointsEntries)) {
154
+ for (let i = 0; i < usedPointsEntries.length; i++) {
155
+ const pointsEntry = usedPointsEntries[i];
156
+ const pointsToRefund = pointsEntry.original_active_points
157
+ - pointsEntry.active_points;
158
+ if (pointsToRefund > 0) {
159
+ const pointIndex = findPointIndex(pointsList, pointsEntry);
160
+
161
+ if (pointIndex && pointsList) {
162
+ const activePoints = pointsList[pointIndex].active_points;
163
+ const body = {
164
+ active_points: activePoints + pointsToRefund,
165
+ };
166
+
167
+ // eslint-disable-next-line no-await-in-loop
168
+ await api.patch(
169
+ `customers/${customerId}/loyalty_points_entries/${pointIndex}`,
170
+ body,
171
+ );
172
+
173
+ return responsePubSub(ECHO_SUCCESS);
174
+ }
175
+ }
176
+ }
177
+
178
+ const transaction = order.transactions
179
+ && order.transactions.find((transactionFound) => {
180
+ return transactionFound.payment_method.code === 'loyalty_points';
181
+ });
182
+
183
+ if (transaction) {
184
+ const bodyPaymentHistory = {
185
+ transaction_id: transaction._id,
186
+ date_time: new Date().toISOString(),
187
+ status: currentStatus?.startsWith('partially') ? 'refunded' : currentStatus,
188
+ customer_notified: true,
189
+ } as any; // TODO: incompatible type=> amount and status
190
+
191
+ await api.post(`orders/${orderId}/payments_history`, bodyPaymentHistory);
192
+ }
193
+ }
194
+ }
195
+ }
196
+ }
197
+ return responsePubSub('');
198
+ }
199
+ // not paid nor cancelled
200
+ return responsePubSub(ECHO_SKIP);
201
+ } catch (err) {
202
+ logger.error('(App Loyalty Points) Error =>', err);
203
+ throw err;
204
+ }
205
+ };
206
+
207
+ export default handleLoyaltyPointsEvent;