recharge.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import Alipay from "expo-native-alipay";
  2. import { Stack, useRouter } from "expo-router";
  3. import React, { useState } from "react";
  4. import {
  5. ActivityIndicator,
  6. Alert,
  7. AppState,
  8. StyleSheet,
  9. Text,
  10. TextInput,
  11. TouchableOpacity,
  12. View,
  13. } from "react-native";
  14. import services from "../../services/api";
  15. const MAX_AMOUNT = 100000;
  16. export default function RechargeScreen() {
  17. const router = useRouter();
  18. const [money, setMoney] = useState("");
  19. const [loading, setLoading] = useState(false);
  20. const validateInput = (text: string) => {
  21. let value = text
  22. .replace(/[^\d.]/g, "")
  23. .replace(/^\./g, "")
  24. .replace(/\.{2,}/g, ".");
  25. value = value.replace(/^(-)*(\d+)\.(\d\d).*$/, "$1$2.$3");
  26. if (parseFloat(value) > MAX_AMOUNT) {
  27. Alert.alert("提示", `最大充值金额为${MAX_AMOUNT}元`);
  28. value = MAX_AMOUNT.toString();
  29. }
  30. setMoney(value);
  31. };
  32. const handleRecharge = async () => {
  33. const amount = parseFloat(money);
  34. if (!amount || amount <= 0) {
  35. Alert.alert("提示", "请输入有效的充值金额");
  36. return;
  37. }
  38. setLoading(true);
  39. setLoading(true);
  40. try {
  41. // Use ALIPAY_APP for native payment with correct API
  42. const res: any = await services.wallet.rechargeSubmit(
  43. amount,
  44. "ALIPAY_APP",
  45. "CASH",
  46. );
  47. console.log("Recharge API Res:", res);
  48. // Handle response which might be direct string or object with payInfo
  49. const payInfo =
  50. typeof res?.data === "string"
  51. ? res?.data
  52. : res?.data?.payInfo || res?.data?.orderInfo || res?.data?.tradeNo;
  53. if (
  54. payInfo &&
  55. typeof payInfo === "string" &&
  56. (payInfo.startsWith("alipay_root_cert_sn") ||
  57. payInfo.includes("alipay_sdk"))
  58. ) {
  59. let appStateSub: any = null;
  60. let isResolved = false;
  61. try {
  62. Alipay.setAlipayScheme("alipay2021005175632205");
  63. appStateSub = AppState.addEventListener(
  64. "change",
  65. async (nextAppState) => {
  66. if (nextAppState === "active" && !isResolved) {
  67. setTimeout(async () => {
  68. if (!isResolved) {
  69. console.log(
  70. "Alipay SDK did not resolve natively, assuming success to refresh...",
  71. );
  72. isResolved = true;
  73. Alert.alert("提示", "充值已发起,请稍后查看余额");
  74. router.replace("/wallet/recharge_record");
  75. }
  76. }, 3000);
  77. }
  78. },
  79. );
  80. const result = await Alipay.pay(payInfo);
  81. isResolved = true;
  82. console.log("Alipay Result:", result);
  83. if (result?.resultStatus === "9000") {
  84. Alert.alert("提示", "充值成功");
  85. router.replace("/wallet/recharge_record");
  86. } else if (result?.resultStatus === "6001") {
  87. Alert.alert("提示", "用户取消支付");
  88. } else {
  89. Alert.alert("提示", "支付未完成");
  90. }
  91. } catch (e: any) {
  92. isResolved = true;
  93. Alert.alert("提示", "调用支付宝失败: " + (e.message || ""));
  94. } finally {
  95. if (appStateSub) {
  96. appStateSub.remove();
  97. }
  98. }
  99. } else if (res?.data?.tradeNo && !payInfo) {
  100. Alert.alert("提示", "获取支付信息失败(仅获取到订单号)");
  101. } else {
  102. Alert.alert("失败", "生成充值订单失败 " + (res?.msg || ""));
  103. }
  104. } catch (error) {
  105. console.error("Recharge failed", error);
  106. Alert.alert("错误", "充值失败,请重试");
  107. } finally {
  108. setLoading(false);
  109. }
  110. };
  111. return (
  112. <View style={styles.container}>
  113. <Stack.Screen
  114. options={{
  115. title: "充值",
  116. headerStyle: { backgroundColor: "#fff" },
  117. headerShadowVisible: false,
  118. }}
  119. />
  120. <View style={styles.content}>
  121. <View style={styles.inputWrapper}>
  122. <Text style={styles.label}>充值金额:</Text>
  123. <TextInput
  124. style={styles.input}
  125. value={money}
  126. onChangeText={validateInput}
  127. placeholder="请输入金额"
  128. keyboardType="decimal-pad"
  129. />
  130. </View>
  131. <Text style={styles.tip}>充值金额可以用于购买手办,奖池</Text>
  132. <Text style={styles.tipSub}>
  133. 充值金额不可提现,
  134. <Text style={styles.highlight}>线下充值额外返10%</Text>
  135. </Text>
  136. <TouchableOpacity
  137. style={[styles.btn, loading && styles.disabledBtn]}
  138. onPress={handleRecharge}
  139. disabled={loading}
  140. >
  141. {loading ? (
  142. <ActivityIndicator color="#fff" />
  143. ) : (
  144. <Text style={styles.btnText}>充值</Text>
  145. )}
  146. </TouchableOpacity>
  147. <TouchableOpacity
  148. style={styles.recordLink}
  149. onPress={() => router.push("/wallet/recharge_record")}
  150. >
  151. <Text style={styles.recordLinkText}>
  152. 充值记录 {">"}
  153. {">"}
  154. </Text>
  155. </TouchableOpacity>
  156. </View>
  157. </View>
  158. );
  159. }
  160. const styles = StyleSheet.create({
  161. container: {
  162. flex: 1,
  163. backgroundColor: "#fff",
  164. },
  165. content: {
  166. paddingHorizontal: 24,
  167. paddingTop: 32,
  168. },
  169. inputWrapper: {
  170. flexDirection: "row",
  171. alignItems: "center",
  172. borderWidth: 1,
  173. borderColor: "#ddd",
  174. borderRadius: 8,
  175. paddingHorizontal: 16,
  176. height: 52, // 104rpx
  177. marginTop: 15,
  178. },
  179. label: {
  180. fontSize: 16, // font5
  181. color: "#333",
  182. marginRight: 10,
  183. },
  184. input: {
  185. flex: 1,
  186. fontSize: 16, // font5
  187. height: "100%",
  188. },
  189. tip: {
  190. textAlign: "center",
  191. color: "#666", // color-2
  192. fontSize: 14,
  193. marginTop: 36,
  194. },
  195. tipSub: {
  196. textAlign: "center",
  197. color: "#666", // color-2
  198. fontSize: 14,
  199. marginTop: 5,
  200. marginBottom: 20,
  201. },
  202. highlight: {
  203. color: "#07C160",
  204. textDecorationLine: "underline",
  205. },
  206. btn: {
  207. backgroundColor: "#0081ff", // bg-blue
  208. height: 44,
  209. borderRadius: 4,
  210. justifyContent: "center",
  211. alignItems: "center",
  212. width: "70%",
  213. alignSelf: "center",
  214. },
  215. disabledBtn: {
  216. opacity: 0.7,
  217. },
  218. btnText: {
  219. color: "#fff",
  220. fontSize: 14,
  221. fontWeight: "bold",
  222. },
  223. recordLink: {
  224. marginTop: 20,
  225. alignItems: "center",
  226. },
  227. recordLinkText: {
  228. color: "#8b3dff", // color-theme
  229. fontSize: 14,
  230. },
  231. });