feat(recharge): add full virtual recharge flow for aigc credits

- Replace legacy recharge API endpoints with new dedicated VirtualRechargeApi with sensitive request logging
- Implement pending order tracking, automatic payment reconciliation, and recovery for interrupted payments
- Overhaul recharge component logic to support both pre-defined package and custom amount recharge
- Fix currency price formatting to consistently display 2 decimal places for all monetary values
- Remove unused xiaoqi-avatar asset and related component imports
- Simplify Vite build asset naming configuration
- Switch application to test development mode
This commit is contained in:
duanshuwen
2026-07-22 16:17:16 +08:00
parent 2407a9d941
commit 14defcc5b7
15 changed files with 615 additions and 140 deletions

View File

@@ -7,7 +7,7 @@
<input <input
class="custom-input" class="custom-input"
type="number" type="number"
:maxlength="4" :maxlength="6"
:value="modelValue" :value="modelValue"
@focus="emit('select')" @focus="emit('select')"
@input="handleInput" @input="handleInput"
@@ -21,7 +21,7 @@
</template> </template>
<script setup> <script setup>
defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: String, type: String,
default: "", default: "",
@@ -33,12 +33,16 @@ defineProps({
}); });
const emit = defineEmits(["update:modelValue", "select"]); const emit = defineEmits(["update:modelValue", "select"]);
const MAX_CUSTOM_AMOUNT = 9999; const MAX_CUSTOM_AMOUNT = 999999;
const handleInput = (event) => { const handleInput = (event) => {
const rawValue = event?.detail?.value ?? event?.target?.value ?? ""; const rawValue = event?.detail?.value ?? event?.target?.value ?? "";
const digits = String(rawValue).replace(/\D/g, ""); const amountText = String(rawValue).trim();
const value = digits ? String(Math.min(Number(digits), MAX_CUSTOM_AMOUNT)) : ""; const value = /^\d*$/.test(amountText)
? amountText
? String(Math.min(Number(amountText), MAX_CUSTOM_AMOUNT))
: ""
: props.modelValue;
emit("select"); emit("select");
emit("update:modelValue", value); emit("update:modelValue", value);
}; };

View File

