فهرست منبع

feat: 应用内购开发

victor.zhou 4 ماه پیش
والد
کامیت
bd11507fda
2فایلهای تغییر یافته به همراه308 افزوده شده و 5 حذف شده
  1. 304 4
      src/App.tsx
  2. 4 1
      src/README.md

+ 304 - 4
src/App.tsx

@@ -1,6 +1,6 @@
 import 'react-native-gesture-handler';
 
-import React, { createContext, useContext, useRef, useState } from 'react';
+import React, { createContext, useContext, useRef, useState, useEffect } from 'react';
 import { Platform, StyleSheet, View, Alert } from 'react-native';
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 import { GestureHandlerRootView } from 'react-native-gesture-handler';
@@ -18,8 +18,9 @@ import { ThemeProvider } from '@/theme';
 import '@/translations';
 
 import * as RNIap from 'react-native-iap';
+import type { Purchase, PurchaseError } from 'react-native-iap';
 
-const itemSubs = ['com.recipemuse.vip.monthly']; // 你的订阅ID
+const itemSubs = ['com.recipemuse.vip.monthly']; // 月度会员订阅ID
 
 
 // 在 DEFAULT_URL 常量下方添加以下常量
@@ -71,13 +72,16 @@ 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>, message: any) => {
+  const sendMessageToWebView = (webViewRef: React.RefObject<WebView | null>, message: any) => {
     if (webViewRef.current) {
       const messageString = typeof message === 'string' ? message : JSON.stringify(message);
       // 使用更可靠的通信方式
@@ -90,7 +94,7 @@ function App() {
    */
   const handleWebViewMessage = (
     event: any,
-    webViewRef: React.RefObject<WebView>,
+    webViewRef: React.RefObject<WebView | null>,
   ) => {
     try {
       const data = event.nativeEvent.data;
@@ -220,8 +224,304 @@ function App() {
     }
   };
 
+  /**
+   * 初始化IAP
+   */
+  useEffect(() => {
+    initIAP();
+    return () => {
+      // 清理IAP连接
+      RNIap.endConnection();
+    };
+  }, []);
+
+  /**
+   * 初始化应用内购买
+   */
+  const initIAP = async () => {
+    try {
+      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) {
+      console.error('IAP初始化失败:', error);
+      Alert.alert('初始化失败', '无法连接到应用商店');
+    }
+  };
+
+  /**
+   * 验证收据(示例实现,实际应该发送到后端服务器)
+   */
+  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 || '请稍后重试');
+      }
+    }
+  };
+
+  /**
+   * 恢复购买
+   */
+  const restorePurchases = async () => {
+    try {
+      // iOS调用系统恢复购买API
+      if (Platform.OS === 'ios') {
+        await RNIap.restorePurchases();
+        console.log('iOS恢复购买请求已发送');
+      }
+      
+      // 获取可用购买记录
+      const availablePurchases = await RNIap.getAvailablePurchases();
+      console.log('可恢复的购买:', availablePurchases);
+
+      if (availablePurchases.length === 0) {
+        Alert.alert('提示', '没有找到可恢复的购买记录');
+        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: '订阅已恢复',
+        });
+      } else {
+        Alert.alert('提示', '没有找到会员订阅记录');
+      }
+    } catch (error: any) {
+      console.error('恢复购买失败:', error);
+      Alert.alert('恢复失败', error.message || '无法恢复购买记录');
+    }
+  };
+
+  /**
+   * 检查订阅状态
+   */
+  const checkSubscriptionStatus = async () => {
+    try {
+      const availablePurchases = await RNIap.getAvailablePurchases();
+      console.log('所有可用购买记录:', availablePurchases);
+      
+      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;
+    } catch (error) {
+      console.error('检查订阅状态失败:', error);
+      setIsSubscribed(false);
+      return false;
+    }
   };
 
   return (

+ 4 - 1
src/README.md

@@ -26,4 +26,7 @@ https://developer.apple.com/account
 
 apple 沙盒测试用户
 mealmuse@gmail.com
-Abc753951
+Abc753951
+
+
+[在React Native应用中实现Android内购功能](https://juejin.cn/post/7342323580788703266?searchId=202511121454080F98B5820B69EFEA1FDB)