修正购买流程的bug

This commit is contained in:
zhl 2023-06-02 19:54:21 +08:00
parent 476da08a8f
commit 0688261153
9 changed files with 168 additions and 67 deletions

View File

@ -14,8 +14,11 @@ EMAIL_VERIFY_URL="https://wallet.cebggame.com"
ALCHEMY_APPID="f83Is2y7L425rxl8"
ALCHEMY_APP_SECRET="4Yn8RkxDXN71Q3p0"
ALCHEMY_API_BASE="https://openapi-test.alchemypay.org"
ALCHEMY_PAGE_BASE="https://ramptest.alchemypay.org"
# ALCHEMY_API_BASE="https://openapi-test.alchemypay.org"
# ALCHEMY_PAGE_BASE="https://ramptest.alchemypay.org"
ALCHEMY_API_BASE="http://127.0.0.1:3009"
ALCHEMY_PAGE_BASE="http://127.0.0.1:3009/pay_page"
ALCHEMY_PAY_CB_URL="https://wallet.cebggame.com"
CHAIN_CLIENT_URL=http://127.0.0.1:3006
@ -29,10 +32,12 @@ AVAILABLE_TOKENS=cec|ceg
ARBITRUM_CHAIN_ID=421163
ARBITRUM_CEC_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
ARBITRUM_CEG_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
ARBITRUM_WALLET=''
ARBITRUM_WALLET='0x50A8e60041A206AcaA5F844a1104896224be6F39'
BSC_CHAIN_ID=97
BSC_CEC_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
BSC_CEG_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
BSC_WALLET=''
BSC_WALLET='0x50A8e60041A206AcaA5F844a1104896224be6F39'
# 链端转账回调地址
PAY_TRANSFER_CB_URL=''
PAY_TRANSFER_CB_URL='http://127.0.0.1:3007/api/internal/update_task'
# 链端回调hash的ket
HASH_SALT='iG4Rpsa)6U31$H#^T85$^^3'

View File

@ -36,9 +36,9 @@ let chainCfgs = {}
for (let network of networks) {
let data = { chainId: 0, wallet: '', tokens: {} }
data.chainId = parseInt(process.env[`${network.toUpperCase()}_CHAIN_ID`])
data.wallet = process.env[`WALLET_${network.toUpperCase()}`]
data.wallet = process.env[`${network.toUpperCase()}_WALLET`]
for (let sub of tokenList) {
data.tokens[sub] = process.env[`ADDRESS_${sub.toUpperCase()}_${network.toUpperCase()}`]
data.tokens[sub] = process.env[`${network.toUpperCase()}_${sub.toUpperCase()}_ADDRESS`]
}
chainCfgs[network] = data
}

View File

