微信支付
This commit is contained in:
parent
db4b454898
commit
a42638c17b
|
@ -1,4 +1,4 @@
|
|||
# 全局 ts 类型检查(此操作会增加 git commit 时长)
|
||||
npx vue-tsc
|
||||
# npx vue-tsc
|
||||
# 执行 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 { useUserStore } from "@/pinia/stores/user"
|
||||
import { useDark } from "@@/composables/useDark"
|
||||
import { useWxStore } from "@/pinia/stores/wx"
|
||||
|
||||
// const userStore = useUserStore()
|
||||
const wxStore = useWxStore()
|
||||
const route = useRoute()
|
||||
|
||||
const { isDark, initDark } = useDark()
|
||||
|
||||
|
@ -21,6 +24,15 @@ const isLoading = false;
|
|||
// )
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { request } from "@/http/axios"
|
||||
import { ShopGoodsResponseData } from './type'
|
||||
import { ShopGoodsResponseData, SubmitOrderRequestData, SubmitOrderResponseData } from './type'
|
||||
import { GetOpenIdRequestParams } from './type'
|
||||
|
||||
|
||||
/** 获取当前登录用户详情 */
|
||||
export function getShopGoodsApi() {
|
||||
|
@ -8,3 +10,21 @@ export function getShopGoodsApi() {
|
|||
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
|
||||
}
|
||||
|
||||
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<{
|
||||
goodsList: Goods[],
|
||||
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">
|
||||
import { useCartStore } from "@/pinia/stores/cart"
|
||||
import { useWxStore } from "@/pinia/stores/wx"
|
||||
import { storeToRefs } from "pinia"
|
||||
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 { cartItems, totalPrice } = storeToRefs(cartStore)
|
||||
|
||||
// 支付方式选项
|
||||
const paymentMethods = [
|
||||
{ value: 1, label: "微信支付", icon: "wechat" },
|
||||
{ value: 2, label: "支付宝", icon: "alipay" },
|
||||
{ value: 3, label: "银行卡支付", icon: "credit-pay" }
|
||||
]
|
||||
const wxStore = useWxStore()
|
||||
const { openid } = storeToRefs(wxStore)
|
||||
|
||||
const selectedPayment = ref<number>()
|
||||
const contact = ref("")
|
||||
const remark = ref("")
|
||||
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() {
|
||||
if (!cartItems.value.length) {
|
||||
return showConfirmDialog({
|
||||
|
@ -28,31 +60,53 @@ async function handleSubmit() {
|
|||
})
|
||||
}
|
||||
|
||||
if (!selectedPayment.value) {
|
||||
if (!openid.value) {
|
||||
return showConfirmDialog({
|
||||
title: "提示",
|
||||
message: "请选择支付方式"
|
||||
title: "登录提示",
|
||||
message: "请从微信中打开"
|
||||
})
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
// TODO: 调用实际的下单API
|
||||
console.log("提交订单", {
|
||||
items: cartItems.value,
|
||||
total: totalPrice.value,
|
||||
paymentMethod: selectedPayment.value,
|
||||
contact: contact.value,
|
||||
remark: remark.value
|
||||
})
|
||||
// 构造请求参数
|
||||
const requestData: SubmitOrderRequestData = {
|
||||
openid: openid.value, // 假设联系方式即为openid,根据实际情况调整
|
||||
goodsList: cartItems.value.map(item => ({
|
||||
goodsId: item.product.id,
|
||||
quantity: item.quantity
|
||||
}))
|
||||
}
|
||||
|
||||
// 调用提交订单接口
|
||||
const { data } = await submitOrderApi(requestData)
|
||||
|
||||
await showConfirmDialog({
|
||||
title: "提交成功",
|
||||
message: "订单已创建,正在跳转支付..."
|
||||
message: `订单号:${data.orderId},正在跳转支付...`
|
||||
})
|
||||
|
||||
// 调用微信支付
|
||||
if (data.paymentInfo) {
|
||||
await callWxJsApi(data.paymentInfo);
|
||||
// 支付成功后跳转
|
||||
router.push('/order-success');
|
||||
} else {
|
||||
throw new Error('无法获取支付信息');
|
||||
}
|
||||
|
||||
// 清空购物车
|
||||
cartStore.clearCart()
|
||||
|
||||
// 这里添加支付跳转逻辑(根据paymentInfo处理)
|
||||
// 例如:调用微信JSAPI支付等
|
||||
} catch (error) {
|
||||
if(error !== 'user_cancel') {
|
||||
showConfirmDialog({
|
||||
title: "支付失败",
|
||||
message: error instanceof Error ? error.message : "支付流程中断"
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
|
@ -61,31 +115,14 @@ async function handleSubmit() {
|
|||
|
||||
<template>
|
||||
<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">
|
||||
<!-- 原有商品列表等代码保持不动 -->
|
||||
<van-cell-group class="product-list">
|
||||
<van-cell
|
||||
v-for="item in cartItems"
|
||||
:key="item.product.id"
|
||||
class="product-item"
|
||||
>
|
||||
<van-cell v-for="item in cartItems" :key="item.product.id" class="product-item">
|
||||
<template #icon>
|
||||
<van-image
|
||||
:src="item.product.image"
|
||||
width="60"
|
||||
height="60"
|
||||
class="product-image"
|
||||
/>
|
||||
<van-image :src="item.product.image" width="60" height="60" class="product-image" />
|
||||
</template>
|
||||
|
||||
<div class="product-info">
|
||||
|
@ -106,45 +143,11 @@ async function handleSubmit() {
|
|||
|
||||
<!-- 联系方式与备注 -->
|
||||
<van-cell-group class="contact-form">
|
||||
<van-field
|
||||
v-model="contact"
|
||||
label="联系方式"
|
||||
placeholder="请输入手机号"
|
||||
:rules="[
|
||||
{ required: true, message: '请填写联系方式' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' },
|
||||
]"
|
||||
/>
|
||||
<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-field v-model="contact" label="联系方式" placeholder="请输入手机号" :rules="[
|
||||
{ required: true, message: '请填写联系方式' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' },
|
||||
]" />
|
||||
<van-field v-model="remark" label="备注" type="textarea" placeholder="选填,可备注特殊需求" rows="2" autosize />
|
||||
</van-cell-group>
|
||||
|
||||
<!-- 提交订单栏 -->
|
||||
|
@ -152,13 +155,7 @@ async function handleSubmit() {
|
|||
<div class="total-price">
|
||||
合计:¥{{ totalPrice.toFixed(2) }}
|
||||
</div>
|
||||
<van-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="submitting"
|
||||
loading-text="提交中..."
|
||||
@click="handleSubmit"
|
||||
>
|
||||
<van-button type="primary" size="large" :loading="submitting" loading-text="提交中..." @click="handleSubmit">
|
||||
提交订单
|
||||
</van-button>
|
||||
</div>
|
||||
|
@ -177,7 +174,8 @@ async function handleSubmit() {
|
|||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding-top: 46px; /* 导航栏高度 */
|
||||
padding-top: 46px;
|
||||
/* 导航栏高度 */
|
||||
}
|
||||
|
||||
.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) {
|
||||
installPermissionDirective(app)
|
||||
// installConsole()
|
||||
installConsole()
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useTitle } from "@@/composables/useTitle"
|
|||
import { getToken } from "@@/utils/cache/cookies"
|
||||
import NProgress from "nprogress"
|
||||
|
||||
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
const { setTitle } = useTitle()
|
||||
|
|
|
@ -21,8 +21,6 @@ declare module 'vue' {
|
|||
VanImage: typeof import('vant/es')['Image']
|
||||
VanLoading: typeof import('vant/es')['Loading']
|
||||
VanNavBar: typeof import('vant/es')['NavBar']
|
||||
VanRadio: typeof import('vant/es')['Radio']
|
||||
VanRadioGroup: typeof import('vant/es')['RadioGroup']
|
||||
VanSidebar: typeof import('vant/es')['Sidebar']
|
||||
VanSidebarItem: typeof import('vant/es')['SidebarItem']
|
||||
VanTabbar: typeof import('vant/es')['Tabbar']
|
||||
|
|
|
@ -82,7 +82,7 @@ export default defineConfig(({ mode }) => {
|
|||
? undefined
|
||||
: {
|
||||
// 打包构建时移除 console.log
|
||||
pure: ["console.log"],
|
||||
// pure: ["console.log"],
|
||||
// 打包构建时移除 debugger
|
||||
drop: ["debugger"],
|
||||
// 打包构建时移除所有注释
|
||||
|
|
Loading…
Reference in New Issue