|
@@ -19,14 +19,14 @@ import '@/translations';
|
|
|
|
|
|
|
|
import * as RNIap from 'react-native-iap';
|
|
import * as RNIap from 'react-native-iap';
|
|
|
import type { Purchase, PurchaseError } from 'react-native-iap';
|
|
import type { Purchase, PurchaseError } from 'react-native-iap';
|
|
|
|
|
+import { eventTypeEnum } from './utils/h5';
|
|
|
|
|
|
|
|
const itemSubs = ['com.recipemuse.vip.monthly']; // 月度会员订阅ID
|
|
const itemSubs = ['com.recipemuse.vip.monthly']; // 月度会员订阅ID
|
|
|
|
|
|
|
|
|
|
|
|
|
-// 在 DEFAULT_URL 常量下方添加以下常量
|
|
|
|
|
-const MESSAGE_PREFIX = 'NATIVE_LOGIN_';
|
|
|
|
|
-const LOGIN_SUCCESS = `${MESSAGE_PREFIX}SUCCESS`;
|
|
|
|
|
-const LOGIN_REQUEST = `${MESSAGE_PREFIX}REQUEST`;
|
|
|
|
|
|
|
+// 订阅相关消息常量
|
|
|
|
|
+const SUBSCRIPTION_REQUEST = 'NATIVE_SUBSCRIPTION_REQUEST';
|
|
|
|
|
+const RESTORE_PURCHASE_REQUEST = 'NATIVE_RESTORE_PURCHASE_REQUEST';
|
|
|
|
|
|
|
|
export const queryClient = new QueryClient({
|
|
export const queryClient = new QueryClient({
|
|
|
defaultOptions: {
|
|
defaultOptions: {
|
|
@@ -61,7 +61,8 @@ export const useWebViewContext = () => {
|
|
|
// 配置常量
|
|
// 配置常量
|
|
|
// const DEFAULT_URL = 'http://localhost:5173';
|
|
// const DEFAULT_URL = 'http://localhost:5173';
|
|
|
|
|
|
|
|
-const DEFAULT_URL = 'http://10.13.51.57:5173';
|
|
|
|
|
|
|
+const DEFAULT_URL = 'http://10.15.49.43:5173';
|
|
|
|
|
+
|
|
|
|
|
|
|
|
// WebView用户代理
|
|
// WebView用户代理
|
|
|
const USER_AGENT = Platform.select({
|
|
const USER_AGENT = Platform.select({
|
|
@@ -97,27 +98,43 @@ function App() {
|
|
|
webViewRef: React.RefObject<WebView | null>,
|
|
webViewRef: React.RefObject<WebView | null>,
|
|
|
) => {
|
|
) => {
|
|
|
try {
|
|
try {
|
|
|
- const data = event.nativeEvent.data;
|
|
|
|
|
|
|
+ const messageData = event.nativeEvent.data;
|
|
|
|
|
+ const { type, data } = messageData ? JSON.parse(messageData) : {};
|
|
|
|
|
+ console.log('Received message:', type, data);
|
|
|
|
|
+
|
|
|
// 检查是否为登录请求
|
|
// 检查是否为登录请求
|
|
|
- if (data === LOGIN_REQUEST) {
|
|
|
|
|
|
|
+ if (type === eventTypeEnum.NATIVE_LOGIN_REQUEST) {
|
|
|
// 延迟执行登录以确保 WebView 已完全显示
|
|
// 延迟执行登录以确保 WebView 已完全显示
|
|
|
handleLogin()
|
|
handleLogin()
|
|
|
- .then(() => {
|
|
|
|
|
- // 登录成功后发送成功消息到 WebView
|
|
|
|
|
- sendMessageToWebView(webViewRef, {
|
|
|
|
|
- type: LOGIN_SUCCESS,
|
|
|
|
|
- status: 'success',
|
|
|
|
|
- message: '登录成功'
|
|
|
|
|
- });
|
|
|
|
|
- })
|
|
|
|
|
- .catch((error) => {
|
|
|
|
|
- // 登录失败时发送失败消息到 WebView
|
|
|
|
|
- sendMessageToWebView(webViewRef, {
|
|
|
|
|
- type: `${MESSAGE_PREFIX}ERROR`,
|
|
|
|
|
- status: 'error',
|
|
|
|
|
- message: error.message || '登录失败'
|
|
|
|
|
- });
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ } else if (type === SUBSCRIPTION_REQUEST) {
|
|
|
|
|
+ // 检查是否为订阅请求
|
|
|
|
|
+ console.log('收到订阅请求');
|
|
|
|
|
+ handleInAppPurchase()
|
|
|
|
|
+ .then(() => {
|
|
|
|
|
+ console.log('订阅流程已启动');
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((error) => {
|
|
|
|
|
+ console.error('启动订阅失败:', error);
|
|
|
|
|
+ sendMessageToWebView(webViewRef, {
|
|
|
|
|
+ type: 'SUBSCRIPTION_ERROR',
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ // 检查是否为恢复购买请求
|
|
|
|
|
+ else if (type === RESTORE_PURCHASE_REQUEST) {
|
|
|
|
|
+ console.log('收到恢复购买请求');
|
|
|
|
|
+ restorePurchases()
|
|
|
|
|
+ .then(() => {
|
|
|
|
|
+ console.log('恢复购买流程已完成');
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch((error) => {
|
|
|
|
|
+ console.error('恢复购买失败:', error);
|
|
|
|
|
+ sendMessageToWebView(webViewRef, {
|
|
|
|
|
+ type: 'RESTORE_PURCHASE_ERROR',
|
|
|
|
|
+ status: 'error',
|
|
|
|
|
+ message: error.message || '恢复购买失败'
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('处理 WebView 消息时出错:', error);
|
|
console.error('处理 WebView 消息时出错:', error);
|
|
@@ -130,16 +147,14 @@ function App() {
|
|
|
console.log('设备ID:', deviceId);
|
|
console.log('设备ID:', deviceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
- let reqData = null
|
|
|
|
|
if (Platform.OS === 'ios') {
|
|
if (Platform.OS === 'ios') {
|
|
|
// iOS使用Apple登录
|
|
// iOS使用Apple登录
|
|
|
- reqData = await handleAppleLogin();
|
|
|
|
|
|
|
+ await handleAppleLogin();
|
|
|
} else {
|
|
} else {
|
|
|
// Android使用Google登录
|
|
// Android使用Google登录
|
|
|
- reqData = await handleGoogleLogin();
|
|
|
|
|
|
|
+ await handleGoogleLogin();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- console.log('reqData 请求参数',reqData)
|
|
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('登录失败:', error);
|
|
console.error('登录失败:', error);
|
|
|
Alert.alert('登录失败', '请稍后重试');
|
|
Alert.alert('登录失败', '请稍后重试');
|
|
@@ -159,59 +174,74 @@ function App() {
|
|
|
const credentialState = await appleAuth.getCredentialStateForUser(
|
|
const credentialState = await appleAuth.getCredentialStateForUser(
|
|
|
appleAuthRequestResponse.user,
|
|
appleAuthRequestResponse.user,
|
|
|
);
|
|
);
|
|
|
-
|
|
|
|
|
- if (credentialState === appleAuth.State.AUTHORIZED) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ console.log('凭证状态:', credentialState, '(0=REVOKED, 1=AUTHORIZED, 2=NOT_FOUND)');
|
|
|
|
|
+
|
|
|
|
|
+ // 如果performRequest成功返回,说明登录已完成
|
|
|
|
|
+ // 模拟器中credentialState可能为0(REVOKED),但不影响登录流程
|
|
|
|
|
+ console.log('appleAuthRequestResponse.user',appleAuthRequestResponse.user)
|
|
|
|
|
+ debugger
|
|
|
|
|
+ if (appleAuthRequestResponse.user) {
|
|
|
|
|
+ debugger
|
|
|
console.log('Apple登录成功:', appleAuthRequestResponse);
|
|
console.log('Apple登录成功:', appleAuthRequestResponse);
|
|
|
- // Alert.alert('登录成功', `欢迎 ${appleAuthRequestResponse.fullName?.givenName || '用户'}`);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 仅在真机且状态为REVOKED时警告
|
|
|
|
|
+ if (credentialState === appleAuth.State.REVOKED && !__DEV__) {
|
|
|
|
|
+ console.warn('⚠️ 凭证状态为REVOKED,但登录已完成');
|
|
|
|
|
+ }
|
|
|
sendMessageToWebView(webViewRef, {
|
|
sendMessageToWebView(webViewRef, {
|
|
|
- type: 'LOGIN_SUCCESS_IOS',
|
|
|
|
|
- status: 'success',
|
|
|
|
|
|
|
+ type: eventTypeEnum.H5_LOGIN_SUCCESS_IOS,
|
|
|
message: '登录成功',
|
|
message: '登录成功',
|
|
|
data: appleAuthRequestResponse
|
|
data: appleAuthRequestResponse
|
|
|
})
|
|
})
|
|
|
- // return appleAuthRequestResponse
|
|
|
|
|
- // appleAuthRequestResponse : {
|
|
|
|
|
- // user: '001746.b066d194b82f46eaac8f3f26b9a915fc.0901',
|
|
|
|
|
- // realUserStatus: 2,
|
|
|
|
|
- // authorizedScopes: [],
|
|
|
|
|
- // identityToken: 'eyJraWQiOiJIdlZJNkVzWlhKIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmlwZWFraW5nLnJlY2lwZW11c2UiLCJleHAiOjE3NjIzMzMyNzcsImlhdCI6MTc2MjI0Njg3Nywic3ViIjoiMDAxNzQ2LmIwNjZkMTk0YjgyZjQ2ZWFhYzhmM2YyNmI5YTkxNWZjLjA5MDEiLCJub25jZSI6IjYyYTliNmQzMWI4M2EyMjdhYzA3MjI2ZDZiMmQxYTc1MTRhZTRiZjZjMjhlMjJjMmU4OTkwNTkzYTVkYzE5N2MiLCJjX2hhc2giOiIyZ0hUN1M1NHBWUExfeW52aHVrRGF3IiwiZW1haWwiOiIyNzYyMDM4NzZAcXEuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF1dGhfdGltZSI6MTc2MjI0Njg3Nywibm9uY2Vfc3VwcG9ydGVkIjp0cnVlLCJyZWFsX3VzZXJfc3RhdHVzIjoyfQ.Tt2X4fBYYY5xMKSDZW8DoLsmDFzVw4AaTkMVdvruKgEOlYnBmW5MJAgCIz8iqMSFwq1008YOcpcdnAAd1J43J9HdDV-Rpg_ryEP76ph0MiynbWPPPMexz3ndxe4_ZS3s7BZdweWJuwJ9x6SqoFl01tEqs8jGEaJ13Z3byYZgsbzQxtqLzof4W1RcjLGrgj_xagjqimbex3PAgD7bIh2SUBJbhNjIK_RcNPNLQTQ4EGFwNmfG22sFUFqY5W6ipyZvk6gXGegXgnTYZpeAS1bc8UL4KBz4T4zRQMkN3uJezPD6A_IfJKSkHmwsXgPYfLNhScuO2wSBc4Q-UvuS_Y3nhg',
|
|
|
|
|
- // authorizationCode: 'c9da4feae2b0248bea11861ca60088eb1.0.rrxuw.hLfeCQRQVY12lVyUyHIbtw',
|
|
|
|
|
- // fullName: {
|
|
|
|
|
- // namePrefix: null,
|
|
|
|
|
- // givenName: '凯',
|
|
|
|
|
- // nameSuffix: null,
|
|
|
|
|
- // middleName: null,
|
|
|
|
|
- // familyName: '周',
|
|
|
|
|
- // nickname: null
|
|
|
|
|
- // },
|
|
|
|
|
- // email: '276203876@qq.com',
|
|
|
|
|
- // state: null,
|
|
|
|
|
- // nonce: '202K2RJL4_8SJmIS9AThnL6PGbMGzAz8'
|
|
|
|
|
- // }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log('未获取到用户信息');
|
|
|
|
|
+ // throw new Error('未获取到用户信息');
|
|
|
}
|
|
}
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error('Apple登录失败:', error);
|
|
console.error('Apple登录失败:', error);
|
|
|
- throw error;
|
|
|
|
|
|
|
+ // throw error;
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const handleGoogleLogin = async () => {
|
|
const handleGoogleLogin = async () => {
|
|
|
try {
|
|
try {
|
|
|
|
|
+ // 检查是否有Google Play Services
|
|
|
|
|
+ const hasPlayServices = await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: false });
|
|
|
|
|
+
|
|
|
|
|
+ if (!hasPlayServices) {
|
|
|
|
|
+ console.log('Google Play Services不可用,使用Mock数据');
|
|
|
|
|
+ // Mock登录数据(仅用于开发测试)
|
|
|
|
|
+ const mockUserInfo = {
|
|
|
|
|
+ data: {
|
|
|
|
|
+ user: {
|
|
|
|
|
+ id: 'mock_user_123',
|
|
|
|
|
+ name: '测试用户',
|
|
|
|
|
+ email: 'test@example.com',
|
|
|
|
|
+ photo: null,
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ Alert.alert('开发模式', '当前使用Mock登录数据(Google服务不可用)');
|
|
|
|
|
+ sendMessageToWebView(webViewRef, {
|
|
|
|
|
+ type: eventTypeEnum.H5_LOGIN_SUCCESS_ANDROID,
|
|
|
|
|
+ status: 'success',
|
|
|
|
|
+ message: '登录成功(Mock模式)',
|
|
|
|
|
+ data: mockUserInfo,
|
|
|
|
|
+ });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 配置Google登录
|
|
// 配置Google登录
|
|
|
await GoogleSignin.configure({
|
|
await GoogleSignin.configure({
|
|
|
webClientId: AUTH_CONFIG.GOOGLE.WEB_CLIENT_ID,
|
|
webClientId: AUTH_CONFIG.GOOGLE.WEB_CLIENT_ID,
|
|
|
offlineAccess: true,
|
|
offlineAccess: true,
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
- // 检查是否已登录
|
|
|
|
|
- await GoogleSignin.hasPlayServices();
|
|
|
|
|
|
|
|
|
|
// 执行登录
|
|
// 执行登录
|
|
|
const userInfo = await GoogleSignin.signIn();
|
|
const userInfo = await GoogleSignin.signIn();
|
|
|
console.log('Google登录成功:', userInfo);
|
|
console.log('Google登录成功:', userInfo);
|
|
|
Alert.alert('登录成功', `欢迎 ${userInfo.data?.user.name || '用户'}`);
|
|
Alert.alert('登录成功', `欢迎 ${userInfo.data?.user.name || '用户'}`);
|
|
|
- // return userInfo
|
|
|
|
|
sendMessageToWebView(webViewRef, {
|
|
sendMessageToWebView(webViewRef, {
|
|
|
type: 'LOGIN_SUCCESS_ANDROID',
|
|
type: 'LOGIN_SUCCESS_ANDROID',
|
|
|
status: 'success',
|
|
status: 'success',
|
|
@@ -227,21 +257,55 @@ function App() {
|
|
|
/**
|
|
/**
|
|
|
* 初始化IAP
|
|
* 初始化IAP
|
|
|
*/
|
|
*/
|
|
|
- useEffect(() => {
|
|
|
|
|
- initIAP();
|
|
|
|
|
- return () => {
|
|
|
|
|
- // 清理IAP连接
|
|
|
|
|
- RNIap.endConnection();
|
|
|
|
|
- };
|
|
|
|
|
- }, []);
|
|
|
|
|
|
|
+ // 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 () => {
|
|
const initIAP = async () => {
|
|
|
try {
|
|
try {
|
|
|
|
|
+ console.log('🚀 开始初始化IAP...');
|
|
|
|
|
+ console.log('📱 平台:', Platform.OS);
|
|
|
|
|
+
|
|
|
await RNIap.initConnection();
|
|
await RNIap.initConnection();
|
|
|
- console.log('IAP连接初始化成功');
|
|
|
|
|
|
|
+ console.log('✅ IAP连接初始化成功');
|
|
|
setIsIAPInitialized(true);
|
|
setIsIAPInitialized(true);
|
|
|
|
|
|
|
|
// 获取可用的订阅产品
|
|
// 获取可用的订阅产品
|
|
@@ -325,9 +389,33 @@ function App() {
|
|
|
purchaseUpdateSubscription.remove();
|
|
purchaseUpdateSubscription.remove();
|
|
|
purchaseErrorSubscription.remove();
|
|
purchaseErrorSubscription.remove();
|
|
|
};
|
|
};
|
|
|
- } catch (error) {
|
|
|
|
|
- console.error('IAP初始化失败:', error);
|
|
|
|
|
- Alert.alert('初始化失败', '无法连接到应用商店');
|
|
|
|
|
|
|
+ } 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: '知道了' }]
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|