From 1300084481958423f2fd4700b61bedb97401e4df Mon Sep 17 00:00:00 2001 From: warringtond Date: Tue, 24 Mar 2026 14:16:27 -0400 Subject: [PATCH] production secret added --- .firebase/hosting.ZGlzdA.cache | 27 +++++++++++++++++++++++++++ firestore.rules | 6 ++++++ functions/index.js | 32 +++++++++++++++++++++++--------- src/donors.js | 4 +++- src/pending-donations.js | 1 + 5 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 .firebase/hosting.ZGlzdA.cache diff --git a/.firebase/hosting.ZGlzdA.cache b/.firebase/hosting.ZGlzdA.cache new file mode 100644 index 0000000..72af8eb --- /dev/null +++ b/.firebase/hosting.ZGlzdA.cache @@ -0,0 +1,27 @@ +thank-you.html,1774294010867,a98548e027b70a2458181b1f196d304ba6b6fab91579d9beca30bdbdadc7d0d4 +images/whs-logo.svg,1774294010745,f2f19a1d66481b40716d2fae51d3d95dbf0b9617838ff17a881872a3a828cd73 +admin.html,1774294010867,8af127db93bb249aa05e8d1161f60cbac31895135f496507a0dbd2eac501dff7 +index.html,1774294010867,7a8dd6e44319df434aa173ca63eab80a8ff1a47299dde4496e0b228acfab66bc +images/image-8.jpeg,1774294010737,1fec63c572754a3ad0e9c5a15cfcc805c53379ae76e37d21f69a800616a6d358 +images/image-5.jpeg,1774294010736,4cc8d453cd23f99f35a1e586ccc2df3b41ec0174a86d0a96a31157d4d0eb27ca +images/image-4.jpeg,1774294010736,843a3e70184b50e1c011092b35abc51c7b8484f32f6f07ae1cd469ba37a551fc +images/image-3.jpeg,1774294010735,8d9ca0c0bc4a905bade23eda94e41fda805c1f00234f8194bdfef0350f99966c +images/favicon.svg,1774294010733,bbc0b0e1127a983a4959994990de60d262fabc2841eaafd1da90f3722e2f4316 +images/image-2.jpeg,1774294010735,24aeeaf130a7b960d996192827b9b6142ab6d41f1d87ff49418ac484b60f2887 +images/image-10.jpeg,1774294010734,af37c6807cadfd5c5d39848fbacb96022038eeca29a5f7aafe05aa48ea001fcf +assets/style-B2qcWQAK.css,1774294010867,8d9acb18fb8a1d37e887273bce7fd56a36a9c2268383c842e52df6abfac65c56 +assets/admin-r6G2dek0.css,1774294010867,6ac9a2ac211285672f35c9a7c38ef92debe018923b2ae8936382173be7bb8a6c +assets/main-CCpTsy09.js,1774294010867,9666c47573d9b382b8924a05258ccf2fba644594f050b7fcaca3e6a03490d31b +assets/admin-CsJ8T6K2.js,1774294010867,8a6602998b0e3569ca2f07d245806f04057dd221cbe2ef910b72bb1325a7ba7a +images/logo-white.png,1774294010744,2323f3f0fb9e21f903d72a49a2986d1fa0b54a2504ecd9b0292537fb7991d8fc +images/image-7.jpeg,1774294010737,cb7578c686fd0944fff610a639e99dc16333db0d293bb8ba9a2a233d35faeb8a +images/image-9.jpeg,1774294010738,f90c90cacc15bb5a0cdbcbaf3f7d04188578d5f416c86b5a6926da5c19638b78 +images/logo-white.jpeg,1774294010744,d9da6cd236f2da999142179a51130ae2ce16ab62498aeffde5708a26ef1bbf5d +images/image-1.jpeg,1774294010733,ecd0ecf9188d492a91f3eefaf11aa34a8767beafcc205e4e3976f06370a2d16f +images/image-6.jpeg,1774294010736,c1b308cfc1cd79523c04b0d77901a1025c95d7526bb446dbc064cd75d80c549e +images/logo-orange.jpeg,1774294010743,27bb4f3c065919ff52cf6bd1a493cc2fd7cd14451316307105f18768f724491a +images/logo-black.jpeg,1774294010738,30b9860ce62ac0f5e47f6b5a0e36674f67ae358ffa368099264c84b2ee82db7d +images/logo-blue.jpeg,1774294010740,7263a76d431ec3327052ea46e43d8fc126f5e50353fff32d2b27fe729ff72f26 +images/image-11.jpeg,1774294010734,ff55cff55378fddf547dbb8a9d4f46be690417117ceacd3dc422fb051b462c75 +assets/donors-BfBdprwo.js,1774294010867,25ba97a96b4b6acf626544695794c0be01d25aa62ef6c3b4d647c862d1eb8c13 +images/logo-blue.png,1774294010742,1164ff2f709525030b5f4be9794880c6374177516ad22a4789bf4a3b5ecd9160 diff --git a/firestore.rules b/firestore.rules index 446858e..464c818 100644 --- a/firestore.rules +++ b/firestore.rules @@ -50,6 +50,12 @@ service cloud.firestore { allow update: if false; } + // Pending donations (created before payment, confirmed via webhook) + match /pendingDonations/{donationId} { + allow create: if true; + allow read, update, delete: if false; + } + // Newsletter subscribers match /subscribers/{subscriberId} { allow create: if true; diff --git a/functions/index.js b/functions/index.js index 7dde698..0b23b0d 100644 --- a/functions/index.js +++ b/functions/index.js @@ -7,14 +7,14 @@ initializeApp(); const db = getFirestore(); /** - * Shopdm Pay Webhook Handler + * Shopdm Pay Webhook Handler (v2) * * Receives payment.successful events from Shopdm Pay. * Matches the invoice_id to a pending donation and creates * a confirmed donor record. */ exports.shopdmPayWebhook = onRequest( - { secrets: ["SHOPDM_MERCHANT_SECRET_DEV"] }, + { secrets: ["SHOPDM_MERCHANT_SECRET", "SHOPDM_MERCHANT_SECRET_DEV"], invoker: "public" }, async (req, res) => { // Only accept POST requests if (req.method !== "POST") { @@ -24,8 +24,7 @@ exports.shopdmPayWebhook = onRequest( const payload = req.body; - // Verify webhook signature - // Supports both dev and prod secrets + // Verify webhook signature using sorted keys + HMAC-SHA256 (per Shopdm Pay docs) const secrets = [ process.env.SHOPDM_MERCHANT_SECRET, process.env.SHOPDM_MERCHANT_SECRET_DEV, @@ -39,10 +38,7 @@ exports.shopdmPayWebhook = onRequest( return; } - const sortedPayload = JSON.stringify( - payload, - Object.keys(payload).sort() - ); + const sortedPayload = JSON.stringify(payload, Object.keys(payload).sort()); const isValid = secrets.some((secret) => { const expected = crypto @@ -57,14 +53,21 @@ exports.shopdmPayWebhook = onRequest( res.status(401).send("Unauthorized"); return; } + + console.log("Webhook signature verified successfully"); } // Only process successful payments - if (payload.event !== "payment.successful") { + if (payload.event !== "payment.successful" && payload.event !== "payment.success") { res.status(200).send("Event ignored"); return; } + // Determine environment from the live flag in the payload + const isLive = payload.live === true; + const webhookEnv = isLive ? "production" : "dev"; + console.log(`Webhook environment: ${webhookEnv} (live=${payload.live})`); + const invoiceId = payload.metadata?.invoice_id; if (!invoiceId) { console.log("No invoice_id in webhook payload, skipping donor creation"); @@ -85,6 +88,16 @@ exports.shopdmPayWebhook = onRequest( const pending = pendingDoc.data(); + // Ensure the webhook environment matches the pending donation environment + const pendingEnv = pending.env || "production"; + if (pendingEnv !== webhookEnv) { + console.warn( + `Environment mismatch: pending donation is ${pendingEnv} but webhook is ${webhookEnv}. Skipping.` + ); + res.status(200).send("OK - environment mismatch"); + return; + } + // Check if already processed (idempotency) if (pending.status === "confirmed") { console.log(`Donation ${invoiceId} already confirmed, skipping`); @@ -103,6 +116,7 @@ exports.shopdmPayWebhook = onRequest( message: pending.message || "", anonymous: !!pending.anonymous, status: "confirmed", + env: webhookEnv, pendingDonationId: invoiceId, paymentId: payload.data?.object_id || "", date: FieldValue.serverTimestamp(), diff --git a/src/donors.js b/src/donors.js index 3058db2..a58b8ae 100644 --- a/src/donors.js +++ b/src/donors.js @@ -24,7 +24,9 @@ export function subscribeToDonors(callback) { (snapshot) => { const donors = []; snapshot.forEach((doc) => { - donors.push({ id: doc.id, ...doc.data() }); + const data = doc.data(); + if (data.env === 'dev') return; // skip test donations + donors.push({ id: doc.id, ...data }); }); callback(donors); }, diff --git a/src/pending-donations.js b/src/pending-donations.js index ca85070..91adef0 100644 --- a/src/pending-donations.js +++ b/src/pending-donations.js @@ -35,6 +35,7 @@ export function buildPaymentUrl(pendingId, amount, donorName) { reason: reason, invoice_id: pendingId, redirect: 'true', + webhook: 'true', }); return `${PAYMENT_BASE_URL}/${MERCHANT_HANDLE}?${params.toString()}`; }