소스 검색

fix: 修复支付宝回调延迟与多层页面Bug

- 修复 ExpoNativeAlipayDelegate 方法签名不匹配 (4参数→3参数)
  导致 Expo 的 SubscriberManager 从未分派 openURL 给支付宝插件
- 修复 handleSuccess 竞态条件: await 后再检查 isResolved
- 防止多个 AppState setInterval 实例 (pollingStarted 守卫)
- router.push → router.replace 防止页面堆叠
- isNavigatingRef 永不重置, 杜绝延迟重复导航
- safepay.tsx 改为纯占位 (return null)
- 添加 patch-package + postinstall 确保 EAS 构建生效
- Build Number: 17
zbb 1 개월 전
부모
커밋
05686de1a8

+ 1 - 1
app.json

@@ -12,7 +12,7 @@
       "supportsTablet": false,
       "bundleIdentifier": "com.asios",
       "appleTeamId": "Y9ZVX3FRX6",
-      "buildNumber": "11",
+      "buildNumber": "17",
       "infoPlist": {
         "ITSAppUsesNonExemptEncryption": false,
         "NSPhotoLibraryUsageDescription": "App需要访问您的相册以便您可以上传头像或保存商品分享图片。",

+ 61 - 12
app/award-detail/components/CheckoutModal.tsx

@@ -260,24 +260,21 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
       }
     };
 
