feat: add Binance Pay webhook handler and implement payment order creation in billing service
This commit is contained in:
@@ -1,33 +1,115 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
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) {}
|
||||
|
||||
/**
|
||||
* Mock Binance Pay Order Creation
|
||||
* Will lead to a success/fail redirect for testing
|
||||
*/
|
||||
async createOrder(tenantId: string, amount: number, plan: string) {
|
||||
this.logger.log(`[Mock] Creating Binance Pay order for ${tenantId} - ${plan}`);
|
||||
|
||||
// In production, this calls https://bpay.binanceapi.com/binancepay/openapi/v2/order
|
||||
// and returns a checkoutUrl.
|
||||
|
||||
return {
|
||||
checkoutUrl: `https://map-dashbord.intaleqapp.com/api/billing/mock-binance-success?tenantId=${tenantId}&plan=${plan}`,
|
||||
prepayId: `mock_${Date.now()}`
|
||||
};
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock Webhook Signature Verification
|
||||
* Create the Binance Pay Signature
|
||||
*/
|
||||
verifySignature(payload: any, signature: string): boolean {
|
||||
// In production, uses HMAC-SHA512 with Binance Secret Key
|
||||
return true;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user