tango-app-api-payment-subscription 3.1.12 → 3.1.14-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,339 @@
1
+
2
+ import { createOrder, download, logger } from 'tango-app-api-middleware';
3
+ import { findOneAccount, updateOneAccount } from '../services/paymentAccount.service.js';
4
+ import * as invoiceService from '../services/invoice.service.js';
5
+ import { updateOrder, verifySignature, verifyWebhook } from 'tango-app-api-middleware/src/utils/razorPay.js';
6
+ import { aggregateTransaction, createTransaction } from '../services/transaction.service.js';
7
+ import dayjs from 'dayjs';
8
+
9
+
10
+ export const getVirtualAccount = async ( req, res ) => {
11
+ try {
12
+ const account = await findOneAccount( { clientId: req.query.clientId }, { accountNumber: 1, ifsc: 1, branch: 1, paymentType: 1, credit: 1, currency: 1, _id: 0 } );
13
+
14
+ if ( !account ) {
15
+ return res.sendError( 'No data found', 204 );
16
+ }
17
+
18
+ return res.sendSuccess( account );
19
+ } catch ( error ) {
20
+ logger.error( { error: error, function: 'getVirtualAccount' } );
21
+ return res.sendError( error, 500 );
22
+ }
23
+ };
24
+
25
+ export const payUsingVallet = async ( req, res ) => {
26
+ try {
27
+ const invoice = await invoiceService.findOne( { invoice: req.params.invoice } );
28
+ if ( !invoice ) {
29
+ return res.sendError( 'Invoice not found', 404 );
30
+ }
31
+
32
+ const account = await findOneAccount( { clientId: invoice.clientId } );
33
+
34
+ if ( !account ) {
35
+ return res.sendError( 'Wallet not found', 404 );
36
+ }
37
+
38
+ if ( account.credit < invoice.totalAmount ) {
39
+ return res.sendError( 'Insufficient balance for payment', 409 );
40
+ }
41
+
42
+ const reducedCredit = Math.round( account.credit - invoice.totalAmount );
43
+
44
+ const deductFromVallet = await updateOneAccount( { clientId: invoice.clientId }, { credit: reducedCredit } );
45
+
46
+ let statusChange;
47
+
48
+ if ( deductFromVallet.modifiedCount ) {
49
+ statusChange = await invoiceService.updateOne( { invoice: req.params.invoice }, { paymentStatus: 'paid' } );
50
+
51
+ const transaction = {
52
+ clientId: account.clientId,
53
+ debtType: 'wallet',
54
+ billingDate: invoice.billingDate,
55
+ invoice: invoice.invoice,
56
+ groupName: invoice.groupName,
57
+ groupId: invoice.groupId,
58
+ amount: invoice.totalAmount,
59
+ currency: invoice.currency,
60
+ transactionType: 'debt',
61
+ balanceCredit: Math.round( account.credit - invoice.totalAmount ),
62
+ balanceCreditCurrency: account.currency,
63
+ };
64
+
65
+ await createTransaction( transaction );
66
+ }
67
+
68
+ if ( !statusChange?.modifiedCount ) {
69
+ logger.error( { error: { ...account, ...invoice }, function: 'payUsingVallet' } );
70
+ }
71
+
72
+ return res.sendSuccess( 'Payment successful !' );
73
+ } catch ( error ) {
74
+ logger.error( { error: error, function: 'payUsingVallet' } );
75
+ return res.sendError( error, 500 );
76
+ }
77
+ };
78
+
79
+ export const generateOrder = async ( req, res ) => {
80
+ try {
81
+ const invoice = await invoiceService.findOne( { invoice: req.params.invoice } );
82
+ if ( !invoice ) {
83
+ return res.sendError( 'Invoice not found', 404 );
84
+ }
85
+ const orderData = {
86
+ amount: Number( Math.round( invoice.totalAmount ) + '00' ),
87
+ currency: invoice.currency === 'inr' ? 'INR' : 'USD',
88
+ receipt: invoice.invoice,
89
+ };
90
+
91
+ const createdOrder = await createOrder( orderData );
92
+
93
+ if ( !createdOrder ) {
94
+ return res.sendError( 'Payment order generation failed', 500 );
95
+ }
96
+
97
+ const updatedOrder = await updateOrder( { orderId: createdOrder.id, notes: { oid: createdOrder.id } } );
98
+
99
+ if ( !updatedOrder ) {
100
+ return res.sendError( 'Failed to generate order', 500 );
101
+ }
102
+
103
+ await invoiceService.updateOne( { invoice: invoice.invoice }, { orderId: createdOrder.id } );
104
+
105
+ const order = {
106
+ ...createdOrder,
107
+ apiKey: process.env.RAZORPAY_KEY_ID,
108
+ name: req.user.userName,
109
+ email: req.user.email,
110
+ contact: req.user.mobileNumber,
111
+ };
112
+
113
+ return res.sendSuccess( order );
114
+ } catch ( error ) {
115
+ logger.error( { error: error, function: 'generateOrder' } );
116
+ return res.sendError( error, 500 );
117
+ }
118
+ };
119
+
120
+ export const verifyPayment = async ( req, res ) => {
121
+ try {
122
+ const invoice = await invoiceService.findOne( { invoice: req.params.invoice } );
123
+ if ( !invoice ) {
124
+ return res.sendError( 'Invoice not found', 404 );
125
+ }
126
+
127
+ const verify = await verifySignature( { razorpayOrderId: invoice.orderId, razorpayPaymentId: req.body.razorpay_payment_id, signature: req.body.razorpay_signature } );
128
+
129
+ if ( !verify ) {
130
+ return res.sendError( 'Invalid signature', 409 );
131
+ }
132
+
133
+ return res.sendSuccess( 'Payment successful !' );
134
+ } catch ( error ) {
135
+ logger.error( { error: error, function: 'verifyPayment' } );
136
+ return res.sendError( error, 500 );
137
+ }
138
+ };
139
+
140
+ export const paymentHook = async ( req, res ) => {
141
+ try {
142
+ const signature = req.headers['x-razorpay-signature'];
143
+ const isValid = await verifyWebhook( { body: req.body, signature: signature } );
144
+ if ( isValid ) {
145
+ const { event, payload } = req.body;
146
+
147
+ switch ( event ) {
148
+ case 'payment.captured':
149
+ await handlePaymentCapture( payload );
150
+ break;
151
+ default:
152
+ logger.error( { error: payload, function: `Unhandled event: ${event}` } );
153
+ break;
154
+ }
155
+ }
156
+ res.status( 200 ).send();
157
+ } catch ( error ) {
158
+ logger.error( { error: error, function: 'paymentHook' } );
159
+ return res.sendError( error, 500 );
160
+ }
161
+ };
162
+
163
+ async function handlePaymentCapture( responseBody ) {
164
+ const paidInvoice = await invoiceService.findOne( { orderId: responseBody?.payment?.entity?.notes?.oid } );
165
+
166
+ if ( !paidInvoice ) {
167
+ return;
168
+ }
169
+
170
+ const account = await findOneAccount( { clientId: paidInvoice.clientId } );
171
+
172
+ await invoiceService.updateOne( { invoice: paidInvoice.invoice }, { paymentStatus: 'paid', paymentReferenceId: responseBody?.payment?.entity?.notes?.oid } );
173
+
174
+ const transaction = {
175
+ debtType: 'razor',
176
+ clientId: account.clientId,
177
+ billingDate: paidInvoice.billingDate,
178
+ invoice: paidInvoice.invoice,
179
+ groupName: paidInvoice.groupName,
180
+ groupId: paidInvoice.groupId,
181
+ amount: paidInvoice.totalAmount,
182
+ currency: paidInvoice.currency,
183
+ transactionType: 'debt',
184
+ balanceCredit: Math.round( account.credit ),
185
+ balanceCreditCurrency: account.currency,
186
+ };
187
+
188
+ await createTransaction( transaction );
189
+
190
+ return;
191
+ }
192
+
193
+ export const transactionList = async ( req, res ) => {
194
+ try {
195
+ const matchStage = {
196
+ $match: {
197
+ clientId: req.body.clientId,
198
+ },
199
+ };
200
+
201
+ let filterStartDate = '';
202
+ let filterEndDate = '';
203
+
204
+ if ( req.body?.filter && req.body.filter == 'current' ) {
205
+ filterStartDate = new Date( dayjs().startOf( 'month' ).format( 'YYYY-MM-DD' ) );
206
+ filterEndDate = new Date( dayjs().endOf( 'month' ).format( 'YYYY-MM-DD' ) );
207
+ }
208
+ if ( req.body?.filter && req.body.filter == 'prev' ) {
209
+ filterStartDate = new Date( dayjs().subtract( 1, 'month' ).startOf( 'month' ).format( 'YYYY-MM-DD' ) );
210
+ filterEndDate = new Date( dayjs().subtract( 1, 'month' ).endOf( 'month' ).format( 'YYYY-MM-DD' ) );
211
+ }
212
+ if ( req.body?.filter && req.body.filter == 'last' ) {
213
+ filterStartDate = new Date( dayjs().subtract( 3, 'month' ).startOf( 'month' ).format( 'YYYY-MM-DD' ) );
214
+ filterEndDate = new Date( dayjs().endOf( 'month' ).format( 'YYYY-MM-DD' ) );
215
+ }
216
+
217
+ if ( req.body?.filter && !req.body?.searchValue ) {
218
+ matchStage.$match['$and'] = [
219
+ { billingDate: { $gte: filterStartDate } },
220
+ { billingDate: { $lte: filterEndDate } },
221
+ ];
222
+ }
223
+
224
+ if ( req.body.searchValue ) {
225
+ matchStage.$match.$or = [
226
+ {
227
+ groupName: {
228
+ $regex: req.body.searchValue,
229
+ $options: 'i',
230
+ },
231
+ },
232
+ ];
233
+ }
234
+
235
+
236
+ const pipeline = [
237
+ matchStage,
238
+ {
239
+ $project: {
240
+ '_id': 1,
241
+ 'groupName': 1,
242
+ 'billingDate': 1,
243
+ 'amount': 1,
244
+ 'currency': 1,
245
+ 'transactionType': 1,
246
+ 'balanceCredit': 1,
247
+ 'balanceCreditCurrency': 1,
248
+ },
249
+ },
250
+ ];
251
+
252
+ if ( req.body?.sortColumn && req.body?.sortBy ) {
253
+ pipeline.push(
254
+ {
255
+ $addFields: {
256
+ sortField: {
257
+ $toLower: `$${req.body.sortColumn}`,
258
+ },
259
+ },
260
+ },
261
+ {
262
+ $sort: {
263
+ [req.body.sortColumn]: req.body.sortBy,
264
+ },
265
+ },
266
+ );
267
+ }
268
+
269
+ pipeline.push(
270
+ {
271
+ $project: {
272
+ sortField: 0,
273
+ },
274
+ },
275
+
276
+ );
277
+
278
+ const facetStage = {
279
+ $facet: {
280
+ data: [
281
+ {
282
+ $skip: ( ( req.body.offset - 1 ) * req.body.limit ),
283
+ },
284
+ {
285
+ $limit: ( req.body.limit ),
286
+ },
287
+ ],
288
+ pageInfo: [
289
+ {
290
+ $count: 'count',
291
+ },
292
+ ],
293
+ },
294
+ };
295
+
296
+ if ( req.body?.isExport ) {
297
+ facetStage.$facet.data = [];
298
+ }
299
+
300
+ pipeline.push( facetStage );
301
+
302
+ pipeline.push( {
303
+ $unwind: {
304
+ path: '$pageInfo',
305
+ },
306
+ } );
307
+
308
+ const transactionList = await aggregateTransaction( pipeline );
309
+
310
+ if ( !transactionList[0] ) {
311
+ return res.sendError( 'No data found', 204 );
312
+ }
313
+
314
+ if ( req.body.isExport ) {
315
+ const exportResult = [];
316
+ for ( let transaction of transactionList[0].data ) {
317
+ exportResult.push( {
318
+ 'Billing date': transaction?.billingDate ? dayjs( transaction.billingDate ).format( 'DD MMM, YYYY' ) : '',
319
+ 'Transaction': transaction?.transactionType === 'debt' ? `Consumption for ${dayjs( transaction?.billingDate ).format( 'MMM YYYY' )}- ${transaction?.groupName}` : 'Paid Credit',
320
+ 'Debit': transaction?.transactionType === 'debt' ? `${transaction?.currency === 'inr' ? '₹' : '$'} ${transaction.amount}` : '',
321
+ 'Credit': transaction?.transactionType === 'credit' ? `${transaction?.currency === 'inr' ? '₹' : '$'} ${transaction.amount}` : '',
322
+ 'Balance credit': `${transaction?.balanceCreditCurrency === 'inr' ? '₹' : '$'} ${transaction.balanceCredit}` || '',
323
+
324
+ } );
325
+ }
326
+
327
+ await download( exportResult, res );
328
+ return;
329
+ }
330
+
331
+
332
+ return res.sendSuccess( transactionList[0] );
333
+ } catch ( error ) {
334
+ logger.error( { error: error, function: 'transactionList' } );
335
+ return res.sendError( error, 500 );
336
+ }
337
+ };
338
+
339
+