dra-qris-api 1.0.7 → 1.0.8

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.

Potentially problematic release.


This version of dra-qris-api might be problematic. Click here for more details.

@@ -0,0 +1,390 @@
1
+ // lib/merchantBalanceManager.js
2
+ import axios from "axios";
3
+ import crypto from "crypto";
4
+ var MerchantBalanceManager = class {
5
+ constructor() {
6
+ this.balances = /* @__PURE__ */ new Map();
7
+ this.mutations = /* @__PURE__ */ new Map();
8
+ this.transactions = /* @__PURE__ */ new Map();
9
+ }
10
+ /**
11
+ * Hash API key untuk privacy
12
+ */
13
+ hashApiKey(apiKey) {
14
+ return crypto.createHash("sha256").update(apiKey).digest("hex").substring(0, 16);
15
+ }
16
+ /**
17
+ * Update merchant balance setelah payment berhasil
18
+ */
19
+ async updateBalance({
20
+ apiKey,
21
+ merchantName,
22
+ transactionId,
23
+ amount,
24
+ description
25
+ }) {
26
+ const merchantHash = this.hashApiKey(apiKey);
27
+ const key = `${merchantHash}:${merchantName}`;
28
+ let balanceData = this.balances.get(key) || {
29
+ merchantHash,
30
+ merchantName,
31
+ balance: 0,
32
+ totalTransactions: 0,
33
+ totalIncome: 0,
34
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
35
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
36
+ };
37
+ const balanceBefore = balanceData.balance;
38
+ const balanceAfter = balanceBefore + amount;
39
+ balanceData.balance = balanceAfter;
40
+ balanceData.totalTransactions += 1;
41
+ balanceData.totalIncome += amount;
42
+ balanceData.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
43
+ this.balances.set(key, balanceData);
44
+ const mutation = {
45
+ transactionId,
46
+ amount,
47
+ description,
48
+ balanceBefore,
49
+ balanceAfter,
50
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
51
+ };
52
+ const mutationKey = `${key}:${Date.now()}_${transactionId}`;
53
+ if (!this.mutations.has(key)) {
54
+ this.mutations.set(key, []);
55
+ }
56
+ this.mutations.get(key).push(mutation);
57
+ this.transactions.set(transactionId, {
58
+ merchantHash,
59
+ merchantName,
60
+ amount,
61
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
62
+ });
63
+ return {
64
+ merchantName,
65
+ amount,
66
+ balanceBefore,
67
+ balanceAfter,
68
+ timestamp: mutation.timestamp
69
+ };
70
+ }
71
+ /**
72
+ * Get merchant balance
73
+ */
74
+ async getBalance(apiKey, merchantName = null) {
75
+ const merchantHash = this.hashApiKey(apiKey);
76
+ if (merchantName) {
77
+ const key = `${merchantHash}:${merchantName}`;
78
+ return this.balances.get(key) || null;
79
+ }
80
+ const results = [];
81
+ for (const [key, value] of this.balances.entries()) {
82
+ if (key.startsWith(merchantHash + ":")) {
83
+ results.push(value);
84
+ }
85
+ }
86
+ return results;
87
+ }
88
+ /**
89
+ * Get balance mutations (transaction history)
90
+ */
91
+ async getMutations(apiKey, merchantName = null, limit = 50) {
92
+ const merchantHash = this.hashApiKey(apiKey);
93
+ if (merchantName) {
94
+ const key = `${merchantHash}:${merchantName}`;
95
+ const mutations = this.mutations.get(key) || [];
96
+ return mutations.slice(-limit).reverse();
97
+ }
98
+ const allMutations = [];
99
+ for (const [key, value] of this.mutations.entries()) {
100
+ if (key.startsWith(merchantHash + ":")) {
101
+ allMutations.push(...value);
102
+ }
103
+ }
104
+ allMutations.sort(
105
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
106
+ );
107
+ return allMutations.slice(0, limit);
108
+ }
109
+ /**
110
+ * Get merchant statistics
111
+ */
112
+ async getMerchantStats(apiKey, merchantName = null) {
113
+ const merchantHash = this.hashApiKey(apiKey);
114
+ if (merchantName) {
115
+ const key = `${merchantHash}:${merchantName}`;
116
+ const balance = this.balances.get(key);
117
+ if (!balance) {
118
+ return {
119
+ totalTransactions: 0,
120
+ totalIncome: 0,
121
+ balance: 0,
122
+ merchantName
123
+ };
124
+ }
125
+ return {
126
+ totalTransactions: balance.totalTransactions,
127
+ totalIncome: balance.totalIncome,
128
+ balance: balance.balance,
129
+ merchantName: balance.merchantName,
130
+ createdAt: balance.createdAt,
131
+ updatedAt: balance.updatedAt
132
+ };
133
+ }
134
+ let totalTransactions = 0;
135
+ let totalIncome = 0;
136
+ let totalBalance = 0;
137
+ let merchantCount = 0;
138
+ for (const [key, value] of this.balances.entries()) {
139
+ if (key.startsWith(merchantHash + ":")) {
140
+ totalTransactions += value.totalTransactions;
141
+ totalIncome += value.totalIncome;
142
+ totalBalance += value.balance;
143
+ merchantCount++;
144
+ }
145
+ }
146
+ return {
147
+ merchantCount,
148
+ totalTransactions,
149
+ totalIncome,
150
+ totalBalance,
151
+ averageBalance: merchantCount > 0 ? totalBalance / merchantCount : 0
152
+ };
153
+ }
154
+ /**
155
+ * Get transaction history dengan filtering
156
+ */
157
+ async getTransactionHistory(apiKey, filters = {}) {
158
+ const merchantHash = this.hashApiKey(apiKey);
159
+ const {
160
+ merchantName,
161
+ status,
162
+ startDate,
163
+ endDate,
164
+ minAmount,
165
+ maxAmount,
166
+ limit = 100
167
+ } = filters;
168
+ const mutations = await this.getMutations(apiKey, merchantName, 1e3);
169
+ let filtered = [...mutations];
170
+ if (startDate) {
171
+ const start = new Date(startDate).getTime();
172
+ filtered = filtered.filter(
173
+ (m) => new Date(m.timestamp).getTime() >= start
174
+ );
175
+ }
176
+ if (endDate) {
177
+ const end = new Date(endDate).getTime();
178
+ filtered = filtered.filter((m) => new Date(m.timestamp).getTime() <= end);
179
+ }
180
+ if (minAmount) {
181
+ filtered = filtered.filter((m) => m.amount >= minAmount);
182
+ }
183
+ if (maxAmount) {
184
+ filtered = filtered.filter((m) => m.amount <= maxAmount);
185
+ }
186
+ return filtered.slice(0, limit);
187
+ }
188
+ /**
189
+ * Get daily sales summary
190
+ */
191
+ async getDailySales(apiKey, merchantName = null, date = null) {
192
+ const targetDate = date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
193
+ const mutations = await this.getMutations(apiKey, merchantName, 1e3);
194
+ const dailyMutations = mutations.filter(
195
+ (m) => m.timestamp.startsWith(targetDate)
196
+ );
197
+ const totalSales = dailyMutations.reduce((sum, m) => sum + m.amount, 0);
198
+ const transactionCount = dailyMutations.length;
199
+ return {
200
+ date: targetDate,
201
+ merchantName,
202
+ totalSales,
203
+ transactionCount,
204
+ averageTransaction: transactionCount > 0 ? totalSales / transactionCount : 0,
205
+ transactions: dailyMutations
206
+ };
207
+ }
208
+ /**
209
+ * Get monthly report
210
+ */
211
+ async getMonthlyReport(apiKey, merchantName = null, year = null, month = null) {
212
+ const now = /* @__PURE__ */ new Date();
213
+ const targetYear = year || now.getFullYear();
214
+ const targetMonth = month || now.getMonth() + 1;
215
+ const monthStr = `${targetYear}-${String(targetMonth).padStart(2, "0")}`;
216
+ const mutations = await this.getMutations(apiKey, merchantName, 1e4);
217
+ const monthlyMutations = mutations.filter(
218
+ (m) => m.timestamp.startsWith(monthStr)
219
+ );
220
+ const totalRevenue = monthlyMutations.reduce((sum, m) => sum + m.amount, 0);
221
+ const transactionCount = monthlyMutations.length;
222
+ const dailyBreakdown = {};
223
+ monthlyMutations.forEach((m) => {
224
+ const day = m.timestamp.split("T")[0];
225
+ if (!dailyBreakdown[day]) {
226
+ dailyBreakdown[day] = {
227
+ date: day,
228
+ totalSales: 0,
229
+ transactionCount: 0
230
+ };
231
+ }
232
+ dailyBreakdown[day].totalSales += m.amount;
233
+ dailyBreakdown[day].transactionCount += 1;
234
+ });
235
+ return {
236
+ year: targetYear,
237
+ month: targetMonth,
238
+ merchantName,
239
+ totalRevenue,
240
+ transactionCount,
241
+ averageTransaction: transactionCount > 0 ? totalRevenue / transactionCount : 0,
242
+ dailyBreakdown: Object.values(dailyBreakdown).sort(
243
+ (a, b) => a.date.localeCompare(b.date)
244
+ )
245
+ };
246
+ }
247
+ /**
248
+ * Export data untuk backup/reporting
249
+ */
250
+ async exportData(apiKey, format = "json") {
251
+ const merchantHash = this.hashApiKey(apiKey);
252
+ const balances = await this.getBalance(apiKey);
253
+ const mutations = await this.getMutations(apiKey, null, 1e4);
254
+ const stats = await this.getMerchantStats(apiKey);
255
+ const data = {
256
+ exportDate: (/* @__PURE__ */ new Date()).toISOString(),
257
+ merchantHash,
258
+ balances,
259
+ mutations,
260
+ stats
261
+ };
262
+ if (format === "json") {
263
+ return JSON.stringify(data, null, 2);
264
+ }
265
+ if (format === "csv") {
266
+ let csv = "Date,Merchant,Amount,Balance Before,Balance After,Description\n";
267
+ mutations.forEach((m) => {
268
+ const balance = balances.find((b) => b.merchantName === m.merchantName);
269
+ csv += `${m.timestamp},${(balance == null ? void 0 : balance.merchantName) || "N/A"},${m.amount},${m.balanceBefore},${m.balanceAfter},"${m.description}"
270
+ `;
271
+ });
272
+ return csv;
273
+ }
274
+ return data;
275
+ }
276
+ /**
277
+ * Reset balance (untuk testing atau koreksi)
278
+ */
279
+ async resetBalance(apiKey, merchantName) {
280
+ const merchantHash = this.hashApiKey(apiKey);
281
+ const key = `${merchantHash}:${merchantName}`;
282
+ this.balances.delete(key);
283
+ this.mutations.delete(key);
284
+ return {
285
+ success: true,
286
+ message: `Balance reset for ${merchantName}`
287
+ };
288
+ }
289
+ /**
290
+ * Sync dengan API eksternal (untuk production)
291
+ */
292
+ async syncWithAPI(apiKey, baseURL) {
293
+ var _a;
294
+ try {
295
+ const balanceResp = await axios.get(`${baseURL}/api/qris?op=balance`, {
296
+ headers: { "x-api-key": apiKey }
297
+ });
298
+ const mutationsResp = await axios.get(
299
+ `${baseURL}/api/qris?op=balance-mutations&limit=100`,
300
+ {
301
+ headers: { "x-api-key": apiKey }
302
+ }
303
+ );
304
+ if (balanceResp.data.balance) {
305
+ const balance = balanceResp.data.balance;
306
+ const merchantHash = this.hashApiKey(apiKey);
307
+ const key = `${merchantHash}:${balance.merchantName}`;
308
+ this.balances.set(key, balance);
309
+ }
310
+ if (mutationsResp.data.mutations) {
311
+ const mutations = mutationsResp.data.mutations;
312
+ mutations.forEach((mutation) => {
313
+ var _a2;
314
+ const merchantHash = this.hashApiKey(apiKey);
315
+ const merchantName = ((_a2 = mutation.description.match(/from (.+)$/)) == null ? void 0 : _a2[1]) || "Unknown";
316
+ const key = `${merchantHash}:${merchantName}`;
317
+ if (!this.mutations.has(key)) {
318
+ this.mutations.set(key, []);
319
+ }
320
+ this.mutations.get(key).push(mutation);
321
+ });
322
+ }
323
+ return {
324
+ success: true,
325
+ synced: {
326
+ balances: 1,
327
+ mutations: ((_a = mutationsResp.data.mutations) == null ? void 0 : _a.length) || 0
328
+ }
329
+ };
330
+ } catch (err) {
331
+ console.error("Sync error:", err);
332
+ return {
333
+ success: false,
334
+ error: err.message
335
+ };
336
+ }
337
+ }
338
+ /**
339
+ * Get top merchants by revenue
340
+ */
341
+ async getTopMerchants(apiKey, limit = 10) {
342
+ const balances = await this.getBalance(apiKey);
343
+ return balances.sort((a, b) => b.totalIncome - a.totalIncome).slice(0, limit).map((balance, index) => ({
344
+ rank: index + 1,
345
+ merchantName: balance.merchantName,
346
+ totalIncome: balance.totalIncome,
347
+ totalTransactions: balance.totalTransactions,
348
+ averageTransaction: balance.totalIncome / balance.totalTransactions,
349
+ currentBalance: balance.balance
350
+ }));
351
+ }
352
+ /**
353
+ * Calculate growth rate
354
+ */
355
+ async getGrowthRate(apiKey, merchantName, days = 7) {
356
+ const mutations = await this.getMutations(apiKey, merchantName, 1e3);
357
+ const now = /* @__PURE__ */ new Date();
358
+ const periodStart = new Date(now.getTime() - days * 24 * 60 * 60 * 1e3);
359
+ const recentMutations = mutations.filter(
360
+ (m) => new Date(m.timestamp).getTime() >= periodStart.getTime()
361
+ );
362
+ const currentPeriodRevenue = recentMutations.reduce(
363
+ (sum, m) => sum + m.amount,
364
+ 0
365
+ );
366
+ const prevPeriodStart = new Date(
367
+ periodStart.getTime() - days * 24 * 60 * 60 * 1e3
368
+ );
369
+ const prevMutations = mutations.filter((m) => {
370
+ const ts = new Date(m.timestamp).getTime();
371
+ return ts >= prevPeriodStart.getTime() && ts < periodStart.getTime();
372
+ });
373
+ const previousPeriodRevenue = prevMutations.reduce(
374
+ (sum, m) => sum + m.amount,
375
+ 0
376
+ );
377
+ const growthRate = previousPeriodRevenue > 0 ? (currentPeriodRevenue - previousPeriodRevenue) / previousPeriodRevenue * 100 : 0;
378
+ return {
379
+ period: `${days} days`,
380
+ currentPeriodRevenue,
381
+ previousPeriodRevenue,
382
+ growthRate: parseFloat(growthRate.toFixed(2)),
383
+ trend: growthRate > 0 ? "up" : growthRate < 0 ? "down" : "stable"
384
+ };
385
+ }
386
+ };
387
+
388
+ export {
389
+ MerchantBalanceManager
390
+ };