Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | import { PaymentProvider } from '../payment-provider.interface';
import { Logger } from '@nestjs/common';
import * as crypto from 'crypto';
export class EasebuzzProvider implements PaymentProvider {
name = 'Easebuzz';
private readonly logger = new Logger(EasebuzzProvider.name);
private key: string;
private salt: string;
private env: string;
private baseUrl: string;
constructor(key: string, salt: string, env: 'test' | 'prod' = 'test') {
this.key = key;
this.salt = salt;
this.env = env;
this.baseUrl =
env === 'prod'
? 'https://pay.easebuzz.in'
: 'https://testpay.easebuzz.in';
}
private generateHash(data: string): string {
const hash = crypto.createHash('sha512');
hash.update(data);
return hash.digest('hex');
}
async createCheckoutLink(params: {
tenantId: string;
amount: number;
currency: string;
invoiceId: string;
callbackUrl: string;
}): Promise<{ checkoutUrl: string; providerIntentId: string }> {
try {
// Amount in Easebuzz is expected to be a string like '10.00' (Rupees, not paisa usually, but depends on config)
// We assume minor units in our app (e.g. 1000 for 10.00 INR). We must convert it to major units.
const amountMajor = (params.amount / 100).toFixed(2);
const txnid = `TXN_${params.invoiceId}_${Date.now()}`;
const productInfo = `Invoice ${params.invoiceId}`;
const firstname = 'Tenant Admin'; // we might need real names from DB
const email = 'admin@example.com';
const phone = '9999999999';
// Hash format: key|txnid|amount|productinfo|firstname|email|udf1|udf2|udf3|udf4|udf5|udf6|udf7|udf8|udf9|udf10|salt
const hashString = `${this.key}|${txnid}|${amountMajor}|${productInfo}|${firstname}|${email}|||||||||||${this.salt}`;
const hash = this.generateHash(hashString);
// In a real scenario, we initiate payment via API to get an access_key
// For simplicity in this adapter without the official SDK, we mock the access_key creation API call
// You would typically make an HTTP POST to `${this.baseUrl}/payment/initiateLink`
const requestData = new URLSearchParams({
key: this.key,
txnid,
amount: amountMajor,
productinfo: productInfo,
firstname,
email,
phone,
surl: params.callbackUrl,
furl: params.callbackUrl,
hash,
});
const response = await fetch(`${this.baseUrl}/payment/initiateLink`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
},
body: requestData.toString(),
});
const data = await response.json();
if (data.status === 1) {
return {
checkoutUrl: `${this.baseUrl}/pay/${data.data}`, // access_key
providerIntentId: data.data,
};
} else {
throw new Error(
data.error_desc || 'Failed to initiate Easebuzz payment',
);
}
} catch (error: any) {
this.logger.error(`Easebuzz create checkout failed: ${error.message}`);
throw error;
}
}
async verifyWebhook(
payload: any,
signature: string,
): Promise<{
success: boolean;
transactionId: string;
amountPaid: number;
status: 'SUCCESS' | 'FAILED';
}> {
// Easebuzz posts back the transaction response to surl/furl and webhook.
// Hash format for response: salt|status|||||||||||email|firstname|productinfo|amount|txnid|key
const event = typeof payload === 'string' ? JSON.parse(payload) : payload;
const hashString = `${this.salt}|${event.status}|||||||||||${event.email}|${event.firstname}|${event.productinfo}|${event.amount}|${event.txnid}|${this.key}`;
const expectedHash = this.generateHash(hashString);
if (expectedHash !== event.hash) {
this.logger.error('Easebuzz signature verification failed');
return {
success: false,
transactionId: '',
amountPaid: 0,
status: 'FAILED',
};
}
if (event.status === 'success') {
return {
success: true,
transactionId: event.easepayid, // Easebuzz payment ID
amountPaid: Math.round(parseFloat(event.amount) * 100), // convert back to minor units
status: 'SUCCESS',
};
}
return {
success: true,
transactionId: '',
amountPaid: 0,
status: 'FAILED',
};
}
async refund(
transactionId: string,
amount: number,
): Promise<{ success: boolean; refundId: string }> {
// Refund logic via HTTP request
try {
const amountMajor = (amount / 100).toFixed(2);
const refundId = `REF_${Date.now()}`;
const hashString = `${this.key}|${transactionId}|${amountMajor}|${refundId}|${this.salt}`;
const hash = this.generateHash(hashString);
const requestData = new URLSearchParams({
key: this.key,
easepayid: transactionId,
refund_amount: amountMajor,
merchant_refund_id: refundId,
hash,
});
const response = await fetch(`${this.baseUrl}/transaction/v1/refund`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
},
body: requestData.toString(),
});
const data = await response.json();
if (data.status) {
return { success: true, refundId: refundId };
}
return { success: false, refundId: '' };
} catch (error: any) {
this.logger.error(`Easebuzz refund failed: ${error.message}`);
return { success: false, refundId: '' };
}
}
}
|