@classytic/revenue 0.2.4 → 1.0.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.
- package/README.md +498 -501
- package/dist/actions-CwG-b7fR.d.ts +519 -0
- package/dist/core/index.d.ts +884 -0
- package/dist/core/index.js +2941 -0
- package/dist/core/index.js.map +1 -0
- package/dist/enums/index.d.ts +130 -0
- package/dist/enums/index.js +167 -0
- package/dist/enums/index.js.map +1 -0
- package/dist/index-BnJWVXuw.d.ts +378 -0
- package/dist/index-ChVD3P9k.d.ts +504 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +4362 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/index.d.ts +132 -0
- package/dist/providers/index.js +122 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/retry-80lBCmSe.d.ts +234 -0
- package/dist/schemas/index.d.ts +906 -0
- package/dist/schemas/index.js +533 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/validation.d.ts +309 -0
- package/dist/schemas/validation.js +249 -0
- package/dist/schemas/validation.js.map +1 -0
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.js +1632 -0
- package/dist/services/index.js.map +1 -0
- package/dist/split.enums-DHdM1YAV.d.ts +255 -0
- package/dist/split.schema-CETjPq10.d.ts +976 -0
- package/dist/utils/index.d.ts +24 -0
- package/dist/utils/index.js +1067 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +48 -32
- package/core/builder.js +0 -219
- package/core/container.js +0 -119
- package/core/errors.js +0 -262
- package/dist/types/core/builder.d.ts +0 -97
- package/dist/types/core/container.d.ts +0 -57
- package/dist/types/core/errors.d.ts +0 -122
- package/dist/types/enums/escrow.enums.d.ts +0 -24
- package/dist/types/enums/index.d.ts +0 -69
- package/dist/types/enums/monetization.enums.d.ts +0 -6
- package/dist/types/enums/payment.enums.d.ts +0 -16
- package/dist/types/enums/split.enums.d.ts +0 -25
- package/dist/types/enums/subscription.enums.d.ts +0 -15
- package/dist/types/enums/transaction.enums.d.ts +0 -24
- package/dist/types/index.d.ts +0 -22
- package/dist/types/providers/base.d.ts +0 -128
- package/dist/types/schemas/escrow/hold.schema.d.ts +0 -54
- package/dist/types/schemas/escrow/index.d.ts +0 -6
- package/dist/types/schemas/index.d.ts +0 -506
- package/dist/types/schemas/split/index.d.ts +0 -8
- package/dist/types/schemas/split/split.schema.d.ts +0 -142
- package/dist/types/schemas/subscription/index.d.ts +0 -152
- package/dist/types/schemas/subscription/info.schema.d.ts +0 -128
- package/dist/types/schemas/subscription/plan.schema.d.ts +0 -39
- package/dist/types/schemas/transaction/common.schema.d.ts +0 -12
- package/dist/types/schemas/transaction/gateway.schema.d.ts +0 -86
- package/dist/types/schemas/transaction/index.d.ts +0 -202
- package/dist/types/schemas/transaction/payment.schema.d.ts +0 -145
- package/dist/types/services/escrow.service.d.ts +0 -51
- package/dist/types/services/monetization.service.d.ts +0 -193
- package/dist/types/services/payment.service.d.ts +0 -117
- package/dist/types/services/transaction.service.d.ts +0 -40
- package/dist/types/utils/category-resolver.d.ts +0 -46
- package/dist/types/utils/commission-split.d.ts +0 -56
- package/dist/types/utils/commission.d.ts +0 -29
- package/dist/types/utils/hooks.d.ts +0 -17
- package/dist/types/utils/index.d.ts +0 -6
- package/dist/types/utils/logger.d.ts +0 -12
- package/dist/types/utils/subscription/actions.d.ts +0 -28
- package/dist/types/utils/subscription/index.d.ts +0 -2
- package/dist/types/utils/subscription/period.d.ts +0 -47
- package/dist/types/utils/transaction-type.d.ts +0 -102
- package/enums/escrow.enums.js +0 -36
- package/enums/index.d.ts +0 -116
- package/enums/index.js +0 -110
- package/enums/monetization.enums.js +0 -15
- package/enums/payment.enums.js +0 -64
- package/enums/split.enums.js +0 -37
- package/enums/subscription.enums.js +0 -33
- package/enums/transaction.enums.js +0 -69
- package/index.js +0 -76
- package/providers/base.js +0 -162
- package/schemas/escrow/hold.schema.js +0 -62
- package/schemas/escrow/index.js +0 -15
- package/schemas/index.d.ts +0 -33
- package/schemas/index.js +0 -27
- package/schemas/split/index.js +0 -16
- package/schemas/split/split.schema.js +0 -86
- package/schemas/subscription/index.js +0 -17
- package/schemas/subscription/info.schema.js +0 -115
- package/schemas/subscription/plan.schema.js +0 -48
- package/schemas/transaction/common.schema.js +0 -22
- package/schemas/transaction/gateway.schema.js +0 -69
- package/schemas/transaction/index.js +0 -20
- package/schemas/transaction/payment.schema.js +0 -110
- package/services/escrow.service.js +0 -353
- package/services/monetization.service.js +0 -675
- package/services/payment.service.js +0 -535
- package/services/transaction.service.js +0 -142
- package/utils/category-resolver.js +0 -74
- package/utils/commission-split.js +0 -180
- package/utils/commission.js +0 -83
- package/utils/hooks.js +0 -44
- package/utils/index.d.ts +0 -164
- package/utils/index.js +0 -16
- package/utils/logger.js +0 -36
- package/utils/subscription/actions.js +0 -68
- package/utils/subscription/index.js +0 -20
- package/utils/subscription/period.js +0 -123
- package/utils/transaction-type.js +0 -254
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gateway and Commission Schemas
|
|
3
|
-
* @classytic/revenue
|
|
4
|
-
*
|
|
5
|
-
* Schemas for payment gateway and commission tracking
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Schema } from 'mongoose';
|
|
9
|
-
import { PAYMENT_GATEWAY_TYPE_VALUES } from '../../enums/index.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Gateway Schema
|
|
13
|
-
* For payment gateway integration details
|
|
14
|
-
*/
|
|
15
|
-
export const gatewaySchema = new Schema({
|
|
16
|
-
type: {
|
|
17
|
-
type: String,
|
|
18
|
-
enum: PAYMENT_GATEWAY_TYPE_VALUES,
|
|
19
|
-
default: 'manual'
|
|
20
|
-
},
|
|
21
|
-
paymentIntentId: { type: String },
|
|
22
|
-
sessionId: { type: String },
|
|
23
|
-
paymentUrl: { type: String },
|
|
24
|
-
expiresAt: { type: Date },
|
|
25
|
-
metadata: { type: Schema.Types.Mixed },
|
|
26
|
-
}, { _id: false });
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Commission Schema
|
|
30
|
-
* Commission tracking for marketplace transactions
|
|
31
|
-
*/
|
|
32
|
-
export const commissionSchema = new Schema({
|
|
33
|
-
rate: {
|
|
34
|
-
type: Number,
|
|
35
|
-
min: 0,
|
|
36
|
-
max: 1
|
|
37
|
-
},
|
|
38
|
-
grossAmount: {
|
|
39
|
-
type: Number,
|
|
40
|
-
min: 0
|
|
41
|
-
},
|
|
42
|
-
gatewayFeeRate: {
|
|
43
|
-
type: Number,
|
|
44
|
-
min: 0,
|
|
45
|
-
max: 1
|
|
46
|
-
},
|
|
47
|
-
gatewayFeeAmount: {
|
|
48
|
-
type: Number,
|
|
49
|
-
min: 0
|
|
50
|
-
},
|
|
51
|
-
netAmount: {
|
|
52
|
-
type: Number,
|
|
53
|
-
min: 0
|
|
54
|
-
},
|
|
55
|
-
status: {
|
|
56
|
-
type: String,
|
|
57
|
-
enum: ['pending', 'due', 'paid', 'waived'],
|
|
58
|
-
default: 'pending'
|
|
59
|
-
},
|
|
60
|
-
dueDate: { type: Date },
|
|
61
|
-
paidDate: { type: Date },
|
|
62
|
-
paidBy: { type: Schema.Types.ObjectId, ref: 'User' },
|
|
63
|
-
notes: { type: String },
|
|
64
|
-
}, { _id: false });
|
|
65
|
-
|
|
66
|
-
export default {
|
|
67
|
-
gatewaySchema,
|
|
68
|
-
commissionSchema,
|
|
69
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transaction Schemas
|
|
3
|
-
* @classytic/revenue
|
|
4
|
-
*
|
|
5
|
-
* Re-exports all transaction-related schemas
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export * from './payment.schema.js';
|
|
9
|
-
export * from './gateway.schema.js';
|
|
10
|
-
export * from './common.schema.js';
|
|
11
|
-
|
|
12
|
-
import paymentSchemas from './payment.schema.js';
|
|
13
|
-
import gatewaySchemas from './gateway.schema.js';
|
|
14
|
-
import commonSchemas from './common.schema.js';
|
|
15
|
-
|
|
16
|
-
export default {
|
|
17
|
-
...paymentSchemas,
|
|
18
|
-
...gatewaySchemas,
|
|
19
|
-
...commonSchemas,
|
|
20
|
-
};
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Payment Schemas for Transaction Model
|
|
3
|
-
* @classytic/revenue
|
|
4
|
-
*
|
|
5
|
-
* Schemas for payment tracking in transactions
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Schema } from 'mongoose';
|
|
9
|
-
import {
|
|
10
|
-
PAYMENT_STATUS,
|
|
11
|
-
PAYMENT_STATUS_VALUES,
|
|
12
|
-
} from '../../enums/index.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Current Payment Schema
|
|
16
|
-
* Use this in your model: currentPayment: { type: currentPaymentSchema }
|
|
17
|
-
*
|
|
18
|
-
* Tracks the latest payment transaction for an entity
|
|
19
|
-
*/
|
|
20
|
-
export const currentPaymentSchema = new Schema({
|
|
21
|
-
transactionId: {
|
|
22
|
-
type: Schema.Types.ObjectId,
|
|
23
|
-
ref: 'Transaction',
|
|
24
|
-
index: true
|
|
25
|
-
},
|
|
26
|
-
amount: {
|
|
27
|
-
type: Number,
|
|
28
|
-
min: 0
|
|
29
|
-
},
|
|
30
|
-
status: {
|
|
31
|
-
type: String,
|
|
32
|
-
enum: PAYMENT_STATUS_VALUES,
|
|
33
|
-
default: 'pending',
|
|
34
|
-
index: true
|
|
35
|
-
},
|
|
36
|
-
method: {
|
|
37
|
-
type: String,
|
|
38
|
-
// Users define payment methods in their transaction model
|
|
39
|
-
},
|
|
40
|
-
reference: {
|
|
41
|
-
type: String,
|
|
42
|
-
trim: true
|
|
43
|
-
},
|
|
44
|
-
verifiedAt: {
|
|
45
|
-
type: Date
|
|
46
|
-
},
|
|
47
|
-
verifiedBy: {
|
|
48
|
-
type: Schema.Types.ObjectId,
|
|
49
|
-
ref: 'User'
|
|
50
|
-
},
|
|
51
|
-
}, { _id: false });
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Payment Summary Schema
|
|
55
|
-
* Use this in your model: paymentSummary: { type: paymentSummarySchema }
|
|
56
|
-
*
|
|
57
|
-
* Tracks payment history and totals
|
|
58
|
-
*/
|
|
59
|
-
export const paymentSummarySchema = new Schema({
|
|
60
|
-
totalPayments: {
|
|
61
|
-
type: Number,
|
|
62
|
-
default: 0,
|
|
63
|
-
min: 0
|
|
64
|
-
},
|
|
65
|
-
totalAmountPaid: {
|
|
66
|
-
type: Number,
|
|
67
|
-
default: 0,
|
|
68
|
-
min: 0
|
|
69
|
-
},
|
|
70
|
-
lastPaymentDate: {
|
|
71
|
-
type: Date
|
|
72
|
-
},
|
|
73
|
-
lastPaymentAmount: {
|
|
74
|
-
type: Number,
|
|
75
|
-
min: 0
|
|
76
|
-
},
|
|
77
|
-
}, { _id: false });
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Payment Details Schema (for manual payments)
|
|
81
|
-
* Embedded in Transaction model
|
|
82
|
-
*/
|
|
83
|
-
export const paymentDetailsSchema = new Schema({
|
|
84
|
-
provider: { type: String },
|
|
85
|
-
walletNumber: { type: String },
|
|
86
|
-
walletType: { type: String },
|
|
87
|
-
trxId: { type: String },
|
|
88
|
-
bankName: { type: String },
|
|
89
|
-
accountNumber: { type: String },
|
|
90
|
-
accountName: { type: String },
|
|
91
|
-
proofUrl: { type: String },
|
|
92
|
-
}, { _id: false });
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Tenant Snapshot Schema
|
|
96
|
-
* Captures organization payment details at transaction time (audit trail)
|
|
97
|
-
*/
|
|
98
|
-
export const tenantSnapshotSchema = new Schema({
|
|
99
|
-
paymentInstructions: { type: String },
|
|
100
|
-
bkashNumber: { type: String },
|
|
101
|
-
nagadNumber: { type: String },
|
|
102
|
-
bankAccount: { type: String },
|
|
103
|
-
}, { _id: false });
|
|
104
|
-
|
|
105
|
-
export default {
|
|
106
|
-
currentPaymentSchema,
|
|
107
|
-
paymentSummarySchema,
|
|
108
|
-
paymentDetailsSchema,
|
|
109
|
-
tenantSnapshotSchema,
|
|
110
|
-
};
|
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Escrow Service
|
|
3
|
-
* @classytic/revenue
|
|
4
|
-
*
|
|
5
|
-
* Platform-as-intermediary payment flow
|
|
6
|
-
* Hold funds → Verify → Split/Deduct → Release to organization
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { TransactionNotFoundError } from '../core/errors.js';
|
|
10
|
-
import { HOLD_STATUS, RELEASE_REASON, HOLD_REASON } from '../enums/escrow.enums.js';
|
|
11
|
-
import { TRANSACTION_TYPE, TRANSACTION_STATUS } from '../enums/transaction.enums.js';
|
|
12
|
-
import { SPLIT_STATUS } from '../enums/split.enums.js';
|
|
13
|
-
import { triggerHook } from '../utils/hooks.js';
|
|
14
|
-
import { calculateSplits, calculateOrganizationPayout } from '../utils/commission-split.js';
|
|
15
|
-
|
|
16
|
-
export class EscrowService {
|
|
17
|
-
constructor(container) {
|
|
18
|
-
this.container = container;
|
|
19
|
-
this.models = container.get('models');
|
|
20
|
-
this.providers = container.get('providers');
|
|
21
|
-
this.config = container.get('config');
|
|
22
|
-
this.hooks = container.get('hooks');
|
|
23
|
-
this.logger = container.get('logger');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Hold funds in escrow
|
|
28
|
-
*
|
|
29
|
-
* @param {String} transactionId - Transaction to hold
|
|
30
|
-
* @param {Object} options - Hold options
|
|
31
|
-
* @returns {Promise<Object>} Updated transaction
|
|
32
|
-
*/
|
|
33
|
-
async hold(transactionId, options = {}) {
|
|
34
|
-
const {
|
|
35
|
-
reason = HOLD_REASON.PAYMENT_VERIFICATION,
|
|
36
|
-
holdUntil = null,
|
|
37
|
-
metadata = {},
|
|
38
|
-
} = options;
|
|
39
|
-
|
|
40
|
-
const TransactionModel = this.models.Transaction;
|
|
41
|
-
const transaction = await TransactionModel.findById(transactionId);
|
|
42
|
-
|
|
43
|
-
if (!transaction) {
|
|
44
|
-
throw new TransactionNotFoundError(transactionId);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (transaction.status !== TRANSACTION_STATUS.VERIFIED) {
|
|
48
|
-
throw new Error(`Cannot hold transaction with status: ${transaction.status}. Must be verified.`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
transaction.hold = {
|
|
52
|
-
status: HOLD_STATUS.HELD,
|
|
53
|
-
heldAmount: transaction.amount,
|
|
54
|
-
releasedAmount: 0,
|
|
55
|
-
reason,
|
|
56
|
-
heldAt: new Date(),
|
|
57
|
-
...(holdUntil && { holdUntil }),
|
|
58
|
-
releases: [],
|
|
59
|
-
metadata,
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
await transaction.save();
|
|
63
|
-
|
|
64
|
-
this._triggerHook('escrow.held', {
|
|
65
|
-
transaction,
|
|
66
|
-
heldAmount: transaction.amount,
|
|
67
|
-
reason,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
return transaction;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Release funds from escrow to recipient
|
|
75
|
-
*
|
|
76
|
-
* @param {String} transactionId - Transaction to release
|
|
77
|
-
* @param {Object} options - Release options
|
|
78
|
-
* @returns {Promise<Object>} { transaction, releaseTransaction }
|
|
79
|
-
*/
|
|
80
|
-
async release(transactionId, options = {}) {
|
|
81
|
-
const {
|
|
82
|
-
amount = null,
|
|
83
|
-
recipientId,
|
|
84
|
-
recipientType = 'organization',
|
|
85
|
-
reason = RELEASE_REASON.PAYMENT_VERIFIED,
|
|
86
|
-
releasedBy = null,
|
|
87
|
-
createTransaction = true,
|
|
88
|
-
metadata = {},
|
|
89
|
-
} = options;
|
|
90
|
-
|
|
91
|
-
const TransactionModel = this.models.Transaction;
|
|
92
|
-
const transaction = await TransactionModel.findById(transactionId);
|
|
93
|
-
|
|
94
|
-
if (!transaction) {
|
|
95
|
-
throw new TransactionNotFoundError(transactionId);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (!transaction.hold || transaction.hold.status !== HOLD_STATUS.HELD) {
|
|
99
|
-
throw new Error(`Transaction is not in held status. Current: ${transaction.hold?.status || 'none'}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (!recipientId) {
|
|
103
|
-
throw new Error('recipientId is required for release');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const releaseAmount = amount || (transaction.hold.heldAmount - transaction.hold.releasedAmount);
|
|
107
|
-
const availableAmount = transaction.hold.heldAmount - transaction.hold.releasedAmount;
|
|
108
|
-
|
|
109
|
-
if (releaseAmount > availableAmount) {
|
|
110
|
-
throw new Error(`Release amount (${releaseAmount}) exceeds available held amount (${availableAmount})`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const releaseRecord = {
|
|
114
|
-
amount: releaseAmount,
|
|
115
|
-
recipientId,
|
|
116
|
-
recipientType,
|
|
117
|
-
releasedAt: new Date(),
|
|
118
|
-
releasedBy,
|
|
119
|
-
reason,
|
|
120
|
-
metadata,
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
transaction.hold.releases.push(releaseRecord);
|
|
124
|
-
transaction.hold.releasedAmount += releaseAmount;
|
|
125
|
-
|
|
126
|
-
const isFullRelease = transaction.hold.releasedAmount >= transaction.hold.heldAmount;
|
|
127
|
-
const isPartialRelease = transaction.hold.releasedAmount > 0 && transaction.hold.releasedAmount < transaction.hold.heldAmount;
|
|
128
|
-
|
|
129
|
-
if (isFullRelease) {
|
|
130
|
-
transaction.hold.status = HOLD_STATUS.RELEASED;
|
|
131
|
-
transaction.hold.releasedAt = new Date();
|
|
132
|
-
transaction.status = TRANSACTION_STATUS.COMPLETED;
|
|
133
|
-
} else if (isPartialRelease) {
|
|
134
|
-
transaction.hold.status = HOLD_STATUS.PARTIALLY_RELEASED;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
await transaction.save();
|
|
138
|
-
|
|
139
|
-
let releaseTransaction = null;
|
|
140
|
-
if (createTransaction) {
|
|
141
|
-
releaseTransaction = await TransactionModel.create({
|
|
142
|
-
organizationId: transaction.organizationId,
|
|
143
|
-
customerId: recipientId,
|
|
144
|
-
amount: releaseAmount,
|
|
145
|
-
currency: transaction.currency,
|
|
146
|
-
category: transaction.category,
|
|
147
|
-
type: TRANSACTION_TYPE.INCOME,
|
|
148
|
-
method: transaction.method,
|
|
149
|
-
status: TRANSACTION_STATUS.COMPLETED,
|
|
150
|
-
gateway: transaction.gateway,
|
|
151
|
-
referenceId: transaction.referenceId,
|
|
152
|
-
referenceModel: transaction.referenceModel,
|
|
153
|
-
metadata: {
|
|
154
|
-
...metadata,
|
|
155
|
-
isRelease: true,
|
|
156
|
-
heldTransactionId: transaction._id.toString(),
|
|
157
|
-
releaseReason: reason,
|
|
158
|
-
recipientType,
|
|
159
|
-
},
|
|
160
|
-
idempotencyKey: `release_${transaction._id}_${Date.now()}`,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
this._triggerHook('escrow.released', {
|
|
165
|
-
transaction,
|
|
166
|
-
releaseTransaction,
|
|
167
|
-
releaseAmount,
|
|
168
|
-
recipientId,
|
|
169
|
-
recipientType,
|
|
170
|
-
reason,
|
|
171
|
-
isFullRelease,
|
|
172
|
-
isPartialRelease,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
transaction,
|
|
177
|
-
releaseTransaction,
|
|
178
|
-
releaseAmount,
|
|
179
|
-
isFullRelease,
|
|
180
|
-
isPartialRelease,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Cancel hold and release back to customer
|
|
186
|
-
*
|
|
187
|
-
* @param {String} transactionId - Transaction to cancel hold
|
|
188
|
-
* @param {Object} options - Cancel options
|
|
189
|
-
* @returns {Promise<Object>} Updated transaction
|
|
190
|
-
*/
|
|
191
|
-
async cancel(transactionId, options = {}) {
|
|
192
|
-
const { reason = 'Hold cancelled', metadata = {} } = options;
|
|
193
|
-
|
|
194
|
-
const TransactionModel = this.models.Transaction;
|
|
195
|
-
const transaction = await TransactionModel.findById(transactionId);
|
|
196
|
-
|
|
197
|
-
if (!transaction) {
|
|
198
|
-
throw new TransactionNotFoundError(transactionId);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (!transaction.hold || transaction.hold.status !== HOLD_STATUS.HELD) {
|
|
202
|
-
throw new Error(`Transaction is not in held status. Current: ${transaction.hold?.status || 'none'}`);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
transaction.hold.status = HOLD_STATUS.CANCELLED;
|
|
206
|
-
transaction.hold.cancelledAt = new Date();
|
|
207
|
-
transaction.hold.metadata = {
|
|
208
|
-
...transaction.hold.metadata,
|
|
209
|
-
...metadata,
|
|
210
|
-
cancelReason: reason,
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
transaction.status = TRANSACTION_STATUS.CANCELLED;
|
|
214
|
-
|
|
215
|
-
await transaction.save();
|
|
216
|
-
|
|
217
|
-
this._triggerHook('escrow.cancelled', {
|
|
218
|
-
transaction,
|
|
219
|
-
reason,
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
return transaction;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Split payment to multiple recipients
|
|
227
|
-
* Deducts splits from held amount and releases remainder to organization
|
|
228
|
-
*
|
|
229
|
-
* @param {String} transactionId - Transaction to split
|
|
230
|
-
* @param {Array} splitRules - Split configuration
|
|
231
|
-
* @returns {Promise<Object>} { transaction, splitTransactions, organizationTransaction }
|
|
232
|
-
*/
|
|
233
|
-
async split(transactionId, splitRules = []) {
|
|
234
|
-
const TransactionModel = this.models.Transaction;
|
|
235
|
-
const transaction = await TransactionModel.findById(transactionId);
|
|
236
|
-
|
|
237
|
-
if (!transaction) {
|
|
238
|
-
throw new TransactionNotFoundError(transactionId);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (!transaction.hold || transaction.hold.status !== HOLD_STATUS.HELD) {
|
|
242
|
-
throw new Error(`Transaction must be held before splitting. Current: ${transaction.hold?.status || 'none'}`);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (!splitRules || splitRules.length === 0) {
|
|
246
|
-
throw new Error('splitRules cannot be empty');
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const splits = calculateSplits(
|
|
250
|
-
transaction.amount,
|
|
251
|
-
splitRules,
|
|
252
|
-
transaction.commission?.gatewayFeeRate || 0
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
transaction.splits = splits;
|
|
256
|
-
await transaction.save();
|
|
257
|
-
|
|
258
|
-
const splitTransactions = [];
|
|
259
|
-
|
|
260
|
-
for (const split of splits) {
|
|
261
|
-
const splitTransaction = await TransactionModel.create({
|
|
262
|
-
organizationId: transaction.organizationId,
|
|
263
|
-
customerId: split.recipientId,
|
|
264
|
-
amount: split.netAmount,
|
|
265
|
-
currency: transaction.currency,
|
|
266
|
-
category: split.type,
|
|
267
|
-
type: TRANSACTION_TYPE.EXPENSE,
|
|
268
|
-
method: transaction.method,
|
|
269
|
-
status: TRANSACTION_STATUS.COMPLETED,
|
|
270
|
-
gateway: transaction.gateway,
|
|
271
|
-
referenceId: transaction.referenceId,
|
|
272
|
-
referenceModel: transaction.referenceModel,
|
|
273
|
-
metadata: {
|
|
274
|
-
isSplit: true,
|
|
275
|
-
splitType: split.type,
|
|
276
|
-
recipientType: split.recipientType,
|
|
277
|
-
originalTransactionId: transaction._id.toString(),
|
|
278
|
-
grossAmount: split.grossAmount,
|
|
279
|
-
gatewayFeeAmount: split.gatewayFeeAmount,
|
|
280
|
-
},
|
|
281
|
-
idempotencyKey: `split_${transaction._id}_${split.recipientId}_${Date.now()}`,
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
split.payoutTransactionId = splitTransaction._id.toString();
|
|
285
|
-
split.status = SPLIT_STATUS.PAID;
|
|
286
|
-
split.paidDate = new Date();
|
|
287
|
-
|
|
288
|
-
splitTransactions.push(splitTransaction);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
await transaction.save();
|
|
292
|
-
|
|
293
|
-
const organizationPayout = calculateOrganizationPayout(transaction.amount, splits);
|
|
294
|
-
|
|
295
|
-
const organizationTransaction = await this.release(transactionId, {
|
|
296
|
-
amount: organizationPayout,
|
|
297
|
-
recipientId: transaction.organizationId,
|
|
298
|
-
recipientType: 'organization',
|
|
299
|
-
reason: RELEASE_REASON.PAYMENT_VERIFIED,
|
|
300
|
-
createTransaction: true,
|
|
301
|
-
metadata: {
|
|
302
|
-
afterSplits: true,
|
|
303
|
-
totalSplits: splits.length,
|
|
304
|
-
totalSplitAmount: transaction.amount - organizationPayout,
|
|
305
|
-
},
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
this._triggerHook('escrow.split', {
|
|
309
|
-
transaction,
|
|
310
|
-
splits,
|
|
311
|
-
splitTransactions,
|
|
312
|
-
organizationTransaction: organizationTransaction.releaseTransaction,
|
|
313
|
-
organizationPayout,
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
return {
|
|
317
|
-
transaction,
|
|
318
|
-
splits,
|
|
319
|
-
splitTransactions,
|
|
320
|
-
organizationTransaction: organizationTransaction.releaseTransaction,
|
|
321
|
-
organizationPayout,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Get escrow status
|
|
327
|
-
*
|
|
328
|
-
* @param {String} transactionId - Transaction ID
|
|
329
|
-
* @returns {Promise<Object>} Escrow status
|
|
330
|
-
*/
|
|
331
|
-
async getStatus(transactionId) {
|
|
332
|
-
const TransactionModel = this.models.Transaction;
|
|
333
|
-
const transaction = await TransactionModel.findById(transactionId);
|
|
334
|
-
|
|
335
|
-
if (!transaction) {
|
|
336
|
-
throw new TransactionNotFoundError(transactionId);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return {
|
|
340
|
-
transaction,
|
|
341
|
-
hold: transaction.hold || null,
|
|
342
|
-
splits: transaction.splits || [],
|
|
343
|
-
hasHold: !!transaction.hold,
|
|
344
|
-
hasSplits: transaction.splits && transaction.splits.length > 0,
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
_triggerHook(event, data) {
|
|
349
|
-
triggerHook(this.hooks, event, data, this.logger);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
export default EscrowService;
|