diff --git a/app/(tabs)/rights.tsx b/app/(tabs)/rights.tsx index 3472bde..b40a201 100644 --- a/app/(tabs)/rights.tsx +++ b/app/(tabs)/rights.tsx @@ -1,15 +1,14 @@ -import ChoicePaySvg from '@/assets/icons/svg/choicePay.svg'; import ReturnArrowSvg from '@/assets/icons/svg/returnArrow.svg'; import StarSvg from '@/assets/icons/svg/whiteStart.svg'; -import YesSvg from '@/assets/icons/svg/yes.svg'; import PrivacyModal from '@/components/owner/qualification/privacy'; import Normal from '@/components/owner/rights/normal'; +import PayTypeModal from '@/components/owner/rights/payType'; 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, payFailure, paySuccess } from '@/components/owner/rights/utils'; import { ThemedText } from '@/components/ThemedText'; -import { fetchApi } from '@/lib/server-api-util'; -import { useIAP } from 'expo-iap'; +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'; @@ -31,9 +30,10 @@ export default function Rights() { finishTransaction, validateReceipt, } = useIAP(); - + // 购买方式弹窗 + const [showPayType, setShowPayType] = useState(false); // 选择购买方式 - const [payChoice, setPayChoice] = useState<'weChatPay' | 'apple'>('weChatPay'); + const [payChoice, setPayChoice] = useState<'ApplePay'>('ApplePay'); // 获取路由参数 const { credit } = useLocalSearchParams<{ credit: string; @@ -50,25 +50,39 @@ 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); - } - useEffect(() => { - getPAy(); - }, []); - // 处理购买 - const handlePurchase = async (productId: string) => { - console.log(productId); + // 确认支付 + const [confirmPay, setConfirmPay] = useState(false); + // 查看历史订单 + const fetchPurchaseHistory = async () => { try { - // Platform-specific purchase requests (v2.7.0+) - await requestPurchase({ + const purchaseHistories = await getPurchaseHistories(); + console.log('Purchase history fetched:', 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); + } + } catch (error) { + console.error('Restore failed:', error); + } + }; + + // 处理购买 + const handlePurchase = async (sku: string, transaction_id: string) => { + console.log('handlePurchase', sku); + try { + const res = await requestPurchase({ request: { ios: { sku: "MEMBERSHIP_PRO_QUARTERLY", @@ -76,11 +90,16 @@ export default function Rights() { }, }, }); - } catch (error) { - alert(productId) - console.error('Purchase failed:', error); + // 支付成功 + paySuccess(transaction_id, res?.transaction_id || "") + } catch (error: any) { + console.log('Purchase failed:', error); + // 支付失败 + payFailure(transaction_id, ErrorCode[error?.code as keyof typeof ErrorCode || "E_UNKNOWN"]) } }; + + // 获取苹果订单信息 useEffect(() => { console.log('connected', connected); @@ -89,16 +108,59 @@ export default function Rights() { const initializeStore = async () => { try { await requestProducts({ skus: ["MEMBERSHIP_PRO_QUARTERLY"], type: 'subs' }); - // await getSubscriptions(["MEMBERSHIP_PRO_QUARTERLY"]) - console.log("products", products); + console.log("subscriptions", subscriptions); } catch (error) { console.error('Failed to initialize store:', error); } }; initializeStore(); - }, [connected, premiumPay]); + }, [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); + }) + }, []); + + // 用户确认购买时,进行 创建订单,创建支付 接口调用 + useEffect(() => { + console.log('confirmPay', confirmPay); + if (confirmPay) { + // 创建订单 + createOrder(premiumPay?.filter((item) => item.product_code === payType)?.[0]?.id || 1, 1).then((res: CreateOrder) => { + // 创建支付 + createPayment(res?.id || "", payChoice).then((res) => { + console.log("createPayment", res); + console.log("payType", payType); + // 苹果支付 + handlePurchase(payType, res?.transaction_id || "") + }).catch((err) => { + console.log("createPayment", err); + }) + }).catch((err) => { + console.log("createOrder", err); + }) + } + }, [confirmPay]); + + useEffect(() => { + if (currentPurchase) { + console.log('currentPurchase', currentPurchase); + } + }, [currentPurchase]); + + useEffect(() => { + + console.log('currentPurchaseError', currentPurchaseError); + + }, [currentPurchaseError]); return ( @@ -157,34 +219,7 @@ export default function Rights() { {/* 支付方式 */} - - { setPayChoice('weChatPay') }} - style={{ padding: 16, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', borderBottomWidth: 1, borderBottomColor: '#FFD38D' }} - > - - - - {t('personal:rights.weChatPay')} - - - {payChoice === 'weChatPay' ? : null} - - { setPayChoice('apple') }} - > - - - - {t('personal:rights.apple')} - - - - {payChoice === 'apple' ? : null} - - - + {/* 会员权益信息 */} @@ -207,7 +242,8 @@ export default function Rights() { style={styles.goPay} onPress={async () => { setUserType('premium'); - handlePurchase(payType); + // handlePurchase(payType); + setShowPayType(true) }} activeOpacity={0.8} > @@ -350,3 +386,7 @@ const styles = StyleSheet.create({ lineHeight: 32 } }); +function validateAndGrantPurchase(purchase: ProductPurchase) { + throw new Error('Function not implemented.'); +} + diff --git a/components/owner/rights/payType.tsx b/components/owner/rights/payType.tsx new file mode 100644 index 0000000..2d6948f --- /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); + }}> + + + + 支付方式 + 支付方式 + 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/utils.ts b/components/owner/rights/utils.ts index b7e16c8..a82aac7 100644 --- a/components/owner/rights/utils.ts +++ b/components/owner/rights/utils.ts @@ -1,12 +1,86 @@ +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; }); +} + + +// 查看产品项 +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, + third_party_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 } \ No newline at end of file diff --git a/i18n/locales/en/personal.json b/i18n/locales/en/personal.json index 34235b7..a31d2c5 100644 --- a/i18n/locales/en/personal.json +++ b/i18n/locales/en/personal.json @@ -111,6 +111,8 @@ "storage": "10GB of Cloud Storage", "storageText": "Safely store your cherished photos, videos, and generated memories.", "weChatPay": "WeChat", - "apple": "Apple Pay" + "apple": "Apple Pay", + "confirm": "Confirm", + "cancel": "Cancel" } } \ No newline at end of file diff --git a/i18n/locales/zh/personal.json b/i18n/locales/zh/personal.json index 77295a4..cafcf38 100644 --- a/i18n/locales/zh/personal.json +++ b/i18n/locales/zh/personal.json @@ -111,6 +111,8 @@ "storage": "10GB的云存储", "storageText": "安全存储你的珍贵照片、视频和生成的记忆。", "weChatPay": "微信支付", - "apple": "苹果支付" + "apple": "苹果支付", + "confirm": "确认", + "cancel": "取消" } } \ No newline at end of file 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; +}