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.
- package/dist/chunk-4NA4NJ4G.js +390 -0
- package/dist/chunk-CSCJMH5H.js +363 -0
- package/dist/chunk-EBJCGUAH.js +7 -0
- package/dist/chunk-G7UHT62G.js +56 -0
- package/dist/chunk-XOTK7GV5.js +240 -0
- package/dist/chunk-Z7AZPJL4.js +545 -0
- package/dist/index.cjs +1623 -0
- package/dist/index.js +34 -0
- package/dist/lib/createQris.cjs +278 -0
- package/dist/lib/createQris.js +16 -0
- package/dist/lib/globalPending.cjs +26 -0
- package/dist/lib/globalPending.js +6 -0
- package/dist/lib/handlePaymentProof.cjs +1373 -0
- package/dist/lib/handlePaymentProof.js +14 -0
- package/dist/lib/merchantBalanceManager.cjs +423 -0
- package/dist/lib/merchantBalanceManager.js +6 -0
- package/dist/lib/securityValidator.cjs +578 -0
- package/dist/lib/securityValidator.js +6 -0
- package/dist/lib/submitProof.cjs +85 -0
- package/dist/lib/submitProof.js +6 -0
- package/package.json +6 -5
|
@@ -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
|
+
};
|