完善支付流程

This commit is contained in:
zhl 2023-06-01 13:04:38 +08:00
parent 5220dd2232
commit 476da08a8f
11 changed files with 475 additions and 38 deletions

View File

@ -18,6 +18,21 @@ ALCHEMY_API_BASE="https://openapi-test.alchemypay.org"
ALCHEMY_PAGE_BASE="https://ramptest.alchemypay.org" ALCHEMY_PAGE_BASE="https://ramptest.alchemypay.org"
ALCHEMY_PAY_CB_URL="https://wallet.cebggame.com" ALCHEMY_PAY_CB_URL="https://wallet.cebggame.com"
CHAIN_CLIENT_URL=http://127.0.0.1:3006
#cryptocompare api key from metamask^_^ #cryptocompare api key from metamask^_^
CRYPTOCOMPARE_API_KEY=d1ec8cd68228095debc9db2dca45771b905ce1f27f522ebfef025c236f4aef3b CRYPTOCOMPARE_API_KEY=d1ec8cd68228095debc9db2dca45771b905ce1f27f522ebfef025c236f4aef3b
# alchemy 相关配置
AVAILABLE_NETWORK=arbitrum|bsc
AVAILABLE_TOKENS=cec|ceg
ARBITRUM_CHAIN_ID=421163
ARBITRUM_CEC_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
ARBITRUM_CEG_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
ARBITRUM_WALLET=''
BSC_CHAIN_ID=97
BSC_CEC_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
BSC_CEG_ADDRESS='0xaa34B79A0Ab433eaC900fB3CB9f191F5Cd27501D'
BSC_WALLET=''
# 链端转账回调地址
PAY_TRANSFER_CB_URL=''

View File

