修正购买流程的bug
This commit is contained in:
parent
476da08a8f
commit
0688261153
@ -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'
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 },
|
||||
|
@ -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
|
||||
|
@ -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('')
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user