diff --git a/src/pages-aigc/recharge/components/CustomAmountCard/index.vue b/src/pages-aigc/recharge/components/CustomAmountCard/index.vue new file mode 100644 index 0000000..5b31776 --- /dev/null +++ b/src/pages-aigc/recharge/components/CustomAmountCard/index.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/pages-aigc/recharge/components/RechargeFooter/index.vue b/src/pages-aigc/recharge/components/RechargeFooter/index.vue new file mode 100644 index 0000000..7f789af --- /dev/null +++ b/src/pages-aigc/recharge/components/RechargeFooter/index.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/pages-aigc/recharge/components/RechargePackageList/index.vue b/src/pages-aigc/recharge/components/RechargePackageList/index.vue new file mode 100644 index 0000000..a73a21d --- /dev/null +++ b/src/pages-aigc/recharge/components/RechargePackageList/index.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/pages-aigc/recharge/recharge.vue b/src/pages-aigc/recharge/recharge.vue new file mode 100644 index 0000000..36f9e72 --- /dev/null +++ b/src/pages-aigc/recharge/recharge.vue @@ -0,0 +1,463 @@ + + + + + diff --git a/src/pages-aigc/recharge/services/pendingVirtualRecharge.js b/src/pages-aigc/recharge/services/pendingVirtualRecharge.js new file mode 100644 index 0000000..cb6cce0 --- /dev/null +++ b/src/pages-aigc/recharge/services/pendingVirtualRecharge.js @@ -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); +} diff --git a/src/pages-aigc/recharge/services/wechatVirtualPayment.js b/src/pages-aigc/recharge/services/wechatVirtualPayment.js new file mode 100644 index 0000000..6249eb6 --- /dev/null +++ b/src/pages-aigc/recharge/services/wechatVirtualPayment.js @@ -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, + }); + }); +} diff --git a/src/pages-aigc/recharge/utils/virtualRecharge.js b/src/pages-aigc/recharge/utils/virtualRecharge.js new file mode 100644 index 0000000..9673319 --- /dev/null +++ b/src/pages-aigc/recharge/utils/virtualRecharge.js @@ -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); +} diff --git a/src/pages-aigc/useTemplate/assets/xiaoqi-avatar.png b/src/pages-aigc/useTemplate/assets/xiaoqi-avatar.png deleted file mode 100644 index 4ba612e..0000000 Binary files a/src/pages-aigc/useTemplate/assets/xiaoqi-avatar.png and /dev/null differ diff --git a/src/pages-aigc/useTemplate/components/PhotoConfirmDrawer/index.vue b/src/pages-aigc/useTemplate/components/PhotoConfirmDrawer/index.vue index 34ef169..b87639b 100644 --- a/src/pages-aigc/useTemplate/components/PhotoConfirmDrawer/index.vue +++ b/src/pages-aigc/useTemplate/components/PhotoConfirmDrawer/index.vue @@ -1,12 +1,6 @@