import * as ConfigStore from "@/activityConfig/store"; import { reconcileLegacyOrder } from "@/goldMiner/legacyPayment"; import { TABLES as T } from "@/goldMiner/config"; import { collection, currentPeriod, closePlayer, activateEntitlement } from "@/goldMiner/service"; import { fulfillOrder } from "@/goldMiner/payment"; /** Bounded pending-state batches: successful rows leave the queue; failures back off. */ export default async function () { const result: any = { players: 0, entitlements: 0, orders: 0, merchant: 0, failures: 0 }; try { await currentPeriod(); } catch { result.failures++; console.error("goldMiner current configuration requires review"); } const due = (field: string) => ({ $or: [{ [field]: { $exists: false } }, { [field]: { $lte: Date.now() } }] }); const scans = [ { name: "merchant", table: "order", batch: 5, retry: "goldMiner.nextQueryAt", filter: { "goldMiner.paymentChannel": "legacy_ios", "goldMiner.confirmedAt": 0, "goldMiner.prepayState": { $nin: ["closed", "paid"] }, "goldMiner.nextQueryAt": { $lte: Date.now() }, "goldMiner.ioLeaseUntil": { $lte: Date.now() } }, run: reconcileLegacyOrder }, { name: "entitlements", table: T.players, retry: "entitlementNextRetryAt", filter: { "entitlement.state": "reserved", "entitlement.activatesAt": { $lte: Date.now() }, ...due("entitlementNextRetryAt") }, run: (r: any) => activateEntitlement(r.entitlement) }, { name: "players", table: T.players, retry: "settlementNextRetryAt", filter: { endsAt: { $lte: Date.now() }, tasks: { $exists: true }, settlementPreparedAt: null, ...due("settlementNextRetryAt") }, run: (r: any) => closePlayer(r._id) }, { name: "orders", table: "order", retry: "goldMiner.nextRetryAt", filter: { "goldMiner.confirmedAt": { $gt: 0 }, "goldMiner.fulfillmentStatus": { $in: ["pending", "processing", "retryable"] }, ...due("goldMiner.nextRetryAt") }, run: (r: any) => fulfillOrder(r.outTradeNo) }, ]; for (const scan of scans) { const rows = await collection(scan.table).find(scan.filter).sort({ [scan.retry]: 1, _id: 1 }).limit(scan.batch || 100).toArray(); for (const row of rows) { try { await scan.run(row); result[scan.name]++; } catch { result.failures++; // Keep a longer merchant/provider backoff already recorded by the handler. const nextRetryAt = Date.now() + 60000; try { await collection(scan.table).updateOne({ _id: row._id, $or: [{ [scan.retry]: { $exists: false } }, { [scan.retry]: { $lt: nextRetryAt } }] }, { $set: { [scan.retry]: nextRetryAt } }); } catch { console.error("goldMiner retry scheduling failed", scan.name, String(row._id)); } console.error("goldMiner job retry required", scan.name, String(row._id)); } } } return result; } export async function setupIndexes() { await ConfigStore.setupIndexes(); await collection(T.players).createIndex({ uid: 1, periodId: 1 }, { unique: true }); await collection(T.players).createIndex({ uid: 1, endsAt: 1, _id: 1 }); await collection(T.players).createIndex({ settlementPreparedAt: 1, settlementNextRetryAt: 1, endsAt: 1 }); await collection(T.players).createIndex({ "entitlement.sourceOrderNo": 1 }, { unique: true, partialFilterExpression: { "entitlement.sourceOrderNo": { $type: "string" } } }); await collection(T.players).createIndex({ "entitlement.state": 1, "entitlement.activatesAt": 1, entitlementNextRetryAt: 1 }); await collection(T.players).createIndex({ settlementId: 1 }, { unique: true, partialFilterExpression: { settlementId: { $type: "string" } } }); await collection("order").createIndex({ outTradeNo: 1 }, { unique: true, partialFilterExpression: { "goldMiner.schemaVersion": 1 }, name: "goldMiner_order_no" }); await collection("order").createIndex({ "goldMiner.uid": 1, "goldMiner.periodId": 1 }, { unique: true, partialFilterExpression: { "goldMiner.schemaVersion": 1 }, name: "goldMiner_player_period" }); await collection("order").createIndex({ "goldMiner.merchantSnapshot.mchid": 1, "goldMiner.transactionId": 1 }, { unique: true, partialFilterExpression: { "goldMiner.paymentChannel": "legacy_ios", "goldMiner.transactionId": { $type: "string" } }, name: "goldMiner_merchant_transaction" }); await collection("order").createIndex({ "goldMiner.paymentChannel": 1, "goldMiner.confirmedAt": 1, "goldMiner.nextQueryAt": 1 }); await collection("order").createIndex({ "goldMiner.fulfillmentStatus": 1, "goldMiner.confirmedAt": 1, _id: 1 }); }