victor.zhou преди 1 месец
родител
ревизия
5e63773f59

+ 126 - 0
README.md

@@ -0,0 +1,126 @@
+https://thecodingmachine.github.io/react-native-boilerplate/docs/navigate
+
+
+
+
+
+
+api 文档: https://www.yuque.com/kolo7/ly4p08/koe2e766ehtb7gai?singleDoc# 《PAYMENT_API》
+https://www.yuque.com/kolo7/ly4p08/yps2h45o8l3cktgb?singleDoc# 《AUTH_API》
+https://www.yuque.com/kolo7/ly4p08/rz9ylvxih1ibv476?singleDoc# 《APPLE_AUTH_API》
+设计文档:https://www.yuque.com/kolo7/ly4p08/lgz7bru6f2gvrciq?singleDoc# 《统一登录充值平台》
+https://www.yuque.com/kolo7/ly4p08/uuwa89hedxi4qtds?singleDoc# 《google Pay》
+
+订阅产品api:https://www.yuque.com/kolo7/ly4p08/gz4urnur0hmpqfd5?singleDoc# 《SUBSCRIPTION_API》录充值平台》
+
+包名:com.ipeaking.recipemuse
+
+
+
+
+Google控制台
+https://console.cloud.google.com/welcome?project=lithe-land-475707-b1&authuser=1
+
+
+
+Apple控制台
+https://developer.apple.com/account
+ 
+
+apple 沙盒测试用户
+mealmuse@gmail.com
+Abc753951
+
+
+[在React Native应用中实现Android内购功能](https://juejin.cn/post/7342323580788703266?searchId=202511121454080F98B5820B69EFEA1FDB)
+
+
+### 安卓打包
+
+```bash
+cd android 
+ 
+./gradlew assembleRelease
+```
+
+
+[React Native IAP苹果支付(ApplePay)](https://www.jianshu.com/p/e25d3b1a316e)
+[在React Native应用中实现Android内购功能](https://juejin.cn/post/7342323580788703266)
+
+
+  
+## IOS订阅的流程
+
+1. 初始化 IAP
+2. 获取订阅产品信息
+3. 发起订阅购买
+4. 监听购买事件
+5. 恢复购买
+
+
+用户点击订阅
+    ↓
+调用 requestSubscription()
+    ↓
+Apple 显示支付界面
+    ↓
+用户确认支付(Face ID/密码)
+    ↓
+purchaseUpdatedListener 触发
+    ↓
+获取 transactionReceipt
+    ↓
+发送收据到后端验证
+    ↓
+后端调用 Apple 验证接口
+    ↓
+Apple 返回验证结果
+    ↓
+后端更新数据库用户订阅状态
+    ↓
+返回验证结果到客户端
+    ↓
+客户端调用 finishTransaction()
+    ↓
+更新 UI 显示会员状态
+
+
+## 最佳实践(推荐的架构)
+📍步骤 1:前端调用后台
+
+获取后台定义的商品 SKU 列表(业务控制层)
+
+GET /subscription/products
+
+
+后台返回:
+
+[
+  {
+    "productId": "com.xxx.vip.month",
+    "displayName": "月会员",
+    "sort": 1
+  },
+  {
+    "productId": "com.xxx.vip.year",
+    "displayName": "年会员",
+    "sort": 2
+  }
+]
+
+📍步骤 2:前端把 productId 列表丢给 react-native-iap
+const ids = productsFromBackend.map(item => item.productId)
+const storeProducts = await RNIap.getSubscriptions(ids)
+
+📍步骤 3:前端把“商店信息 + 后台业务信息”合并
+const mergedProducts = backendList.map(item => ({
+  ...item,
+  ...storeProducts.find(p => p.productId === item.productId)
+}));
+
+
+最终得到:
+
+商店价格与周期(来自 IAP)
+
+商品业务含义、排序、描述(来自后台)

+ 1 - 0
android/app/src/main/AndroidManifest.xml

@@ -1,6 +1,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
 
   <uses-permission android:name="android.permission.INTERNET" />
+  <uses-permission android:name="com.android.vending.BILLING" />
 
   <application
       android:name=".MainApplication"

+ 84 - 11
ios/Podfile.lock

@@ -36,6 +36,69 @@ PODS:
   - hermes-engine (0.80.2):
     - hermes-engine/Pre-built (= 0.80.2)
   - hermes-engine/Pre-built (0.80.2)
+  - NitroIap (14.4.39):
+    - boost
+    - DoubleConversion
+    - fast_float
+    - fmt
+    - glog
+    - hermes-engine
+    - NitroModules
+    - openiap (~> 1.2.33)
+    - RCT-Folly
+    - RCT-Folly/Fabric
+    - RCTRequired
+    - RCTTypeSafety
+    - React-callinvoker
+    - React-Core
+    - React-debug
+    - React-Fabric
+    - React-featureflags
+    - React-graphics
+    - React-hermes
+    - React-ImageManager
+    - React-jsi
+    - React-NativeModulesApple
+    - React-RCTFabric
+    - React-renderercss
+    - React-rendererdebug
+    - React-utils
+    - ReactCodegen
+    - ReactCommon/turbomodule/bridging
+    - ReactCommon/turbomodule/core
+    - SocketRocket
+    - Yoga
+  - NitroModules (0.31.5):
+    - boost
+    - DoubleConversion
+    - fast_float
+    - fmt
+    - glog
+    - hermes-engine
+    - RCT-Folly
+    - RCT-Folly/Fabric
+    - RCTRequired
+    - RCTTypeSafety
+    - React-callinvoker
+    - React-Core
+    - React-debug
+    - React-Fabric
+    - React-featureflags
+    - React-graphics
+    - React-hermes
+    - React-ImageManager
+    - React-jsi
+    - React-NativeModulesApple
+    - React-RCTFabric
+    - React-renderercss
+    - React-rendererdebug
+    - React-utils
+    - ReactCodegen
+    - ReactCommon/turbomodule/bridging
+    - ReactCommon/turbomodule/core
+    - SocketRocket
+    - Yoga
+  - openiap (1.2.39)
   - PromisesObjC (2.4.0)
   - RCT-Folly (2024.11.18.00):
     - boost
@@ -2299,7 +2362,7 @@ PODS:
     - React-perflogger (= 0.80.2)
     - React-utils (= 0.80.2)
     - SocketRocket
-  - RNAppleAuthentication (2.4.1):
+  - RNAppleAuthentication (2.5.0):
     - React-Core
   - RNCMaskedView (0.3.2):
     - boost
@@ -2703,6 +2766,8 @@ DEPENDENCIES:
   - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`)
   - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
   - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
+  - NitroIap (from `../node_modules/react-native-iap`)
+  - NitroModules (from `../node_modules/react-native-nitro-modules`)
   - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
   - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
   - RCTRequired (from `../node_modules/react-native/Libraries/Required`)
@@ -2791,6 +2856,7 @@ SPEC REPOS:
     - GoogleUtilities
     - GTMAppAuth
     - GTMSessionFetcher
+    - openiap
     - PromisesObjC
     - SocketRocket
 
@@ -2810,6 +2876,10 @@ EXTERNAL SOURCES:
   hermes-engine:
     :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
     :tag: hermes-2025-07-24-RNv0.80.2-5c7dbc0a78cb2d2a8bc81c41c617c3abecf209ff
+  NitroIap:
+    :path: "../node_modules/react-native-iap"
+  NitroModules:
+    :path: "../node_modules/react-native-nitro-modules"
   RCT-Folly:
     :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
   RCTDeprecation:
@@ -2979,6 +3049,9 @@ SPEC CHECKSUMS:
   GTMAppAuth: 217a876b249c3c585a54fd6f73e6b58c4f5c4238
   GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
   hermes-engine: bbc1152da7d2d40f9e59c28acc6576fcf5d28e2a
+  NitroIap: 2bbff78fbe5a6698c9cfa240e05c2e9f4d510580
+  NitroModules: dd8ff7952e241d99278d10e4ea9ca07ae106edd4
+  openiap: d374c792df4c6047c8f846394b9c00815d64f327
   PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
   RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
   RCTDeprecation: 300c5eb91114d4339b0bb39505d0f4824d7299b7
@@ -3013,9 +3086,9 @@ SPEC CHECKSUMS:
   React-logger: 7cfc7b1ae1f8e5fe5097f9c746137cc3a8fad4ce
   React-Mapbuffer: 7018c5b7da5b13ed22fe55dae51d50187a00b2d7
   React-microtasksnativemodule: 8ff9cb220a8efa625b5885996bd69e69db9edf02
-  react-native-mmkv: a079b4492bf9cb13e3fa6535556cb72085d13e89
-  react-native-safe-area-context: b6714cf77746147d1fef3f89892e4f82e35a4ef6
-  react-native-webview: a5d11a99159d88fb9f7ab45ad57b549e232218d5
+  react-native-mmkv: b189248d0ad153f44ef126270c201a1828a056fd
+  react-native-safe-area-context: 01f6d357d42395422eed7c3bfe715614728f2989
+  react-native-webview: 38b7d49cbe748b6fa02381be0a097d3922caf94a
   React-NativeModulesApple: 37c08c3c54db55854de816b0df0f3683832be35a
   React-oscompat: 56d6de59f9ae95cd006a1c40be2cde83bc06a4e1
   React-perflogger: 4008bd05a8b6c157b06608c0ea0b8bd5d9c5e6c9
@@ -3045,17 +3118,17 @@ SPEC CHECKSUMS:
   React-timing: 4f97958cc918f0af9444f93e4a7083415e6f5daf
   React-utils: f491e2726eb8ced8af13893e1f77317f0fa9a954
   ReactAppDependencyProvider: 8df342c127fd0c1e30e8b9f71ff814c22414a7c0
-  ReactCodegen: 439c427ccc115d71d16cc84256e5fbdc7fcef57a
+  ReactCodegen: ed311d9dd602544dbe0b8053ddfa9c5d5b7aaba3
   ReactCommon: 592ef441605638b95e533653259254b4bd35ff4f
-  RNAppleAuthentication: c3dddf5918126c9aae85dc2e2ce9fb87835e9e04
-  RNCMaskedView: a8ae6be2c3f50e525e9354b21d62aa55317eca1a
-  RNDeviceInfo: 0682ce0060b1e2ca0b6618d934fb51466d175a1a
+  RNAppleAuthentication: 9027af8aa92b4719ef1b6030a8e954d37079473a
+  RNCMaskedView: 7e0ce15656772a939ff0d269100bca3a182163c8
+  RNDeviceInfo: bcce8752b5043a623fe3c26789679b473f705d3c
   RNGestureHandler: b10adefe65287088d425204632d519c800a935e5
-  RNGoogleSignin: 258e9877f77405711597d77c1dc6647c29390c68
-  RNReanimated: 955f1dab987d22aeca21e6f2c1a45919c51b0b6f
+  RNGoogleSignin: efc10ce8306b6394077598f2a615d5993bd3cee0
+  RNReanimated: b3d970249c5cc79c9623a3d63e4eb5840522bda9
   RNScreens: c63849403489bd068ea160f276fbc8416f19f2f7
   RNSVG: cb3156ab4865f6a8bc145fb431a1003f02df2ff7
-  RNWorklets: 87c6ebbd068acaed7c6d7ca78576df9e67587352
+  RNWorklets: 5cd06f372d75bbbecdee9adc93aa60693b33a122
   SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
   Yoga: a742cc68e8366fcfc681808162492bc0aa7a9498
 

+ 0 - 8
ios/recipemuse.xcodeproj/project.pbxproj

@@ -249,14 +249,10 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-resources-${CONFIGURATION}-input-files.xcfilelist",
 			);
-			inputPaths = (
-			);
 			name = "[CP] Copy Pods Resources";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-resources-${CONFIGURATION}-output-files.xcfilelist",
 			);
-			outputPaths = (
-			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-resources.sh\"\n";
@@ -270,14 +266,10 @@
 			inputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 			);
-			inputPaths = (
-			);
 			name = "[CP] Embed Pods Frameworks";
 			outputFileListPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 			);
-			outputPaths = (
-			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-recipemuse/Pods-recipemuse-frameworks.sh\"\n";

+ 114 - 370
src/App.tsx

@@ -1,6 +1,6 @@
 import 'react-native-gesture-handler';
 
-import React, { createContext, useContext, useRef, useState, useEffect } from 'react';
+import React, { createContext, useContext, useRef, useState, useEffect, useCallback } from 'react';
 import { Platform, StyleSheet, View, Alert } from 'react-native';
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 import { GestureHandlerRootView } from 'react-native-gesture-handler';
@@ -17,16 +17,13 @@ import ApplicationNavigator from '@/navigation/Application';
 import { ThemeProvider } from '@/theme';
 import '@/translations';
 
-import * as RNIap from 'react-native-iap';
-import type { Purchase, PurchaseError } from 'react-native-iap';
+
 import { eventTypeEnum } from './utils/h5';
+import useSubscription from './hooks/useSub';
 
-const itemSubs = ['com.recipemuse.vip.monthly']; // 月度会员订阅ID
 
+import { useRequest, setToken } from '@/hooks';
 
-// 订阅相关消息常量
-const SUBSCRIPTION_REQUEST = 'NATIVE_SUBSCRIPTION_REQUEST';
-const RESTORE_PURCHASE_REQUEST = 'NATIVE_RESTORE_PURCHASE_REQUEST';
 
 export const queryClient = new QueryClient({
   defaultOptions: {
@@ -61,7 +58,7 @@ export const useWebViewContext = () => {
 // 配置常量
 // const DEFAULT_URL = 'http://localhost:5173';
 
-const DEFAULT_URL = 'http://10.15.49.43:5173';
+const DEFAULT_URL = 'http://10.13.51.63:5173/';
 
 
 // WebView用户代理
@@ -73,16 +70,14 @@ const USER_AGENT = Platform.select({
 function App() {
   const webViewRef = useRef<WebView>(null);
   const [showWebView, setShowWebView] = useState(false);
-  const [isIAPInitialized, setIsIAPInitialized] = useState(false);
-  const [availableProducts, setAvailableProducts] = useState<any[]>([]);
-  const [isSubscribed, setIsSubscribed] = useState(false);
+
 
 
 
   /**
    * 发送消息到 WebView
    */
-  const sendMessageToWebView = (webViewRef: React.RefObject<WebView | null>, message: any) => {
+  const sendMessageToWebView = (message: any) => {
     if (webViewRef.current) {
       const messageString = typeof message === 'string' ? message : JSON.stringify(message);
       // 使用更可靠的通信方式
@@ -95,7 +90,6 @@ function App() {
    */
   const handleWebViewMessage = (
     event: any,
-    webViewRef: React.RefObject<WebView | null>,
   ) => {
     try {
       const messageData = event.nativeEvent.data;
@@ -103,25 +97,40 @@ function App() {
       console.log('Received message:', type, data);
       
       // 检查是否为登录请求
-      if (type === eventTypeEnum.NATIVE_LOGIN_REQUEST) {
+      if (type === eventTypeEnum.login2Native) {
         // 延迟执行登录以确保 WebView 已完全显示
-        handleLogin()
-      } else if (type === SUBSCRIPTION_REQUEST) {
-         // 检查是否为订阅请求
-        console.log('收到订阅请求');
-        handleInAppPurchase()
+        handleLogin();
+      } 
+      // 检查是否为订阅请求
+      else if (type === eventTypeEnum.SUBSCRIPTION_REQUEST) {
+        console.log('📱 收到订阅请求:', data);
+        
+        // 从 H5 传来的数据中获取订阅类型
+        const { productId, subscriptionType } = data || {};
+        
+        // 根据订阅类型选择产品ID
+        let targetProductId = productId;
+        if (!targetProductId && subscriptionType) {
+          targetProductId = subscriptionType === 'yearly' 
+            ? SUBSCRIPTION_PRODUCT_IDS.YEARLY 
+            : SUBSCRIPTION_PRODUCT_IDS.MONTHLY;
+        }
+        
+        handleInAppPurchase(targetProductId)
           .then(() => {
-            console.log('订阅流程已启动');
+            console.log('订阅流程已启动');
           })
           .catch((error) => {
-            console.error('启动订阅失败:', error);
-            sendMessageToWebView(webViewRef, {
+            console.error('启动订阅失败:', error);
+            sendMessageToWebView({
               type: 'SUBSCRIPTION_ERROR',
+              status: 'error',
+              message: error.message || '订阅失败',
             });
           });
       }
       // 检查是否为恢复购买请求
-      else if (type === RESTORE_PURCHASE_REQUEST) {
+      else if (type === eventTypeEnum.RESTORE_PURCHASE_REQUEST) {
         console.log('收到恢复购买请求');
         restorePurchases()
           .then(() => {
@@ -129,7 +138,7 @@ function App() {
           })
           .catch((error) => {
             console.error('恢复购买失败:', error);
-            sendMessageToWebView(webViewRef, {
+            sendMessageToWebView({
               type: 'RESTORE_PURCHASE_ERROR',
               status: 'error',
               message: error.message || '恢复购买失败'
@@ -189,7 +198,7 @@ function App() {
         if (credentialState === appleAuth.State.REVOKED && !__DEV__) {
           console.warn('⚠️ 凭证状态为REVOKED,但登录已完成');
         }
-        sendMessageToWebView(webViewRef, {
+        sendMessageToWebView({
           type: eventTypeEnum.H5_LOGIN_SUCCESS_IOS,
           message: '登录成功',
           data: appleAuthRequestResponse
@@ -223,7 +232,7 @@ function App() {
           },
         };
         Alert.alert('开发模式', '当前使用Mock登录数据(Google服务不可用)');
-        sendMessageToWebView(webViewRef, {
+        sendMessageToWebView({
           type: eventTypeEnum.H5_LOGIN_SUCCESS_ANDROID,
           status: 'success',
           message: '登录成功(Mock模式)',
@@ -242,7 +251,7 @@ function App() {
       const userInfo = await GoogleSignin.signIn();
       console.log('Google登录成功:', userInfo);
       Alert.alert('登录成功', `欢迎 ${userInfo.data?.user.name || '用户'}`);  
-      sendMessageToWebView(webViewRef, {
+      sendMessageToWebView({
         type: 'LOGIN_SUCCESS_ANDROID',
         status: 'success',
         message: '登录成功',
@@ -254,363 +263,98 @@ function App() {
     }
   };
 
-  /**
-   * 初始化IAP
-   */
-  // useEffect(() => {
-  //   // 检查Google Play Services后再初始化IAP
-  //   checkAndInitIAP();
-  //   return () => {
-  //     // 清理IAP连接
-  //     if (isIAPInitialized) {
-  //       RNIap.endConnection();
-  //     }
-  //   };
-  // }, []);
-
-  /**
-   * 检查Google服务并初始化IAP
-   */
-  const checkAndInitIAP = async () => {
-    try {
-      // Android平台检查Google Play Services
-      Alert.alert('Platform.OS ', Platform.OS);
-      if (Platform.OS === 'android') {
-        const hasPlayServices = await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: false });
-        if (!hasPlayServices) {
-          console.log('⚠️ Google Play Services不可用,IAP功能将被禁用');
-          console.log('💡 建议:使用Android模拟器或配置系统代理进行测试');
-          Alert.alert(
-            '提示',
-            'Google Play服务不可用\n\n建议使用以下方式测试:\n1. Android Studio模拟器(带Google Play)\n2. 真机配置系统代理\n3. 使用Mock模式开发',
-            [{ text: '知道了' }]
-          );
-          return;
-        }
-      }
- 
-      // 有Google服务才初始化IAP
-      await initIAP();
-    } catch (error) {
-      console.log('Google服务检查失败,跳过IAP初始化:', error);
-    }
-  };
-
-  /**
-   * 初始化应用内购买
-   */
-  const initIAP = async () => {
-    try {
-      console.log('🚀 开始初始化IAP...');
-      console.log('📱 平台:', Platform.OS);
-      
-      await RNIap.initConnection();
-      console.log('✅ IAP连接初始化成功');
-      setIsIAPInitialized(true);
-
-      // 获取可用的订阅产品
-      const products = await RNIap.fetchProducts({ skus: itemSubs, type: 'subs' });
-      console.log('可用订阅产品:', products);
-      setAvailableProducts(products || []);
-
-      // 检查现有订阅状态
-      await checkSubscriptionStatus();
-
-      // 监听购买更新
-      const purchaseUpdateSubscription = RNIap.purchaseUpdatedListener(
-        async (purchase: Purchase) => {
-          console.log('购买更新:', purchase);
-          console.log('购买状态:', purchase.purchaseState);
-          console.log('平台:', purchase.platform);
-          
-          const receipt = purchase.purchaseToken || purchase.transactionId;
-          
-          if (receipt) {
-            try {
-              // 验证收据(这里应该发送到你的后端服务器验证)
-              const isValid = await validateReceipt(receipt, purchase);
-              
-              if (!isValid) {
-                Alert.alert('订阅失败', '收据验证失败,请联系客服');
-                return;
-              }
-
-              // 完成交易 - iOS和Android处理方式不同
-              if (Platform.OS === 'ios') {
-                // iOS: 完成交易,订阅类产品不消耗
-                await RNIap.finishTransaction({ purchase, isConsumable: false });
-                console.log('iOS交易已完成');
-              } else if (Platform.OS === 'android') {
-                // Android: 需要确认购买(acknowledge)
-                if (purchase.purchaseToken) {
-                  await RNIap.acknowledgePurchaseAndroid(purchase.purchaseToken);
-                  console.log('Android购买已确认');
-                }
-              }
-              
-              // 更新订阅状态
-              setIsSubscribed(true);
-              
-              Alert.alert('订阅成功', '感谢您订阅会员服务!');
-              
-              // 发送订阅成功消息到WebView
-              sendMessageToWebView(webViewRef, {
-                type: 'SUBSCRIPTION_SUCCESS',
-                status: 'success',
-                message: '订阅成功',
-                data: {
-                  productId: purchase.productId,
-                  transactionId: purchase.transactionId,
-                  purchaseToken: purchase.purchaseToken,
-                  platform: purchase.platform,
-                  transactionDate: purchase.transactionDate,
-                }
-              });
-            } catch (error: any) {
-              console.error('验证收据失败:', error);
-              Alert.alert('订阅失败', `验证收据时出错: ${error.message || '未知错误'}`);
-            }
-          }
-        },
-      );
-
-      // 监听购买错误
-      const purchaseErrorSubscription = RNIap.purchaseErrorListener(
-        (error: PurchaseError) => {
-          console.error('购买错误:', error);
-          if (error.code !== RNIap.ErrorCode.UserCancelled) {
-            Alert.alert('购买失败', error.message || '请稍后重试');
-          }
-        },
-      );
 
-      // 保存订阅以便清理
-      return () => {
-        purchaseUpdateSubscription.remove();
-        purchaseErrorSubscription.remove();
-      };
-    } catch (error: any) {
-      debugger
-      console.error('❌ IAP初始化失败 - 详细信息:');
-      console.error('错误对象:', error);
-      console.error('错误消息:', error.message);
-      console.error('错误代码:', error.code);
-      console.error('错误栈:', error.stack);
-      
-      let errorMessage = '无法连接到应用商店';
-      
-      if (error.message) {
-        if (error.message.includes('BILLING_UNAVAILABLE')) {
-          errorMessage = 'Google Play Billing服务不可用\n\n可能原因:\n1. 模拟器未安装Google Play\n2. 未登录Google账号\n3. 国内网络限制';
-        } else if (error.message.includes('SERVICE_DISCONNECTED')) {
-          errorMessage = 'Google Play服务连接断开\n请检查网络和代理配置';
-        } else if (error.message.includes('SERVICE_UNAVAILABLE')) {
-          errorMessage = 'Google Play服务暂时不可用\n请稍后重试';
-        } else {
-          errorMessage = `连接失败: ${error.message}`;
-        }
-      }
-      
-      Alert.alert(
-        'IAP初始化失败', 
-        errorMessage + '\n\n技术信息: ' + (error.code || '未知错误'),
-        [{ text: '知道了' }]
-      );
-    }
+  // 定义订阅产品ID(根据你的App Store/Google Play配置修改)
+  const SUBSCRIPTION_PRODUCT_IDS = {
+    MONTHLY: Platform.select({
+      ios: 'com.recipemuse.premium.monthly',
+      android: 'com.recipemuse.premium.monthly',
+    }) || 'com.recipemuse.premium.monthly',
+    YEARLY: Platform.select({
+      ios: 'com.recipemuse.premium.yearly',
+      android: 'com.recipemuse.premium.yearly',
+    }) || 'com.recipemuse.premium.yearly',
   };
 
-  /**
-   * 验证收据(示例实现,实际应该发送到后端服务器)
-   */
-  const validateReceipt = async (
-    receipt: string,
-    purchase: Purchase,
-  ) => {
-    // TODO: 将收据发送到你的后端服务器进行验证
-    // 示例代码:
-    // const response = await fetch('https://your-api.com/validate-receipt', {
-    //   method: 'POST',
-    //   headers: { 'Content-Type': 'application/json' },
-    //   body: JSON.stringify({ receipt, productId: purchase.productId }),
-    // });
-    // const result = await response.json();
-    // return result.valid;
-    
-    console.log('验证收据:', receipt);
-    console.log('购买信息:', purchase);
-    return true;
-  };
-
-  /**
-   * 处理应用内购买 - 购买月度会员订阅
-   */
-  const handleInAppPurchase = async () => {
-    if (!isIAPInitialized) {
-      Alert.alert('提示', '应用商店连接未就绪,请稍后重试');
-      return;
-    }
-
-    // 检查是否已订阅
-    if (isSubscribed) {
-      Alert.alert('提示', '您已经是会员了');
-      return;
-    }
-
-    try {
-      // 构建购买请求参数
-      const purchaseRequest: any = {
-        type: 'subs',
-        request: {
-          ios: {
-            sku: itemSubs[0],
-          },
-          android: {
-            skus: [itemSubs[0]],
-          },
-        },
-      };
-
-      // Android需要提供subscriptionOffers(从产品信息中获取)
-      if (Platform.OS === 'android' && availableProducts.length > 0) {
-        const product = availableProducts.find(p => p.id === itemSubs[0]);
-        if (product?.subscriptionOfferDetailsAndroid?.[0]) {
-          const offer = product.subscriptionOfferDetailsAndroid[0];
-          purchaseRequest.request.android.subscriptionOffers = [
-            {
-              sku: itemSubs[0],
-              offerToken: offer.offerToken,
-            },
-          ];
-          console.log('Android订阅offer:', offer);
-        } else {
-          console.warn('未找到Android订阅offer,将尝试不带offer购买');
-        }
-      }
-
-      console.log('发起购买请求:', purchaseRequest);
-      
-      // 请求购买订阅
-      await RNIap.requestPurchase(purchaseRequest);
-    } catch (error: any) {
-      console.error('购买订阅失败:', error);
-      
-      // 区分不同的错误类型
-      if (error.code === RNIap.ErrorCode.UserCancelled) {
-        console.log('用户取消购买');
-      } else if (error.code === RNIap.ErrorCode.AlreadyOwned) {
-        Alert.alert('提示', '您已经拥有此订阅');
-        setIsSubscribed(true);
-      } else if (error.code === RNIap.ErrorCode.ItemUnavailable) {
-        Alert.alert('购买失败', '此商品暂时不可用,请稍后重试');
-      } else {
-        Alert.alert('购买失败', error.message || '请稍后重试');
-      }
-    }
-  };
+  // 使用订阅 hook
+  const {
+    connected,
+    products,
+    loading,
+    error,
+    purchasing,
+    handlePurchase,
+    restorePurchases,
+    getProduct,
+    clearError,
+  } = useSubscription({
+    productIds: [SUBSCRIPTION_PRODUCT_IDS.MONTHLY, SUBSCRIPTION_PRODUCT_IDS.YEARLY],
+    productType: 'subs',
+    sendMessageToWebView,
+    onPurchaseComplete: (purchase) => {
+      console.log('✅ 订阅购买成功:', purchase);
+      // 可以在这里添加额外的成功处理逻辑
+      // 例如:更新用户状态、记录分析事件等
+    },
+    onPurchaseFailed: (error) => {
+      console.error('❌ 订阅购买失败:', error);
+      // 可以在这里添加额外的失败处理逻辑
+      // 例如:记录错误日志、发送错误报告等
+    },
+  });
 
-  /**
-   * 恢复购买
-   */
-  const restorePurchases = async () => {
+  // 处理订阅请求
+  const handleInAppPurchase = useCallback(async (productId?: string) => {
     try {
-      // iOS调用系统恢复购买API
-      if (Platform.OS === 'ios') {
-        await RNIap.restorePurchases();
-        console.log('iOS恢复购买请求已发送');
-      }
+      // 如果没有传入 productId,默认使用月度订阅
+      const targetProductId = productId || SUBSCRIPTION_PRODUCT_IDS.MONTHLY;
       
-      // 获取可用购买记录
-      const availablePurchases = await RNIap.getAvailablePurchases();
-      console.log('可恢复的购买:', availablePurchases);
-
-      if (availablePurchases.length === 0) {
-        Alert.alert('提示', '没有找到可恢复的购买记录');
+      console.log('🛒 开始订阅流程:', targetProductId);
+      console.log('📦 可用产品:', products);
+      console.log('🔌 IAP连接状态:', connected);
+
+      // 检查连接状态
+      if (!connected) {
+        const errorMsg = '应用商店未连接,请稍后重试';
+        Alert.alert('错误', errorMsg);
+        sendMessageToWebView({
+          type: 'SUBSCRIPTION_ERROR',
+          status: 'error',
+          message: errorMsg,
+        });
         return;
       }
 
-      let hasSubscription = false;
-      
-      // 处理恢复的购买
-      for (const purchase of availablePurchases) {
-        if (itemSubs.includes(purchase.productId)) {
-          hasSubscription = true;
-          
-          // 验证并激活订阅
-          const receipt = purchase.purchaseToken || purchase.transactionId || '';
-          const isValid = await validateReceipt(receipt, purchase);
-          
-          if (isValid) {
-            // Android需要确认购买
-            if (Platform.OS === 'android' && purchase.purchaseToken) {
-              try {
-                await RNIap.acknowledgePurchaseAndroid(purchase.purchaseToken);
-                console.log('Android购买已确认');
-              } catch (error) {
-                console.log('购买可能已经被确认过:', error);
-              }
-            }
-            
-            setIsSubscribed(true);
-            console.log('订阅已恢复:', purchase.productId);
-          }
-        }
-      }
-
-      if (hasSubscription) {
-        Alert.alert('恢复成功', '您的订阅已恢复');
-        
-        // 发送恢复成功消息到WebView
-        sendMessageToWebView(webViewRef, {
-          type: 'SUBSCRIPTION_RESTORED',
-          status: 'success',
-          message: '订阅已恢复',
+      // 检查产品是否已加载
+      const product = getProduct(targetProductId);
+      if (!product) {
+        const errorMsg = '订阅产品不可用,请稍后重试';
+        console.warn('⚠️ 产品未找到:', targetProductId);
+        Alert.alert('错误', errorMsg);
+        sendMessageToWebView({
+          type: 'SUBSCRIPTION_ERROR',
+          status: 'error',
+          message: errorMsg,
         });
-      } else {
-        Alert.alert('提示', '没有找到会员订阅记录');
+        return;
       }
-    } catch (error: any) {
-      console.error('恢复购买失败:', error);
-      Alert.alert('恢复失败', error.message || '无法恢复购买记录');
-    }
-  };
 
-  /**
-   * 检查订阅状态
-   */
-  const checkSubscriptionStatus = async () => {
-    try {
-      const availablePurchases = await RNIap.getAvailablePurchases();
-      console.log('所有可用购买记录:', availablePurchases);
+      console.log('💰 产品信息:', product);
       
-      const activeSubscription = availablePurchases.find(
-        (purchase) => itemSubs.includes(purchase.productId),
-      );
-
-      if (activeSubscription) {
-        console.log('当前订阅状态:', activeSubscription);
-        
-        // 检查订阅是否过期(Android)
-        if (Platform.OS === 'android') {
-          // Android的自动续订状态
-          const androidPurchase = activeSubscription as any;
-          const isActive = androidPurchase.autoRenewingAndroid !== false;
-          setIsSubscribed(isActive);
-          return isActive;
-        } else {
-          // iOS的订阅状态
-          setIsSubscribed(true);
-          return true;
-        }
-      }
-      
-      setIsSubscribed(false);
-      return false;
+      // 发起购买
+      await handlePurchase(targetProductId);
     } catch (error) {
-      console.error('检查订阅状态失败:', error);
-      setIsSubscribed(false);
-      return false;
+      console.error('订阅流程错误:', error);
+      const errorMessage = error instanceof Error ? error.message : '订阅失败';
+      sendMessageToWebView({
+        type: 'SUBSCRIPTION_ERROR',
+        status: 'error',
+        message: errorMessage,
+      });
+      throw error;
     }
-  };
+  }, [connected, products, handlePurchase, getProduct, sendMessageToWebView, SUBSCRIPTION_PRODUCT_IDS]);
+  
 
   return (
     <GestureHandlerRootView style={styles.container}>
@@ -636,7 +380,7 @@ function App() {
                   startInLoadingState={true}
                   scalesPageToFit={true}
                   allowsBackForwardNavigationGestures={Platform.OS === 'ios'}
-                  onMessage={(event) => handleWebViewMessage(event, webViewRef)}
+                  onMessage={(event) => handleWebViewMessage(event)}
                   onError={(error) => console.error('WebView 错误:', error)}
                   onHttpError={(error) => console.error('WebView HTTP 错误:', error)}
                 />

+ 0 - 41
src/README.md

@@ -1,41 +0,0 @@
-https://thecodingmachine.github.io/react-native-boilerplate/docs/navigate
-
-
-
-
-
-
-api 文档: https://www.yuque.com/kolo7/ly4p08/koe2e766ehtb7gai?singleDoc# 《PAYMENT_API》
-https://www.yuque.com/kolo7/ly4p08/yps2h45o8l3cktgb?singleDoc# 《AUTH_API》
-设计文档:https://www.yuque.com/kolo7/ly4p08/lgz7bru6f2gvrciq?singleDoc# 《统一登录充值平台》
-https://www.yuque.com/kolo7/ly4p08/uuwa89hedxi4qtds?singleDoc# 《google Pay》
-
-包名:com.ipeaking.recipemuse
-
-
-
-
-Google控制台
-https://console.cloud.google.com/welcome?project=lithe-land-475707-b1&authuser=1
-
-
-
-Apple控制台
-https://developer.apple.com/account
- 
-
-apple 沙盒测试用户
-mealmuse@gmail.com
-Abc753951
-
-
-[在React Native应用中实现Android内购功能](https://juejin.cn/post/7342323580788703266?searchId=202511121454080F98B5820B69EFEA1FDB)
-
-
-### 安卓打包
-
-```bash
-cd android 
- 
-./gradlew assembleRelease
-```

+ 39 - 1
src/components/templates/SafeScreen/SafeScreen.tsx

@@ -1,7 +1,7 @@
 import type { PropsWithChildren } from 'react';
 import type { SafeAreaViewProps } from 'react-native-safe-area-context';
 
-import { StatusBar } from 'react-native';
+import { StatusBar, StyleSheet, TouchableOpacity, Text } from 'react-native';
 import { SafeAreaView } from 'react-native-safe-area-context';
 
 import { useTheme } from '@/theme';
@@ -12,6 +12,7 @@ import { ErrorBoundary } from '@/components/organisms';
 type Properties = PropsWithChildren<
   {
     readonly isError?: boolean;
+    readonly navigation?: any;
     readonly onResetError?: () => void;
   } & Omit<SafeAreaViewProps, 'mode'>
 >;
@@ -21,12 +22,21 @@ function SafeScreen({
   isError = false,
   onResetError = undefined,
   style,
+  navigation,
   ...props
 }: Properties) {
   const { layout, navigationTheme, variant } = useTheme();
 
   return (
     <SafeAreaView {...props} mode="padding" style={[layout.flex_1, style]}>
+      {navigation && (
+        <TouchableOpacity 
+          style={styles.backButton}
+          onPress={() => navigation.goBack()}
+        >
+          <Text style={styles.backText}>← Back</Text>
+        </TouchableOpacity>
+      )}
       <StatusBar
         backgroundColor={navigationTheme.colors.background}
         barStyle={variant === 'dark' ? 'light-content' : 'dark-content'}
@@ -38,4 +48,32 @@ function SafeScreen({
   );
 }
 
+const styles = StyleSheet.create({
+  backButton: {
+    position: 'absolute',
+    top: 48,
+    left: 16,
+    zIndex: 1000,
+    backgroundColor: 'transparent',
+    paddingHorizontal: 8,
+    paddingVertical: 4,
+    borderRadius: 20,
+    borderWidth: 1,
+    borderColor: 'rgba(0, 0, 0, 0.1)',
+    shadowColor: '#000',
+    shadowOffset: {
+      width: 0,
+      height: 2,
+    },
+    shadowOpacity: 0.15,
+    shadowRadius: 4,
+    elevation: 3, // Android 阴影
+  },
+  backText: {
+    fontSize: 16,
+    fontWeight: '600',
+    color: '#1F2937',
+  },
+});
+
 export default SafeScreen;

+ 91 - 0
src/hooks/IAPExample.tsx

@@ -0,0 +1,91 @@
+import React, {useEffect} from 'react';
+import {View, Text, Button, StyleSheet} from 'react-native';
+import {useIAP, ErrorCode} from 'react-native-iap';
+
+export default function SimpleStore() {
+  const {
+    connected,
+    products,
+    fetchProducts,
+    requestPurchase,
+    finishTransaction,
+  } = useIAP({
+    onPurchaseSuccess: async (purchase) => {
+      try {
+        console.log('Purchase successful:', purchase.productId);
+
+        // TODO: Verify the receipt on your backend before granting access
+        // const isValid = await verifyReceiptOnBackend(purchase);
+        // if (!isValid) {
+        //   throw new Error('Invalid receipt');
+        // }
+
+        await finishTransaction({
+          purchase,
+          isConsumable: true,
+        });
+      } catch (error) {
+        console.error('Failed to complete purchase:', error);
+      }
+    },
+    onPurchaseError: (error) => {
+      if (error.code !== ErrorCode.UserCancelled) {
+        console.error('Purchase error:', error.message);
+      }
+    },
+  });
+
+  const productIds = ['com.example.coins.pack1', 'com.example.premium'];
+
+  useEffect(() => {
+    if (connected) {
+      fetchProducts({skus: productIds, type: 'in-app'});
+    }
+  }, [connected]);
+
+  const handlePurchase = async (productId: string) => {
+    try {
+      await requestPurchase({
+        request: {
+          ios: {
+            sku: productId,
+          },
+          android: {
+            skus: [productId],
+          },
+        },
+      });
+    } catch (error) {
+      console.error('Purchase failed:', error);
+    }
+  };
+
+  return (
+    <View style={styles.container}>
+      <Text style={styles.status}>
+        Store: {connected ? 'Connected ✅' : 'Connecting...'}
+      </Text>
+
+      {products.map((product) => (
+        <View key={product.id} style={styles.product}>
+          <Text style={styles.title}>{product.title}</Text>
+          <Text style={styles.price}>{product.displayPrice}</Text>
+          <Button title="Buy Now" onPress={() => handlePurchase(product.id)} />
+        </View>
+      ))}
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: {padding: 20},
+  status: {fontSize: 16, marginBottom: 20},
+  product: {
+    padding: 15,
+    marginVertical: 5,
+    backgroundColor: '#f0f0f0',
+    borderRadius: 8,
+  },
+  title: {fontSize: 16, fontWeight: 'bold'},
+  price: {fontSize: 14, color: '#666', marginVertical: 5},
+});

+ 1 - 0
src/hooks/index.ts

@@ -1,2 +1,3 @@
 export * from './domain';
 export { useI18n } from './language/useI18n';
+export { useRequest, getToken, setToken, removeToken, http } from './useRequest';

+ 399 - 0
src/hooks/useRequest.README.md

@@ -0,0 +1,399 @@
+# useRequest Hook 使用文档
+
+React Native 项目的统一网络请求 Hook,基于 `ky` 实现,提供了完整的错误处理、Token 认证等功能。
+
+## 特性
+
+- ✅ 基于 `ky` 库,轻量且现代化
+- ✅ 自动添加 Token 认证(Bearer Token)
+- ✅ 统一的错误处理和错误提示
+- ✅ 支持 TypeScript 类型推断
+- ✅ 自动处理业务错误码(401、403、404、500等)
+- ✅ Token 过期自动清除并提示重新登录
+- ✅ 支持跳过认证和自定义错误处理
+- ✅ 完美配合 React Query 使用
+- ✅ 存储使用 MMKV(高性能)
+
+## 安装
+
+此 Hook 已集成到项目中,无需额外安装。依赖的库:
+
+```json
+{
+  "ky": "^1.8.2",
+  "react-native-mmkv": "^3.3.0"
+}
+```
+
+## 基础用法
+
+### 1. GET 请求
+
+```tsx
+import { useRequest } from '@/hooks';
+
+function UserProfile() {
+  const { get } = useRequest();
+  const [user, setUser] = useState(null);
+
+  const fetchUser = async () => {
+    try {
+      const data = await get('/api/user/profile');
+      setUser(data);
+    } catch (error) {
+      // 错误已自动处理
+    }
+  };
+
+  useEffect(() => {
+    fetchUser();
+  }, []);
+
+  return <View>{/* UI */}</View>;
+}
+```
+
+### 2. POST 请求
+
+```tsx
+import { useRequest } from '@/hooks';
+
+function UpdateProfile() {
+  const { post } = useRequest();
+
+  const updateProfile = async () => {
+    try {
+      const result = await post('/api/user/update', {
+        name: '张三',
+        email: 'zhangsan@example.com',
+      });
+      console.log('更新成功:', result);
+    } catch (error) {
+      // 错误已自动处理
+    }
+  };
+
+  return <Button onPress={updateProfile} title="更新资料" />;
+}
+```
+
+### 3. PUT 和 DELETE 请求
+
+```tsx
+import { useRequest } from '@/hooks';
+
+function UserManagement() {
+  const { put, delete: del } = useRequest();
+
+  // 更新用户
+  const updateUser = async (id: number) => {
+    await put(`/api/user/${id}`, { name: '李四' });
+  };
+
+  // 删除用户
+  const deleteUser = async (id: number) => {
+    await del(`/api/user/${id}`);
+  };
+
+  return <View>{/* UI */}</View>;
+}
+```
+
+## 高级用法
+
+### 1. 带查询参数的 GET 请求
+
+```tsx
+const { get } = useRequest();
+
+const fetchProducts = async () => {
+  const result = await get('/api/products', {
+    searchParams: {
+      page: 1,
+      pageSize: 20,
+      category: 'electronics',
+    },
+  });
+};
+```
+
+### 2. 跳过认证(登录接口)
+
+```tsx
+import { useRequest, setToken } from '@/hooks';
+
+function Login() {
+  const { post } = useRequest();
+
+  const handleLogin = async (username: string, password: string) => {
+    try {
+      // 登录接口不需要 token,使用 skipAuth
+      const result = await post<{ token: string }>(
+        '/api/auth/login',
+        { username, password },
+        { skipAuth: true }
+      );
+
+      // 保存 token
+      if (result.token) {
+        setToken(result.token);
+        // 导航到首页
+      }
+    } catch (error) {
+      // 错误处理
+    }
+  };
+
+  return <View>{/* UI */}</View>;
+}
+```
+
+### 3. 自定义错误处理
+
+```tsx
+const { get } = useRequest();
+
+const fetchData = async () => {
+  try {
+    // 跳过全局错误处理,使用自定义错误处理
+    const result = await get('/api/data', {
+      skipErrorHandler: true,
+    });
+  } catch (error) {
+    // 自定义错误处理逻辑
+    console.error('自定义错误处理:', error);
+    Alert.alert('提示', '加载失败,请重试');
+  }
+};
+```
+
+### 4. 配合 React Query 使用
+
+```tsx
+import { useQuery } from '@tanstack/react-query';
+import { useRequest } from '@/hooks';
+
+function UserList() {
+  const { get } = useRequest();
+
+  const { data, isLoading, error, refetch } = useQuery({
+    queryKey: ['users'],
+    queryFn: () => get('/api/users'),
+  });
+
+  if (isLoading) return <ActivityIndicator />;
+  if (error) return <Text>加载失败</Text>;
+
+  return (
+    <View>
+      {data.map(user => (
+        <Text key={user.id}>{user.name}</Text>
+      ))}
+      <Button title="刷新" onPress={() => refetch()} />
+    </View>
+  );
+}
+```
+
+### 5. 文件上传
+
+```tsx
+const { post } = useRequest();
+
+const uploadFile = async (file: File) => {
+  const formData = new FormData();
+  formData.append('file', file);
+
+  try {
+    const result = await post('/api/upload', formData, {
+      headers: {
+        'Content-Type': 'multipart/form-data',
+      },
+    });
+    console.log('上传成功:', result);
+  } catch (error) {
+    console.error('上传失败:', error);
+  }
+};
+```
+
+### 6. 直接使用 http 实例
+
+```tsx
+import { http } from '@/hooks';
+
+// 不使用 Hook 的场景下,可以直接使用导出的 http 实例
+export async function quickRequest() {
+  try {
+    const result = await http.get('api/quick-request').json();
+    return result;
+  } catch (error) {
+    console.error('请求失败:', error);
+  }
+}
+```
+
+## Token 管理
+
+### 获取、设置和删除 Token
+
+```tsx
+import { getToken, setToken, removeToken } from '@/hooks';
+
+// 获取当前 token
+const token = getToken();
+console.log('当前token:', token);
+
+// 设置 token(登录成功后)
+setToken('your-jwt-token-here');
+
+// 删除 token(退出登录)
+removeToken();
+```
+
+### 自动 Token 认证
+
+所有请求会自动在 Header 中添加 `Authorization: Bearer {token}`,无需手动设置。
+
+如果需要跳过认证(如登录接口),使用 `skipAuth: true` 选项:
+
+```tsx
+await post('/api/login', { username, password }, { skipAuth: true });
+```
+
+## 错误处理
+
+### 自动错误处理
+
+Hook 会自动处理以下错误:
+
+- **401**: 认证失败,自动清除 token 并提示重新登录
+- **403**: 权限不足
+- **404**: 资源不存在
+- **500**: 服务器错误
+- **网络错误**: 超时、网络连接异常等
+
+### 业务错误码处理
+
+后端返回的业务错误码也会自动处理:
+
+```json
+{
+  "code": 401,
+  "msg": "token已过期",
+  "data": null
+}
+```
+
+### 自定义错误处理
+
+使用 `skipErrorHandler: true` 跳过全局错误处理:
+
+```tsx
+try {
+  await get('/api/data', { skipErrorHandler: true });
+} catch (error) {
+  // 自定义错误处理
+}
+```
+
+## API 响应格式
+
+默认期望后端返回以下格式:
+
+```json
+{
+  "code": 200,
+  "msg": "success",
+  "data": {
+    // 实际数据
+  }
+}
+```
+
+Hook 会自动提取 `data` 字段作为返回值。
+
+## RequestConfig 配置选项
+
+```typescript
+interface RequestConfig {
+  // ky 原生选项
+  headers?: Record<string, string>;
+  searchParams?: Record<string, any>;
+  timeout?: number;
+  retry?: number;
+  
+  // 自定义选项
+  skipErrorHandler?: boolean; // 跳过全局错误处理
+  skipAuth?: boolean;         // 跳过认证(不添加 token)
+}
+```
+
+## TypeScript 类型支持
+
+完整的 TypeScript 类型推断:
+
+```tsx
+interface User {
+  id: number;
+  name: string;
+  email: string;
+}
+
+const { get } = useRequest();
+
+// result 会被推断为 User 类型
+const result = await get<User>('/api/user/1');
+console.log(result.name); // 类型安全
+```
+
+## 环境变量配置
+
+在 `.env` 文件中配置 API 基础 URL:
+
+```env
+API_URL=https://api.example.com
+```
+
+## 完整示例
+
+查看 `useRequest.example.tsx` 文件获取更多实际使用示例。
+
+## 常见问题
+
+### Q: 如何修改 Token 存储键?
+
+A: 修改 `useRequest.ts` 中的 `TOKEN_KEY` 常量。
+
+### Q: 如何自定义导航到登录页面?
+
+A: 修改 `tokenFailureHandler` 函数,使用导航 ref 或全局事件实现。
+
+### Q: 支持多个 baseURL 吗?
+
+A: 可以创建多个 ky 实例,每个实例配置不同的 prefixUrl。
+
+### Q: 如何添加全局请求拦截器?
+
+A: 修改 `createKyInstance` 函数中的 `hooks.beforeRequest`。
+
+### Q: 如何禁用自动 Alert 提示?
+
+A: 使用 `skipErrorHandler: true` 跳过全局错误处理。
+
+## 与 H5 axios 版本的区别
+
+| 特性 | H5 (axios) | RN (ky) |
+|------|-----------|---------|
+| 库 | axios | ky |
+| Toast 提示 | vant showToast | React Native Alert |
+| 存储 | localStorage | MMKV |
+| 导入方式 | import.meta.env | process.env |
+| 体积 | 较大 | 更小 |
+| 现代化 | 传统 | 现代 Fetch API |
+
+## 参考资料
+
+- [ky 文档](https://github.com/sindresorhus/ky)
+- [React Query 文档](https://tanstack.com/query/latest)
+- [MMKV 文档](https://github.com/mrousavy/react-native-mmkv)

+ 271 - 0
src/hooks/useRequest.example.tsx

@@ -0,0 +1,271 @@
+/**
+ * useRequest Hook 使用示例
+ * 
+ * 此文件展示了如何在React Native项目中使用useRequest hook
+ */
+
+import { useEffect, useState } from 'react';
+import { View, Text, Button, ActivityIndicator } from 'react-native';
+import { useRequest } from './useRequest';
+
+// ===== 示例1: 基础GET请求 =====
+export function BasicGetExample() {
+  const { get } = useRequest();
+  const [data, setData] = useState<any>(null);
+  const [loading, setLoading] = useState(false);
+
+  const fetchUserInfo = async () => {
+    setLoading(true);
+    try {
+      const result = await get('/api/user/info');
+      setData(result);
+    } catch (error) {
+      // 错误已经在useRequest中处理,这里可以添加额外的错误逻辑
+      console.error('获取用户信息失败:', error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <View>
+      <Button title="获取用户信息" onPress={fetchUserInfo} />
+      {loading && <ActivityIndicator />}
+      {data && <Text>{JSON.stringify(data, null, 2)}</Text>}
+    </View>
+  );
+}
+
+// ===== 示例2: POST请求 =====
+export function PostExample() {
+  const { post } = useRequest();
+  const [loading, setLoading] = useState(false);
+
+  const submitForm = async () => {
+    setLoading(true);
+    try {
+      const result = await post('/api/user/update', {
+        name: '张三',
+        email: 'zhangsan@example.com',
+      });
+      console.log('更新成功:', result);
+    } catch (error) {
+      console.error('更新失败:', error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <View>
+      <Button title="提交表单" onPress={submitForm} disabled={loading} />
+    </View>
+  );
+}
+
+// ===== 示例3: 带查询参数的GET请求 =====
+export function GetWithParamsExample() {
+  const { get } = useRequest();
+
+  const fetchList = async () => {
+    try {
+      // 使用searchParams传递查询参数
+      const result = await get('/api/products', {
+        searchParams: {
+          page: 1,
+          pageSize: 10,
+          category: 'electronics',
+        },
+      });
+      console.log('产品列表:', result);
+    } catch (error) {
+      console.error('获取列表失败:', error);
+    }
+  };
+
+  useEffect(() => {
+    fetchList();
+  }, []);
+
+  return <View />;
+}
+
+// ===== 示例4: 跳过认证的请求(登录接口)=====
+export function LoginExample() {
+  const { post } = useRequest();
+  const [username, setUsername] = useState('');
+  const [password, setPassword] = useState('');
+
+  const handleLogin = async () => {
+    try {
+      // 登录接口不需要token,使用skipAuth
+      const result = await post<{ token: string }>(
+        '/api/auth/login',
+        { username, password },
+        { skipAuth: true }
+      );
+      
+      // 登录成功后保存token
+      if (result.token) {
+        const { setToken } = await import('./useRequest');
+        setToken(result.token);
+        console.log('登录成功');
+      }
+    } catch (error) {
+      console.error('登录失败:', error);
+    }
+  };
+
+  return (
+    <View>
+      <Button title="登录" onPress={handleLogin} />
+    </View>
+  );
+}
+
+// ===== 示例5: 自定义错误处理 =====
+export function CustomErrorHandlingExample() {
+  const { get } = useRequest();
+
+  const fetchDataWithCustomError = async () => {
+    try {
+      // 跳过全局错误处理,使用自定义错误处理
+      const result = await get('/api/risky-operation', {
+        skipErrorHandler: true,
+      });
+      console.log('操作成功:', result);
+    } catch (error) {
+      // 自定义错误处理逻辑
+      console.error('这是自定义的错误处理:', error);
+      // 可以显示自定义的错误提示
+    }
+  };
+
+  return (
+    <View>
+      <Button title="执行操作" onPress={fetchDataWithCustomError} />
+    </View>
+  );
+}
+
+// ===== 示例6: 配合React Query使用 =====
+import { useQuery } from '@tanstack/react-query';
+
+export function WithReactQueryExample() {
+  const { get } = useRequest();
+
+  const { data, isLoading, error, refetch } = useQuery({
+    queryKey: ['userInfo'],
+    queryFn: () => get('/api/user/info'),
+  });
+
+  return (
+    <View>
+      {isLoading && <ActivityIndicator />}
+      {error && <Text>加载失败</Text>}
+      {data && <Text>{JSON.stringify(data, null, 2)}</Text>}
+      <Button title="刷新" onPress={() => refetch()} />
+    </View>
+  );
+}
+
+// ===== 示例7: PUT和DELETE请求 =====
+export function UpdateDeleteExample() {
+  const { put, delete: del } = useRequest();
+
+  const updateUser = async (userId: number) => {
+    try {
+      await put(`/api/user/${userId}`, {
+        name: '李四',
+        age: 25,
+      });
+      console.log('更新成功');
+    } catch (error) {
+      console.error('更新失败:', error);
+    }
+  };
+
+  const deleteUser = async (userId: number) => {
+    try {
+      await del(`/api/user/${userId}`);
+      console.log('删除成功');
+    } catch (error) {
+      console.error('删除失败:', error);
+    }
+  };
+
+  return (
+    <View>
+      <Button title="更新用户" onPress={() => updateUser(1)} />
+      <Button title="删除用户" onPress={() => deleteUser(1)} />
+    </View>
+  );
+}
+
+// ===== 示例8: 文件上传 =====
+export function FileUploadExample() {
+  const { post } = useRequest();
+
+  const uploadFile = async (file: File) => {
+    try {
+      const formData = new FormData();
+      formData.append('file', file);
+
+      const result = await post('/api/upload', formData, {
+        headers: {
+          // FormData会自动设置Content-Type
+          'Content-Type': 'multipart/form-data',
+        },
+      });
+      console.log('上传成功:', result);
+    } catch (error) {
+      console.error('上传失败:', error);
+    }
+  };
+
+  return <View />;
+}
+
+// ===== 示例9: 直接使用http实例 =====
+import { http } from './useRequest';
+
+export async function DirectHttpExample() {
+  try {
+    // 直接使用导出的http实例
+    const result = await http.get('api/quick-request').json();
+    console.log('快速请求结果:', result);
+  } catch (error) {
+    console.error('请求失败:', error);
+  }
+}
+
+// ===== 示例10: Token管理 =====
+import { getToken, setToken, removeToken } from './useRequest';
+
+export function TokenManagementExample() {
+  const handleLoginSuccess = (token: string) => {
+    // 保存token
+    setToken(token);
+    console.log('Token已保存');
+  };
+
+  const checkToken = () => {
+    // 获取当前token
+    const currentToken = getToken();
+    console.log('当前token:', currentToken);
+  };
+
+  const handleLogout = () => {
+    // 清除token
+    removeToken();
+    console.log('Token已清除');
+  };
+
+  return (
+    <View>
+      <Button title="登录" onPress={() => handleLoginSuccess('mock-token-123')} />
+      <Button title="检查Token" onPress={checkToken} />
+      <Button title="登出" onPress={handleLogout} />
+    </View>
+  );
+}

+ 349 - 0
src/hooks/useRequest.ts

@@ -0,0 +1,349 @@
+import { useCallback } from 'react';
+import { Alert } from 'react-native';
+import ky, { HTTPError, type Options as KyOptions } from 'ky';
+import { storage } from '@/App';
+
+// Token存储键
+const TOKEN_KEY = 'auth_token';
+
+// 错误码映射
+const ERROR_CODE_MAP: Record<string, string> = {
+  '401': '认证失败,无法访问系统资源',
+  '403': '当前操作没有权限',
+  '404': '访问资源不存在',
+  'default': '系统未知错误,请反馈给管理员',
+};
+
+// Token相关工具函数
+export const getToken = (): string | undefined => {
+  return storage.getString(TOKEN_KEY);
+};
+
+export const setToken = (token: string): void => {
+  storage.set(TOKEN_KEY, token);
+};
+
+export const removeToken = (): void => {
+  storage.delete(TOKEN_KEY);
+};
+
+// Token失败处理(跳转登录)
+const tokenFailureHandler = () => {
+  removeToken();
+  // TODO: 导航到登录页面
+  // 可以通过全局事件或导航ref来实现
+  Alert.alert('提示', '登录已过期,请重新登录');
+};
+
+// 显示错误提示
+const showErrorToast = (message: string) => {
+  Alert.alert('错误', message);
+};
+
+// 请求响应数据结构
+interface ApiResponse<T = any> {
+  code: number;
+  msg?: string;
+  data?: T;
+}
+
+// 创建基础ky实例
+const baseUrl = 'https://recipe-muse.com:8443';
+
+const createKyInstance = () => {
+  return ky.create({
+    prefixUrl: baseUrl,
+    timeout: 3 * 60 * 1000, // 3分钟超时
+    headers: {
+      'Content-Type': 'application/json',
+    },
+    hooks: {
+      // 请求前钩子 - 添加token
+      beforeRequest: [
+        (request) => {
+          const token = getToken();
+          if (token) {
+            request.headers.set('Authorization', `Bearer ${token}`);
+          }
+        },
+      ],
+      // 响应后钩子 - 处理业务错误码
+      afterResponse: [
+        async (request, options, response) => {
+          // 如果HTTP状态码正常,检查业务状态码
+          if (response.ok) {
+            const data = (await response.json()) as ApiResponse;
+            const code = data.code ?? 200;
+            const msg = ERROR_CODE_MAP[String(code)] ?? data.msg ?? ERROR_CODE_MAP['default'];
+
+            // 处理业务错误码
+            if (code === 401) {
+              tokenFailureHandler();
+              throw new Error(msg);
+            } else if (code === 500) {
+              showErrorToast(msg);
+              throw new Error(msg);
+            } else if (code !== 200) {
+              showErrorToast(msg);
+              throw new Error(msg);
+            }
+
+            // 返回修改后的响应(只返回data部分)
+            return new Response(JSON.stringify(data.data), {
+              status: response.status,
+              statusText: response.statusText,
+              headers: response.headers,
+            });
+          }
+          return response;
+        },
+      ],
+    },
+  });
+};
+
+// 全局ky实例
+const kyInstance = createKyInstance();
+
+// 错误处理函数
+const handleRequestError = (error: unknown): never => {
+  let message = '网络请求失败';
+
+  if (error instanceof HTTPError) {
+    const statusCode = error.response.status;
+    
+    // 处理HTTP错误
+    if (statusCode === 401) {
+      tokenFailureHandler();
+      message = '认证失败,请重新登录';
+    } else if (statusCode >= 500) {
+      message = '服务器错误,请稍后重试';
+    } else if (statusCode >= 400) {
+      message = '请求参数错误';
+    }
+
+    // 尝试获取错误响应体
+    error.response.json().then((data: any) => {
+      const codeStart = String(data.code).substring(0, 2);
+      
+      // Token认证错误
+      if (codeStart === '10') {
+        tokenFailureHandler();
+      }
+      
+      if (data.message) {
+        message = data.message;
+      }
+    }).catch(() => {
+      // JSON解析失败,使用默认错误消息
+    });
+  } else if (error instanceof Error) {
+    // 网络错误
+    if (error.message.includes('Network Error') || error.message.includes('network')) {
+      message = '网络连接异常';
+    } else if (error.message.includes('timeout')) {
+      message = '请求超时,请重试';
+    } else {
+      message = error.message;
+    }
+  }
+
+  setTimeout(() => {
+    Alert.alert('错误', message);
+  }, 0);
+
+  throw error;
+};
+
+// 自定义请求配置接口
+interface RequestConfig extends KyOptions {
+  skipErrorHandler?: boolean; // 跳过全局错误处理
+  skipAuth?: boolean; // 跳过认证token
+}
+
+/**
+ * useRequest Hook
+ * 提供统一的API请求功能
+ */
+export const useRequest = () => {
+  // GET请求
+  const get = useCallback(async <T = any>(
+    url: string,
+    options?: RequestConfig
+  ): Promise<T> => {
+    try {
+      const { skipErrorHandler, skipAuth, ...kyOptions } = options ?? {};
+      
+      const requestOptions: KyOptions = {
+        ...kyOptions,
+        method: 'get',
+      };
+
+      // 如果跳过认证,移除Authorization头
+      if (skipAuth) {
+        requestOptions.hooks = {
+          beforeRequest: [
+            (request) => {
+              request.headers.delete('Authorization');
+            },
+          ],
+        };
+      }
+
+      const response = await kyInstance(url, requestOptions).json<T>();
+      return response;
+    } catch (error) {
+      if (!options?.skipErrorHandler) {
+        handleRequestError(error);
+      }
+      throw error;
+    }
+  }, []);
+
+  // POST请求
+  const post = useCallback(async <T = any>(
+    url: string,
+    data?: any,
+    options?: RequestConfig
+  ): Promise<T> => {
+    try {
+      const { skipErrorHandler, skipAuth, ...kyOptions } = options ?? {};
+      
+      const requestOptions: KyOptions = {
+        ...kyOptions,
+        method: 'post',
+        json: data,
+      };
+
+      if (skipAuth) {
+        requestOptions.hooks = {
+          beforeRequest: [
+            (request) => {
+              request.headers.delete('Authorization');
+            },
+          ],
+        };
+      }
+
+      const response = await kyInstance(url, requestOptions).json<T>();
+      return response;
+    } catch (error) {
+      if (!options?.skipErrorHandler) {
+        handleRequestError(error);
+      }
+      throw error;
+    }
+  }, []);
+
+  // PUT请求
+  const put = useCallback(async <T = any>(
+    url: string,
+    data?: any,
+    options?: RequestConfig
+  ): Promise<T> => {
+    try {
+      const { skipErrorHandler, skipAuth, ...kyOptions } = options ?? {};
+      
+      const requestOptions: KyOptions = {
+        ...kyOptions,
+        method: 'put',
+        json: data,
+      };
+
+      if (skipAuth) {
+        requestOptions.hooks = {
+          beforeRequest: [
+            (request) => {
+              request.headers.delete('Authorization');
+            },
+          ],
+        };
+      }
+
+      const response = await kyInstance(url, requestOptions).json<T>();
+      return response;
+    } catch (error) {
+      if (!options?.skipErrorHandler) {
+        handleRequestError(error);
+      }
+      throw error;
+    }
+  }, []);
+
+  // DELETE请求
+  const del = useCallback(async <T = any>(
+    url: string,
+    options?: RequestConfig
+  ): Promise<T> => {
+    try {
+      const { skipErrorHandler, skipAuth, ...kyOptions } = options ?? {};
+      
+      const requestOptions: KyOptions = {
+        ...kyOptions,
+        method: 'delete',
+      };
+
+      if (skipAuth) {
+        requestOptions.hooks = {
+          beforeRequest: [
+            (request) => {
+              request.headers.delete('Authorization');
+            },
+          ],
+        };
+      }
+
+      const response = await kyInstance(url, requestOptions).json<T>();
+      return response;
+    } catch (error) {
+      if (!options?.skipErrorHandler) {
+        handleRequestError(error);
+      }
+      throw error;
+    }
+  }, []);
+
+  // 通用请求方法
+  const request = useCallback(async <T = any>(
+    url: string,
+    options?: RequestConfig
+  ): Promise<T> => {
+    try {
+      const { skipErrorHandler, skipAuth, ...kyOptions } = options ?? {};
+      
+      const requestOptions: KyOptions = kyOptions;
+
+      if (skipAuth) {
+        requestOptions.hooks = {
+          beforeRequest: [
+            (request) => {
+              request.headers.delete('Authorization');
+            },
+          ],
+        };
+      }
+
+      const response = await kyInstance(url, requestOptions).json<T>();
+      return response;
+    } catch (error) {
+      if (!options?.skipErrorHandler) {
+        handleRequestError(error);
+      }
+      throw error;
+    }
+  }, []);
+
+  return {
+    get,
+    post,
+    put,
+    delete: del,
+    request,
+  };
+};
+
+// 导出ky实例供直接使用
+export const http = kyInstance;
+
+// 默认导出
+export default useRequest;

+ 268 - 0
src/hooks/useSub.ts

@@ -0,0 +1,268 @@
+// useSubscription.ts
+import { useEffect, useRef, useState, useCallback } from 'react';
+import { Platform, Alert } from 'react-native';
+import { useIAP, ErrorCode, Product, Purchase, PurchaseError, ProductType, getAvailablePurchases as getAvailablePurchasesAPI } from 'react-native-iap';
+
+interface UseSubscriptionParams {
+  productIds?: string[];
+  productType?: ProductType;
+  sendMessageToWebView?: (msg: any) => void;
+  onPurchaseComplete?: (purchase: Purchase) => void;
+  onPurchaseFailed?: (error: PurchaseError) => void;
+}
+
+export interface SubscriptionState {
+  connected: boolean;
+  products: Product[];
+  loading: boolean;
+  error: string | null;
+  purchasing: boolean;
+}
+
+// NOTE 文档:https://hyochan.github.io/react-native-iap/docs/intro
+
+export default function useSubscription({
+  productIds = ['com.example.coins.pack1', 'com.example.premium'],
+  productType = 'subs',
+  sendMessageToWebView,
+  onPurchaseComplete,
+  onPurchaseFailed,
+}: UseSubscriptionParams = {}) {
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+  const [purchasing, setPurchasing] = useState(false);
+  const isProcessingRef = useRef(false);
+
+  const {
+    connected,
+    products,
+    fetchProducts,
+    requestPurchase,
+    finishTransaction,
+  } = useIAP({
+    onPurchaseSuccess: async (purchase) => {
+      // 防止重复处理
+      if (isProcessingRef.current) {
+        console.log('Already processing a purchase, skipping...');
+        return;
+      }
+
+      isProcessingRef.current = true;
+      setPurchasing(true);
+
+      try {
+        console.log('Purchase successful:', purchase.productId);
+
+        // 发送消息到 WebView
+        sendMessageToWebView?.({
+          type: 'PURCHASE_SUCCESS',
+          data: {
+            productId: purchase.productId,
+            transactionId: 'transactionId' in purchase ? purchase.transactionId : purchase.id,
+            purchaseToken: purchase.purchaseToken,
+          },
+        });
+
+        // TODO: Verify the receipt on your backend before granting access
+        // const isValid = await verifyReceiptOnBackend(purchase);
+        // if (!isValid) {
+        //   throw new Error('Invalid receipt');
+        // }
+
+        // 完成交易
+        await finishTransaction({
+          purchase,
+          isConsumable: productType === 'in-app', // 订阅为 false,消耗品为 true
+        });
+
+        // 调用成功回调
+        onPurchaseComplete?.(purchase);
+
+        setError(null);
+        Alert.alert('购买成功', '感谢您的购买!');
+      } catch (error) {
+        console.error('Failed to complete purchase:', error);
+        const errorMessage = error instanceof Error ? error.message : '购买处理失败';
+        setError(errorMessage);
+        onPurchaseFailed?.(error as PurchaseError);
+
+        sendMessageToWebView?.({
+          type: 'PURCHASE_ERROR',
+          data: { error: errorMessage },
+        });
+
+        Alert.alert('购买失败', errorMessage);
+      } finally {
+        setPurchasing(false);
+        isProcessingRef.current = false;
+      }
+    },
+    onPurchaseError: (error) => {
+      setPurchasing(false);
+      isProcessingRef.current = false;
+
+      if (error.code !== ErrorCode.UserCancelled) {
+        console.error('Purchase error:', error.message);
+        setError(error.message);
+        onPurchaseFailed?.(error);
+
+        sendMessageToWebView?.({
+          type: 'PURCHASE_ERROR',
+          data: { error: error.message, code: error.code },
+        });
+
+        Alert.alert('购买错误', error.message);
+      } else {
+        console.log('Purchase cancelled by user');
+        sendMessageToWebView?.({
+          type: 'PURCHASE_CANCELLED',
+        });
+      }
+    },
+  });
+
+  // 获取产品列表
+  useEffect(() => {
+    if (connected && productIds.length > 0) {
+      setLoading(true);
+      setError(null);
+
+      fetchProducts({ skus: productIds, type: productType as any })
+        .then(() => {
+          setLoading(false);
+          console.log('Products fetched successfully');
+        })
+        .catch((err) => {
+          setLoading(false);
+          const errorMessage = err instanceof Error ? err.message : '获取产品失败';
+          setError(errorMessage);
+          console.error('Failed to fetch products:', err);
+        });
+    }
+  }, [connected, productIds.join(','), productType]);
+
+  // 发起购买
+  const handlePurchase = useCallback(
+    async (productId: string) => {
+      if (!connected) {
+        const errorMsg = '商店未连接,请稍后重试';
+        setError(errorMsg);
+        Alert.alert('错误', errorMsg);
+        return;
+      }
+
+      if (purchasing) {
+        console.log('Purchase already in progress');
+        return;
+      }
+
+      setPurchasing(true);
+      setError(null);
+
+      try {
+        sendMessageToWebView?.({
+          type: 'PURCHASE_STARTED',
+          data: { productId },
+        });
+
+        if (productType === 'in-app') {
+          await requestPurchase({
+            request: {
+              ios: {
+                sku: productId,
+              },
+              android: {
+                skus: [productId],
+              },
+            },
+            type: 'in-app',
+          });
+        } else {
+          await requestPurchase({
+            request: {
+              ios: {
+                sku: productId,
+              },
+              android: {
+                skus: [productId],
+              },
+            },
+            type: 'subs',
+          });
+        }
+      } catch (error) {
+        setPurchasing(false);
+        const errorMessage = error instanceof Error ? error.message : '购买失败';
+        console.error('Purchase failed:', error);
+        setError(errorMessage);
+        Alert.alert('购买失败', errorMessage);
+      }
+    },
+    [connected, purchasing, requestPurchase, sendMessageToWebView]
+  );
+
+  // 恢复购买
+  const restorePurchases = useCallback(async () => {
+    if (!connected) {
+      const errorMsg = '商店未连接,请稍后重试';
+      setError(errorMsg);
+      Alert.alert('错误', errorMsg);
+      return;
+    }
+
+    setLoading(true);
+    setError(null);
+
+    try {
+      const availablePurchases = await getAvailablePurchasesAPI() || [];
+      console.log('Available purchases:', availablePurchases);
+
+      if (availablePurchases.length === 0) {
+        Alert.alert('提示', '没有可恢复的购买记录');
+      } else {
+        // TODO: 验证恢复的购买并更新用户状态
+        sendMessageToWebView?.({
+          type: 'PURCHASES_RESTORED',
+          data: { purchases: availablePurchases },
+        });
+
+        Alert.alert('成功', `已恢复 ${availablePurchases.length} 个购买记录`);
+      }
+    } catch (error) {
+      const errorMessage = error instanceof Error ? error.message : '恢复购买失败';
+      console.error('Restore purchases failed:', error);
+      setError(errorMessage);
+      Alert.alert('恢复失败', errorMessage);
+    } finally {
+      setLoading(false);
+    }
+  }, [connected, sendMessageToWebView]);
+
+  // 根据 productId 获取产品信息
+  const getProduct = useCallback(
+    (productId: string) => {
+      return products.find((p) => p.id === productId);
+    },
+    [products]
+  );
+
+  // 清除错误
+  const clearError = useCallback(() => {
+    setError(null);
+  }, []);
+
+  return {
+    // 状态
+    connected,
+    products,
+    loading,
+    error,
+    purchasing,
+    
+    // 方法
+    handlePurchase,
+    restorePurchases,
+    getProduct,
+    clearError,
+  };
+}

+ 3 - 1
src/navigation/Application.tsx

@@ -7,7 +7,7 @@ import { SafeAreaProvider } from 'react-native-safe-area-context';
 import { Paths } from '@/navigation/paths';
 import { useTheme } from '@/theme';
 
-import { Example, Startup, Login, Home} from '@/screens';
+import { Example, Startup, Login, Home, Pay, SubList} from '@/screens';
 
 const Stack = createStackNavigator<RootStackParamList>();
 
@@ -22,6 +22,8 @@ function ApplicationNavigator() {
           <Stack.Screen component={Example} name={Paths.Example} />
           <Stack.Screen component={Login} name={Paths.Login} />
           <Stack.Screen component={Home} name={Paths.Home} />
+          <Stack.Screen component={Pay} name={Paths.Pay} />
+          <Stack.Screen component={SubList} name={Paths.SubList} />
         </Stack.Navigator>
       </NavigationContainer>
     </SafeAreaProvider>

+ 2 - 0
src/navigation/paths.ts

@@ -3,4 +3,6 @@ export const enum Paths {
   Startup = 'startup',
   Login = 'login',
   Home = 'home',
+  Pay = 'pay',
+  SubList = 'sublist',
 }

+ 2 - 0
src/navigation/types.ts

@@ -10,4 +10,6 @@ export type RootStackParamList = {
   [Paths.Startup]: undefined;
   [Paths.Login]: undefined;
   [Paths.Home]: undefined;
+  [Paths.Pay]: undefined;
+  [Paths.SubList]: undefined;
 };

+ 21 - 1
src/screens/Login/Login.tsx

@@ -39,6 +39,10 @@ function Login({ navigation }: RootScreenProps<Paths.Login>) {
     navigation.navigate(Paths.Home);
   }
 
+  const onGoPage = (page: Paths) => {
+    navigation.navigate(page);
+  }
+
 
   const handleLogin = async () => {
     try {
@@ -103,7 +107,7 @@ function Login({ navigation }: RootScreenProps<Paths.Login>) {
   };
 
   return (
-    <SafeScreen>
+    <SafeScreen> 
       <ScrollView>
         <View
           style={[
@@ -168,6 +172,22 @@ function Login({ navigation }: RootScreenProps<Paths.Login>) {
               <IconByVariant path="home" stroke={colors.purple500} />
             </TouchableOpacity>
 
+            <TouchableOpacity
+              onPress={() => onGoPage(Paths.Pay)}
+              style={[components.buttonCircle, gutters.marginBottom_16]}
+              testID="change-theme-button"
+            >
+              <View>支付</View>
+            </TouchableOpacity>
+
+            <TouchableOpacity
+              onPress={() => onGoPage(Paths.SubList)}
+              style={[components.buttonCircle, gutters.marginBottom_16]}
+              testID="change-theme-button"
+            >
+              <View>订阅</View>
+            </TouchableOpacity>
+
             <TouchableOpacity
               onPress={onChangeTheme}
               style={[components.buttonCircle, gutters.marginBottom_16]}

+ 371 - 0
src/screens/Pay/index.tsx

@@ -0,0 +1,371 @@
+import React, { useState, useEffect } from 'react';
+import {
+  View,
+  Text,
+  TouchableOpacity,
+  ScrollView,
+  StyleSheet,
+  Alert,
+  Dimensions,
+} from 'react-native';
+import Svg, { Path } from 'react-native-svg';
+import { useTheme } from '@/theme';
+import { instance } from '@/services/instance';
+
+import { SafeScreen } from '@/components/templates';
+import { RootScreenProps } from '@/navigation/types';
+import { Paths } from '@/navigation/paths';
+
+const { width } = Dimensions.get('window');
+
+// Google Pay Icon Component
+const GooglePayIcon = () => (
+  <Svg width={48} height={48} viewBox="0 0 48 48" fill="none">
+    <Path
+      d="M24 9.5C20.65 9.5 17.45 10.65 14.9 12.7L18.3 16.1C19.95 14.85 21.95 14.15 24 14.15C28.55 14.15 32.35 17.4 33.25 21.75H38.95C37.95 14.45 31.65 9.5 24 9.5Z"
+      fill="#EA4335"
+    />
+    <Path
+      d="M9.05 24C9.05 26.05 9.55 28 10.45 29.7L5.8 33.1C4.1 30.35 3 27.25 3 24C3 20.75 4.1 17.65 5.8 14.9L10.45 18.3C9.55 20 9.05 21.95 9.05 24Z"
+      fill="#FBBC05"
+    />
+    <Path
+      d="M24 38.5C20.65 38.5 17.45 37.35 14.9 35.3L10.45 38.7C13.2 41.4 16.95 43 21 43.5V43.5C28.3 42.5 34.3 37.5 36.75 30.75H33.25C32.35 35.1 28.55 38.5 24 38.5Z"
+      fill="#34A853"
+    />
+    <Path
+      d="M45 24C45 22.85 44.9 21.7 44.7 20.6H24V27.65H35.4C34.7 30.55 32.85 32.95 30.35 34.45L33.85 37.2C37.95 33.45 40.2 28.15 40.2 24C40.2 22.85 40.1 21.7 39.9 20.6H45V24Z"
+      fill="#4285F4"
+    />
+  </Svg>
+);
+
+// Apple Pay Icon Component
+const ApplePayIcon = () => (
+  <Svg width={40} height={40} viewBox="0 0 24 24" fill="none">
+    <Path
+      d="M17.05 20.28C16.03 21.23 14.96 21.08 13.93 20.63C12.84 20.17 11.85 20.15 10.7 20.63C9.28 21.25 8.52 21.07 7.62 20.28C2.02 14.58 2.83 5.97 9.26 5.67C10.76 5.75 11.79 6.52 12.65 6.58C13.95 6.31 15.21 5.52 16.61 5.63C18.31 5.78 19.6 6.45 20.45 7.65C16.77 9.87 17.63 14.72 20.95 16.11C20.27 17.89 19.4 19.65 17.04 20.29L17.05 20.28ZM12.53 5.62C12.38 3.06 14.42 0.93 16.87 0.75C17.2 3.66 14.18 5.89 12.53 5.62Z"
+      fill="white"
+    />
+  </Svg>
+);
+
+interface PriceConfig {
+  prices?: Array<{
+    id: string;
+    name: string;
+    price: number;
+    currency: string;
+  }>;
+}
+
+const Pay = ( { navigation }: RootScreenProps<Paths.Pay> ) => {
+  const { colors, layout, gutters, fonts } = useTheme();
+  const [selectedPayment, setSelectedPayment] = useState<
+    'google' | 'apple' | ''
+  >('');
+  const [productList, setProductList] = useState<PriceConfig['prices']>([]);
+
+  useEffect(() => {
+    // 获取价格配置
+    const fetchPriceConfig = async () => {
+      try {
+        const response = await instance.get('price-config').json<PriceConfig>();
+        setProductList(response?.prices || []);
+        console.log('getPriceConfig', response?.prices);
+      } catch (err) {
+        console.error('获取价格配置失败:', err);
+      }
+    };
+
+    fetchPriceConfig();
+  }, []);
+
+  const selectPaymentMethod = (method: 'google' | 'apple') => {
+    setSelectedPayment(method);
+  };
+
+  const handleCompleteSubscription = () => {
+    if (!selectedPayment) {
+      Alert.alert('提示', '请选择支付方式');
+      return;
+    }
+
+    const paymentName =
+      selectedPayment === 'google' ? 'Google Pay' : 'Apple Pay';
+    Alert.alert('提示', `正在使用 ${paymentName} 完成订阅...`);
+  };
+
+  return (
+    <SafeScreen style={styles.pageBgColor} navigation={navigation}>
+      <ScrollView style={styles.container}>
+        <View style={styles.content}>
+          {/* 页面标题区域 */}
+          <View style={styles.header}>
+            <View style={styles.headerContent}>
+              <Text style={[styles.title, fonts.bold]}>
+                Complete Your Subscription
+              </Text>
+              <View style={styles.premiumBadge}>
+                <Text style={[styles.premiumTitle, fonts.bold]}>
+                  Premium Plan
+                </Text>
+                <Text style={styles.premiumPrice}>• $9.99/month</Text>
+              </View>
+            </View>
+          </View>
+
+          {/* 订阅摘要卡片 */}
+          <View style={[styles.card, gutters.marginBottom_24]}>
+            <Text style={[styles.cardTitle, fonts.bold]}>
+              Subscription Summary
+            </Text>
+
+            <View style={styles.summaryList}>
+              {/* Plan */}
+              <View style={styles.summaryItem}>
+                <Text style={styles.summaryLabel}>Plan:</Text>
+                <Text style={[styles.summaryValue, fonts.bold]}>
+                  Premium Plan
+                </Text>
+              </View>
+
+              {/* Price */}
+              <View style={styles.summaryItem}>
+                <Text style={styles.summaryLabel}>Price:</Text>
+                <Text style={[styles.summaryValue, fonts.bold]}>
+                  $9.99/month
+                </Text>
+              </View>
+
+              {/* Billing Cycle */}
+              <View style={styles.summaryItem}>
+                <Text style={styles.summaryLabel}>Billing Cycle:</Text>
+                <Text style={[styles.summaryValue, fonts.bold]}>Monthly</Text>
+              </View>
+            </View>
+          </View>
+
+          {/* 选择支付方式 */}
+          <View style={gutters.marginBottom_24}>
+            <Text
+              style={[styles.sectionTitle, fonts.bold, gutters.marginBottom_16]}
+            >
+              Choose Payment Method
+            </Text>
+
+            <View style={styles.paymentMethods}>
+              {/* Google Pay */}
+              <TouchableOpacity
+                onPress={() => selectPaymentMethod('google')}
+                style={[
+                  styles.paymentButton,
+                  styles.paymentButtonWhite,
+                  selectedPayment === 'google' && styles.paymentButtonSelected,
+                ]}
+              >
+                <View style={styles.paymentContent}>
+                  <GooglePayIcon />
+                  <Text style={[styles.paymentText, fonts.bold]}>
+                    Google Pay
+                  </Text>
+                </View>
+              </TouchableOpacity>
+
+              {/* Apple Pay */}
+              <TouchableOpacity
+                onPress={() => selectPaymentMethod('apple')}
+                style={[
+                  styles.paymentButton,
+                  styles.paymentButtonBlack,
+                  selectedPayment === 'apple' && styles.paymentButtonSelected,
+                ]}
+              >
+                <View style={styles.paymentContent}>
+                  <ApplePayIcon />
+                  <Text style={[styles.paymentTextWhite, fonts.bold]}>
+                    Apple Pay
+                  </Text>
+                </View>
+              </TouchableOpacity>
+            </View>
+          </View>
+
+          {/* 完成订阅按钮和条款 */}
+          <View style={gutters.marginBottom_24}>
+            <TouchableOpacity
+              onPress={handleCompleteSubscription}
+              disabled={!selectedPayment}
+              style={[
+                styles.completeButton,
+                !selectedPayment && styles.completeButtonDisabled,
+              ]}
+            >
+              <Text style={[styles.completeButtonText, fonts.bold]}>
+                Complete Subscription
+              </Text>
+            </TouchableOpacity>
+
+            <Text style={styles.termsText}>
+              By subscribing, you agree to our{' '}
+              <Text style={styles.termsLink}>Terms of Service</Text> and{' '}
+              <Text style={styles.termsLink}>Privacy Policy</Text>.
+            </Text>
+          </View>
+        </View>
+      </ScrollView>
+    </SafeScreen>
+  );
+};
+
+const styles = StyleSheet.create({
+  pageBgColor: {
+    backgroundColor: '#FBBF24', // yellow-400
+  },
+  container: {
+    flex: 1,
+  },
+  content: {
+    maxWidth: 672, // 2xl
+    width: width > 672 ? 672 : '100%',
+    alignSelf: 'center',
+    paddingHorizontal: 16,
+    paddingVertical: 24,
+  },
+  header: {
+    marginBottom: 24,
+  },
+  headerContent: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'flex-start',
+    marginBottom: 16,
+  },
+  title: {
+    fontSize: 32,
+    color: '#1F2937', // dark-800
+    flex: 1,
+    marginRight: 16,
+  },
+  premiumBadge: {
+    width: 168,
+    backgroundColor: '#2563EB', // blue-600
+    paddingHorizontal: 16,
+    paddingVertical: 8,
+    borderRadius: 24,
+    borderWidth: 2,
+    borderColor: '#0A0910',
+  },
+  premiumTitle: {
+    color: '#FFFFFF',
+    fontSize: 14,
+  },
+  premiumPrice: {
+    color: '#FFFFFF',
+    fontSize: 12,
+  },
+  card: {
+    backgroundColor: '#FFFFFF',
+    borderWidth: 2,
+    borderColor: '#0A0910',
+    borderRadius: 8,
+    padding: 24,
+  },
+  cardTitle: {
+    fontSize: 24,
+    color: '#1D4ED8', // blue-700
+    marginBottom: 24,
+  },
+  summaryList: {
+    gap: 16,
+  },
+  summaryItem: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+    marginBottom: 16,
+  },
+  summaryLabel: {
+    fontSize: 16,
+    color: '#6B7280', // gray-600
+  },
+  summaryValue: {
+    fontSize: 18,
+    color: '#1F2937', // dark-800
+  },
+  sectionTitle: {
+    fontSize: 24,
+    color: '#1F2937', // dark-800
+  },
+  paymentMethods: {
+    flexDirection: 'row',
+    gap: 16,
+  },
+  paymentButton: {
+    flex: 1,
+    borderWidth: 2,
+    borderRadius: 8,
+    padding: 24,
+    minHeight: 120,
+    justifyContent: 'center',
+  },
+  paymentButtonWhite: {
+    backgroundColor: '#FFFFFF',
+    borderColor: '#0A0910',
+  },
+  paymentButtonBlack: {
+    backgroundColor: '#000000',
+    borderColor: '#0A0910',
+  },
+  paymentButtonSelected: {
+    borderColor: '#2563EB', // blue-600
+    shadowColor: '#000',
+    shadowOffset: { width: 0, height: 4 },
+    shadowOpacity: 0.25,
+    shadowRadius: 8,
+    elevation: 8,
+  },
+  paymentContent: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center',
+    gap: 12,
+  },
+  paymentText: {
+    fontSize: 20,
+    color: '#1F2937', // dark-800
+  },
+  paymentTextWhite: {
+    fontSize: 20,
+    color: '#FFFFFF',
+  },
+  completeButton: {
+    width: '100%',
+    backgroundColor: '#2563EB', // blue-600
+    paddingHorizontal: 24,
+    paddingVertical: 16,
+    borderRadius: 8,
+    borderWidth: 2,
+    borderColor: '#0A0910',
+    marginBottom: 16,
+  },
+  completeButtonDisabled: {
+    backgroundColor: '#9CA3AF', // gray-400
+  },
+  completeButtonText: {
+    color: '#FFFFFF',
+    fontSize: 18,
+    textAlign: 'center',
+  },
+  termsText: {
+    fontSize: 14,
+    color: '#6B7280', // gray-600
+    textAlign: 'center',
+  },
+  termsLink: {
+    color: '#2563EB', // blue-600
+    textDecorationLine: 'underline',
+  },
+});
+
+export default Pay;

+ 1 - 1
src/screens/Startup/Startup.tsx

@@ -26,7 +26,7 @@ function Startup({ navigation }: RootScreenProps<Paths.Startup>) {
     if (isSuccess) {
       navigation.reset({
         index: 0,
-        routes: [{ name: Paths.Home }],
+        routes: [{ name: Paths.Login }],
       });
     }
   }, [isSuccess, navigation]);

+ 279 - 0
src/screens/SubList/index.tsx

@@ -0,0 +1,279 @@
+import React from 'react';
+import {
+  View,
+  Text,
+  TouchableOpacity,
+  ScrollView,
+  StyleSheet,
+  Alert,
+  Dimensions,
+} from 'react-native';
+import Svg, { Path } from 'react-native-svg';
+import { useTheme } from '@/theme';
+import { SafeScreen } from '@/components/templates';
+import { RootScreenProps } from '@/navigation/types';
+import { Paths } from '@/navigation/paths';
+
+const { width } = Dimensions.get('window');
+
+// CheckIcon Component
+const CheckIcon = () => (
+  <Svg width={24} height={24} viewBox="0 0 24 24" fill="none" stroke="#10B981">
+    <Path
+      strokeLinecap="round"
+      strokeLinejoin="round"
+      strokeWidth={2}
+      d="M5 13l4 4L19 7"
+    />
+  </Svg>
+);
+
+const SubList = ({ navigation }: RootScreenProps<Paths.SubList>) => {
+  const { colors, layout, gutters, fonts } = useTheme();
+
+  // 处理免费试用按钮点击
+  const handleFreeTrial = () => {
+    Alert.alert('成功', '开始7天免费试用');
+  };
+
+  // 处理高级套餐按钮点击
+  const handlePremium = () => {
+    Alert.alert('提示', '正在跳转到支付页面...');
+  };
+
+  return (
+    <SafeScreen style={styles.pageBgColor} navigation={navigation}>
+      <ScrollView style={styles.container}>
+        <View style={styles.content}>
+          {/* 页面标题区域 */}
+          <View style={styles.headerSection}>
+            <Text style={[styles.mainTitle, fonts.bold]}>Choose Your Plan</Text>
+            <Text style={styles.subtitle}>
+              Unlock premium features with our flexible{'\n'}subscription
+              options
+            </Text>
+          </View>
+
+          {/* 7天免费试用卡片 */}
+          <View style={[styles.card, gutters.marginBottom_24]}>
+            {/* 卡片头部 */}
+            <View style={styles.cardHeaderLight}>
+              <Text style={[styles.cardHeaderTitle, fonts.bold]}>
+                7-Day Free Trial
+              </Text>
+            </View>
+
+            {/* 卡片内容 */}
+            <View style={styles.cardBody}>
+              {/* 价格 */}
+              <View style={styles.priceSection}>
+                <Text style={[styles.priceAmount, fonts.bold]}>$0</Text>
+                <Text style={styles.pricePeriod}> for 7 days</Text>
+              </View>
+
+              {/* 特性列表 */}
+              <View style={styles.featureList}>
+                <View style={styles.featureItem}>
+                  <CheckIcon />
+                  <Text style={styles.featureText}>
+                    Full access to all features
+                  </Text>
+                </View>
+                <View style={styles.featureItem}>
+                  <CheckIcon />
+                  <Text style={styles.featureText}>
+                    No credit card required
+                  </Text>
+                </View>
+                <View style={styles.featureItem}>
+                  <CheckIcon />
+                  <Text style={styles.featureText}>Cancel anytime</Text>
+                </View>
+              </View>
+
+              {/* 按钮 */}
+              <TouchableOpacity
+                onPress={handleFreeTrial}
+                style={styles.buttonLight}
+              >
+                <Text style={[styles.buttonLightText, fonts.bold]}>
+                  Start Free Trial
+                </Text>
+              </TouchableOpacity>
+            </View>
+          </View>
+
+          {/* Premium 高级套餐卡片 */}
+          <View
+            style={[styles.card, styles.cardPremium, gutters.marginBottom_24]}
+          >
+            {/* 卡片头部 */}
+            <View style={styles.cardHeaderDark}>
+              <Text style={[styles.cardHeaderTitleWhite, fonts.bold]}>
+                Premium Plan
+              </Text>
+            </View>
+
+            {/* 卡片内容 */}
+            <View style={styles.cardBody}>
+              {/* 价格 */}
+              <View style={styles.priceSection}>
+                <Text style={[styles.priceAmount, fonts.bold]}>$9.99</Text>
+                <Text style={styles.pricePeriod}> per month</Text>
+              </View>
+
+              {/* 特性列表 */}
+              <View style={styles.featureList}>
+                <View style={styles.featureItem}>
+                  <CheckIcon />
+                  <Text style={styles.featureText}>All features included</Text>
+                </View>
+                <View style={styles.featureItem}>
+                  <CheckIcon />
+                  <Text style={styles.featureText}>Priority support</Text>
+                </View>
+                <View style={styles.featureItem}>
+                  <CheckIcon />
+                  <Text style={styles.featureText}>Cancel anytime</Text>
+                </View>
+              </View>
+
+              {/* 按钮 */}
+              <TouchableOpacity
+                onPress={handlePremium}
+                style={styles.buttonDark}
+              >
+                <Text style={[styles.buttonDarkText, fonts.bold]}>
+                  Get Premium
+                </Text>
+              </TouchableOpacity>
+            </View>
+          </View>
+        </View>
+      </ScrollView>
+    </SafeScreen>
+  );
+};
+
+const styles = StyleSheet.create({
+  pageBgColor: {
+    backgroundColor: '#FBBF24', // yellow-400
+  },
+  container: {
+    flex: 1,
+  },
+  content: {
+    maxWidth: 672, // 2xl
+    width: width > 672 ? 672 : '100%',
+    alignSelf: 'center',
+    paddingHorizontal: 16,
+    paddingVertical: 24,
+  },
+  headerSection: {
+    alignItems: 'center',
+    marginBottom: 32,
+  },
+  mainTitle: {
+    fontSize: 30,
+    color: '#1F2937', // gray-800
+    marginBottom: 8,
+    textAlign: 'center',
+  },
+  subtitle: {
+    fontSize: 16,
+    color: '#6B7280', // gray-600
+    textAlign: 'center',
+    lineHeight: 24,
+  },
+  card: {
+    backgroundColor: '#FFFFFF',
+    borderRadius: 24,
+    overflow: 'hidden',
+    shadowColor: '#000',
+    shadowOffset: { width: 0, height: 4 },
+    shadowOpacity: 0.15,
+    shadowRadius: 12,
+    elevation: 8,
+  },
+  cardPremium: {
+    borderWidth: 2,
+    borderColor: '#4F46E5', // indigo-600
+  },
+  cardHeaderLight: {
+    backgroundColor: '#E0E7FF', // indigo-100
+    paddingHorizontal: 24,
+    paddingVertical: 16,
+  },
+  cardHeaderDark: {
+    backgroundColor: '#4F46E5', // indigo-600
+    paddingHorizontal: 24,
+    paddingVertical: 16,
+  },
+  cardHeaderTitle: {
+    fontSize: 24,
+    color: '#4338CA', // indigo-700
+  },
+  cardHeaderTitleWhite: {
+    fontSize: 24,
+    color: '#FFFFFF',
+  },
+  cardBody: {
+    paddingHorizontal: 24,
+    paddingVertical: 32,
+  },
+  priceSection: {
+    flexDirection: 'row',
+    alignItems: 'baseline',
+    marginBottom: 24,
+  },
+  priceAmount: {
+    fontSize: 48,
+    color: '#1F2937', // gray-800
+  },
+  pricePeriod: {
+    fontSize: 20,
+    color: '#6B7280', // gray-500
+    marginLeft: 8,
+  },
+  featureList: {
+    marginBottom: 32,
+    gap: 16,
+  },
+  featureItem: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    marginBottom: 16,
+  },
+  featureText: {
+    fontSize: 18,
+    color: '#374151', // gray-700
+    marginLeft: 12,
+    flex: 1,
+  },
+  buttonLight: {
+    width: '100%',
+    backgroundColor: '#E0E7FF', // indigo-100
+    paddingVertical: 16,
+    paddingHorizontal: 24,
+    borderRadius: 16,
+  },
+  buttonLightText: {
+    color: '#4338CA', // indigo-700
+    fontSize: 16,
+    textAlign: 'center',
+  },
+  buttonDark: {
+    width: '100%',
+    backgroundColor: '#4F46E5', // indigo-600
+    paddingVertical: 16,
+    paddingHorizontal: 24,
+    borderRadius: 16,
+  },
+  buttonDarkText: {
+    color: '#FFFFFF',
+    fontSize: 16,
+    textAlign: 'center',
+  },
+});
+
+export default SubList;

+ 2 - 0
src/screens/index.ts

@@ -2,3 +2,5 @@ export { default as Example } from './Example/Example';
 export { default as Startup } from './Startup/Startup';
 export { default as Login } from './Login/Login';
 export { default as Home } from './Home/Home';
+export { default as Pay } from './Pay/index';
+export { default as SubList } from './SubList/index';

+ 11 - 2
src/utils/h5.ts

@@ -1,6 +1,15 @@
 export enum eventTypeEnum {
   // 发起原生登录
-  'NATIVE_LOGIN_REQUEST' = 'NATIVE_LOGIN_REQUEST',
+  'login2Native' = 'NATIVE_LOGIN_REQUEST',
   'H5_LOGIN_SUCCESS_IOS' = 'H5_LOGIN_SUCCESS_IOS',
-  'H5_LOGIN_SUCCESS_ANDROID' = 'H5_LOGIN_SUCCESS_ANDROID'
+  'H5_LOGIN_SUCCESS_ANDROID' = 'H5_LOGIN_SUCCESS_ANDROID',
+  // 订阅相关消息常量
+  /**
+   * 用户发起订阅请求
+   */
+  'SUBSCRIPTION_REQUEST' = 'NATIVE_SUBSCRIPTION_REQUEST',
+  /**
+   * 恢复购买请求
+   */
+  'RESTORE_PURCHASE_REQUEST' = 'NATIVE_RESTORE_PURCHASE_REQUEST',
 }