From eba607cd10676251fdf5bb3f4c47490a7b281e11 Mon Sep 17 00:00:00 2001 From: yezian Date: Fri, 14 Jun 2024 18:12:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90iap?= =?UTF-8?q?=E5=92=8C=E9=87=91=E5=B8=81=E6=94=AF=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 4 +- components/CoinPayModal/index.jsx | 245 +++++++++++++++++++ components/SpacePost/index.jsx | 65 ++++- context/IapProvider.jsx | 16 ++ screeens/Login/index.jsx | 2 +- screeens/My/index.jsx | 16 +- screeens/RechargeGold/index.jsx | 25 ++ screeens/Setting/SelectSettingItem/index.jsx | 22 +- screeens/SpaceIntroduce/index.jsx | 32 ++- screeens/StreamerSpace/index.jsx | 124 ++++++++-- screeens/Wallet/index.jsx | 15 +- screeens/WebWithHeader/index.jsx | 67 ++++- screeens/WebWithoutHeader/index.jsx | 67 ++++- 13 files changed, 655 insertions(+), 45 deletions(-) create mode 100644 components/CoinPayModal/index.jsx create mode 100644 screeens/RechargeGold/index.jsx diff --git a/.env b/.env index 50b212f..0ddd27a 100644 --- a/.env +++ b/.env @@ -1,3 +1,5 @@ EXPO_PUBLIC_API_URL=https://api.tiefen.fun EXPO_PUBLIC_RSA_KEY=-----BEGIN PUBLIC KEY-----MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMXPIjKV6CMi5O9tIXJWNIfnqXjqOZ1KmRByRAP073DU+gzMLygzEsrztJzbz/K/Julkz6XhheZ8vdz+boAl1HsCAwEAAQ==-----END PUBLIC KEY----- -EXPO_PUBLIC_WEB_URL=https://tiefen.fun \ No newline at end of file +EXPO_PUBLIC_WEB_URL=https://tiefen.fun +EXPO_PUBLIC_RC_APPLE_KEY=appl_QkkgJKLkfdCfcwPvsncIdbwPwQk +SENTRY_AUTH_TOKEN=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 \ No newline at end of file diff --git a/components/CoinPayModal/index.jsx b/components/CoinPayModal/index.jsx new file mode 100644 index 0000000..fac3e4d --- /dev/null +++ b/components/CoinPayModal/index.jsx @@ -0,0 +1,245 @@ +import { View, Text, Modal, TouchableOpacity } from "react-native"; +import React, { useState, useEffect } from "react"; +import { useTailwind } from "tailwind-rn"; +import { Button } from "@rneui/themed"; +import Toast from "react-native-toast-message"; +import baseRequest from "../../utils/baseRequest"; +import { generateSignature } from "../../utils/crypto"; +import { get } from "../../utils/storeInfo"; +import { useNavigation } from "@react-navigation/native"; + +export default function CoinPayModal({ + visible, + setVisible, + url, + body = {}, + product, + coinPrice, + validity, + info, + onPurchaseDone = () => {}, +}) { + const tailwind = useTailwind(); + const navigation = useNavigation(); + const apiUrl = process.env.EXPO_PUBLIC_API_URL; + + //用户金币余额 + const [coinBalance, setCoinBalance] = useState(); + useEffect(() => { + if (!visible) { + setIsCoinEnough(true); + setIsWaitingConfirm(false); + return; + } + const init = async () => { + const base = await baseRequest(); + const account = await get("account"); + const body = { + ...base, + mid: account.mid, + }; + const signature = await generateSignature(body); + const _response = await fetch( + `${apiUrl}/api/account/list_by_mid?signature=${signature}`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + } + ); + const _data = await _response.json(); + if (_data.ret === -1) { + Toast.show({ + type: "error", + text1: _data.msg, + topOffset: 60, + }); + return; + } + setCoinBalance(_data.data.account.gold_num); + }; + init(); + }, [visible]); + + //判断余额是否充足 + const [isCoinEnough, setIsCoinEnough] = useState(true); + const [isWaitingConfirm, setIsWaitingConfirm] = useState(false); + const checkCoinBalance = async () => { + if (coinBalance < coinPrice) { + setIsCoinEnough(false); + return; + } + setIsWaitingConfirm(true); + }; + + //进行金币支付 + const handleCoinPay = async () => { + try { + const base = await baseRequest(); + const _body = { + ...base, + ...body, + pay_type: "coin", + from: "app", + }; + const signature = await generateSignature(_body); + const _response = await fetch(`${apiUrl + url}?signature=${signature}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(_body), + }); + const _data = await _response.json(); + if (_data.ret === -1) { + Toast.show({ + type: "error", + text1: _data.msg, + topOffset: 60, + }); + return; + } + setVisible(false); + onPurchaseDone(); + } catch (error) { + console.error(error); + } + }; + + return ( + + setVisible(false)} + style={{ + backgroundColor: "#00000080", + ...tailwind("flex-1 justify-center items-center"), + }} + > + + + {isCoinEnough ? ( + isWaitingConfirm ? ( + <> + + 本次消费将扣除您{coinPrice}金币,是否确认购买? + + + 当前余额:{coinBalance}金币 + + + ) : ( + <> + + 项目:{product} + + + 价格:{coinPrice}金币 + + + ( 1 RMB = 10 金币 ) + + {validity && ( + + 有效期:{validity} + + )} + {info && ( + + {info} + + )} + + ) + ) : ( + <> + + 余额不足,请先充值金币 + + + 当前余额:{coinBalance}金币 + + + )} + {!isWaitingConfirm && isCoinEnough && ( + + )} + {isWaitingConfirm && ( + + )} + {!isCoinEnough && ( + + )} + setVisible(false)} + style={tailwind("mt-4")} + > + + 取消 + + + + + + + ); +} diff --git a/components/SpacePost/index.jsx b/components/SpacePost/index.jsx index 11b6657..00b1576 100644 --- a/components/SpacePost/index.jsx +++ b/components/SpacePost/index.jsx @@ -22,6 +22,7 @@ import { Icon } from "@rneui/themed"; import ParsedText from "react-native-parsed-text"; import * as Linking from "expo-linking"; import { useImageViewer } from "../../context/ImageViewProvider"; +import CoinPayModal from "../CoinPayModal"; //todo:完善视频逻辑;完善图片模糊逻辑 const blurhash = "LcKUTa%gOYWBYRt6xuoJo~s8V@fk"; @@ -34,6 +35,9 @@ export default function SpacePost({ data }) { ); const [showVideo, setShowVideo] = useState(false); + //金币支付Modal是否展示 + const [isCoinPayModalVisible, setIsCoinPayModalVisible] = useState(false); + //是否查看全文 const [isFullTextBtnShow, setIsFullTextBtnShow] = useState(false); const [isTextCollapsed, setIsTextCollapsed] = useState(true); @@ -301,7 +305,11 @@ export default function SpacePost({ data }) { {data.c_type === 1 && data.is_zone_moment_unlocked === 0 && ( + onPress={() => { + if (Platform.OS === "ios") { + setIsCoinPayModalVisible(true); + return; + } navigation.navigate("WebWithoutHeader", { uri: process.env.EXPO_PUBLIC_WEB_URL + @@ -309,8 +317,8 @@ export default function SpacePost({ data }) { data?.zid + "/h5_zone_moment/" + data?.id, - }) - } + }); + }} style={{ backgroundColor: data.is_ironfan_visible === 1 ? "#301024" : "#331F0B", @@ -335,7 +343,9 @@ export default function SpacePost({ data }) { ...tailwind("text-base font-semibold ml-1"), }} > - {data.price / 100} + {Platform.OS === "ios" + ? data.coin_price + : data.price / 100} - 元 + {Platform.OS === "ios" ? "金币" : "元"} @@ -373,7 +383,11 @@ export default function SpacePost({ data }) { {data.is_ironfan_visible === 1 && ( - 空间内任意消费满{data.ironfanship_price / 100}元即可成为铁粉 + 空间内任意消费满 + {Platform.OS === "ios" + ? data.ironfanship_coin_price + "金币" + : data.ironfanship_price / 100 + "元"} + 即可成为铁粉 )} @@ -412,7 +426,9 @@ export default function SpacePost({ data }) { ...tailwind("text-base font-semibold ml-1"), }} > - {data.price / 100} + {Platform.OS === "ios" + ? data.coin_price + : data.price / 100} - 元 + {Platform.OS === "ios" ? "金币" : "元"} @@ -494,7 +510,9 @@ export default function SpacePost({ data }) { ...tailwind("text-base font-semibold ml-1"), }} > - {data.price / 100} + {Platform.OS === "ios" + ? data.coin_price + : data.price / 100} - 元 + {Platform.OS === "ios" ? "金币" : "元"} @@ -528,7 +546,11 @@ export default function SpacePost({ data }) { {data.is_ironfan_visible === 1 && ( - 空间内任意消费满{data.ironfanship_price / 100}元即可成为铁粉 + 空间内任意消费满 + {Platform.OS === "ios" + ? data.ironfanship_coin_price + "金币" + : data.ironfanship_price / 100 + "元"} + 即可成为铁粉 )} @@ -585,6 +607,27 @@ export default function SpacePost({ data }) { + {/* 金币支付Modal */} + + Toast.show({ + type: "success", + text1: "购买完成,请刷新当前页面查看", + topOffset: 60, + }) + } + /> ); } diff --git a/context/IapProvider.jsx b/context/IapProvider.jsx index 84ce877..735629a 100644 --- a/context/IapProvider.jsx +++ b/context/IapProvider.jsx @@ -3,6 +3,7 @@ import { Platform } from "react-native"; import Purchases, { LOG_LEVEL } from "react-native-purchases"; import { AuthContext } from "../App"; import { get } from "../utils/storeInfo"; +import Toast from "react-native-toast-message"; const IapContext = createContext(); @@ -54,10 +55,25 @@ export const IapProvider = ({ children }) => { } }; + const restorePurchases = async () => { + if (Platform.OS !== "ios") return; + try { + await Purchases.restorePurchases(); + Toast.show({ + type: "success", + text1: "恢复成功", + topOffset: 60, + }); + } catch (e) { + console.error(e); + } + }; + const value = { packages, purchasePackage, getCustomerInformation, + restorePurchases, }; if (!isReady) return <>; diff --git a/screeens/Login/index.jsx b/screeens/Login/index.jsx index 3e3f570..839929a 100644 --- a/screeens/Login/index.jsx +++ b/screeens/Login/index.jsx @@ -23,7 +23,7 @@ export default function Login() { const [index, setIndex] = useState(0); const [routes] = useState([ { key: "phoneNumLogin", title: "验证码登录" }, - { key: "passwordLogin", title: "账号密码登陆" }, + { key: "passwordLogin", title: "账号密码登录" }, ]); const renderScene = useCallback( diff --git a/screeens/My/index.jsx b/screeens/My/index.jsx index dd122f5..ffb4d96 100644 --- a/screeens/My/index.jsx +++ b/screeens/My/index.jsx @@ -4,6 +4,7 @@ import { TouchableOpacity, ScrollView, Image as NativeImage, + Platform, } from "react-native"; import React, { useState, useEffect, useCallback } from "react"; import { useTailwind } from "tailwind-rn"; @@ -24,7 +25,7 @@ export default function My({ navigation }) { //获取当前页面数据 const [data, setData] = useState(); - const [vipPrice, setVipPrice] = useState(); + const [vipPrice, setVipPrice] = useState({}); useEffect(() => { const initData = async () => { const account = await get("account"); @@ -59,7 +60,7 @@ export default function My({ navigation }) { }); return; } - setVipPrice(_data.data.product.real_price / 100); + setVipPrice(_data.data.product); } catch (error) { console.error(error); } @@ -352,7 +353,9 @@ export default function My({ navigation }) { activeOpacity={1} onPress={() => navigation.navigate("WebWithoutHeader", { - uri: process.env.EXPO_PUBLIC_WEB_URL + "/vip", + uri: + process.env.EXPO_PUBLIC_WEB_URL + + `/vip?use_coinpay=${Platform.OS === "ios" ? "1" : "0"}`, }) } style={tailwind( @@ -388,7 +391,12 @@ export default function My({ navigation }) { )} > - ¥{vipPrice}/永久 + {`${ + Platform.OS === "ios" + ? vipPrice?.real_coin_price + "金币" + : "¥" + vipPrice?.real_price / 100 + }`} + /永久 )} diff --git a/screeens/RechargeGold/index.jsx b/screeens/RechargeGold/index.jsx new file mode 100644 index 0000000..8d1539b --- /dev/null +++ b/screeens/RechargeGold/index.jsx @@ -0,0 +1,25 @@ +import { View, Text } from "react-native"; +import React from "react"; +import { useTailwind } from "tailwind-rn"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import Toast from "react-native-toast-message"; + +export default function RechargeGold({ navigation, route }) { + const blurhash = "LcKUTa%gOYWBYRt6xuoJo~s8V@fk"; + + const tailwind = useTailwind(); + const insets = useSafeAreaInsets(); + return ( + + Example + + ); +} diff --git a/screeens/Setting/SelectSettingItem/index.jsx b/screeens/Setting/SelectSettingItem/index.jsx index df9a0a7..866f868 100644 --- a/screeens/Setting/SelectSettingItem/index.jsx +++ b/screeens/Setting/SelectSettingItem/index.jsx @@ -1,4 +1,10 @@ -import { View, Text, ScrollView, TouchableOpacity } from "react-native"; +import { + View, + Text, + ScrollView, + TouchableOpacity, + Platform, +} from "react-native"; import React, { useState, useEffect, useContext } from "react"; import { useTailwind } from "tailwind-rn"; import { useSafeAreaInsets } from "react-native-safe-area-context"; @@ -6,11 +12,15 @@ import { Icon, Button } from "@rneui/themed"; import * as FileSystem from "expo-file-system"; import { AuthContext } from "../../../App"; import MyDivider from "../../../components/MyDivider"; +import { useIap } from "../../../context/IapProvider"; export default function SelectSettingItem({ navigation }) { const tailwind = useTailwind(); const insets = useSafeAreaInsets(); + //恢复购买 + const { restorePurchases } = useIap(); + //点击退出登陆 const { signOut } = useContext(AuthContext); @@ -67,6 +77,16 @@ export default function SelectSettingItem({ navigation }) { + {Platform.OS === "ios" && ( + + 恢复购买 + + + )} + ; @@ -345,14 +350,19 @@ export default function SpaceIntroduce({ navigation, route }) { onPress={ data?.admission_price === 0 ? handleJoinFreeSpace - : () => + : () => { + if (Platform.OS === "ios") { + setIsCoinPayModalVisible(true); + return; + } navigation.navigate("WebWithoutHeader", { uri: process.env.EXPO_PUBLIC_WEB_URL + "/zone/pay/" + data?.id + "/h5_zone_admission/0", - }) + }); + } } style={tailwind( "flex flex-row items-center justify-center h-12 rounded-full px-10 bg-[#FF669E]" @@ -367,12 +377,28 @@ export default function SpaceIntroduce({ navigation, route }) { {data?.admission_price === 0 ? "免费加入" - : `${data?.admission_price / 100}元立即加入`} + : `${ + Platform.OS === "ios" + ? data?.admission_coin_price + "金币" + : data?.admission_price / 100 + "元" + }立即加入`} )} + {/* 金币支付Modal */} + + navigation.replace("SpaceIntroduce", route.params) + } + /> {/* 查看微信Modal */} {data?.streamer_ext?.wechat_lock_type === 0 ? ( { @@ -257,14 +262,37 @@ export default function StreamerSpace({ navigation, route }) { color="#FF669E" variant="determinate" /> - {`${ - data?.expenditure / 100 - } / ${data?.ironfanship_price / 100}`} - - 空间内累计消费达到¥{data?.ironfanship_price / 100}即可成为 - 铁粉 - ,可查看所有铁粉专享内容哦,快来成为我的铁粉吧~ - + {Platform.OS === "ios" ? ( + <> + {`${data?.expenditure / 10} / ${ + data?.ironfanship_coin_price + }`} + + 空间内累计消费达到{data?.ironfanship_coin_price}金币即可成为 + 铁粉 + ,可查看所有铁粉专享内容哦,快来成为我的铁粉吧~ + + + ) : ( + <> + {`${data?.expenditure / 100} / ${ + data?.ironfanship_price / 100 + }`} + + 空间内累计消费达到¥{data?.ironfanship_price / 100}即可成为 + 铁粉 + ,可查看所有铁粉专享内容哦,快来成为我的铁粉吧~ + + + )}