增加查询crypto兑换美元价格的接口

This commit is contained in:
zhl 2023-05-26 14:04:52 +08:00
parent 5fbd59b738
commit 5220dd2232
6 changed files with 243 additions and 37 deletions

View File

@ -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

View File

@ -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,

View File

@ -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 }
}
}

View 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)
}
}

View File

@ -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
View 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()
})
}
}