+    const [verifyLoading, setVerifyLoading] = useState(false);
     const isNavigatingRef = useRef(false);
 
     const handleSuccess = (tradeNo: string) => {
+      setVerifyLoading(false);
       setVisible(false);
 
       if (isNavigatingRef.current) return;
       isNavigatingRef.current = true;
 
-      router.push({
+      router.replace({
         pathname: "/lottery" as any,
         params: { tradeNo, num, poolId },
       });
       onSuccess({ tradeNo, num });
-
-      // Reset after a short delay to allow subsequent purchases
-      setTimeout(() => {
-        isNavigatingRef.current = false;
-      }, 2000);
     };
 
     const handleNativePay = async (
@@ -288,6 +285,7 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
       if (type === "ALIPAY" || type.includes("ALIPAY")) {
         let appStateSub: any = null;
         let isResolved = false;
+        let pollingStarted = false;
 
         try {
           Alipay.setAlipayScheme("alipay2021005175632205");
@@ -296,8 +294,11 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
           appStateSub = AppState.addEventListener(
             "change",
             async (nextAppState) => {
-              if (nextAppState === "active" && !isResolved) {
-                // 瞬间回退没有拿到原生支付结果时,立即开始每秒轮询后端
+              if (nextAppState === "active" && !isResolved && !pollingStarted) {
+                pollingStarted = true;
+                const loadingTimer = setTimeout(() => {
+                  if (!isResolved) setVerifyLoading(true);
+                }, 800);
                 let attempts = 0;
                 const pollInterval = setInterval(async () => {
                   if (isResolved) {
@@ -307,22 +308,30 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
                   attempts++;
                   try {
                     const res = await getApplyResult(tradeNo);
+                    // 关键修复:await 返回后再次检查 isResolved,防止竞态
+                    if (isResolved) {
+                      clearInterval(pollInterval);
+                      return;
+                    }
                     if (
                       res?.paySuccess ||
                       (res?.inventoryList && res.inventoryList.length > 0)
                     ) {
                       isResolved = true;
                       clearInterval(pollInterval);
+                      clearTimeout(loadingTimer);
+                      setVerifyLoading(false);
                       handleSuccess(tradeNo);
                     }
                   } catch (e) {
                     console.log("Fallback poll failed", e);
                   }
-                  if (attempts >= 5) {
-                    // 5秒后放弃
+                  if (attempts >= 15) {
                     clearInterval(pollInterval);
+                    clearTimeout(loadingTimer);
+                    setVerifyLoading(false);
                   }
-                }, 1000);
+                }, 300);
               }
             },
           );
@@ -330,6 +339,7 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
           const result = await Alipay.pay(payInfo);
           if (isResolved) return; // if fallback already worked
           isResolved = true;
+          setVerifyLoading(false);
           console.log("Alipay Result:", result);
 
           const status = result?.resultStatus;
@@ -341,6 +351,7 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
           } else {
             // Also poll server just in case the status is wrong but it succeeded
             try {
+              setVerifyLoading(true);
               const res = await getApplyResult(tradeNo);
               if (
                 res?.paySuccess ||
@@ -349,16 +360,21 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
                 handleSuccess(tradeNo);
                 return;
               }
-            } catch (ignore) {}
+            } catch (ignore) {
+            } finally {
+              setVerifyLoading(false);
+            }
 
             Alert.alert("支付中断", `状态码: ${status}`);
           }
         } catch (e: any) {
           if (isResolved) return;
           isResolved = true;
+          setVerifyLoading(false);
           console.log("Alipay Error:", e);
           Alert.alert("支付异常", e.message || "调用支付宝失败");
         } finally {
+          setVerifyLoading(false);
           if (appStateSub) {
             appStateSub.remove();
           }
@@ -715,12 +731,45 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
             </View>
           </View>
         </Modal>
+
+        {/* 支付结果轮询确认加载中 */}
+        <Modal
+          visible={verifyLoading}
+          transparent
+          animationType="fade"
+          onRequestClose={() => {}}
+        >
+          <View style={styles.verifyLoadingOverlay}>
+            <View style={styles.verifyLoadingBox}>
+              <ActivityIndicator size="large" color="#ff9600" />
+              <Text style={styles.verifyLoadingText}>正在确认支付结果...</Text>
+            </View>
+          </View>
+        </Modal>
       </>
     );
   },
 );
 
 const styles = StyleSheet.create({
+  verifyLoadingOverlay: {
+    ...StyleSheet.absoluteFillObject,
+    backgroundColor: "rgba(0,0,0,0.4)",
+    justifyContent: "center",
+    alignItems: "center",
+    zIndex: 9999,
+  },
+  verifyLoadingBox: {
+    backgroundColor: "rgba(0,0,0,0.8)",
+    padding: 20,
+    borderRadius: 12,
+    alignItems: "center",
+  },
+  verifyLoadingText: {
+    color: "#fff",
+    marginTop: 10,
+    fontSize: 14,
+  },
   overlay: {
     flex: 1,
     backgroundColor: "rgba(0,0,0,0.5)",

+ 262 - 82
app/cloud-warehouse/components/CheckoutModal.tsx

@@ -1,10 +1,11 @@
-import { Image } from 'expo-image';
-import Alipay from 'expo-native-alipay';
-import { useRouter } from 'expo-router';
-import React, { useEffect, useState } from 'react';
+import { Image } from "expo-image";
+import Alipay from "expo-native-alipay";
+import { useRouter } from "expo-router";
+import React, { useEffect, useState } from "react";
 import {
     ActivityIndicator,
     Alert,
+    AppState,
     ImageBackground,
     Modal,
     Platform,
@@ -13,26 +14,41 @@ import {
     Text,
     TouchableOpacity,
     View,
-} from 'react-native';
-import { useSafeAreaInsets } from 'react-native-safe-area-context';
+} from "react-native";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
 
-import { Images } from '@/constants/images';
-import { Address, getDefaultAddress } from '@/services/address';
-import { takeApply, takePreview } from '@/services/award';
+import { Images } from "@/constants/images";
+import { Address, getDefaultAddress } from "@/services/address";
+import { takeApply, takePreview } from "@/services/award";
 
 interface GroupedGoods {
   total: number;
-  data: { id: string; cover: string; spuId: string; level: string; name?: string };
+  data: {
+    id: string;
+    cover: string;
+    spuId: string;
+    level: string;
+    name?: string;
+  };
 }
 
 interface CheckoutModalProps {
   visible: boolean;
-  selectedItems: Array<{ id: string; spu: { id: string; name: string; cover: string }; level: string }>;
+  selectedItems: Array<{
+    id: string;
+    spu: { id: string; name: string; cover: string };
+    level: string;
+  }>;
   onClose: () => void;
   onSuccess: () => void;
 }
 
-export default function CheckoutModal({ visible, selectedItems, onClose, onSuccess }: CheckoutModalProps) {
+export default function CheckoutModal({
+  visible,
+  selectedItems,
+  onClose,
+  onSuccess,
+}: CheckoutModalProps) {
   const router = useRouter();
   const insets = useSafeAreaInsets();
   const [loading, setLoading] = useState(false);
@@ -41,9 +57,11 @@ export default function CheckoutModal({ visible, selectedItems, onClose, onSucce
   const [expressAmount, setExpressAmount] = useState(0);
   const [goodsList, setGoodsList] = useState<GroupedGoods[]>([]);
 
+  const [verifyLoading, setVerifyLoading] = useState(false);
+
   const showAlert = (msg: string) => {
-    if (Platform.OS === 'web') window.alert(msg);
-    else Alert.alert('提示', msg);
+    if (Platform.OS === "web") window.alert(msg);
+    else Alert.alert("提示", msg);
   };
 
   useEffect(() => {
@@ -60,8 +78,8 @@ export default function CheckoutModal({ visible, selectedItems, onClose, onSucce
       setAddress(addr);
 
       // 获取提货预览
-      const ids = selectedItems.map(item => item.id);
-      const res = await takePreview(ids, addr?.id || '');
+      const ids = selectedItems.map((item) => item.id);
+      const res = await takePreview(ids, addr?.id || "");
       if (res) {
         setExpressAmount(res.expressAmount || 0);
       }
@@ -87,37 +105,33 @@ export default function CheckoutModal({ visible, selectedItems, onClose, onSucce
       });
       setGoodsList(Object.values(goodsMap));
     } catch (e) {
-      console.error('加载提货信息失败:', e);
+      console.error("加载提货信息失败:", e);
     }
     setLoading(false);
   };
 
   const goToAddress = () => {
     onClose();
-    router.push('/address?type=1' as any);
+    router.push("/address?type=1" as any);
   };
 
-  /* 
+  /*
    * Handle Submit with Payment Choice
    */
   const handleSubmit = async () => {
     if (!address) {
-      showAlert('请选择收货地址');
+      showAlert("请选择收货地址");
       return;
     }
-    
+
     if (expressAmount > 0) {
-        Alert.alert(
-            '支付运费',
-            `需支付运费 ¥${expressAmount}`,
-            [
-                { text: '取消', style: 'cancel' },
-                { text: '钱包支付', onPress: () => processTakeApply('WALLET') },
-                { text: '支付宝支付', onPress: () => processTakeApply('ALIPAY_APP') }
-            ]
-        );
+      Alert.alert("支付运费", `需支付运费 ¥${expressAmount}`, [
+        { text: "取消", style: "cancel" },
+        { text: "钱包支付", onPress: () => processTakeApply("WALLET") },
+        { text: "支付宝支付", onPress: () => processTakeApply("ALIPAY_APP") },
+      ]);
     } else {
-        processTakeApply('WALLET');
+      processTakeApply("WALLET");
     }
   };
 
@@ -125,38 +139,82 @@ export default function CheckoutModal({ visible, selectedItems, onClose, onSucce
     if (submitting) return;
     setSubmitting(true);
     try {
-      const ids = selectedItems.map(item => item.id);
+      const ids = selectedItems.map((item) => item.id);
       const res: any = await takeApply(ids, address!.id, paymentType);
-      
-      console.log('Take Apply Res:', res, paymentType);
-
-      if (paymentType === 'ALIPAY_APP' && res?.payInfo) {
-           Alipay.setAlipayScheme('alipay2021005175632205');
-           const result = await Alipay.pay(res.payInfo);
-           console.log('Alipay Result:', result);
-           if (result?.resultStatus === '9000') {
-               showAlert('提货成功');
-               onSuccess();
-           } else {
-               showAlert('支付未完成');
-           }
+
+      console.log("Take Apply Res:", res, paymentType);
+
+      if (paymentType === "ALIPAY_APP" && res?.payInfo) {
+        let appStateSub: any = null;
+        let isResolved = false;
+
+        try {
+          Alipay.setAlipayScheme("alipay2021005175632205");
+
+          appStateSub = AppState.addEventListener(
+            "change",
+            async (nextAppState) => {
+              if (nextAppState === "active" && !isResolved) {
+                setTimeout(async () => {
+                  if (!isResolved) {
+                    isResolved = true;
+                    setVerifyLoading(false);
+                    showAlert("支付完成正在核实");
+                    onSuccess();
+                  }
+                }, 500);
+              }
+            },
+          );
+
+          const result = await Alipay.pay(res.payInfo);
+          if (isResolved) return;
+          isResolved = true;
+          setVerifyLoading(false);
+          console.log("Alipay Result:", result);
+          if (result?.resultStatus === "9000") {
+            showAlert("提货成功");
+            onSuccess();
+          } else {
+            showAlert("支付未完成");
+          }
+        } catch (e) {
+          isResolved = true;
+          setVerifyLoading(false);
+          console.error("Alipay Error:", e);
+          showAlert("调用支付宝失败");
+        } finally {
+          setVerifyLoading(false);
+          if (appStateSub) {
+            appStateSub.remove();
+          }
+        }
       } else if (res) {
-         // Wallet payment or free success
-         showAlert('提货成功');
-         onSuccess();
+        // Wallet payment or free success
+        showAlert("提货成功");
+        onSuccess();
       }
     } catch (e) {
-      console.error('提货失败:', e);
+      console.error("提货失败:", e);
       // Usually axios interceptor handles error alerts, but just in case
-      // showAlert('提货失败'); 
+      // showAlert('提货失败');
     }
     setSubmitting(false);
   };
 
   return (
-    <Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
+    <Modal
+      visible={visible}
+      transparent
+      animationType="slide"
+      onRequestClose={onClose}
+    >
       <View style={styles.overlay}>
-        <TouchableOpacity style={styles.overlayBg} onPress={onClose} activeOpacity={1} />
+        <TouchableOpacity
+          style={styles.overlayBg}
+          onPress={onClose}
+          activeOpacity={1}
+        />
         <View style={[styles.container, { paddingBottom: insets.bottom + 20 }]}>
           {/* 标题 */}
           <View style={styles.header}>
@@ -177,9 +235,15 @@ export default function CheckoutModal({ visible, selectedItems, onClose, onSucce
                 <ScrollView horizontal showsHorizontalScrollIndicator={false}>
                   {goodsList.map((goods, idx) => (
                     <View key={idx} style={styles.goodsItem}>
-                      <Image source={{ uri: goods.data.cover }} style={styles.goodsImg} contentFit="contain" />
+                      <Image
+                        source={{ uri: goods.data.cover }}
+                        style={styles.goodsImg}
+                        contentFit="contain"
+                      />
                       <View style={styles.goodsCount}>
-                        <Text style={styles.goodsCountText}>x{goods.total}</Text>
+                        <Text style={styles.goodsCountText}>
+                          x{goods.total}
+                        </Text>
                       </View>
                     </View>
                   ))}
@@ -195,56 +259,172 @@ export default function CheckoutModal({ visible, selectedItems, onClose, onSucce
               )}
 
               {/* 收货地址 */}
-              <TouchableOpacity style={styles.addressSection} onPress={goToAddress}>
+              <TouchableOpacity
+                style={styles.addressSection}
+                onPress={goToAddress}
+              >
                 {!address ? (
                   <Text style={styles.noAddress}>请填写收货地址</Text>
                 ) : (
                   <View style={styles.addressInfo}>
-                    <Text style={styles.addressName}>{address.contactName} {address.contactNo}</Text>
-                    <Text style={styles.addressDetail}>{address.province}{address.city}{address.district}{address.address}</Text>
+                    <Text style={styles.addressName}>
+                      {address.contactName} {address.contactNo}
+                    </Text>
+                    <Text style={styles.addressDetail}>
+                      {address.province}
+                      {address.city}
+                      {address.district}
+                      {address.address}
+                    </Text>
                   </View>
                 )}
                 <Text style={styles.arrowIcon}>›</Text>
               </TouchableOpacity>
 
               {/* 提交按钮 */}
-              <TouchableOpacity style={styles.submitBtn} onPress={handleSubmit} disabled={submitting}>
-                <ImageBackground source={{ uri: Images.common.loginBtn }} style={styles.submitBtnBg} resizeMode="contain">
-                  <Text style={styles.submitBtnText}>{submitting ? '提交中...' : '确定发货'}</Text>
+              <TouchableOpacity
+                style={styles.submitBtn}
+                onPress={handleSubmit}
+                disabled={submitting}
+              >
+                <ImageBackground
+                  source={{ uri: Images.common.loginBtn }}
+                  style={styles.submitBtnBg}
+                  resizeMode="contain"
+                >
+                  <Text style={styles.submitBtnText}>
+                    {submitting ? "提交中..." : "确定发货"}
+                  </Text>
                 </ImageBackground>
               </TouchableOpacity>
             </>
           )}
         </View>
       </View>
+
+      {/* 支付结果轮询确认加载中 */}
+      <Modal
+        visible={verifyLoading}
+        transparent
+        animationType="fade"
+        onRequestClose={() => {}}
+      >
+        <View style={styles.verifyLoadingOverlay}>
+          <View style={styles.verifyLoadingBox}>
+            <ActivityIndicator size="large" color="#ff9600" />
+            <Text style={styles.verifyLoadingText}>正在确认支付结果...</Text>
+          </View>
+        </View>
+      </Modal>
     </Modal>
   );
 }
 
 const styles = StyleSheet.create({
-  overlay: { flex: 1, justifyContent: 'flex-end' },
-  overlayBg: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.5)' },
-  container: { backgroundColor: '#fff', borderTopLeftRadius: 15, borderTopRightRadius: 15, paddingHorizontal: 14 },
-  header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 20, position: 'relative' },
-  title: { fontSize: 16, fontWeight: 'bold', color: '#000' },
-  closeBtn: { position: 'absolute', right: 0, top: 15, width: 24, height: 24, backgroundColor: '#ebebeb', borderRadius: 12, justifyContent: 'center', alignItems: 'center' },
-  closeBtnText: { fontSize: 18, color: '#a2a2a2', lineHeight: 20 },
-  loadingBox: { height: 200, justifyContent: 'center', alignItems: 'center' },
+  verifyLoadingOverlay: {
+    ...StyleSheet.absoluteFillObject,
+    backgroundColor: "rgba(0,0,0,0.4)",
+    justifyContent: "center",
+    alignItems: "center",
+    zIndex: 9999,
+  },
+  verifyLoadingBox: {
+    backgroundColor: "rgba(0,0,0,0.8)",
+    padding: 20,
+    borderRadius: 12,
+    alignItems: "center",
+  },
+  verifyLoadingText: {
+    color: "#fff",
+    marginTop: 10,
+    fontSize: 14,
+  },
+  overlay: { flex: 1, justifyContent: "flex-end" },
+  overlayBg: {
+    position: "absolute",
+    top: 0,
+    left: 0,
+    right: 0,
+    bottom: 0,
+    backgroundColor: "rgba(0,0,0,0.5)",
+  },
+  container: {
+    backgroundColor: "#fff",
+    borderTopLeftRadius: 15,
+    borderTopRightRadius: 15,
+    paddingHorizontal: 14,
+  },
+  header: {
+    flexDirection: "row",
+    alignItems: "center",
+    justifyContent: "center",
+    paddingVertical: 20,
+    position: "relative",
+  },
+  title: { fontSize: 16, fontWeight: "bold", color: "#000" },
+  closeBtn: {
+    position: "absolute",
+    right: 0,
+    top: 15,
+    width: 24,
+    height: 24,
+    backgroundColor: "#ebebeb",
+    borderRadius: 12,
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  closeBtnText: { fontSize: 18, color: "#a2a2a2", lineHeight: 20 },
+  loadingBox: { height: 200, justifyContent: "center", alignItems: "center" },
   goodsSection: { paddingVertical: 10 },
-  goodsItem: { width: 79, height: 103, backgroundColor: '#fff', borderRadius: 6, marginRight: 8, alignItems: 'center', justifyContent: 'center', position: 'relative', borderWidth: 1, borderColor: '#eaeaea' },
+  goodsItem: {
+    width: 79,
+    height: 103,
+    backgroundColor: "#fff",
+    borderRadius: 6,
+    marginRight: 8,
+    alignItems: "center",
+    justifyContent: "center",
+    position: "relative",
+    borderWidth: 1,
+    borderColor: "#eaeaea",
+  },
   goodsImg: { width: 73, height: 85 },
-  goodsCount: { position: 'absolute', top: 0, right: 0, backgroundColor: '#ff6b00', borderRadius: 2, paddingHorizontal: 4, paddingVertical: 2 },
-  goodsCountText: { color: '#fff', fontSize: 10, fontWeight: 'bold' },
-  feeRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 8 },
-  feeLabel: { fontSize: 14, color: '#333' },
-  feeValue: { fontSize: 14, color: '#ff6b00', fontWeight: 'bold' },
-  addressSection: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10, borderTopWidth: 1, borderTopColor: '#f0f0f0' },
-  noAddress: { flex: 1, fontSize: 16, fontWeight: 'bold', color: '#333' },
+  goodsCount: {
+    position: "absolute",
+    top: 0,
+    right: 0,
+    backgroundColor: "#ff6b00",
+    borderRadius: 2,
+    paddingHorizontal: 4,
+    paddingVertical: 2,
+  },
+  goodsCountText: { color: "#fff", fontSize: 10, fontWeight: "bold" },
+  feeRow: {
+    flexDirection: "row",
+    justifyContent: "space-between",
+    alignItems: "center",
+    paddingVertical: 8,
+  },
+  feeLabel: { fontSize: 14, color: "#333" },
+  feeValue: { fontSize: 14, color: "#ff6b00", fontWeight: "bold" },
+  addressSection: {
+    flexDirection: "row",
+    alignItems: "center",
+    paddingVertical: 10,
+    borderTopWidth: 1,
+    borderTopColor: "#f0f0f0",
+  },
+  noAddress: { flex: 1, fontSize: 16, fontWeight: "bold", color: "#333" },
   addressInfo: { flex: 1 },
-  addressName: { fontSize: 14, color: '#333', fontWeight: 'bold' },
-  addressDetail: { fontSize: 12, color: '#666', marginTop: 4 },
-  arrowIcon: { fontSize: 20, color: '#999', marginLeft: 10 },
-  submitBtn: { alignItems: 'center', marginTop: 15 },
-  submitBtnBg: { width: 260, height: 60, justifyContent: 'center', alignItems: 'center' },
-  submitBtnText: { color: '#000', fontSize: 16, fontWeight: 'bold' },
+  addressName: { fontSize: 14, color: "#333", fontWeight: "bold" },
+  addressDetail: { fontSize: 12, color: "#666", marginTop: 4 },
+  arrowIcon: { fontSize: 20, color: "#999", marginLeft: 10 },
+  submitBtn: { alignItems: "center", marginTop: 15 },
+  submitBtnBg: {
+    width: 260,
+    height: 60,
+    justifyContent: "center",
+    alignItems: "center",
+  },
+  submitBtnText: { color: "#000", fontSize: 16, fontWeight: "bold" },
 });

+ 2 - 15
app/safepay.tsx

@@ -1,22 +1,9 @@
-import { useRouter } from "expo-router";
-import { useEffect } from "react";
-
 /**
  * Alipay Callback Handler
  * Handles the "asios://safepay" deep link from Alipay.
- * 仅仅作为 deep link 的接收器,没有 UI,瞬间返回上一页。
+ * 纯占位页面,仅防止 Expo Router 报 Unmatched Route 错误。
+ * 不做任何导航操作,支付结果由 CheckoutModal 中的 AppState 轮询获取。
  */
 export default function AlipayResult() {
-  const router = useRouter();
-
-  useEffect(() => {
-    // 只要进入这个页面,直接默默退回上一页,不显示任何多余 UI。
-    if (router.canGoBack()) {
-      router.back();
-    } else {
-      router.replace("/");
-    }
-  }, []);
-
   return null;
 }

+ 38 - 0
app/store/components/CheckoutModal.tsx

@@ -52,6 +52,7 @@ export default function CheckoutModal({
   const router = useRouter();
   const insets = useSafeAreaInsets();
   const [loading, setLoading] = useState(false);
+  const [verifyLoading, setVerifyLoading] = useState(false);
   const [submitting, setSubmitting] = useState(false);
   const [address, setAddress] = useState<Address | null>(null);
   const [expressAmount, setExpressAmount] = useState(0);
@@ -162,6 +163,7 @@ export default function CheckoutModal({
                     // but if they returned to the app we can just assume success and
                     // refresh the page to let the backend state reflect.
                     isResolved = true;
+                    setVerifyLoading(false);
                     showAlert("支付完成正在核实");
                     onSuccess();
                   }
@@ -173,6 +175,7 @@ export default function CheckoutModal({
           const result = await Alipay.pay(res.payInfo);
           if (isResolved) return;
           isResolved = true;
+          setVerifyLoading(false);
           console.log("Alipay Result:", result);
 
           if (result?.resultStatus === "9000") {
@@ -185,9 +188,11 @@ export default function CheckoutModal({
           }
         } catch (e: any) {
           isResolved = true;
+          setVerifyLoading(false);
           console.error("Alipay Error:", e);
           showAlert("调用支付宝失败");
         } finally {
+          setVerifyLoading(false);
           if (appStateSub) {
             appStateSub.remove();
           }
@@ -304,11 +309,44 @@ export default function CheckoutModal({
           )}
         </View>
       </View>
+
+      {/* 支付结果轮询确认加载中 */}
+      <Modal
+        visible={verifyLoading}
+        transparent
+        animationType="fade"
+        onRequestClose={() => {}}
+      >
+        <View style={styles.verifyLoadingOverlay}>
+          <View style={styles.verifyLoadingBox}>
+            <ActivityIndicator size="large" color="#ff9600" />
+            <Text style={styles.verifyLoadingText}>正在确认支付结果...</Text>
+          </View>
+        </View>
+      </Modal>
     </Modal>
   );
 }
 
 const styles = StyleSheet.create({
+  verifyLoadingOverlay: {
+    ...StyleSheet.absoluteFillObject,
+    backgroundColor: "rgba(0,0,0,0.4)",
+    justifyContent: "center",
+    alignItems: "center",
+    zIndex: 9999,
+  },
+  verifyLoadingBox: {
+    backgroundColor: "rgba(0,0,0,0.8)",
+    padding: 20,
+    borderRadius: 12,
+    alignItems: "center",
+  },
+  verifyLoadingText: {
+    color: "#fff",
+    marginTop: 10,
+    fontSize: 14,
+  },
   overlay: { flex: 1, justifyContent: "flex-end" },
   overlayBg: {
     position: "absolute",

+ 62 - 13
app/treasure-hunt/components/CheckoutModal.tsx

@@ -260,24 +260,21 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
       }
     };
 
+    const [verifyLoading, setVerifyLoading] = useState(false);
     const isNavigatingRef = useRef(false);
 
     const handleSuccess = (tradeNo: string) => {
+      setVerifyLoading(false);
       setVisible(false);
 
       if (isNavigatingRef.current) return;
       isNavigatingRef.current = true;
 
-      router.push({
-        pathname: "/happy-spin" as any,
+      router.replace({
+        pathname: "/treasure-hunt/happy-spin" as any,
         params: { tradeNo, num, poolId },
       });
       onSuccess({ tradeNo, num });
-
-      // Reset after a short delay to allow subsequent purchases
-      setTimeout(() => {
-        isNavigatingRef.current = false;
-      }, 2000);
     };
 
     const handleNativePay = async (
@@ -288,6 +285,7 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
       if (type === "ALIPAY" || type.includes("ALIPAY")) {
         let appStateSub: any = null;
         let isResolved = false;
+        let pollingStarted = false;
 
         try {
           Alipay.setAlipayScheme("alipay2021005175632205");
@@ -296,8 +294,11 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
           appStateSub = AppState.addEventListener(
             "change",
             async (nextAppState) => {
-              if (nextAppState === "active" && !isResolved) {
-                // 瞬间回退没有拿到原生支付结果时,立即开始每秒轮询后端
+              if (nextAppState === "active" && !isResolved && !pollingStarted) {
+                pollingStarted = true;
+                const loadingTimer = setTimeout(() => {
+                  if (!isResolved) setVerifyLoading(true);
+                }, 800);
                 let attempts = 0;
                 const pollInterval = setInterval(async () => {
                   if (isResolved) {
@@ -307,22 +308,30 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
                   attempts++;
                   try {
                     const res = await getApplyResult(tradeNo);
+                    // 关键修复:await 返回后再次检查 isResolved,防止竞态
+                    if (isResolved) {
+                      clearInterval(pollInterval);
+                      return;
+                    }
                     if (
                       res?.paySuccess ||
                       (res?.inventoryList && res.inventoryList.length > 0)
                     ) {
                       isResolved = true;
                       clearInterval(pollInterval);
+                      clearTimeout(loadingTimer);
+                      setVerifyLoading(false);
                       handleSuccess(tradeNo);
                     }
                   } catch (e) {
                     console.log("Fallback poll failed", e);
                   }
-                  if (attempts >= 5) {
-                    // 5秒后放弃
+                  if (attempts >= 15) {
                     clearInterval(pollInterval);
+                    clearTimeout(loadingTimer);
+                    setVerifyLoading(false);
                   }
-                }, 1000);
+                }, 300);
               }
             },
           );
@@ -330,6 +339,7 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
           const result = await Alipay.pay(payInfo);
           if (isResolved) return; // if fallback already worked
           isResolved = true;
+          setVerifyLoading(false);
           console.log("Alipay Result:", result);
 
           const status = result?.resultStatus;
@@ -340,6 +350,7 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
           } else {
             // Also poll server just in case the status is wrong but it succeeded
             try {
+              setVerifyLoading(true);
               const res = await getApplyResult(tradeNo);
               if (
                 res?.paySuccess ||
@@ -348,16 +359,21 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
                 handleSuccess(tradeNo);
                 return;
               }
-            } catch (ignore) {}
+            } catch (ignore) {
+            } finally {
+              setVerifyLoading(false);
+            }
 
             Alert.alert("支付中断", `状态码: ${status}`);
           }
         } catch (e: any) {
           if (isResolved) return;
           isResolved = true;
+          setVerifyLoading(false);
           console.log("Alipay Error:", e);
           Alert.alert("支付异常", e.message || "调用支付宝失败");
         } finally {
+          setVerifyLoading(false);
           if (appStateSub) {
             appStateSub.remove();
           }
@@ -714,12 +730,45 @@ export const CheckoutModal = forwardRef<CheckoutModalRef, CheckoutModalProps>(
             </View>
           </View>
         </Modal>
+
+        {/* 支付结果轮询确认加载中 */}
+        <Modal
+          visible={verifyLoading}
+          transparent
+          animationType="fade"
+          onRequestClose={() => {}}
+        >
+          <View style={styles.verifyLoadingOverlay}>
+            <View style={styles.verifyLoadingBox}>
+              <ActivityIndicator size="large" color="#ff9600" />
+              <Text style={styles.verifyLoadingText}>正在确认支付结果...</Text>
+            </View>
+          </View>
+        </Modal>
       </>
     );
   },
 );
 
 const styles = StyleSheet.create({
+  verifyLoadingOverlay: {
+    ...StyleSheet.absoluteFillObject,
+    backgroundColor: "rgba(0,0,0,0.4)",
+    justifyContent: "center",
+    alignItems: "center",
+    zIndex: 9999,
+  },
+  verifyLoadingBox: {
+    backgroundColor: "rgba(0,0,0,0.8)",
+    padding: 20,
+    borderRadius: 12,
+    alignItems: "center",
+  },
+  verifyLoadingText: {
+    color: "#fff",
+    marginTop: 10,
+    fontSize: 14,
+  },
   overlay: {
     flex: 1,
     backgroundColor: "rgba(0,0,0,0.5)",

+ 176 - 0
package-lock.json

@@ -7,6 +7,7 @@
     "": {
       "name": "asios",
       "version": "1.0.0",
+      "hasInstallScript": true,
       "dependencies": {
         "@expo/vector-icons": "^15.0.3",
         "@react-native-async-storage/async-storage": "^2.2.0",
@@ -47,6 +48,7 @@
         "@types/react": "~19.1.0",
         "eslint": "^9.25.0",
         "eslint-config-expo": "~10.0.0",
+        "patch-package": "^8.0.1",
         "typescript": "~5.9.2"
       }
     },
@@ -5176,6 +5178,13 @@
         "node": ">=10.0.0"
       }
     },
+    "node_modules/@yarnpkg/lockfile": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
+      "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
+      "dev": true,
+      "license": "BSD-2-Clause"
+    },
     "node_modules/abort-controller": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -8347,6 +8356,16 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/find-yarn-workspace-root": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
+      "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "micromatch": "^4.0.2"
+      }
+    },
     "node_modules/flat-cache": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
@@ -8414,6 +8433,21 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/fs-extra": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+      "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^6.0.1",
+        "universalify": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -9790,6 +9824,26 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/json-stable-stringify": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz",
+      "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "call-bind": "^1.0.8",
+        "call-bound": "^1.0.4",
+        "isarray": "^2.0.5",
+        "jsonify": "^0.0.1",
+        "object-keys": "^1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/json-stable-stringify-without-jsonify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
@@ -9809,6 +9863,29 @@
         "node": ">=6"
       }
     },
+    "node_modules/jsonfile": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+      "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "universalify": "^2.0.0"
+      },
+      "optionalDependencies": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "node_modules/jsonify": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
+      "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
+      "dev": true,
+      "license": "Public Domain",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/jsx-ast-utils": {
       "version": "3.3.5",
       "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -9835,6 +9912,16 @@
         "json-buffer": "3.0.1"
       }
     },
+    "node_modules/klaw-sync": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
+      "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "graceful-fs": "^4.1.11"
+      }
+    },
     "node_modules/kleur": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@@ -11618,6 +11705,75 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/patch-package": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.1.tgz",
+      "integrity": "sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@yarnpkg/lockfile": "^1.1.0",
+        "chalk": "^4.1.2",
+        "ci-info": "^3.7.0",
+        "cross-spawn": "^7.0.3",
+        "find-yarn-workspace-root": "^2.0.0",
+        "fs-extra": "^10.0.0",
+        "json-stable-stringify": "^1.0.2",
+        "klaw-sync": "^6.0.0",
+        "minimist": "^1.2.6",
+        "open": "^7.4.2",
+        "semver": "^7.5.3",
+        "slash": "^2.0.0",
+        "tmp": "^0.2.4",
+        "yaml": "^2.2.2"
+      },
+      "bin": {
+        "patch-package": "index.js"
+      },
+      "engines": {
+        "node": ">=14",
+        "npm": ">5"
+      }
+    },
+    "node_modules/patch-package/node_modules/ci-info": {
+      "version": "3.9.0",
+      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+      "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/sibiraj-s"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/patch-package/node_modules/semver": {
+      "version": "7.7.4",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+      "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/patch-package/node_modules/slash": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+      "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/path-exists": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -13695,6 +13851,16 @@
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
+    "node_modules/tmp": {
+      "version": "0.2.5",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
+      "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.14"
+      }
+    },
     "node_modules/tmpl": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -14014,6 +14180,16 @@
         "node": ">=8"
       }
     },
+    "node_modules/universalify": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+      "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 10.0.0"
+      }
+    },
     "node_modules/unpipe": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

+ 3 - 1
package.json

@@ -8,7 +8,8 @@
     "android": "expo run:android",
     "ios": "expo run:ios",
     "web": "expo start --web",
-    "lint": "expo lint"
+    "lint": "expo lint",
+    "postinstall": "patch-package"
   },
   "dependencies": {
     "@expo/vector-icons": "^15.0.3",
@@ -50,6 +51,7 @@
     "@types/react": "~19.1.0",
     "eslint": "^9.25.0",
     "eslint-config-expo": "~10.0.0",
+    "patch-package": "^8.0.1",
     "typescript": "~5.9.2"
   },
   "private": true

