Files
maps-saas/apps/api/src/billing/providers/binance.provider.ts
T

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