-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: billing #636
feat: billing #636
Changes from 12 commits
3cb38fd
7505bfd
69eeaa4
1e49929
1a4956e
dbb064e
adba6ac
a472a6c
afd0ab7
485d700
443d217
b5b0da7
9d95156
44d0ed6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import { View, StyleSheet } from 'react-native'; | ||
import React, { useState } from 'react'; | ||
import { Text, Button, ActivityIndicator } from 'react-native-paper'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { Image } from 'expo-image'; | ||
import Purchases from 'react-native-purchases'; | ||
|
||
import useVIP, { checkGuardVIP, purchaseVIP } from '@hooks/useVIP'; | ||
import { styles } from '../style'; | ||
import logger from '@utils/Logger'; | ||
|
||
interface LoadingChildrenProps { | ||
setLoading: React.Dispatch<React.SetStateAction<boolean>>; | ||
} | ||
interface LoadingWrapperProps { | ||
Child: (p: LoadingChildrenProps) => JSX.Element; | ||
} | ||
|
||
const LoadingIconWrapper = ({ Child }: LoadingWrapperProps) => { | ||
const [loading, setLoading] = useState(false); | ||
if (loading) { | ||
return <ActivityIndicator />; | ||
} | ||
return <Child setLoading={setLoading} />; | ||
}; | ||
|
||
const BiliVIP = ({ setLoading }: LoadingChildrenProps) => { | ||
const { t } = useTranslation(); | ||
|
||
const checkBiliVIP = async () => { | ||
setLoading(true); | ||
await checkGuardVIP(); | ||
setLoading(false); | ||
}; | ||
|
||
return <Button onPress={checkBiliVIP}>{t('Billing.NoxAuth')}</Button>; | ||
}; | ||
|
||
const RevenueCatVIP = ({ setLoading }: LoadingChildrenProps) => { | ||
const { t } = useTranslation(); | ||
const checkRevenueCatVIP = async () => { | ||
setLoading(true); | ||
try { | ||
const offerings = await Purchases.getOfferings(); | ||
if ( | ||
offerings.current !== null && | ||
offerings.current.availablePackages.length !== 0 | ||
) { | ||
const { customerInfo } = await Purchases.purchasePackage( | ||
offerings.current.availablePackages[0], | ||
); | ||
if ( | ||
typeof customerInfo.entitlements.active['apm-pro'] !== 'undefined' | ||
) { | ||
purchaseVIP(); | ||
} | ||
} | ||
} catch (e) { | ||
logger.error(JSON.stringify(e)); | ||
} | ||
setLoading(false); | ||
}; | ||
|
||
return ( | ||
<Button onPress={checkRevenueCatVIP}>{t('Billing.RevenueCat')}</Button> | ||
); | ||
}; | ||
Comment on lines
+39
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve RevenueCat purchase flow implementation. Several improvements can be made to enhance code quality and user experience:
+const VIP_ENTITLEMENT_ID = 'apm-pro';
+
const RevenueCatVIP = ({ setLoading }: LoadingChildrenProps) => {
const { t } = useTranslation();
+
+ const hasAvailableOfferings = (offerings: Purchases.Offerings) =>
+ offerings.current?.availablePackages.length > 0;
+
const checkRevenueCatVIP = async () => {
setLoading(true);
try {
const offerings = await Purchases.getOfferings();
- if (
- offerings.current !== null &&
- offerings.current.availablePackages.length !== 0
- ) {
- const { customerInfo } = await Purchases.purchasePackage(
- offerings.current.availablePackages[0],
- );
- if (
- typeof customerInfo.entitlements.active['apm-pro'] !== 'undefined'
- ) {
- purchaseVIP();
- }
+ if (!hasAvailableOfferings(offerings)) {
+ throw new Error(t('Billing.NoOfferings'));
}
+
+ const { customerInfo } = await Purchases.purchasePackage(
+ offerings.current!.availablePackages[0],
+ );
+
+ if (customerInfo.entitlements.active[VIP_ENTITLEMENT_ID]) {
+ await purchaseVIP();
+ // Show success message
+ }
} catch (e) {
logger.error(JSON.stringify(e));
+ // Show error message to user
+ Alert.alert(t('Billing.PurchaseError'), e.message);
+ } finally {
+ setLoading(false);
}
- setLoading(false);
};
|
||
|
||
const PurchaseVIPScreen = () => { | ||
const { t } = useTranslation(); | ||
return ( | ||
<View style={mStyle.container}> | ||
<Image | ||
style={mStyle.azusaBeg} | ||
source={{ | ||
uri: 'https://img.nga.178.com/attachments/mon_202201/31/-zue37Q2p-ixpkXsZ7tT3cS9y-af.gif', | ||
}} | ||
/> | ||
<Text>{t('Billing.PremiumFeaturesIntro')}</Text> | ||
<View style={styles.alignMiddle}> | ||
<LoadingIconWrapper Child={BiliVIP} /> | ||
<LoadingIconWrapper Child={RevenueCatVIP} /> | ||
</View> | ||
<Text>{t('Billing.NoxFans')}</Text> | ||
</View> | ||
); | ||
}; | ||
Comment on lines
+69
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Move hardcoded URLs to configuration and add image error handling. The hardcoded image URL should be moved to a configuration file, and error handling should be added for image loading. +// Add to a config file
+export const IMAGES = {
+ AZUSA_BEG: 'https://img.nga.178.com/attachments/mon_202201/31/-zue37Q2p-ixpkXsZ7tT3cS9y-af.gif'
+};
+
const PurchaseVIPScreen = () => {
const { t } = useTranslation();
+ const [imageError, setImageError] = useState(false);
+
return (
<View style={mStyle.container}>
<Image
style={mStyle.azusaBeg}
source={{
- uri: 'https://img.nga.178.com/attachments/mon_202201/31/-zue37Q2p-ixpkXsZ7tT3cS9y-af.gif',
+ uri: IMAGES.AZUSA_BEG,
}}
+ onError={() => setImageError(true)}
/>
+ {imageError && (
+ <Text style={styles.errorText}>{t('Common.ImageLoadError')}</Text>
+ )}
|
||
|
||
const VIPScreen = () => { | ||
const { t } = useTranslation(); | ||
|
||
return ( | ||
<View style={mStyle.container}> | ||
<Image | ||
style={mStyle.azusaMock} | ||
source={{ | ||
uri: 'https://img.nga.178.com/attachments/mon_202202/01/-zue37Q2p-6rfwK2dT1kShs-b4.jpg', | ||
}} | ||
/> | ||
<Text style={styles.centerText}>{t('Billing.thankU')}</Text> | ||
<Text style={styles.centerText}>{t('Billing.godBlessU')}</Text> | ||
<Text style={styles.centerText}>{t('Billing.urVeryVeryGorgeous')}</Text> | ||
</View> | ||
); | ||
}; | ||
|
||
export default () => { | ||
const vip = useVIP(state => state.VIP); | ||
|
||
if (vip) { | ||
return <VIPScreen />; | ||
} | ||
return <PurchaseVIPScreen />; | ||
}; | ||
|
||
const mStyle = StyleSheet.create({ | ||
container: { flex: 1, paddingHorizontal: 10, paddingTop: 10 }, | ||
text: { | ||
fontSize: 20, | ||
}, | ||
azusaBeg: { | ||
width: '100%', | ||
height: '40%', | ||
alignSelf: 'center', | ||
}, | ||
azusaMock: { | ||
width: '100%', | ||
height: '30%', | ||
alignSelf: 'center', | ||
}, | ||
}); |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling and user feedback for VIP check.
The current implementation lacks error handling and doesn't provide feedback to users about the success or failure of the VIP check operation.