完善支付流程
This commit is contained in:
parent
5220dd2232
commit
476da08a8f
@ -18,6 +18,21 @@ ALCHEMY_API_BASE="https://openapi-test.alchemypay.org"
|
||||
ALCHEMY_PAGE_BASE="https://ramptest.alchemypay.org"
|
||||
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=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=''
|
@ -30,7 +30,18 @@ const publicKey = `
|
||||
${process.env.API_TOKEN_SECRET_PUBLIC}
|
||||
-----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 = {
|
||||
api: {
|
||||
port: parseInt(process.env.API_PORT),
|
||||
@ -42,6 +53,7 @@ let baseConfig = {
|
||||
|
||||
db_main: process.env.DB_MAIN,
|
||||
db_second: process.env.DB_SECOND,
|
||||
chainCfgs: chainCfgs,
|
||||
}
|
||||
|
||||
export default baseConfig
|
||||
|
@ -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, queryPrice, refreshToken } from 'service/alchemy.svr'
|
||||
import { 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'
|
||||
@ -12,7 +12,17 @@ class AlchemyController extends BaseController {
|
||||
@router('post /pay/alchemy/buy')
|
||||
async beginPay(req, res) {
|
||||
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)
|
||||
console.log(tokenResult)
|
||||
if (!tokenResult.success || tokenResult.returnCode !== '0000') {
|
||||
@ -20,7 +30,10 @@ class AlchemyController extends BaseController {
|
||||
throw new ZError(10, 'fetch pay token error')
|
||||
}
|
||||
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()
|
||||
const merchantOrderNo = record.id
|
||||
let dataOrign: any = {
|
||||
@ -30,8 +43,11 @@ class AlchemyController extends BaseController {
|
||||
showTable: 'buy',
|
||||
merchantOrderNo,
|
||||
}
|
||||
if (chain) dataOrign.network = chain
|
||||
if (currency) dataOrign.crypto = currency
|
||||
if (network) dataOrign.network = network
|
||||
if (crypto) dataOrign.crypto = crypto
|
||||
if (fiat) dataOrign.fiat = fiat
|
||||
if (fiatAmount) dataOrign.fiatAmount = fiatAmount
|
||||
if (country) dataOrign.country = country
|
||||
let dataSign: any = {
|
||||
appId: process.env.ALCHEMY_APPID,
|
||||
address,
|
||||
@ -49,10 +65,16 @@ class AlchemyController extends BaseController {
|
||||
|
||||
@router('post /pay/alchemy/crypto_price')
|
||||
async queryCryptoPrice(req, res) {
|
||||
let { token, chain, currency } = req.params
|
||||
let { token, chain, currency, env } = req.params
|
||||
if (!token || !chain) {
|
||||
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 = {
|
||||
crypto: token,
|
||||
network: chain,
|
||||
@ -61,4 +83,13 @@ class AlchemyController extends BaseController {
|
||||
let result = await new PriceSvr().fetchPrice(data)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,20 @@
|
||||
|
||||
import logger from 'logger/logger'
|
||||
import BaseController, { ROLE_ANON } from 'common/base.controller'
|
||||
import { ZError } from 'common/ZError'
|
||||
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 { TransferQueue } from 'queue/transfer.queue'
|
||||
|
||||
let errorRes = function (msg: string) {
|
||||
return {
|
||||
direct: 1,
|
||||
data: null,
|
||||
success: false,
|
||||
returnCode: '9999',
|
||||
returnMsg: msg,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* for Alchemy call
|
||||
*/
|
||||
@ -22,7 +32,7 @@ class AlchemyOutController extends BaseController {
|
||||
logger.info(`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`)
|
||||
throw new ZError(13, 'alchemy callback record status error')
|
||||
}
|
||||
@ -34,8 +44,8 @@ class AlchemyOutController extends BaseController {
|
||||
}
|
||||
|
||||
record.outOrderId = orderNo
|
||||
record.chain = network
|
||||
record.currency = crypto
|
||||
record.network = network
|
||||
record.crypto = crypto
|
||||
record.outData = req.params
|
||||
record.status = status == 'PAY_SUCCESS' ? PayStatus.SUCCESS : PayStatus.FAIL
|
||||
await record.save()
|
||||
@ -51,20 +61,29 @@ class AlchemyOutController extends BaseController {
|
||||
async queryToken(req, res) {
|
||||
const { crypto } = req.params
|
||||
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 = {
|
||||
direct: 1,
|
||||
data: {
|
||||
price: "1.0",
|
||||
price: '1.0',
|
||||
networkList: [
|
||||
{
|
||||
network: "ETH",
|
||||
networkFee: "1.21"
|
||||
}
|
||||
]
|
||||
},
|
||||
network: 'ETH',
|
||||
networkFee: '1.21',
|
||||
},
|
||||
],
|
||||
},
|
||||
success: true,
|
||||
returnCode: "0000", // false: 9999
|
||||
returnMsg: "in amet",
|
||||
returnCode: '0000', // false: 9999
|
||||
returnMsg: 'in amet',
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -76,16 +95,37 @@ class AlchemyOutController extends BaseController {
|
||||
@role(ROLE_ANON)
|
||||
@router('post /pay/out/alchemy/distribute')
|
||||
async distributeToken(req, res) {
|
||||
const { appId, timestamp, sign } = req.headers
|
||||
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 = {
|
||||
direct: 1,
|
||||
data: null,
|
||||
data: null,
|
||||
success: true,
|
||||
returnCode: "0000", // false: 9999
|
||||
returnMsg: "in amet",
|
||||
returnCode: '0000', // false: 9999
|
||||
returnMsg: 'in amet',
|
||||
}
|
||||
res.send(result)
|
||||
}
|
||||
|
||||
}
|
||||
|
83
src/controllers/internal.controller.ts
Normal file
83
src/controllers/internal.controller.ts
Normal 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 {}
|
||||
}
|
||||
}
|
@ -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 { BaseModule } from './Base'
|
||||
|
||||
@ -13,16 +13,20 @@ export enum PayType {
|
||||
|
||||
export enum PayStatus {
|
||||
PENDING = 0,
|
||||
SUCCESS = 1,
|
||||
FAIL = 2,
|
||||
TRANSFERING = 1, //只有国库模式才会有该状态
|
||||
TRANSFERED = 2, //只有国库模式才会有该状态
|
||||
SUCCESS = 9,
|
||||
TRANSFER_FAIL = 98, // 转账错误
|
||||
FAIL = 99,
|
||||
}
|
||||
|
||||
@dbconn()
|
||||
@index({ outOrderId: 1 }, { unique: true, partialFilterExpression: { outOrderId: { $exists: true } } })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'pay_record', timestamps: true },
|
||||
options: { allowMixed: Severity.ALLOW },
|
||||
})
|
||||
class PayRecordClass extends BaseModule {
|
||||
export class PayRecordClass extends BaseModule {
|
||||
@prop({ enum: PayPlatEnum, default: PayPlatEnum.ALCHEMY })
|
||||
public channel!: PayPlatEnum
|
||||
|
||||
@ -36,10 +40,30 @@ class PayRecordClass extends BaseModule {
|
||||
public address: string
|
||||
|
||||
@prop()
|
||||
public chain: string
|
||||
public network?: string
|
||||
|
||||
@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 })
|
||||
public status: PayStatus
|
||||
@ -52,7 +76,11 @@ class PayRecordClass extends BaseModule {
|
||||
public outOrderId: string
|
||||
// 交易的txHash
|
||||
@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 })
|
||||
|
73
src/modules/TransferRecord.ts
Normal file
73
src/modules/TransferRecord.ts
Normal 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 })
|
82
src/queue/transfer.queue.ts
Normal file
82
src/queue/transfer.queue.ts
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
import axios from 'axios'
|
||||
import { sha1 } from 'utils/security.util'
|
||||
import { hmacsha256, sha1 } from 'utils/security.util'
|
||||
import crypto from 'crypto'
|
||||
|
||||
export function createSimpleSign() {
|
||||
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 = sha1(appid + secret + timestamp)
|
||||
let sign = hmacsha256(appid + timestamp, secret)
|
||||
return {
|
||||
appid,
|
||||
timestamp,
|
||||
@ -21,10 +22,18 @@ export function createSimpleSign() {
|
||||
*/
|
||||
export function checkPayResultSign(data: any) {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
* @param plainText - plain text to be encrypted
|
||||
@ -51,6 +60,7 @@ export function createPageSign(plainText: string) {
|
||||
|
||||
/**
|
||||
* Refresh token
|
||||
* https://alchemycn.readme.io/docs/获取token
|
||||
* @param email - user email
|
||||
* @returns token
|
||||
*/
|
||||
@ -101,7 +111,7 @@ export async function createOrder(token: string, data: any) {
|
||||
* https://alchemycn.readme.io/docs/更新订单状态
|
||||
* TODO:: test
|
||||
*/
|
||||
export async function updateOrderStatus(token: string, data: any) {
|
||||
export async function updateOrderStatus(data: any) {
|
||||
const { appid, timestamp, sign } = createSimpleSign()
|
||||
const url = process.env.ALCHEMY_API_BASE + '/webhooks/treasure'
|
||||
const config = {
|
||||
@ -109,7 +119,6 @@ export async function updateOrderStatus(token: string, data: any) {
|
||||
url,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'access-token': token,
|
||||
appId: appid,
|
||||
sign: sign,
|
||||
timestamp,
|
||||
@ -121,6 +130,7 @@ export async function updateOrderStatus(token: string, data: any) {
|
||||
}
|
||||
/**
|
||||
* 查询数字货币币价
|
||||
* https://alchemycn.readme.io/docs/查询数字货币币价
|
||||
*/
|
||||
export async function queryPrice(data: any) {
|
||||
const { appid, timestamp, sign } = createSimpleSign()
|
||||
@ -147,3 +157,28 @@ export async function queryPrice(data: any) {
|
||||
let response = await axios(config)
|
||||
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
31
src/service/chain.svr.ts
Normal 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)
|
||||
}
|
@ -24,6 +24,13 @@ export function aesDecrypt(encryptedText: string, password: string, iv: string)
|
||||
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) {
|
||||
let hash = crypto.createHmac('sha512', salt)
|
||||
hash.update(password)
|
||||
|
Loading…
x
Reference in New Issue
Block a user