production secret added
This commit is contained in:
27
.firebase/hosting.ZGlzdA.cache
Normal file
27
.firebase/hosting.ZGlzdA.cache
Normal file
@@ -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
|
||||||
@@ -50,6 +50,12 @@ service cloud.firestore {
|
|||||||
allow update: if false;
|
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
|
// Newsletter subscribers
|
||||||
match /subscribers/{subscriberId} {
|
match /subscribers/{subscriberId} {
|
||||||
allow create: if true;
|
allow create: if true;
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ initializeApp();
|
|||||||
const db = getFirestore();
|
const db = getFirestore();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shopdm Pay Webhook Handler
|
* Shopdm Pay Webhook Handler (v2)
|
||||||
*
|
*
|
||||||
* Receives payment.successful events from Shopdm Pay.
|
* Receives payment.successful events from Shopdm Pay.
|
||||||
* Matches the invoice_id to a pending donation and creates
|
* Matches the invoice_id to a pending donation and creates
|
||||||
* a confirmed donor record.
|
* a confirmed donor record.
|
||||||
*/
|
*/
|
||||||
exports.shopdmPayWebhook = onRequest(
|
exports.shopdmPayWebhook = onRequest(
|
||||||
{ secrets: ["SHOPDM_MERCHANT_SECRET_DEV"] },
|
{ secrets: ["SHOPDM_MERCHANT_SECRET", "SHOPDM_MERCHANT_SECRET_DEV"], invoker: "public" },
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// Only accept POST requests
|
// Only accept POST requests
|
||||||
if (req.method !== "POST") {
|
if (req.method !== "POST") {
|
||||||
@@ -24,8 +24,7 @@ exports.shopdmPayWebhook = onRequest(
|
|||||||
|
|
||||||
const payload = req.body;
|
const payload = req.body;
|
||||||
|
|
||||||
// Verify webhook signature
|
// Verify webhook signature using sorted keys + HMAC-SHA256 (per Shopdm Pay docs)
|
||||||
// Supports both dev and prod secrets
|
|
||||||
const secrets = [
|
const secrets = [
|
||||||
process.env.SHOPDM_MERCHANT_SECRET,
|
process.env.SHOPDM_MERCHANT_SECRET,
|
||||||
process.env.SHOPDM_MERCHANT_SECRET_DEV,
|
process.env.SHOPDM_MERCHANT_SECRET_DEV,
|
||||||
@@ -39,10 +38,7 @@ exports.shopdmPayWebhook = onRequest(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedPayload = JSON.stringify(
|
const sortedPayload = JSON.stringify(payload, Object.keys(payload).sort());
|
||||||
payload,
|
|
||||||
Object.keys(payload).sort()
|
|
||||||
);
|
|
||||||
|
|
||||||
const isValid = secrets.some((secret) => {
|
const isValid = secrets.some((secret) => {
|
||||||
const expected = crypto
|
const expected = crypto
|
||||||
@@ -57,14 +53,21 @@ exports.shopdmPayWebhook = onRequest(
|
|||||||
res.status(401).send("Unauthorized");
|
res.status(401).send("Unauthorized");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Webhook signature verified successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only process successful payments
|
// Only process successful payments
|
||||||
if (payload.event !== "payment.successful") {
|
if (payload.event !== "payment.successful" && payload.event !== "payment.success") {
|
||||||
res.status(200).send("Event ignored");
|
res.status(200).send("Event ignored");
|
||||||
return;
|
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;
|
const invoiceId = payload.metadata?.invoice_id;
|
||||||
if (!invoiceId) {
|
if (!invoiceId) {
|
||||||
console.log("No invoice_id in webhook payload, skipping donor creation");
|
console.log("No invoice_id in webhook payload, skipping donor creation");
|
||||||
@@ -85,6 +88,16 @@ exports.shopdmPayWebhook = onRequest(
|
|||||||
|
|
||||||
const pending = pendingDoc.data();
|
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)
|
// Check if already processed (idempotency)
|
||||||
if (pending.status === "confirmed") {
|
if (pending.status === "confirmed") {
|
||||||
console.log(`Donation ${invoiceId} already confirmed, skipping`);
|
console.log(`Donation ${invoiceId} already confirmed, skipping`);
|
||||||
@@ -103,6 +116,7 @@ exports.shopdmPayWebhook = onRequest(
|
|||||||
message: pending.message || "",
|
message: pending.message || "",
|
||||||
anonymous: !!pending.anonymous,
|
anonymous: !!pending.anonymous,
|
||||||
status: "confirmed",
|
status: "confirmed",
|
||||||
|
env: webhookEnv,
|
||||||
pendingDonationId: invoiceId,
|
pendingDonationId: invoiceId,
|
||||||
paymentId: payload.data?.object_id || "",
|
paymentId: payload.data?.object_id || "",
|
||||||
date: FieldValue.serverTimestamp(),
|
date: FieldValue.serverTimestamp(),
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ export function subscribeToDonors(callback) {
|
|||||||
(snapshot) => {
|
(snapshot) => {
|
||||||
const donors = [];
|
const donors = [];
|
||||||
snapshot.forEach((doc) => {
|
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);
|
callback(donors);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export function buildPaymentUrl(pendingId, amount, donorName) {
|
|||||||
reason: reason,
|
reason: reason,
|
||||||
invoice_id: pendingId,
|
invoice_id: pendingId,
|
||||||
redirect: 'true',
|
redirect: 'true',
|
||||||
|
webhook: 'true',
|
||||||
});
|
});
|
||||||
return `${PAYMENT_BASE_URL}/${MERCHANT_HANDLE}?${params.toString()}`;
|
return `${PAYMENT_BASE_URL}/${MERCHANT_HANDLE}?${params.toString()}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user