data-primals-engine 1.2.6 → 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.
@@ -0,0 +1,11 @@
1
+ import * as stripeService from './stripe.js';
2
+ // Importez d'autres services ici à l'avenir
3
+ // import * as anotherService from './anotherService.js';
4
+
5
+ /**
6
+ * Registre central de tous les services exposés au moteur de workflow.
7
+ */
8
+ export const services = {
9
+ stripe: stripeService,
10
+ // anotherService: anotherService,
11
+ };
@@ -0,0 +1,141 @@
1
+ import Stripe from 'stripe';
2
+ import {editData, insertData, searchData} from '../modules/data/index.js';
3
+ import {getEnv} from "../modules/user.js";
4
+
5
+
6
+ /**
7
+ * Crée ou récupère un client Stripe pour un utilisateur de notre système.
8
+ */
9
+ async function getOrCreateCustomer(user, db) {
10
+ // Vérifier que l'utilisateur et son email de contact sont valides
11
+ if (!user || !user.contact || !user.contact.email) {
12
+ // Dans un vrai scénario, il faudrait peut-être chercher l'email directement sur le modèle user
13
+ const userRecord = await db.findOne('user', {_id: user._id});
14
+ if(!userRecord || !userRecord.contact || !userRecord.contact.email) {
15
+ throw new Error("User or user contact email is missing.");
16
+ }
17
+ user = userRecord;
18
+ }
19
+
20
+ const existingCustomer = await db.findOne('StripeCustomer', { user: user._id });
21
+
22
+ if (existingCustomer) {
23
+ return existingCustomer;
24
+ }
25
+
26
+ // Créer le client sur Stripe
27
+ const stripeCustomer = await stripe.customers.create({
28
+ email: user.contact.email,
29
+ name: user.username,
30
+ metadata: {
31
+ userId: user._id.toString() // Lien vers notre système
32
+ }
33
+ });
34
+
35
+ // Enregistrer le client dans notre BDD
36
+ const { insertedIds } = await insertData('StripeCustomer', {
37
+ user: user._id,
38
+ stripeCustomerId: stripeCustomer.id,
39
+ email: user.email
40
+ }, {}, user);
41
+
42
+ const newCustomer = await searchData({ model: 'StripeCustomer', filter: { _id: insertedIds[0] } });
43
+ return newCustomer.data[0];
44
+ }
45
+ /**
46
+ * Crée une session de checkout Stripe.
47
+ */
48
+ export async function createCheckoutSession(priceId, user, db) {
49
+ const customer = await getOrCreateCustomer(user, db);
50
+ const session = await stripe.checkout.sessions.create({
51
+ payment_method_types: ['card'],
52
+ mode: 'subscription' // ou 'payment' pour un paiement unique
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Vérifie le statut d'une session de checkout.
58
+ */
59
+ export async function verifyCheckoutSession(sessionId) {
60
+ try {
61
+ const session = await stripe.checkout.sessions.retrieve(sessionId);
62
+ return {
63
+ status: session.status,
64
+ payment_status: session.payment_status,
65
+ customer: session.customer,
66
+ subscription: session.subscription
67
+ };
68
+ } catch (error) {
69
+ console.error("Error verifying checkout session:", error);
70
+ throw new Error("Could not verify checkout session.");
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Vérifie la signature d'un webhook Stripe pour s'assurer qu'il est authentique.
76
+ * C'est une étape de sécurité cruciale.
77
+ *
78
+ * @param {object} headers - Les en-têtes de la requête (notamment 'stripe-signature').
79
+ * @param {string} rawBody - Le corps brut de la requête.
80
+ * @returns {Stripe.Event} L'objet événement Stripe vérifié.
81
+ * @throws {Error} Si la signature est invalide ou si les en-têtes/body sont manquants.
82
+ */
83
+ export async function verifyWebhookSignature(headers, rawBody, user) {
84
+ // Initialisation de Stripe avec la clé secrète (à charger depuis une config sécurisée)
85
+ const env = await getEnv(user);
86
+ const stripe = env.STRIPE_SECRET_KEY ? new Stripe(env.STRIPE_SECRET_KEY) : null;
87
+ const webhookSecret = env.STRIPE_WEBHOOK_SECRET;
88
+
89
+ if( !stripe ){
90
+ throw new Error("Stripe unknown API key.");
91
+ }
92
+ if (!webhookSecret) {
93
+ throw new Error("Stripe webhook secret (STRIPE_WEBHOOK_SECRET) is not configured on the server.");
94
+ }
95
+
96
+ const signature = headers['stripe-signature'];
97
+ if (!signature) {
98
+ throw new Error("Missing 'stripe-signature' header.");
99
+ }
100
+
101
+ try {
102
+ return stripe.webhooks.constructEvent(rawBody, signature, webhookSecret);
103
+ } catch (err) {
104
+ console.error(`❌ Error verifying Stripe webhook signature: ${err.message}`);
105
+ throw new Error(`Webhook signature verification failed: ${err.message}`);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Crée un remboursement pour une commande spécifique.
111
+ */
112
+ export async function createRefund(returnId, db, user) {
113
+ const returnRequest = await db.findOne('return', { _id: returnId });
114
+ if (!returnRequest) {
115
+ throw new Error("Return request not found.");
116
+ }
117
+
118
+ // Trouver la commande et le paiement associés
119
+ const order = await db.findOne('order', { _id: returnRequest.order });
120
+ if (!order || !order.paymentIntentId) {
121
+ throw new Error("Associated order or its paymentIntentId not found.");
122
+ }
123
+
124
+ const refund = await stripe.refunds.create({
125
+ payment_intent: order.paymentIntentId,
126
+ amount: Math.round(returnRequest.amount * 100), // Stripe attend le montant en centimes
127
+ reason: 'requested_by_customer',
128
+ metadata: {
129
+ returnId: returnId.toString(),
130
+ orderId: order.orderId
131
+ }
132
+ });
133
+
134
+ // Mettre à jour le statut de la demande de retour dans notre BDD
135
+ await editData('return', { _id: returnId }, {
136
+ status: 'refunded',
137
+ refundDate: new Date()
138
+ }, {}, user);
139
+
140
+ return { refundId: refund.id, status: refund.status };
141
+ }
@@ -697,7 +697,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
697
697
 
698
698
  // 2. Insérer le pack dans la collection pour le rendre disponible au test
699
699
  const packInsertResult = await testPacksColInstance.insertOne(mockPack);
700
- const packId = packInsertResult.insertedId;
700
+ const packId = packInsertResult.insertedId.toString();
701
701
 
702
702
  // 3. Appeler la fonction à tester avec la nouvelle signature
703
703
  const result = await installPack(packId, currentTestUser, 'en');
@@ -751,7 +751,7 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
751
751
  };
752
752
 
753
753
  const packInsertResult = await testPacksColInstance.insertOne(mockPackWithInvalidModel);
754
- const packId = packInsertResult.insertedId;
754
+ const packId = packInsertResult.insertedId.toString();
755
755
 
756
756
  const result = await installPack(packId, currentTestUser, 'en');
757
757
 
@@ -770,6 +770,69 @@ describe('Intégration des fonctions CRUD de données avec validation complète'
770
770
  const validModel = await testModelsColInstance.findOne({ name: 'validPackModel', _user: currentTestUser.username });
771
771
  expect(validModel).not.toBeNull();
772
772
  });
773
+
774
+ it('devrait correctement insérer des données JSON depuis un pack', async () => {
775
+ const { currentTestUser } = await setupTestContext();
776
+ // 1. Définir le pack avec des données JSON
777
+ const mockPackWithJson = {
778
+ name: 'test-pack-with-json',
779
+ models: [
780
+ {
781
+ name: 'packConfig',
782
+ description: 'Modèle avec un champ JSON',
783
+ fields: [
784
+ { name: 'configName', type: 'string', required: true },
785
+ { name: 'settings', type: 'code', language: 'json' }
786
+ ]
787
+ }
788
+ ],
789
+ data: {
790
+ all: {
791
+ packConfig: [
792
+ {
793
+ configName: 'mainConfig',
794
+ settings: {
795
+ theme: 'dark',
796
+ notifications: {
797
+ enabled: true,
798
+ level: 'important'
799
+ }
800
+ }
801
+ }
802
+ ]
803
+ }
804
+ }
805
+ };
806
+
807
+ // 2. Insérer le pack
808
+ const packInsertResult = await testPacksColInstance.insertOne(mockPackWithJson);
809
+ const packId = packInsertResult.insertedId.toString();
810
+
811
+ // 3. Appeler la fonction à tester
812
+ const result = await installPack(packId, currentTestUser, 'en');
813
+
814
+ // 4. Assertions
815
+ expect(result.success, `L'installation du pack JSON a échoué: ${result.errors?.join('; ')}`).toBe(true);
816
+ expect(result.summary.models.installed).toHaveLength(1);
817
+ expect(result.summary.datas.inserted).toBe(1);
818
+
819
+ // 5. Vérifier les données insérées en BDD
820
+ const datasCol = await getCollectionForUser(currentTestUser);
821
+ const insertedConfig = await datasCol.findOne({ _model: 'packConfig', _user: currentTestUser.username });
822
+
823
+ expect(insertedConfig).not.toBeNull();
824
+ expect(insertedConfig.configName).toBe('mainConfig');
825
+ expect(typeof insertedConfig.settings).toBe('object');
826
+ expect(insertedConfig.settings).toEqual({
827
+ theme: 'dark',
828
+ notifications: {
829
+ enabled: true,
830
+ level: 'important'
831
+ }
832
+ });
833
+
834
+ await getCollection('packs').deleteMany({ _id: packId});
835
+ });
773
836
  });
774
837
  // In test/data.integration.test.js
775
838
 
@@ -251,7 +251,7 @@ describe('Intégration des Actions de Workflow', () => {
251
251
  expect(deletedTask).toBeNull();
252
252
  });
253
253
 
254
- it('Action Webhook: devrait appeler fetch avec les bonnes informations substituées', async () => {
254
+ it('Action HttpRequest: devrait appeler fetch avec les bonnes informations substituées', async () => {
255
255
  global.fetch.mockResolvedValue({
256
256
  ok: true,
257
257
  status: 200,
@@ -261,7 +261,7 @@ describe('Intégration des Actions de Workflow', () => {
261
261
 
262
262
  const { workflowId } = await setupWorkflow({
263
263
  name: 'Notify External System',
264
- type: 'Webhook',
264
+ type: 'HttpRequest',
265
265
  method: 'POST',
266
266
  url: 'https://api.example.com/notify/{triggerData.status}',
267
267
  headers: { "Authorization": "Bearer {env.API_KEY}" },
@@ -113,7 +113,7 @@ const workflowMetaModels = [
113
113
  { name: 'updateMultiple', type: 'boolean' },
114
114
  { name: 'deleteMultiple', type: 'boolean' },
115
115
  // GenerateAIContent
116
- { name: 'aiProvider', type: 'enum', items: ['OpenAI', 'GoogleGemini'] },
116
+ { name: 'aiProvider', type: 'enum', items: ['OpenAI', 'Google', 'Anthropic', 'DeepSeek'] },
117
117
  { name: 'aiModel', type: 'string' },
118
118
  { name: 'prompt', type: 'richtext' },
119
119
  // SendEmail