feat: push
This commit is contained in:
parent
9c5d7a53a0
commit
1d9f80f975
@ -20,6 +20,18 @@ export default function Rights() {
|
|||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const {
|
||||||
|
connected,
|
||||||
|
products,
|
||||||
|
subscriptions,
|
||||||
|
currentPurchase,
|
||||||
|
currentPurchaseError,
|
||||||
|
requestProducts,
|
||||||
|
requestPurchase,
|
||||||
|
finishTransaction,
|
||||||
|
validateReceipt,
|
||||||
|
} = useIAP();
|
||||||
|
|
||||||
// 选择购买方式
|
// 选择购买方式
|
||||||
const [payChoice, setPayChoice] = useState<'weChatPay' | 'apple'>('weChatPay');
|
const [payChoice, setPayChoice] = useState<'weChatPay' | 'apple'>('weChatPay');
|
||||||
// 获取路由参数
|
// 获取路由参数
|
||||||
@ -49,16 +61,44 @@ export default function Rights() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getPAy();
|
getPAy();
|
||||||
}, []);
|
}, []);
|
||||||
|
// 处理购买
|
||||||
|
|
||||||
|
const handlePurchase = async (productId: string) => {
|
||||||
|
console.log(productId);
|
||||||
|
|
||||||
function TestComponent() {
|
try {
|
||||||
const { connected } = useIAP();
|
// Platform-specific purchase requests (v2.7.0+)
|
||||||
|
await requestPurchase({
|
||||||
|
request: {
|
||||||
|
ios: {
|
||||||
|
sku: "MEMBERSHIP_PRO_QUARTERLY",
|
||||||
|
andDangerouslyFinishTransactionAutomaticallyIOS: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
alert(productId)
|
||||||
|
console.error('Purchase failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('connected', connected);
|
||||||
|
|
||||||
console.log('IAP Connection status:', connected);
|
if (!connected) return;
|
||||||
|
|
||||||
|
const initializeStore = async () => {
|
||||||
|
try {
|
||||||
|
await requestProducts({ skus: ["MEMBERSHIP_PRO_QUARTERLY"], type: 'subs' });
|
||||||
|
// await getSubscriptions(["MEMBERSHIP_PRO_QUARTERLY"])
|
||||||
|
console.log("products", products);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize store:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initializeStore();
|
||||||
|
}, [connected, premiumPay]);
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
TestComponent()
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<ScrollView style={[styles.container, { paddingTop: insets.top, paddingBottom: insets.bottom + 80 }]}>
|
<ScrollView style={[styles.container, { paddingTop: insets.top, paddingBottom: insets.bottom + 80 }]}>
|
||||||
@ -149,8 +189,7 @@ export default function Rights() {
|
|||||||
<View style={{ flex: 1, marginBottom: 80 }}>
|
<View style={{ flex: 1, marginBottom: 80 }}>
|
||||||
<ProRights style={{ display: userType === 'normal' ? "none" : "flex" }} />
|
<ProRights style={{ display: userType === 'normal' ? "none" : "flex" }} />
|
||||||
</View>
|
</View>
|
||||||
|
{/* <ApplePay /> */}
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
{/* 付费按钮 */}
|
{/* 付费按钮 */}
|
||||||
<View style={{
|
<View style={{
|
||||||
@ -168,6 +207,7 @@ export default function Rights() {
|
|||||||
style={styles.goPay}
|
style={styles.goPay}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
setUserType('premium');
|
setUserType('premium');
|
||||||
|
handlePurchase(payType);
|
||||||
}}
|
}}
|
||||||
activeOpacity={0.8}
|
activeOpacity={0.8}
|
||||||
>
|
>
|
||||||
|
|||||||
252
components/owner/rights/apple.tsx
Normal file
252
components/owner/rights/apple.tsx
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
import { ThemedText } from '@/components/ThemedText';
|
||||||
|
import { useIAP } from 'expo-iap';
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { Alert, InteractionManager, Platform, View } from 'react-native';
|
||||||
|
|
||||||
|
// Define your product SKUs
|
||||||
|
const bulbPackSkus = ['dev.hyo.martie.10bulbs', 'dev.hyo.martie.30bulbs'];
|
||||||
|
const subscriptionSkus = ['dev.hyo.martie.premium'];
|
||||||
|
|
||||||
|
export default function PurchaseScreen() {
|
||||||
|
const {
|
||||||
|
connected,
|
||||||
|
products,
|
||||||
|
subscriptions,
|
||||||
|
currentPurchase,
|
||||||
|
currentPurchaseError,
|
||||||
|
requestProducts,
|
||||||
|
requestPurchase,
|
||||||
|
finishTransaction,
|
||||||
|
validateReceipt,
|
||||||
|
} = useIAP();
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isReady, setIsReady] = useState(false);
|
||||||
|
|
||||||
|
// Initialize products when IAP connection is established
|
||||||
|
useEffect(() => {
|
||||||
|
if (!connected) return;
|
||||||
|
|
||||||
|
const initializeIAP = async () => {
|
||||||
|
try {
|
||||||
|
// Get both products and subscriptions
|
||||||
|
await requestProducts({ skus: bulbPackSkus, type: 'inapp' });
|
||||||
|
await requestProducts({ skus: subscriptionSkus, type: 'subs' });
|
||||||
|
setIsReady(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing IAP:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initializeIAP();
|
||||||
|
}, [connected, requestProducts]);
|
||||||
|
|
||||||
|
// Validate receipt helper
|
||||||
|
const handleValidateReceipt = useCallback(
|
||||||
|
async (sku: string, purchase: any) => {
|
||||||
|
try {
|
||||||
|
if (Platform.OS === 'ios') {
|
||||||
|
return await validateReceipt(sku);
|
||||||
|
} else if (Platform.OS === 'android') {
|
||||||
|
const purchaseToken = purchase.purchaseTokenAndroid;
|
||||||
|
const packageName =
|
||||||
|
purchase.packageNameAndroid || 'your.package.name';
|
||||||
|
const isSub = subscriptionSkus.includes(sku);
|
||||||
|
|
||||||
|
return await validateReceipt(sku, {
|
||||||
|
packageName,
|
||||||
|
productToken: purchaseToken,
|
||||||
|
isSub,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { isValid: true }; // Default for unsupported platforms
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Receipt validation failed:', error);
|
||||||
|
return { isValid: false };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[validateReceipt],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle successful purchases
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentPurchase) {
|
||||||
|
handlePurchaseUpdate(currentPurchase);
|
||||||
|
}
|
||||||
|
}, [currentPurchase]);
|
||||||
|
|
||||||
|
// Handle purchase errors
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentPurchaseError) {
|
||||||
|
setIsLoading(false);
|
||||||
|
|
||||||
|
// Don't show error for user cancellation
|
||||||
|
if (currentPurchaseError.code === 'E_USER_CANCELLED') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Alert.alert(
|
||||||
|
'Purchase Error',
|
||||||
|
'Failed to complete purchase. Please try again.',
|
||||||
|
);
|
||||||
|
console.error('Purchase error:', currentPurchaseError);
|
||||||
|
}
|
||||||
|
}, [currentPurchaseError]);
|
||||||
|
|
||||||
|
const handlePurchaseUpdate = async (purchase: any) => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
console.log('Processing purchase:', purchase);
|
||||||
|
|
||||||
|
const productId = purchase.id;
|
||||||
|
|
||||||
|
// Validate receipt on your server
|
||||||
|
const validationResult = await handleValidateReceipt(productId, purchase);
|
||||||
|
|
||||||
|
if (validationResult.isValid) {
|
||||||
|
// Determine if this is a consumable product
|
||||||
|
const isConsumable = bulbPackSkus.includes(productId);
|
||||||
|
|
||||||
|
// Finish the transaction
|
||||||
|
await finishTransaction({
|
||||||
|
purchase,
|
||||||
|
isConsumable, // Set to true for consumable products
|
||||||
|
});
|
||||||
|
|
||||||
|
// Record purchase in your database
|
||||||
|
await recordPurchaseInDatabase(purchase, productId);
|
||||||
|
|
||||||
|
// Update local state (e.g., add bulbs, enable premium features)
|
||||||
|
await updateLocalState(productId);
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
showSuccessMessage(productId);
|
||||||
|
} else {
|
||||||
|
Alert.alert(
|
||||||
|
'Validation Error',
|
||||||
|
'Purchase could not be validated. Please contact support.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error handling purchase:', error);
|
||||||
|
Alert.alert('Error', 'Failed to process purchase.');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Request purchase for products
|
||||||
|
const handlePurchaseBulbs = async (productId: string) => {
|
||||||
|
if (!connected) {
|
||||||
|
Alert.alert(
|
||||||
|
'Not Connected',
|
||||||
|
'Store connection unavailable. Please try again later.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
// Platform-specific purchase request (v2.7.0+)
|
||||||
|
await requestPurchase({
|
||||||
|
request: {
|
||||||
|
ios: {
|
||||||
|
sku: productId,
|
||||||
|
andDangerouslyFinishTransactionAutomaticallyIOS: false,
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
skus: [productId],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
setIsLoading(false);
|
||||||
|
console.error('Purchase request failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Request purchase for subscriptions
|
||||||
|
const handlePurchaseSubscription = async (subscriptionId: string) => {
|
||||||
|
if (!connected) {
|
||||||
|
Alert.alert(
|
||||||
|
'Not Connected',
|
||||||
|
'Store connection unavailable. Please try again later.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
// Find subscription to get offer details
|
||||||
|
const subscription = subscriptions.find((s) => s.id === subscriptionId);
|
||||||
|
const subscriptionOffers = subscription?.subscriptionOfferDetails?.map(
|
||||||
|
(offer) => ({
|
||||||
|
sku: subscriptionId,
|
||||||
|
offerToken: offer.offerToken,
|
||||||
|
}),
|
||||||
|
) || [{ sku: subscriptionId, offerToken: '' }];
|
||||||
|
|
||||||
|
// Platform-specific subscription request (v2.7.0+)
|
||||||
|
await requestPurchase({
|
||||||
|
request: {
|
||||||
|
ios: {
|
||||||
|
sku: subscriptionId,
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
skus: [subscriptionId],
|
||||||
|
subscriptionOffers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: 'subs',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
setIsLoading(false);
|
||||||
|
console.error('Subscription request failed:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const recordPurchaseInDatabase = async (purchase: any, productId: string) => {
|
||||||
|
// Implement your database recording logic here
|
||||||
|
console.log('Recording purchase in database:', { purchase, productId });
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLocalState = async (productId: string) => {
|
||||||
|
// Update your local app state based on the purchase
|
||||||
|
if (bulbPackSkus.includes(productId)) {
|
||||||
|
// Add bulbs to user's account
|
||||||
|
const bulbCount = productId.includes('10bulbs') ? 10 : 30;
|
||||||
|
console.log(`Adding ${bulbCount} bulbs to user account`);
|
||||||
|
} else if (subscriptionSkus.includes(productId)) {
|
||||||
|
// Enable premium features
|
||||||
|
console.log('Enabling premium features');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const showSuccessMessage = (productId: string) => {
|
||||||
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
if (bulbPackSkus.includes(productId)) {
|
||||||
|
const bulbCount = productId.includes('10bulbs') ? 10 : 30;
|
||||||
|
Alert.alert(
|
||||||
|
'Thank You!',
|
||||||
|
`${bulbCount} bulbs have been added to your account.`,
|
||||||
|
);
|
||||||
|
} else if (subscriptionSkus.includes(productId)) {
|
||||||
|
Alert.alert(
|
||||||
|
'Thank You!',
|
||||||
|
'Premium subscription activated successfully.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
{/* Your purchase UI components */}
|
||||||
|
<ThemedText>Connection Status: {connected ? 'Connected' : 'Disconnected'}</ThemedText>
|
||||||
|
<ThemedText>Products Ready: {isReady ? 'Yes' : 'No'}</ThemedText>
|
||||||
|
{/* Add your purchase buttons and UI here */}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -67,11 +67,11 @@ const Premium = (props: Props) => {
|
|||||||
{item.product_code?.split('_')[item.product_code?.split('_')?.length - 1]}
|
{item.product_code?.split('_')[item.product_code?.split('_')?.length - 1]}
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
<ThemedText style={[styles.titleText, { fontSize: 32, lineHeight: 32 }]}>
|
<ThemedText style={[styles.titleText, { fontSize: 32, lineHeight: 32 }]}>
|
||||||
$ {item.unit_price.amount}
|
$ {(Number(item.unit_price.amount) - Number(item.discount_amount.amount)).toFixed(2)}
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
<ThemedText style={[styles.titleText, { fontSize: 12, color: "#AC7E35", textDecorationLine: 'line-through' }]}>
|
<ThemedText style={[styles.titleText, { fontSize: 12, color: "#AC7E35", textDecorationLine: 'line-through' }]}>
|
||||||
$ {item.discount_amount.amount}
|
$ {item.unit_price.amount}
|
||||||
</ThemedText>
|
</ThemedText>
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user