增加查询crypto兑换美元价格的接口
This commit is contained in:
parent
5fbd59b738
commit
5220dd2232
@ -17,3 +17,7 @@ ALCHEMY_APP_SECRET="4Yn8RkxDXN71Q3p0"
|
||||
ALCHEMY_API_BASE="https://openapi-test.alchemypay.org"
|
||||
ALCHEMY_PAGE_BASE="https://ramptest.alchemypay.org"
|
||||
ALCHEMY_PAY_CB_URL="https://wallet.cebggame.com"
|
||||
|
||||
|
||||
#cryptocompare api key from metamask^_^
|
||||
CRYPTOCOMPARE_API_KEY=d1ec8cd68228095debc9db2dca45771b905ce1f27f522ebfef025c236f4aef3b
|
@ -7,6 +7,7 @@ import logger from 'logger/logger'
|
||||
import config from 'config/config'
|
||||
import { ConnectOptions } from 'mongoose'
|
||||
import CodeTaskSchedule from 'schedule/codetask.schedule'
|
||||
import { PriceSvr } from 'service/price.svr'
|
||||
|
||||
const zReqParserPlugin = require('plugins/zReqParser')
|
||||
|
||||
@ -87,6 +88,7 @@ export class ApiServer {
|
||||
|
||||
initSchedules() {
|
||||
new CodeTaskSchedule().scheduleAll()
|
||||
new PriceSvr().scheduleAll()
|
||||
}
|
||||
|
||||
async connectDB() {
|
||||
@ -131,8 +133,8 @@ export class ApiServer {
|
||||
/**
|
||||
* 格式化接口返回数据, 统一封装成如下格式
|
||||
* {
|
||||
* code: 0,
|
||||
* msg?: '',
|
||||
* errcode: 0,
|
||||
* errmsg?: '',
|
||||
* data: any
|
||||
* }
|
||||
* @private
|
||||
@ -142,6 +144,12 @@ export class ApiServer {
|
||||
reply.header('X-Powered-By', 'PHP/5.4.16')
|
||||
// @ts-ignore
|
||||
if (!payload.errcode) {
|
||||
// @ts-ignore
|
||||
if (payload.direct) {
|
||||
// @ts-ignore
|
||||
delete payload.direct
|
||||
return payload
|
||||
}
|
||||
payload = {
|
||||
errcode: 0,
|
||||
data: payload,
|
||||
|
@ -1,12 +1,13 @@
|
||||
import logger from 'logger/logger'
|
||||
import BaseController, { ROLE_ANON } from 'common/base.controller'
|
||||
import BaseController from 'common/base.controller'
|
||||
import { ZError } from 'common/ZError'
|
||||
import { role, router } from 'decorators/router'
|
||||
import { checkPayResultSign, createPageSign, refreshToken } from 'service/alchemy.svr'
|
||||
import { router } from 'decorators/router'
|
||||
import { createPageSign, queryPrice, refreshToken } from 'service/alchemy.svr'
|
||||
import { generateKVStr } from 'utils/net.util'
|
||||
import { PayRecord, PayStatus } from 'modules/PayRecord'
|
||||
import { PriceSvr } from 'service/price.svr'
|
||||
|
||||
const CALL_BACK_URL = `${process.env.ALCHEMY_PAY_CB_URL}/pay/alchemy/cb`
|
||||
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) {
|
||||
@ -19,7 +20,6 @@ class AlchemyController extends BaseController {
|
||||
throw new ZError(10, 'fetch pay token error')
|
||||
}
|
||||
const { id, email, accessToken } = tokenResult.data
|
||||
const redirectUrl = ''
|
||||
let record = new PayRecord({ account: user.id, address, chain, currency })
|
||||
await record.save()
|
||||
const merchantOrderNo = record.id
|
||||
@ -47,36 +47,18 @@ class AlchemyController extends BaseController {
|
||||
return { url }
|
||||
}
|
||||
|
||||
@role(ROLE_ANON)
|
||||
@router('post /pay/alchemy/cb')
|
||||
async alchemyCallback(req, res) {
|
||||
let { orderNo, status, crypto, network, merchantOrderNo } = req.params
|
||||
if (!merchantOrderNo) {
|
||||
logger.info(`alchemy callback merchantOrderNo not found`)
|
||||
throw new ZError(11, 'alchemy callback merchantOrderNo not found')
|
||||
@router('post /pay/alchemy/crypto_price')
|
||||
async queryCryptoPrice(req, res) {
|
||||
let { token, chain, currency } = req.params
|
||||
if (!token || !chain) {
|
||||
throw new ZError(11, 'token or network 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')
|
||||
let data = {
|
||||
crypto: token,
|
||||
network: chain,
|
||||
fiat: currency || 'USD',
|
||||
}
|
||||
if (record.status !== PayStatus.PENDING) {
|
||||
logger.info(`alchemy callback record status error`)
|
||||
throw new ZError(13, 'alchemy callback record status error')
|
||||
}
|
||||
if (!checkPayResultSign(req.params)) {
|
||||
logger.info(`alchemy callback sign error`)
|
||||
record.status = PayStatus.FAIL
|
||||
await record.save()
|
||||
throw new ZError(14, 'alchemy callback sign error')
|
||||
}
|
||||
|
||||
record.outOrderId = orderNo
|
||||
record.chain = network
|
||||
record.currency = crypto
|
||||
record.outData = req.params
|
||||
record.status = status == 'PAY_SUCCESS' ? PayStatus.SUCCESS : PayStatus.FAIL
|
||||
await record.save()
|
||||
return {}
|
||||
let result = await new PriceSvr().fetchPrice(data)
|
||||
return { price: result }
|
||||
}
|
||||
}
|
||||
|
91
src/controllers/alchemyout.controller.ts
Normal file
91
src/controllers/alchemyout.controller.ts
Normal file
@ -0,0 +1,91 @@
|
||||
|
||||
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 { PayRecord, PayStatus } from 'modules/PayRecord'
|
||||
/**
|
||||
* for Alchemy call
|
||||
*/
|
||||
class AlchemyOutController extends BaseController {
|
||||
@role(ROLE_ANON)
|
||||
@router('post /pay/out/alchemy/buycb')
|
||||
async alchemyCallback(req, res) {
|
||||
let { orderNo, status, crypto, network, merchantOrderNo } = req.params
|
||||
if (!merchantOrderNo) {
|
||||
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) {
|
||||
logger.info(`alchemy callback record status error`)
|
||||
throw new ZError(13, 'alchemy callback record status error')
|
||||
}
|
||||
if (!checkPayResultSign(req.params)) {
|
||||
logger.info(`alchemy callback sign error`)
|
||||
record.status = PayStatus.FAIL
|
||||
await record.save()
|
||||
throw new ZError(14, 'alchemy callback sign error')
|
||||
}
|
||||
|
||||
record.outOrderId = orderNo
|
||||
record.chain = network
|
||||
record.currency = crypto
|
||||
record.outData = req.params
|
||||
record.status = status == 'PAY_SUCCESS' ? PayStatus.SUCCESS : PayStatus.FAIL
|
||||
await record.save()
|
||||
return {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向商户查询币价
|
||||
* TODO::
|
||||
*/
|
||||
@role(ROLE_ANON)
|
||||
@router('get /pay/out/alchemy/queryprice')
|
||||
async queryToken(req, res) {
|
||||
const { crypto } = req.params
|
||||
const { appId, timestamp, sign } = req.headers
|
||||
let result = {
|
||||
direct: 1,
|
||||
data: {
|
||||
price: "1.0",
|
||||
networkList: [
|
||||
{
|
||||
network: "ETH",
|
||||
networkFee: "1.21"
|
||||
}
|
||||
]
|
||||
},
|
||||
success: true,
|
||||
returnCode: "0000", // false: 9999
|
||||
returnMsg: "in amet",
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知商户打币
|
||||
* TODO::
|
||||
*/
|
||||
@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
|
||||
let result = {
|
||||
direct: 1,
|
||||
data: null,
|
||||
success: true,
|
||||
returnCode: "0000", // false: 9999
|
||||
returnMsg: "in amet",
|
||||
}
|
||||
res.send(result)
|
||||
}
|
||||
|
||||
}
|
@ -72,7 +72,10 @@ export async function refreshToken(email: string) {
|
||||
let response = await axios(config)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
* https://alchemycn.readme.io/docs/创建订单
|
||||
*/
|
||||
export async function createOrder(token: string, data: any) {
|
||||
const { appid, timestamp, sign } = createSimpleSign()
|
||||
const host = process.env.ALCHEMY_API_BASE
|
||||
@ -91,3 +94,56 @@ export async function createOrder(token: string, data: any) {
|
||||
let response = await axios(config)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新订单状态
|
||||
* 当您给用户转移token后,您需要通过此接口通知Alchemy打币的信息
|
||||
* https://alchemycn.readme.io/docs/更新订单状态
|
||||
* TODO:: test
|
||||
*/
|
||||
export async function updateOrderStatus(token: string, data: any) {
|
||||
const { appid, timestamp, sign } = createSimpleSign()
|
||||
const url = process.env.ALCHEMY_API_BASE + '/webhooks/treasure'
|
||||
const config = {
|
||||
method: 'post',
|
||||
url,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'access-token': token,
|
||||
appId: appid,
|
||||
sign: sign,
|
||||
timestamp,
|
||||
},
|
||||
data,
|
||||
}
|
||||
let response = await axios(config)
|
||||
return response.data
|
||||
}
|
||||
/**
|
||||
* 查询数字货币币价
|
||||
*/
|
||||
export async function queryPrice(data: any) {
|
||||
const { appid, timestamp, sign } = createSimpleSign()
|
||||
let dataOrign = {
|
||||
crypto: 'ETH',
|
||||
network: 'ETH',
|
||||
fiat: 'USD',
|
||||
amount: '100',
|
||||
payWayCode: '10001',
|
||||
side: 'BUY',
|
||||
}
|
||||
dataOrign = { ...dataOrign, ...data }
|
||||
const config = {
|
||||
method: 'post',
|
||||
url: process.env.ALCHEMY_API_BASE + '/merchant/order/quote',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
appId: appid,
|
||||
sign,
|
||||
timestamp,
|
||||
},
|
||||
data: dataOrign,
|
||||
}
|
||||
let response = await axios(config)
|
||||
return response.data
|
||||
}
|
||||
|
65
src/service/price.svr.ts
Normal file
65
src/service/price.svr.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import axios from 'axios'
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import { queryPrice } from './alchemy.svr'
|
||||
import * as schedule from 'node-schedule'
|
||||
import logger from 'logger/logger'
|
||||
|
||||
@singleton
|
||||
export class PriceSvr {
|
||||
private priceMap: Map<string, string> = new Map()
|
||||
|
||||
public async queryEthPrice(eth: string) {
|
||||
const usd = 'USD'
|
||||
const apiKey = process.env.CRYPTOCOMPARE_API_KEY
|
||||
const url = `https://min-api.cryptocompare.com/data/price?fsym=${eth}&tsyms=${usd}&api_key=${apiKey}`
|
||||
let priceData = await axios.get(url).then(res => res.data)
|
||||
let price = priceData[usd] + ''
|
||||
return price
|
||||
}
|
||||
|
||||
public async fetchPrice(data: any) {
|
||||
let { crypto, network, fiat } = data
|
||||
let key = `${crypto}_${network}_${fiat}`
|
||||
let price = this.priceMap.get(key)
|
||||
if (!price) {
|
||||
await this.refreshOne(data)
|
||||
price = this.priceMap.get(key)
|
||||
}
|
||||
return price
|
||||
}
|
||||
|
||||
private async refreshOne(data) {
|
||||
let { crypto, network, fiat } = data
|
||||
let key = `${crypto}_${network}_${fiat}`
|
||||
try {
|
||||
let priceData = await queryPrice(data)
|
||||
this.priceMap.set(key, priceData.data.cryptoPrice + '')
|
||||
} catch (e) {
|
||||
logger.debug('update eth price with alchemy error: ' + e.message || e)
|
||||
try {
|
||||
let price = await this.queryEthPrice(crypto)
|
||||
this.priceMap.set(key, price + '')
|
||||
} catch (e) {
|
||||
logger.debug('update eth price with cryptocompare error: ' + e.message || e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshAll() {
|
||||
for (let key of this.priceMap.keys()) {
|
||||
let [crypto, chain, fiat] = key.split('_')
|
||||
let data = {
|
||||
crypto,
|
||||
network: chain,
|
||||
fiat,
|
||||
}
|
||||
await this.refreshOne(data)
|
||||
}
|
||||
}
|
||||
|
||||
scheduleAll() {
|
||||
const job = schedule.scheduleJob('*/30 * * * * *', async () => {
|
||||
this.refreshAll()
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user