@legible-sync/example-eda 1.2.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.
- package/.eslintrc.js +16 -0
- package/README.md +103 -0
- package/__tests__/integration/ecommerce-flow.test.ts +247 -0
- package/__tests__/unit/EventBus.test.ts +154 -0
- package/__tests__/unit/PluginManager.test.ts +111 -0
- package/__tests__/unit/concepts/User.test.ts +130 -0
- package/dist/core/EventBus.d.ts +16 -0
- package/dist/core/EventBus.d.ts.map +1 -0
- package/dist/core/EventBus.js +44 -0
- package/dist/core/EventBus.js.map +1 -0
- package/dist/core/PluginManager.d.ts +16 -0
- package/dist/core/PluginManager.d.ts.map +1 -0
- package/dist/core/PluginManager.js +37 -0
- package/dist/core/PluginManager.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +145 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/analytics/concepts/Analytics.d.ts +3 -0
- package/dist/plugins/analytics/concepts/Analytics.d.ts.map +1 -0
- package/dist/plugins/analytics/concepts/Analytics.js +43 -0
- package/dist/plugins/analytics/concepts/Analytics.js.map +1 -0
- package/dist/plugins/analytics/index.d.ts +3 -0
- package/dist/plugins/analytics/index.d.ts.map +1 -0
- package/dist/plugins/analytics/index.js +16 -0
- package/dist/plugins/analytics/index.js.map +1 -0
- package/dist/plugins/analytics/syncs/analytics-events.sync.d.ts +3 -0
- package/dist/plugins/analytics/syncs/analytics-events.sync.d.ts.map +1 -0
- package/dist/plugins/analytics/syncs/analytics-events.sync.js +101 -0
- package/dist/plugins/analytics/syncs/analytics-events.sync.js.map +1 -0
- package/dist/plugins/inventory/concepts/Inventory.d.ts +3 -0
- package/dist/plugins/inventory/concepts/Inventory.d.ts.map +1 -0
- package/dist/plugins/inventory/concepts/Inventory.js +60 -0
- package/dist/plugins/inventory/concepts/Inventory.js.map +1 -0
- package/dist/plugins/inventory/index.d.ts +3 -0
- package/dist/plugins/inventory/index.d.ts.map +1 -0
- package/dist/plugins/inventory/index.js +15 -0
- package/dist/plugins/inventory/index.js.map +1 -0
- package/dist/plugins/notifications/concepts/Notification.d.ts +3 -0
- package/dist/plugins/notifications/concepts/Notification.d.ts.map +1 -0
- package/dist/plugins/notifications/concepts/Notification.js +42 -0
- package/dist/plugins/notifications/concepts/Notification.js.map +1 -0
- package/dist/plugins/notifications/index.d.ts +3 -0
- package/dist/plugins/notifications/index.d.ts.map +1 -0
- package/dist/plugins/notifications/index.js +15 -0
- package/dist/plugins/notifications/index.js.map +1 -0
- package/dist/plugins/orders/concepts/Order.d.ts +3 -0
- package/dist/plugins/orders/concepts/Order.d.ts.map +1 -0
- package/dist/plugins/orders/concepts/Order.js +98 -0
- package/dist/plugins/orders/concepts/Order.js.map +1 -0
- package/dist/plugins/orders/index.d.ts +3 -0
- package/dist/plugins/orders/index.d.ts.map +1 -0
- package/dist/plugins/orders/index.js +16 -0
- package/dist/plugins/orders/index.js.map +1 -0
- package/dist/plugins/orders/syncs/order-workflow.sync.d.ts +3 -0
- package/dist/plugins/orders/syncs/order-workflow.sync.d.ts.map +1 -0
- package/dist/plugins/orders/syncs/order-workflow.sync.js +168 -0
- package/dist/plugins/orders/syncs/order-workflow.sync.js.map +1 -0
- package/dist/plugins/payments/concepts/Payment.d.ts +3 -0
- package/dist/plugins/payments/concepts/Payment.d.ts.map +1 -0
- package/dist/plugins/payments/concepts/Payment.js +156 -0
- package/dist/plugins/payments/concepts/Payment.js.map +1 -0
- package/dist/plugins/payments/index.d.ts +3 -0
- package/dist/plugins/payments/index.d.ts.map +1 -0
- package/dist/plugins/payments/index.js +16 -0
- package/dist/plugins/payments/index.js.map +1 -0
- package/dist/plugins/payments/syncs/payment-workflow.sync.d.ts +3 -0
- package/dist/plugins/payments/syncs/payment-workflow.sync.d.ts.map +1 -0
- package/dist/plugins/payments/syncs/payment-workflow.sync.js +264 -0
- package/dist/plugins/payments/syncs/payment-workflow.sync.js.map +1 -0
- package/dist/plugins/products/concepts/Product.d.ts +3 -0
- package/dist/plugins/products/concepts/Product.d.ts.map +1 -0
- package/dist/plugins/products/concepts/Product.js +85 -0
- package/dist/plugins/products/concepts/Product.js.map +1 -0
- package/dist/plugins/products/index.d.ts +3 -0
- package/dist/plugins/products/index.d.ts.map +1 -0
- package/dist/plugins/products/index.js +16 -0
- package/dist/plugins/products/index.js.map +1 -0
- package/dist/plugins/products/syncs/product-events.sync.d.ts +3 -0
- package/dist/plugins/products/syncs/product-events.sync.d.ts.map +1 -0
- package/dist/plugins/products/syncs/product-events.sync.js +77 -0
- package/dist/plugins/products/syncs/product-events.sync.js.map +1 -0
- package/dist/plugins/users/concepts/User.d.ts +3 -0
- package/dist/plugins/users/concepts/User.d.ts.map +1 -0
- package/dist/plugins/users/concepts/User.js +81 -0
- package/dist/plugins/users/concepts/User.js.map +1 -0
- package/dist/plugins/users/index.d.ts +3 -0
- package/dist/plugins/users/index.d.ts.map +1 -0
- package/dist/plugins/users/index.js +16 -0
- package/dist/plugins/users/index.js.map +1 -0
- package/dist/plugins/users/syncs/user-events.sync.d.ts +3 -0
- package/dist/plugins/users/syncs/user-events.sync.d.ts.map +1 -0
- package/dist/plugins/users/syncs/user-events.sync.js +75 -0
- package/dist/plugins/users/syncs/user-events.sync.js.map +1 -0
- package/package.json +40 -0
- package/src/core/EventBus.ts +55 -0
- package/src/core/PluginManager.ts +51 -0
- package/src/index.ts +169 -0
- package/src/plugins/analytics/concepts/Analytics.ts +53 -0
- package/src/plugins/analytics/index.ts +15 -0
- package/src/plugins/analytics/syncs/analytics-events.sync.ts +103 -0
- package/src/plugins/inventory/concepts/Inventory.ts +73 -0
- package/src/plugins/inventory/index.ts +14 -0
- package/src/plugins/notifications/concepts/Notification.ts +49 -0
- package/src/plugins/notifications/index.ts +14 -0
- package/src/plugins/orders/concepts/Order.ts +118 -0
- package/src/plugins/orders/index.ts +15 -0
- package/src/plugins/orders/syncs/order-workflow.sync.ts +173 -0
- package/src/plugins/payments/concepts/Payment.ts +186 -0
- package/src/plugins/payments/index.ts +15 -0
- package/src/plugins/payments/syncs/payment-workflow.sync.ts +274 -0
- package/src/plugins/products/concepts/Product.ts +102 -0
- package/src/plugins/products/index.ts +15 -0
- package/src/plugins/products/syncs/product-events.sync.ts +78 -0
- package/src/plugins/users/concepts/User.ts +97 -0
- package/src/plugins/users/index.ts +15 -0
- package/src/plugins/users/syncs/user-events.sync.ts +76 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// plugins/payments/concepts/Payment.ts
|
|
2
|
+
import { Concept } from '@legible-sync/core';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
|
|
5
|
+
export const Payment: Concept = {
|
|
6
|
+
state: {
|
|
7
|
+
payments: new Map<string, any>(),
|
|
8
|
+
transactions: new Map<string, any>(),
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
async execute(action: string, input: any) {
|
|
12
|
+
const state = this.state;
|
|
13
|
+
|
|
14
|
+
switch (action) {
|
|
15
|
+
case 'initiate': {
|
|
16
|
+
const { orderId, amount, method } = input;
|
|
17
|
+
|
|
18
|
+
// Validation
|
|
19
|
+
if (!orderId || !amount || amount <= 0) {
|
|
20
|
+
throw new Error('Order ID and positive amount are required');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Create payment
|
|
24
|
+
const paymentId = uuidv4();
|
|
25
|
+
const payment = {
|
|
26
|
+
id: paymentId,
|
|
27
|
+
orderId,
|
|
28
|
+
amount,
|
|
29
|
+
method: method || 'credit_card',
|
|
30
|
+
status: 'pending',
|
|
31
|
+
createdAt: new Date(),
|
|
32
|
+
transactions: []
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
state.payments.set(paymentId, payment);
|
|
36
|
+
|
|
37
|
+
return { paymentId, payment };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
case 'process': {
|
|
41
|
+
const { paymentId } = input;
|
|
42
|
+
const payment = state.payments.get(paymentId);
|
|
43
|
+
if (!payment) {
|
|
44
|
+
throw new Error('Payment not found');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (payment.status !== 'pending') {
|
|
48
|
+
throw new Error('Payment is not in pending status');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Simulate payment processing (in real app, integrate with payment gateway)
|
|
52
|
+
const transactionId = uuidv4();
|
|
53
|
+
const transaction = {
|
|
54
|
+
id: transactionId,
|
|
55
|
+
paymentId,
|
|
56
|
+
type: 'charge',
|
|
57
|
+
amount: payment.amount,
|
|
58
|
+
status: 'processing',
|
|
59
|
+
gatewayResponse: 'Processing payment...',
|
|
60
|
+
createdAt: new Date()
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
state.transactions.set(transactionId, transaction);
|
|
64
|
+
payment.transactions.push(transactionId);
|
|
65
|
+
payment.status = 'processing';
|
|
66
|
+
state.payments.set(paymentId, payment);
|
|
67
|
+
|
|
68
|
+
return { payment, transaction };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case 'confirm': {
|
|
72
|
+
const { paymentId, transactionId } = input;
|
|
73
|
+
const payment = state.payments.get(paymentId);
|
|
74
|
+
const transaction = state.transactions.get(transactionId);
|
|
75
|
+
|
|
76
|
+
if (!payment || !transaction) {
|
|
77
|
+
throw new Error('Payment or transaction not found');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (payment.status !== 'processing') {
|
|
81
|
+
throw new Error('Payment is not in processing status');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Confirm payment
|
|
85
|
+
payment.status = 'completed';
|
|
86
|
+
payment.completedAt = new Date();
|
|
87
|
+
transaction.status = 'completed';
|
|
88
|
+
transaction.gatewayResponse = 'Payment successful';
|
|
89
|
+
transaction.completedAt = new Date();
|
|
90
|
+
|
|
91
|
+
state.payments.set(paymentId, payment);
|
|
92
|
+
state.transactions.set(transactionId, transaction);
|
|
93
|
+
|
|
94
|
+
return { payment, transaction };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case 'fail': {
|
|
98
|
+
const { paymentId, reason } = input;
|
|
99
|
+
const payment = state.payments.get(paymentId);
|
|
100
|
+
if (!payment) {
|
|
101
|
+
throw new Error('Payment not found');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
payment.status = 'failed';
|
|
105
|
+
payment.failedAt = new Date();
|
|
106
|
+
payment.failureReason = reason;
|
|
107
|
+
state.payments.set(paymentId, payment);
|
|
108
|
+
|
|
109
|
+
// Mark any pending transactions as failed
|
|
110
|
+
for (const txId of payment.transactions) {
|
|
111
|
+
const tx = state.transactions.get(txId);
|
|
112
|
+
if (tx && tx.status === 'processing') {
|
|
113
|
+
tx.status = 'failed';
|
|
114
|
+
tx.gatewayResponse = reason;
|
|
115
|
+
state.transactions.set(txId, tx);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { payment };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
case 'refund': {
|
|
123
|
+
const { paymentId, amount, reason } = input;
|
|
124
|
+
const payment = state.payments.get(paymentId);
|
|
125
|
+
if (!payment) {
|
|
126
|
+
throw new Error('Payment not found');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (payment.status !== 'completed') {
|
|
130
|
+
throw new Error('Can only refund completed payments');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const refundAmount = amount || payment.amount;
|
|
134
|
+
if (refundAmount > payment.amount) {
|
|
135
|
+
throw new Error('Refund amount cannot exceed payment amount');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Create refund transaction
|
|
139
|
+
const transactionId = uuidv4();
|
|
140
|
+
const transaction = {
|
|
141
|
+
id: transactionId,
|
|
142
|
+
paymentId,
|
|
143
|
+
type: 'refund',
|
|
144
|
+
amount: refundAmount,
|
|
145
|
+
status: 'completed',
|
|
146
|
+
gatewayResponse: 'Refund processed',
|
|
147
|
+
reason,
|
|
148
|
+
createdAt: new Date(),
|
|
149
|
+
completedAt: new Date()
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
state.transactions.set(transactionId, transaction);
|
|
153
|
+
payment.transactions.push(transactionId);
|
|
154
|
+
payment.status = refundAmount === payment.amount ? 'refunded' : 'partially_refunded';
|
|
155
|
+
state.payments.set(paymentId, payment);
|
|
156
|
+
|
|
157
|
+
return { payment, transaction };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
case 'get': {
|
|
161
|
+
const { paymentId } = input;
|
|
162
|
+
const payment = state.payments.get(paymentId);
|
|
163
|
+
if (!payment) {
|
|
164
|
+
throw new Error('Payment not found');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const transactions = payment.transactions.map((txId: string) => state.transactions.get(txId));
|
|
168
|
+
return { payment: { ...payment, transactions } };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
case 'getByOrderId': {
|
|
172
|
+
const { orderId } = input;
|
|
173
|
+
for (const [paymentId, payment] of state.payments) {
|
|
174
|
+
if (payment.orderId === orderId) {
|
|
175
|
+
const transactions = payment.transactions.map((txId: string) => state.transactions.get(txId));
|
|
176
|
+
return { payment: { ...payment, transactions }, paymentId };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
throw new Error('Payment not found for order');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
default:
|
|
183
|
+
throw new Error(`Unknown action: ${action}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// plugins/payments/index.ts
|
|
2
|
+
import { Plugin } from '../../core/PluginManager';
|
|
3
|
+
import { Payment } from './concepts/Payment';
|
|
4
|
+
import { paymentWorkflowSyncs } from './syncs/payment-workflow.sync';
|
|
5
|
+
|
|
6
|
+
export const paymentsPlugin: Plugin = {
|
|
7
|
+
name: 'payments',
|
|
8
|
+
concepts: {
|
|
9
|
+
Payment
|
|
10
|
+
},
|
|
11
|
+
syncs: paymentWorkflowSyncs,
|
|
12
|
+
initialize: async (_engine) => {
|
|
13
|
+
console.log('💳 Payments plugin initialized');
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// plugins/payments/syncs/payment-workflow.sync.ts
|
|
2
|
+
import { SyncRule } from '@legible-sync/core';
|
|
3
|
+
|
|
4
|
+
export const paymentWorkflowSyncs: SyncRule[] = [
|
|
5
|
+
// When order is confirmed, initiate payment
|
|
6
|
+
{
|
|
7
|
+
name: "InitiatePaymentOnOrderConfirm",
|
|
8
|
+
when: [
|
|
9
|
+
{
|
|
10
|
+
concept: "Order",
|
|
11
|
+
action: "confirm"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
then: [
|
|
15
|
+
{
|
|
16
|
+
concept: "Payment",
|
|
17
|
+
action: "initiate",
|
|
18
|
+
input: {
|
|
19
|
+
orderId: "?orderId",
|
|
20
|
+
amount: "?order.total",
|
|
21
|
+
method: "credit_card"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// When payment is initiated, automatically process it
|
|
28
|
+
{
|
|
29
|
+
name: "AutoProcessPayment",
|
|
30
|
+
when: [
|
|
31
|
+
{
|
|
32
|
+
concept: "Payment",
|
|
33
|
+
action: "initiate"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
then: [
|
|
37
|
+
{
|
|
38
|
+
concept: "Payment",
|
|
39
|
+
action: "process",
|
|
40
|
+
input: {
|
|
41
|
+
paymentId: "?paymentId"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// When payment processing starts, confirm it (simplified for demo)
|
|
48
|
+
{
|
|
49
|
+
name: "AutoConfirmPayment",
|
|
50
|
+
when: [
|
|
51
|
+
{
|
|
52
|
+
concept: "Payment",
|
|
53
|
+
action: "process"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
then: [
|
|
57
|
+
{
|
|
58
|
+
concept: "Payment",
|
|
59
|
+
action: "confirm",
|
|
60
|
+
input: {
|
|
61
|
+
paymentId: "?paymentId",
|
|
62
|
+
transactionId: "?transaction.id"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// When payment is confirmed, get order details first
|
|
69
|
+
{
|
|
70
|
+
name: "GetOrderDetailsAfterPaymentConfirm",
|
|
71
|
+
when: [
|
|
72
|
+
{
|
|
73
|
+
concept: "Payment",
|
|
74
|
+
action: "confirm"
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
then: [
|
|
78
|
+
{
|
|
79
|
+
concept: "Order",
|
|
80
|
+
action: "get",
|
|
81
|
+
input: {
|
|
82
|
+
orderId: "?payment.orderId"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// When order details are retrieved after payment confirmation, get user details
|
|
89
|
+
{
|
|
90
|
+
name: "GetUserDetailsAfterPaymentConfirm",
|
|
91
|
+
when: [
|
|
92
|
+
{
|
|
93
|
+
concept: "Order",
|
|
94
|
+
action: "get"
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
then: [
|
|
98
|
+
{
|
|
99
|
+
concept: "User",
|
|
100
|
+
action: "get",
|
|
101
|
+
input: {
|
|
102
|
+
userId: "?order.userId"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// When user details are retrieved after payment confirmation, send notification
|
|
109
|
+
{
|
|
110
|
+
name: "SendPaymentSuccessNotification",
|
|
111
|
+
when: [
|
|
112
|
+
{
|
|
113
|
+
concept: "User",
|
|
114
|
+
action: "get"
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
then: [
|
|
118
|
+
{
|
|
119
|
+
concept: "Notification",
|
|
120
|
+
action: "send",
|
|
121
|
+
input: {
|
|
122
|
+
type: "email",
|
|
123
|
+
to: "?user.email",
|
|
124
|
+
template: "payment-success",
|
|
125
|
+
data: {
|
|
126
|
+
orderId: "?payment.orderId",
|
|
127
|
+
amount: "?payment.amount",
|
|
128
|
+
paymentId: "?paymentId"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// When payment fails, get order details first
|
|
136
|
+
{
|
|
137
|
+
name: "GetOrderDetailsAfterPaymentFail",
|
|
138
|
+
when: [
|
|
139
|
+
{
|
|
140
|
+
concept: "Payment",
|
|
141
|
+
action: "fail"
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
then: [
|
|
145
|
+
{
|
|
146
|
+
concept: "Order",
|
|
147
|
+
action: "get",
|
|
148
|
+
input: {
|
|
149
|
+
orderId: "?payment.orderId"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
concept: "Order",
|
|
154
|
+
action: "cancel",
|
|
155
|
+
input: {
|
|
156
|
+
orderId: "?payment.orderId"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
// When order details are retrieved after payment failure, get user details
|
|
163
|
+
{
|
|
164
|
+
name: "GetUserDetailsAfterPaymentFail",
|
|
165
|
+
when: [
|
|
166
|
+
{
|
|
167
|
+
concept: "Order",
|
|
168
|
+
action: "get"
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
then: [
|
|
172
|
+
{
|
|
173
|
+
concept: "User",
|
|
174
|
+
action: "get",
|
|
175
|
+
input: {
|
|
176
|
+
userId: "?order.userId"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// When user details are retrieved after payment failure, send notification
|
|
183
|
+
{
|
|
184
|
+
name: "SendPaymentFailureNotification",
|
|
185
|
+
when: [
|
|
186
|
+
{
|
|
187
|
+
concept: "User",
|
|
188
|
+
action: "get"
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
then: [
|
|
192
|
+
{
|
|
193
|
+
concept: "Notification",
|
|
194
|
+
action: "send",
|
|
195
|
+
input: {
|
|
196
|
+
type: "email",
|
|
197
|
+
to: "?user.email",
|
|
198
|
+
template: "payment-failed",
|
|
199
|
+
data: {
|
|
200
|
+
orderId: "?payment.orderId",
|
|
201
|
+
reason: "?payment.failureReason"
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
// When refund is processed, get order details first
|
|
209
|
+
{
|
|
210
|
+
name: "GetOrderDetailsAfterRefund",
|
|
211
|
+
when: [
|
|
212
|
+
{
|
|
213
|
+
concept: "Payment",
|
|
214
|
+
action: "refund"
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
then: [
|
|
218
|
+
{
|
|
219
|
+
concept: "Order",
|
|
220
|
+
action: "get",
|
|
221
|
+
input: {
|
|
222
|
+
orderId: "?payment.orderId"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
// When order details are retrieved after refund, get user details
|
|
229
|
+
{
|
|
230
|
+
name: "GetUserDetailsAfterRefund",
|
|
231
|
+
when: [
|
|
232
|
+
{
|
|
233
|
+
concept: "Order",
|
|
234
|
+
action: "get"
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
then: [
|
|
238
|
+
{
|
|
239
|
+
concept: "User",
|
|
240
|
+
action: "get",
|
|
241
|
+
input: {
|
|
242
|
+
userId: "?order.userId"
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
// When user details are retrieved after refund, send notification
|
|
249
|
+
{
|
|
250
|
+
name: "SendRefundNotification",
|
|
251
|
+
when: [
|
|
252
|
+
{
|
|
253
|
+
concept: "User",
|
|
254
|
+
action: "get"
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
then: [
|
|
258
|
+
{
|
|
259
|
+
concept: "Notification",
|
|
260
|
+
action: "send",
|
|
261
|
+
input: {
|
|
262
|
+
type: "email",
|
|
263
|
+
to: "?user.email",
|
|
264
|
+
template: "payment-refund",
|
|
265
|
+
data: {
|
|
266
|
+
orderId: "?payment.orderId",
|
|
267
|
+
amount: "?transaction.amount",
|
|
268
|
+
reason: "?transaction.reason"
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
];
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// plugins/products/concepts/Product.ts
|
|
2
|
+
import { Concept } from '@legible-sync/core';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
|
|
5
|
+
export const Product: Concept = {
|
|
6
|
+
state: {
|
|
7
|
+
products: new Map<string, any>(),
|
|
8
|
+
skus: new Set<string>(),
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
async execute(action: string, input: any) {
|
|
12
|
+
const state = this.state;
|
|
13
|
+
|
|
14
|
+
switch (action) {
|
|
15
|
+
case 'create': {
|
|
16
|
+
const { name, sku, price, description, category } = input;
|
|
17
|
+
|
|
18
|
+
// Validation
|
|
19
|
+
if (!name || !sku || price === undefined) {
|
|
20
|
+
throw new Error('Name, SKU, and price are required');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (state.skus.has(sku)) {
|
|
24
|
+
throw new Error('SKU already exists');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Create product
|
|
28
|
+
const productId = uuidv4();
|
|
29
|
+
const product = {
|
|
30
|
+
id: productId,
|
|
31
|
+
name,
|
|
32
|
+
sku,
|
|
33
|
+
price: Number(price),
|
|
34
|
+
description: description || '',
|
|
35
|
+
category: category || 'general',
|
|
36
|
+
status: 'active',
|
|
37
|
+
createdAt: new Date()
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
state.products.set(productId, product);
|
|
41
|
+
state.skus.add(sku);
|
|
42
|
+
|
|
43
|
+
return { productId, product };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
case 'get': {
|
|
47
|
+
const { productId } = input;
|
|
48
|
+
const product = state.products.get(productId);
|
|
49
|
+
if (!product) {
|
|
50
|
+
throw new Error('Product not found');
|
|
51
|
+
}
|
|
52
|
+
return { product };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
case 'update': {
|
|
56
|
+
const { productId, updates } = input;
|
|
57
|
+
const product = state.products.get(productId);
|
|
58
|
+
if (!product) {
|
|
59
|
+
throw new Error('Product not found');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Update product
|
|
63
|
+
const updatedProduct = { ...product, ...updates, updatedAt: new Date() };
|
|
64
|
+
state.products.set(productId, updatedProduct);
|
|
65
|
+
|
|
66
|
+
return { product: updatedProduct };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
case 'deactivate': {
|
|
70
|
+
const { productId } = input;
|
|
71
|
+
const product = state.products.get(productId);
|
|
72
|
+
if (!product) {
|
|
73
|
+
throw new Error('Product not found');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
product.status = 'inactive';
|
|
77
|
+
product.deactivatedAt = new Date();
|
|
78
|
+
state.products.set(productId, product);
|
|
79
|
+
|
|
80
|
+
return { product };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
case 'list': {
|
|
84
|
+
const { category, status = 'active' } = input;
|
|
85
|
+
const products = Array.from(state.products.values())
|
|
86
|
+
.filter((p: any) => p.status === status)
|
|
87
|
+
.filter((p: any) => !category || p.category === category);
|
|
88
|
+
|
|
89
|
+
return { products };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case 'reset': {
|
|
93
|
+
state.products.clear();
|
|
94
|
+
state.skus.clear();
|
|
95
|
+
return { reset: true };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
default:
|
|
99
|
+
throw new Error(`Unknown action: ${action}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// plugins/products/index.ts
|
|
2
|
+
import { Plugin } from '../../core/PluginManager';
|
|
3
|
+
import { Product } from './concepts/Product';
|
|
4
|
+
import { productEventSyncs } from './syncs/product-events.sync';
|
|
5
|
+
|
|
6
|
+
export const productsPlugin: Plugin = {
|
|
7
|
+
name: 'products',
|
|
8
|
+
concepts: {
|
|
9
|
+
Product
|
|
10
|
+
},
|
|
11
|
+
syncs: productEventSyncs,
|
|
12
|
+
initialize: async (_engine) => {
|
|
13
|
+
console.log('📦 Products plugin initialized');
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// plugins/products/syncs/product-events.sync.ts
|
|
2
|
+
import { SyncRule } from '@legible-sync/core';
|
|
3
|
+
|
|
4
|
+
export const productEventSyncs: SyncRule[] = [
|
|
5
|
+
// When a product is created, publish product.created event
|
|
6
|
+
{
|
|
7
|
+
name: "PublishProductCreatedEvent",
|
|
8
|
+
when: [
|
|
9
|
+
{
|
|
10
|
+
concept: "Product",
|
|
11
|
+
action: "create"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
then: [
|
|
15
|
+
{
|
|
16
|
+
concept: "EventBus",
|
|
17
|
+
action: "publish",
|
|
18
|
+
input: {
|
|
19
|
+
event: "product.created",
|
|
20
|
+
data: {
|
|
21
|
+
productId: "?productId",
|
|
22
|
+
name: "?product.name",
|
|
23
|
+
sku: "?product.sku",
|
|
24
|
+
price: "?product.price",
|
|
25
|
+
category: "?product.category"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// When a product is updated, publish product.updated event
|
|
33
|
+
{
|
|
34
|
+
name: "PublishProductUpdatedEvent",
|
|
35
|
+
when: [
|
|
36
|
+
{
|
|
37
|
+
concept: "Product",
|
|
38
|
+
action: "update"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
then: [
|
|
42
|
+
{
|
|
43
|
+
concept: "EventBus",
|
|
44
|
+
action: "publish",
|
|
45
|
+
input: {
|
|
46
|
+
event: "product.updated",
|
|
47
|
+
data: {
|
|
48
|
+
productId: "?productId",
|
|
49
|
+
updates: "?updates"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// When a product is deactivated, publish product.deactivated event
|
|
57
|
+
{
|
|
58
|
+
name: "PublishProductDeactivatedEvent",
|
|
59
|
+
when: [
|
|
60
|
+
{
|
|
61
|
+
concept: "Product",
|
|
62
|
+
action: "deactivate"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
then: [
|
|
66
|
+
{
|
|
67
|
+
concept: "EventBus",
|
|
68
|
+
action: "publish",
|
|
69
|
+
input: {
|
|
70
|
+
event: "product.deactivated",
|
|
71
|
+
data: {
|
|
72
|
+
productId: "?productId"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
];
|