+ 180 - 0
patches/expo-native-alipay+0.1.1.patch

@@ -0,0 +1,180 @@
+diff --git a/node_modules/expo-native-alipay/ios/ExpoNativeAlipayDelegate.swift b/node_modules/expo-native-alipay/ios/ExpoNativeAlipayDelegate.swift
+index d5c3506..331addd 100644
+--- a/node_modules/expo-native-alipay/ios/ExpoNativeAlipayDelegate.swift
++++ b/node_modules/expo-native-alipay/ios/ExpoNativeAlipayDelegate.swift
+@@ -9,34 +9,17 @@ import ExpoModulesCore
+ import AlipaySDK
+ 
+ public class ExpoNativeAlipayDelegate: ExpoAppDelegateSubscriber {
+-    // 适配 iOS 9 及以上
+-    public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:], appContext: AppContext?) -> Bool {
++    // 适配标准 UIApplicationDelegate (3 参数签名,匹配 ExpoAppDelegateSubscriberManager 的调用)
++    public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
+         if url.host == "safepay" {
+             // 支付跳转支付宝钱包进行支付,处理支付结果
+             AlipaySDK.defaultService()?.processOrder(withPaymentResult: url, standbyCallback: { resultDic in
+-                self.sendAlipayResult(resultDic, appContext: appContext)
++                self.sendAlipayResult(resultDic)
+             })
+ 
+             // 授权跳转支付宝钱包进行支付,处理支付结果
+             AlipaySDK.defaultService()?.processAuth_V2Result(url, standbyCallback: { resultDic in
+-                self.sendAlipayResult(resultDic, appContext: appContext)
+-            })
+-            return true
+-        }
+-        return false
+-    }
+-
+-    // 兼容 iOS 8 及以下(如需支持)
+-    public func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any, appContext: AppContext?) -> Bool {
+-        if url.host == "safepay" {
+-            // 支付跳转支付宝钱包进行支付,处理支付结果
+-            AlipaySDK.defaultService()?.processOrder(withPaymentResult: url, standbyCallback: { resultDic in
+-                self.sendAlipayResult(resultDic, appContext: appContext)
+-            })
+-            
+-            // 授权跳转支付宝钱包进行支付,处理支付结果
+-            AlipaySDK.defaultService()?.processAuth_V2Result(url, standbyCallback: { resultDic in
+-                self.sendAlipayResult(resultDic, appContext: appContext)
++                self.sendAlipayResult(resultDic)
+             })
+             return true
+         }
+@@ -44,36 +27,26 @@ public class ExpoNativeAlipayDelegate: ExpoAppDelegateSubscriber {
+     }
+ 
+     // 处理 Universal Link 回调
+-    public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void, appContext: AppContext?) -> Bool {
++    public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
+         if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
+             AlipaySDK.defaultService()?.handleOpenUniversalLink(userActivity, standbyCallback: { resultDic in
+-                self.sendAlipayResult(resultDic, appContext: appContext)
++                self.sendAlipayResult(resultDic)
+             })
+         }
+         return true
+     }
+ 
+-    private func sendAlipayResult(_ resultDic: [AnyHashable: Any]?, appContext: AppContext?) {
+-        var module: ExpoNativeAlipayModule? = nil
+-
+-        if let appContext = appContext {
+-            // 新版优先
+-            if let moduleRegistry = appContext.value(forKey: "moduleRegistry") as? NSObject,
+-               let m = moduleRegistry.perform(NSSelectorFromString("getModuleImplementingProtocol:"), with: ExpoNativeAlipayModule.self)?.takeUnretainedValue() as? ExpoNativeAlipayModule {
+-                module = m
+-            } else if let m = appContext.perform(NSSelectorFromString("getModule:"), with: ExpoNativeAlipayModule.self)?.takeUnretainedValue() as? ExpoNativeAlipayModule {
+-                // 旧版
+-                module = m
+-            }
++    private func sendAlipayResult(_ resultDic: [AnyHashable: Any]?) {
++        guard let module = ExpoNativeAlipayModule.shared else {
++            print("[ExpoNativeAlipay] WARNING: Module.shared is nil, cannot resolve pay promise!")
++            return
+         }
+ 
+-        if let module = module {
+-            // 判断是支付还是授权,根据 resultDic 内容区分
+-            if let result = resultDic?["result"] as? String, result.contains("auth_code=") {
+-                module.handleAlipayAuthResult(resultDic ?? [:])
+-            } else {
+-                module.handleAlipayResult(resultDic ?? [:])
+-            }
++        // 判断是支付还是授权,根据 resultDic 内容区分
++        if let result = resultDic?["result"] as? String, result.contains("auth_code=") {
++            module.handleAlipayAuthResult(resultDic ?? [:])
++        } else {
++            module.handleAlipayResult(resultDic ?? [:])
+         }
+     }
+ }
+\ No newline at end of file
+diff --git a/node_modules/expo-native-alipay/ios/ExpoNativeAlipayModule.swift b/node_modules/expo-native-alipay/ios/ExpoNativeAlipayModule.swift
+index 2d925ec..a72ae4e 100644
+--- a/node_modules/expo-native-alipay/ios/ExpoNativeAlipayModule.swift
++++ b/node_modules/expo-native-alipay/ios/ExpoNativeAlipayModule.swift
+@@ -2,6 +2,7 @@ import ExpoModulesCore
+ import AlipaySDK
+ 
+ public class ExpoNativeAlipayModule: Module {
++  public static var shared: ExpoNativeAlipayModule?
+   private var alipayScheme: String?
+   private var payPromise: Promise?
+   private var authPromise: Promise?
+@@ -10,6 +11,11 @@ public class ExpoNativeAlipayModule: Module {
+ 
+   public func definition() -> ModuleDefinition {
+     Name("ExpoNativeAlipay")
++    
++    OnCreate {
++      ExpoNativeAlipayModule.shared = self
++    }
++    
+     /**
+     * 注册接口
+     *
+@@ -46,6 +52,17 @@ public class ExpoNativeAlipayModule: Module {
+       let version = AlipaySDK.defaultService()?.currentVersion() ?? ""
+       promise.resolve(version)
+     }
++
++    AsyncFunction("processOrder") { (urlStr: String, promise: Promise) in
++      if let url = URL(string: urlStr) {
++        AlipaySDK.defaultService()?.processOrder(withPaymentResult: url, standbyCallback: { resultDic in
++            self.handleAlipayResult(resultDic ?? [:])
++            promise.resolve(resultDic)
++        })
++      } else {
++        promise.resolve(nil)
++      }
++    }
+   }
+ 
+   // 处理支付结果
+diff --git a/node_modules/expo-native-alipay/src/ExpoNativeAlipay.types.ts b/node_modules/expo-native-alipay/src/ExpoNativeAlipay.types.ts
+index 0969717..310aeec 100644
+--- a/node_modules/expo-native-alipay/src/ExpoNativeAlipay.types.ts
++++ b/node_modules/expo-native-alipay/src/ExpoNativeAlipay.types.ts
+@@ -48,6 +48,11 @@ export interface OrderResultStr {
+   sign_type: 'RSA2' | 'RSA';
+ }
+ 
++/**
++ * 处理外部跳转回来的支付宝 URL (主要是为了给拦截了 URL 的框架手动处理用)
++ */
++export declare function processOrder(url: string): Promise<OrderResult>;
++
+ /**
+  * 支付订单返回结果
+  * @returns 成功返回
+diff --git a/node_modules/expo-native-alipay/src/ExpoNativeAlipayModule.ts b/node_modules/expo-native-alipay/src/ExpoNativeAlipayModule.ts
+index 2f22a8f..03dead7 100644
+--- a/node_modules/expo-native-alipay/src/ExpoNativeAlipayModule.ts
++++ b/node_modules/expo-native-alipay/src/ExpoNativeAlipayModule.ts
+@@ -60,6 +60,14 @@ export function getVersion(): Promise<string> {
+     return ExpoNativeAlipay.getVersion()
+ }
+ 
++/**
++ * 手动处理外部唤起返回的支付 URL
++ * @param url App 唤起的 URL
++ */
++export function processOrder(url: string): Promise<OrderResult> {
++    return ExpoNativeAlipay.processOrder(url)
++}
++
+ /**
+  * 设置支付宝跳转Scheme,仅 iOS
+  * @param scheme
+diff --git a/node_modules/expo-native-alipay/src/index.ts b/node_modules/expo-native-alipay/src/index.ts
+index 1a1b5d0..3e7f3cb 100644
+--- a/node_modules/expo-native-alipay/src/index.ts
++++ b/node_modules/expo-native-alipay/src/index.ts
+@@ -1,4 +1,5 @@
+ // Reexport the native module. On web, it will be resolved to ExpoNativeAlipayModule.web.ts
+ // and on native platforms to ExpoNativeAlipayModule.ts
+ export * from './ExpoNativeAlipay.types';
+-export { authInfo, default, getVersion, alipay, setAlipaySandbox, setAlipayScheme } from './ExpoNativeAlipayModule';
++export { alipay, authInfo, default, getVersion, processOrder, setAlipaySandbox, setAlipayScheme } from './ExpoNativeAlipayModule';
++