recharge.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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.purse.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. }, 500); // 0.5秒后提示充值已发起
  77. }
  78. },
  79. );
  80. const result = await Alipay.pay(payInfo);
  81. if (isResolved) return;
  82. isResolved = true;
  83. console.log("Alipay Result:", result);
  84. if (result?.resultStatus === "9000") {
  85. Alert.alert("提示", "充值成功");
  86. router.replace("/wallet/recharge_record");
  87. } else if (result?.resultStatus === "6001") {
  88. Alert.alert("提示", "用户取消支付");
  89. } else {
  90. Alert.alert("提示", "支付未完成");
  91. }
  92. } catch (e: any) {
  93. isResolved = true;
  94. Alert.alert("提示", "调用支付宝失败: " + (e.message || ""));
  95. } finally {
  96. if (appStateSub) {
  97. appStateSub.remove();
  98. }
  99. }
  100. } else if (res?.data?.tradeNo && !payInfo) {
  101. Alert.alert("提示", "获取支付信息失败(仅获取到订单号)");
  102. } else {
  103. Alert.alert("失败", "生成充值订单失败 " + (res?.msg || ""));
  104. }
  105. } catch (error) {
  106. console.error("Recharge failed", error);
  107. Alert.alert("错误", "充值失败,请重试");
  108. } finally {
  109. setLoading(false);
  110. }
  111. };
  112. return (
  113. <View style={styles.container}>
  114. <Stack.Screen
  115. options={{
  116. title: "充值",
  117. headerStyle: { backgroundColor: "#fff" },
  118. headerShadowVisible: false,
  119. }}
  120. />
  121. <View style={styles.content}>
  122. <View style={styles.inputWrapper}>
  123. <Text style={styles.label}>充值金额:</Text>
  124. <TextInput
  125. style={styles.input}
  126. value={money}
  127. onChangeText={validateInput}
  128. placeholder="请输入金额"
  129. keyboardType="decimal-pad"
  130. />
  131. </View>
  132. <Text style={styles.tip}>充值金额可以用于购买手办,奖池</Text>
  133. <Text style={styles.tipSub}>
  134. 充值金额不可提现,
  135. <Text style={styles.highlight}>线下充值额外返10%</Text>
  136. </Text>
  137. <TouchableOpacity
  138. style={[styles.btn, loading && styles.disabledBtn]}
  139. onPress={handleRecharge}
  140. disabled={loading}
  141. >
  142. {loading ? (
  143. <ActivityIndicator color="#fff" />
  144. ) : (
  145. <Text style={styles.btnText}>充值</Text>
  146. )}
  147. </TouchableOpacity>
  148. <TouchableOpacity
  149. style={styles.recordLink}
  150. onPress={() => router.push("/wallet/recharge_record")}
  151. >
  152. <Text style={styles.recordLinkText}>
  153. 充值记录 {">"}
  154. {">"}
  155. </Text>
  156. </TouchableOpacity>
  157. </View>
  158. </View>
  159. );
  160. }
  161. const styles = StyleSheet.create({
  162. container: {
  163. flex: 1,
  164. backgroundColor: "#fff",
  165. },
  166. content: {
  167. paddingHorizontal: 24,
  168. paddingTop: 32,
  169. },
  170. inputWrapper: {
  171. flexDirection: "row",
  172. alignItems: "center",
  173. borderWidth: 1,
  174. borderColor: "#ddd",
  175. borderRadius: 8,
  176. paddingHorizontal: 16,
  177. height: 52, // 104rpx
  178. marginTop: 15,
  179. },
  180. label: {
  181. fontSize: 16, // font5
  182. color: "#333",
  183. marginRight: 10,
  184. },
  185. input: {
  186. flex: 1,
  187. fontSize: 16, // font5
  188. height: "100%",
  189. },
  190. tip: {
  191. textAlign: "center",
  192. color: "#666", // color-2
  193. fontSize: 14,
  194. marginTop: 36,
  195. },
  196. tipSub: {
  197. textAlign: "center",
  198. color: "#666", // color-2
  199. fontSize: 14,
  200. marginTop: 5,
  201. marginBottom: 20,
  202. },
  203. highlight: {
  204. color: "#07C160",
  205. textDecorationLine: "underline",
  206. },
  207. btn: {
  208. backgroundColor: "#0081ff", // bg-blue
  209. height: 44,
  210. borderRadius: 4,
  211. justifyContent: "center",
  212. alignItems: "center",
  213. width: "70%",
  214. alignSelf: "center",
  215. },
  216. disabledBtn: {
  217. opacity: 0.7,
  218. },
  219. btnText: {
  220. color: "#fff",
  221. fontSize: 14,
  222. fontWeight: "bold",
  223. },
  224. recordLink: {
  225. marginTop: 20,
  226. alignItems: "center",
  227. },
  228. recordLinkText: {
  229. color: "#8b3dff", // color-theme
  230. fontSize: 14,
  231. },
  232. });