diff --git a/app.json b/app.json
index 3b5b2de..426908f 100644
--- a/app.json
+++ b/app.json
@@ -46,7 +46,7 @@
"plugins": [
"expo-router",
"expo-secure-store",
- [
+ [
"expo-background-task",
{
"minimumInterval": 15
diff --git a/app/(tabs)/owner.tsx b/app/(tabs)/owner.tsx
index b1d34cc..3cc0eab 100644
--- a/app/(tabs)/owner.tsx
+++ b/app/(tabs)/owner.tsx
@@ -89,7 +89,7 @@ export default function OwnerPage() {
router.push({
pathname: '/rights',
- params: { credit: userInfoDetails?.remain_points }
+ params: { credit: userInfoDetails?.remain_points, pro: userInfoDetails?.membership_level }
})}
style={styles.resourceContainer}
>
diff --git a/app/(tabs)/rights.tsx b/app/(tabs)/rights.tsx
index f175b94..1fb9eda 100644
--- a/app/(tabs)/rights.tsx
+++ b/app/(tabs)/rights.tsx
@@ -1,28 +1,49 @@
import ReturnArrowSvg from '@/assets/icons/svg/returnArrow.svg';
import StarSvg from '@/assets/icons/svg/whiteStart.svg';
+import CheckSvg from '@/assets/icons/svg/yes.svg';
import PrivacyModal from '@/components/owner/qualification/privacy';
import Normal from '@/components/owner/rights/normal';
import Premium, { PayItem } from '@/components/owner/rights/premium';
import ProRights from '@/components/owner/rights/proRights';
-import { maxDiscountProduct } from '@/components/owner/rights/utils';
+import { createOrder, createPayment, getPAy, isOrderExpired, payFailure, payProcessing, paySuccess } from '@/components/owner/rights/utils';
import { ThemedText } from '@/components/ThemedText';
-import { fetchApi } from '@/lib/server-api-util';
+import { CreateOrder } from '@/types/personal-info';
+import { ErrorCode, getAvailablePurchases, getPurchaseHistories, ProductPurchase, useIAP } from 'expo-iap';
import { useLocalSearchParams, useRouter } from "expo-router";
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { Image, ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
+import { ActivityIndicator, Image, ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function Rights() {
const insets = useSafeAreaInsets();
const router = useRouter();
const { t } = useTranslation();
+ const {
+ connected,
+ products,
+ subscriptions,
+ currentPurchase,
+ currentPurchaseError,
+ requestProducts,
+ requestPurchase,
+ finishTransaction,
+ validateReceipt,
+ } = useIAP();
+ // 用户勾选协议
+ const [agree, setAgree] = useState(false);
+ // 用户选择购买的loading
+ const [confirmLoading, setConfirmLoading] = useState(false);
+ // 选择购买方式
+ const [payChoice, setPayChoice] = useState<'ApplePay'>('ApplePay');
// 获取路由参数
- const { credit } = useLocalSearchParams<{
+ const { credit, pro } = useLocalSearchParams<{
credit: string;
+ pro: string;
}>();
// 普通用户,会员
const [userType, setUserType] = useState<'normal' | 'premium'>('normal');
+
// 选择权益方式
const [payType, setPayType] = useState('');
@@ -32,26 +53,154 @@ export default function Rights() {
// 调接口获取支付信息
const [premiumPay, setPremiumPay] = useState();
const [loading, setLoading] = useState(false);
- const getPAy = async () => {
- setLoading(true);
- const payInfo = await fetchApi(`/order/product-items?product_type=Membership`)
- let bestValue = maxDiscountProduct(payInfo)
- setPayType(bestValue?.product_code)
- setPremiumPay([bestValue, ...payInfo?.filter((item) => item.product_code !== bestValue?.product_code)]);
- setLoading(false);
- }
+
+ // 查看历史订单
+ const fetchPurchaseHistory = async () => {
+ try {
+ const purchaseHistories = await getPurchaseHistories();
+ console.log('Purchase history fetched:', purchaseHistories);
+ return purchaseHistories
+ } catch (error) {
+ console.error('Failed to fetch purchase history:', error);
+ }
+ };
+
+ // 恢复购买
+ const restorePurchases = async () => {
+ try {
+ const purchases = await getAvailablePurchases();
+ console.log('Available purchases:', purchases);
+ // Process and validate restored purchases
+ for (const purchase of purchases) {
+ await validateAndGrantPurchase(purchase);
+ }
+ alert(t('personal:rights.restoreSuccess'));
+ } catch (error) {
+ console.error('Restore failed:', error);
+ }
+ };
+
+ // 处理购买
+ const handlePurchase = async (sku: string, transaction_id: string) => {
+ try {
+ // 支付中
+ await payProcessing(transaction_id, "")
+ const res = await requestPurchase({
+ request: {
+ ios: {
+ sku: payType,
+ andDangerouslyFinishTransactionAutomaticallyIOS: false,
+ },
+ },
+ });
+ console.log('Purchase success:', res);
+
+ // 支付成功
+ await paySuccess(transaction_id, res?.transactionId || "")
+ } catch (error: any) {
+ console.log('Purchase failed:', error);
+ // 支付失败
+ payFailure(transaction_id, ErrorCode[error?.code as keyof typeof ErrorCode || "E_UNKNOWN"])
+ }
+ };
+
+ // 获取苹果订单信息
useEffect(() => {
- getPAy();
+ if (!connected) return;
+
+ const initializeStore = async () => {
+ try {
+ await requestProducts({ skus: ["MEMBERSHIP_PRO_QUARTERLY", "MEMBERSHIP_PRO_YEARLY", "MEMBERSHIP_PRO_MONTH"], type: 'subs' });
+ } catch (error) {
+ console.error('Failed to initialize store:', error);
+ }
+ };
+
+ initializeStore();
+ }, [connected]);
+
+ // 初始化获取产品项
+ useEffect(() => {
+ setLoading(true);
+ getPAy().then(({ bestValue, payInfo }) => {
+ setPayType(bestValue?.product_code)
+ setPremiumPay([bestValue, ...payInfo?.filter((item) => item.product_code !== bestValue?.product_code)]);
+ setLoading(false);
+ }).catch(() => {
+ setLoading(false);
+ })
}, []);
+ // 用户确认购买时,进行 创建订单,创建支付 接口调用
+ const confirmPurchase = async () => {
+ if (!agree) {
+ alert(t('personal:rights.agreementError'));
+ return
+ }
+ setConfirmLoading(true);
+ const history = await fetchPurchaseHistory()
+ const historyIds = history?.filter((item: any) => isOrderExpired(item?.expirationDateIos))?.map((i) => { return i?.id })
+ if (historyIds?.includes(payType)) {
+ setConfirmLoading(false);
+ setTimeout(() => {
+ alert(t('personal:rights.againError'));
+ }, 0);
+ return
+ }
+
+ try {
+ // 创建订单
+ createOrder(premiumPay?.filter((item) => item.product_code === payType)?.[0]?.id || 1, 1).then((res: CreateOrder) => {
+ // 创建支付
+ createPayment(res?.id || "", payChoice).then(async (res) => {
+ // 苹果支付
+ await handlePurchase(payType, res?.transaction_id || "")
+ setConfirmLoading(false);
+ }).catch((err) => {
+ console.log("createPayment", err);
+ setConfirmLoading(false);
+ })
+ }).catch((err) => {
+ console.log("createOrder", err);
+ setConfirmLoading(false);
+ })
+ } catch (error) {
+ console.log("confirmPurchase", error);
+ setConfirmLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ if (pro === "Pro") {
+ setUserType('premium')
+ } else {
+ setUserType('normal')
+ }
+ }, [pro])
+
+ useEffect(() => {
+ fetchPurchaseHistory()
+ }, [])
+
return (
+ {/* 整个页面的中间添加一个loading */}
+ {confirmLoading && (
+
+
+
+
+ {t('personal:rights.confirmLoading')}
+
+
+
+ )}
{/* 导航栏 */}
- { router.push('/owner') }} style={{ padding: 16 }}>
+ { router.push('/owner'); setConfirmLoading(false) }} style={{ padding: 16 }}>
@@ -99,8 +248,10 @@ export default function Rights() {
{/* 普通权益 */}
{/* 会员权益 */}
-
+
+ {/* 支付方式 */}
+ {/* */}
{/* 会员权益信息 */}
@@ -109,6 +260,7 @@ export default function Rights() {
{/* 付费按钮 */}
+
+ { setAgree(!agree) }} activeOpacity={0.8}>
+
+ {agree && }
+
+
+
+
+ {t('personal:rights.agreement')}
+
+ {
+ setShowTerms(true);
+ }}
+ activeOpacity={0.8}
+ >
+
+ {t('personal:rights.membership')}
+
+
+
+
{
- setUserType('premium');
+ confirmPurchase()
}}
activeOpacity={0.8}
>
@@ -129,24 +303,62 @@ export default function Rights() {
{t('rights.subscribe', { ns: 'personal' })}
- {
- setShowTerms(true);
- }}
- activeOpacity={0.8}
- >
-
- {t('rights.terms', { ns: 'personal' })}
-
-
+
{/* 协议弹窗 */}
-
+
);
}
const styles = StyleSheet.create({
+ agree: {
+ width: 15,
+ height: 15,
+ borderRadius: 15,
+ alignItems: 'center',
+ justifyContent: 'center',
+ borderColor: '#AC7E35',
+ borderWidth: 1,
+ },
+ loadingContent: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: '#fff',
+ borderRadius: 12,
+ padding: 16,
+ },
+ loadingContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ bottom: 0,
+ zIndex: 9,
+ backgroundColor: 'rgba(255, 255, 255, 0.5)',
+ },
+ payChoice: {
+ width: 20,
+ height: 20,
+ borderRadius: 15,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ paymentMethod: {
+ marginHorizontal: 16,
+ marginVertical: 16,
+ borderRadius: 12,
+ backgroundColor: '#fff',
+ shadowColor: '#000',
+ shadowOffset: {
+ width: 0,
+ height: 2,
+ },
+ shadowOpacity: 0.25,
+ shadowRadius: 5,
+ elevation: 5,
+ },
goPay: {
backgroundColor: '#E2793F',
borderRadius: 24,
@@ -243,3 +455,6 @@ const styles = StyleSheet.create({
lineHeight: 32
}
});
+function validateAndGrantPurchase(purchase: ProductPurchase) {
+ throw new Error('Function not implemented.');
+}
diff --git a/assets/icons/svg/choicePay.svg b/assets/icons/svg/choicePay.svg
new file mode 100644
index 0000000..cd6758a
--- /dev/null
+++ b/assets/icons/svg/choicePay.svg
@@ -0,0 +1,3 @@
+
diff --git a/components/owner/qualification/privacy.tsx b/components/owner/qualification/privacy.tsx
index 3540069..1d95722 100644
--- a/components/owner/qualification/privacy.tsx
+++ b/components/owner/qualification/privacy.tsx
@@ -3,10 +3,25 @@ import { Policy } from '@/types/personal-info';
import React, { useEffect, useState } from 'react';
import { Modal, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import RenderHtml from 'react-native-render-html';
+import { useSafeAreaInsets } from "react-native-safe-area-context";
-const PrivacyModal = (props: { modalVisible: boolean, setModalVisible: (visible: boolean) => void, type: string }) => {
- const { modalVisible, setModalVisible, type } = props;
+interface PrivacyModalProps {
+ modalVisible: boolean;
+ setModalVisible: (visible: boolean) => void;
+ type: 'ai' | 'terms' | 'privacy' | 'user' | 'membership';
+}
+
+const titleMap = {
+ ai: 'AI Policy',
+ terms: 'Terms of Service',
+ privacy: 'Privacy Policy',
+ user: 'User Agreement',
+ membership: 'Membership Agreement'
+};
+
+const PrivacyModal = ({ modalVisible, setModalVisible, type }: PrivacyModalProps) => {
const [article, setArticle] = useState({} as Policy);
+ const insets = useSafeAreaInsets();
useEffect(() => {
const loadArticle = async () => {
// ai协议
@@ -41,6 +56,14 @@ const PrivacyModal = (props: { modalVisible: boolean, setModalVisible: (visible:
console.log(error)
})
}
+ // 会员协议
+ if (type === 'membership') {
+ fetchApi(`/system-config/policy/membership_agreement`).then((res: any) => {
+ setArticle(res)
+ }).catch((error: any) => {
+ console.log(error)
+ })
+ }
};
if (type) {
loadArticle();
@@ -63,11 +86,13 @@ const PrivacyModal = (props: { modalVisible: boolean, setModalVisible: (visible:
onRequestClose={() => {
setModalVisible(!modalVisible);
}}>
-
+
Settings
- {type === 'ai' ? 'AI Policy' : type === 'terms' ? 'Terms of Service' : type === 'privacy' ? 'Privacy Policy' : 'User Agreement'}
+
+ {titleMap[type] || 'User Agreement'}
+
setModalVisible(false)}>
×
diff --git a/components/owner/rights/payType.tsx b/components/owner/rights/payType.tsx
new file mode 100644
index 0000000..3aa909f
--- /dev/null
+++ b/components/owner/rights/payType.tsx
@@ -0,0 +1,156 @@
+import ChoicePaySvg from '@/assets/icons/svg/choicePay.svg';
+import YesSvg from '@/assets/icons/svg/yes.svg';
+import { ThemedText } from '@/components/ThemedText';
+import React, { useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
+
+interface Props {
+ modalVisible: boolean;
+ setModalVisible: (visible: boolean) => void;
+ payChoice: 'ApplePay';
+ setPayChoice: (choice: 'ApplePay') => void;
+ setConfirmPay: (confirm: boolean) => void;
+}
+
+const PayTypeModal = (props: Props) => {
+ const { modalVisible, setModalVisible, payChoice, setPayChoice, setConfirmPay } = props;
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ if (modalVisible) {
+ setConfirmPay(false)
+ }
+ }, [modalVisible]);
+
+ return (
+ {
+ setModalVisible(!modalVisible);
+ }}>
+
+
+
+ {t('personal:rights.payType')}
+ {t('personal:rights.payType')}
+ setModalVisible(false)}>
+ ×
+
+
+
+ { setPayChoice('ApplePay') }}
+ >
+
+
+
+ {t('personal:rights.apple')}
+
+
+
+ {payChoice === 'ApplePay' ? : null}
+
+
+
+
+ {
+ setConfirmPay(true);
+ setModalVisible(false)
+ }}
+ >
+
+ {t('personal:rights.confirm')}
+
+
+ { setModalVisible(false) }}
+ >
+
+ {t('personal:rights.cancel')}
+
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ modalTitle: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: '#4C320C',
+ },
+ footer: {
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'flex-end',
+ alignItems: 'center',
+ gap: 16,
+ marginBottom: 32,
+ },
+ button: {
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ borderRadius: 12,
+ alignItems: 'center',
+ },
+ payChoice: {
+ width: 20,
+ height: 20,
+ borderRadius: 15,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ paymentMethod: {
+ marginHorizontal: 16,
+ marginVertical: 16,
+ borderRadius: 12,
+ backgroundColor: '#fff',
+ shadowColor: '#000',
+ shadowOffset: {
+ width: 0,
+ height: 2,
+ },
+ shadowOpacity: 0.25,
+ shadowRadius: 5,
+ elevation: 5,
+ },
+ centeredView: {
+ flex: 1,
+ justifyContent: 'flex-end',
+ backgroundColor: 'rgba(0,0,0,0.5)',
+ },
+ modalView: {
+ width: '100%',
+ backgroundColor: 'white',
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ paddingHorizontal: 16,
+ },
+ modalHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 20,
+ },
+ modalTitle: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: '#4C320C',
+ },
+ closeButton: {
+ fontSize: 28,
+ color: '#4C320C',
+ padding: 10,
+ }
+});
+export default PayTypeModal;
\ No newline at end of file
diff --git a/components/owner/rights/premium.tsx b/components/owner/rights/premium.tsx
index 634f993..2a64dcc 100644
--- a/components/owner/rights/premium.tsx
+++ b/components/owner/rights/premium.tsx
@@ -11,6 +11,7 @@ interface Props {
premiumPay: any;
loading: boolean;
setShowTerms: (visible: boolean) => void;
+ restorePurchases: () => void;
}
export interface PayItem {
@@ -30,7 +31,7 @@ export interface PayItem {
}
const Premium = (props: Props) => {
- const { style, payType, setPayType, premiumPay, loading, setShowTerms } = props;
+ const { style, payType, setPayType, premiumPay, loading, setShowTerms, restorePurchases } = props;
const bestValue = maxDiscountProduct(premiumPay)?.product_code
const { t } = useTranslation();
@@ -53,6 +54,7 @@ const Premium = (props: Props) => {
onPress={async () => {
setPayType(item?.product_code);
}}
+ key={item?.product_code}
style={[styles.yearly, { borderColor: payType === item?.product_code ? '#FFB645' : '#E1E1E1', opacity: payType === item?.product_code ? 1 : 0.5 }]}
activeOpacity={0.8}
>
@@ -67,24 +69,26 @@ const Premium = (props: Props) => {
{item.product_code?.split('_')[item.product_code?.split('_')?.length - 1]}
- $ {item.unit_price.amount}
+ $ {(Number(item.unit_price.amount) - Number(item.discount_amount.amount)).toFixed(2)}
- $ {item.discount_amount.amount}
+ $ {item.unit_price.amount}
})
}
-
+
- {t('rights.cancelAnytimeBeforeRenewal', { ns: 'personal' })}
-
- setShowTerms(true)}>
- {t('rights.terms', { ns: 'personal' })}
+ {t('personal:rights.restorePurchase')}
+
+
+ {t('personal:rights.restore')}
+
+
);
diff --git a/components/owner/rights/utils.ts b/components/owner/rights/utils.ts
index b7e16c8..475475b 100644
--- a/components/owner/rights/utils.ts
+++ b/components/owner/rights/utils.ts
@@ -1,12 +1,100 @@
+import { fetchApi } from "@/lib/server-api-util";
+import { CreateOrder, PayOrder } from "@/types/personal-info";
import { PayItem } from "./premium";
// 使用 reduce 方法获取 discount_amount 的 amount 值最大的对象
export const maxDiscountProduct = (products: PayItem[]) => {
+ if (!products || products.length === 0) {
+ return products?.[0];
+ }
return products?.reduce((max, current) => {
- // 将 amount 转换为数字进行比较
- const maxAmount = parseFloat(max.discount_amount.amount);
- const currentAmount = parseFloat(current.discount_amount.amount);
+ const maxAmount = parseFloat(max.discount_amount?.amount || '0');
+ const currentAmount = parseFloat(current.discount_amount?.amount || '0');
return currentAmount > maxAmount ? current : max;
});
-}
\ No newline at end of file
+}
+
+
+// 查看产品项
+export const getPAy = async () => {
+ const payInfo = await fetchApi(`/order/product-items?product_type=Membership`)
+ let bestValue = maxDiscountProduct(payInfo)
+ return { bestValue, payInfo }
+}
+
+// 创建订单
+export const createOrder = async (id: number, quantity: number) => {
+ const order = await fetchApi(`/order/create`, {
+ method: 'POST',
+ body: JSON.stringify({
+ items: [{
+ product_item_id: id,
+ quantity: quantity
+ }]
+ })
+ })
+ return order
+}
+
+// 创建支付
+export const createPayment = async (order_id: string, payment_method: string) => {
+ const payment = await fetchApi(`/order/pay`, {
+ method: 'POST',
+ body: JSON.stringify({
+ order_id,
+ payment_method
+ })
+ })
+ return payment
+}
+
+// 支付中
+export const payProcessing = async (transaction_id: string, third_party_transaction_id: string) => {
+ const payment = await fetchApi(`/order/pay-processing`, {
+ method: 'POST',
+ body: JSON.stringify({
+ transaction_id
+ })
+ })
+ return payment
+}
+
+// 支付失败
+export const payFailure = async (transaction_id: string, reason: string) => {
+ const payment = await fetchApi(`/order/pay-failure`, {
+ method: 'POST',
+ body: JSON.stringify({
+ transaction_id,
+ reason
+ })
+ })
+ return payment
+}
+
+// 支付成功
+export const paySuccess = async (transaction_id: string, third_party_transaction_id: string) => {
+ const payment = await fetchApi(`/order/pay-success`, {
+ method: 'POST',
+ body: JSON.stringify({
+ transaction_id,
+ third_party_transaction_id
+ })
+ })
+ return payment
+}
+
+// 判断订单是否过期
+/**
+* 判断指定时间戳是否已过期(即当前时间是否已超过该时间戳)
+* @param expirationTimestamp - 过期时间戳(单位:毫秒)
+* @returns boolean - true: 未过期,false: 已过期
+*/
+export const isOrderExpired = async (transactionDate: number) => {
+ // 如果没有提供过期时间,视为无效或未设置,认为“已过期”或状态未知
+ if (!transactionDate || isNaN(transactionDate)) {
+ return false;
+ }
+ const now = Date.now(); // 当前时间戳(毫秒)
+ return now < transactionDate;
+}
diff --git a/i18n/locales/en/personal.json b/i18n/locales/en/personal.json
index d1f4bd4..62cb0cc 100644
--- a/i18n/locales/en/personal.json
+++ b/i18n/locales/en/personal.json
@@ -109,6 +109,19 @@
"bonus": "Enjoy 100 Bonus Credits Every Month",
"bonusText": "Generate more memory pictures & videos and explore your past.",
"storage": "10GB of Cloud Storage",
- "storageText": "Safely store your cherished photos, videos, and generated memories."
+ "storageText": "Safely store your cherished photos, videos, and generated memories.",
+ "weChatPay": "WeChat",
+ "apple": "Apple Pay",
+ "confirm": "Confirm",
+ "cancel": "Cancel",
+ "confirmLoading": "Confirming...",
+ "againError": "You have already purchased this benefit, no need to purchase again",
+ "payType": "Pay Type",
+ "restoreSuccess": "Restore purchase successfully",
+ "restore": "restore purchase",
+ "restorePurchase": "Membership purchased but not active,please try to",
+ "agreement": "I have read and agree to",
+ "membership": "《Membership Agreement》",
+ "agreementError": "Please read and agree to the agreement"
}
}
\ No newline at end of file
diff --git a/i18n/locales/zh/personal.json b/i18n/locales/zh/personal.json
index c2c740e..6ba9bab 100644
--- a/i18n/locales/zh/personal.json
+++ b/i18n/locales/zh/personal.json
@@ -109,6 +109,19 @@
"bonus": "每月享受100积分",
"bonusText": "生成更多记忆照片和视频,探索你的过去。",
"storage": "10GB的云存储",
- "storageText": "安全存储你的珍贵照片、视频和生成的记忆。"
+ "storageText": "安全存储你的珍贵照片、视频和生成的记忆。",
+ "weChatPay": "微信支付",
+ "apple": "苹果支付",
+ "confirm": "确认",
+ "cancel": "取消",
+ "confirmLoading": "正在购买...",
+ "againError": "您已购买过该权益,无需重复购买",
+ "payType": "支付方式",
+ "restoreSuccess": "恢复购买成功",
+ "restore": "恢复购买",
+ "restorePurchase": "已购买会员,但未生效,请尝试",
+ "agreement": "我已阅读并同意",
+ "membership": "《会员协议》",
+ "agreementError": "请先阅读并同意协议"
}
}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 21a7ef9..09db5d3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,6 +19,7 @@
"expo-audio": "~0.4.8",
"expo-background-task": "^0.2.8",
"expo-blur": "~14.1.5",
+ "expo-build-properties": "^0.14.8",
"expo-clipboard": "~7.1.5",
"expo-constants": "~17.1.6",
"expo-dev-client": "~5.2.4",
@@ -26,6 +27,7 @@
"expo-file-system": "~18.1.10",
"expo-font": "~13.3.1",
"expo-haptics": "~14.1.4",
+ "expo-iap": "^2.7.5",
"expo-image-manipulator": "~13.1.7",
"expo-image-picker": "~16.1.4",
"expo-linear-gradient": "~14.1.5",
@@ -8710,6 +8712,53 @@
"react-native": "*"
}
},
+ "node_modules/expo-build-properties": {
+ "version": "0.14.8",
+ "resolved": "https://registry.npmjs.org/expo-build-properties/-/expo-build-properties-0.14.8.tgz",
+ "integrity": "sha512-GTFNZc5HaCS9RmCi6HspCe2+isleuOWt2jh7UEKHTDQ9tdvzkIoWc7U6bQO9lH3Mefk4/BcCUZD/utl7b1wdqw==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.11.0",
+ "semver": "^7.6.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-build-properties/node_modules/ajv": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/expo-build-properties/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/expo-build-properties/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/expo-clipboard": {
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-7.1.5.tgz",
@@ -8879,6 +8928,17 @@
"expo": "*"
}
},
+ "node_modules/expo-iap": {
+ "version": "2.7.5",
+ "resolved": "https://registry.npmjs.org/expo-iap/-/expo-iap-2.7.5.tgz",
+ "integrity": "sha512-+UMLBXKtyoVsfJMQxqGLv4qXeMZzFoOoDMRVJa8OYngDCqfIADkpyNb28HKNZdYiaa0Yq5LHYu42zNkD2m2w0Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": "*",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/expo-image-loader": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-5.1.0.tgz",
diff --git a/package.json b/package.json
index f0c441a..788a6a7 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"expo-audio": "~0.4.8",
"expo-background-task": "^0.2.8",
"expo-blur": "~14.1.5",
+ "expo-build-properties": "^0.14.8",
"expo-clipboard": "~7.1.5",
"expo-constants": "~17.1.6",
"expo-dev-client": "~5.2.4",
@@ -32,6 +33,7 @@
"expo-file-system": "~18.1.10",
"expo-font": "~13.3.1",
"expo-haptics": "~14.1.4",
+ "expo-iap": "^2.7.5",
"expo-image-manipulator": "~13.1.7",
"expo-image-picker": "~16.1.4",
"expo-linear-gradient": "~14.1.5",
diff --git a/types/personal-info.ts b/types/personal-info.ts
index d1e77e0..317d5d2 100644
--- a/types/personal-info.ts
+++ b/types/personal-info.ts
@@ -51,4 +51,49 @@ export interface Policy {
content: string,
created_at: string,
updated_at: string
-}
\ No newline at end of file
+}
+
+
+// 订单
+export interface Amount {
+ amount: string;
+ currency: string;
+}
+
+export interface OrderItem {
+ discount_amount: Amount;
+ id: string;
+ product_code: string;
+ product_id: number;
+ product_name: string;
+ product_type: string;
+ quantity: number;
+ total_price: Amount;
+ unit_price: Amount;
+}
+
+// 订单
+export interface CreateOrder {
+ created_at: string;
+ expired_at: string;
+ id: string;
+ items: OrderItem[];
+ payment_info: any | null; // 使用 any 或更具体的类型
+ status: string;
+ total_amount: Amount;
+ updated_at: string;
+ user_id: string;
+}
+
+// 创建订单
+export interface PayOrder {
+ created_at: string;
+ id: string;
+ paid_at: string;
+ payment_amount: Amount;
+ payment_method: string;
+ payment_status: string;
+ third_party_transaction_id: string;
+ transaction_id: string;
+ updated_at: string;
+}