payment-kit 1.25.8 → 1.26.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/api/src/crons/index.ts +24 -0
- package/api/src/libs/archive/config.ts +254 -0
- package/api/src/libs/archive/executor.ts +729 -0
- package/api/src/libs/archive/index.ts +7 -0
- package/api/src/libs/archive/lock.ts +50 -0
- package/api/src/libs/archive/policy.ts +55 -0
- package/api/src/libs/archive/query.ts +136 -0
- package/api/src/libs/archive/snapshot.ts +291 -0
- package/api/src/libs/archive/store.ts +200 -0
- package/api/src/libs/session.ts +43 -25
- package/api/src/queues/archive.ts +32 -0
- package/api/src/queues/subscription.ts +3 -1
- package/api/src/routes/archive.ts +176 -0
- package/api/src/routes/checkout-sessions.ts +50 -34
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/meters.ts +28 -0
- package/api/src/routes/payment-stats.ts +167 -20
- package/api/src/store/migrations/20260203-archive.ts +12 -0
- package/api/src/store/migrations/20260204-revenue-snapshot.ts +19 -0
- package/api/src/store/models/archive-lock.ts +55 -0
- package/api/src/store/models/archive-metadata.ts +132 -0
- package/api/src/store/models/index.ts +9 -0
- package/api/src/store/models/revenue-snapshot.ts +110 -0
- package/api/tests/libs/archive-config.spec.ts +185 -0
- package/api/tests/libs/archive-executor.spec.ts +678 -0
- package/api/tests/libs/archive-lock.spec.ts +130 -0
- package/api/tests/libs/archive-policy.spec.ts +255 -0
- package/api/tests/libs/archive-query.spec.ts +267 -0
- package/api/tests/libs/archive-store.spec.ts +159 -0
- package/blocklet.prefs.json +187 -0
- package/blocklet.yml +2 -1
- package/package.json +10 -10
- package/src/components/customer/actions.tsx +1 -1
- package/src/components/customer/credit-overview.tsx +3 -1
- package/src/components/customer/overdraft-protection.tsx +1 -1
- package/src/components/event/list.tsx +1 -1
- package/src/components/filter-toolbar.tsx +2 -2
- package/src/components/invoice/action.tsx +3 -3
- package/src/components/invoice/list.tsx +1 -1
- package/src/components/invoice/recharge.tsx +2 -2
- package/src/components/meter/add-usage-dialog.tsx +1 -1
- package/src/components/passport/actions.tsx +1 -1
- package/src/components/passport/assign.tsx +1 -1
- package/src/components/payment-currency/add.tsx +1 -1
- package/src/components/payment-currency/edit.tsx +1 -1
- package/src/components/payment-intent/actions.tsx +4 -4
- package/src/components/payment-intent/list.tsx +1 -1
- package/src/components/payment-link/actions.tsx +4 -4
- package/src/components/payment-link/item.tsx +1 -1
- package/src/components/payouts/list.tsx +1 -1
- package/src/components/payouts/portal/list.tsx +1 -1
- package/src/components/price/upsell-select.tsx +1 -1
- package/src/components/price/upsell.tsx +2 -2
- package/src/components/pricing-table/actions.tsx +3 -3
- package/src/components/pricing-table/product-item.tsx +1 -1
- package/src/components/product/actions.tsx +3 -3
- package/src/components/product/create.tsx +1 -1
- package/src/components/product/cross-sell.tsx +2 -2
- package/src/components/promotion/active-redemptions.tsx +1 -1
- package/src/components/refund/list.tsx +1 -1
- package/src/components/subscription/actions/index.tsx +1 -1
- package/src/components/subscription/items/usage-records.tsx +4 -2
- package/src/components/subscription/list.tsx +1 -1
- package/src/components/subscription/metrics.tsx +3 -3
- package/src/components/subscription/portal/actions.tsx +15 -12
- package/src/components/subscription/portal/list.tsx +1 -1
- package/src/components/webhook/attempts.tsx +4 -4
- package/src/hooks/subscription.ts +2 -2
- package/src/locales/en.tsx +4 -0
- package/src/locales/zh.tsx +4 -0
- package/src/pages/admin/billing/meter-events/index.tsx +3 -3
- package/src/pages/admin/billing/meters/index.tsx +1 -1
- package/src/pages/admin/billing/overdue/index.tsx +2 -2
- package/src/pages/admin/billing/subscriptions/detail.tsx +2 -2
- package/src/pages/admin/customers/customers/credit-grant/detail.tsx +1 -1
- package/src/pages/admin/customers/customers/credit-transaction/detail.tsx +1 -1
- package/src/pages/admin/customers/customers/detail.tsx +4 -4
- package/src/pages/admin/developers/events/detail.tsx +1 -1
- package/src/pages/admin/developers/webhooks/detail.tsx +1 -1
- package/src/pages/admin/developers/webhooks/index.tsx +1 -1
- package/src/pages/admin/overview.tsx +2 -0
- package/src/pages/admin/payments/intents/detail.tsx +2 -2
- package/src/pages/admin/payments/payouts/detail.tsx +2 -2
- package/src/pages/admin/payments/refunds/detail.tsx +2 -2
- package/src/pages/admin/products/coupons/detail.tsx +1 -1
- package/src/pages/admin/products/coupons/index.tsx +1 -1
- package/src/pages/admin/products/exchange-rate-providers/index.tsx +1 -1
- package/src/pages/admin/products/links/create.tsx +1 -1
- package/src/pages/admin/products/links/detail.tsx +2 -2
- package/src/pages/admin/products/links/index.tsx +1 -1
- package/src/pages/admin/products/passports/index.tsx +1 -1
- package/src/pages/admin/products/prices/actions.tsx +4 -4
- package/src/pages/admin/products/prices/detail.tsx +2 -2
- package/src/pages/admin/products/pricing-tables/create.tsx +1 -1
- package/src/pages/admin/products/pricing-tables/detail.tsx +2 -2
- package/src/pages/admin/products/pricing-tables/index.tsx +1 -1
- package/src/pages/admin/products/products/index.tsx +1 -1
- package/src/pages/admin/products/promotion-codes/actions.tsx +2 -2
- package/src/pages/admin/products/promotion-codes/detail.tsx +2 -2
- package/src/pages/admin/products/promotion-codes/list.tsx +1 -1
- package/src/pages/admin/settings/payment-methods/create.tsx +1 -1
- package/src/pages/admin/settings/payment-methods/edit.tsx +1 -1
- package/src/pages/admin/settings/payment-methods/index.tsx +2 -2
- package/src/pages/admin/tax/detail.tsx +2 -2
- package/src/pages/admin/tax/list.tsx +1 -1
- package/src/pages/checkout/pay.tsx +2 -2
- package/src/pages/customer/index.tsx +1 -1
- package/src/pages/customer/invoice/past-due.tsx +1 -1
- package/src/pages/customer/payout/detail.tsx +1 -1
- package/src/pages/customer/refund/list.tsx +1 -1
- package/src/pages/customer/subscription/change-payment.tsx +2 -2
- package/src/pages/customer/subscription/change-plan.tsx +3 -3
- package/src/pages/customer/subscription/detail.tsx +3 -3
- package/src/pages/integrations/donations/index.tsx +1 -1
- package/vite.config.ts +3 -1
package/api/src/routes/meters.ts
CHANGED
|
@@ -137,6 +137,34 @@ router.post('/', auth, async (req, res) => {
|
|
|
137
137
|
}
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
+
// Public endpoint: only returns safe fields, no auth required
|
|
141
|
+
const PUBLIC_METER_FIELDS = ['id', 'name', 'event_name', 'status', 'unit', 'description', 'currency_id'] as const;
|
|
142
|
+
|
|
143
|
+
router.get('/public/:id', async (req, res) => {
|
|
144
|
+
try {
|
|
145
|
+
const meter = await Meter.findOne({
|
|
146
|
+
where: {
|
|
147
|
+
[Op.or]: [{ id: req.params.id }, { event_name: req.params.id }],
|
|
148
|
+
},
|
|
149
|
+
include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (!meter) {
|
|
153
|
+
return res.status(404).json({ error: 'Meter not found' });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return res.json({
|
|
157
|
+
...pick(meter.toJSON(), PUBLIC_METER_FIELDS),
|
|
158
|
+
paymentCurrency: (meter as any).paymentCurrency
|
|
159
|
+
? pick((meter as any).paymentCurrency.toJSON(), ['id', 'name', 'symbol', 'decimal', 'logo', 'type'])
|
|
160
|
+
: null,
|
|
161
|
+
});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
logger.error('get public meter failed', { error: err?.message, meterId: req.params.id });
|
|
164
|
+
return res.status(400).json({ error: err?.message });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
140
168
|
router.get('/:id', auth, async (req, res) => {
|
|
141
169
|
try {
|
|
142
170
|
const meter = await Meter.findOne({
|
|
@@ -11,6 +11,7 @@ import { ethWallet, wallet } from '../libs/auth';
|
|
|
11
11
|
import dayjs from '../libs/dayjs';
|
|
12
12
|
import { authenticate } from '../libs/security';
|
|
13
13
|
import {
|
|
14
|
+
CreditGrant,
|
|
14
15
|
EVMChainType,
|
|
15
16
|
Invoice,
|
|
16
17
|
InvoiceItem,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
PaymentStat,
|
|
21
22
|
Payout,
|
|
22
23
|
Refund,
|
|
24
|
+
RevenueSnapshot,
|
|
23
25
|
Subscription,
|
|
24
26
|
} from '../store/models';
|
|
25
27
|
import { EVM_CHAIN_TYPES } from '../libs/constants';
|
|
@@ -121,7 +123,9 @@ async function getCurrencyLinks(livemode: boolean) {
|
|
|
121
123
|
|
|
122
124
|
type RevenueStats = {
|
|
123
125
|
totalRevenue: string;
|
|
126
|
+
refundAmount: string;
|
|
124
127
|
promotionCost: string;
|
|
128
|
+
creditGrantCost: string;
|
|
125
129
|
vendorCost: string;
|
|
126
130
|
taxedRevenue: string;
|
|
127
131
|
netRevenue: string;
|
|
@@ -129,7 +133,9 @@ type RevenueStats = {
|
|
|
129
133
|
|
|
130
134
|
const ZERO_REVENUE_STATS: RevenueStats = {
|
|
131
135
|
totalRevenue: '0',
|
|
136
|
+
refundAmount: '0',
|
|
132
137
|
promotionCost: '0',
|
|
138
|
+
creditGrantCost: '0',
|
|
133
139
|
vendorCost: '0',
|
|
134
140
|
taxedRevenue: '0',
|
|
135
141
|
netRevenue: '0',
|
|
@@ -202,6 +208,62 @@ async function getTaxedInvoiceIds(
|
|
|
202
208
|
|
|
203
209
|
async function getRevenueStats(livemode: boolean, currencyId?: string, start?: number, end?: number) {
|
|
204
210
|
const currencyFilter = currencyId ? [currencyId] : undefined;
|
|
211
|
+
const byCurrency: Record<string, RevenueStats> = {};
|
|
212
|
+
|
|
213
|
+
// Step 1: Query snapshots within the date range
|
|
214
|
+
const snapshotWhere: WhereOptions = { livemode };
|
|
215
|
+
if (currencyId) {
|
|
216
|
+
snapshotWhere.currency_id = currencyId;
|
|
217
|
+
}
|
|
218
|
+
if (start || end) {
|
|
219
|
+
snapshotWhere.timestamp = {};
|
|
220
|
+
if (start) {
|
|
221
|
+
(snapshotWhere.timestamp as any)[Op.gte] = start;
|
|
222
|
+
}
|
|
223
|
+
if (end) {
|
|
224
|
+
(snapshotWhere.timestamp as any)[Op.lt] = end;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const snapshots = await RevenueSnapshot.findAll({ where: snapshotWhere });
|
|
229
|
+
|
|
230
|
+
// Track the latest snapshot end timestamp to avoid double counting
|
|
231
|
+
// Each snapshot's timestamp is month start, so the data covers [timestamp, timestamp + 1 month)
|
|
232
|
+
let latestSnapshotEnd = 0;
|
|
233
|
+
|
|
234
|
+
for (const snapshot of snapshots) {
|
|
235
|
+
const id = snapshot.currency_id;
|
|
236
|
+
if (!byCurrency[id]) {
|
|
237
|
+
byCurrency[id] = { ...ZERO_REVENUE_STATS };
|
|
238
|
+
}
|
|
239
|
+
const entry = byCurrency[id]!;
|
|
240
|
+
entry.totalRevenue = new BN(entry.totalRevenue).add(new BN(snapshot.total_revenue || '0')).toString();
|
|
241
|
+
entry.refundAmount = new BN(entry.refundAmount).add(new BN(snapshot.refund_amount || '0')).toString();
|
|
242
|
+
entry.promotionCost = new BN(entry.promotionCost).add(new BN(snapshot.promotion_cost || '0')).toString();
|
|
243
|
+
entry.creditGrantCost = new BN(entry.creditGrantCost).add(new BN(snapshot.credit_grant_cost || '0')).toString();
|
|
244
|
+
entry.vendorCost = new BN(entry.vendorCost).add(new BN(snapshot.vendor_cost || '0')).toString();
|
|
245
|
+
entry.taxedRevenue = new BN(entry.taxedRevenue).add(new BN(snapshot.taxed_revenue || '0')).toString();
|
|
246
|
+
entry.netRevenue = new BN(entry.netRevenue).add(new BN(snapshot.net_revenue || '0')).toString();
|
|
247
|
+
|
|
248
|
+
// Calculate the end of this snapshot's coverage (start of next month)
|
|
249
|
+
const snapshotEndTimestamp = dayjs.unix(snapshot.timestamp).add(1, 'month').startOf('month').unix();
|
|
250
|
+
if (snapshotEndTimestamp > latestSnapshotEnd) {
|
|
251
|
+
latestSnapshotEnd = snapshotEndTimestamp;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Step 2: Query live data only for time AFTER the latest snapshot
|
|
256
|
+
// This prevents double counting data that's already in snapshots
|
|
257
|
+
const liveDataStart = latestSnapshotEnd > 0 ? Math.max(latestSnapshotEnd, start || 0) : start;
|
|
258
|
+
const liveDataEnd = end;
|
|
259
|
+
|
|
260
|
+
// Skip live data query if the requested range is entirely covered by snapshots
|
|
261
|
+
if (liveDataStart && liveDataEnd && liveDataStart >= liveDataEnd) {
|
|
262
|
+
if (Object.keys(byCurrency).length === 0 && currencyId) {
|
|
263
|
+
byCurrency[currencyId] = { ...ZERO_REVENUE_STATS };
|
|
264
|
+
}
|
|
265
|
+
return byCurrency;
|
|
266
|
+
}
|
|
205
267
|
|
|
206
268
|
const invoiceWhere: WhereOptions = {
|
|
207
269
|
livemode,
|
|
@@ -214,13 +276,13 @@ async function getRevenueStats(livemode: boolean, currencyId?: string, start?: n
|
|
|
214
276
|
invoiceWhere.currency_id = invoiceCurrencyFilter;
|
|
215
277
|
}
|
|
216
278
|
|
|
217
|
-
if (
|
|
279
|
+
if (liveDataStart || liveDataEnd) {
|
|
218
280
|
invoiceWhere.created_at = {};
|
|
219
|
-
if (
|
|
220
|
-
invoiceWhere.created_at[Op.gte] = new Date(
|
|
281
|
+
if (liveDataStart) {
|
|
282
|
+
invoiceWhere.created_at[Op.gte] = new Date(liveDataStart * 1000);
|
|
221
283
|
}
|
|
222
|
-
if (
|
|
223
|
-
invoiceWhere.created_at[Op.lt] = new Date(
|
|
284
|
+
if (liveDataEnd) {
|
|
285
|
+
invoiceWhere.created_at[Op.lt] = new Date(liveDataEnd * 1000);
|
|
224
286
|
}
|
|
225
287
|
}
|
|
226
288
|
|
|
@@ -229,7 +291,7 @@ async function getRevenueStats(livemode: boolean, currencyId?: string, start?: n
|
|
|
229
291
|
where: invoiceWhere,
|
|
230
292
|
attributes: ['id', 'total', 'total_discount_amounts', 'currency_id'],
|
|
231
293
|
}),
|
|
232
|
-
getTaxedInvoiceIds(livemode, currencyFilter,
|
|
294
|
+
getTaxedInvoiceIds(livemode, currencyFilter, liveDataStart, liveDataEnd),
|
|
233
295
|
]);
|
|
234
296
|
|
|
235
297
|
const totalRevenueByCurrency: Record<string, string> = {};
|
|
@@ -255,6 +317,7 @@ async function getRevenueStats(livemode: boolean, currencyId?: string, start?: n
|
|
|
255
317
|
}
|
|
256
318
|
});
|
|
257
319
|
|
|
320
|
+
// Query payouts for live data period
|
|
258
321
|
const payoutWhere: WhereOptions = {
|
|
259
322
|
livemode,
|
|
260
323
|
status: { [Op.in]: ['paid', 'deferred'] },
|
|
@@ -266,13 +329,13 @@ async function getRevenueStats(livemode: boolean, currencyId?: string, start?: n
|
|
|
266
329
|
payoutWhere.currency_id = payoutCurrencyFilter;
|
|
267
330
|
}
|
|
268
331
|
|
|
269
|
-
if (
|
|
332
|
+
if (liveDataStart || liveDataEnd) {
|
|
270
333
|
payoutWhere.created_at = {};
|
|
271
|
-
if (
|
|
272
|
-
payoutWhere.created_at[Op.gte] = new Date(
|
|
334
|
+
if (liveDataStart) {
|
|
335
|
+
payoutWhere.created_at[Op.gte] = new Date(liveDataStart * 1000);
|
|
273
336
|
}
|
|
274
|
-
if (
|
|
275
|
-
payoutWhere.created_at[Op.lt] = new Date(
|
|
337
|
+
if (liveDataEnd) {
|
|
338
|
+
payoutWhere.created_at[Op.lt] = new Date(liveDataEnd * 1000);
|
|
276
339
|
}
|
|
277
340
|
}
|
|
278
341
|
|
|
@@ -286,32 +349,116 @@ async function getRevenueStats(livemode: boolean, currencyId?: string, start?: n
|
|
|
286
349
|
addToMap(vendorCostByCurrency, payout.currency_id, payout.amount || '0');
|
|
287
350
|
});
|
|
288
351
|
|
|
352
|
+
// Query refunds for live data period
|
|
353
|
+
const refundWhere: WhereOptions = {
|
|
354
|
+
livemode,
|
|
355
|
+
status: 'succeeded',
|
|
356
|
+
};
|
|
357
|
+
const refundCurrencyFilter = buildCurrencyFilter(currencyFilter);
|
|
358
|
+
if (refundCurrencyFilter) {
|
|
359
|
+
refundWhere.currency_id = refundCurrencyFilter;
|
|
360
|
+
}
|
|
361
|
+
if (liveDataStart || liveDataEnd) {
|
|
362
|
+
refundWhere.created_at = {};
|
|
363
|
+
if (liveDataStart) {
|
|
364
|
+
refundWhere.created_at[Op.gte] = new Date(liveDataStart * 1000);
|
|
365
|
+
}
|
|
366
|
+
if (liveDataEnd) {
|
|
367
|
+
refundWhere.created_at[Op.lt] = new Date(liveDataEnd * 1000);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const refunds = await Refund.findAll({
|
|
372
|
+
where: refundWhere,
|
|
373
|
+
attributes: ['amount', 'currency_id'],
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const refundAmountByCurrency: Record<string, string> = {};
|
|
377
|
+
refunds.forEach((refund) => {
|
|
378
|
+
addToMap(refundAmountByCurrency, refund.currency_id, refund.amount || '0');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Query credit grants for live data period
|
|
382
|
+
// Include granted (active), depleted, and expired - all have consumed portions
|
|
383
|
+
// Exclude: pending (not yet active), voided (revoked, not a cost)
|
|
384
|
+
// Cost = amount - remaining_amount (the actually consumed portion)
|
|
385
|
+
const creditGrantWhere: WhereOptions = {
|
|
386
|
+
livemode,
|
|
387
|
+
status: { [Op.in]: ['granted', 'depleted', 'expired'] },
|
|
388
|
+
};
|
|
389
|
+
const creditGrantCurrencyFilter = buildCurrencyFilter(currencyFilter);
|
|
390
|
+
if (creditGrantCurrencyFilter) {
|
|
391
|
+
creditGrantWhere.currency_id = creditGrantCurrencyFilter;
|
|
392
|
+
}
|
|
393
|
+
if (liveDataStart || liveDataEnd) {
|
|
394
|
+
creditGrantWhere.created_at = {};
|
|
395
|
+
if (liveDataStart) {
|
|
396
|
+
creditGrantWhere.created_at[Op.gte] = new Date(liveDataStart * 1000);
|
|
397
|
+
}
|
|
398
|
+
if (liveDataEnd) {
|
|
399
|
+
creditGrantWhere.created_at[Op.lt] = new Date(liveDataEnd * 1000);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const creditGrants = await CreditGrant.findAll({
|
|
404
|
+
where: creditGrantWhere,
|
|
405
|
+
attributes: ['amount', 'remaining_amount', 'currency_id'],
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const creditGrantCostByCurrency: Record<string, string> = {};
|
|
409
|
+
creditGrants.forEach((grant) => {
|
|
410
|
+
// Cost = consumed amount = total amount - remaining amount
|
|
411
|
+
const consumed = new BN(grant.amount || '0').sub(new BN(grant.remaining_amount || '0'));
|
|
412
|
+
addToMap(creditGrantCostByCurrency, grant.currency_id, consumed.toString());
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Merge live data with snapshot data
|
|
289
416
|
const currencySet = new Set<string>(currencyFilter || []);
|
|
290
|
-
[
|
|
417
|
+
[
|
|
418
|
+
totalRevenueByCurrency,
|
|
419
|
+
promotionCostByCurrency,
|
|
420
|
+
taxedRevenueByCurrency,
|
|
421
|
+
vendorCostByCurrency,
|
|
422
|
+
refundAmountByCurrency,
|
|
423
|
+
creditGrantCostByCurrency,
|
|
424
|
+
].forEach((map) => {
|
|
291
425
|
Object.keys(map).forEach((id) => currencySet.add(id));
|
|
292
426
|
});
|
|
427
|
+
Object.keys(byCurrency).forEach((id) => currencySet.add(id));
|
|
293
428
|
|
|
294
429
|
const currencyList = Array.from(currencySet);
|
|
295
|
-
const byCurrency: Record<string, RevenueStats> = {};
|
|
296
430
|
|
|
297
431
|
currencyList.forEach((id) => {
|
|
298
|
-
const
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
const
|
|
432
|
+
const snapshotData = byCurrency[id] || ZERO_REVENUE_STATS;
|
|
433
|
+
const liveTotal = totalRevenueByCurrency[id] || '0';
|
|
434
|
+
const liveRefund = refundAmountByCurrency[id] || '0';
|
|
435
|
+
const livePromotion = promotionCostByCurrency[id] || '0';
|
|
436
|
+
const liveCreditGrant = creditGrantCostByCurrency[id] || '0';
|
|
437
|
+
const liveTaxed = taxedRevenueByCurrency[id] || '0';
|
|
438
|
+
const liveVendor = vendorCostByCurrency[id] || '0';
|
|
439
|
+
|
|
440
|
+
const totalRevenue = new BN(snapshotData.totalRevenue).add(new BN(liveTotal)).toString();
|
|
441
|
+
const refundAmount = new BN(snapshotData.refundAmount).add(new BN(liveRefund)).toString();
|
|
442
|
+
const promotionCost = new BN(snapshotData.promotionCost).add(new BN(livePromotion)).toString();
|
|
443
|
+
const creditGrantCost = new BN(snapshotData.creditGrantCost).add(new BN(liveCreditGrant)).toString();
|
|
444
|
+
const taxedRevenue = new BN(snapshotData.taxedRevenue).add(new BN(liveTaxed)).toString();
|
|
445
|
+
const vendorCost = new BN(snapshotData.vendorCost).add(new BN(liveVendor)).toString();
|
|
446
|
+
// Net Revenue = Total Revenue - Refunds
|
|
447
|
+
const netRevenue = new BN(totalRevenue).sub(new BN(refundAmount)).toString();
|
|
302
448
|
|
|
303
449
|
const includeEntry =
|
|
304
|
-
hasNonZeroValue([totalRevenue, promotionCost, taxedRevenue, vendorCost]) ||
|
|
450
|
+
hasNonZeroValue([totalRevenue, refundAmount, promotionCost, creditGrantCost, taxedRevenue, vendorCost]) ||
|
|
451
|
+
Boolean(currencyFilter);
|
|
305
452
|
|
|
306
453
|
if (!includeEntry) {
|
|
307
454
|
return;
|
|
308
455
|
}
|
|
309
456
|
|
|
310
|
-
const netRevenue = new BN(totalRevenue).sub(new BN(vendorCost)).toString();
|
|
311
|
-
|
|
312
457
|
byCurrency[id] = {
|
|
313
458
|
totalRevenue,
|
|
459
|
+
refundAmount,
|
|
314
460
|
promotionCost,
|
|
461
|
+
creditGrantCost,
|
|
315
462
|
vendorCost,
|
|
316
463
|
taxedRevenue,
|
|
317
464
|
netRevenue,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Migration } from '../migrate';
|
|
2
|
+
import models from '../models';
|
|
3
|
+
|
|
4
|
+
export const up: Migration = async ({ context }) => {
|
|
5
|
+
await context.createTable('archive_metadata', models.ArchiveMetadata.GENESIS_ATTRIBUTES);
|
|
6
|
+
await context.createTable('archive_locks', models.ArchiveLock.GENESIS_ATTRIBUTES);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const down: Migration = async ({ context }) => {
|
|
10
|
+
await context.dropTable('archive_metadata');
|
|
11
|
+
await context.dropTable('archive_locks');
|
|
12
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Migration } from '../migrate';
|
|
2
|
+
import { createIndexIfNotExists } from '../migrate';
|
|
3
|
+
import models from '../models';
|
|
4
|
+
|
|
5
|
+
export const up: Migration = async ({ context }) => {
|
|
6
|
+
await context.createTable('revenue_snapshots', models.RevenueSnapshot.GENESIS_ATTRIBUTES);
|
|
7
|
+
await createIndexIfNotExists(
|
|
8
|
+
context,
|
|
9
|
+
'revenue_snapshots',
|
|
10
|
+
['timestamp', 'currency_id', 'livemode', 'period_type'],
|
|
11
|
+
'idx_revenue_snapshots_unique',
|
|
12
|
+
{ unique: true }
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const down: Migration = async ({ context }) => {
|
|
17
|
+
await context.removeIndex('revenue_snapshots', 'idx_revenue_snapshots_unique');
|
|
18
|
+
await context.dropTable('revenue_snapshots');
|
|
19
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
|
2
|
+
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
|
|
3
|
+
|
|
4
|
+
export class ArchiveLock extends Model<InferAttributes<ArchiveLock>, InferCreationAttributes<ArchiveLock>> {
|
|
5
|
+
declare id: CreationOptional<string>;
|
|
6
|
+
declare locked_by?: string | null;
|
|
7
|
+
declare locked_at?: number | null;
|
|
8
|
+
declare expires_at?: number | null;
|
|
9
|
+
declare created_at: CreationOptional<Date>;
|
|
10
|
+
declare updated_at: CreationOptional<Date>;
|
|
11
|
+
|
|
12
|
+
public static readonly GENESIS_ATTRIBUTES = {
|
|
13
|
+
id: {
|
|
14
|
+
type: DataTypes.STRING(40),
|
|
15
|
+
primaryKey: true,
|
|
16
|
+
allowNull: false,
|
|
17
|
+
},
|
|
18
|
+
locked_by: {
|
|
19
|
+
type: DataTypes.STRING(64),
|
|
20
|
+
allowNull: true,
|
|
21
|
+
},
|
|
22
|
+
locked_at: {
|
|
23
|
+
type: DataTypes.INTEGER,
|
|
24
|
+
allowNull: true,
|
|
25
|
+
},
|
|
26
|
+
expires_at: {
|
|
27
|
+
type: DataTypes.INTEGER,
|
|
28
|
+
allowNull: true,
|
|
29
|
+
},
|
|
30
|
+
created_at: {
|
|
31
|
+
type: DataTypes.DATE,
|
|
32
|
+
defaultValue: DataTypes.NOW,
|
|
33
|
+
allowNull: false,
|
|
34
|
+
},
|
|
35
|
+
updated_at: {
|
|
36
|
+
type: DataTypes.DATE,
|
|
37
|
+
defaultValue: DataTypes.NOW,
|
|
38
|
+
allowNull: false,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
public static initialize(sequelize: any) {
|
|
43
|
+
this.init(ArchiveLock.GENESIS_ATTRIBUTES, {
|
|
44
|
+
sequelize,
|
|
45
|
+
modelName: 'ArchiveLock',
|
|
46
|
+
tableName: 'archive_locks',
|
|
47
|
+
createdAt: 'created_at',
|
|
48
|
+
updatedAt: 'updated_at',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public static associate() {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type TArchiveLock = InferAttributes<ArchiveLock>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
|
2
|
+
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
|
|
3
|
+
|
|
4
|
+
export class ArchiveMetadata extends Model<InferAttributes<ArchiveMetadata>, InferCreationAttributes<ArchiveMetadata>> {
|
|
5
|
+
declare id: CreationOptional<string>;
|
|
6
|
+
declare archive_file: string;
|
|
7
|
+
declare created_at: CreationOptional<Date>;
|
|
8
|
+
declare updated_at: CreationOptional<Date>;
|
|
9
|
+
|
|
10
|
+
declare date_range_start: number;
|
|
11
|
+
declare date_range_end: number;
|
|
12
|
+
|
|
13
|
+
declare tables: Record<
|
|
14
|
+
string,
|
|
15
|
+
{
|
|
16
|
+
archived_count: number;
|
|
17
|
+
deleted_count: number;
|
|
18
|
+
failed_count: number;
|
|
19
|
+
failed_ids?: string[];
|
|
20
|
+
}
|
|
21
|
+
>;
|
|
22
|
+
declare total_records: number;
|
|
23
|
+
|
|
24
|
+
declare checksum?: string;
|
|
25
|
+
declare file_size?: number;
|
|
26
|
+
|
|
27
|
+
declare duration_ms?: number;
|
|
28
|
+
declare triggered_by: 'cron' | 'manual';
|
|
29
|
+
declare triggered_by_user_id?: string;
|
|
30
|
+
declare status: 'completed' | 'failed' | 'in_progress';
|
|
31
|
+
declare error?: string;
|
|
32
|
+
|
|
33
|
+
declare query_count: number;
|
|
34
|
+
declare query_actor_ids?: string[];
|
|
35
|
+
declare last_queried_at?: number;
|
|
36
|
+
|
|
37
|
+
public static readonly GENESIS_ATTRIBUTES = {
|
|
38
|
+
id: {
|
|
39
|
+
type: DataTypes.STRING(40),
|
|
40
|
+
primaryKey: true,
|
|
41
|
+
allowNull: false,
|
|
42
|
+
},
|
|
43
|
+
archive_file: {
|
|
44
|
+
type: DataTypes.STRING(128),
|
|
45
|
+
allowNull: false,
|
|
46
|
+
},
|
|
47
|
+
date_range_start: {
|
|
48
|
+
type: DataTypes.INTEGER,
|
|
49
|
+
allowNull: false,
|
|
50
|
+
},
|
|
51
|
+
date_range_end: {
|
|
52
|
+
type: DataTypes.INTEGER,
|
|
53
|
+
allowNull: false,
|
|
54
|
+
},
|
|
55
|
+
tables: {
|
|
56
|
+
type: DataTypes.JSON,
|
|
57
|
+
allowNull: false,
|
|
58
|
+
defaultValue: {},
|
|
59
|
+
},
|
|
60
|
+
total_records: {
|
|
61
|
+
type: DataTypes.INTEGER,
|
|
62
|
+
allowNull: false,
|
|
63
|
+
defaultValue: 0,
|
|
64
|
+
},
|
|
65
|
+
checksum: {
|
|
66
|
+
type: DataTypes.STRING(128),
|
|
67
|
+
allowNull: true,
|
|
68
|
+
},
|
|
69
|
+
file_size: {
|
|
70
|
+
type: DataTypes.INTEGER,
|
|
71
|
+
allowNull: true,
|
|
72
|
+
},
|
|
73
|
+
duration_ms: {
|
|
74
|
+
type: DataTypes.INTEGER,
|
|
75
|
+
allowNull: true,
|
|
76
|
+
},
|
|
77
|
+
triggered_by: {
|
|
78
|
+
type: DataTypes.ENUM('cron', 'manual'),
|
|
79
|
+
allowNull: false,
|
|
80
|
+
},
|
|
81
|
+
triggered_by_user_id: {
|
|
82
|
+
type: DataTypes.STRING(64),
|
|
83
|
+
allowNull: true,
|
|
84
|
+
},
|
|
85
|
+
status: {
|
|
86
|
+
type: DataTypes.ENUM('completed', 'failed', 'in_progress'),
|
|
87
|
+
allowNull: false,
|
|
88
|
+
},
|
|
89
|
+
error: {
|
|
90
|
+
type: DataTypes.TEXT,
|
|
91
|
+
allowNull: true,
|
|
92
|
+
},
|
|
93
|
+
query_count: {
|
|
94
|
+
type: DataTypes.INTEGER,
|
|
95
|
+
allowNull: false,
|
|
96
|
+
defaultValue: 0,
|
|
97
|
+
},
|
|
98
|
+
query_actor_ids: {
|
|
99
|
+
type: DataTypes.JSON,
|
|
100
|
+
allowNull: true,
|
|
101
|
+
defaultValue: [],
|
|
102
|
+
},
|
|
103
|
+
last_queried_at: {
|
|
104
|
+
type: DataTypes.INTEGER,
|
|
105
|
+
allowNull: true,
|
|
106
|
+
},
|
|
107
|
+
created_at: {
|
|
108
|
+
type: DataTypes.DATE,
|
|
109
|
+
defaultValue: DataTypes.NOW,
|
|
110
|
+
allowNull: false,
|
|
111
|
+
},
|
|
112
|
+
updated_at: {
|
|
113
|
+
type: DataTypes.DATE,
|
|
114
|
+
defaultValue: DataTypes.NOW,
|
|
115
|
+
allowNull: false,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
public static initialize(sequelize: any) {
|
|
120
|
+
this.init(ArchiveMetadata.GENESIS_ATTRIBUTES, {
|
|
121
|
+
sequelize,
|
|
122
|
+
modelName: 'ArchiveMetadata',
|
|
123
|
+
tableName: 'archive_metadata',
|
|
124
|
+
createdAt: 'created_at',
|
|
125
|
+
updatedAt: 'updated_at',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public static associate() {}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type TArchiveMetadata = InferAttributes<ArchiveMetadata>;
|
|
@@ -36,6 +36,9 @@ import { ProductVendor } from './product-vendor';
|
|
|
36
36
|
import { TaxRate } from './tax-rate';
|
|
37
37
|
import { ExchangeRateProvider } from './exchange-rate-provider';
|
|
38
38
|
import { PriceQuote } from './price-quote';
|
|
39
|
+
import { ArchiveMetadata } from './archive-metadata';
|
|
40
|
+
import { ArchiveLock } from './archive-lock';
|
|
41
|
+
import { RevenueSnapshot } from './revenue-snapshot';
|
|
39
42
|
|
|
40
43
|
const models = {
|
|
41
44
|
CheckoutSession,
|
|
@@ -75,6 +78,9 @@ const models = {
|
|
|
75
78
|
TaxRate,
|
|
76
79
|
ExchangeRateProvider,
|
|
77
80
|
PriceQuote,
|
|
81
|
+
ArchiveMetadata,
|
|
82
|
+
ArchiveLock,
|
|
83
|
+
RevenueSnapshot,
|
|
78
84
|
};
|
|
79
85
|
|
|
80
86
|
export function initialize(sequelize: any) {
|
|
@@ -128,6 +134,9 @@ export * from './product-vendor';
|
|
|
128
134
|
export * from './tax-rate';
|
|
129
135
|
export * from './exchange-rate-provider';
|
|
130
136
|
export * from './price-quote';
|
|
137
|
+
export * from './archive-metadata';
|
|
138
|
+
export * from './archive-lock';
|
|
139
|
+
export * from './revenue-snapshot';
|
|
131
140
|
|
|
132
141
|
export type TPriceExpanded = TPrice & {
|
|
133
142
|
object: 'price';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/lines-between-class-members */
|
|
2
|
+
import { CreationOptional, DataTypes, InferAttributes, InferCreationAttributes, Model } from 'sequelize';
|
|
3
|
+
|
|
4
|
+
import { createIdGenerator } from '../../libs/util';
|
|
5
|
+
|
|
6
|
+
const nextId = createIdGenerator('rs', 24);
|
|
7
|
+
|
|
8
|
+
export class RevenueSnapshot extends Model<InferAttributes<RevenueSnapshot>, InferCreationAttributes<RevenueSnapshot>> {
|
|
9
|
+
declare id: CreationOptional<string>;
|
|
10
|
+
declare livemode: boolean;
|
|
11
|
+
declare currency_id: string;
|
|
12
|
+
|
|
13
|
+
declare timestamp: number;
|
|
14
|
+
declare period_type: 'monthly' | 'quarterly';
|
|
15
|
+
|
|
16
|
+
declare total_revenue: string;
|
|
17
|
+
declare refund_amount: string;
|
|
18
|
+
declare promotion_cost: string;
|
|
19
|
+
declare credit_grant_cost: string;
|
|
20
|
+
declare vendor_cost: string;
|
|
21
|
+
declare taxed_revenue: string;
|
|
22
|
+
declare net_revenue: string;
|
|
23
|
+
|
|
24
|
+
declare archive_metadata_id: CreationOptional<string>;
|
|
25
|
+
|
|
26
|
+
declare created_at: CreationOptional<Date>;
|
|
27
|
+
declare updated_at: CreationOptional<Date>;
|
|
28
|
+
|
|
29
|
+
public static readonly GENESIS_ATTRIBUTES = {
|
|
30
|
+
id: {
|
|
31
|
+
type: DataTypes.STRING(30),
|
|
32
|
+
primaryKey: true,
|
|
33
|
+
allowNull: false,
|
|
34
|
+
defaultValue: nextId,
|
|
35
|
+
},
|
|
36
|
+
livemode: {
|
|
37
|
+
type: DataTypes.BOOLEAN,
|
|
38
|
+
allowNull: false,
|
|
39
|
+
},
|
|
40
|
+
currency_id: {
|
|
41
|
+
type: DataTypes.STRING(16),
|
|
42
|
+
allowNull: false,
|
|
43
|
+
},
|
|
44
|
+
timestamp: {
|
|
45
|
+
type: DataTypes.INTEGER,
|
|
46
|
+
allowNull: false,
|
|
47
|
+
},
|
|
48
|
+
period_type: {
|
|
49
|
+
type: DataTypes.ENUM('monthly', 'quarterly'),
|
|
50
|
+
allowNull: false,
|
|
51
|
+
defaultValue: 'monthly',
|
|
52
|
+
},
|
|
53
|
+
total_revenue: {
|
|
54
|
+
type: DataTypes.STRING(64),
|
|
55
|
+
defaultValue: '0',
|
|
56
|
+
},
|
|
57
|
+
refund_amount: {
|
|
58
|
+
type: DataTypes.STRING(64),
|
|
59
|
+
defaultValue: '0',
|
|
60
|
+
},
|
|
61
|
+
promotion_cost: {
|
|
62
|
+
type: DataTypes.STRING(64),
|
|
63
|
+
defaultValue: '0',
|
|
64
|
+
},
|
|
65
|
+
credit_grant_cost: {
|
|
66
|
+
type: DataTypes.STRING(64),
|
|
67
|
+
defaultValue: '0',
|
|
68
|
+
},
|
|
69
|
+
vendor_cost: {
|
|
70
|
+
type: DataTypes.STRING(64),
|
|
71
|
+
defaultValue: '0',
|
|
72
|
+
},
|
|
73
|
+
taxed_revenue: {
|
|
74
|
+
type: DataTypes.STRING(64),
|
|
75
|
+
defaultValue: '0',
|
|
76
|
+
},
|
|
77
|
+
net_revenue: {
|
|
78
|
+
type: DataTypes.STRING(64),
|
|
79
|
+
defaultValue: '0',
|
|
80
|
+
},
|
|
81
|
+
archive_metadata_id: {
|
|
82
|
+
type: DataTypes.STRING(40),
|
|
83
|
+
allowNull: true,
|
|
84
|
+
},
|
|
85
|
+
created_at: {
|
|
86
|
+
type: DataTypes.DATE,
|
|
87
|
+
defaultValue: DataTypes.NOW,
|
|
88
|
+
allowNull: false,
|
|
89
|
+
},
|
|
90
|
+
updated_at: {
|
|
91
|
+
type: DataTypes.DATE,
|
|
92
|
+
defaultValue: DataTypes.NOW,
|
|
93
|
+
allowNull: false,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
public static initialize(sequelize: any) {
|
|
98
|
+
this.init(RevenueSnapshot.GENESIS_ATTRIBUTES, {
|
|
99
|
+
sequelize,
|
|
100
|
+
modelName: 'RevenueSnapshot',
|
|
101
|
+
tableName: 'revenue_snapshots',
|
|
102
|
+
createdAt: 'created_at',
|
|
103
|
+
updatedAt: 'updated_at',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public static associate() {}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type TRevenueSnapshot = InferAttributes<RevenueSnapshot>;
|