105 lines
5.0 KiB
TypeScript
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;
|
|
}
|
|
}
|