@@ -11,7 +11,10 @@ import PageShell from '../../components/PageShell';
|
||||
import WechatLoginButton from '../../components/WechatLoginButton';
|
||||
import { BRAND_LOGO_WIDE_URL } from '@dukang/shared-types';
|
||||
import { finishLoginNavigate } from '../../lib/auth-nav';
|
||||
import { bindWechatForUser } from '../../lib/wechat-auth';
|
||||
import {
|
||||
bindWechatForUser,
|
||||
loginWithWechat,
|
||||
} from '../../lib/wechat-auth';
|
||||
import { fetchUserProfile } from '../../lib/pay-wechat';
|
||||
import { saveUserPhone, resolveDefaultUserPhone } from '../../lib/user-phone';
|
||||
import {
|
||||
@@ -21,7 +24,6 @@ import {
|
||||
type MiniWechatProfile,
|
||||
} from '../../lib/mini-wechat-profile';
|
||||
import { isLoggedIn, request, saveAuth, toast, type SessionPayload } from '../../lib/api';
|
||||
import { loginWithWechat } from '../../lib/wechat-auth';
|
||||
|
||||
function normalizePhone(value: string) {
|
||||
return value.replace(/\D/g, '').slice(0, 11);
|
||||
@@ -224,7 +226,10 @@ export default function LoginPage() {
|
||||
|
||||
if (completeMode === 'wechat' && isLoggedIn()) {
|
||||
const result = await bindWechatForUser(wxInfo);
|
||||
if (!result.ok && result.needBindPhone) {
|
||||
if (!result.ok && 'redirecting' in result && result.redirecting) {
|
||||
return;
|
||||
}
|
||||
if (!result.ok && 'needBindPhone' in result && result.needBindPhone) {
|
||||
setBindMode(true);
|
||||
setWxSessionKey(result.wxSessionKey);
|
||||
setCompleteMode('phone');
|
||||
@@ -240,11 +245,13 @@ export default function LoginPage() {
|
||||
return;
|
||||
}
|
||||
const result = await loginWithWechat();
|
||||
handleWechatLoginResult(result, wxInfo);
|
||||
if (result) handleWechatLoginResult(result, wxInfo);
|
||||
} catch (e) {
|
||||
const raw = e instanceof Error ? e.message : '微信登录失败';
|
||||
const hint = /invalid code/i.test(raw)
|
||||
? '微信 code 无效:请确认后端 WX_MINI_APP_ID 与小程序 appid 一致,或本地联调使用 MOCK_WECHAT'
|
||||
? process.env.TARO_ENV === 'weapp'
|
||||
? '微信 code 无效:请确认后端 WX_MINI_APP_ID 与小程序 appid 一致,或本地联调使用 MOCK_WECHAT'
|
||||
: '微信授权失败:请确认公众号 WX_APP_ID / 网页授权域名配置正确'
|
||||
: raw;
|
||||
setMsg(hint);
|
||||
} finally {
|
||||
|
||||
@@ -11,10 +11,10 @@ import { bindWechatForUser } from '../../lib/wechat-auth';
|
||||
import { fetchUserProfile } from '../../lib/pay-wechat';
|
||||
import {
|
||||
fetchMiniWechatUserInfo,
|
||||
getCachedWxProfile,
|
||||
mergeWxDisplayProfile,
|
||||
} from '../../lib/mini-wechat-profile';
|
||||
import { isLoggedIn, logout, request, toast, type UserProfile } from '../../lib/api';
|
||||
import { isWechatEnv } from '../../lib/weixin';
|
||||
|
||||
const ORDER_SHORTCUTS = [
|
||||
{ tab: 'pending_pay', icon: '付', label: '待付款' },
|
||||
@@ -111,13 +111,39 @@ export default function MinePage() {
|
||||
toast('当前环境未开启微信授权');
|
||||
return;
|
||||
}
|
||||
if (process.env.TARO_ENV !== 'weapp') {
|
||||
toast('请在微信小程序中完成微信授权');
|
||||
return;
|
||||
}
|
||||
|
||||
setBindingWx(true);
|
||||
try {
|
||||
if (process.env.TARO_ENV === 'h5') {
|
||||
if (!isWechatEnv()) {
|
||||
toast('请在微信内打开后授权');
|
||||
return;
|
||||
}
|
||||
const result = await bindWechatForUser();
|
||||
if (!result.ok && 'redirecting' in result && result.redirecting) {
|
||||
return;
|
||||
}
|
||||
if (!result.ok && 'needBindPhone' in result && result.needBindPhone) {
|
||||
goLogin('/pages/mine/index', {
|
||||
bindMode: '1',
|
||||
wxSessionKey: result.wxSessionKey,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (result.ok) {
|
||||
setProfile(
|
||||
mergeWxDisplayProfile({
|
||||
...(result.profile ?? {}),
|
||||
id: result.profile?.id ?? profile?.id ?? '',
|
||||
hasWechat: true,
|
||||
}),
|
||||
);
|
||||
loadProfile();
|
||||
toast('微信授权成功', 'success');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let wxInfo = null;
|
||||
try {
|
||||
wxInfo = await fetchMiniWechatUserInfo();
|
||||
@@ -127,7 +153,7 @@ export default function MinePage() {
|
||||
}
|
||||
|
||||
const result = await bindWechatForUser(wxInfo);
|
||||
if (!result.ok && result.needBindPhone) {
|
||||
if (!result.ok && 'needBindPhone' in result && result.needBindPhone) {
|
||||
goLogin('/pages/mine/index', { bindMode: '1', wxSessionKey: result.wxSessionKey });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import Taro, { useRouter } from '@tarojs/taro';
|
||||
import PageShell from '../../components/PageShell';
|
||||
import SubPageHeader from '../../components/SubPageHeader';
|
||||
import { buildAddressListUrl, buildPayUrl, readCheckoutContext } from '../../lib/checkout-nav';
|
||||
import { tryGetClientGpsLocation } from '../../lib/client-location';
|
||||
import { maskPhone } from '../../lib/phone';
|
||||
import { ensurePayReady } from '../../lib/pay-ready';
|
||||
import { request, toast } from '../../lib/api';
|
||||
import { request } from '../../lib/api';
|
||||
import { getProductMainImage } from '../../lib/product-images';
|
||||
|
||||
type Address = {
|
||||
@@ -127,12 +128,19 @@ export default function OrderConfirmPage() {
|
||||
}
|
||||
|
||||
async function doSubmit() {
|
||||
let clientLocation = null;
|
||||
try {
|
||||
clientLocation = await tryGetClientGpsLocation();
|
||||
} catch {
|
||||
/* GPS 获取失败不阻塞下单 */
|
||||
}
|
||||
const order = await request<{ id: string }>('/trade/orders', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
productId,
|
||||
quantity,
|
||||
addressId,
|
||||
...(clientLocation ? { clientLocation } : {}),
|
||||
},
|
||||
});
|
||||
Taro.redirectTo({
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom',
|
||||
navigationBarTitleText: '订单详情',
|
||||
enableShareAppMessage: true,
|
||||
enableShareTimeline: true,
|
||||
});
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { useRouter } from '@tarojs/taro';
|
||||
import { useRouter, useShareAppMessage, useShareTimeline } from '@tarojs/taro';
|
||||
import PageShell from '../../components/PageShell';
|
||||
import SubPageHeader from '../../components/SubPageHeader';
|
||||
import ShareNavButton from '../../components/ShareNavButton';
|
||||
import WechatShareReady from '../../components/WechatShareReady';
|
||||
import { request, toast } from '../../lib/api';
|
||||
import {
|
||||
DEFAULT_SHARE_DESC,
|
||||
DEFAULT_SHARE_TITLE,
|
||||
toWeappShareMessage,
|
||||
} from '../../lib/wechat-share';
|
||||
|
||||
type OrderDetail = {
|
||||
id: string;
|
||||
@@ -28,9 +35,30 @@ export default function OrderDetailPage() {
|
||||
.catch((e) => toast(e instanceof Error ? e.message : '加载失败'));
|
||||
}, [orderId]);
|
||||
|
||||
const sharePayload = useMemo(
|
||||
() => ({
|
||||
title: order?.productName
|
||||
? `我买了${order.productName} · 杜康好客`
|
||||
: DEFAULT_SHARE_TITLE,
|
||||
desc: DEFAULT_SHARE_DESC,
|
||||
path: orderId ? `/pages/order-detail/index?id=${orderId}` : '/pages/home/index',
|
||||
}),
|
||||
[order, orderId],
|
||||
);
|
||||
|
||||
useShareAppMessage(() => toWeappShareMessage(sharePayload));
|
||||
useShareTimeline(() => ({
|
||||
title: sharePayload.title || DEFAULT_SHARE_TITLE,
|
||||
query: orderId ? `id=${orderId}` : '',
|
||||
}));
|
||||
|
||||
return (
|
||||
<PageShell variant="sub" className="order-detail-page">
|
||||
<SubPageHeader title="订单详情" />
|
||||
<WechatShareReady payload={sharePayload} />
|
||||
<SubPageHeader
|
||||
title="订单详情"
|
||||
right={<ShareNavButton payload={sharePayload} />}
|
||||
/>
|
||||
<View className="sub-page-body">
|
||||
{!order ? (
|
||||
<View className="u-empty">加载中…</View>
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useRouter } from '@tarojs/taro';
|
||||
import Taro, { useDidShow, useRouter } from '@tarojs/taro';
|
||||
import PageShell from '../../components/PageShell';
|
||||
import SubPageHeader from '../../components/SubPageHeader';
|
||||
import WechatLoginButton from '../../components/WechatLoginButton';
|
||||
import { ensurePayReady } from '../../lib/pay-ready';
|
||||
import {
|
||||
authorizeWechatForPay,
|
||||
fetchClientConfig,
|
||||
fetchUserProfile,
|
||||
isWechatAuthRequiredError,
|
||||
needsWechatAuthForPay,
|
||||
payOrder,
|
||||
saveWechatLoginResult,
|
||||
} from '../../lib/pay-wechat';
|
||||
import { applyWechatLoginResult } from '../../lib/wechat-auth';
|
||||
import { isWechatEnv } from '../../lib/weixin';
|
||||
import { goLogin } from '../../lib/auth-nav';
|
||||
import { request, toast } from '../../lib/api';
|
||||
|
||||
export default function PayPage() {
|
||||
const router = useRouter();
|
||||
const orderId = router.params.orderId ?? '';
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [authLoading, setAuthLoading] = useState(false);
|
||||
const [mockMode, setMockMode] = useState(true);
|
||||
const [needsWechatAuth, setNeedsWechatAuth] = useState(false);
|
||||
const [msg, setMsg] = useState('');
|
||||
@@ -27,23 +34,27 @@ export default function PayPage() {
|
||||
? `/pages/pay/index?orderId=${orderId}`
|
||||
: '/pages/pay/index';
|
||||
|
||||
useEffect(() => {
|
||||
if (!orderId) return;
|
||||
void ensurePayReady(returnPath);
|
||||
}, [orderId, returnPath]);
|
||||
const refreshPayReadiness = useCallback(async () => {
|
||||
try {
|
||||
const [config, profile] = await Promise.all([fetchClientConfig(), fetchUserProfile()]);
|
||||
setMockMode(config.mockPay);
|
||||
setNeedsWechatAuth(needsWechatAuthForPay(config, profile));
|
||||
return profile;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useDidShow(() => {
|
||||
void refreshPayReadiness();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function load() {
|
||||
try {
|
||||
const [config, profile] = await Promise.all([fetchClientConfig(), fetchUserProfile()]);
|
||||
setMockMode(config.mockPay);
|
||||
setNeedsWechatAuth(needsWechatAuthForPay(config, profile));
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
if (!orderId) return;
|
||||
if (process.env.TARO_ENV === 'weapp') {
|
||||
void ensurePayReady(returnPath);
|
||||
}
|
||||
void load();
|
||||
}, []);
|
||||
}, [orderId, returnPath]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!orderId) {
|
||||
@@ -67,6 +78,38 @@ export default function PayPage() {
|
||||
});
|
||||
}, [orderId]);
|
||||
|
||||
async function wechatAuthorize() {
|
||||
setAuthLoading(true);
|
||||
setMsg('');
|
||||
try {
|
||||
if (!isWechatEnv()) {
|
||||
setMsg('请在微信内打开以授权微信支付');
|
||||
return;
|
||||
}
|
||||
if (process.env.TARO_ENV === 'weapp') {
|
||||
const ready = await ensurePayReady(returnPath);
|
||||
if (ready) await refreshPayReadiness();
|
||||
return;
|
||||
}
|
||||
const result = await authorizeWechatForPay();
|
||||
if (result) {
|
||||
if (result.needBindPhone && result.wxSessionKey) {
|
||||
goLogin(returnPath, { bindMode: '1', wxSessionKey: result.wxSessionKey });
|
||||
return;
|
||||
}
|
||||
if (saveWechatLoginResult(result) || applyWechatLoginResult(result)) {
|
||||
setMsg('');
|
||||
await refreshPayReadiness();
|
||||
toast('微信授权成功', 'success');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
setMsg(e instanceof Error ? e.message : '微信授权失败');
|
||||
} finally {
|
||||
setAuthLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function pay() {
|
||||
if (!orderId) {
|
||||
toast('订单不存在');
|
||||
@@ -74,8 +117,11 @@ export default function PayPage() {
|
||||
}
|
||||
if (needsWechatAuth) {
|
||||
setMsg('请先完成微信授权后再支付');
|
||||
const ready = await ensurePayReady(returnPath);
|
||||
if (!ready) return;
|
||||
if (process.env.TARO_ENV === 'h5') {
|
||||
await wechatAuthorize();
|
||||
} else {
|
||||
await ensurePayReady(returnPath);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,7 +142,6 @@ export default function PayPage() {
|
||||
if (isWechatAuthRequiredError(e)) {
|
||||
setNeedsWechatAuth(true);
|
||||
setMsg('微信支付需要先完成微信授权');
|
||||
await ensurePayReady(returnPath);
|
||||
return;
|
||||
}
|
||||
const message = e instanceof Error ? e.message : '支付失败';
|
||||
@@ -124,6 +169,20 @@ export default function PayPage() {
|
||||
</Text>
|
||||
<Text className="pay-status-amount">¥{payAmount}</Text>
|
||||
</View>
|
||||
|
||||
{needsWechatAuth ? (
|
||||
<View className="pay-wechat-auth-card">
|
||||
<Text className="pay-wechat-auth-title">尚未授权微信</Text>
|
||||
<Text className="pay-wechat-auth-desc">
|
||||
授权后可安全调起微信支付,不会重复扣款
|
||||
</Text>
|
||||
<WechatLoginButton
|
||||
loading={authLoading}
|
||||
onClick={() => void wechatAuthorize()}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<View className="order-card">
|
||||
<View className="order-row">
|
||||
<Text className="order-row-label">订单号</Text>
|
||||
@@ -140,15 +199,34 @@ export default function PayPage() {
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
{msg ? <Text className="u-muted" style={{ display: 'block', marginTop: 12 }}>{msg}</Text> : null}
|
||||
{msg ? (
|
||||
<Text className="pay-wechat-auth-msg" style={{ display: 'block', marginTop: 12 }}>
|
||||
{msg}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
<View className="pay-bar">
|
||||
<View
|
||||
className="order-confirm-submit"
|
||||
style={{ flex: 1, opacity: loading ? 0.7 : 1 }}
|
||||
onClick={() => !loading && void pay()}
|
||||
style={{ flex: 1, opacity: loading || needsWechatAuth ? 0.7 : 1 }}
|
||||
onClick={() => {
|
||||
if (loading) return;
|
||||
if (needsWechatAuth) {
|
||||
void wechatAuthorize();
|
||||
return;
|
||||
}
|
||||
void pay();
|
||||
}}
|
||||
>
|
||||
<Text>{loading ? '支付中…' : needsWechatAuth ? '去授权' : '立即支付'}</Text>
|
||||
<Text>
|
||||
{loading
|
||||
? '支付中…'
|
||||
: needsWechatAuth
|
||||
? authLoading
|
||||
? '授权中…'
|
||||
: '微信一键授权'
|
||||
: '立即支付'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</PageShell>
|
||||
|
||||
Reference in New Issue
Block a user