xytara 1.0.0

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,303 @@
1
+ "use strict";
2
+
3
+ const crypto = require("crypto");
4
+
5
+ function setResultRemediation(state, transactionId, patch) {
6
+ if (!transactionId || !state.transactions.has(transactionId)) {
7
+ return;
8
+ }
9
+ const current = state.transactions.get(transactionId);
10
+ const currentTransactionRemediation = current.transaction && current.transaction.remediation && typeof current.transaction.remediation === "object"
11
+ ? current.transaction.remediation
12
+ : {};
13
+ const currentReceiptRemediation = current.receipt && current.receipt.remediation && typeof current.receipt.remediation === "object"
14
+ ? current.receipt.remediation
15
+ : {};
16
+ const nextRemediation = {
17
+ ...currentTransactionRemediation,
18
+ ...currentReceiptRemediation,
19
+ ...(patch || {}),
20
+ updated_at_iso: new Date().toISOString()
21
+ };
22
+ current.transaction.remediation = nextRemediation;
23
+ current.receipt.remediation = nextRemediation;
24
+ }
25
+
26
+ function appendHistory(record, event, body, nextStatus) {
27
+ const timestamp = new Date().toISOString();
28
+ const actor = body && body.actor && typeof body.actor === "object" ? body.actor : { type: "operator", role: "reviewer" };
29
+ const context = body && body.context && typeof body.context === "object" ? body.context : null;
30
+ const previousStatus = record.status || null;
31
+ return {
32
+ ...record,
33
+ status: nextStatus || record.status,
34
+ updated_at_iso: timestamp,
35
+ history: [
36
+ ...(Array.isArray(record.history) ? record.history : []),
37
+ {
38
+ at_iso: timestamp,
39
+ event,
40
+ note: body && typeof body.note === "string" ? body.note : null,
41
+ reason_code: body && typeof body.reason_code === "string" ? body.reason_code : record.reason_code || null,
42
+ action_code: body && typeof body.action_code === "string" ? body.action_code : null,
43
+ previous_status: previousStatus,
44
+ next_status: nextStatus || record.status,
45
+ actor,
46
+ context
47
+ }
48
+ ].slice(-20)
49
+ };
50
+ }
51
+
52
+ function createDispute(state, body) {
53
+ const timestamp = new Date().toISOString();
54
+ const actor = body && body.actor && typeof body.actor === "object" ? body.actor : { type: "operator", role: "creator" };
55
+ const context = body && body.context && typeof body.context === "object" ? body.context : null;
56
+ const dispute = {
57
+ dispute_id: `dsp_${crypto.randomBytes(6).toString("hex")}`,
58
+ title: typeof body.title === "string" && body.title.trim() ? body.title.trim() : "Dispute",
59
+ description: typeof body.description === "string" && body.description.trim() ? body.description.trim() : null,
60
+ status: "open",
61
+ transaction_id: body.transaction_id || null,
62
+ receipt_id: body.receipt_id || null,
63
+ reason_code: body.reason_code || "other",
64
+ note: body.note || null,
65
+ evidence: body && body.evidence && typeof body.evidence === "object" ? body.evidence : {},
66
+ created_at_iso: timestamp,
67
+ updated_at_iso: timestamp,
68
+ created_actor: actor,
69
+ created_context: context,
70
+ history: [
71
+ {
72
+ at_iso: timestamp,
73
+ event: "opened",
74
+ note: body.note || body.description || null,
75
+ reason_code: body.reason_code || "other",
76
+ action_code: null,
77
+ previous_status: null,
78
+ next_status: "open",
79
+ actor,
80
+ context
81
+ }
82
+ ]
83
+ };
84
+ state.disputes.set(dispute.dispute_id, dispute);
85
+ setResultRemediation(state, dispute.transaction_id, {
86
+ status: "under_dispute",
87
+ reason_code: dispute.reason_code,
88
+ active_case_type: "dispute",
89
+ active_case_id: dispute.dispute_id,
90
+ compensating_action_status: "none"
91
+ });
92
+ return dispute;
93
+ }
94
+
95
+ function createRefund(state, body) {
96
+ const timestamp = new Date().toISOString();
97
+ const actor = body && body.actor && typeof body.actor === "object" ? body.actor : { type: "operator", role: "creator" };
98
+ const context = body && body.context && typeof body.context === "object" ? body.context : null;
99
+ const refund = {
100
+ refund_id: `rfd_${crypto.randomBytes(6).toString("hex")}`,
101
+ status: "requested",
102
+ transaction_id: body.transaction_id || null,
103
+ receipt_id: body.receipt_id || null,
104
+ reason_code: body.reason_code || "other",
105
+ note: body.note || null,
106
+ requested_by: typeof body.requested_by === "string" && body.requested_by.trim() ? body.requested_by.trim() : "operator",
107
+ created_at_iso: timestamp,
108
+ updated_at_iso: timestamp,
109
+ created_actor: actor,
110
+ created_context: context,
111
+ history: [
112
+ {
113
+ at_iso: timestamp,
114
+ event: "requested",
115
+ note: body.note || null,
116
+ reason_code: body.reason_code || "other",
117
+ action_code: null,
118
+ previous_status: null,
119
+ next_status: "requested",
120
+ actor,
121
+ context
122
+ }
123
+ ]
124
+ };
125
+ state.refunds.set(refund.refund_id, refund);
126
+ setResultRemediation(state, refund.transaction_id, {
127
+ status: "remediation_pending",
128
+ reason_code: refund.reason_code,
129
+ active_case_type: "refund",
130
+ active_case_id: refund.refund_id,
131
+ compensating_action_status: "none"
132
+ });
133
+ return refund;
134
+ }
135
+
136
+ function actOnDispute(state, disputeId, body) {
137
+ const current = state.disputes.get(disputeId);
138
+ if (!current) return null;
139
+ const action = String((body && body.action) || "").trim();
140
+ const statusByAction = {
141
+ review: "under_review",
142
+ escalate: "escalated",
143
+ reopen: "open",
144
+ resolve: "resolved",
145
+ dismiss: "dismissed"
146
+ };
147
+ const nextStatus = statusByAction[action] || current.status;
148
+ const next = appendHistory(current, action || "review", body, nextStatus);
149
+ if (body && typeof body.resolution_code === "string" && body.resolution_code.trim()) {
150
+ next.resolution_code = body.resolution_code.trim();
151
+ }
152
+ state.disputes.set(disputeId, next);
153
+ const remediationStatus = next.status === "resolved" || next.status === "dismissed"
154
+ ? "none"
155
+ : next.status === "escalated"
156
+ ? "escalated"
157
+ : "under_dispute";
158
+ setResultRemediation(state, next.transaction_id, {
159
+ status: remediationStatus,
160
+ reason_code: remediationStatus === "none" ? null : next.reason_code,
161
+ active_case_type: remediationStatus === "none" ? null : "dispute",
162
+ active_case_id: remediationStatus === "none" ? null : next.dispute_id,
163
+ compensating_action_status: remediationStatus === "none" ? null : "none"
164
+ });
165
+ return next;
166
+ }
167
+
168
+ function actOnRefund(state, refundId, body) {
169
+ const current = state.refunds.get(refundId);
170
+ if (!current) return null;
171
+ const action = String((body && body.action) || "").trim();
172
+ const statusByAction = {
173
+ review: "under_review",
174
+ approve: "approved",
175
+ complete: "completed",
176
+ cancel: "cancelled",
177
+ fail: "failed",
178
+ pending_manual: "pending_manual"
179
+ };
180
+ const nextStatus = statusByAction[action] || current.status;
181
+ const next = appendHistory(current, action || "review", body, nextStatus);
182
+ if (body && typeof body.action_code === "string" && body.action_code.trim()) {
183
+ next.action_code = body.action_code.trim();
184
+ }
185
+ state.refunds.set(refundId, next);
186
+ const remediationStatus = next.status === "completed"
187
+ ? "compensated"
188
+ : next.status === "cancelled"
189
+ ? "none"
190
+ : next.status === "approved"
191
+ ? "remediation_pending"
192
+ : next.status === "pending_manual"
193
+ ? "remediation_pending"
194
+ : next.status === "failed"
195
+ ? "compensation_failed"
196
+ : "remediation_pending";
197
+ setResultRemediation(state, next.transaction_id, {
198
+ status: remediationStatus,
199
+ reason_code: remediationStatus === "none" ? null : next.reason_code,
200
+ active_case_type: remediationStatus === "compensated" || remediationStatus === "none" ? null : "refund",
201
+ active_case_id: remediationStatus === "compensated" || remediationStatus === "none" ? null : next.refund_id,
202
+ compensating_action_status: remediationStatus === "none" ? null : next.status
203
+ });
204
+ return next;
205
+ }
206
+
207
+ function createRemediationTicket(state, body) {
208
+ const timestamp = new Date().toISOString();
209
+ const actor = body && body.actor && typeof body.actor === "object" ? body.actor : { type: "operator", role: "creator" };
210
+ const context = body && body.context && typeof body.context === "object" ? body.context : null;
211
+ const ticket = {
212
+ ticket_id: `rmt_${crypto.randomBytes(6).toString("hex")}`,
213
+ status: "open",
214
+ transaction_id: body.transaction_id || null,
215
+ receipt_id: body.receipt_id || null,
216
+ reason_code: body.reason_code || "other",
217
+ action_code: body.action_code || "investigate",
218
+ assigned_owner: typeof body.assigned_owner === "string" && body.assigned_owner.trim() ? body.assigned_owner.trim() : null,
219
+ note: body.note || null,
220
+ created_at_iso: timestamp,
221
+ updated_at_iso: timestamp,
222
+ created_actor: actor,
223
+ created_context: context,
224
+ history: [
225
+ {
226
+ at_iso: timestamp,
227
+ event: "opened",
228
+ note: body.note || null,
229
+ reason_code: body.reason_code || "other",
230
+ action_code: body.action_code || "investigate",
231
+ previous_status: null,
232
+ next_status: "open",
233
+ actor,
234
+ context
235
+ }
236
+ ]
237
+ };
238
+ state.remediationTickets.set(ticket.ticket_id, ticket);
239
+ setResultRemediation(state, ticket.transaction_id, {
240
+ status: "remediation_pending",
241
+ reason_code: ticket.reason_code,
242
+ active_case_type: "remediation_ticket",
243
+ active_case_id: ticket.ticket_id,
244
+ compensating_action_status: ticket.action_code
245
+ });
246
+ return ticket;
247
+ }
248
+
249
+ function actOnRemediationTicket(state, ticketId, body) {
250
+ const current = state.remediationTickets.get(ticketId);
251
+ if (!current) return null;
252
+ const action = String((body && body.action) || "").trim();
253
+ const statusByAction = {
254
+ acknowledge: "acknowledged",
255
+ assign: "assigned",
256
+ escalate: "escalated",
257
+ resolve: "resolved",
258
+ clear: "cleared"
259
+ };
260
+ const next = appendHistory(current, action || "acknowledge", body, statusByAction[action] || current.status);
261
+ if (body && body.reason_code) {
262
+ next.reason_code = body.reason_code;
263
+ }
264
+ if (body && body.action_code) {
265
+ next.action_code = body.action_code;
266
+ }
267
+ if (body && typeof body.assigned_owner === "string" && body.assigned_owner.trim()) {
268
+ next.assigned_owner = body.assigned_owner.trim();
269
+ }
270
+ if (body && typeof body.note === "string" && body.note.trim()) {
271
+ if (next.status === "assigned") {
272
+ next.assignment_note = body.note.trim();
273
+ }
274
+ if (next.status === "cleared") {
275
+ next.cleared_note = body.note.trim();
276
+ }
277
+ }
278
+ state.remediationTickets.set(ticketId, next);
279
+ const remediationStatus = next.status === "cleared"
280
+ ? "none"
281
+ : next.status === "resolved"
282
+ ? "none"
283
+ : next.status === "escalated"
284
+ ? "escalated"
285
+ : "remediation_pending";
286
+ setResultRemediation(state, next.transaction_id, {
287
+ status: remediationStatus,
288
+ reason_code: remediationStatus === "none" ? null : next.reason_code,
289
+ active_case_type: remediationStatus === "none" ? null : "remediation_ticket",
290
+ active_case_id: remediationStatus === "none" ? null : next.ticket_id,
291
+ compensating_action_status: remediationStatus === "none" ? null : next.action_code
292
+ });
293
+ return next;
294
+ }
295
+
296
+ module.exports = {
297
+ actOnDispute,
298
+ actOnRefund,
299
+ actOnRemediationTicket,
300
+ createDispute,
301
+ createRefund,
302
+ createRemediationTicket
303
+ };