nodebb-plugin-calendar-onekite 11.1.39 → 11.1.41

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/lib/admin.js CHANGED
@@ -93,29 +93,8 @@ admin.approveReservation = async function (req, res) {
93
93
  let paymentUrl = null;
94
94
  if (token) {
95
95
  const requester = await user.getUserFields(r.uid, ['username', 'email']);
96
- // Determine amount from HelloAsso items (pricing comes from HelloAsso)
97
- let totalAmount = 0;
98
- try {
99
- const items = await helloasso.listItems({
100
- env,
101
- token,
102
- organizationSlug: settings.helloassoOrganizationSlug,
103
- formType: settings.helloassoFormType,
104
- formSlug: settings.helloassoFormSlug,
105
- });
106
- const normalized = (items || []).map((it) => ({
107
- id: String(it.id || it.itemId || it.reference || it.name),
108
- price: it.price || it.amount || it.unitPrice || 0,
109
- })).filter(it => it.id);
110
- const match = normalized.find(it => it.id === String(r.itemId));
111
- totalAmount = match ? parseInt(match.price, 10) || 0 : 0;
112
- } catch (e) {
113
- totalAmount = 0;
114
- }
115
-
116
- if (!totalAmount) {
117
- return res.status(400).json({ error: 'item-price-not-found' });
118
- }
96
+ // r.total is stored as an estimated total in euros; HelloAsso expects cents.
97
+ const totalAmount = Math.max(0, Math.round((Number(r.total) || 0) * 100));
119
98
  paymentUrl = await helloasso.createCheckoutIntent({
120
99
  env,
121
100
  token,
@@ -124,11 +103,17 @@ admin.approveReservation = async function (req, res) {
124
103
  formSlug: settings.helloassoFormSlug,
125
104
  totalAmount,
126
105
  payerEmail: requester && requester.email,
106
+ callbackUrl: 'https://api.onekite.com/helloasso',
107
+ itemName: 'Réservation matériel OneKite',
108
+ containsDonation: false,
109
+ metadata: { reservationId: String(rid) },
127
110
  });
128
111
  }
129
112
 
130
113
  if (paymentUrl) {
131
114
  r.paymentUrl = paymentUrl;
115
+ } else {
116
+ console.warn('[calendar-onekite] HelloAsso payment link not created (approve ACP)', { rid });
132
117
  }
133
118
 
134
119
  await dbLayer.saveReservation(r);
package/lib/api.js CHANGED
@@ -103,7 +103,7 @@ function eventsFor(resv) {
103
103
  start: startIsoDate,
104
104
  end: endIsoDate,
105
105
  extendedProps: {
106
- rid: resv.
106
+ rid: resv.rid,
107
107
  status,
108
108
  uid: resv.uid,
109
109
  itemIds: itemIds.filter(Boolean),
@@ -308,12 +308,18 @@ api.approveReservation = async function (req, res) {
308
308
  organizationSlug: settings2.helloassoOrganizationSlug,
309
309
  formType: settings2.helloassoFormType,
310
310
  formSlug: settings2.helloassoFormSlug,
311
- totalAmount: parseInt(r.total || 0, 10),
311
+ // r.total is stored as an estimated total in euros; HelloAsso expects cents.
312
+ totalAmount: Math.max(0, Math.round((Number(r.total) || 0) * 100)),
312
313
  payerEmail: payer && payer.email ? payer.email : '',
313
314
  callbackUrl: 'https://api.onekite.com/helloasso',
315
+ itemName: 'Réservation matériel OneKite',
316
+ containsDonation: false,
317
+ metadata: { reservationId: String(rid) },
314
318
  });
315
319
  if (paymentUrl) {
316
320
  r.paymentUrl = paymentUrl;
321
+ } else {
322
+ console.warn('[calendar-onekite] HelloAsso payment link not created (approve API)', { rid });
317
323
  }
318
324
  } catch (e) {
319
325
  // ignore payment link errors, admin can retry
package/lib/helloasso.js CHANGED
@@ -174,14 +174,17 @@ async function listCatalogItems({ env, token, organizationSlug, formType, formSl
174
174
  };
175
175
  }
176
176
 
177
- async function createCheckoutIntent({ env, token, organizationSlug, formType, formSlug, totalAmount, payerEmail, callbackUrl }) {
177
+ async function createCheckoutIntent({ env, token, organizationSlug, formType, formSlug, totalAmount, payerEmail, callbackUrl, itemName, containsDonation, metadata }) {
178
178
  if (!token || !organizationSlug) return null;
179
179
  // Checkout intents are created at organization level.
180
180
  const url = `${baseUrl(env)}/v5/organizations/${encodeURIComponent(organizationSlug)}/checkout-intents`;
181
181
  const payload = {
182
182
  totalAmount: totalAmount,
183
183
  initialAmount: totalAmount,
184
+ itemName: itemName || 'Réservation matériel',
185
+ containsDonation: (typeof containsDonation === 'boolean' ? containsDonation : false),
184
186
  payer: payerEmail ? { email: payerEmail } : undefined,
187
+ metadata: metadata || undefined,
185
188
  backUrl: callbackUrl || '',
186
189
  errorUrl: callbackUrl || '',
187
190
  returnUrl: callbackUrl || '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-calendar-onekite",
3
- "version": "11.1.39",
3
+ "version": "11.1.41",
4
4
  "description": "FullCalendar-based equipment reservation workflow with admin approval & HelloAsso payment for NodeBB",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/public/admin.js CHANGED
@@ -66,15 +66,15 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
66
66
  for (const r of list) {
67
67
  const created = r.createdAt ? fmtFR(r.createdAt) : '';
68
68
  const itemNames = Array.isArray(r.itemNames) && r.itemNames.length ? r.itemNames : [r.itemName || r.itemId].filter(Boolean);
69
- const itemsHtml = itemNames.map(n => `<div>• ${esc(String(n))}</div>`).join('');
69
+ const itemsHtml = itemNames.map(n => `<div>• ${escapeHtml(String(n))}</div>`).join('');
70
70
  const div = document.createElement('div');
71
71
  div.className = 'list-group-item';
72
72
  div.innerHTML = `
73
73
  <div class="d-flex justify-content-between align-items-start gap-2">
74
74
  <div style="min-width: 0;">
75
- <div><strong>${itemsHtml || esc(r.itemName || '')}</strong></div>
76
- <div class="text-muted" style="font-size: 12px;">Créée: ${esc(created)}</div>
77
- <div class="text-muted" style="font-size: 12px;">Période: ${esc(new Date(parseInt(r.start, 10)).toLocaleDateString('fr-FR'))} → ${esc(new Date(parseInt(r.end, 10)).toLocaleDateString('fr-FR'))}</div>
75
+ <div><strong>${itemsHtml || escapeHtml(r.itemName || '')}</strong></div>
76
+ <div class="text-muted" style="font-size: 12px;">Créée: ${escapeHtml(created)}</div>
77
+ <div class="text-muted" style="font-size: 12px;">Période: ${escapeHtml(new Date(parseInt(r.start, 10)).toLocaleDateString('fr-FR'))} → ${escapeHtml(new Date(parseInt(r.end, 10)).toLocaleDateString('fr-FR'))}</div>
78
78
  </div>
79
79
  <div class="d-flex gap-2">
80
80
  <button class="btn btn-outline-danger btn-sm">Rejeter</button>
@@ -86,7 +86,8 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
86
86
  refuseBtn.addEventListener('click', async () => {
87
87
  if (!confirm('Rejeter cette demande ?')) return;
88
88
  try {
89
- await fetchJson(`/api/v3/admin/plugins/calendar-onekite/pending/${encodeURIComponent(r.rid)}/refuse`, { method: 'POST', body: JSON.stringify({}) });
89
+ // Server route: PUT /api/v3/admin/plugins/calendar-onekite/reservations/:rid/refuse
90
+ await fetchJson(`/api/v3/admin/plugins/calendar-onekite/reservations/${encodeURIComponent(r.rid)}/refuse`, { method: 'PUT' });
90
91
  showAlert('success', 'Demande rejetée.');
91
92
  await loadPending();
92
93
  } catch (e) {
@@ -128,7 +129,8 @@ define('admin/plugins/calendar-onekite', ['alerts', 'bootbox'], function (alerts
128
129
  try {
129
130
  const adminNote = (document.getElementById('onekite-admin-note')?.value || '').trim();
130
131
  const pickupTime = (document.getElementById('onekite-pickup-time')?.value || '').trim();
131
- await fetchJson(`/api/v3/admin/plugins/calendar-onekite/pending/${encodeURIComponent(r.rid)}/approve`, { method: 'POST', body: JSON.stringify({ adminNote, pickupTime }) });
132
+ // Server route: PUT /api/v3/admin/plugins/calendar-onekite/reservations/:rid/approve
133
+ await fetchJson(`/api/v3/admin/plugins/calendar-onekite/reservations/${encodeURIComponent(r.rid)}/approve`, { method: 'PUT', body: JSON.stringify({ adminNote, pickupTime }) });
132
134
  showAlert('success', 'Demande validée.');
133
135
  await loadPending();
134
136
  } catch (e) {
package/public/client.js CHANGED
@@ -245,7 +245,14 @@ define('forum/calendar-onekite', ['alerts', 'bootbox', 'hooks'], function (alert
245
245
  calendar.unselect();
246
246
  isDialogOpen = false;
247
247
  } catch (e) {
248
- showAlert('error', 'Impossible de créer la demande (droits/groupe ?).');
248
+ const code = String(e && e.message || '');
249
+ if (code === '403') {
250
+ showAlert('error', 'Impossible de créer la demande : droits insuffisants (groupe).');
251
+ } else if (code === '409') {
252
+ showAlert('error', 'Impossible : au moins un matériel est déjà réservé ou en attente sur cette période.');
253
+ } else {
254
+ showAlert('error', 'Erreur lors de la création de la demande.');
255
+ }
249
256
  calendar.unselect();
250
257
  isDialogOpen = false;
251
258
  }