@ -30,7 +30,18 @@ const publicKey = `
${process.env.API_TOKEN_SECRET_PUBLIC} ${process.env.API_TOKEN_SECRET_PUBLIC}
-----END PUBLIC KEY----- -----END PUBLIC KEY-----
` `
let networks = process.env.AVAILABLE_NETWORK.split('|')
let tokenList = process.env.AVAILABLE_TOKENS.split('|')
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()}`]
for (let sub of tokenList) {
data.tokens[sub] = process.env[`ADDRESS_${sub.toUpperCase()}_${network.toUpperCase()}`]
}
chainCfgs[network] = data
}
let baseConfig = { let baseConfig = {
api: { api: {
port: parseInt(process.env.API_PORT), port: parseInt(process.env.API_PORT),
@ -42,6 +53,7 @@ let baseConfig = {
db_main: process.env.DB_MAIN, db_main: process.env.DB_MAIN,
db_second: process.env.DB_SECOND, db_second: process.env.DB_SECOND,
chainCfgs: chainCfgs,
} }
export default baseConfig export default baseConfig

View File

@ -2,7 +2,7 @@ import logger from 'logger/logger'
import BaseController from 'common/base.controller' import BaseController from 'common/base.controller'
import { ZError } from 'common/ZError' import { ZError } from 'common/ZError'
import { router } from 'decorators/router' import { router } from 'decorators/router'
import { createPageSign, queryPrice, refreshToken } from 'service/alchemy.svr' import { createPageSign, queryFiat, queryPrice, refreshToken } from 'service/alchemy.svr'
import { generateKVStr } from 'utils/net.util' import { generateKVStr } from 'utils/net.util'
import { PayRecord, PayStatus } from 'modules/PayRecord' import { PayRecord, PayStatus } from 'modules/PayRecord'
import { PriceSvr } from 'service/price.svr' import { PriceSvr } from 'service/price.svr'
@ -12,7 +12,17 @@ class AlchemyController extends BaseController {
@router('post /pay/alchemy/buy') @router('post /pay/alchemy/buy')
async beginPay(req, res) { async beginPay(req, res) {
const user = req.user const user = req.user
const { chain, currency, address } = req.params 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) const tokenResult = await refreshToken(user.emailReal || user.email)
console.log(tokenResult) console.log(tokenResult)
if (!tokenResult.success || tokenResult.returnCode !== '0000') { if (!tokenResult.success || tokenResult.returnCode !== '0000') {
@ -20,7 +30,10 @@ class AlchemyController extends BaseController {
throw new ZError(10, 'fetch pay token error') throw new ZError(10, 'fetch pay token error')
} }
const { id, email, accessToken } = tokenResult.data const { id, email, accessToken } = tokenResult.data
let record = new PayRecord({ account: user.id, address, chain, currency }) 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() await record.save()
const merchantOrderNo = record.id const merchantOrderNo = record.id
let dataOrign: any = { let dataOrign: any = {
@ -30,8 +43,11 @@ class AlchemyController extends BaseController {
showTable: 'buy', showTable: 'buy',
merchantOrderNo, merchantOrderNo,
} }
if (chain) dataOrign.network = chain if (network) dataOrign.network = network
if (currency) dataOrign.crypto = currency if (crypto) dataOrign.crypto = crypto
if (fiat) dataOrign.fiat = fiat
if (fiatAmount) dataOrign.fiatAmount = fiatAmount
if (country) dataOrign.country = country
let dataSign: any = { let dataSign: any = {
appId: process.env.ALCHEMY_APPID, appId: process.env.ALCHEMY_APPID,
address, address,
@ -49,10 +65,16 @@ class AlchemyController extends BaseController {
@router('post /pay/alchemy/crypto_price') @router('post /pay/alchemy/crypto_price')
async queryCryptoPrice(req, res) { async queryCryptoPrice(req, res) {
let { token, chain, currency } = req.params let { token, chain, currency, env } = req.params
if (!token || !chain) { if (!token || !chain) {
throw new ZError(11, 'token or network not found') throw new ZError(11, 'token or network not found')
} }
if (env.toLowerCase() === 'dev' && (token.toLowerCase() === 'ceg' || token.toLowerCase() === 'cec')) {
return { price: 1 }
}
if (token.toLowerCase() === 'ceg') {
return { price: 1 }
}
let data = { let data = {
crypto: token, crypto: token,
network: chain, network: chain,
@ -61,4 +83,13 @@ class AlchemyController extends BaseController {
let result = await new PriceSvr().fetchPrice(data) let result = await new PriceSvr().fetchPrice(data)
return { price: result } return { price: result }
} }
@router('get /pay/alchemy/fait_list')
async cryptoList(req, res) {
let result = await queryFiat()
if (!result.success) {
throw new ZError(10, result.returnMsg || 'fetch fiat list error')
}
return result.data
}
} }

View File

@ -1,10 +1,20 @@
import logger from 'logger/logger' import logger from 'logger/logger'
import BaseController, { ROLE_ANON } from 'common/base.controller' import BaseController, { ROLE_ANON } from 'common/base.controller'
import { ZError } from 'common/ZError' import { ZError } from 'common/ZError'
import { role, router } from 'decorators/router' import { role, router } from 'decorators/router'
import { checkPayResultSign } from 'service/alchemy.svr' import { checkPayResultSign, checkSimpleSign } from 'service/alchemy.svr'
import { PayRecord, PayStatus } from 'modules/PayRecord' import { PayRecord, PayStatus } from 'modules/PayRecord'
import { TransferQueue } from 'queue/transfer.queue'
let errorRes = function (msg: string) {
return {
direct: 1,
data: null,
success: false,
returnCode: '9999',
returnMsg: msg,
}
}
/** /**
* for Alchemy call * for Alchemy call
*/ */
@ -22,7 +32,7 @@ class AlchemyOutController extends BaseController {
logger.info(`alchemy callback record not found`) logger.info(`alchemy callback record not found`)
throw new ZError(12, 'alchemy callback record not found') throw new ZError(12, 'alchemy callback record not found')
} }
if (record.status !== PayStatus.PENDING) { if (record.status !== PayStatus.PENDING && record.status !== PayStatus.TRANSFERING) {
logger.info(`alchemy callback record status error`) logger.info(`alchemy callback record status error`)
throw new ZError(13, 'alchemy callback record status error') throw new ZError(13, 'alchemy callback record status error')
} }
@ -34,8 +44,8 @@ class AlchemyOutController extends BaseController {
} }
record.outOrderId = orderNo record.outOrderId = orderNo
record.chain = network record.network = network
record.currency = crypto record.crypto = crypto
record.outData = req.params record.outData = req.params
record.status = status == 'PAY_SUCCESS' ? PayStatus.SUCCESS : PayStatus.FAIL record.status = status == 'PAY_SUCCESS' ? PayStatus.SUCCESS : PayStatus.FAIL
await record.save() await record.save()
@ -51,20 +61,29 @@ class AlchemyOutController extends BaseController {
async queryToken(req, res) { async queryToken(req, res) {
const { crypto } = req.params const { crypto } = req.params
const { appId, timestamp, sign } = req.headers const { appId, timestamp, sign } = req.headers
if (!crypto) {
return errorRes('params mismatch')
}
if (!appId || !timestamp || !sign) {
return errorRes('headers mismatch')
}
if (!checkSimpleSign(req.headers)) {
return errorRes('sign error')
}
let result = { let result = {
direct: 1, direct: 1,
data: { data: {
price: "1.0", price: '1.0',
networkList: [ networkList: [
{ {
network: "ETH", network: 'ETH',
networkFee: "1.21" networkFee: '1.21',
} },
] ],
}, },
success: true, success: true,
returnCode: "0000", // false: 9999 returnCode: '0000', // false: 9999
returnMsg: "in amet", returnMsg: 'in amet',
} }
return result return result
} }
@ -76,16 +95,37 @@ class AlchemyOutController extends BaseController {
@role(ROLE_ANON) @role(ROLE_ANON)
@router('post /pay/out/alchemy/distribute') @router('post /pay/out/alchemy/distribute')
async distributeToken(req, res) { async distributeToken(req, res) {
const { appId, timestamp, sign } = req.headers
const { orderNo, crypto, network, address, cryptoAmount, cryptoPrice, usdtAmount } = req.params const { orderNo, crypto, network, address, cryptoAmount, cryptoPrice, usdtAmount } = req.params
const { appId, timestamp, sign } = req.headers
if (!orderNo || !crypto || !network || !address || !cryptoAmount || !cryptoPrice || !usdtAmount) {
return errorRes('params mismatch')
}
if (!appId || !timestamp || !sign) {
return errorRes('headers mismatch')
}
if (!checkSimpleSign(req.headers)) {
return errorRes('sign error')
}
let record = await PayRecord.findByRecordId(orderNo)
if (!record) {
return errorRes('orderNo not found')
}
if (record.crypto != crypto || record.network != network || record.address != address) {
return errorRes('params mismatch')
}
record.cryptoAmount = cryptoAmount
record.cryptoPrice = cryptoPrice
record.usdtAdmount = usdtAmount
await record.save()
new TransferQueue().addTask(record)
let result = { let result = {
direct: 1, direct: 1,
data: null, data: null,
success: true, success: true,
returnCode: "0000", // false: 9999 returnCode: '0000', // false: 9999
returnMsg: "in amet", returnMsg: 'in amet',
} }
res.send(result) res.send(result)
} }
} }

View File

@ -0,0 +1,83 @@
import { ZError } from 'common/ZError'
import BaseController, { ROLE_ANON } from 'common/base.controller'
import { role, router } from 'decorators/router'
import logger from 'logger/logger'
import { PayRecord, PayRecordClass, PayStatus } from 'modules/PayRecord'
import { TransferRecord, TransferRecordClass } from 'modules/TransferRecord'
import { hmacsha256 } from 'utils/security.util'
import { DocumentType } from '@typegoose/typegoose'
import { queryPrice, updateOrderStatus } from 'service/alchemy.svr'
const calcHash = function (data: any) {
let signStr = JSON.stringify(data)
return hmacsha256(signStr, process.env.HASH_SALT)
}
const notify = async function (record: DocumentType<PayRecordClass>, subTask: DocumentType<TransferRecordClass>) {
let data: any = {
orderNo: record.outOrderId, // AlchemyPay订单号
crypto: record.crypto, // 用户购买的数字货币
cryptoAmount: record.cryptoAmount, //用户的提币数量
cryptoPrice: record.cryptoPrice, // 实时的价格/USDT CEG锚定USDT
txHash: record.txHash, // 给用户转账的hash
network: record.network, // 用户购买的数字货币对应的网络
// networkFee: record.networkFee, // 网络费用/USDT
address: record.address, // 用户的提币地址
status: 'SUCCESS', // SUCCESS/FAIL
}
try {
let priceData = await queryPrice({ gas: subTask.gas })
data.networkFee = priceData.leagel
let result = await updateOrderStatus(data)
logger.info('update transfer status success::', JSON.stringify(result))
if (result.success) {
subTask.status = 8
await subTask.save()
}
} catch (err) {
logger.error(`notify alchemy error:: ${err.message}`)
}
}
export default class InternalController extends BaseController {
@role(ROLE_ANON)
@router('post /api/internal/update_task')
async updateTaskInfo(req) {
let { sign, id, result, successCount, errorCount, gas, gasPrice, hashList } = req.params
if (!sign) {
throw new ZError(10, 'sign not found')
}
let hash = calcHash({ id, result, successCount, errorCount, hashList })
console.log(hash, sign)
if (sign !== hash) {
throw new ZError(11, 'sign not match')
}
logger.info(`task report:: ${id}|${result}|${successCount}|${errorCount}|${JSON.stringify(hashList)}}`)
if (!id) {
throw new ZError(11, 'taskId not found')
}
let record = await TransferRecord.findById(id)
if (!record) {
throw new ZError(12, 'TransferRecord not found')
}
let task = await PayRecord.findById(record.recordId)
if (!task) {
throw new ZError(13, 'PayRecord not found')
}
if (result === 2) {
record.status = 9
record.gas = gas
record.gasPrice = gasPrice
record.hashList = hashList
task.txHash = hashList[0]
task.status = PayStatus.TRANSFERED
setImmediate(notify.apply(this, [task]))
} else {
record.status = 10
task.status = PayStatus.TRANSFER_FAIL
}
await record.save()
await task.save()
return {}
}
}

View File

@ -1,4 +1,4 @@
import { getModelForClass, index, modelOptions, mongoose, prop, Severity } from '@typegoose/typegoose' import { getModelForClass, index, modelOptions, mongoose, prop, ReturnModelType, Severity } from '@typegoose/typegoose'
import { dbconn } from 'decorators/dbconn' import { dbconn } from 'decorators/dbconn'
import { BaseModule } from './Base' import { BaseModule } from './Base'
@ -13,16 +13,20 @@ export enum PayType {
export enum PayStatus { export enum PayStatus {
PENDING = 0, PENDING = 0,
SUCCESS = 1, TRANSFERING = 1, //只有国库模式才会有该状态
FAIL = 2, TRANSFERED = 2, //只有国库模式才会有该状态
SUCCESS = 9,
TRANSFER_FAIL = 98, // 转账错误
FAIL = 99,
} }
@dbconn() @dbconn()
@index({ outOrderId: 1 }, { unique: true, partialFilterExpression: { outOrderId: { $exists: true } } })
@modelOptions({ @modelOptions({
schemaOptions: { collection: 'pay_record', timestamps: true }, schemaOptions: { collection: 'pay_record', timestamps: true },
options: { allowMixed: Severity.ALLOW }, options: { allowMixed: Severity.ALLOW },
}) })
class PayRecordClass extends BaseModule { export class PayRecordClass extends BaseModule {
@prop({ enum: PayPlatEnum, default: PayPlatEnum.ALCHEMY }) @prop({ enum: PayPlatEnum, default: PayPlatEnum.ALCHEMY })
public channel!: PayPlatEnum public channel!: PayPlatEnum
@ -36,10 +40,30 @@ class PayRecordClass extends BaseModule {
public address: string public address: string
@prop() @prop()
public chain: string public network?: string
@prop() @prop()
public currency?: string public crypto?: string
// 法币
@prop()
public fiat?: string
// 法币数量
@prop()
public fiatAmount?: string
// 加密货币数量
@prop()
public cryptoAmount?: string
// 加密货币价格
@prop()
public cryptoPrice?: string
// 该笔交易渠道会给我们多少usdt
@prop()
public usdtAdmount?: string
// 国家
@prop()
public country?: string
@prop({ required: true, default: PayStatus.PENDING }) @prop({ required: true, default: PayStatus.PENDING })
public status: PayStatus public status: PayStatus
@ -52,7 +76,11 @@ class PayRecordClass extends BaseModule {
public outOrderId: string public outOrderId: string
// 交易的txHash // 交易的txHash
@prop() @prop()
public txHash: string public txHash?: string
public static async findByRecordId(this: ReturnModelType<typeof PayRecordClass>, outOrderid: string) {
return this.findOne({ outOrderid }).exec()
}
} }
export const PayRecord = getModelForClass(PayRecordClass, { existingConnection: PayRecordClass.db }) export const PayRecord = getModelForClass(PayRecordClass, { existingConnection: PayRecordClass.db })

View File

@ -0,0 +1,73 @@
import { ReturnModelType, getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
import { dbconn } from 'decorators/dbconn'
import { BaseModule } from './Base'
import { PayType } from './PayRecord'
/**
*
*/
@dbconn()
@index({ recordId: 1 }, { unique: true })
@modelOptions({ schemaOptions: { collection: 'pay_transfer_record', timestamps: true } })
export class TransferRecordClass extends BaseModule {
@prop({ required: true })
public account: string
// pay_record 的 id
@prop()
public recordId: string
@prop({ required: true, default: PayType.BUY })
public type: PayType
@prop()
public chain: number
// token 的合约地址
@prop()
public contract: string
// 用户的钱包地址
@prop()
public to: string
// 转出钱包
@prop()
public from: string
@prop()
public amount: string
@prop()
public gas: string
@prop()
public gasPrice: string
/**
* 0: 队列中,
* 1: 已请求上链,
* 2: 成功上链,
* 9: 已确认成功()
* 8: 已上报状态
* 10: 失败
* 11: 部分失败
*/
@prop({ default: 0 })
public status: number
@prop({ default: 0 })
public errorCount: number
@prop({ type: () => [String] })
public hashList: string[]
@prop({ default: 0 })
public version: number
public static async findByRecordId(this: ReturnModelType<typeof TransferRecordClass>, recordId: string) {
return this.findOne({ recordId }).exec()
}
}
export const TransferRecord = getModelForClass(TransferRecordClass, { existingConnection: TransferRecordClass.db })

View File

@ -0,0 +1,82 @@
import { singleton } from 'decorators/singleton'
import { AsyncQueue, createAsyncQueue } from 'common/AsyncQueue'
import { DocumentType } from '@typegoose/typegoose'
import { PayRecordClass } from 'modules/PayRecord'
import logger from 'logger/logger'
import { pushTaskToChain } from 'service/chain.svr'
import { TransferRecord } from 'modules/TransferRecord'
import config from 'config/config'
import assert from 'assert'
/**
* let data = {
taskId: '1',
source: 'pay',
cb: 'status update callback url',
data: [
{
address: '0xb592244aa6477eBDDc14475aaeF921cdDcC0170f',
from: '',
to: '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1',
amount: 1000,
type: 3
},
],
}
*/
@singleton
export class TransferQueue {
private queue: AsyncQueue
constructor() {
this.queue = createAsyncQueue()
}
public addTask(task: DocumentType<PayRecordClass>) {
this.queue.push(async () => {
try {
let chainCfg = config.chainCfgs[task.network.toLowerCase()]
assert(chainCfg, `chain config not found: ${task.network}`)
let chainId = chainCfg.chainId
let wallet = chainCfg.wallet
let address = chainCfg.tokens[task.crypto]
assert(address, `token address not found: ${task.crypto}`)
let record = await TransferRecord.insertOrUpdate(
{ recordId: task.id },
{
account: task.account,
chain: chainId,
contract: address,
to: task.address,
from: wallet,
amount: task.cryptoAmount,
$inc: { version: 1 },
},
)
let datas: any = [
{
chain: record.chain,
address: record.contract,
from: record.from,
to: record.to,
amount: record.amount,
type: 3,
},
]
let reqData = {
taskId: record.id,
source: 'pay',
data: datas,
cb: process.env.PAY_TRANSFER_CB_URL,
}
await pushTaskToChain(reqData)
await task.save()
} catch (err) {
logger.error('error add chain task: ')
logger.error(err)
}
})
}
}

View File

@ -1,12 +1,13 @@
import axios from 'axios' import axios from 'axios'
import { sha1 } from 'utils/security.util' import { hmacsha256, sha1 } from 'utils/security.util'
import crypto from 'crypto' import crypto from 'crypto'
export function createSimpleSign() { export function createSimpleSign() {
let timestamp = Date.now() let timestamp = Date.now()
let appid = process.env.ALCHEMY_APPID let appid = process.env.ALCHEMY_APPID
let secret = process.env.ALCHEMY_APP_SECRET let secret = process.env.ALCHEMY_APP_SECRET
let sign = sha1(appid + secret + timestamp) // let sign = sha1(appid + secret + timestamp)
let sign = hmacsha256(appid + timestamp, secret)
return { return {
appid, appid,
timestamp, timestamp,
@ -21,10 +22,18 @@ export function createSimpleSign() {
*/ */
export function checkPayResultSign(data: any) { export function checkPayResultSign(data: any) {
const { appId, orderNo, crypto, network, address, signature } = data const { appId, orderNo, crypto, network, address, signature } = data
const sign = sha1(appId + process.env.ALCHEMY_APP_SECRET + appId + orderNo + crypto + network + address) const sign = hmacsha256(appId + orderNo + crypto + network + address, process.env.ALCHEMY_APP_SECRET)
return sign === signature return sign === signature
} }
export function checkSimpleSign(data: any) {
// alchemy 很不严谨, 有时候是 appid, 有时候是 appId
const { appid, appId, timestamp, sign } = data
let appIdToCheck = appId || appid
const expectedSign = sha1(appIdToCheck + process.env.ALCHEMY_APP_SECRET + timestamp)
return sign === expectedSign
}
/** /**
* Create page sign * Create page sign
* @param plainText - plain text to be encrypted * @param plainText - plain text to be encrypted
@ -51,6 +60,7 @@ export function createPageSign(plainText: string) {
/** /**
* Refresh token * Refresh token
* https://alchemycn.readme.io/docs/获取token
* @param email - user email * @param email - user email
* @returns token * @returns token
*/ */
@ -101,7 +111,7 @@ export async function createOrder(token: string, data: any) {
* https://alchemycn.readme.io/docs/更新订单状态 * https://alchemycn.readme.io/docs/更新订单状态
* TODO:: test * TODO:: test
*/ */
export async function updateOrderStatus(token: string, data: any) { export async function updateOrderStatus(data: any) {
const { appid, timestamp, sign } = createSimpleSign() const { appid, timestamp, sign } = createSimpleSign()
const url = process.env.ALCHEMY_API_BASE + '/webhooks/treasure' const url = process.env.ALCHEMY_API_BASE + '/webhooks/treasure'
const config = { const config = {
@ -109,7 +119,6 @@ export async function updateOrderStatus(token: string, data: any) {
url, url,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'access-token': token,
appId: appid, appId: appid,
sign: sign, sign: sign,
timestamp, timestamp,
@ -121,6 +130,7 @@ export async function updateOrderStatus(token: string, data: any) {
} }
/** /**
* *
* https://alchemycn.readme.io/docs/查询数字货币币价
*/ */
export async function queryPrice(data: any) { export async function queryPrice(data: any) {
const { appid, timestamp, sign } = createSimpleSign() const { appid, timestamp, sign } = createSimpleSign()
@ -147,3 +157,28 @@ export async function queryPrice(data: any) {
let response = await axios(config) let response = await axios(config)
return response.data return response.data
} }
/**
*
* https://alchemycn.readme.io/docs/查询法币及支付方式
* @returns
*/
export async function queryFiat() {
const { appid, timestamp, sign } = createSimpleSign()
let dataOrign = {
type: 'BUY',
}
const config = {
method: 'GET',
url: process.env.ALCHEMY_API_BASE + '/merchant/fiat/list',
headers: {
'Content-Type': 'application/json',
appId: appid,
sign,
timestamp,
},
data: dataOrign,
}
let response = await axios(config)
return response.data
}

31
src/service/chain.svr.ts Normal file
View File

@ -0,0 +1,31 @@
import axios from 'axios'
const ADD_TASK_URI = '/chain/req'
const GAS_PRICE_URI = '/chain/estimate_gas'
export async function pushTaskToChain(data) {
let url = `${process.env.CHAIN_CLIENT_URL}${ADD_TASK_URI}`
let reqConfig: any = {
method: 'post',
url,
headers: {
'Content-Type': 'application/json',
},
data: JSON.stringify(data),
}
return axios(reqConfig)
}
export async function fetchGasPrice(data) {
let url = `${process.env.CHAIN_CLIENT_URL}${GAS_PRICE_URI}`
let reqConfig: any = {
method: 'post',
url,
headers: {
'Content-Type': 'application/json',
},
data: JSON.stringify(data),
}
return axios(reqConfig)
}

View File

@ -24,6 +24,13 @@ export function aesDecrypt(encryptedText: string, password: string, iv: string)
return decrypted + decipher.final('utf8') return decrypted + decipher.final('utf8')
} }
export function hmacsha256(text: string, secret: string) {
const mac = crypto.createHmac('sha256', secret)
const data = mac.update(text).digest('hex').toLowerCase()
console.log(`HmacSHA256 rawContent is [${text}], key is [${secret}], hash result is [${data}]`)
return data
}
export function sha512(password: string, salt: string) { export function sha512(password: string, salt: string) {
let hash = crypto.createHmac('sha512', salt) let hash = crypto.createHmac('sha512', salt)
hash.update(password) hash.update(password)