tango-app-api-payment-subscription 3.0.62-dev → 3.0.63-dev
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.
- package/package.json +1 -1
- package/src/controllers/billing.controllers.js +59 -9
- package/src/controllers/payment.controller.js +307 -15
- package/src/dtos/isUniqueEvent.js +16 -0
- package/src/dtos/validation.dtos.js +30 -2
- package/src/routes/billing.routes.js +8 -2
- package/src/routes/payment.routes.js +21 -6
- package/src/services/invoice.service.js +4 -0
- package/src/services/paymentAccount.service.js +3 -0
- package/src/services/transaction.service.js +10 -0
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ import { download, logger } from 'tango-app-api-middleware';
|
|
|
2
2
|
import { aggregate } from '../services/store.service.js';
|
|
3
3
|
import * as invoice from '../services/invoice.service.js';
|
|
4
4
|
import { aggregatebilling, countDocuments, create, deleteOne, find, findOne, updateMany, updateOne } from '../services/billing.service.js';
|
|
5
|
+
import * as invoiceService from '../services/invoice.service.js';
|
|
5
6
|
import mongoose from 'mongoose';
|
|
6
7
|
import dayjs from 'dayjs';
|
|
7
8
|
|
|
@@ -470,7 +471,7 @@ export const getInvoices = async ( req, res ) => {
|
|
|
470
471
|
filterEndDate = new Date( dayjs().subtract( 1, 'month' ).endOf( 'month' ).format( 'YYYY-MM-DD' ) );
|
|
471
472
|
}
|
|
472
473
|
if ( req.body?.filter && req.body.filter == 'last' ) {
|
|
473
|
-
filterStartDate = new Date( dayjs().subtract(
|
|
474
|
+
filterStartDate = new Date( dayjs().subtract( 3, 'month' ).startOf( 'month' ).format( 'YYYY-MM-DD' ) );
|
|
474
475
|
filterEndDate = new Date( dayjs().endOf( 'month' ).format( 'YYYY-MM-DD' ) );
|
|
475
476
|
}
|
|
476
477
|
|
|
@@ -613,8 +614,8 @@ export const getInvoices = async ( req, res ) => {
|
|
|
613
614
|
|
|
614
615
|
const billingDate = invoiceData.billingDate;
|
|
615
616
|
|
|
616
|
-
if ( !groups[billingDate] ) {
|
|
617
|
-
groups[billingDate] = {
|
|
617
|
+
if ( !groups[dayjs( billingDate ).format( 'MMMM' )] ) {
|
|
618
|
+
groups[dayjs( billingDate ).format( 'MMMM' )] = {
|
|
618
619
|
summary: `Summary - ${dayjs( billingDate ).format( 'MMM YYYY' )}`,
|
|
619
620
|
products: new Set(),
|
|
620
621
|
stores: 0,
|
|
@@ -626,23 +627,23 @@ export const getInvoices = async ( req, res ) => {
|
|
|
626
627
|
};
|
|
627
628
|
}
|
|
628
629
|
|
|
629
|
-
groups[billingDate].invoices.push( invoiceData );
|
|
630
|
-
groups[billingDate].stores += invoiceData.stores;
|
|
631
|
-
groups[billingDate].totalAmount += invoiceData.totalAmount;
|
|
630
|
+
groups[dayjs( billingDate ).format( 'MMMM' )].invoices.push( invoiceData );
|
|
631
|
+
groups[dayjs( billingDate ).format( 'MMMM' )].stores += invoiceData.stores;
|
|
632
|
+
groups[dayjs( billingDate ).format( 'MMMM' )].totalAmount += invoiceData.totalAmount;
|
|
632
633
|
|
|
633
634
|
if ( invoiceData.paymentStatus === 'unpaid' ) {
|
|
634
635
|
const currentDate = dayjs();
|
|
635
636
|
const givenDate = dayjs( invoiceData.billingDate );
|
|
636
637
|
const daysDifference = currentDate.diff( givenDate, 'day' );
|
|
637
638
|
if ( daysDifference <= invoiceData.paymentTerm && daysDifference >= 0 ) {
|
|
638
|
-
groups[billingDate].paymentStatus = 'due';
|
|
639
|
+
groups[dayjs( billingDate ).format( 'MMMM' )].paymentStatus = 'due';
|
|
639
640
|
} else {
|
|
640
|
-
groups[billingDate].paymentStatus = 'unpaid';
|
|
641
|
+
groups[dayjs( billingDate ).format( 'MMMM' )].paymentStatus = 'unpaid';
|
|
641
642
|
}
|
|
642
643
|
}
|
|
643
644
|
|
|
644
645
|
invoiceData.products.forEach( ( product ) => {
|
|
645
|
-
groups[billingDate].products.add( product );
|
|
646
|
+
groups[dayjs( billingDate ).format( 'MMMM' )].products.add( product );
|
|
646
647
|
} );
|
|
647
648
|
} );
|
|
648
649
|
|
|
@@ -669,4 +670,53 @@ export const getInvoices = async ( req, res ) => {
|
|
|
669
670
|
}
|
|
670
671
|
};
|
|
671
672
|
|
|
673
|
+
export const onetimePayment = async ( req, res ) => {
|
|
674
|
+
try {
|
|
675
|
+
const invoice = await invoiceService.findOne( { invoice: req.params.invoice } );
|
|
676
|
+
if ( !invoice ) {
|
|
677
|
+
return res.sendError( 'Invoice not found', 404 );
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const onetimefeeObj = {
|
|
681
|
+
amount: req.body.oneTimeFee,
|
|
682
|
+
productName: 'installationFee',
|
|
683
|
+
description: 'One time setup & installation',
|
|
684
|
+
month: dayjs( invoice.billingDate ).format( 'MMM YYYY' ),
|
|
685
|
+
HsnNumber: '998314',
|
|
686
|
+
price: 0,
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
await invoiceService.invoiceUpdateOne( { invoice: invoice.invoice }, { $push: { products: onetimefeeObj } } );
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
if ( invoice.currency === 'inr' ) {
|
|
693
|
+
let totalTaxRate = 0;
|
|
694
|
+
|
|
695
|
+
if ( invoice.tax.length ) {
|
|
696
|
+
for ( let i = 0; i<invoice._doc.tax.length; i++ ) {
|
|
697
|
+
totalTaxRate += invoice._doc.tax[i].value;
|
|
698
|
+
invoice._doc.tax[i].taxAmount = Number( invoice._doc.tax[i].taxAmount ) + ( req.body.oneTimeFee * invoice._doc.tax[i].value ) / 100;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
invoice._doc.amount += req.body.oneTimeFee;
|
|
704
|
+
|
|
705
|
+
invoice._doc.totalAmount = ( ( invoice._doc.amount * totalTaxRate ) / 100 ) + invoice._doc.amount;
|
|
706
|
+
} else {
|
|
707
|
+
invoice._doc.amount += req.body.oneTimeFee;
|
|
708
|
+
invoice._doc.totalAmount += req.body.oneTimeFee;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
await invoiceService.invoiceUpdateOne( { invoice: invoice.invoice }, { $set: { 'tax': invoice._doc.tax, 'amount': invoice._doc.amount, 'totalAmount': invoice._doc.totalAmount } } );
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
return res.sendSuccess( 'Fee added successfully to invoice' );
|
|
716
|
+
} catch ( error ) {
|
|
717
|
+
logger.error( { error: error, function: 'onetimePayment' } );
|
|
718
|
+
return res.sendError( error, 500 );
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
|
|
672
722
|
|
|
@@ -1,17 +1,115 @@
|
|
|
1
1
|
|
|
2
|
-
import { createOrder } from 'tango-app-api-middleware';
|
|
3
|
-
import { findOneAccount } from '../services/paymentAccount.service.js';
|
|
2
|
+
import { createOrder, download, logger } from 'tango-app-api-middleware';
|
|
3
|
+
import { findOneAccount, updateOneAccount } from '../services/paymentAccount.service.js';
|
|
4
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';
|
|
5
8
|
|
|
6
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
|
+
|
|
7
79
|
export const generateOrder = async ( req, res ) => {
|
|
8
80
|
try {
|
|
9
|
-
const
|
|
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
|
+
};
|
|
10
90
|
|
|
11
|
-
|
|
12
|
-
|
|
91
|
+
const createdOrder = await createOrder( orderData );
|
|
92
|
+
|
|
93
|
+
if ( !createdOrder ) {
|
|
94
|
+
return res.sendError( 'Payment order generation failed', 500 );
|
|
13
95
|
}
|
|
14
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
|
+
|
|
15
113
|
return res.sendSuccess( order );
|
|
16
114
|
} catch ( error ) {
|
|
17
115
|
logger.error( { error: error, function: 'generateOrder' } );
|
|
@@ -19,29 +117,223 @@ export const generateOrder = async ( req, res ) => {
|
|
|
19
117
|
}
|
|
20
118
|
};
|
|
21
119
|
|
|
22
|
-
export const
|
|
120
|
+
export const verifyPayment = async ( req, res ) => {
|
|
23
121
|
try {
|
|
24
|
-
const
|
|
122
|
+
const invoice = await invoiceService.findOne( { invoice: req.params.invoice } );
|
|
123
|
+
if ( !invoice ) {
|
|
124
|
+
return res.sendError( 'Invoice not found', 404 );
|
|
125
|
+
}
|
|
25
126
|
|
|
26
|
-
|
|
27
|
-
|
|
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 );
|
|
28
131
|
}
|
|
29
132
|
|
|
30
|
-
return res.sendSuccess(
|
|
133
|
+
return res.sendSuccess( 'Payment successful !' );
|
|
31
134
|
} catch ( error ) {
|
|
32
|
-
logger.error( { error: error, function: '
|
|
135
|
+
logger.error( { error: error, function: 'verifyPayment' } );
|
|
33
136
|
return res.sendError( error, 500 );
|
|
34
137
|
}
|
|
35
138
|
};
|
|
36
139
|
|
|
37
|
-
export const
|
|
140
|
+
export const paymentHook = async ( req, res ) => {
|
|
38
141
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
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] ) {
|
|
41
311
|
return res.sendError( 'No data found', 204 );
|
|
42
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] );
|
|
43
333
|
} catch ( error ) {
|
|
44
|
-
logger.error( { error: error, function: '
|
|
334
|
+
logger.error( { error: error, function: 'transactionList' } );
|
|
45
335
|
return res.sendError( error, 500 );
|
|
46
336
|
}
|
|
47
337
|
};
|
|
338
|
+
|
|
339
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
import { logger } from 'tango-app-api-middleware';
|
|
3
|
+
|
|
4
|
+
const processedEventIds = new Set();
|
|
5
|
+
|
|
6
|
+
export const checkDuplicateEvent = ( req, res, next ) => {
|
|
7
|
+
const eventId = req.headers['x-razorpay-event-id'];
|
|
8
|
+
|
|
9
|
+
if ( processedEventIds.has( eventId ) ) {
|
|
10
|
+
logger.error( { error: `Duplicate event with ID ${eventId}. Skipping processing.`, function: 'checkDuplicateEvent' } );
|
|
11
|
+
res.status( 200 ).send();
|
|
12
|
+
} else {
|
|
13
|
+
processedEventIds.add( eventId );
|
|
14
|
+
next();
|
|
15
|
+
}
|
|
16
|
+
};
|
|
@@ -298,14 +298,14 @@ export const createBillingGroupBody = joi.object(
|
|
|
298
298
|
groupName: joi.string().required(),
|
|
299
299
|
groupTag: joi.string().required(),
|
|
300
300
|
registeredCompanyName: joi.string().required(),
|
|
301
|
-
gst: joi.string().
|
|
301
|
+
gst: joi.string().optional(),
|
|
302
302
|
addressLineOne: joi.string().optional(),
|
|
303
303
|
addressLineTwo: joi.string().optional(),
|
|
304
304
|
city: joi.string().optional(),
|
|
305
305
|
state: joi.string().optional(),
|
|
306
306
|
country: joi.string().optional(),
|
|
307
307
|
pinCode: joi.string().optional(),
|
|
308
|
-
placeOfSupply: joi.string().
|
|
308
|
+
placeOfSupply: joi.string().optional(),
|
|
309
309
|
po: joi.string().optional(),
|
|
310
310
|
stores: joi.array().optional(),
|
|
311
311
|
proRata: joi.string().optional(),
|
|
@@ -409,3 +409,31 @@ export const valletPayParam = joi.object().keys( {
|
|
|
409
409
|
export const valletPayValid = {
|
|
410
410
|
params: valletPayParam,
|
|
411
411
|
};
|
|
412
|
+
|
|
413
|
+
export const verifyPaymentParam = joi.object().keys( {
|
|
414
|
+
invoice: joi.string().required(),
|
|
415
|
+
} );
|
|
416
|
+
|
|
417
|
+
export const verifyPaymentBody = joi.object().keys( {
|
|
418
|
+
razorpay_payment_id: joi.string().required(),
|
|
419
|
+
razorpay_order_id: joi.string().required(),
|
|
420
|
+
razorpay_signature: joi.string().required(),
|
|
421
|
+
} );
|
|
422
|
+
|
|
423
|
+
export const verifyPaymentValid = {
|
|
424
|
+
params: verifyPaymentParam,
|
|
425
|
+
body: verifyPaymentBody,
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
export const onetimeFeeParam = joi.object().keys( {
|
|
429
|
+
invoice: joi.string().required(),
|
|
430
|
+
} );
|
|
431
|
+
|
|
432
|
+
export const onetimeFeeBody = joi.object().keys( {
|
|
433
|
+
oneTimeFee: joi.number().required(),
|
|
434
|
+
} );
|
|
435
|
+
|
|
436
|
+
export const onetimeFeeValid = {
|
|
437
|
+
params: onetimeFeeParam,
|
|
438
|
+
body: onetimeFeeBody,
|
|
439
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
export const billingRouter = express.Router();
|
|
3
3
|
import { authorize, isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
|
|
4
|
-
import { createBillingGroup, deleteBillingGroup, getAllBillingGroups, getBillingGroups, getInvoices, subscribedStoreList, updateBillingGroup } from '../controllers/billing.controllers.js';
|
|
5
|
-
import { billingGroupSchema, createBillingGroupsSchema, deleteBillingGroupsSchema, getBillingGroupsSchema, getInvoiceSchema, subscribedStoreListSchema, updateBillingGroupsSchema } from '../dtos/validation.dtos.js';
|
|
4
|
+
import { createBillingGroup, deleteBillingGroup, getAllBillingGroups, getBillingGroups, getInvoices, onetimePayment, subscribedStoreList, updateBillingGroup } from '../controllers/billing.controllers.js';
|
|
5
|
+
import { billingGroupSchema, createBillingGroupsSchema, deleteBillingGroupsSchema, getBillingGroupsSchema, getInvoiceSchema, onetimeFeeValid, subscribedStoreListSchema, updateBillingGroupsSchema } from '../dtos/validation.dtos.js';
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
billingRouter.post( '/get-subscribed-store-list', isAllowedSessionHandler, authorize( {
|
|
@@ -48,3 +48,9 @@ billingRouter.post( '/get-invoices', isAllowedSessionHandler, authorize( {
|
|
|
48
48
|
],
|
|
49
49
|
} ), validate( getInvoiceSchema ), getInvoices );
|
|
50
50
|
|
|
51
|
+
billingRouter.post( '/onetime-payment/:invoice', isAllowedSessionHandler, authorize( {
|
|
52
|
+
userType: [ 'tango' ], access: [
|
|
53
|
+
{ featureName: 'settings', name: 'paymentSubscriptions', permissions: [ 'isEdit' ] },
|
|
54
|
+
],
|
|
55
|
+
} ), validate( onetimeFeeValid ), onetimePayment );
|
|
56
|
+
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
export const paymentRouter = express.Router();
|
|
3
3
|
import { authorize, isAllowedSessionHandler, validate } from 'tango-app-api-middleware';
|
|
4
|
-
import { generateOrder, payUsingVallet } from '../controllers/payment.controller.js';
|
|
4
|
+
import { generateOrder, paymentHook, payUsingVallet, transactionList, verifyPayment } from '../controllers/payment.controller.js';
|
|
5
5
|
import { getVirtualAccount } from '../controllers/payment.controller.js';
|
|
6
|
-
import { getVirtualAccountSchema, valletPayValid } from '../dtos/validation.dtos.js';
|
|
6
|
+
import { getInvoiceSchema, getVirtualAccountSchema, valletPayValid, verifyPaymentValid } from '../dtos/validation.dtos.js';
|
|
7
|
+
import { checkDuplicateEvent } from '../dtos/isUniqueEvent.js';
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
paymentRouter.get( '/get-virtual-account', isAllowedSessionHandler, authorize( {
|
|
@@ -12,15 +13,29 @@ paymentRouter.get( '/get-virtual-account', isAllowedSessionHandler, authorize( {
|
|
|
12
13
|
],
|
|
13
14
|
} ), validate( getVirtualAccountSchema ), getVirtualAccount );
|
|
14
15
|
|
|
15
|
-
paymentRouter.
|
|
16
|
+
paymentRouter.get( '/wallet-pay/:invoice', isAllowedSessionHandler, authorize( {
|
|
16
17
|
userType: [ 'tango' ], access: [
|
|
17
18
|
{ featureName: 'settings', name: 'paymentSubscriptions', permissions: [ 'isEdit' ] },
|
|
18
19
|
],
|
|
19
|
-
} ),
|
|
20
|
+
} ), validate( valletPayValid ), payUsingVallet );
|
|
20
21
|
|
|
21
|
-
paymentRouter.get( '/
|
|
22
|
+
paymentRouter.get( '/generate-order/:invoice', isAllowedSessionHandler, authorize( {
|
|
22
23
|
userType: [ 'tango' ], access: [
|
|
23
24
|
{ featureName: 'settings', name: 'paymentSubscriptions', permissions: [ 'isEdit' ] },
|
|
24
25
|
],
|
|
25
|
-
} ), validate( valletPayValid ),
|
|
26
|
+
} ), validate( valletPayValid ), generateOrder );
|
|
27
|
+
|
|
28
|
+
paymentRouter.post( '/verify-payment/:invoice', isAllowedSessionHandler, authorize( {
|
|
29
|
+
userType: [ 'tango' ], access: [
|
|
30
|
+
{ featureName: 'settings', name: 'paymentSubscriptions', permissions: [ 'isEdit' ] },
|
|
31
|
+
],
|
|
32
|
+
} ), validate( verifyPaymentValid ), verifyPayment );
|
|
33
|
+
|
|
34
|
+
paymentRouter.post( '/get-transactions', isAllowedSessionHandler, authorize( {
|
|
35
|
+
userType: [ 'tango' ], access: [
|
|
36
|
+
{ featureName: 'settings', name: 'paymentSubscriptions', permissions: [ 'isView' ] },
|
|
37
|
+
],
|
|
38
|
+
} ), validate( getInvoiceSchema ), transactionList );
|
|
39
|
+
|
|
40
|
+
paymentRouter.post( '/payment-hook', checkDuplicateEvent, paymentHook );
|
|
26
41
|
|
|
@@ -26,6 +26,10 @@ export const count = async ( query ) => {
|
|
|
26
26
|
return await model.invoiceModel.count( query );
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
export const invoiceUpdateOne = async ( query ={}, record={} ) => {
|
|
30
|
+
return await model.invoiceModel.updateOne( query, record );
|
|
31
|
+
};
|
|
32
|
+
|
|
29
33
|
export const deleteRecord = async ( query ) => {
|
|
30
34
|
return await model.invoiceModel.deleteOne( query );
|
|
31
35
|
};
|
|
@@ -6,6 +6,9 @@ export const findOneAccount = ( query = {}, record = {} ) => {
|
|
|
6
6
|
return model.paymentAccountModel.findOne( query, record );
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
export const updateOneAccount = async ( query ={}, record={} ) => {
|
|
10
|
+
return await model.paymentAccountModel.updateOne( query, { $set: record } );
|
|
11
|
+
};
|
|
9
12
|
export const aggregate = ( query = [] ) => {
|
|
10
13
|
return model.paymentAccountModel.aggregate( query );
|
|
11
14
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import model from 'tango-api-schema';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const createTransaction = async ( data ) => {
|
|
5
|
+
return await model.transactionModel.create( data );
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const aggregateTransaction = async ( query ={} ) => {
|
|
9
|
+
return await model.transactionModel.aggregate( query );
|
|
10
|
+
};
|