MatchMaster/server/laf-cloud/functions/wx/checkIos.ts
2026-09-30 22:30:22 +08:00

105 lines
5.0 KiB
TypeScript

import { associateLegacyOrder, matchesCustomerLinkTime } from "@/goldMiner/legacyPayment";
import cloud from '@lafjs/cloud'
const db = cloud.database();
export default async function (ctx: FunctionContext) {
const body = ctx.body || {};
const paymentAppEnv = process.env.PAYMENT_APP_ENV || "production";
if (!["production", "test"].includes(paymentAppEnv) || typeof body.outTradeNo !== "string" || !body.outTradeNo) return false;
const testOrder = body.outTradeNo.startsWith("wct_");
if (testOrder && paymentAppEnv === "production") return forwardTestCheckIos(ctx);
if (!testOrder && paymentAppEnv === "test") return false;
console.log("检查提醒");
let openid = ctx.body.openid;
let time = ctx.body.time;
let outTradeNo = ctx.body.outTradeNo;
const canonical = typeof outTradeNo === "string" ? (await db.collection("order").where({ outTradeNo }).getOne()).data : null;
const compatibility = typeof outTradeNo === "string" ? (await db.collection("iosOrder").where({ outTradeNo }).getOne()).data : null;
if (canonical?.goldMiner || compatibility?.goldMiner || String(canonical?.itemid || compatibility?.itemid || "").startsWith("gold_miner")) {
try { return await associateLegacyOrder(ctx.body, canonical, compatibility); }
catch (error) {
const order = canonical || compatibility;
const errorCode = typeof error?.errorCode === "string" ? error.errorCode : "RETRYABLE";
const runtimeMessage = typeof error?.message === "string"
&& /^[A-Za-z_$][A-Za-z0-9_$.]*( is not defined| is not a function)$/.test(error.message) ? error.message : null;
const message = errorCode !== "RETRYABLE" && typeof error?.message === "string"
? error.message : runtimeMessage || "转单发生内部异常,请检查服务端调用链";
console.error("[GoldMiner checkIos] rejected", {
outTradeNo, errorCode, message, errorName: error?.name,
paymentAppEnv: process.env.PAYMENT_APP_ENV || "production", orderEnv: order.paymentAppEnv,
orderFound: !!canonical, iosOrderFound: !!compatibility,
timeMatches: matchesCustomerLinkTime(time, order),
serverTime: Date.now(), startsAt: order.goldMiner?.startsAt, endsAt: order.goldMiner?.endsAt,
});
return false;
}
}
if (!canonical && !compatibility) {
console.error("[checkIos] order not found", {
outTradeNo, paymentAppEnv: process.env.PAYMENT_APP_ENV || "production",
testOrder: typeof outTradeNo === "string" && outTradeNo.startsWith("wct_"),
});
}
if (!openid || !time) {
return false;
}
let user = await db.collection("users").where({ openid: openid }).getOne();
if (user.data) {
if (user.data.iosTime == time) {
let res = await db.collection("iosOrder").where({ outTradeNo: outTradeNo }).getOne();
if (res.data) {
await db.collection("order").add({
...(res.data.passVersion === 2 ? { passVersion: 2, passEnd: res.data.passEnd } : {}),
openid: res.data.openid,
outTradeNo: res.data.outTradeNo,
itemid: res.data.itemid,
...(res.data.activityType ? { activityType: res.data.activityType } : {}),
...(res.data.rewardVersion ? { rewardVersion: res.data.rewardVersion } : {}),
...(res.data.paymentAppEnv ? { paymentAppEnv: res.data.paymentAppEnv } : {}),
itemCount: res.data.itemCount,
goodsPrice: res.data.goodsPrice,
time: res.data.time,
orderTime: res.data.time,
chargeTime: 0,
getTime: 0,
type: "ios",
state: 0,
});
db.collection("iosOrder").where({ outTradeNo: outTradeNo }).remove({ multi: true });
return true;
} else {
let res = await db.collection("order").where({ outTradeNo: outTradeNo }).getOne();
if (res.data) {
console.log("验证成功");
return true;
}
return false;
}
}
}
return false;
}
async function forwardTestCheckIos(ctx: FunctionContext): Promise<boolean> {
if (Object.keys(ctx.headers || {}).some(name => name.toLowerCase() === "x-payment-forwarded")) return false;
try {
// Server-owned destination only. Preserve credentials/tickets for validation by the test app.
const configured = process.env.TEST_WX_CHECK_IOS_URL;
const target = configured ? new URL(configured) : new URL("/wx/checkIos", process.env.TEST_WX_PAY_NOTIFY_URL);
if (target.protocol !== "https:" || target.username || target.password || target.hash
|| target.search || target.pathname !== "/wx/checkIos") return false;
const response = await cloud.fetch({
url: target.href,
method: "POST",
data: ctx.body,
headers: { "content-type": "application/json", "x-payment-forwarded": "1" },
timeout: 3000,
maxRedirects: 0,
});
// Association success is not payment confirmation; retain the existing boolean protocol.
return response.status === 200 && response.data === true;
} catch {
console.error("测试订单 checkIos 转发失败");
return false;
}
}