116 lines
3.7 KiB
TypeScript
116 lines
3.7 KiB
TypeScript
import { Injectable, Logger, InternalServerErrorException } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import axios from 'axios';
|
|
import * as crypto from 'crypto';
|
|
|
|
@Injectable()
|
|
export class BinanceProvider {
|
|
private readonly logger = new Logger(BinanceProvider.name);
|
|
private readonly apiUrl = 'https://bpay.binanceapi.com/binancepay/openapi/v2/order';
|
|
|
|
constructor(private configService: ConfigService) {}
|
|
|
|
private get apiKey(): string {
|
|
return this.configService.get<string>('BINANCE_PAY_API_KEY') || '';
|
|
}
|
|
|
|
private get secretKey(): string {
|
|
return this.configService.get<string>('BINANCE_PAY_SECRET_KEY') || '';
|
|
}
|
|
|
|
/**
|
|
* Create the Binance Pay Signature
|
|
*/
|
|
private generateSignature(timestamp: string, nonce: string, body: any): string {
|
|
const payload = timestamp + '\n' + nonce + '\n' + JSON.stringify(body) + '\n';
|
|
return crypto
|
|
.createHmac('sha512', this.secretKey)
|
|
.update(payload)
|
|
.digest('hex')
|
|
.toUpperCase();
|
|
}
|
|
|
|
/**
|
|
* Create a Binance Pay Order
|
|
*/
|
|
async createOrder(tenantId: string, amount: number, plan: string) {
|
|
this.logger.log(`Creating Binance Pay order for ${tenantId} - ${plan}`);
|
|
|
|
if (!this.apiKey || !this.secretKey) {
|
|
this.logger.error('Binance Pay API keys are not configured');
|
|
throw new InternalServerErrorException('Payment provider is not configured properly');
|
|
}
|
|
|
|
const nonce = crypto.randomBytes(16).toString('hex');
|
|
const timestamp = Date.now().toString();
|
|
const merchantTradeNo = `txn_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
|
|
|
|
const body = {
|
|
env: {
|
|
terminalType: 'WEB',
|
|
},
|
|
merchantTradeNo: merchantTradeNo,
|
|
orderAmount: amount,
|
|
currency: 'USDT',
|
|
goods: {
|
|
goodsType: '01',
|
|
goodsCategory: 'Z000',
|
|
referenceGoodsId: plan,
|
|
goodsName: `${plan} Plan Subscription`,
|
|
goodsDetail: `Subscription to ${plan} plan for Maps SaaS`,
|
|
},
|
|
passThroughInfo: JSON.stringify({ tenantId, plan }),
|
|
returnUrl: `https://map-dashboard.intaleqapp.com/dashboard.html#billing`,
|
|
};
|
|
|
|
const signature = this.generateSignature(timestamp, nonce, body);
|
|
|
|
try {
|
|
const response = await axios.post(this.apiUrl, body, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'BinancePay-Timestamp': timestamp,
|
|
'BinancePay-Nonce': nonce,
|
|
'BinancePay-Certificate-SN': this.apiKey,
|
|
'BinancePay-Signature': signature,
|
|
},
|
|
});
|
|
|
|
if (response.data && response.data.status === 'SUCCESS') {
|
|
return {
|
|
checkoutUrl: response.data.data.checkoutUrl,
|
|
orderId: merchantTradeNo,
|
|
prepayId: response.data.data.prepayId,
|
|
};
|
|
} else {
|
|
this.logger.error(`Binance API Error: ${JSON.stringify(response.data)}`);
|
|
throw new InternalServerErrorException('Failed to create Binance order');
|
|
}
|
|
} catch (error: any) {
|
|
this.logger.error(`Error communicating with Binance API: ${error.message}`);
|
|
throw new InternalServerErrorException('Failed to communicate with payment provider');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify Webhook Signature
|
|
*/
|
|
verifySignature(timestamp: string, nonce: string, signature: string, payloadBody: any): boolean {
|
|
const payload = timestamp + '\n' + nonce + '\n' + JSON.stringify(payloadBody) + '\n';
|
|
const expectedSignature = crypto
|
|
.createHmac('sha512', this.secretKey)
|
|
.update(payload)
|
|
.digest('hex')
|
|
.toUpperCase();
|
|
|
|
try {
|
|
return crypto.timingSafeEqual(
|
|
Buffer.from(signature || ''),
|
|
Buffer.from(expectedSignature)
|
|
);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|