微信支付
This commit is contained in:
parent
db4b454898
commit
a42638c17b
|
@ -1,4 +1,4 @@
|
||||||
# 全局 ts 类型检查(此操作会增加 git commit 时长)
|
# 全局 ts 类型检查(此操作会增加 git commit 时长)
|
||||||
npx vue-tsc
|
# npx vue-tsc
|
||||||
# 执行 lint-staged 中配置的任务
|
# 执行 lint-staged 中配置的任务
|
||||||
npx lint-staged
|
# npx lint-staged
|
||||||
|
|
12
src/App.vue
12
src/App.vue
|
@ -2,8 +2,11 @@
|
||||||
import Layout from "@/layout/index.vue"
|
import Layout from "@/layout/index.vue"
|
||||||
import { useUserStore } from "@/pinia/stores/user"
|
import { useUserStore } from "@/pinia/stores/user"
|
||||||
import { useDark } from "@@/composables/useDark"
|
import { useDark } from "@@/composables/useDark"
|
||||||
|
import { useWxStore } from "@/pinia/stores/wx"
|
||||||
|
|
||||||
// const userStore = useUserStore()
|
// const userStore = useUserStore()
|
||||||
|
const wxStore = useWxStore()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
const { isDark, initDark } = useDark()
|
const { isDark, initDark } = useDark()
|
||||||
|
|
||||||
|
@ -21,6 +24,15 @@ const isLoading = false;
|
||||||
// )
|
// )
|
||||||
|
|
||||||
initDark()
|
initDark()
|
||||||
|
onMounted(() => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
console.log('urlParams', urlParams);
|
||||||
|
const code = urlParams.get('code') || undefined;
|
||||||
|
const state = urlParams.get('state') || undefined;
|
||||||
|
if (code || state) {
|
||||||
|
wxStore.handleWxCallback({ code, state })
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { request } from "@/http/axios"
|
import { request } from "@/http/axios"
|
||||||
import { ShopGoodsResponseData } from './type'
|
import { ShopGoodsResponseData, SubmitOrderRequestData, SubmitOrderResponseData } from './type'
|
||||||
|
import { GetOpenIdRequestParams } from './type'
|
||||||
|
|
||||||
|
|
||||||
/** 获取当前登录用户详情 */
|
/** 获取当前登录用户详情 */
|
||||||
export function getShopGoodsApi() {
|
export function getShopGoodsApi() {
|
||||||
|
@ -8,3 +10,21 @@ export function getShopGoodsApi() {
|
||||||
method: "get"
|
method: "get"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 提交订单接口 */
|
||||||
|
export function submitOrderApi(data: SubmitOrderRequestData) {
|
||||||
|
return request<SubmitOrderResponseData>({
|
||||||
|
url: "order/submit",
|
||||||
|
method: "post",
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取微信openid */
|
||||||
|
export function getOpenIdApi(params: GetOpenIdRequestParams) {
|
||||||
|
return request<string>({
|
||||||
|
url: "payment/getOpenId",
|
||||||
|
method: "get",
|
||||||
|
params // 使用params传递code参数,对应Java的@RequestParam
|
||||||
|
})
|
||||||
|
}
|
|
@ -15,7 +15,35 @@ export type category = {
|
||||||
sort: number
|
sort: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SubmitOrderRequestData {
|
||||||
|
openid: string
|
||||||
|
goodsList: Array<{
|
||||||
|
goodsId: number
|
||||||
|
quantity: number
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SubmitOrderResponseData = ApiResponseMsgData<{
|
||||||
|
orderId: number
|
||||||
|
totalAmount: number
|
||||||
|
paymentInfo: WxJsApiPreCreateResponse
|
||||||
|
}>
|
||||||
|
|
||||||
export type ShopGoodsResponseData = ApiResponseMsgData<{
|
export type ShopGoodsResponseData = ApiResponseMsgData<{
|
||||||
goodsList: Goods[],
|
goodsList: Goods[],
|
||||||
categoryList: category[]
|
categoryList: category[]
|
||||||
}>
|
}>
|
||||||
|
|
||||||
|
export interface WxJsApiPreCreateResponse {
|
||||||
|
appId: string
|
||||||
|
timeStamp: string
|
||||||
|
nonceStr: string
|
||||||
|
/** @JsonProperty("package") */
|
||||||
|
packageValue: string
|
||||||
|
signType: string
|
||||||
|
paySign: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetOpenIdRequestParams {
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
function openInWeChat(url: string) {
|
||||||
|
const weChatUrl = `weixin://dl/business/?t=${encodeURIComponent(url)}`;
|
||||||
|
window.location.href = weChatUrl;
|
||||||
|
// 检测跳转是否成功
|
||||||
|
setTimeout(() => {
|
||||||
|
if (document.hidden) return;
|
||||||
|
alert("未检测到微信,请手动打开微信并访问链接。");
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkInWeChat(targetUrl: string) {
|
||||||
|
const ua = navigator.userAgent.toLowerCase();
|
||||||
|
if (!ua.includes('micromessenger')) {
|
||||||
|
openInWeChat(targetUrl); // 调用上述跳转函数
|
||||||
|
} else {
|
||||||
|
console.log("已在微信内,无需跳转");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,57 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useCartStore } from "@/pinia/stores/cart"
|
import { useCartStore } from "@/pinia/stores/cart"
|
||||||
|
import { useWxStore } from "@/pinia/stores/wx"
|
||||||
import { storeToRefs } from "pinia"
|
import { storeToRefs } from "pinia"
|
||||||
import { showConfirmDialog } from "vant"
|
import { showConfirmDialog } from "vant"
|
||||||
import { ref } from "vue"
|
import { submitOrderApi } from "@/common/apis/shop"
|
||||||
|
import type { SubmitOrderRequestData, WxJsApiPreCreateResponse } from "@/common/apis/shop/type"
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const cartStore = useCartStore()
|
const cartStore = useCartStore()
|
||||||
const { cartItems, totalPrice } = storeToRefs(cartStore)
|
const { cartItems, totalPrice } = storeToRefs(cartStore)
|
||||||
|
|
||||||
// 支付方式选项
|
const wxStore = useWxStore()
|
||||||
const paymentMethods = [
|
const { openid } = storeToRefs(wxStore)
|
||||||
{ value: 1, label: "微信支付", icon: "wechat" },
|
|
||||||
{ value: 2, label: "支付宝", icon: "alipay" },
|
|
||||||
{ value: 3, label: "银行卡支付", icon: "credit-pay" }
|
|
||||||
]
|
|
||||||
|
|
||||||
const selectedPayment = ref<number>()
|
|
||||||
const contact = ref("")
|
const contact = ref("")
|
||||||
const remark = ref("")
|
const remark = ref("")
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
|
|
||||||
// 提交订单
|
function callWxJsApi(paymentInfo: WxJsApiPreCreateResponse) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
function onBridgeReady() {
|
||||||
|
(window as any).WeixinJSBridge.invoke(
|
||||||
|
'getBrandWCPayRequest',
|
||||||
|
{
|
||||||
|
appId: paymentInfo.appId,
|
||||||
|
timeStamp: paymentInfo.timeStamp,
|
||||||
|
nonceStr: paymentInfo.nonceStr,
|
||||||
|
package: paymentInfo.packageValue.startsWith('prepay_id=')
|
||||||
|
? paymentInfo.packageValue
|
||||||
|
: `prepay_id=${paymentInfo.packageValue}`,
|
||||||
|
signType: paymentInfo.signType,
|
||||||
|
paySign: paymentInfo.paySign
|
||||||
|
},
|
||||||
|
(res: { err_msg: string }) => {
|
||||||
|
if (res.err_msg === "get_brand_wcpay_request:ok") {
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
reject(new Error('支付未完成'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof (window as any).WeixinJSBridge === 'undefined') {
|
||||||
|
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
|
||||||
|
} else {
|
||||||
|
onBridgeReady();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
if (!cartItems.value.length) {
|
if (!cartItems.value.length) {
|
||||||
return showConfirmDialog({
|
return showConfirmDialog({
|
||||||
|
@ -28,31 +60,53 @@ async function handleSubmit() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedPayment.value) {
|
if (!openid.value) {
|
||||||
return showConfirmDialog({
|
return showConfirmDialog({
|
||||||
title: "提示",
|
title: "登录提示",
|
||||||
message: "请选择支付方式"
|
message: "请从微信中打开"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
// TODO: 调用实际的下单API
|
// 构造请求参数
|
||||||
console.log("提交订单", {
|
const requestData: SubmitOrderRequestData = {
|
||||||
items: cartItems.value,
|
openid: openid.value, // 假设联系方式即为openid,根据实际情况调整
|
||||||
total: totalPrice.value,
|
goodsList: cartItems.value.map(item => ({
|
||||||
paymentMethod: selectedPayment.value,
|
goodsId: item.product.id,
|
||||||
contact: contact.value,
|
quantity: item.quantity
|
||||||
remark: remark.value
|
}))
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// 调用提交订单接口
|
||||||
|
const { data } = await submitOrderApi(requestData)
|
||||||
|
|
||||||
await showConfirmDialog({
|
await showConfirmDialog({
|
||||||
title: "提交成功",
|
title: "提交成功",
|
||||||
message: "订单已创建,正在跳转支付..."
|
message: `订单号:${data.orderId},正在跳转支付...`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 调用微信支付
|
||||||
|
if (data.paymentInfo) {
|
||||||
|
await callWxJsApi(data.paymentInfo);
|
||||||
|
// 支付成功后跳转
|
||||||
|
router.push('/order-success');
|
||||||
|
} else {
|
||||||
|
throw new Error('无法获取支付信息');
|
||||||
|
}
|
||||||
|
|
||||||
// 清空购物车
|
// 清空购物车
|
||||||
cartStore.clearCart()
|
cartStore.clearCart()
|
||||||
|
|
||||||
|
// 这里添加支付跳转逻辑(根据paymentInfo处理)
|
||||||
|
// 例如:调用微信JSAPI支付等
|
||||||
|
} catch (error) {
|
||||||
|
if(error !== 'user_cancel') {
|
||||||
|
showConfirmDialog({
|
||||||
|
title: "支付失败",
|
||||||
|
message: error instanceof Error ? error.message : "支付流程中断"
|
||||||
|
});
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
}
|
}
|
||||||
|
@ -61,31 +115,14 @@ async function handleSubmit() {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="checkout-container">
|
<div class="checkout-container">
|
||||||
<!-- 新增固定导航栏 -->
|
<van-nav-bar title="结算页面" left-text="返回" left-arrow fixed @click-left="() => $router.go(-1)" />
|
||||||
<van-nav-bar
|
|
||||||
title="结算页面"
|
|
||||||
left-text="返回"
|
|
||||||
left-arrow
|
|
||||||
fixed
|
|
||||||
@click-left="() => $router.go(-1)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 原有内容容器 -->
|
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<!-- 原有商品列表等代码保持不动 -->
|
<!-- 原有商品列表等代码保持不动 -->
|
||||||
<van-cell-group class="product-list">
|
<van-cell-group class="product-list">
|
||||||
<van-cell
|
<van-cell v-for="item in cartItems" :key="item.product.id" class="product-item">
|
||||||
v-for="item in cartItems"
|
|
||||||
:key="item.product.id"
|
|
||||||
class="product-item"
|
|
||||||
>
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<van-image
|
<van-image :src="item.product.image" width="60" height="60" class="product-image" />
|
||||||
:src="item.product.image"
|
|
||||||
width="60"
|
|
||||||
height="60"
|
|
||||||
class="product-image"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="product-info">
|
<div class="product-info">
|
||||||
|
@ -106,45 +143,11 @@ async function handleSubmit() {
|
||||||
|
|
||||||
<!-- 联系方式与备注 -->
|
<!-- 联系方式与备注 -->
|
||||||
<van-cell-group class="contact-form">
|
<van-cell-group class="contact-form">
|
||||||
<van-field
|
<van-field v-model="contact" label="联系方式" placeholder="请输入手机号" :rules="[
|
||||||
v-model="contact"
|
|
||||||
label="联系方式"
|
|
||||||
placeholder="请输入手机号"
|
|
||||||
:rules="[
|
|
||||||
{ required: true, message: '请填写联系方式' },
|
{ required: true, message: '请填写联系方式' },
|
||||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' },
|
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' },
|
||||||
]"
|
]" />
|
||||||
/>
|
<van-field v-model="remark" label="备注" type="textarea" placeholder="选填,可备注特殊需求" rows="2" autosize />
|
||||||
<van-field
|
|
||||||
v-model="remark"
|
|
||||||
label="备注"
|
|
||||||
type="textarea"
|
|
||||||
placeholder="选填,可备注特殊需求"
|
|
||||||
rows="2"
|
|
||||||
autosize
|
|
||||||
/>
|
|
||||||
</van-cell-group>
|
|
||||||
|
|
||||||
<!-- 支付方式选择 -->
|
|
||||||
<van-cell-group title="支付方式" class="payment-methods">
|
|
||||||
<van-radio-group v-model="selectedPayment">
|
|
||||||
<van-cell
|
|
||||||
v-for="method in paymentMethods"
|
|
||||||
:key="method.value"
|
|
||||||
clickable
|
|
||||||
@click="selectedPayment = method.value"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<van-icon :name="method.icon" class="method-icon" />
|
|
||||||
</template>
|
|
||||||
<template #title>
|
|
||||||
<span class="method-label">{{ method.label }}</span>
|
|
||||||
</template>
|
|
||||||
<template #right-icon>
|
|
||||||
<van-radio :name="method.value" />
|
|
||||||
</template>
|
|
||||||
</van-cell>
|
|
||||||
</van-radio-group>
|
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
<!-- 提交订单栏 -->
|
<!-- 提交订单栏 -->
|
||||||
|
@ -152,13 +155,7 @@ async function handleSubmit() {
|
||||||
<div class="total-price">
|
<div class="total-price">
|
||||||
合计:¥{{ totalPrice.toFixed(2) }}
|
合计:¥{{ totalPrice.toFixed(2) }}
|
||||||
</div>
|
</div>
|
||||||
<van-button
|
<van-button type="primary" size="large" :loading="submitting" loading-text="提交中..." @click="handleSubmit">
|
||||||
type="primary"
|
|
||||||
size="large"
|
|
||||||
:loading="submitting"
|
|
||||||
loading-text="提交中..."
|
|
||||||
@click="handleSubmit"
|
|
||||||
>
|
|
||||||
提交订单
|
提交订单
|
||||||
</van-button>
|
</van-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -177,7 +174,8 @@ async function handleSubmit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
padding-top: 46px; /* 导航栏高度 */
|
padding-top: 46px;
|
||||||
|
/* 导航栏高度 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-container {
|
.checkout-container {
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { pinia } from "@/pinia"
|
||||||
|
import { getOpenIdApi } from "@/common/apis/shop"
|
||||||
|
|
||||||
|
|
||||||
|
export const useWxStore = defineStore("wx", () => {
|
||||||
|
// 微信授权 code
|
||||||
|
const code = ref<string>("")
|
||||||
|
// 防止 CSRF 攻击的 state 参数
|
||||||
|
const state = ref<string>("")
|
||||||
|
// 用户 openid
|
||||||
|
const openid = ref<string>("")
|
||||||
|
|
||||||
|
// 模拟微信授权获取 code(需对接实际微信接口)
|
||||||
|
const getWxAuth = async () => {
|
||||||
|
// 这里应替换为实际微信授权逻辑
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
code.value = "模拟的微信code"
|
||||||
|
state.value = Date.now().toString()
|
||||||
|
resolve()
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置 openid
|
||||||
|
const setOpenid = (id: string) => {
|
||||||
|
openid.value = id
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleWxCallback = async (params: { code?: string; state?: string }) => {
|
||||||
|
console.log('handleWxCallback:', params)
|
||||||
|
if (params.code) {
|
||||||
|
code.value = params.code
|
||||||
|
state.value = params.state || state.value
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用获取 openid 的接口
|
||||||
|
const res = await getOpenIdApi({ code: params.code })
|
||||||
|
console.log('获取 openid 成功:', res)
|
||||||
|
if (res) {
|
||||||
|
openid.value = res
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取 openid 失败:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(window as any).testWxSetOpenid = setOpenid
|
||||||
|
|
||||||
|
return { code, state, openid, getWxAuth, setOpenid, handleWxCallback }
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 用于在 setup 外使用 store
|
||||||
|
*/
|
||||||
|
export function useWxStoreOutside() {
|
||||||
|
return useWxStore(pinia)
|
||||||
|
}
|
|
@ -4,5 +4,5 @@ import { installPermissionDirective } from "./permission-directive"
|
||||||
|
|
||||||
export function installPlugins(app: App) {
|
export function installPlugins(app: App) {
|
||||||
installPermissionDirective(app)
|
installPermissionDirective(app)
|
||||||
// installConsole()
|
installConsole()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { useTitle } from "@@/composables/useTitle"
|
||||||
import { getToken } from "@@/utils/cache/cookies"
|
import { getToken } from "@@/utils/cache/cookies"
|
||||||
import NProgress from "nprogress"
|
import NProgress from "nprogress"
|
||||||
|
|
||||||
|
|
||||||
NProgress.configure({ showSpinner: false })
|
NProgress.configure({ showSpinner: false })
|
||||||
|
|
||||||
const { setTitle } = useTitle()
|
const { setTitle } = useTitle()
|
||||||
|
|
|
@ -21,8 +21,6 @@ declare module 'vue' {
|
||||||
VanImage: typeof import('vant/es')['Image']
|
VanImage: typeof import('vant/es')['Image']
|
||||||
VanLoading: typeof import('vant/es')['Loading']
|
VanLoading: typeof import('vant/es')['Loading']
|
||||||
VanNavBar: typeof import('vant/es')['NavBar']
|
VanNavBar: typeof import('vant/es')['NavBar']
|
||||||
VanRadio: typeof import('vant/es')['Radio']
|
|
||||||
VanRadioGroup: typeof import('vant/es')['RadioGroup']
|
|
||||||
VanSidebar: typeof import('vant/es')['Sidebar']
|
VanSidebar: typeof import('vant/es')['Sidebar']
|
||||||
VanSidebarItem: typeof import('vant/es')['SidebarItem']
|
VanSidebarItem: typeof import('vant/es')['SidebarItem']
|
||||||
VanTabbar: typeof import('vant/es')['Tabbar']
|
VanTabbar: typeof import('vant/es')['Tabbar']
|
||||||
|
|
|
@ -82,7 +82,7 @@ export default defineConfig(({ mode }) => {
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
// 打包构建时移除 console.log
|
// 打包构建时移除 console.log
|
||||||
pure: ["console.log"],
|
// pure: ["console.log"],
|
||||||
// 打包构建时移除 debugger
|
// 打包构建时移除 debugger
|
||||||
drop: ["debugger"],
|
drop: ["debugger"],
|
||||||
// 打包构建时移除所有注释
|
// 打包构建时移除所有注释
|
||||||
|
|
Loading…
Reference in New Issue