nodebb-plugin-onekite-calendar 2.0.96 → 2.0.97

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.
@@ -336,17 +336,22 @@ async function handler(req, res, next) {
336
336
  // Extract paymentId.
337
337
  // Format A: data.id is the payment ID.
338
338
  // Format B: order.payments[0].id is the payment ID (data.id is the checkout intent ID).
339
+ // For Format B, HelloAsso sometimes omits order.payments — fall back to checkoutIntentId
340
+ // as the idempotency key (prefixed to avoid collision with numeric payment IDs).
339
341
  const _payments = (_d && _d.order && Array.isArray(_d.order.payments)) ? _d.order.payments : [];
340
342
  const paymentId = payload.data
341
343
  ? (_d.id || _d.paymentId)
342
344
  : (_payments.length ? _payments[0].id : null);
343
- if (!paymentId) {
345
+ const idempotencyKey = paymentId
346
+ ? String(paymentId)
347
+ : (!payload.data && checkoutIntentId ? `ci_${checkoutIntentId}` : null);
348
+ if (!idempotencyKey) {
344
349
  // eslint-disable-next-line no-console
345
- console.warn('[calendar-onekite] HelloAsso webhook: missing payment id', { eventType, dataKeys: _d ? Object.keys(_d) : [] });
350
+ console.warn('[calendar-onekite] HelloAsso webhook: missing payment id and checkout intent id', { eventType, dataKeys: _d ? Object.keys(_d) : [] });
346
351
  return res.json({ ok: true, ignored: true, missingPaymentId: true });
347
352
  }
348
353
 
349
- if (await alreadyProcessed(paymentId)) {
354
+ if (await alreadyProcessed(idempotencyKey)) {
350
355
  return res.json({ ok: true, duplicate: true });
351
356
  }
352
357
 
@@ -369,13 +374,13 @@ async function handler(req, res, next) {
369
374
  if (!r) {
370
375
  // eslint-disable-next-line no-console
371
376
  console.warn('[calendar-onekite] HelloAsso webhook: reservation not found (expired/deleted?)', { rid: resolvedRid, paymentId });
372
- await markProcessed(paymentId);
377
+ await markProcessed(idempotencyKey);
373
378
  return res.json({ ok: true, processed: true, reservationNotFound: true });
374
379
  }
375
380
 
376
381
  // Idempotency: already paid → just mark as processed and return.
377
382
  if (r.status === 'paid') {
378
- await markProcessed(paymentId);
383
+ await markProcessed(idempotencyKey);
379
384
  return res.json({ ok: true, processed: true, alreadyPaid: true });
380
385
  }
381
386
 
@@ -384,7 +389,7 @@ async function handler(req, res, next) {
384
389
 
385
390
  r.status = 'paid';
386
391
  r.paidAt = Date.now();
387
- r.paymentId = String(paymentId);
392
+ if (paymentId) r.paymentId = String(paymentId);
388
393
  // paymentReceiptUrl: Format A = data.paymentReceiptUrl, Format B = order.payments[0].paymentReceiptUrl
389
394
  const _receiptUrl = (payload.data && payload.data.paymentReceiptUrl)
390
395
  ? String(payload.data.paymentReceiptUrl)
@@ -444,7 +449,7 @@ async function handler(req, res, next) {
444
449
  });
445
450
  } catch (e) {}
446
451
 
447
- await markProcessed(paymentId);
452
+ await markProcessed(idempotencyKey);
448
453
  return res.json({ ok: true, processed: true });
449
454
  } catch (err) {
450
455
  return next(err);
package/lib/scheduler.js CHANGED
@@ -1,8 +1,54 @@
1
1
  'use strict';
2
2
 
3
3
  const nconf = require.main.require('nconf');
4
+ const meta = require.main.require('./src/meta');
5
+
6
+ const dbLayer = require('./db');
7
+ const { syncPayment } = require('./syncPayment');
8
+
9
+ const SETTINGS_KEY = 'calendar-onekite';
10
+ const SYNC_INTERVAL_MS = 60 * 1000;
11
+ // Minimum time between HelloAsso API checks for the same reservation (avoid spamming)
12
+ const MIN_CHECK_INTERVAL_MS = 2 * 60 * 1000;
4
13
 
5
14
  let timer = null;
15
+ const lastChecked = new Map(); // rid → timestamp of last HelloAsso check
16
+
17
+ async function runPaymentSync() {
18
+ try {
19
+ const settings = await meta.settings.get(SETTINGS_KEY);
20
+ if (!settings || !settings.helloassoClientId || !settings.helloassoClientSecret) return;
21
+
22
+ const ids = await dbLayer.listAllReservationIds(5000);
23
+ if (!ids || !ids.length) return;
24
+
25
+ const reservations = await dbLayer.getReservations(ids);
26
+ const now = Date.now();
27
+
28
+ const toSync = (reservations || []).filter((r) => {
29
+ if (!r || r.status !== 'awaiting_payment' || !r.checkoutIntentId) return false;
30
+ const last = lastChecked.get(String(r.rid));
31
+ return !last || (now - last) >= MIN_CHECK_INTERVAL_MS;
32
+ });
33
+
34
+ for (const r of toSync) {
35
+ lastChecked.set(String(r.rid), now);
36
+ try {
37
+ const result = await syncPayment({ r, settings, actorUid: 0 });
38
+ if (result.synced) {
39
+ // eslint-disable-next-line no-console
40
+ console.info('[calendar-onekite] Scheduler: auto-synced payment from HelloAsso', { rid: r.rid });
41
+ }
42
+ } catch (e) {
43
+ // eslint-disable-next-line no-console
44
+ console.warn('[calendar-onekite] Scheduler: syncPayment error', { rid: r.rid, err: e && e.message });
45
+ }
46
+ }
47
+ } catch (e) {
48
+ // eslint-disable-next-line no-console
49
+ console.warn('[calendar-onekite] Scheduler: payment sync error', { err: e && e.message });
50
+ }
51
+ }
6
52
 
7
53
  function start() {
8
54
  const runJobs = nconf.get('runJobs');
@@ -11,8 +57,9 @@ function start() {
11
57
  console.info('[calendar-onekite] Scheduler disabled (runJobs=false)');
12
58
  return;
13
59
  }
60
+ timer = setInterval(runPaymentSync, SYNC_INTERVAL_MS);
14
61
  // eslint-disable-next-line no-console
15
- console.info('[calendar-onekite] Scheduler started (no expiry timers configured)');
62
+ console.info('[calendar-onekite] Scheduler started');
16
63
  }
17
64
 
18
65
  function stop() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-calendar",
3
- "version": "2.0.96",
3
+ "version": "2.0.97",
4
4
  "description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "license": "MIT",