@ -2,7 +2,7 @@ import logger from 'logger/logger'
import BaseController from 'common/base.controller'
import { ZError } from 'common/ZError'
import { router } from 'decorators/router'
import { createPageSign, queryFiat, queryPrice, refreshToken } from 'service/alchemy.svr'
import { createOrder, createPageSign, queryFiat, queryPrice, refreshToken } from 'service/alchemy.svr'
import { generateKVStr } from 'utils/net.util'
import { PayRecord, PayStatus } from 'modules/PayRecord'
import { PriceSvr } from 'service/price.svr'
@ -10,7 +10,59 @@ import { PriceSvr } from 'service/price.svr'
const CALL_BACK_URL = `${process.env.ALCHEMY_PAY_CB_URL}/pay/out/alchemy/buycb`
class AlchemyController extends BaseController {
@router('post /pay/alchemy/buy')
async beginPay(req, res) {
async beginApiPay(req, res) {
const user = req.user
const { network, crypto, address, fiat, fiatAmount, country } = req.params
if (fiat || fiatAmount || country) {
if (!fiat || !fiatAmount || !country) {
throw new ZError(11, 'fiat, fiatAmount and country must be provided')
}
}
if (network || crypto) {
if (!network || !crypto) {
throw new ZError(12, 'network and crypto must be provided')
}
}
const tokenResult = await refreshToken(user.emailReal || user.email)
console.log(tokenResult)
if (!tokenResult.success || tokenResult.returnCode !== '0000') {
logger.info(`fetch pay token error::code: ${tokenResult.returnCode} msg: ${tokenResult.returnMsg}`)
throw new ZError(10, 'fetch pay token error')
}
const { id, email, accessToken } = tokenResult.data
let record = new PayRecord({ account: user.id, address, network, crypto })
if (fiat) record.fiat = fiat
if (fiatAmount) record.fiatAmount = fiatAmount
if (country) record.country = country
await record.save()
let payData: any = {
side: 'BUY',
merchantOrderNo: record.id,
amount: record.fiatAmount,
fiatCurrency: record.fiat,
cryptoCurrency: record.crypto,
depositType: '2',
address: address,
network: record.network,
payWayCode: '10001',
alpha2: record.country,
callbackUrl: CALL_BACK_URL,
merchantName: 'CEBG',
}
logger.info(`create order data::${JSON.stringify(payData)}`)
let payRes = await createOrder(accessToken, payData)
logger.info(`create order result::${JSON.stringify(payRes)}`)
record.outData = payRes.data
if (payRes.success) {
record.outOrderId = payRes.data.orderNo
} else {
record.status = PayStatus.FAIL
}
record.save()
return { url: payRes.data.payUrl }
}
@router('post /pay/alchemy/buypage')
async beginPagePay(req, res) {
const user = req.user
const { network, crypto, address, fiat, fiatAmount, country } = req.params
if (fiat || fiatAmount || country) {

View File

@ -5,6 +5,7 @@ import { role, router } from 'decorators/router'
import { checkPayResultSign, checkSimpleSign } from 'service/alchemy.svr'
import { PayRecord, PayStatus } from 'modules/PayRecord'
import { TransferQueue } from 'queue/transfer.queue'
import { TransferRecord } from 'modules/TransferRecord'
let errorRes = function (msg: string) {
return {
@ -27,12 +28,17 @@ class AlchemyOutController extends BaseController {
logger.info(`alchemy callback merchantOrderNo not found`)
throw new ZError(11, 'alchemy callback merchantOrderNo not found')
}
let record = await PayRecord.findById(merchantOrderNo)
if (!record) {
logger.info(`alchemy callback record not found`)
throw new ZError(12, 'alchemy callback record not found')
}
if (record.status !== PayStatus.PENDING && record.status !== PayStatus.TRANSFERING) {
if (
record.status !== PayStatus.PENDING &&
record.status !== PayStatus.TRANSFERING &&
record.status !== PayStatus.TRANSFERED
) {
logger.info(`alchemy callback record status error`)
throw new ZError(13, 'alchemy callback record status error')
}
@ -42,13 +48,18 @@ class AlchemyOutController extends BaseController {
await record.save()
throw new ZError(14, 'alchemy callback sign error')
}
let transferRecord = await TransferRecord.findByRecordId(record.id)
if (transferRecord) {
transferRecord.status = 9
await transferRecord.save()
}
record.outOrderId = orderNo
record.network = network
record.crypto = crypto
record.outData = req.params
record.status = status == 'PAY_SUCCESS' ? PayStatus.SUCCESS : PayStatus.FAIL
await record.save()
logger.info(`alchemy callback success, pay finished`)
return {}
}
@ -60,14 +71,15 @@ class AlchemyOutController extends BaseController {
@router('get /pay/out/alchemy/queryprice')
async queryToken(req, res) {
const { crypto } = req.params
const { appId, timestamp, sign } = req.headers
let { appId, appid, timestamp, sign } = req.headers
if (!crypto) {
return errorRes('params mismatch')
}
appId = appId || appid
if (!appId || !timestamp || !sign) {
return errorRes('headers mismatch')
}
if (!checkSimpleSign(req.headers)) {
if (!checkSimpleSign(req.headers, { crypto })) {
return errorRes('sign error')
}
let result = {
@ -90,20 +102,22 @@ class AlchemyOutController extends BaseController {
/**
*
* TODO::
* TODO::test
*/
@role(ROLE_ANON)
@router('post /pay/out/alchemy/distribute')
async distributeToken(req, res) {
const { orderNo, crypto, network, address, cryptoAmount, cryptoPrice, usdtAmount } = req.params
const { appId, timestamp, sign } = req.headers
let { appId, appid, timestamp, sign } = req.headers
if (!orderNo || !crypto || !network || !address || !cryptoAmount || !cryptoPrice || !usdtAmount) {
return errorRes('params mismatch')
}
appId = appId || appid
if (!appId || !timestamp || !sign) {
return errorRes('headers mismatch')
}
if (!checkSimpleSign(req.headers)) {
let signData = { orderNo, crypto, network, address, cryptoAmount, cryptoPrice, usdtAmount }
if (!checkSimpleSign(req.headers, signData)) {
return errorRes('sign error')
}
@ -117,6 +131,7 @@ class AlchemyOutController extends BaseController {
record.cryptoAmount = cryptoAmount
record.cryptoPrice = cryptoPrice
record.usdtAdmount = usdtAmount
record.status = PayStatus.TRANSFERING
await record.save()
new TransferQueue().addTask(record)
let result = {
@ -126,6 +141,6 @@ class AlchemyOutController extends BaseController {
returnCode: '0000', // false: 9999
returnMsg: 'in amet',
}
res.send(result)
return result
}
}

View File

@ -47,7 +47,7 @@ export default class InternalController extends BaseController {
if (!sign) {
throw new ZError(10, 'sign not found')
}
let hash = calcHash({ id, result, successCount, errorCount, hashList })
let hash = calcHash({ id, result, successCount, errorCount, gas, gasPrice, hashList })
console.log(hash, sign)
if (sign !== hash) {
throw new ZError(11, 'sign not match')
@ -71,7 +71,9 @@ export default class InternalController extends BaseController {
record.hashList = hashList
task.txHash = hashList[0]
task.status = PayStatus.TRANSFERED
setImmediate(notify.apply(this, [task]))
setImmediate(async () => {
await notify(task, record)
})
} else {
record.status = 10
task.status = PayStatus.TRANSFER_FAIL

View File

@ -78,8 +78,8 @@ export class PayRecordClass extends BaseModule {
@prop()
public txHash?: string
public static async findByRecordId(this: ReturnModelType<typeof PayRecordClass>, outOrderid: string) {
return this.findOne({ outOrderid }).exec()
public static async findByRecordId(this: ReturnModelType<typeof PayRecordClass>, outOrderId: string) {
return this.findOne({ outOrderId }).exec()
}
}

View File

@ -40,7 +40,7 @@ export class TransferQueue {
assert(chainCfg, `chain config not found: ${task.network}`)
let chainId = chainCfg.chainId
let wallet = chainCfg.wallet
let address = chainCfg.tokens[task.crypto]
let address = chainCfg.tokens[task.crypto.toLowerCase()]
assert(address, `token address not found: ${task.crypto}`)
let record = await TransferRecord.insertOrUpdate(
{ recordId: task.id },

View File

@ -1,13 +1,31 @@
import axios from 'axios'
import { hmacsha256, sha1 } from 'utils/security.util'
import crypto from 'crypto'
import { generateKVStr } from 'utils/net.util'
export function createSimpleSign() {
export function createSimpleSign(data: any) {
let timestamp = Date.now()
let appid = process.env.ALCHEMY_APPID
let secret = process.env.ALCHEMY_APP_SECRET
// let sign = sha1(appid + secret + timestamp)
let sign = hmacsha256(appid + timestamp, secret)
let signData = { appid, timestamp }
signData = Object.assign(signData, data)
let signStr = Object.keys(signData)
.sort()
.map(key => `${key}=${signData[key]}`)
.join('&')
let sign = hmacsha256(signStr, secret)
return {
appid,
timestamp,
sign,
}
}
export function createSha1Sign() {
let timestamp = Date.now()
let appid = process.env.ALCHEMY_APPID
let secret = process.env.ALCHEMY_APP_SECRET
let sign = sha1(appid + secret + timestamp)
return {
appid,
timestamp,
@ -26,11 +44,18 @@ export function checkPayResultSign(data: any) {
return sign === signature
}
export function checkSimpleSign(data: any) {
export function checkSimpleSign(headers: any, data: any) {
// alchemy 很不严谨, 有时候是 appid, 有时候是 appId
const { appid, appId, timestamp, sign } = data
const { appid, appId, timestamp, sign } = headers
let appIdToCheck = appId || appid
const expectedSign = sha1(appIdToCheck + process.env.ALCHEMY_APP_SECRET + timestamp)
let signData = { appid: appIdToCheck, timestamp }
signData = Object.assign(signData, data)
let signStr = Object.keys(signData)
.sort()
.map(key => `${key}=${signData[key]}`)
.join('&')
const expectedSign = hmacsha256(signStr, process.env.ALCHEMY_APP_SECRET)
// const expectedSign = sha1(appIdToCheck + process.env.ALCHEMY_APP_SECRET + timestamp)
return sign === expectedSign
}
@ -66,11 +91,11 @@ export function createPageSign(plainText: string) {
*/
export async function refreshToken(email: string) {
const data = JSON.stringify({ email })
const { appid, timestamp, sign } = createSimpleSign()
const { appid, timestamp, sign } = createSimpleSign({ email })
const host = process.env.ALCHEMY_API_BASE
const config = {
method: 'post',
url: `${host}/merchant/getToken`,
url: `${host}/open/api/v3/merchant/getToken`,
headers: {
appId: appid,
timestamp: timestamp,
@ -87,11 +112,11 @@ export async function refreshToken(email: string) {
* https://alchemycn.readme.io/docs/创建订单
*/
export async function createOrder(token: string, data: any) {
const { appid, timestamp, sign } = createSimpleSign()
const { appid, timestamp, sign } = createSimpleSign(data)
const host = process.env.ALCHEMY_API_BASE
const config = {
method: 'post',
url: `${host}/merchant/trade/create`,
url: `${host}/open/api/v3/merchant/trade/create`,
headers: {
'access-token': token,
appId: appid,
@ -112,7 +137,7 @@ export async function createOrder(token: string, data: any) {
* TODO:: test
*/
export async function updateOrderStatus(data: any) {
const { appid, timestamp, sign } = createSimpleSign()
const { appid, timestamp, sign } = createSha1Sign()
const url = process.env.ALCHEMY_API_BASE + '/webhooks/treasure'
const config = {
method: 'post',
@ -133,7 +158,6 @@ export async function updateOrderStatus(data: any) {
* https://alchemycn.readme.io/docs/查询数字货币币价
*/
export async function queryPrice(data: any) {
const { appid, timestamp, sign } = createSimpleSign()
let dataOrign = {
crypto: 'ETH',
network: 'ETH',
@ -143,9 +167,10 @@ export async function queryPrice(data: any) {
side: 'BUY',
}
dataOrign = { ...dataOrign, ...data }
const { appid, timestamp, sign } = createSimpleSign(dataOrign)
const config = {
method: 'post',
url: process.env.ALCHEMY_API_BASE + '/merchant/order/quote',
url: process.env.ALCHEMY_API_BASE + '/open/api/v3/merchant/order/quote',
headers: {
'Content-Type': 'application/json',
appId: appid,
@ -164,20 +189,21 @@ export async function queryPrice(data: any) {
* @returns
*/
export async function queryFiat() {
const { appid, timestamp, sign } = createSimpleSign()
let dataOrign = {
type: 'BUY',
}
const { appid, timestamp, sign } = createSimpleSign(dataOrign)
let url = process.env.ALCHEMY_API_BASE + '/open/api/v3/merchant/fiat/list'
url = generateKVStr({ data: dataOrign, encode: true, uri: url })
const config = {
method: 'GET',
url: process.env.ALCHEMY_API_BASE + '/merchant/fiat/list',
url,
headers: {
'Content-Type': 'application/json',
appId: appid,
sign,
timestamp,
},
data: dataOrign,
}
let response = await axios(config)
return response.data

View File

@ -5,7 +5,7 @@
* @param splitChar , &
* @param equalChar =
*/
export function generateKeyValStr(data: {}, ignoreNull = true, splitChar: string = '&', equalChar = '=') {
export function generateKeyValStr(data: Record<string, any>, ignoreNull = true, splitChar = '&', equalChar = '=') {
const keys = Object.keys(data)
keys.sort()
let result = ''
@ -15,7 +15,7 @@ export function generateKeyValStr(data: {}, ignoreNull = true, splitChar: string
return
}
if (i++ > 0) result += splitChar
result += `${key}${equalChar}${data[key]}`
result += `${key}${equalChar}${data[key]}`
}
return result
}
@ -47,15 +47,15 @@ export function keyValToObject(str: string, splitChar: string = '&', equalChar =
export function isTrue(obj) {
return (
obj === 'true' ||
obj === 'TRUE' ||
obj === 'True' ||
obj === 'on' ||
obj === 'ON' ||
obj === true ||
obj === 1 ||
obj === '1' ||
obj === 'YES' ||
obj === 'yes'
obj === 'TRUE' ||
obj === 'True' ||
obj === 'on' ||
obj === 'ON' ||
obj === true ||
obj === 1 ||
obj === '1' ||
obj === 'YES' ||
obj === 'yes'
)
}
@ -84,7 +84,7 @@ export function string10to62(number: string | number) {
qutient = (qutient - mod) / radix
arr.unshift(chars[mod])
} while (qutient)
return arr.join('')
return arr.join('')
}
/**
@ -105,32 +105,33 @@ export function string62to10(numberCode: string) {
return originNumber
}
const reNormalUUID = /^[0-9a-fA-F-]{36}$/;
const reLongUUID = /^[0-9a-fA-F]{32}$/;
const reShortUUID = /^[0-9a-zA-Z+/]{22,23}$/;
const n = /-/g;
const reNormalUUID = /^[0-9a-fA-F-]{36}$/
const reLongUUID = /^[0-9a-fA-F]{32}$/
const reShortUUID = /^[0-9a-zA-Z+/]{22,23}$/
const n = /-/g
export function compressUuid(e:string, t: boolean = false) {
export function compressUuid(e: string, t: boolean = false) {
if (reNormalUUID.test(e)) {
e = e.replace(n, '');
e = e.replace(n, '')
} else if (!reLongUUID.test(e)) {
return e;
return e
}
var r = !0 === t ? 2 : 5;
var r = !0 === t ? 2 : 5
return compressHex(e, r)
}
}
const CHARS_BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
const CHARS_BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
export function compressHex(e: string, r: number) {
var i, n = e.length;
i = void 0 !== r ? r : n % 3;
for (var s = e.slice(0, i), o = []; i < n;) {
var i,
n = e.length
i = void 0 !== r ? r : n % 3
for (var s = e.slice(0, i), o = []; i < n; ) {
var u = parseInt(e[i], 16),
a = parseInt(e[i + 1], 16),
c = parseInt(e[i + 2], 16);
o.push(CHARS_BASE64[u << 2 | a >> 2]);
o.push(CHARS_BASE64[(3 & a) << 4 | c]);
i += 3;
c = parseInt(e[i + 2], 16)
o.push(CHARS_BASE64[(u << 2) | (a >> 2)])
o.push(CHARS_BASE64[((3 & a) << 4) | c])
i += 3
}
return s + o.join('')
}