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('BINANCE_PAY_API_KEY') || ''; } private get secretKey(): string { return this.configService.get('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; } } }