data-primals-engine 1.2.6-rc3 → 1.3.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.
Files changed (40) hide show
  1. package/README.md +38 -2
  2. package/client/package-lock.json +247 -4354
  3. package/client/package.json +16 -15
  4. package/client/src/App.jsx +13 -20
  5. package/client/src/App.scss +1 -0
  6. package/client/src/AssistantChat.jsx +5 -4
  7. package/client/src/DataEditor.jsx +2 -2
  8. package/client/src/DataTable.jsx +47 -3
  9. package/client/src/ExportDialog.jsx +2 -2
  10. package/client/src/Field.jsx +6 -18
  11. package/client/src/KanbanCard.jsx +4 -2
  12. package/client/src/KanbanConfigModal.jsx +5 -7
  13. package/client/src/ModelCreatorField.jsx +9 -9
  14. package/client/src/PackGallery.jsx +89 -9
  15. package/client/src/PackGallery.scss +58 -4
  16. package/client/src/RTETrans.jsx +11 -0
  17. package/client/src/RelationValue.jsx +3 -4
  18. package/client/src/constants.js +1 -1
  19. package/client/src/core/data.js +2 -1
  20. package/client/src/translations.js +80 -0
  21. package/package.json +8 -1
  22. package/server.js +4 -4
  23. package/src/constants.js +6 -0
  24. package/src/defaultModels.js +23 -10
  25. package/src/filter.js +35 -5
  26. package/src/i18n.js +1 -1
  27. package/src/modules/{assistant.js → assistant/assistant.js} +42 -27
  28. package/src/modules/assistant/constants.js +16 -0
  29. package/src/modules/data/data.core.js +1 -3
  30. package/src/modules/data/data.js +4601 -4525
  31. package/src/modules/data/data.routes.js +29 -3
  32. package/src/modules/mongodb.js +3 -1
  33. package/src/modules/user.js +12 -1
  34. package/src/modules/workflow.js +198 -117
  35. package/src/packs.js +1015 -9
  36. package/src/services/index.js +11 -0
  37. package/src/services/stripe.js +141 -0
  38. package/test/data.integration.test.js +66 -3
  39. package/test/workflow.actions.integration.test.js +474 -0
  40. package/test/workflow.integration.test.js +1 -1
package/src/packs.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {getModels} from "./modules/data/index.js";
2
+ import {providers} from "./modules/assistant/constants.js";
2
3
 
3
4
 
