recharge.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import Alipay from 'expo-native-alipay';
  2. import { Stack, useRouter } from 'expo-router';
  3. import React, { useState } from 'react';
  4. import { ActivityIndicator, Alert, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
  5. import services from '../../services/api';
  6. const MAX_AMOUNT = 100000;
  7. export default function RechargeScreen() {
  8. const router = useRouter();
  9. const [money, setMoney] = useState('');
  10. const [loading, setLoading] = useState(false);
  11. const validateInput = (text: string) => {
  12. let value = text.replace(/[^\d.]/g, '')
  13. .replace(/^\./g, '')
  14. .replace(/\.{2,}/g, '.');
  15. value = value.replace(/^(-)*(\d+)\.(\d\d).*$/, '$1$2.$3');
  16. if (parseFloat(value) > MAX_AMOUNT) {
  17. Alert.alert('提示', `最大充值金额为${MAX_AMOUNT}元`);
  18. value = MAX_AMOUNT.toString();
  19. }
  20. setMoney(value);
  21. };
  22. const handleRecharge = async () => {
  23. const amount = parseFloat(money);
  24. if (!amount || amount <= 0) {
  25. Alert.alert('提示', '请输入有效的充值金额');
  26. return;
  27. }
  28. setLoading(true);
  29. setLoading(true);
  30. try {
  31. // Use ALIPAY_APP for native payment with correct API
  32. const res: any = await services.wallet.rechargeSubmit(amount, 'ALIPAY_APP', 'CASH');
  33. console.log('Recharge API Res:', res);
  34. // Handle response which might be direct string or object with payInfo
  35. const payInfo = typeof res?.data === 'string' ? res?.data : (res?.data?.payInfo || res?.data?.orderInfo || res?.data?.tradeNo);
  36. if (payInfo && typeof payInfo === 'string' && (payInfo.startsWith('alipay_root_cert_sn') || payInfo.includes('alipay_sdk'))) {
  37. Alipay.setAlipayScheme('alipay2021005175632205');
  38. const result = await Alipay.pay(payInfo);
  39. console.log('Alipay Result:', result);
  40. if (result?.resultStatus === '9000') {
  41. Alert.alert('提示', '充值成功');
  42. router.replace('/wallet/recharge_record');
  43. } else {
  44. Alert.alert('提示', '支付未完成');
  45. }
  46. } else if (res?.data?.tradeNo && !payInfo) {
  47. Alert.alert('提示', '获取支付信息失败(仅获取到订单号)');
  48. } else {
  49. Alert.alert('失败', '生成充值订单失败 ' + (res?.msg || ''));
  50. }
  51. } catch (error) {
  52. console.error('Recharge failed', error);
  53. Alert.alert('错误', '充值失败,请重试');
  54. } finally {
  55. setLoading(false);
  56. }
  57. };
  58. return (
  59. <View style={styles.container}>
  60. <Stack.Screen options={{ title: '充值', headerStyle: { backgroundColor: '#fff' }, headerShadowVisible: false }} />
  61. <View style={styles.content}>
  62. <View style={styles.inputWrapper}>
  63. <Text style={styles.label}>充值金额:</Text>
  64. <TextInput
  65. style={styles.input}
  66. value={money}
  67. onChangeText={validateInput}
  68. placeholder="请输入金额"
  69. keyboardType="decimal-pad"
  70. />
  71. </View>
  72. <Text style={styles.tip}>充值金额可以用于购买手办,奖池</Text>
  73. <Text style={styles.tipSub}>
  74. 充值金额不可提现,<Text style={styles.highlight}>线下充值额外返10%</Text>
  75. </Text>
  76. <TouchableOpacity
  77. style={[styles.btn, loading && styles.disabledBtn]}
  78. onPress={handleRecharge}
  79. disabled={loading}
  80. >
  81. {loading ? <ActivityIndicator color="#fff" /> : <Text style={styles.btnText}>充值</Text>}
  82. </TouchableOpacity>
  83. <TouchableOpacity style={styles.recordLink} onPress={() => router.push('/wallet/recharge_record')}>
  84. <Text style={styles.recordLinkText}>充值记录 {'>'}{'>'}</Text>
  85. </TouchableOpacity>
  86. </View>
  87. </View>
  88. );
  89. }
  90. const styles = StyleSheet.create({
  91. container: {
  92. flex: 1,
  93. backgroundColor: '#fff',
  94. },
  95. content: {
  96. paddingHorizontal: 24,
  97. paddingTop: 32,
  98. },
  99. inputWrapper: {
  100. flexDirection: 'row',
  101. alignItems: 'center',
  102. borderWidth: 1,
  103. borderColor: '#ddd',
  104. borderRadius: 8,
  105. paddingHorizontal: 16,
  106. height: 52, // 104rpx
  107. marginTop: 15,
  108. },
  109. label: {
  110. fontSize: 16, // font5
  111. color: '#333',
  112. marginRight: 10,
  113. },
  114. input: {
  115. flex: 1,
  116. fontSize: 16, // font5
  117. height: '100%',
  118. },
  119. tip: {
  120. textAlign: 'center',
  121. color: '#666', // color-2
  122. fontSize: 14,
  123. marginTop: 36,
  124. },
  125. tipSub: {
  126. textAlign: 'center',
  127. color: '#666', // color-2
  128. fontSize: 14,
  129. marginTop: 5,
  130. marginBottom: 20,
  131. },
  132. highlight: {
  133. color: '#07C160',
  134. textDecorationLine: 'underline',
  135. },
  136. btn: {
  137. backgroundColor: '#0081ff', // bg-blue
  138. height: 44,
  139. borderRadius: 4,
  140. justifyContent: 'center',
  141. alignItems: 'center',
  142. width: '70%',
  143. alignSelf: 'center',
  144. },
  145. disabledBtn: {
  146. opacity: 0.7,
  147. },
  148. btnText: {
  149. color: '#fff',
  150. fontSize: 14,
  151. fontWeight: 'bold',
  152. },
  153. recordLink: {
  154. marginTop: 20,
  155. alignItems: 'center',
  156. },
  157. recordLinkText: {
  158. color: '#8b3dff', // color-theme
  159. fontSize: 14,
  160. },
  161. });