245 lines
11 KiB
TypeScript
245 lines
11 KiB
TypeScript
import { Ionicons } from '@expo/vector-icons'
|
||
import { root } from '@repo/core'
|
||
import { AlipayController } from '@repo/sdk'
|
||
import { LinearGradient } from 'expo-linear-gradient'
|
||
import Alipay from 'expo-native-alipay'
|
||
import { Stack, useRouter } from 'expo-router'
|
||
import ExpoWeChat from 'expo-wechat'
|
||
import React, { useEffect, useState } from 'react'
|
||
import { Dimensions, ScrollView } from 'react-native'
|
||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||
|
||
import { Block, Img, Text } from '@/@share/components'
|
||
import { IOS_UNIVERSAL_LINK, SCHEME } from '@/app.constants'
|
||
import { useUserBalance } from '@/hooks/core'
|
||
import { useError } from '@/hooks/data/use-error'
|
||
import { cn } from '@/utils/cn'
|
||
|
||
const { width: SCREEN_WIDTH } = Dimensions.get('window')
|
||
const OPTION_WIDTH = (SCREEN_WIDTH - 32 - 15) / 2 // 32px padding, 15px gap
|
||
|
||
const RECHARGE_OPTIONS = [
|
||
{ id: 1, points: '500', price: '$10.00' },
|
||
{ id: 2, points: '1,000', price: '$20.00' },
|
||
{ id: 3, points: '2,500', price: '$50.00' },
|
||
{ id: 4, points: '5,000', price: '$100.00' },
|
||
]
|
||
|
||
const PAYMENT_METHODS = [
|
||
{ id: 'alipay', label: '支付宝' },
|
||
{ id: 'wechat', label: '微信' },
|
||
] as const
|
||
|
||
export default function ChargePage() {
|
||
const router = useRouter()
|
||
const insets = useSafeAreaInsets()
|
||
const [selectedOption, setSelectedOption] = useState(RECHARGE_OPTIONS[0].id)
|
||
const [paymentMethod, setPaymentMethod] = useState<'alipay' | 'wechat'>('alipay')
|
||
|
||
const balanceRes = useUserBalance()
|
||
console.log('balanceRes-------', balanceRes)
|
||
|
||
const selectedRecharge = RECHARGE_OPTIONS.find((option) => option.id === selectedOption)
|
||
|
||
const initPay = async () => {
|
||
// 设置 支付宝 URL Schemes,要表述他是宇宙唯一性,可以使用 `bundle Identifier`
|
||
// scheme = `alipay` + `APPID`,`APPID` 为支付宝分配给开发者的应用ID
|
||
// Alipay.setAlipayScheme(IOS_UNIVERSAL_LINK)
|
||
|
||
console.log('initpay ---------', Alipay)
|
||
|
||
Alipay.setAlipayScheme(SCHEME)
|
||
// ⚠️ 目前不可用,设置支付宝沙箱环境,仅 Android 支持
|
||
Alipay.setAlipaySandbox(true)
|
||
|
||
console.log('-------alipay', await Alipay.getVersion())
|
||
|
||
const wechatAppId = 'wx940e1ed91a5c303c'
|
||
const result = await ExpoWeChat.registerApp(wechatAppId, IOS_UNIVERSAL_LINK)
|
||
|
||
console.log('-------wechat', result)
|
||
}
|
||
|
||
const handlePay = async () => {
|
||
// 支付宝端支付
|
||
// payInfo 是后台拼接好的支付参数
|
||
// return_url=
|
||
// const payInfo =
|
||
// 'alipay_sdk=alipay-sdk-java-dynamicVersionNo&app_id=2021001172656340&biz_content=%7B%22out_trade_no%22%3A%221111112222222%22%2C%22total_amount%22%3A%220.01%22%2C%22subject%22%3A%221234%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%7D&charset=UTF-8&format=json&method=alipay.trade.app.pay¬ify_url=http%3A%2F%2Fane.boshu.ltd%2Fowner%2Fpay%2Fapi%2FownerPay%2Fcallback&sign=oUQmGtkv8mrhJ0YwHl9%2FfxMcoLACWuSFKiMTC4Id8nc%2FZVvDQ6MLQq5hhtEN03Qn1%2BAtzTAaofE8nNixdroxOek2l5YtOAcYcXVYlJIyogN%2B22erN2NpDTWJ7tQTKgYFDJLRiG0DZJaxfADhUUF6UR9kdA8omoXKLDlP17ZPUs5Jr4aKv5HJtH5C53ui7PbmyWYg934L4UDC2F%2F9pPQlRwwDeE1SAaV3HW9Dt83kK52o8%2FlChXdotbFdAvH0d4qYGhpEYU5sepj9xiOMyL9aC4pMXW9INYLLGbvtqtlRchZTAfH5yji6nqqQm9KKMmcVrWdBDLyjFVNpejq1UjbJBw%3D%3D&sign_type=RSA2×tamp=2020-07-09+12%3A16%3A16&version=1.0'
|
||
if (paymentMethod === 'alipay') {
|
||
console.log('handlePay-----------alipay')
|
||
const alipay = root.get(AlipayController)
|
||
const { data, error } = await useError(
|
||
async () =>
|
||
await alipay.appPay({
|
||
subject: '1',
|
||
totalAmount: selectedRecharge ? selectedRecharge.price.replace('$', '') : '0.01',
|
||
body: '',
|
||
outTradeNo: '1111' + Date.now(),
|
||
credits: 1,
|
||
}),
|
||
)
|
||
|
||
console.log('error----------', error)
|
||
|
||
// const payInfo =
|
||
// 'alipay_sdk=alipay-sdk-java-dynamicVersionNo&app_id=9021000158673972&biz_content=%7B%22out_trade_no%22%3A%221111112222222%22%2C%22total_amount%22%3A%220.01%22%2C%22subject%22%3A%221234%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%7D&charset=UTF-8&format=json&method=alipay.trade.app.pay¬ify_url=http%3A%2F%2Fane.boshu.ltd%2Fowner%2Fpay%2Fapi%2FownerPay%2Fcallback&sign=oUQmGtkv8mrhJ0YwHl9%2FfxMcoLACWuSFKiMTC4Id8nc%2FZVvDQ6MLQq5hhtEN03Qn1%2BAtzTAaofE8nNixdroxOek2l5YtOAcYcXVYlJIyogN%2B22erN2NpDTWJ7tQTKgYFDJLRiG0DZJaxfADhUUF6UR9kdA8omoXKLDlP17ZPUs5Jr4aKv5HJtH5C53ui7PbmyWYg934L4UDC2F%2F9pPQlRwwDeE1SAaV3HW9Dt83kK52o8%2FlChXdotbFdAvH0d4qYGhpEYU5sepj9xiOMyL9aC4pMXW9INYLLGbvtqtlRchZTAfH5yji6nqqQm9KKMmcVrWdBDLyjFVNpejq1UjbJBw%3D%3D&sign_type=RSA2×tamp=2020-07-09+12%3A16%3A16&version=1.0'
|
||
const resule = await Alipay.pay(data.orderStr)
|
||
console.log('alipay:resule-->>>', resule)
|
||
} else if (paymentMethod === 'wechat') {
|
||
}
|
||
// console.log('data-----------', JSON.stringify(data))
|
||
// console.log('error-----------', error)
|
||
// const payInfo = 'timestamp=2025-12-24+09%3A03%3A08&app_id=9021000158673972&method=alipay.trade.app.pay&charset=utf-8&format=json&version=1.0&sign_type=RSA2&sign=UVmGNmnCzvnrIVUHkCcDh%2BJQxVW7W9rBQj7VL4OJzkXYQfj1wrb5byPsQKamQWqxMbb76YbMFo5iWjctlYxDny%2BY8RKg7eJXB7GbJWHw3eWXvZsFIUTXALQF%2BXBmuW1b%2BYy8NRy08UgeRJhp4%2FF6HCR4G33Js3TanoR%2FS1HWYns2X%2BiCY5WfnnFmq52KSm3PcYnI%2BfkgdnrbAQxWqjLWd717OnvOMyX9ydssTYUUk34p3RZchS3ltUg0EnHBy0wUs1juc4P3qAeayH2E0TwUBCpJnyTNZfQ0QFIguEFXPLW9kEgfG0dJu%2Ft0BQHdon8KnEkHF4M67wcaJl4itnV5tw%3D%3D&biz_content=%7B%22body%22%3A%22zzzzzzzz%22%2C%22out_trade_no%22%3A%221111112222222%22%2C%22passback_params%22%3A%22%257B%2522userId%2522%253A%2520%25222088721091035181%2522%252C%2520%2522credits%2522%253A%25201%257D%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22seller_id%22%3A%222088301194649043%22%2C%22subject%22%3A%221%22%2C%22timeout_express%22%3A%2290m%22%2C%22total_amount%22%3A%221%22%7D'
|
||
}
|
||
|
||
useEffect(() => {
|
||
balanceRes.load()
|
||
initPay()
|
||
}, [])
|
||
|
||
const renderHeader = () => (
|
||
<Block className="flex-row items-center justify-between px-[16px]" style={{ paddingTop: 12, paddingBottom: 12 }}>
|
||
<Block className="-ml-[8px] size-[40px] items-center justify-center" opacity={0.7} onClick={() => router.back()}>
|
||
<Ionicons color="white" name="chevron-back" size={24} />
|
||
</Block>
|
||
<Text className="text-[16px] font-[700] text-white">积分充值</Text>
|
||
<Block className="w-[32px]" />
|
||
</Block>
|
||
)
|
||
|
||
const renderMyPoints = () => (
|
||
<Block className="mt-[12px] items-center justify-center py-[24px]">
|
||
<Text className="text-[14px] font-[400] text-[#B0B0B0]">我的积分</Text>
|
||
<Text className="mt-[4px] text-[40px] font-[900] italic tracking-tighter text-fg">{balanceRes?.balance}</Text>
|
||
</Block>
|
||
)
|
||
|
||
const renderOptions = () => (
|
||
<Block className="mt-[16px] px-[16px]">
|
||
<Text className="mb-[16px] text-[14px] font-[700] text-white">选择充值积分:</Text>
|
||
<Block className="flex-row flex-wrap justify-between gap-y-[16px]">
|
||
{RECHARGE_OPTIONS.map((item) => {
|
||
const isSelected = selectedOption === item.id
|
||
return (
|
||
<Block
|
||
key={item.id}
|
||
opacity={0.9}
|
||
style={{ width: OPTION_WIDTH }}
|
||
className={cn(
|
||
'relative items-center justify-center overflow-hidden rounded-[16px] border-[2px] bg-[#FFFFFF33] py-[12px]',
|
||
isSelected ? 'border-[#FFE500]' : 'border-transparent',
|
||
)}
|
||
onClick={() => setSelectedOption(item.id)}
|
||
>
|
||
<Img
|
||
className="absolute inset-0"
|
||
source={require('@/assets/images/itemBg.png')}
|
||
style={{ resizeMode: 'cover' }}
|
||
/>
|
||
{/* Flash Icon */}
|
||
<Block className="mb-[8px]">
|
||
<Ionicons color="#FFD700" name="flash" size={24} />
|
||
</Block>
|
||
|
||
{/* Points */}
|
||
<Text className="mb-[16px] text-[24px] font-[700] text-white">{item.points}</Text>
|
||
|
||
{/* Price Button */}
|
||
<Block className="border-[#000000]">
|
||
<LinearGradient
|
||
colors={['#393939', '#060606']}
|
||
end={{ x: 0, y: 1 }}
|
||
start={{ x: 0, y: 0 }}
|
||
style={{
|
||
borderRadius: 100,
|
||
width: 120,
|
||
height: 32,
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
borderColor: '#000000',
|
||
overflow: 'hidden',
|
||
borderWidth: 1,
|
||
}}
|
||
>
|
||
<Text className="text-[12px] font-[600] text-white">只需 {item.price}</Text>
|
||
</LinearGradient>
|
||
</Block>
|
||
</Block>
|
||
)
|
||
})}
|
||
</Block>
|
||
</Block>
|
||
)
|
||
|
||
const renderPaymentMethods = () => (
|
||
<Block className="mt-[32px] px-[16px]">
|
||
<Text className="font-700 mb-[16px] text-[14px] text-white">选择付款方式:</Text>
|
||
|
||
<Block className="gap-[12px]">
|
||
{PAYMENT_METHODS.map((method) => {
|
||
const isSelected = paymentMethod === method.id
|
||
return (
|
||
<Block
|
||
key={method.id}
|
||
opacity={0.8}
|
||
className={cn(
|
||
'flex-row items-center gap-[12px] rounded-full px-[20px] py-[16px]',
|
||
isSelected ? 'bg-white' : 'bg-[#FFFFFF33]',
|
||
)}
|
||
onClick={() => setPaymentMethod(method.id)}
|
||
>
|
||
<Block
|
||
className={cn(
|
||
'h-[20px] w-[20px] items-center justify-center rounded-full',
|
||
isSelected ? 'bg-black' : 'border-[2px] border-white/50',
|
||
)}
|
||
>
|
||
{isSelected && <Ionicons color="white" name="checkmark" size={14} />}
|
||
</Block>
|
||
<Text className={cn('font-700 text-[16px]', isSelected ? 'text-black' : 'text-white')}>
|
||
{method.label}
|
||
</Text>
|
||
</Block>
|
||
)
|
||
})}
|
||
</Block>
|
||
</Block>
|
||
)
|
||
|
||
const renderBottomBar = () => (
|
||
<Block
|
||
className="absolute inset-x-0 bottom-0 border-t border-white/5 bg-[#1C1E22]/95 px-[16px]"
|
||
style={{ paddingBottom: insets.bottom + 12, paddingTop: 16 }}
|
||
>
|
||
<Block
|
||
className="h-[52px] items-center justify-center rounded-full bg-accent shadow-lg shadow-[#FFE500]/20"
|
||
opacity={0.8}
|
||
onClick={handlePay}
|
||
>
|
||
<Text className="text-[16px] font-[900] italic text-black">确认充值</Text>
|
||
</Block>
|
||
|
||
<Block className="mt-[12px] flex-row justify-center">
|
||
<Text className="text-[11px] text-[#888888]">已阅读并同意 </Text>
|
||
<Block opacity={0.6} onClick={() => console.log('Open agreement')}>
|
||
<Text className="text-[11px] text-accent underline">付费服务协议</Text>
|
||
</Block>
|
||
</Block>
|
||
</Block>
|
||
)
|
||
|
||
return (
|
||
<Block className="flex-1 bg-[#21221D]">
|
||
<Stack.Screen options={{ headerShown: false }} />
|
||
{renderHeader()}
|
||
<ScrollView contentContainerStyle={{ flexGrow: 1, paddingBottom: 200 }} showsVerticalScrollIndicator={false}>
|
||
{renderMyPoints()}
|
||
{renderOptions()}
|
||
{renderPaymentMethods()}
|
||
</ScrollView>
|
||
{renderBottomBar()}
|
||
</Block>
|
||
)
|
||
}
|