242 lines
7.9 KiB
Plaintext
242 lines
7.9 KiB
Plaintext
import { ref, onMounted } from "vue";
|
|
import { SUCCESS_CODE } from "@/constants/codes.constants.uts";
|
|
import { ACCESS_TOKEN } from "@/constants/home.constants";
|
|
import {
|
|
apiCreateSubscriptionOrder,
|
|
apiCreateCreditOrder,
|
|
apiGetOrderCashier,
|
|
} from "@/subpackages/servers/subscription";
|
|
import { useI18n } from "@/utils/i18n";
|
|
import { API_BASE_URL } from "@/constants/config";
|
|
import { objectToQueryString } from "@/utils/common";
|
|
|
|
// 全局单例锁:记录已展示过 Toast 的 订单ID + 支付结果 映射,防止多组件重复提示
|
|
const alertedKeys = new Set<string>();
|
|
|
|
export function useSubscriptionPurchase() {
|
|
const { t } = useI18n();
|
|
const processingId = ref<number | null>(null);
|
|
const loading = ref<boolean>(false);
|
|
|
|
// #ifdef H5 || WEB
|
|
const cleanUrlParams = () => {
|
|
// 清空 window.location.search 中的参数
|
|
const search = window.location.search;
|
|
if (search !== "") {
|
|
const params = new URLSearchParams(search);
|
|
if (params.has("payResult") || params.has("orderId")) {
|
|
params.delete("payResult");
|
|
params.delete("orderId");
|
|
const searchStr = params.toString();
|
|
const newSearch = searchStr ? `?${searchStr}` : "";
|
|
const newUrl =
|
|
window.location.pathname + newSearch + window.location.hash;
|
|
window.history.replaceState(null, "", newUrl);
|
|
}
|
|
}
|
|
|
|
// 清空 window.location.hash 中的参数
|
|
const hash = window.location.hash;
|
|
const qIndex = hash.indexOf("?");
|
|
if (qIndex !== -1) {
|
|
const pathPart = hash.substring(0, qIndex);
|
|
const hashSearch = hash.substring(qIndex);
|
|
const params = new URLSearchParams(hashSearch);
|
|
if (params.has("payResult") || params.has("orderId")) {
|
|
params.delete("payResult");
|
|
params.delete("orderId");
|
|
const searchStr = params.toString();
|
|
const newHash = pathPart + (searchStr ? `?${searchStr}` : "");
|
|
const newUrl =
|
|
window.location.pathname + window.location.search + newHash;
|
|
window.history.replaceState(null, "", newUrl);
|
|
}
|
|
}
|
|
};
|
|
|
|
const checkPayResult = () => {
|
|
const hash = window.location.hash;
|
|
const search = window.location.search;
|
|
|
|
let payResult = "";
|
|
let orderId = "";
|
|
|
|
// 优先从 main search 中解析
|
|
if (search !== "") {
|
|
const searchParams = new URLSearchParams(search);
|
|
payResult = searchParams.get("payResult") || "";
|
|
orderId = searchParams.get("orderId") || "";
|
|
}
|
|
|
|
// 若无,从 hash search 中解析
|
|
if (payResult === "") {
|
|
const qIndex = hash.indexOf("?");
|
|
if (qIndex !== -1) {
|
|
const hashSearch = hash.substring(qIndex);
|
|
const hashParams = new URLSearchParams(hashSearch);
|
|
payResult = hashParams.get("payResult") || "";
|
|
orderId = hashParams.get("orderId") || "";
|
|
}
|
|
}
|
|
|
|
if (payResult !== "") {
|
|
const alertKey = `${orderId}_${payResult}`;
|
|
if (alertedKeys.has(alertKey)) return;
|
|
alertedKeys.add(alertKey);
|
|
|
|
if (payResult === "success") {
|
|
uni.showToast({
|
|
title: t("Mobile.MySubscriptions.paySuccess"),
|
|
icon: "success",
|
|
});
|
|
} else if (payResult === "failed") {
|
|
uni.showToast({
|
|
title: t("Mobile.MySubscriptions.payFailed"),
|
|
icon: "none",
|
|
});
|
|
} else if (payResult === "pending") {
|
|
uni.showToast({
|
|
title: t("Mobile.MySubscriptions.payPending"),
|
|
icon: "none",
|
|
});
|
|
}
|
|
|
|
// 清除 URL 参数
|
|
cleanUrlParams();
|
|
}
|
|
};
|
|
// #endif
|
|
|
|
onMounted(() => {
|
|
// #ifdef H5 || WEB
|
|
checkPayResult();
|
|
// #endif
|
|
});
|
|
|
|
const getCashierUrlAndRedirect = async (orderId: number) => {
|
|
loading.value = true;
|
|
try {
|
|
let settlementUrl = "";
|
|
// #ifdef H5 || WEB
|
|
const token = (uni.getStorageSync(ACCESS_TOKEN) as string) || "";
|
|
let settlementOrigin = "";
|
|
const hostname = window.location.hostname;
|
|
if (hostname === "localhost" || hostname === "127.0.0.1") {
|
|
settlementOrigin = "http://localhost:3000";
|
|
settlementUrl = `${settlementOrigin}/static/payment-settlement.html?orderId=${orderId}&returnUrl=${encodeURIComponent(window.location.href)}&token=${encodeURIComponent(token)}`;
|
|
} else {
|
|
settlementOrigin = window.location.origin;
|
|
settlementUrl = `${settlementOrigin}/static/payment-settlement.html?orderId=${orderId}&returnUrl=${encodeURIComponent(window.location.href)}`;
|
|
}
|
|
// #endif
|
|
|
|
// #ifndef H5 || WEB
|
|
const token = (uni.getStorageSync(ACCESS_TOKEN) as string) || "";
|
|
const pages = getCurrentPages();
|
|
let relativePath = "";
|
|
if (pages.length > 0) {
|
|
const currentPage = pages[pages.length - 1];
|
|
const path = currentPage.route;
|
|
const params = objectToQueryString(currentPage.options);
|
|
relativePath = params !== "" ? `${path}?${params}` : path;
|
|
}
|
|
let baseSettlementUrl = API_BASE_URL;
|
|
if (process.env.NODE_ENV === "development") {
|
|
baseSettlementUrl = "http://localhost:3000";
|
|
}
|
|
const h5CurrentPageUrl = `${baseSettlementUrl}/m/#/${relativePath}`;
|
|
settlementUrl = `${baseSettlementUrl}/static/payment-settlement.html?orderId=${orderId}&returnUrl=${encodeURIComponent(h5CurrentPageUrl)}&token=${encodeURIComponent(token)}&isMp=1`;
|
|
// #endif
|
|
|
|
// 获取收银台地址
|
|
const cashierRes = await apiGetOrderCashier(
|
|
orderId,
|
|
settlementUrl !== "" ? settlementUrl : undefined,
|
|
);
|
|
if (cashierRes.code === SUCCESS_CODE && cashierRes.data != null) {
|
|
const cashierInfo = cashierRes.data!;
|
|
|
|
// #ifdef H5 || WEB
|
|
// H5 环境直接重定向跳转到收银台
|
|
window.location.href = cashierInfo.cashierUrl;
|
|
// #endif
|
|
|
|
// #ifndef H5 || WEB
|
|
// 非 H5 环境(如 App 或 小程序)则打开内嵌 WebView 进行支付
|
|
uni.navigateTo({
|
|
url: `/subpackages/pages/webview/webview?url=${encodeURIComponent(cashierInfo.cashierUrl)}`,
|
|
});
|
|
// #endif
|
|
} else {
|
|
uni.showToast({
|
|
title: t("Mobile.MySubscriptions.orderIdNotFound"),
|
|
icon: "none",
|
|
});
|
|
}
|
|
} catch (e) {
|
|
uni.showToast({
|
|
title: t("Mobile.MySubscriptions.loadFailed"),
|
|
icon: "none",
|
|
});
|
|
} finally {
|
|
loading.value = false;
|
|
processingId.value = null;
|
|
}
|
|
};
|
|
|
|
const handlePaySubscription = async (planId: number) => {
|
|
if (processingId.value != null) return;
|
|
processingId.value = planId;
|
|
try {
|
|
const orderRes = await apiCreateSubscriptionOrder(planId);
|
|
if (orderRes.code === SUCCESS_CODE && orderRes.data) {
|
|
const orderId = orderRes.data.id ?? orderRes.data;
|
|
await getCashierUrlAndRedirect(orderId as number);
|
|
} else {
|
|
uni.showToast({
|
|
title: t("Mobile.MySubscriptions.orderIdNotFound"),
|
|
icon: "none",
|
|
});
|
|
processingId.value = null;
|
|
}
|
|
} catch (e) {
|
|
uni.showToast({
|
|
title: t("Mobile.MySubscriptions.loadFailed"),
|
|
icon: "none",
|
|
});
|
|
processingId.value = null;
|
|
}
|
|
};
|
|
|
|
const handlePayCredits = async (packageId: number) => {
|
|
if (processingId.value != null) return;
|
|
processingId.value = packageId;
|
|
try {
|
|
const orderRes = await apiCreateCreditOrder(packageId);
|
|
if (orderRes.code === SUCCESS_CODE && orderRes.data) {
|
|
const orderId = orderRes.data.id ?? orderRes.data;
|
|
await getCashierUrlAndRedirect(orderId as number);
|
|
} else {
|
|
uni.showToast({
|
|
title: t("Mobile.MySubscriptions.orderIdNotFound"),
|
|
icon: "none",
|
|
});
|
|
processingId.value = null;
|
|
}
|
|
} catch (e) {
|
|
uni.showToast({
|
|
title: t("Mobile.MySubscriptions.loadFailed"),
|
|
icon: "none",
|
|
});
|
|
processingId.value = null;
|
|
}
|
|
};
|
|
|
|
return {
|
|
processingId,
|
|
loading,
|
|
handlePaySubscription,
|
|
handlePayCredits,
|
|
};
|
|
}
|