4
5
  /*
@@ -106,6 +107,7 @@ export const getAllPacks = async () => {
106
107
  "description": "A scalable workflow that sends campaign emails in chunks by dynamically querying contacts from an audience.",
107
108
  "startStep": { "$link": { "name": "Start Campaign Processing", "_model": "workflowStep" } }
108
109
  }],
110
+
109
111
  "workflowAction": [
110
112
  {
111
113
  "name": "Set Campaign to 'in_progress'",
@@ -1354,16 +1356,10 @@ return { processedChunk: context.result.chunk };
1354
1356
  "dataFilter": { "$or": [{"$eq": [{ "$type": "$seoDescription"}, "missing"]}, {"$eq": ["$seoDescription", ""]}]},
1355
1357
  "isActive": true
1356
1358
  }],
1357
- "env": [{
1358
- name: "OPENAI_API_KEY",
1359
- value: "demo"
1360
- },{
1361
- name: "GOOGLE_API_KEY",
1362
- value: "demo"
1363
- },{
1364
- name: "DEEPSEEK_API_KEY",
1359
+ "env": Object.values(providers).map(m => ({
1360
+ name: m.key,
1365
1361
  value: "demo"
1366
- }]
1362
+ }))
1367
1363
  }
1368
1364
  }
1369
1365
  },
@@ -4051,6 +4047,1016 @@ return { processedChunk: context.result.chunk };
4051
4047
  }
4052
4048
 
4053
4049
  }
4050
+ },
4051
+ {
4052
+ "name": "Stripe Integration Pro",
4053
+ "description": `
4054
+ This package is a comprehensive solution for integrating Stripe into your application. It not only manages payments, but also automates the entire billing and customer management ecosystem. In summary, this package allows you to:
4055
+
4056
+ ### 1. Automatically synchronize your Stripe data:
4057
+ Thanks to webhooks, all important information (customers, subscriptions, products, prices, invoices) is created and updated in real time in your local database. This gives you a reliable source of truth without manual effort.
4058
+
4059
+ ### 2. Manage the complete subscription lifecycle:
4060
+ • Creation: When a customer subscribes, the package creates the subscription locally and can send a welcome email.
4061
+ • Updates: Status changes (e.g., from trial to active) or cancellations are automatically reflected. • Payment failures: If a subscription payment fails, an email is automatically sent to the customer asking them to update their payment information.
4062
+
4063
+ ### 3. Automate payment and invoice processing:
4064
+ • Records every successful payment in your database. • Sends receipts by email after a payment. • Manages invoices (creation, payment, failure) and can send them to customers.
4065
+
4066
+ ### 4. Facilitate the payment process for your users:
4067
+ • Includes a workflow to create Stripe Checkout sessions, whether for a one-time payment or to start a new subscription.
4068
+
4069
+ ### 5. Provide an overview of your finances:
4070
+ • Offers a pre-configured dashboard with essential key performance indicators (KPIs):
4071
+ • Total revenue • Number of successful payments • Average payment value
4072
+ • Refund rate
4073
+ • Displays a graph of payment trends over time.
4074
+
4075
+ In short, this pack transforms your application into a robust and automated billing platform, while improving your customers' experience through clear and timely communications.
4076
+
4077
+ ### Webhook Configuration
4078
+
4079
+ Ajoutez ces événements supplémentaires pour une synchronisation complète :
4080
+
4081
+ * Customer Events:
4082
+ - customer.created
4083
+ - customer.updated
4084
+ - customer.deleted
4085
+
4086
+ * Product Events:
4087
+ - product.created
4088
+ - product.updated
4089
+ - product.deleted
4090
+ - price.created
4091
+ - price.updated
4092
+
4093
+ ### Webhook Configuration
4094
+
4095
+ To make the integration fully work, you need to configure a webhook in your Stripe Dashboard. This allows Stripe to send real-time notifications (like \`payment.succeeded\` or \`customer.subscription.created\`) to your application.
4096
+
4097
+ 1. **Get Your Webhook URL:**
4098
+ Your application's webhook URL is:
4099
+ https://<your-domain.com>/api/actions/stripe-webhook
4100
+ Replace <your-domain.com> with your actual public domain.
4101
+
4102
+ 2. **Add Endpoint in Stripe:**
4103
+ * Go to your Stripe Dashboard.
4104
+ * Navigate to **Developers > Webhooks**.
4105
+ * Click **+ Add an endpoint**.
4106
+ * Paste your URL in the **Endpoint URL** field.
4107
+
4108
+ 3. **Select Events:**
4109
+ Click on **+ Select events** and choose the following events to listen to:
4110
+ * invoice.paid
4111
+ * invoice.payment_failed
4112
+ * customer.subscription.created
4113
+ * customer.subscription.updated
4114
+ * customer.subscription.deleted
4115
+ * payment_intent.succeeded
4116
+
4117
+ 4. **Secure Your Webhook:**
4118
+ * After creating the endpoint, Stripe will show a **Signing secret**. Click to reveal it.
4119
+ * Copy this secret (it starts with \`whsec_...\`).
4120
+ * In your application's **\`env\` model**, find the variable named \`STRIPE_WEBHOOK_SECRET\` and paste the key there.
4121
+
4122
+ This ensures that your application only processes legitimate requests from Stripe.`,
4123
+ "tags": ["payment", "stripe", "e-commerce", "subscription", "billing"],
4124
+ "models": [
4125
+ "endpoint",
4126
+ "dashboard",
4127
+ "kpi",
4128
+ "workflow",
4129
+ "workflowStep",
4130
+ "workflowAction",
4131
+ "workflowTrigger",
4132
+ "env",
4133
+ "currency",
4134
+ {
4135
+ "name": "StripeCustomer",
4136
+ "description": "Maps local users to Stripe customers with billing details.",
4137
+ "fields": [
4138
+ { "name": "user", "type": "relation", "relation": "user", "required": true, "asMain": true },
4139
+ { "name": "stripeCustomerId", "type": "string", "required": true, "unique": true, "hiddenable": true },
4140
+ { "name": "email", "type": "string", "required": true },
4141
+ { "name": "name", "type": "string" },
4142
+ { "name": "phone", "type": "string" },
4143
+ { "name": "address", "type": "code","language": "json"}/* "fields": [
4144
+ { "name": "line1", "type": "string" },
4145
+ { "name": "line2", "type": "string" },
4146
+ { "name": "city", "type": "string" },
4147
+ { "name": "state", "type": "string" },
4148
+ { "name": "postal_code", "type": "string" },
4149
+ { "name": "country", "type": "string" }
4150
+ ]}*/,
4151
+ { "name": "taxExempt", "type": "enum", "items": ["none", "exempt", "reverse"], "default": "none" },
4152
+ { "name": "defaultPaymentMethod", "type": "string" },
4153
+ { "name": "invoiceSettings", "type": "code", "language": "json"}/* "fields": [
4154
+ { "name": "customFields", "type": "array", "itemsType": "object" },
4155
+ { "name": "footer", "type": "string" }
4156
+ ]}*/,
4157
+ { "name": "metadata", "type": "code", "language": "json" }
4158
+ ]
4159
+ },
4160
+ {
4161
+ "name": "StripeSubscription",
4162
+ "description": "Tracks all subscription details with Stripe.",
4163
+ "fields": [
4164
+ { "name": "stripeSubscriptionId", "type": "string", "required": true, "unique": true, "asMain": true },
4165
+ { "name": "user", "type": "relation", "relation": "user", "required": true },
4166
+ { "name": "customer", "type": "relation", "relation": "StripeCustomer", "required": true },
4167
+ { "name": "plan", "type": "relation", "relation": "StripePlan", "required": true },
4168
+ { "name": "status", "type": "enum", "items": ["trialing", "active", "past_due", "canceled", "unpaid", "incomplete", "incomplete_expired"], "required": true },
4169
+ { "name": "currentPeriodStart", "type": "datetime" },
4170
+ { "name": "currentPeriodEnd", "type": "datetime" },
4171
+ { "name": "cancelAtPeriodEnd", "type": "boolean", "default": false },
4172
+ { "name": "canceledAt", "type": "datetime" },
4173
+ { "name": "daysUntilDue", "type": "number" },
4174
+ { "name": "defaultPaymentMethod", "type": "string" },
4175
+ { "name": "latestInvoice", "type": "relation", "relation": "StripeInvoice" },
4176
+ { "name": "startDate", "type": "datetime" },
4177
+ { "name": "trialEnd", "type": "datetime" },
4178
+ { "name": "metadata", "type": "code", "language": "json" }
4179
+ ]
4180
+ },
4181
+ {
4182
+ "name": "StripePlan",
4183
+ "description": "Subscription plans with detailed pricing information.",
4184
+ "fields": [
4185
+ { "name": "name", "type": "string", "required": true, "asMain": true },
4186
+ { "name": "description", "type": "richtext" },
4187
+ { "name": "stripeProductId", "type": "string", "required": true, "unique": true },
4188
+ { "name": "stripePriceId", "type": "string", "required": true, "unique": true },
4189
+ { "name": "price", "type": "number", "required": true },
4190
+ { "name": "currency", "type": "relation", "relation": "currency", "required": true },
4191
+ { "name": "interval", "type": "enum", "items": ["day", "week", "month", "year"], "required": true },
4192
+ { "name": "intervalCount", "type": "number", "default": 1 },
4193
+ { "name": "trialPeriodDays", "type": "number" },
4194
+ { "name": "active", "type": "boolean", "default": true },
4195
+ { "name": "metadata", "type": "code", "language": "json" },
4196
+ { "name": "features", "type": "array", "itemsType": "string" }
4197
+ ]
4198
+ },
4199
+ {
4200
+ "name": "StripePayment",
4201
+ "description": "Detailed payment records with reconciliation data.",
4202
+ "fields": [
4203
+ { "name": "stripePaymentIntentId", "type": "string", "required": true, "unique": true, "asMain": true, "hiddenable": true },
4204
+ { "name": "user", "type": "relation", "relation": "user", "required": true },
4205
+ { "name": "customer", "type": "relation", "relation": "StripeCustomer" },
4206
+ { "name": "subscription", "type": "relation", "relation": "StripeSubscription" },
4207
+ { "name": "invoice", "type": "relation", "relation": "StripeInvoice" },
4208
+ { "name": "amount", "type": "number", "required": true },
4209
+ { "name": "amountReceived", "type": "number" },
4210
+ { "name": "currency", "type": "relation", "relation": "currency", "required": true },
4211
+ { "name": "status", "type": "enum", "items": ["requires_payment_method", "requires_confirmation", "requires_action", "processing", "requires_capture", "canceled", "succeeded"], "required": true },
4212
+ { "name": "paymentMethod", "type": "string" },
4213
+ { "name": "paymentMethodDetails", "type": "code", "language": "json", "anonymized": true },
4214
+ { "name": "receiptEmail", "type": "string" },
4215
+ { "name": "receiptUrl", "type": "string" },
4216
+ { "name": "created", "type": "datetime", "default": "now" },
4217
+ { "name": "metadata", "type": "code", "language": "json" }
4218
+ ]
4219
+ },
4220
+ {
4221
+ "name": "StripeInvoice",
4222
+ "description": "Complete invoice records from Stripe.",
4223
+ "fields": [
4224
+ { "name": "stripeInvoiceId", "type": "string", "required": true, "unique": true, "asMain": true },
4225
+ { "name": "customer", "type": "relation", "relation": "StripeCustomer", "required": true },
4226
+ { "name": "subscription", "type": "relation", "relation": "StripeSubscription" },
4227
+ { "name": "number", "type": "string" },
4228
+ { "name": "amountDue", "type": "number", "required": true },
4229
+ { "name": "amountPaid", "type": "number" },
4230
+ { "name": "amountRemaining", "type": "number" },
4231
+ { "name": "currency", "type": "relation", "relation": "currency", "required": true },
4232
+ { "name": "status", "type": "enum", "items": ["draft", "open", "paid", "uncollectible", "void"], "required": true },
4233
+ { "name": "periodStart", "type": "datetime" },
4234
+ { "name": "periodEnd", "type": "datetime" },
4235
+ { "name": "dueDate", "type": "datetime" },
4236
+ { "name": "pdfUrl", "type": "string" },
4237
+ { "name": "hostedInvoiceUrl", "type": "string" },
4238
+ { "name": "lines", "type": "code", "language": "json" },
4239
+ { "name": "created", "type": "datetime", "default": "now" },
4240
+ { "name": "metadata", "type": "code", "language": "json" }
4241
+ ]
4242
+ },
4243
+ {
4244
+ "name": "StripeRefund",
4245
+ "description": "Records of refunds processed through Stripe.",
4246
+ "fields": [
4247
+ { "name": "stripeRefundId", "type": "string", "required": true, "unique": true, "asMain": true },
4248
+ { "name": "payment", "type": "relation", "relation": "StripePayment", "required": true },
4249
+ { "name": "amount", "type": "number", "required": true },
4250
+ { "name": "currency", "type": "relation", "relation": "currency", "required": true },
4251
+ { "name": "reason", "type": "enum", "items": ["duplicate", "fraudulent", "requested_by_customer", "expired_uncaptured_charge"] },
4252
+ { "name": "status", "type": "enum", "items": ["pending", "succeeded", "failed", "canceled"], "required": true },
4253
+ { "name": "receiptNumber", "type": "string" },
4254
+ { "name": "created", "type": "datetime", "default": "now" }
4255
+ ]
4256
+ }
4257
+ ],
4258
+ "data": {
4259
+ "all": {
4260
+ "env": [
4261
+ { "name": "STRIPE_SECRET_KEY", "value": "sk_test_YOUR_SECRET_KEY" },
4262
+ { "name": "STRIPE_WEBHOOK_SECRET", "value": "whsec_YOUR_WEBHOOK_SECRET" }
4263
+ ],
4264
+ "endpoint": [
4265
+ {
4266
+ "name": "Stripe Webhook Handler",
4267
+ "path": "stripe-webhook",
4268
+ "method": "POST",
4269
+ "isActive": true,
4270
+ "isPublic": true,
4271
+ "code": `
4272
+ // The entire Stripe event object is passed in the request body.
4273
+ const stripeEvent = context.request.body;
4274
+
4275
+ // We pass this event to the workflow for processing.
4276
+ await workflow.run('Process Stripe Webhook Events', { event: stripeEvent });
4277
+
4278
+ return { received: true };`
4279
+ },
4280
+ {
4281
+ "name": "Create Customer Portal",
4282
+ "path": "stripe/create-portal-session",
4283
+ "method": "POST",
4284
+ "isActive": true,
4285
+ "code": `
4286
+ // Expects the local user ID in the request body to find the Stripe Customer
4287
+ const { userId } = context.body;
4288
+ if (!userId) {
4289
+ return { status: 400, body: { error: 'User ID is required.' } };
4290
+ }
4291
+
4292
+ // Find the corresponding Stripe Customer
4293
+ const stripeCustomer = await db.findOne('StripeCustomer', { user: userId });
4294
+ if (!stripeCustomer) {
4295
+ return { status: 404, body: { error: 'Stripe customer not found for this user.' } };
4296
+ }
4297
+
4298
+ // Run the workflow to get the portal URL
4299
+ const result = await workflow.run('Create Stripe Customer Portal Session', {
4300
+ stripeCustomerId: stripeCustomer.stripeCustomerId,
4301
+ returnUrl: 'https://votre-site.com/account/billing' // URL where user returns
4302
+ });
4303
+
4304
+ // The result of the workflow run will contain the portal session URL
4305
+ // (This part requires a way to retrieve the result of the workflow action)
4306
+ // For now, we can assume the workflow returns the URL in its context
4307
+ // A more advanced implementation would be needed here.
4308
+
4309
+ return { status: 200, body: { message: "Workflow started", runId: result.runId } };
4310
+ `
4311
+ }
4312
+ ],
4313
+ "workflow": [
4314
+ {
4315
+ "name": "Process Stripe Webhook Events",
4316
+ "description": "Processes incoming Stripe webhook events and triggers appropriate actions.",
4317
+ "startStep": { "$link": { "name": "Check for Invoice Paid", "_model": "workflowStep" } }
4318
+ },
4319
+ {
4320
+ "name": "Subscription Lifecycle Management",
4321
+ "description": "Handles subscription creation, updates, and cancellations.",
4322
+ "startStep": { "$link": { "name": "Handle Subscription Created", "_model": "workflowStep" } }
4323
+ },
4324
+ {
4325
+ "name": "Payment Reconciliation",
4326
+ "description": "Ensures payments are properly recorded and reconciled with orders.",
4327
+ "startStep": { "$link": { "name": "Handle Payment Succeeded", "_model": "workflowStep" } }
4328
+ },
4329
+ {
4330
+ "name": "Invoice Processing",
4331
+ "description": "Handles invoice generation, payment, and reminders.",
4332
+ "startStep": { "$link": { "name": "Handle Invoice Created", "_model": "workflowStep" } }
4333
+ },
4334
+ {
4335
+ "name": "Create Stripe Checkout Session",
4336
+ "description": "Creates a Stripe Checkout session for a one-time payment or a subscription.",
4337
+ "startStep": { "$link": { "name": "Select Session Type", "_model": "workflowStep" } }
4338
+ },
4339
+ {
4340
+ "name": "Create Stripe Customer Portal Session",
4341
+ "description": "Generates a secure URL for a customer to access their Stripe portal.",
4342
+ "startStep": { "$link": { "name": "Generate Portal URL", "_model": "workflowStep" } }
4343
+ }
4344
+ ],
4345
+ "workflowAction": [
4346
+ // Ajout dans la section workflowAction
4347
+ {
4348
+ "name": "Send Payment Failure Email",
4349
+ "description": "Envoie un email au client quand un paiement échoue avec instructions pour mettre à jour sa méthode de paiement.",
4350
+ "type": "SendEmail",
4351
+ "emailRecipients": ["{triggerData.customer.email}"],
4352
+ "emailSubject": "Payment Failed for Your Subscription",
4353
+ "emailContent": `
4354
+ <h1>Payment Failed</h1>
4355
+ <p>We couldn't process your payment for invoice #{triggerData.number}.</p>
4356
+ <p>Amount due: {triggerData.amountDue} {triggerData.currency.symbol}</p>
4357
+ <p><strong>Please update your payment method:</strong></p>
4358
+ <a href="{triggerData.hostedInvoiceUrl}" style="background-color: #E53E3E; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">
4359
+ Update Payment Method
4360
+ </a>
4361
+ <p>If you believe this is an error, please contact our support team.</p>
4362
+ `
4363
+ },
4364
+ {
4365
+ "name": "Send Subscription Welcome",
4366
+ "description": "Envoie un email de bienvenue pour un nouvel abonnement.",
4367
+ "type": "SendEmail",
4368
+ "emailRecipients": ["{triggerData.customer_email}"],
4369
+ "emailSubject": "Welcome to your new subscription!",
4370
+ "emailContent": `
4371
+ <h1>Thank you for subscribing!</h1>
4372
+ <p>We're excited to have you on board. Here are the details of your subscription:</p>
4373
+ <ul>
4374
+ <li>Plan: {context.plan.name}</li>
4375
+ <li>Amount: {context.plan.price} {context.plan.currency.code}/month</li>
4376
+ <li>Next billing date: {context.subscription.currentPeriodEnd}</li>
4377
+ </ul>
4378
+ <p>If you have any questions, please don't hesitate to contact our support team.</p>
4379
+ `
4380
+ },
4381
+ {
4382
+ "name": "Process Invoice Payment",
4383
+ "description": "Traite le paiement d'une facture et met à jour le statut.",
4384
+ "type": "ExecuteScript",
4385
+ "script": `
4386
+ const invoice = context.triggerData.data.object;
4387
+
4388
+ // Trouver l'abonnement associé
4389
+ const subscription = await db.findOne('StripeSubscription', {
4390
+ stripeSubscriptionId: invoice.subscription
4391
+ });
4392
+
4393
+ if (!subscription) {
4394
+ logger.warn('Subscription not found for invoice:', invoice.id);
4395
+ return { success: false, message: 'Subscription not found' };
4396
+ }
4397
+
4398
+ // Créer/mettre à jour l'enregistrement de facture
4399
+ const invoiceData = {
4400
+ stripeInvoiceId: invoice.id,
4401
+ customer: subscription.customer,
4402
+ subscription: subscription._id,
4403
+ number: invoice.number,
4404
+ amountDue: invoice.amount_due / 100,
4405
+ amountPaid: invoice.amount_paid / 100,
4406
+ amountRemaining: invoice.amount_remaining / 100,
4407
+ currency: subscription.plan.currency,
4408
+ status: invoice.status,
4409
+ periodStart: new Date(invoice.period_start * 1000),
4410
+ periodEnd: new Date(invoice.period_end * 1000),
4411
+ dueDate: invoice.due_date ? new Date(invoice.due_date * 1000) : null,
4412
+ pdfUrl: invoice.invoice_pdf,
4413
+ hostedInvoiceUrl: invoice.hosted_invoice_url,
4414
+ lines: JSON.stringify(invoice.lines.data),
4415
+ created: new Date(invoice.created * 1000),
4416
+ metadata: invoice.metadata ? JSON.stringify(invoice.metadata) : null
4417
+ };
4418
+
4419
+ await db.upsert('StripeInvoice',
4420
+ { stripeInvoiceId: invoice.id },
4421
+ invoiceData
4422
+ );
4423
+
4424
+ // Si la facture est payée, mettre à jour le statut de l'abonnement
4425
+ if (invoice.status === 'paid') {
4426
+ await db.update('StripeSubscription',
4427
+ { _id: subscription._id },
4428
+ {
4429
+ status: 'active',
4430
+ latestInvoice: invoice.id
4431
+ }
4432
+ );
4433
+ }
4434
+
4435
+ return { success: true };
4436
+ `
4437
+ },
4438
+ {
4439
+ "name": "Send Invoice Email",
4440
+ "description": "Envoie la facture par email au client.",
4441
+ "type": "SendEmail",
4442
+ "emailRecipients": ["{triggerData.customer_email}"],
4443
+ "emailSubject": "Your invoice is ready",
4444
+ "emailContent": `
4445
+ <h1>Your Invoice #{triggerData.number}</h1>
4446
+ <p>Amount due: {triggerData.amount_due / 100} {triggerData.currency.toUpperCase()}</p>
4447
+ <p>Due date: {triggerData.due_date ? new Date(triggerData.due_date * 1000).toLocaleDateString() : 'Immediately'}</p>
4448
+ <p>You can view and pay your invoice <a href="{triggerData.hosted_invoice_url}">here</a>.</p>
4449
+ <p>Thank you for your business!</p>
4450
+ `
4451
+ },
4452
+ {
4453
+ "name": "Stripe: Create Customer",
4454
+ "description": "Creates a new customer in Stripe. Expects 'email' and 'name' in the triggerData.",
4455
+ "type": "HttpRequest",
4456
+ "method": "POST",
4457
+ "url": "https://api.stripe.com/v1/customers",
4458
+ "headers": { "Authorization": "Bearer {env.STRIPE_SECRET_KEY}", "Content-Type": "application/x-www-form-urlencoded" },
4459
+ "body": "email={triggerData.email}&name={triggerData.name}&description=Customer for {triggerData.email}&metadata[userId]={triggerData.user._id}"
4460
+ },
4461
+ {
4462
+ "name": "Stripe: Retrieve Payment Intent",
4463
+ "description": "Retrieves a Payment Intent object from Stripe.",
4464
+ "type": "HttpRequest",
4465
+ "method": "GET",
4466
+ "url": "https://api.stripe.com/v1/payment_intents/{triggerData.event.id}",
4467
+ "headers": { "Authorization": "Bearer {env.STRIPE_SECRET_KEY}" }
4468
+ },
4469
+ {
4470
+ "name": "Stripe Service: Verify Webhook",
4471
+ "description": "Uses the native Stripe service to securely verify a webhook signature.",
4472
+ "type": "ExecuteServiceFunction",
4473
+ "serviceName": "stripe",
4474
+ "functionName": "verifyWebhookSignature",
4475
+ "args": ["{triggerData.request.headers}", "{triggerData.request.rawBody}"]
4476
+ },
4477
+ {
4478
+ "name": "Stripe: Create Checkout Session (Payment)",
4479
+ "description": "Creates a Stripe Checkout session for a one-time payment.",
4480
+ "type": "HttpRequest",
4481
+ "method": "POST",
4482
+ "url": "https://api.stripe.com/v1/checkout/sessions",
4483
+ "headers": { "Authorization": "Bearer {env.STRIPE_SECRET_KEY}", "Content-Type": "application/json" },
4484
+ "body": {
4485
+ "payment_method_types": ["card"],
4486
+ "line_items": [{
4487
+ "price_data": {
4488
+ "currency": "{triggerData.currencyCode}",
4489
+ "product_data": { "name": "{triggerData.productName}" },
4490
+ "unit_amount": "{triggerData.amountInCents}"
4491
+ },
4492
+ "quantity": "{triggerData.quantity}"
4493
+ }],
4494
+ "mode": "payment",
4495
+ "success_url": "{triggerData.successUrl}",
4496
+ "cancel_url": "{triggerData.cancelUrl}",
4497
+ "customer_email": "{triggerData.customerEmail}",
4498
+ "metadata": "{triggerData.metadata}"
4499
+ }
4500
+ },
4501
+ {
4502
+ "name": "Stripe: Create Checkout Session (Subscription)",
4503
+ "description": "Creates a Stripe Checkout session for a new subscription.",
4504
+ "type": "HttpRequest",
4505
+ "method": "POST",
4506
+ "url": "https://api.stripe.com/v1/checkout/sessions",
4507
+ "headers": { "Authorization": "Bearer {env.STRIPE_SECRET_KEY}", "Content-Type": "application/json" },
4508
+ "body": {
4509
+ "payment_method_types": ["card"],
4510
+ "line_items": [{ "price": "{triggerData.priceId}", "quantity": 1 }],
4511
+ "mode": "subscription",
4512
+ "success_url": "{triggerData.successUrl}",
4513
+ "cancel_url": "{triggerData.cancelUrl}",
4514
+ "customer": "{triggerData.stripeCustomerId}"
4515
+ }
4516
+ },
4517
+ {
4518
+ "name": "Send Payment Receipt",
4519
+ "description": "Sends a receipt email for successful payments.",
4520
+ "type": "SendEmail",
4521
+ "emailRecipients": ["{context.httpResponse.receipt_email}"],
4522
+ "emailSubject": "Your Payment Receipt",
4523
+ "emailContent": `
4524
+ <h1>Thank you for your payment!</h1>
4525
+ <p>We've received your payment of {context.httpResponse.amount / 100} {context.httpResponse.currency.toUpperCase()}.</p>
4526
+ <p>Payment ID: {triggerData.payload.id}</p>
4527
+ <p>You can view your receipt <a href="{triggerData.payload.receipt_url}">here</a>.</p>
4528
+ <p>If you have any questions, please contact our support team.</p>
4529
+ `
4530
+ },
4531
+ {
4532
+ "name": "Process Refund",
4533
+ "description": "Handles refund creation and updates order status.",
4534
+ "type": "ExecuteScript",
4535
+ "script": `
4536
+ const { refund } = context.triggerData.payload;
4537
+
4538
+ // Find the original payment
4539
+ const payment = await db.findOne('StripePayment', {
4540
+ stripePaymentIntentId: refund.payment_intent
4541
+ });
4542
+
4543
+ if (!payment) {
4544
+ logger.warn('Original payment not found for refund:', refund.id);
4545
+ return { success: false, message: 'Original payment not found' };
4546
+ }
4547
+
4548
+ // Create refund record
4549
+ await db.create('StripeRefund', {
4550
+ stripeRefundId: refund.id,
4551
+ payment: payment._id,
4552
+ amount: refund.amount / 100,
4553
+ currency: payment.currency,
4554
+ reason: refund.reason,
4555
+ status: refund.status,
4556
+ receiptNumber: refund.receipt_number,
4557
+ created: new Date(refund.created * 1000)
4558
+ });
4559
+
4560
+ // Update order status if linked
4561
+ if (payment.order) {
4562
+ await db.update('order',
4563
+ { _id: payment.order },
4564
+ { status: 'refunded', refundId: refund.id }
4565
+ );
4566
+ }
4567
+
4568
+ return { success: true };
4569
+ `
4570
+ },
4571
+ {
4572
+ "name": "Log Unhandled Stripe Event",
4573
+ "description": "Logs the type and ID of a Stripe event that was not handled by any other workflow step.",
4574
+ "type": "ExecuteScript",
4575
+ "script": `
4576
+ logger.warn('Unhandled Stripe Event Received: Type=' + context.triggerData.event.type + ', ID=' + context.triggerData.event.id);
4577
+ return { success: true, message: 'Event logged as unhandled.' };
4578
+ `
4579
+ },
4580
+ {
4581
+ "name": "Stripe: Create Customer Portal Session",
4582
+ "description": "Creates a session for the Stripe Customer Portal.",
4583
+ "type": "HttpRequest",
4584
+ "method": "POST",
4585
+ "url": "https://api.stripe.com/v1/billing_portal/sessions",
4586
+ "headers": {
4587
+ "Authorization": "Bearer {env.STRIPE_SECRET_KEY}",
4588
+ "Content-Type": "application/x-www-form-urlencoded"
4589
+ },
4590
+ "body": "customer={triggerData.stripeCustomerId}&return_url={triggerData.returnUrl}"
4591
+ },
4592
+ {
4593
+ "name": "Sync Stripe Entity to Local DB",
4594
+ "description": "Synchronise une entité Stripe avec la base locale sans utiliser upsert",
4595
+ "type": "ExecuteScript",
4596
+ "script": `
4597
+ const event = context.triggerData;
4598
+ const stripeObject = event.data.object;
4599
+ const objectType = stripeObject.object;
4600
+
4601
+ // Helper function to safely get nested properties
4602
+ const getNested = (obj, path) => {
4603
+ return path.split('.').reduce((o, p) => (o && o[p] !== undefined ? o[p] : null), obj);
4604
+ };
4605
+
4606
+ // Helper function to safely stringify objects
4607
+ const safeStringify = (obj) => {
4608
+ try {
4609
+ return obj ? JSON.stringify(obj) : null;
4610
+ } catch (e) {
4611
+ logger.warn('Failed to stringify object:', e);
4612
+ return null;
4613
+ }
4614
+ };
4615
+
4616
+ const findCurrencyByCode = async (code) => {
4617
+ if (!code) return null;
4618
+ const currency = await db.findOne('currency', { code: code.toUpperCase() });
4619
+ return currency ? currency._id : null;
4620
+ };
4621
+
4622
+ const findCustomerByStripeId = async (stripeId) => {
4623
+ if (!stripeId) return null;
4624
+ const customer = await db.findOne('StripeCustomer', { stripeCustomerId: stripeId });
4625
+ return customer || null;
4626
+ };
4627
+
4628
+ const findSubscriptionByStripeId = async (stripeId) => {
4629
+ if (!stripeId) return null;
4630
+ const subscription = await db.findOne('StripeSubscription', { stripeSubscriptionId: stripeId });
4631
+ return subscription || null;
4632
+ };
4633
+
4634
+ const findInvoiceByStripeId = async (stripeId) => {
4635
+ if (!stripeId) return null;
4636
+ const invoice = await db.findOne('StripeInvoice', { stripeInvoiceId: stripeId });
4637
+ return invoice || null;
4638
+ };
4639
+
4640
+ const findUserFromCustomer = (customer) => {
4641
+ return customer ? customer.user : null;
4642
+ };
4643
+
4644
+ const findOrCreateCustomer = async (stripeCustomerId, email, name) => {
4645
+ let customer = await findCustomerByStripeId(stripeCustomerId);
4646
+ if (!customer) {
4647
+ logger.info(\`Customer not found locally, creating a new one for Stripe ID: \${stripeCustomerId}\`);
4648
+ const newCustomerData = {
4649
+ stripeCustomerId: stripeCustomerId,
4650
+ email: email,
4651
+ name: name
4652
+ };
4653
+ // Try to link to an existing user by email
4654
+ const user = await db.findOne('user', { email: email });
4655
+ if (user) {
4656
+ newCustomerData.user = user._id;
4657
+ }
4658
+ const result = await db.create('StripeCustomer', newCustomerData);
4659
+ customer = { _id: result.insertedIds[0], ...newCustomerData };
4660
+ }
4661
+ return customer;
4662
+ };
4663
+
4664
+
4665
+ let modelName;
4666
+ let idField;
4667
+ let dataToUpsert = {};
4668
+
4669
+ switch (objectType) {
4670
+ case 'customer':
4671
+ modelName = 'StripeCustomer';
4672
+ idField = 'stripeCustomerId';
4673
+ dataToUpsert = {
4674
+ stripeCustomerId: stripeObject.id,
4675
+ email: stripeObject.email || null,
4676
+ name: stripeObject.name || null,
4677
+ phone: stripeObject.phone || null,
4678
+ address: stripeObject.address ? safeStringify(stripeObject.address) : null,
4679
+ taxExempt: stripeObject.tax_exempt || 'none',
4680
+ defaultPaymentMethod: getNested(stripeObject, 'invoice_settings.default_payment_method'),
4681
+ invoiceSettings: stripeObject.invoice_settings ? safeStringify(stripeObject.invoice_settings) : null,
4682
+ metadata: stripeObject.metadata ? safeStringify(stripeObject.metadata) : null
4683
+ };
4684
+ break;
4685
+
4686
+ case 'subscription':
4687
+ modelName = 'StripeSubscription';
4688
+ idField = 'stripeSubscriptionId';
4689
+ dataToUpsert = {
4690
+ stripeSubscriptionId: stripeObject.id,
4691
+ status: stripeObject.status,
4692
+ currentPeriodStart: stripeObject.current_period_start ? new Date(stripeObject.current_period_start * 1000) : null,
4693
+ currentPeriodEnd: stripeObject.current_period_end ? new Date(stripeObject.current_period_end * 1000) : null,
4694
+ cancelAtPeriodEnd: stripeObject.cancel_at_period_end || false,
4695
+ canceledAt: stripeObject.canceled_at ? new Date(stripeObject.canceled_at * 1000) : null,
4696
+ daysUntilDue: stripeObject.days_until_due || null,
4697
+ defaultPaymentMethod: stripeObject.default_payment_method || null,
4698
+ startDate: stripeObject.start_date ? new Date(stripeObject.start_date * 1000) : null,
4699
+ trialEnd: stripeObject.trial_end ? new Date(stripeObject.trial_end * 1000) : null,
4700
+ metadata: stripeObject.metadata ? safeStringify(stripeObject.metadata) : null
4701
+ };
4702
+
4703
+ // Handle relations
4704
+ if (stripeObject.customer) {
4705
+ const customer = await db.findOne('StripeCustomer', { stripeCustomerId: stripeObject.customer });
4706
+ if (customer) dataToUpsert.customer = customer._id;
4707
+ }
4708
+
4709
+ if (stripeObject.items?.data?.[0]?.price?.id) {
4710
+ const priceId = stripeObject.items.data[0].price.id;
4711
+ const plan = await db.findOne('StripePlan', { stripePriceId: priceId });
4712
+ if (plan) dataToUpsert.plan = plan._id;
4713
+ }
4714
+ break;
4715
+
4716
+ case 'product':
4717
+ modelName = 'StripePlan';
4718
+ idField = 'stripeProductId';
4719
+ dataToUpsert = {
4720
+ stripeProductId: stripeObject.id,
4721
+ name: stripeObject.name || null,
4722
+ description: stripeObject.description || null,
4723
+ active: stripeObject.active !== false,
4724
+ metadata: stripeObject.metadata ? safeStringify(stripeObject.metadata) : null
4725
+ };
4726
+ break;
4727
+
4728
+ case 'price':
4729
+ modelName = 'StripePlan';
4730
+ idField = 'stripePriceId';
4731
+ dataToUpsert = {
4732
+ stripePriceId: stripeObject.id,
4733
+ price: stripeObject.unit_amount ? stripeObject.unit_amount / 100 : null,
4734
+ interval: stripeObject.recurring?.interval || null,
4735
+ intervalCount: stripeObject.recurring?.interval_count || 1
4736
+ };
4737
+
4738
+ if (stripeObject.metadata?.trial_period_days) {
4739
+ dataToUpsert.trialPeriodDays = parseInt(stripeObject.metadata.trial_period_days) || null;
4740
+ }
4741
+
4742
+ if (stripeObject.currency) {
4743
+ const currency = await db.findOne('currency', { code: stripeObject.currency.toUpperCase() });
4744
+ if (currency) dataToUpsert.currency = currency._id;
4745
+ }
4746
+ break;
4747
+
4748
+ case 'invoice':
4749
+ modelName = 'StripeInvoice';
4750
+ idField = 'stripeInvoiceId';
4751
+
4752
+ const customerForInvoice = await findCustomerByStripeId(stripeObject.customer);
4753
+ const subscriptionForInvoice = await findSubscriptionByStripeId(stripeObject.subscription);
4754
+
4755
+ dataToUpsert = {
4756
+ stripeInvoiceId: stripeObject.id,
4757
+ customer: customerForInvoice ? customerForInvoice._id : null,
4758
+ subscription: subscriptionForInvoice ? subscriptionForInvoice._id : null,
4759
+ number: stripeObject.number,
4760
+ amountDue: stripeObject.amount_due / 100,
4761
+ amountPaid: stripeObject.amount_paid / 100,
4762
+ amountRemaining: stripeObject.amount_remaining / 100,
4763
+ currency: await findCurrencyByCode(stripeObject.currency),
4764
+ status: stripeObject.status,
4765
+ periodStart: new Date(stripeObject.period_start * 1000),
4766
+ periodEnd: new Date(stripeObject.period_end * 1000),
4767
+ dueDate: stripeObject.due_date ? new Date(stripeObject.due_date * 1000) : null,
4768
+ pdfUrl: stripeObject.invoice_pdf,
4769
+ hostedInvoiceUrl: stripeObject.hosted_invoice_url,
4770
+ lines: safeStringify(stripeObject.lines?.data),
4771
+ created: new Date(stripeObject.created * 1000),
4772
+ metadata: safeStringify(stripeObject.metadata)
4773
+ };
4774
+ break;
4775
+
4776
+ case 'payment_intent':
4777
+ modelName = 'StripePayment';
4778
+ idField = 'stripePaymentIntentId';
4779
+
4780
+ const customerForPayment = await findCustomerByStripeId(stripeObject.customer);
4781
+ const userForPayment = findUserFromCustomer(customerForPayment);
4782
+ const invoiceForPayment = await findInvoiceByStripeId(stripeObject.invoice);
4783
+ const subscriptionForPayment = invoiceForPayment ? await findSubscriptionByStripeId(invoiceForPayment.subscription) : null;
4784
+
4785
+ dataToUpsert = {
4786
+ stripePaymentIntentId: stripeObject.id,
4787
+ user: userForPayment,
4788
+ customer: customerForPayment ? customerForPayment._id : null,
4789
+ subscription: subscriptionForPayment ? subscriptionForPayment._id : null,
4790
+ invoice: invoiceForPayment ? invoiceForPayment._id : null,
4791
+ amount: stripeObject.amount / 100,
4792
+ amountReceived: stripeObject.amount_received / 100,
4793
+ currency: await findCurrencyByCode(stripeObject.currency),
4794
+ status: stripeObject.status,
4795
+ paymentMethod: stripeObject.payment_method,
4796
+ paymentMethodDetails: safeStringify(stripeObject.payment_method_details),
4797
+ receiptEmail: stripeObject.receipt_email,
4798
+ receiptUrl: getNested(stripeObject, 'charges.data.0.receipt_url'),
4799
+ created: new Date(stripeObject.created * 1000),
4800
+ metadata: safeStringify(stripeObject.metadata)
4801
+ };
4802
+ break;
4803
+
4804
+ default:
4805
+ logger.warn('Unsupported Stripe object type:', objectType);
4806
+ return { success: false, message: 'Unsupported Stripe object type: ' + objectType };
4807
+ }
4808
+
4809
+ // Implémentation manuelle de upsert
4810
+ try {
4811
+ const filter = {};
4812
+ filter[idField] = stripeObject.id;
4813
+
4814
+ // 1. Vérifier si l'entité existe déjà
4815
+ const existing = await db.findOne(modelName, filter);
4816
+
4817
+ if (existing) {
4818
+ // 2. Mise à jour si l'entité existe
4819
+ await db.update(modelName, filter, dataToUpsert);
4820
+ logger.info(\`Updated \${modelName} with \${idField}: \${stripeObject.id}\`);
4821
+ } else {
4822
+ // 3. Création si l'entité n'existe pas
4823
+ await db.create(modelName, { ...filter, ...dataToUpsert });
4824
+ logger.info(\`Created new \${modelName} with \${idField}: \${stripeObject.id}\`);
4825
+ }
4826
+
4827
+ return { success: true };
4828
+ } catch (e) {
4829
+ logger.error('Failed to sync Stripe entity:', e);
4830
+ return { success: false, message: 'Database operation failed: ' + e.message };
4831
+ }
4832
+ `
4833
+ }
4834
+ ],
4835
+ "workflowStep": [
4836
+ {
4837
+ "name": "Log Unhandled Event",
4838
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4839
+ "actions": { "$link": { "name": "Log Unhandled Stripe Event", "_model": "workflowAction" } },
4840
+ "isTerminal": true
4841
+ },
4842
+ // --- Main Webhook Router Steps ---
4843
+ {
4844
+ "name": "Check for Invoice Paid",
4845
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4846
+ "conditions": { "$eq": ["{triggerData.event.type}", "invoice.paid"] },
4847
+ "onSuccessStep": { "$link": { "name": "Handle Paid Invoice", "_model": "workflowStep" } },
4848
+ "onFailureStep": { "$link": { "name": "Check for Invoice Payment Failed", "_model": "workflowStep" } }
4849
+ },
4850
+ {
4851
+ "name": "Check for Invoice Payment Failed",
4852
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4853
+ "conditions": { "$eq": ["{triggerData.event.type}", "invoice.payment_failed"] },
4854
+ "onSuccessStep": { "$link": { "name": "Handle Failed Invoice", "_model": "workflowStep" } },
4855
+ "onFailureStep": { "$link": { "name": "Check for Subscription Created", "_model": "workflowStep" } }
4856
+ },
4857
+ {
4858
+ "name": "Check for Subscription Created",
4859
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4860
+ "conditions": { "$eq": ["{triggerData.event.type}", "customer.subscription.created"] },
4861
+ "onSuccessStep": { "$link": { "name": "Handle Subscription Created", "_model": "workflowStep" } },
4862
+ "onFailureStep": { "$link": { "name": "Check for Subscription Updated", "_model": "workflowStep" } }
4863
+ },
4864
+ {
4865
+ "name": "Check for Subscription Updated",
4866
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4867
+ "conditions": { "$in": ["{triggerData.event.type}", ["customer.subscription.updated", "customer.subscription.deleted"]] },
4868
+ "onSuccessStep": { "$link": { "name": "Handle Subscription Update/Delete", "_model": "workflowStep" } },
4869
+ "onFailureStep": { "$link": { "name": "Check for Payment Succeeded", "_model": "workflowStep" } }
4870
+ },
4871
+ {
4872
+ "name": "Check for Payment Succeeded",
4873
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4874
+ "conditions": { "$eq": ["{triggerData.event.type}", "payment_intent.succeeded"] },
4875
+ "onSuccessStep": { "$link": { "name": "Handle Payment Succeeded", "_model": "workflowStep" } },
4876
+ "onFailureStep": { "$link": { "name": "Check for Customer Updated", "_model": "workflowStep" } }
4877
+ },
4878
+
4879
+ // --- Action-performing Steps ---
4880
+ {
4881
+ "name": "Handle Paid Invoice",
4882
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4883
+ "actions": { "$link": { "name": "Sync Stripe Entity to Local DB", "_model": "workflowAction" } },
4884
+ "isTerminal": true
4885
+ },
4886
+ {
4887
+ "name": "Handle Failed Invoice",
4888
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4889
+ "actions": { "$link": { "name": "Send Payment Failure Email", "_model": "workflowAction" } },
4890
+ "isTerminal": true
4891
+ },
4892
+ {
4893
+ "name": "Handle Invoice Created",
4894
+ "workflow": { "$link": { "name": "Invoice Processing", "_model": "workflow" } },
4895
+ "actions": { "$link": { "name": "Send Payment Failure Email", "_model": "workflowAction" } },
4896
+ "isTerminal": true
4897
+ },
4898
+ {
4899
+ "name": "Handle Subscription Created",
4900
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4901
+ "actions": [
4902
+ { "$link": { "name": "Sync Stripe Entity to Local DB", "_model": "workflowAction" } },
4903
+ { "$link": { "name": "Send Subscription Welcome", "_model": "workflowAction" } }
4904
+ ],
4905
+ "onSuccessStep": { "$link": { "name": "Handle Send Welcome Email", "_model": "workflowStep" } }
4906
+ },
4907
+ {
4908
+ "name": "Handle Send Welcome Email",
4909
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4910
+ "actions": { "$link": { "name": "Send Subscription Welcome", "_model": "workflowAction" } },
4911
+ "isTerminal": true
4912
+ },
4913
+ {
4914
+ "name": "Handle Subscription Update/Delete",
4915
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4916
+ "actions": { "$link": { "name": "Sync Stripe Entity to Local DB", "_model": "workflowAction" } },
4917
+ "isTerminal": true
4918
+ },
4919
+ {
4920
+ "name": "Handle Payment Succeeded",
4921
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4922
+ "actions": { "$link": { "name": "Sync Stripe Entity to Local DB", "_model": "workflowAction" } },
4923
+ "onSuccessStep": { "$link": { "name": "Handle Send Payment Receipt", "_model": "workflowStep" } }
4924
+ },
4925
+ {
4926
+ "name": "Handle Send Payment Receipt",
4927
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4928
+ "actions": { "$link": { "name": "Send Payment Receipt", "_model": "workflowAction" } },
4929
+ "isTerminal": true
4930
+ },
4931
+
4932
+ // --- Other Workflows Steps (Checkout, etc.) ---
4933
+ {
4934
+ "name": "Select Session Type",
4935
+ "workflow": { "$link": { "name": "Create Stripe Checkout Session", "_model": "workflow" } },
4936
+ "conditions": { "$eq": ["{triggerData.mode}", "subscription"] },
4937
+ "onSuccessStep": { "$link": { "name": "Create Subscription Checkout Session", "_model": "workflowStep" } },
4938
+ "onFailureStep": { "$link": { "name": "Create Payment Checkout Session", "_model": "workflowStep" } }
4939
+ },
4940
+ {
4941
+ "name": "Create Payment Checkout Session",
4942
+ "workflow": { "$link": { "name": "Create Stripe Checkout Session", "_model": "workflow" } },
4943
+ "actions": { "$link": { "name": "Stripe: Create Checkout Session (Payment)", "_model": "workflowAction" } },
4944
+ "isTerminal": true
4945
+ },
4946
+ {
4947
+ "name": "Create Subscription Checkout Session",
4948
+ "workflow": { "$link": { "name": "Create Stripe Checkout Session", "_model": "workflow" } },
4949
+ "actions": { "$link": { "name": "Stripe: Create Checkout Session (Subscription)", "_model": "workflowAction" } },
4950
+ "isTerminal": true
4951
+ },
4952
+ {
4953
+ "name": "Check for Customer Updated",
4954
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4955
+ "conditions": { "$in": ["{triggerData.event.type}", ["customer.created", "customer.updated", "customer.deleted"]] },
4956
+ "onSuccessStep": { "$link": { "name": "Handle Customer Update", "_model": "workflowStep" } },
4957
+ "onFailureStep": { "$link": { "name": "Check for Product Updated", "_model": "workflowStep" } }
4958
+ },
4959
+ {
4960
+ "name": "Check for Product Updated",
4961
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4962
+ "conditions": { "$in": ["{triggerData.event.type}", ["product.created", "product.updated", "product.deleted", "price.created", "price.updated"]] },
4963
+ "onSuccessStep": { "$link": { "name": "Handle Product Update", "_model": "workflowStep" } },
4964
+ "onFailureStep": { "$link": { "name": "Log Unhandled Event", "_model": "workflowStep" } }
4965
+ },
4966
+ {
4967
+ "name": "Handle Customer Update",
4968
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4969
+ "actions": { "$link": { "name": "Sync Stripe Entity to Local DB", "_model": "workflowAction" } },
4970
+ "isTerminal": true
4971
+ },
4972
+ {
4973
+ "name": "Handle Product Update",
4974
+ "workflow": { "$link": { "name": "Process Stripe Webhook Events", "_model": "workflow" } },
4975
+ "actions": { "$link": { "name": "Sync Stripe Entity to Local DB", "_model": "workflowAction" } },
4976
+ "isTerminal": true
4977
+ },
4978
+ {
4979
+ "name": "Generate Portal URL",
4980
+ "workflow": { "$link": { "name": "Create Stripe Customer Portal Session", "_model": "workflow" } },
4981
+ "actions": { "$link": { "name": "Stripe: Create Customer Portal Session", "_model": "workflowAction" } },
4982
+ "isTerminal": true
4983
+ }
4984
+ ],
4985
+ "dashboard": [
4986
+ {
4987
+ "name": "Stripe Payments Overview",
4988
+ "description": "Key metrics and recent activity for Stripe payments.",
4989
+ "layout": [
4990
+ {
4991
+ "name": "Payment Metrics",
4992
+ "kpis": [
4993
+ "Total Revenue",
4994
+ "Successful Payments",
4995
+ "Avg. Payment Value",
4996
+ "Refund Rate"
4997
+ ],
4998
+ "chartConfigs": [
4999
+ {
5000
+ "title": "Payments by Day",
5001
+ "type": "line",
5002
+ "model": "StripePayment",
5003
+ "xAxis": { "field": "created", "interval": "day" },
5004
+ "yAxis": { "field": "amount", "aggregation": "sum" },
5005
+ "filter": { "status": "succeeded" }
5006
+ }
5007
+ ]
5008
+ }/*
5009
+ {
5010
+ "name": "Recent Activity",
5011
+ "component": "DataTable",
5012
+ "config": {
5013
+ "model": "StripePayment",
5014
+ "columns": ["created", "amount", "currency", "status", "user"],
5015
+ "limit": 10,
5016
+ "sort": { "created": -1 }
5017
+ }
5018
+ }*/
5019
+ ]
5020
+ }
5021
+ ],
5022
+ "kpi": [
5023
+ {
5024
+ "name": "Total Revenue",
5025
+ "targetModel": "StripePayment",
5026
+ "aggregationType": "sum",
5027
+ "aggregationField": "amount",
5028
+ "filter": { "status": "succeeded" },
5029
+ "unit": "$",
5030
+ "icon": "FaMoneyBillWave"
5031
+ },
5032
+ {
5033
+ "name": "Successful Payments",
5034
+ "targetModel": "StripePayment",
5035
+ "aggregationType": "count",
5036
+ "filter": { "status": "succeeded" },
5037
+ "icon": "FaCreditCard"
5038
+ },
5039
+ {
5040
+ "name": "Avg. Payment Value",
5041
+ "targetModel": "StripePayment",
5042
+ "aggregationType": "avg",
5043
+ "aggregationField": "amount",
5044
+ "filter": { "status": "succeeded" },
5045
+ "unit": "$",
5046
+ "icon": "FaCalculator"
5047
+ },
5048
+ {
5049
+ "name": "Refund Rate",
5050
+ "targetModel": "StripeRefund",
5051
+ "aggregationType": "ratio",
5052
+ "numerator": { "model": "StripeRefund", "aggregation": "count" },
5053
+ "denominator": { "model": "StripePayment", "aggregation": "count" },
5054
+ "unit": "%",
5055
+ "icon": "FaExchangeAlt"
5056
+ }
5057
+ ]
5058
+ }
5059
+ }
4054
5060
  }
4055
5061
  ];
4056
5062
  }