This commit is contained in:
eric
2026-03-10 12:37:36 -05:00
parent 75b21f3e6f
commit 1628efd971
2 changed files with 159 additions and 34 deletions

View File

@@ -16,12 +16,47 @@ import { AtForm, AtMessage, AtDrawer, AtIcon } from "taro-ui";
import { marked } from "marked";
import infoText from "./info.md";
import bookText from "./book.md";
import { getPayEnv, getDeviceFromEnv } from "../../utils/payEnv";
// import youtubeIco from "../../images/youtube.ico";
import hackrobot from "../../images/hackrobot.jpg";
// PocketBase 配置
const PB_URL = "https://pocketbase.hackrobot.cn";
const pb = new PocketBase(PB_URL);
/** PC 扫码支付:生成带二维码和轮询的 HTML 页面 */
function buildQrcodePageHtml(imgSrc, orderId, returnUrl, apiBase) {
const esc = (s) =>
String(s)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
const statusUrl = `${apiBase}/nomadvip/order_status?order_id=${encodeURIComponent(orderId)}`;
return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>微信扫码支付</title>
<style>*{box-sizing:border-box;}body{font-family:system-ui;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);}
.card{background:#fff;border-radius:20px;padding:40px;box-shadow:0 25px 50px -12px rgba(0,0,0,.25);max-width:420px;}
h2{color:#1e293b;margin:0 0 8px;font-size:1.5rem;}.hint{color:#64748b;font-size:14px;margin-bottom:20px;}
.qr-wrap img{width:200px;height:200px;border-radius:12px;border:2px solid #e2e8f0;background:#fff;}
.tip{font-size:13px;color:#64748b;margin-top:16px;}.success-wrap{display:none;}.success-wrap.show{display:block;}
.wait-wrap.hide{display:none;}.success-icon{font-size:4rem;margin-bottom:16px;}
.success-title{font-size:1.25rem;color:#16a34a;font-weight:600;margin-bottom:8px;}
.success-desc{color:#64748b;font-size:14px;margin-bottom:24px;}
</style></head><body><div class="card">
<div class="wait-wrap" id="waitWrap"><h2>微信扫码支付</h2><p class="hint">请使用微信扫描下方二维码完成支付</p>
<div class="qr-wrap"><img src="${esc(imgSrc)}" alt="支付二维码"/></div><p class="tip" id="tip">等待支付中...</p></div>
<div class="success-wrap" id="successWrap"><div class="success-icon">✅</div><div class="success-title">支付成功</div>
<div class="success-desc">感谢您的支持,正在返回...</div></div></div>
<script>
(function(){var orderId="${esc(orderId)}";var returnUrl="${esc(returnUrl)}";var statusUrl="${esc(statusUrl)}";
var waitWrap=document.getElementById("waitWrap");var successWrap=document.getElementById("successWrap");
function showSuccess(){waitWrap.classList.add("hide");successWrap.classList.add("show");
setTimeout(function(){window.location.href=returnUrl;},1500);}
function check(){fetch(statusUrl).then(function(r){return r.json();}).then(function(d){
if(d&&d.paid){showSuccess();return;}setTimeout(check,1000);}).catch(function(){setTimeout(check,1000);});}
setTimeout(check,1000);})();
</script></body></html>`;
}
marked.setOptions({
breaks: true, // 是否回车换行
gfm: true, //使用经批准的 GitHub Flavored Markdown (GFM) 规范
@@ -242,26 +277,28 @@ const Book = () => {
}
};
const payh51111 = (type, total_fee = 1000) => {
/** 微信内:xorpay 收银台 */
const payWechatXorpay = (type, total_fee) => {
const userId = getOrCreateUserId();
const returnUrl = window.location.href;
Taro.request({
method: "POST",
url: `${process.env.TARO_API_API}/payh5`,
url: `${process.env.TARO_API_API}/nomadvip/payh5`,
data: {
total_fee: total_fee,
type: type,
// callback_url 临时不传
},
header: {
"content-type": "application/json",
user_id: userId,
total_fee,
type,
channel: "wxpay",
return_url: returnUrl,
provider: "xorpay",
},
header: { "content-type": "application/json" },
success: (res) => {
if (res.data?.status === "ok" && res.data?.xorpay_params) {
// 创建隐藏 form POST 到 XorPay
const form = document.createElement("form");
form.method = "POST";
form.action = res.data.xorpay_url;
form.style.display = "none";
for (const key in res.data.xorpay_params) {
const input = document.createElement("input");
input.type = "hidden";
@@ -269,25 +306,81 @@ const Book = () => {
input.value = res.data.xorpay_params[key];
form.appendChild(input);
}
document.body.appendChild(form);
form.submit();
} else {
Taro.showToast({
title: "支付发起失败",
icon: "none",
});
Taro.showToast({ title: "支付发起失败", icon: "none" });
}
},
fail: () => {
Taro.showToast({
title: "网络错误",
icon: "none",
});
},
fail: () => Taro.showToast({ title: "网络错误", icon: "none" }),
});
};
/** PC:zpay 扫码,新窗口展示二维码 */
const payPcZpay = (type, total_fee) => {
const userId = getOrCreateUserId();
const returnUrl = window.location.href;
Taro.request({
method: "POST",
url: `${process.env.TARO_API_API}/nomadvip/payh5/redirect`,
data: {
user_id: userId,
total_fee,
type,
channel: "wxpay",
return_url: returnUrl,
device: "pc",
provider: "zpay",
},
header: { "content-type": "application/json" },
success: (res) => {
const d = res.data;
if (d?.status === "ok" && d?.qrcode_mode && d?.img) {
const apiBase = (process.env.TARO_API_API || "").replace(/\/$/, "");
const html = buildQrcodePageHtml(d.img, d.order_id, d.return_url || returnUrl, apiBase);
const win = window.open("", "_blank");
if (win) {
win.document.write(html);
win.document.close();
} else {
Taro.showToast({ title: "请允许弹窗以显示支付二维码", icon: "none" });
}
} else {
Taro.showToast({ title: "支付发起失败", icon: "none" });
}
},
fail: () => Taro.showToast({ title: "网络错误", icon: "none" }),
});
};
/** 手机浏览器:zpay 唤醒微信,form 提交到 redirect 接口触发 302 */
const payH5Zpay = (type, total_fee) => {
const userId = getOrCreateUserId();
const returnUrl = window.location.href;
const form = document.createElement("form");
form.method = "POST";
form.action = `${(process.env.TARO_API_API || "").replace(/\/$/, "")}/nomadvip/payh5/redirect`;
form.style.display = "none";
const fields = [
["user_id", userId],
["total_fee", String(total_fee)],
["type", type],
["channel", "wxpay"],
["return_url", returnUrl],
["device", "h5"],
["provider", "zpay"],
];
fields.forEach(([k, v]) => {
const input = document.createElement("input");
input.type = "hidden";
input.name = k;
input.value = v;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
};
// 生成或获取 userId(修改为无下划线)
const getOrCreateUserId = () => {
let userId = Taro.getStorageSync("userId");
@@ -300,8 +393,22 @@ const Book = () => {
return userId;
};
const payh5 = (type, total_fee = 1000) => {
const userId = getOrCreateUserId(); // ← 新增:获取 userId
/** 统一支付入口:PC扫码(zpay) / 手机浏览器唤醒(zpay) / 微信内(xorpay) */
const handlePay = (type, total_fee) => {
const env = getPayEnv();
const device = getDeviceFromEnv(env);
if (device === "wechat") {
payWechatXorpay(type, total_fee);
} else if (device === "pc") {
payPcZpay(type, total_fee);
} else {
payH5Zpay(type, total_fee);
}
};
/* eslint-disable-next-line no-unused-vars */
const _legacyPayh5 = (type, total_fee) => {
const userId = getOrCreateUserId();
Taro.request({
method: "POST",
url: `${process.env.TARO_API_API}/payh5`,
@@ -455,17 +562,8 @@ const Book = () => {
size="normal"
className="joinSalon"
onClick={() => {
if (paidType == "book" || vipbook) {
return false;
} else {
if (!!isWeChat) {
//199元
payh5("book", 19900);
// window.location.href = `https://vip.hackrobot.cn`;
} else {
window.location.href = `https://mp.weixin.qq.com/s/PBbnS8qC_kqhL9HJO5m-Iw`;
}
}
if (paidType == "book" || vipbook) return;
handlePay("book", 19900);
}}
>
{`🔒阅读更多`}

27
src/utils/payEnv.ts Normal file
View File

@@ -0,0 +1,27 @@
/**
* 支付环境检测:PC / H5 / 微信浏览器
* 参考 nomadvip app/lib/payEnv.ts
*/
export function isWeChat() {
if (typeof navigator === "undefined") return false;
return /MicroMessenger/i.test(navigator.userAgent);
}
export function isMobile() {
if (typeof navigator === "undefined") return false;
return /AppleWebKit.*Mobile|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
);
}
export function getPayEnv() {
if (isWeChat()) return "wechat";
if (isMobile()) return "h5";
return "pc";
}
export function getDeviceFromEnv(env) {
if (env === "wechat") return "wechat";
return env === "pc" ? "pc" : "h5";
}