@@ -48,7 +48,15 @@ const props = defineProps({
const emit = defineEmits(["pay", "update:agreed", "view-agreement"]); const emit = defineEmits(["pay", "update:agreed", "view-agreement"]);
const formattedPrice = computed(() => Number(props.price).toLocaleString("zh-CN")); const formattedPrice = computed(() => {
const price = Number(props.price);
if (!Number.isFinite(price) || price < 0) return "0.00";
return price.toLocaleString("zh-CN", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
});
const payDisabled = computed(() => { const payDisabled = computed(() => {
return props.loading || props.price <= 0 || !props.agreed || !props.agreementReady; return props.loading || props.price <= 0 || !props.agreed || !props.agreementReady;
}); });

View File

@@ -14,13 +14,15 @@
<text class="package-origin">充值金额</text> <text class="package-origin">充值金额</text>
</view> </view>
<text class="package-price">¥{{ formatPrice(item.rechargeAmount) }}</text> <text class="package-price">¥{{ formatPrice(item.expectedAmountFen) }}</text>
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { formatFenAmount } from "../../utils/virtualRecharge.js";
defineProps({ defineProps({
packages: { packages: {
type: Array, type: Array,
@@ -34,9 +36,9 @@ defineProps({
const emit = defineEmits(["select"]); const emit = defineEmits(["select"]);
const formatPrice = (price) => Number(price).toLocaleString("zh-CN"); const formatPrice = (amountFen) => formatFenAmount(amountFen);
const getPackageKey = (item) => `${item?.rechargeAmount ?? ""}-${item?.rechargePoints ?? ""}`; const getPackageKey = (item) => String(item?.optionCode || "");
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -51,19 +51,40 @@
<script setup> <script setup>
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app"; import { onHide, onLoad, onShow, onUnload } from "@dcloudio/uni-app";
import TopNavBar from "@/components/TopNavBar/index.vue"; import TopNavBar from "@/components/TopNavBar/index.vue";
import { useCurrentCredit } from "@/pages-aigc/hooks/useCurrentCredit.js"; import { useCurrentCredit } from "@/pages-aigc/hooks/useCurrentCredit.js";
import {
getPointsTopUpAgreement,
getRechargeOptions,
rechargeCredit,
} from "@/request/api/AigcApi.js";
import AgreePopup from "@/pages/login/components/AgreePopup/index.vue"; import AgreePopup from "@/pages/login/components/AgreePopup/index.vue";
import { getPointsTopUpAgreement } from "@/request/api/AigcApi.js";
import {
confirmVirtualRechargeOrder,
createVirtualRechargeOrder,
getVirtualRechargeOptions,
getVirtualRechargeOrder,
} from "@/request/api/VirtualRechargeApi.js";
import BalanceCard from "./components/BalanceCard/index.vue"; import BalanceCard from "./components/BalanceCard/index.vue";
import CustomAmountCard from "./components/CustomAmountCard/index.vue"; import CustomAmountCard from "./components/CustomAmountCard/index.vue";
import RechargeFooter from "./components/RechargeFooter/index.vue"; import RechargeFooter from "./components/RechargeFooter/index.vue";
import RechargePackageList from "./components/RechargePackageList/index.vue"; import RechargePackageList from "./components/RechargePackageList/index.vue";
import {
getPendingVirtualRechargeOrderNo,
removePendingVirtualRechargeOrderNo,
setPendingVirtualRechargeOrderNo,
} from "./services/pendingVirtualRecharge.js";
import {
getWechatClientPlatform,
getWechatLoginCode,
requestWechatVirtualPayment,
} from "./services/wechatVirtualPayment.js";
import {
buildVirtualRechargeOrderPayload,
classifyVirtualRechargeStatus,
normalizeVirtualPaymentOrder,
VIRTUAL_RECHARGE_STATUS_KIND,
} from "./utils/virtualRecharge.js";
const ORDER_QUERY_INTERVAL = 2000;
const MAX_ORDER_QUERY_ATTEMPTS = 5;
const { pointBalance: balancePoints, fetchCurrentCredit } = useCurrentCredit(); const { pointBalance: balancePoints, fetchCurrentCredit } = useCurrentCredit();
const rechargeOptions = ref([]); const rechargeOptions = ref([]);
@@ -71,51 +92,78 @@ const selectedRechargeOption = ref(null);
const customAmount = ref(""); const customAmount = ref("");
const rechargeMode = ref("package"); const rechargeMode = ref("package");
const paying = ref(false); const paying = ref(false);
const reconciling = ref(false);
const agreementAccepted = ref(false); const agreementAccepted = ref(false);
const agreementContent = ref(""); const agreementContent = ref("");
const agreementLoading = ref(false); const agreementLoading = ref(false);
const agreementVisible = ref(false); const agreementVisible = ref(false);
let orderQueryTimer = null;
let orderQueryResolver = null;
let pageVisible = false;
const agreementReady = computed(() => Boolean(agreementContent.value.trim())); const agreementReady = computed(() => Boolean(agreementContent.value.trim()));
const getRechargeOptionKey = (option) => {
if (!option) return "";
return `${option.rechargeAmount ?? ""}-${option.rechargePoints ?? ""}`;
};
const selectedRechargeKey = computed(() => { const selectedRechargeKey = computed(() => {
if (rechargeMode.value !== "package") return ""; if (rechargeMode.value !== "package") return "";
return String(selectedRechargeOption.value?.optionCode || "");
return getRechargeOptionKey(selectedRechargeOption.value);
}); });
const payPrice = computed(() => { const payPrice = computed(() => {
const amount = if (rechargeMode.value === "custom") {
rechargeMode.value === "custom" const amount = Number(customAmount.value);
? Number.parseInt(customAmount.value || "0", 10) return Number.isInteger(amount) ? amount : 0;
: Number(selectedRechargeOption.value?.rechargeAmount); }
return Number.isFinite(amount) ? amount : 0;
const amountFen = Number(selectedRechargeOption.value?.expectedAmountFen);
return Number.isInteger(amountFen) && amountFen > 0 ? amountFen / 100 : 0;
}); });
const showToast = (title) => { const showToast = (title, icon = "none") => {
uni.showToast({ uni.showToast({ title, icon });
title, };
icon: "none",
const clearOrderQueryTimer = () => {
if (orderQueryTimer) {
clearTimeout(orderQueryTimer);
orderQueryTimer = null;
}
if (orderQueryResolver) {
orderQueryResolver(false);
orderQueryResolver = null;
}
};
const waitForNextOrderQuery = () => {
if (!pageVisible) return Promise.resolve(false);
return new Promise((resolve) => {
orderQueryResolver = resolve;
orderQueryTimer = setTimeout(() => {
orderQueryTimer = null;
orderQueryResolver = null;
resolve(pageVisible);
}, ORDER_QUERY_INTERVAL);
}); });
}; };
const fetchRechargeOptions = async () => { const fetchRechargeOptions = async () => {
try { try {
const res = await getRechargeOptions(); const res = await getVirtualRechargeOptions();
if (res?.code === 0 && Array.isArray(res.data)) { if (res?.code === 0 && Array.isArray(res.data)) {
rechargeOptions.value = res.data; rechargeOptions.value = res.data;
if (rechargeMode.value === "package") { if (rechargeMode.value === "package") {
selectedRechargeOption.value = res.data[0] || null; selectedRechargeOption.value = res.data[0] || null;
} }
return;
} }
rechargeOptions.value = [];
selectedRechargeOption.value = null;
} catch (error) { } catch (error) {
console.error("获取积分充值档位失败", error); console.error("获取虚拟支付充值档位失败", error);
rechargeOptions.value = [];
selectedRechargeOption.value = null;
} }
}; };
@@ -133,16 +181,12 @@ const fetchPointsTopUpAgreement = async (showError = false) => {
agreementContent.value = ""; agreementContent.value = "";
agreementAccepted.value = false; agreementAccepted.value = false;
if (showError) { if (showError) showToast(res?.msg || "协议加载失败,请重试");
showToast(res?.msg || "协议加载失败,请重试");
}
} catch (error) { } catch (error) {
console.error("获取积分充值协议失败", error); console.error("获取积分充值协议失败", error);
agreementContent.value = ""; agreementContent.value = "";
agreementAccepted.value = false; agreementAccepted.value = false;
if (showError) { if (showError) showToast("协议加载失败,请重试");
showToast("协议加载失败,请重试");
}
} finally { } finally {
agreementLoading.value = false; agreementLoading.value = false;
} }
@@ -150,21 +194,129 @@ const fetchPointsTopUpAgreement = async (showError = false) => {
return false; return false;
}; };
onLoad(() => { const finishOrderByStatus = async (orderData) => {
agreementAccepted.value = false; const statusKind = classifyVirtualRechargeStatus(orderData?.status);
fetchRechargeOptions();
fetchPointsTopUpAgreement();
});
onShow(() => { if (statusKind === VIRTUAL_RECHARGE_STATUS_KIND.PAID) {
fetchCurrentCredit(); removePendingVirtualRechargeOrderNo();
}); await fetchCurrentCredit();
showToast("充值成功", "success");
return true;
}
if (statusKind === VIRTUAL_RECHARGE_STATUS_KIND.REFUNDED) {
removePendingVirtualRechargeOrderNo();
await fetchCurrentCredit();
showToast("充值订单已退款");
return true;
}
if (statusKind === VIRTUAL_RECHARGE_STATUS_KIND.FAILED) {
removePendingVirtualRechargeOrderNo();
showToast("充值订单异常,请重新发起支付");
return true;
}
return false;
};
const getPaymentErrorMessage = (error) => {
switch (Number(error?.errCode)) {
case -2:
return "已取消支付";
case -4:
return "支付被微信风控拦截";
case -15007:
return "微信登录状态已过期,请重新发起支付";
default:
return "支付失败,请重试";
}
};
const reconcileVirtualRechargeOrder = async (
rechargeOrderNo,
{ clientPaymentError = null, showPendingToast = true } = {}
) => {
if (reconciling.value || !rechargeOrderNo) return "skipped";
reconciling.value = true;
let latestOrder = null;
try {
let confirmArgs = {};
try {
const wxLoginCode = await getWechatLoginCode();
confirmArgs = { wxLoginCode };
} catch (error) {
console.warn("刷新微信登录凭证失败,将使用现有服务端会话确认订单", error);
}
try {
const confirmRes = await confirmVirtualRechargeOrder(
rechargeOrderNo,
confirmArgs
);
if (confirmRes?.code === 0 && confirmRes.data) {
latestOrder = confirmRes.data;
if (await finishOrderByStatus(latestOrder)) return "terminal";
}
} catch (error) {
console.error("服务端确认虚拟支付订单失败", error);
}
if (Number(clientPaymentError?.errCode) === -2 && latestOrder?.status === "UNPAID") {
removePendingVirtualRechargeOrderNo();
showToast("已取消支付");
return "cancelled";
}
for (let attempt = 0; attempt < MAX_ORDER_QUERY_ATTEMPTS; attempt += 1) {
const shouldContinue = await waitForNextOrderQuery();
if (!shouldContinue) return "paused";
try {
const queryRes = await getVirtualRechargeOrder(rechargeOrderNo);
if (queryRes?.code === 0 && queryRes.data) {
latestOrder = queryRes.data;
if (await finishOrderByStatus(latestOrder)) return "terminal";
}
} catch (error) {
console.error("查询虚拟支付充值订单失败", error);
}
}
if (clientPaymentError && latestOrder?.status === "UNPAID") {
removePendingVirtualRechargeOrderNo();
showToast(getPaymentErrorMessage(clientPaymentError));
return "client-failed";
}
if (showPendingToast) {
showToast("支付结果确认中,请稍后返回查看");
}
return "pending";
} finally {
reconciling.value = false;
}
};
const recoverPendingVirtualRechargeOrder = async () => {
const rechargeOrderNo = getPendingVirtualRechargeOrderNo();
if (!rechargeOrderNo || paying.value || reconciling.value) return;
paying.value = true;
uni.showLoading({ title: "正在确认支付结果...", mask: true });
try {
await reconcileVirtualRechargeOrder(rechargeOrderNo);
} finally {
uni.hideLoading();
paying.value = false;
}
};
const handleBack = () => { const handleBack = () => {
const pages = getCurrentPages(); const pages = getCurrentPages();
if (pages.length > 1) { if (pages.length > 1) uni.navigateBack();
uni.navigateBack();
}
}; };
const handleOpenLedger = () => { const handleOpenLedger = () => {
@@ -177,6 +329,7 @@ const handleOpenLedger = () => {
const handleSelectRechargeOption = (option) => { const handleSelectRechargeOption = (option) => {
rechargeMode.value = "package"; rechargeMode.value = "package";
selectedRechargeOption.value = option; selectedRechargeOption.value = option;
customAmount.value = "";
}; };
const handleSelectCustomAmount = () => { const handleSelectCustomAmount = () => {
@@ -196,53 +349,31 @@ const handleViewAgreement = async () => {
} }
const loaded = await fetchPointsTopUpAgreement(true); const loaded = await fetchPointsTopUpAgreement(true);
if (loaded) { if (loaded) agreementVisible.value = true;
agreementVisible.value = true;
}
}; };
const requestWechatPay = (payData) => { const validateRechargeSelection = () => {
const { nonceStr, packageVal, paySign, signType, timeStamp } = payData || {}; if (rechargeMode.value === "package") {
if (!String(selectedRechargeOption.value?.optionCode || "").trim()) {
if (!nonceStr || !packageVal || !paySign || !signType || !timeStamp) { throw new Error("请选择充值档位");
showToast("支付参数错误,请重试"); }
return; return;
} }
// #ifdef MP-WEIXIN const amountText = String(customAmount.value || "").trim();
uni.requestPayment({ const amount = Number(amountText);
provider: "wxpay", if (!/^\d{1,6}$/.test(amountText) || amount < 1 || amount > 999999) {
timeStamp: String(timeStamp), throw new Error("请输入1至999999的整数充值金额");
nonceStr: String(nonceStr), }
package: String(packageVal),
signType: String(signType),
paySign: String(paySign),
success: () => {
uni.showToast({
title: "支付成功",
icon: "success",
});
fetchCurrentCredit();
},
fail: () => {
showToast("支付失败,请重试");
},
});
// #endif
}; };
const handlePay = async () => { const handlePay = async () => {
if (paying.value) return; if (paying.value) return;
if (rechargeMode.value === "package" && !selectedRechargeOption.value) { try {
showToast("请选择充值档位"); validateRechargeSelection();
return; } catch (error) {
} showToast(error.message);
const rechargeAmount = payPrice.value;
if (!Number.isInteger(rechargeAmount) || rechargeAmount <= 0) {
const message = rechargeMode.value === "custom" ? "请输入正确的充值金额" : "充值档位金额错误,请重试";
showToast(message);
return; return;
} }
@@ -250,43 +381,81 @@ const handlePay = async () => {
showToast("协议加载中,请稍候"); showToast("协议加载中,请稍候");
return; return;
} }
if (!agreementReady.value) { if (!agreementReady.value) {
showToast("协议加载失败,请重试"); showToast("协议加载失败,请重试");
return; return;
} }
if (!agreementAccepted.value) { if (!agreementAccepted.value) {
showToast("请先阅读并同意积分充值协议"); showToast("请先阅读并同意积分充值协议");
return; return;
} }
paying.value = true; paying.value = true;
uni.showLoading({ title: "正在发起支付..." }); clearOrderQueryTimer();
uni.showLoading({ title: "正在发起支付...", mask: true });
try { try {
const res = await rechargeCredit({ const clientPlatform = getWechatClientPlatform();
rechargeAmount, const wxLoginCode = await getWechatLoginCode();
payWay: "0", const createOrderPayload = buildVirtualRechargeOrderPayload({
paySource: "1", mode: rechargeMode.value,
rechargeOptionCode: selectedRechargeOption.value?.optionCode,
rechargeAmount: customAmount.value,
wxLoginCode,
clientPlatform,
}); });
const createRes = await createVirtualRechargeOrder(createOrderPayload);
uni.hideLoading(); if (createRes?.code !== 0 || !createRes.data) {
throw new Error(createRes?.msg || "充值下单失败,请重试");
if (res?.code === 0 && res.data) {
requestWechatPay(res.data);
return;
} }
showToast(res?.msg || "充值下单失败,请重试"); const paymentOrder = normalizeVirtualPaymentOrder(createRes.data);
} catch (error) { setPendingVirtualRechargeOrderNo(paymentOrder.rechargeOrderNo);
uni.hideLoading(); uni.hideLoading();
console.error("充值下单失败", error);
showToast("充值下单失败,请重试"); let clientPaymentError = null;
try {
await requestWechatVirtualPayment(paymentOrder);
} catch (error) {
clientPaymentError = error;
}
pageVisible = true;
uni.showLoading({ title: "正在确认支付结果...", mask: true });
await reconcileVirtualRechargeOrder(paymentOrder.rechargeOrderNo, {
clientPaymentError,
});
} catch (error) {
console.error("虚拟支付积分充值失败", error);
showToast(error?.message || "充值下单失败,请重试");
} finally { } finally {
uni.hideLoading();
paying.value = false; paying.value = false;
} }
}; };
onLoad(() => {
agreementAccepted.value = false;
fetchRechargeOptions();
fetchPointsTopUpAgreement();
});
onShow(() => {
pageVisible = true;
fetchCurrentCredit();
recoverPendingVirtualRechargeOrder();
});
onHide(() => {
pageVisible = false;
clearOrderQueryTimer();
});
onUnload(() => {
pageVisible = false;
clearOrderQueryTimer();
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -0,0 +1,26 @@
import { currentClientType } from "@/constant/base.js";
const PENDING_VIRTUAL_RECHARGE_ORDER_KEY = `${currentClientType()}_AIGC_PENDING_VIRTUAL_RECHARGE_ORDER`;
const getStorageApi = () => (typeof uni !== "undefined" ? uni : null);
export function getPendingVirtualRechargeOrderNo(storage = getStorageApi()) {
const orderNo = storage?.getStorageSync?.(PENDING_VIRTUAL_RECHARGE_ORDER_KEY);
return typeof orderNo === "string" ? orderNo.trim() : "";
}
export function setPendingVirtualRechargeOrderNo(orderNo, storage = getStorageApi()) {
const normalizedOrderNo = String(orderNo || "").trim();
if (!normalizedOrderNo) {
throw new Error("待确认充值订单号不能为空");
}
return storage?.setStorageSync?.(
PENDING_VIRTUAL_RECHARGE_ORDER_KEY,
normalizedOrderNo
);
}
export function removePendingVirtualRechargeOrderNo(storage = getStorageApi()) {
return storage?.removeStorageSync?.(PENDING_VIRTUAL_RECHARGE_ORDER_KEY);
}

View File

@@ -0,0 +1,102 @@
import {
normalizeVirtualPaymentOrder,
normalizeWechatClientPlatform,
} from "../utils/virtualRecharge.js";
const MIN_VIRTUAL_PAYMENT_SDK_VERSION = "2.19.2";
const getWechatApi = () => {
// #ifdef MP-WEIXIN
if (typeof wx !== "undefined") return wx;
// #endif
return null;
};
export function compareVersion(versionA, versionB) {
const versionsA = String(versionA || "").split(".");
const versionsB = String(versionB || "").split(".");
const length = Math.max(versionsA.length, versionsB.length);
while (versionsA.length < length) versionsA.push("0");
while (versionsB.length < length) versionsB.push("0");
for (let index = 0; index < length; index += 1) {
const numberA = Number.parseInt(versionsA[index], 10) || 0;
const numberB = Number.parseInt(versionsB[index], 10) || 0;
if (numberA > numberB) return 1;
if (numberA < numberB) return -1;
}
return 0;
}
export function assertWechatVirtualPaymentCapability(wechatApi = getWechatApi()) {
if (!wechatApi) {
throw new Error("当前环境不支持微信虚拟支付");
}
const appBaseInfo =
typeof wechatApi.getAppBaseInfo === "function"
? wechatApi.getAppBaseInfo()
: wechatApi.getSystemInfoSync?.() || {};
const versionSupported =
compareVersion(appBaseInfo?.SDKVersion, MIN_VIRTUAL_PAYMENT_SDK_VERSION) >= 0;
const apiSupported =
typeof wechatApi.requestVirtualPayment === "function" &&
(versionSupported || wechatApi.canIUse?.("requestVirtualPayment"));
if (!apiSupported) {
throw new Error("当前微信版本不支持微信虚拟支付");
}
}
export function getWechatClientPlatform(wechatApi = getWechatApi()) {
assertWechatVirtualPaymentCapability(wechatApi);
const deviceInfo =
typeof wechatApi.getDeviceInfo === "function"
? wechatApi.getDeviceInfo()
: wechatApi.getSystemInfoSync?.() || {};
const clientPlatform = normalizeWechatClientPlatform(deviceInfo?.platform);
if (!clientPlatform) {
throw new Error("当前客户端平台不支持微信虚拟支付");
}
return clientPlatform;
}
export function getWechatLoginCode(wechatApi = getWechatApi()) {
if (!wechatApi || typeof wechatApi.login !== "function") {
return Promise.reject(new Error("当前环境无法获取微信登录凭证"));
}
return new Promise((resolve, reject) => {
wechatApi.login({
success: (result) => {
const code = String(result?.code || "").trim();
if (code) {
resolve(code);
return;
}
reject(new Error("微信登录凭证获取失败"));
},
fail: () => reject(new Error("微信登录凭证获取失败")),
});
});
}
export function requestWechatVirtualPayment(paymentData, wechatApi = getWechatApi()) {
assertWechatVirtualPaymentCapability(wechatApi);
const order = normalizeVirtualPaymentOrder(paymentData);
return new Promise((resolve, reject) => {
wechatApi.requestVirtualPayment({
signData: order.signData,
paySig: order.paySig,
signature: order.signature,
mode: order.mode,
success: resolve,
fail: reject,
});
});
}

View File

@@ -0,0 +1,136 @@
const ALLOWED_CLIENT_PLATFORMS = new Set([
"ANDROID",
"HARMONY",
"WINDOWS",
"IOS",
]);
const REQUIRED_PAYMENT_ORDER_FIELDS = [
"rechargeOrderNo",
"paymentOrderNo",
"signData",
"paySig",
"signature",
];
export const VIRTUAL_RECHARGE_STATUS_KIND = Object.freeze({
PAID: "paid",
REFUNDED: "refunded",
FAILED: "failed",
PENDING: "pending",
UNKNOWN: "unknown",
});
export function normalizeWechatClientPlatform(platform) {
const normalizedPlatform = String(platform || "").toLowerCase();
const platformMap = {
android: "ANDROID",
ios: "IOS",
ohos: "HARMONY",
ohos_pc: "HARMONY",
windows: "WINDOWS",
};
return platformMap[normalizedPlatform] || "";
}
export function buildVirtualRechargeOrderPayload({
mode,
rechargeOptionCode,
rechargeAmount,
wxLoginCode,
clientPlatform,
} = {}) {
const normalizedLoginCode = String(wxLoginCode || "").trim();
const normalizedPlatform = String(clientPlatform || "").trim().toUpperCase();
if (!normalizedLoginCode) {
throw new Error("微信登录凭证无效");
}
if (!ALLOWED_CLIENT_PLATFORMS.has(normalizedPlatform)) {
throw new Error("当前客户端平台不支持微信虚拟支付");
}
const commonPayload = {
wxLoginCode: normalizedLoginCode,
clientPlatform: normalizedPlatform,
};
if (mode === "package") {
const normalizedOptionCode = String(rechargeOptionCode || "").trim();
if (!normalizedOptionCode) {
throw new Error("请选择充值档位");
}
return {
rechargeOptionCode: normalizedOptionCode,
...commonPayload,
};
}
if (mode === "custom") {
const amountText = String(rechargeAmount ?? "").trim();
if (!/^\d{1,6}$/.test(amountText)) {
throw new Error("充值金额必须为1至999999的整数");
}
const normalizedAmount = Number(amountText);
if (normalizedAmount < 1 || normalizedAmount > 999999) {
throw new Error("充值金额必须为1至999999的整数");
}
return {
rechargeAmount: normalizedAmount,
...commonPayload,
};
}
throw new Error("请选择充值方式");
}
export function normalizeVirtualPaymentOrder(orderData) {
const normalizedOrder = orderData && typeof orderData === "object" ? orderData : {};
const hasRequiredFields = REQUIRED_PAYMENT_ORDER_FIELDS.every((field) => {
return typeof normalizedOrder[field] === "string" && normalizedOrder[field].trim();
});
if (!hasRequiredFields || normalizedOrder.mode !== "short_series_coin") {
throw new Error("虚拟支付订单参数错误");
}
return {
rechargeOrderNo: normalizedOrder.rechargeOrderNo,
paymentOrderNo: normalizedOrder.paymentOrderNo,
mode: normalizedOrder.mode,
signData: normalizedOrder.signData,
paySig: normalizedOrder.paySig,
signature: normalizedOrder.signature,
};
}
export function classifyVirtualRechargeStatus(status) {
switch (status) {
case "PAID":
return VIRTUAL_RECHARGE_STATUS_KIND.PAID;
case "REFUNDED":
return VIRTUAL_RECHARGE_STATUS_KIND.REFUNDED;
case "CREATE_FAILED":
case "PAYMENT_ABNORMAL":
return VIRTUAL_RECHARGE_STATUS_KIND.FAILED;
case "CREATING":
case "UNPAID":
return VIRTUAL_RECHARGE_STATUS_KIND.PENDING;
default:
return VIRTUAL_RECHARGE_STATUS_KIND.UNKNOWN;
}
}
export function formatFenAmount(amountFen) {
const normalizedAmount = Number(amountFen);
if (!Number.isInteger(normalizedAmount) || normalizedAmount < 0) {
return "0.00";
}
return (normalizedAmount / 100).toFixed(2);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

View File

@@ -1,12 +1,6 @@
<template> <template>
<uni-popup <uni-popup ref="popupRef" type="bottom" background-color="transparent" mask-background-color="rgba(0, 0, 0, 0)"
ref="popupRef" :safe-area="false" :is-mask-click="false">
type="bottom"
background-color="transparent"
mask-background-color="rgba(0, 0, 0, 0)"
:safe-area="false"
:is-mask-click="false"
>
<view class="photo-confirm-drawer"> <view class="photo-confirm-drawer">
<view class="photo-confirm-close" @tap="emit('close')"> <view class="photo-confirm-close" @tap="emit('close')">
<uni-icons type="closeempty" size="30" color="#8fa2ba" /> <uni-icons type="closeempty" size="30" color="#8fa2ba" />
@@ -33,12 +27,11 @@
<script setup> <script setup>
import { nextTick, onMounted, ref } from "vue"; import { nextTick, onMounted, ref } from "vue";
import defaultAvatar from "../../assets/xiaoqi-avatar.png";
defineProps({ defineProps({
avatarSrc: { avatarSrc: {
type: String, type: String,
default: defaultAvatar, default: '',
}, },
}); });

View File

@@ -7,8 +7,8 @@
<view class="aigc-use-template-content"> <view class="aigc-use-template-content">
<TemplateVersionHero :template="currentTemplate" /> <TemplateVersionHero :template="currentTemplate" />
<GenerateConfirmPanel v-if="currentStep === 'generateConfirm'" :cost="currentCost" <GenerateConfirmPanel v-if="currentStep === 'generateConfirm'" :cost="currentCost" :balance="pointBalance"
:balance="pointBalance" @generate="handleGenerate" @recharge="handleRecharge" /> @generate="handleGenerate" @recharge="handleRecharge" />
<template v-else> <template v-else>
<VersionOptionList :options="templateItems" :selected-value="selectedTemplateItemId" <VersionOptionList :options="templateItems" :selected-value="selectedTemplateItemId"
@@ -30,7 +30,7 @@
@album="handlePickAlbum" /> @album="handlePickAlbum" />
</view> </view>
<view v-if="currentStep === 'photoConfirm'" class="aigc-use-template-popup-host"> <view v-if="currentStep === 'photoConfirm'" class="aigc-use-template-popup-host">
<PhotoConfirmDrawer :avatar-src="selectedImageLocalPath || guideAvatar" @close="handleClosePhotoConfirm" <PhotoConfirmDrawer :avatar-src="selectedImageLocalPath" @close="handleClosePhotoConfirm"
@confirm="handleConfirmPhoto" /> @confirm="handleConfirmPhoto" />
</view> </view>
<PointInsufficientDialog v-if="pointDialogVisible" :cost="currentCost" :balance="pointBalance" <PointInsufficientDialog v-if="pointDialogVisible" :cost="currentCost" :balance="pointBalance"
@@ -52,7 +52,6 @@ import {
getAigcTemplateList, getAigcTemplateList,
} from "@/request/api/AigcApi.js"; } from "@/request/api/AigcApi.js";
import { updateImageFile } from "@/request/api/UpdateFile.js"; import { updateImageFile } from "@/request/api/UpdateFile.js";
import guideAvatar from "./assets/xiaoqi-avatar.png";
import GenerateConfirmPanel from "./components/GenerateConfirmPanel/index.vue"; import GenerateConfirmPanel from "./components/GenerateConfirmPanel/index.vue";
import PhotoConfirmDrawer from "./components/PhotoConfirmDrawer/index.vue"; import PhotoConfirmDrawer from "./components/PhotoConfirmDrawer/index.vue";
import PhotoGuidePanel from "./components/PhotoGuidePanel/index.vue"; import PhotoGuidePanel from "./components/PhotoGuidePanel/index.vue";

View File

@@ -24,14 +24,6 @@ function getCurrentCredit() {
return request.get("/hotelBiz/credit/current", {}); return request.get("/hotelBiz/credit/current", {});
} }
function getRechargeOptions() {
return request.get("/hotelBiz/credit/recharge/options", {});
}
function rechargeCredit(args) {
return request.post("/hotelBiz/credit/recharge", args);
}
function getCreditLedgerPage(args) { function getCreditLedgerPage(args) {
return request.post("/hotelBiz/credit/ledger/page", args); return request.post("/hotelBiz/credit/ledger/page", args);
} }
@@ -55,8 +47,6 @@ export {
getAigcGeneratorTaskDetail, getAigcGeneratorTaskDetail,
createAigcGeneratorTask, createAigcGeneratorTask,
getCurrentCredit, getCurrentCredit,
getRechargeOptions,
rechargeCredit,
getCreditLedgerPage, getCreditLedgerPage,
getPointsTopUpAgreement, getPointsTopUpAgreement,
createAigcGeneratorTaskShare, createAigcGeneratorTaskShare,

View File

@@ -0,0 +1,44 @@
import request from "../base/request";
const VIRTUAL_RECHARGE_BASE_PATH = "/hotelBiz/credit/virtual-recharge";
const getVirtualRechargeOrderPath = (rechargeOrderNo) => {
const normalizedOrderNo = String(rechargeOrderNo || "").trim();
if (!normalizedOrderNo) {
throw new Error("积分充值订单号不能为空");
}
return `${VIRTUAL_RECHARGE_BASE_PATH}/orders/${encodeURIComponent(normalizedOrderNo)}`;
};
function getVirtualRechargeOptions() {
return request.get(`${VIRTUAL_RECHARGE_BASE_PATH}/options`, {});
}
function createVirtualRechargeOrder(args) {
return request.post(`${VIRTUAL_RECHARGE_BASE_PATH}/orders`, args, {
sensitive: true,
});
}
function confirmVirtualRechargeOrder(rechargeOrderNo, args = {}) {
const wxLoginCode = String(args?.wxLoginCode || "").trim();
const payload = wxLoginCode ? { wxLoginCode } : {};
return request.post(
`${getVirtualRechargeOrderPath(rechargeOrderNo)}/confirm`,
payload,
{ sensitive: true }
);
}
function getVirtualRechargeOrder(rechargeOrderNo) {
return request.get(getVirtualRechargeOrderPath(rechargeOrderNo), {});
}
export {
confirmVirtualRechargeOrder,
createVirtualRechargeOrder,
getVirtualRechargeOptions,
getVirtualRechargeOrder,
};

View File

@@ -7,7 +7,7 @@ import { getServiceUrl } from "../api/GetServiceUrlApi";
const versionValue = "1.1.3"; const versionValue = "1.1.3";
/// 是否是测试版本, 测试版本为true 发布版本为false /// 是否是测试版本, 测试版本为true 发布版本为false
const developVersion = false; const developVersion = true;
// 获取服务地址 // 获取服务地址
const getEvnUrl = async () => { const getEvnUrl = async () => {

View File

@@ -14,6 +14,8 @@ const defaultConfig = {
}; };
function request(url, args = {}, method = "POST", customConfig = {}) { function request(url, args = {}, method = "POST", customConfig = {}) {
const { sensitive = false, ...requestCustomConfig } = customConfig;
customConfig = requestCustomConfig;
const appStore = useAppStore(); const appStore = useAppStore();
// 判断 url 是否以 http 开头 // 判断 url 是否以 http 开头
if (!/^http/.test(url)) { if (!/^http/.test(url)) {
@@ -53,7 +55,11 @@ function request(url, args = {}, method = "POST", customConfig = {}) {
header, header,
}; };
console.log(`\n\n请求接口: ${url}, \n请求参数: ${JSON.stringify(args)}, \n请求头: ${JSON.stringify(config)}\n\n`); if (sensitive) {
console.log(`\n\n请求接口: ${url}, \n敏感请求参数和请求头已隐藏\n\n`);
} else {
console.log(`\n\n请求接口: ${url}, \n请求参数: ${JSON.stringify(args)}, \n请求头: ${JSON.stringify(config)}\n\n`);
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
uni.request({ uni.request({
@@ -62,7 +68,11 @@ function request(url, args = {}, method = "POST", customConfig = {}) {
method, method,
...config, ...config,
success: (res) => { success: (res) => {
console.log(`\n\n请求接口: ${url}, \n请求响应: ${JSON.stringify(res)}, \n\n`); if (sensitive) {
console.log(`\n\n请求接口: ${url}, \n响应状态: ${res.statusCode || "unknown"}\n\n`);
} else {
console.log(`\n\n请求接口: ${url}, \n请求响应: ${JSON.stringify(res)}, \n\n`);
}
resolve(res.data); resolve(res.data);
if (res.statusCode && res.statusCode === 424) { if (res.statusCode && res.statusCode === 424) {

View File

@@ -23,14 +23,6 @@ export default defineConfig({
commonjsOptions: { commonjsOptions: {
transformMixedEsModules: true, transformMixedEsModules: true,
}, },
rollupOptions: { rollupOptions: {},
output: {
assetFileNames(d) {
const baseName = d.name.replace(/\\/g, "/").split("/").pop();
const newName = md5(baseName) + ".[hash].[extname]";
return `assets/${newName}`;
},
},
},
}, },
}); });