Unable to send data from Stripe webhook to Firebase database - node.js

this is the webhook part
the payment is succeeding but it is not showing in firebase database
I have also tried different rules and payload code but it didn't work
this was the terminal command "stripe listen --forward-to localhost:3000/api/webhook"
these are the terminal responses
please check them also
webhook
import { buffer } from "micro";
import * as admin from 'firebase-admin'
//secure a connection to Firebase from backend
const serviceAccount = require('../../../permissions.jason');
const app = !admin.apps.length ? admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
})
: admin.app();
// establish connection to stripe
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_SIGNNING_SECRET;
if (typeof endpointSecret !== "string") {
console.error("Unexpected value for STRIPE_SIGNING_SECRET");
// potentially throw an error here
}
const fulfillOrder = async (session) => {
// console.log('Fulfilling order', session)
return app
.firestore()
.collection("user")
.doc(session.metadata.email)
.collection("orders")
.doc(session.id)
.set({
amount: session.amount_total / 100,
amount_shipping: session.amount_total_details.amount_shipping / 100,
images: JSON.parse(session.metadata.images),
timestamp: admin.firestore.FieldValue.serverTimestamp(),
})
.then(() => {
console.log(`success: order ${session.id} had been added to db`);
});
};
export default async (req, res) =>{
if(req.method === 'post'){
const requestBuffer = await buffer(req);
const payload = requestBuffer.toString();
const sig = req.headers["stripe-signatur"];
let event;
// verify that the event posted came from stripe
try{
const event= stripe.webhooks.constructEvent(
req.rawBody.toString(),
sig,
endpointSecret);
} catch (err) {
console.log('ERROR', err.message)
return res.status(400).send(`Webhook error: ${err.message}`)
}
//handle the checkout event
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
//fulfill the order...
return fulfillOrder(session)
.then(() => res.status(200))
.catch((err) => res.status(400).send(`Webhook error: ${err.message}`));
}
}
};
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
firebase rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if
request.time < timestamp.date(2022, 5, 20);
}
}
}

Related

Create a crypto payment forwarder

I am trying to make my own cryptocurrency payment forwarder, (like https://apirone.com/), but I am facing an issue, this is my code:
const CryptoAccount = require('send-crypto');
const WebSocket = require('ws');
const qrcode = require('qrcode-terminal');
const clipboardy = require('clipboardy');
const init = async function() {
const socket = new WebSocket('wss://ws.blockchain.info/inv');
const wallets = [];
socket.on('open', stream => {
console.log('WebSocket opened');
setInterval(() => socket.send(JSON.stringify({"op": "ping"})), 10000);
});
socket.on('error', err => {
console.error(err.message);
});
socket.on('message', stream => {
try {
const response = JSON.parse(stream.toString('utf-8'));
if (JSON.stringify(response).includes('pong')) {
console.log('pong');
return;
}
console.log('SOCKET RESPONSE', response);
let outAddr = response.x.out[0].addr;
wallets.find(w => w.addr == outAddr).callback(response);
} catch (error) {
console.error(error);
}
})
const generateWallet = function() {
const privateKey = CryptoAccount.newPrivateKey();
const account = new CryptoAccount(privateKey);
return account;
}
const genWalletAndWatch = async function(currency, callback) {
if (currency !== 'BTC') throw new Error('Only BTC is supported currently.');
const wallet = generateWallet();
wallets.push({addr: (await wallet.address('BTC')), callback});
socket.send(JSON.stringify({"op":"addr_sub", "addr": (await wallet.address(currency))}));
return (await wallet.address(currency));
}
socket.onopen = async function(e) {
const wallet = await genWalletAndWatch('BTC', (response) => {
console.log('ANSWER', JSON.stringify(response));
});
console.log(wallet);
qrcode.generate('bitcoin:'+wallet, {small: true});
clipboardy.writeSync(wallet);
}
};
init();
This is a test code so there is no any express server or forward for the moment, I am only trying to create a btc address then to detect when a payment is done, but my problem is: The payment is never detected by the WebSocket, i can create a wallet without problem, I can ping the blockchain.info WebSocket api successfully, but the "addr_sub" never works, I've tried to send BTC etc, nothing is working.

When using forEach in a Cloud Function, I can't make .sendToDevice() method work

I can send messages to the iOS device using the second function shown below.
I get the document id in the collection name "users" which is at the first level and send the message using the token stored in the tokens subcollection therefore admin.firestore().collection('users').doc(userId).collection('tokens').
I have to change the way the function looks for the user. Rather than relying on the document id of the user, I now need a query in order to find the user. Being a query, unless I'm wrong, I'm forced to use forEach in order to send the message to the user. The function now looks as shown immediately below. In essence, once I know I have the user that needs to receive the message, I'm using the original function format to send the message but the message is never sent. All I see in the logs is Firebase messaging error and I have yet to figure out where the mistake is.
exports.sendMessage = functions.https.onRequest(async (res, response) => {
const body = res.body;
const orderTotal = body.total;
const orderId = String(body.id);
const query = await usersRef.where('token', '==', token).get();
if (query.empty) {
console.log('No matching documents.');
return;
}
query.forEach(doc => {
const tokens = usersRef.doc(doc.id).collection('tokens');
tokens.get()
.then(snapshot => {
const results = [];
snapshot.forEach(doc => {
const fcmToken = doc.data().fcmToken
console.log("fcmToken =>", fcmToken);
results.push(fcmToken);
})
const payload = {
notification: {
title_loc_key: 'notify_title',
subtitle_loc_key: 'notify_subtitle',
body_loc_key: 'notify_body',
badge: '1',
sound: 'cha-ching.caf',
mutable_content: 'true'
},
data: {
'total': orderTotal,
'orderId': orderId
}
}
response.send([results, , payload])
admin.messaging().sendToDevice(results, payload).then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
return { success: true };
}).catch((error) => {
return { error: error.code };
})
})
.catch(err => {
console.log("Error getting documents", err);
});
});
});
This is the original function which I used when using the document id.
exports.sendMessage = functions.https.onRequest(async (res, response) => {
const body = res.body
const orderTotal = body.total
const orderId = String(body.id)
const tokenReference = admin.firestore().collection('users').doc(userId).collection('tokens')
const tokenSnapshots = await tokenReference.get()
const results = []
tokenSnapshots.forEach(tokenSnapshot => {
const fcmToken = tokenSnapshot.data().fcmToken
results.push(fcmToken)
})
const payload = {
notification: {
title_loc_key: 'notify_title',
subtitle_loc_key: 'notify_subtitle',
body_loc_key: 'notify_body',
badge: '1',
sound: 'cha-ching.caf',
mutable_content: 'true'
},
data: {
'total': orderTotal,
'orderId': orderId
}
}
response.send([results, , payload])
admin.messaging().sendToDevice(results, payload).then((response) => {
console.log('Successfully sent message:', response);
return { success: true };
}).catch((error) => {
return { error: error.code };
})
})
Screenshot of the error:
The onRequest() function terminates when you return a response. You are using sendToDevice() after response.send(). Also make sure you are handling all the promises correctly. Try refactoring the using async-await syntax as shown below:
exports.sendMessage = functions.https.onRequest(async (res, response) => {
try {
const body = res.body;
const orderTotal = body.total;
const orderId = String(body.id);
const query = await usersRef.where("token", "==", "TOKEN").get();
if (query.empty) {
console.log("No matching documents.");
return;
}
// Query tokens of all users at once
const tokenSnapshots = await Promise.all(
query.docs.map((user) => usersRef.doc(user.id).collection("tokens").get())
);
// Array of all fcmTokens
const results = tokenSnapshots.reduce((acc, snapshot) => {
acc = [...acc, ...snapshot.docs.map((doc) => doc.data().fcmToken)];
return acc;
}, []);
const payload = { ...FCM_PAYLOAD };
const fcmResponse = await getMessaging().sendToDevice(results, payload);
console.log("Successfully sent message:", fcmResponse);
response.send([results, , payload]);
} catch (error) {
console.log(error);
response.json({ error: "An error occured" });
}
});
Also checkout Terminating HTTP Cloud Functions.
After days of working on this, it turns out there wasn't anything wrong with the function. I don't know how VPN works but the fact that I had it enabled on my iPhone was the reason I wasn't getting the notification.
I paused the VPN and the notification was received.

stripe CLI webhook not reciving in firebase without firebasefuctions

It is showing no error but it is not reaching Firebase.i am using stripe CLI
console.log(success: order ${session.id} had been added to db); this line never comes in the console.
the terminal shows everything created but it does not reach Firebase database I am thinking there is an error in the code. i think the firebase is not connecting
The stripe dashboard also says connected
I am using the forward to localhost line in git terminal
webhook code
import { buffer } from "micro";
import * as admin from 'firebase-admin'
//secure a connection to Firebase from backend
const serviceAccount = require('../../../permissions.json');
const app = !admin.apps.length ? admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
})
: admin.app();
// establish connection to stripe
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_SIGNING_SECRET;
if (typeof endpointSecret !== "string") {
console.error("Unexpected value for STRIPE_SIGNING_SECRET");
// potentially throw an error here
}
const fulfillOrder = async (session) => {
//console.log('Fulfilling order', session)
return app
.firestore()
.collection("user")
.doc(session.metadata.email)
.collection("orders")
.doc(session.id)
.set({
amount: session.amount_total / 100,
amount_shipping: session.amount_total_details.amount_shipping / 100,
images: JSON.parse(session.metadata.images),
timestamp: admin.firestore.FieldValue.serverTimestamp(),
})
.then(() => {
console.log(`success: order ${session.id} had been added to db`);
});
};
export default async (req, res) =>{
if(req.method === 'post'){
const requestBuffer = await buffer(req);
const payload = requestBuffer.toString();
const sig = req.headers["stripe-signature"];
let event;
// verify that the event posted came from stripe
try{
event = stripe.webhooks.constructEvent(
payload,
sig,
endpointSecret);
} catch (err) {
console.log('ERROR', err.message)
return res.status(400).send(`Webhook error: ${err.message}`)
}
//handle the checkout event
if (event.type === 'checkout.session.completed') {
const session = event .data.object;
//fulfill the order...
return fulfillOrder(session)
.then(() => res.status(200))
.catch((err) => res.status(400).send(`Webhook error: ${err.message}`));
}
}
};
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
firebase rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow write: if false;
allow read: if true;
}
}
}

Migrating users from OneSignal to our own database that we push notifications from

I am trying to migrate our subscribers from OneSignal. I exported the endpoint, the keys (auth and P256DH) and I configured the VAPID keys of my OS account on my server.
When I try to send a notification from OS then remove the service worker of OS and use my own, it's sending the same notification that I previously sent through OS (quite odd), and when I programmatically remove the service worker of OS (through the console) and register my own service worker, it's responding with 410 error from chrome ("NotRegistered") and 401 from Firefox ("Request did not validate missing authorization header").
app.js file:
let isSubscribed = false;
let swRegistration = null;
let applicationKey = "PUBLIC_VAPID_KEY_HERE";
function urlB64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
if ('serviceWorker' in navigator && 'PushManager' in window) {
console.log('Service Worker and Push is supported');
navigator.serviceWorker.register('sw.js')
.then(function (swReg) {
console.log('service worker registered');
swRegistration = swReg;
swRegistration.pushManager.getSubscription()
.then(function (subscription) {
isSubscribed = !(subscription === null);
if (isSubscribed) {
console.log('User is subscribed');
} else {
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlB64ToUint8Array(applicationKey)
})
.then(function (subscription) {
console.log(subscription);
console.log('User is subscribed');
saveSubscription(subscription);
isSubscribed = true;
})
.catch(function (err) {
console.log('Failed to subscribe user: ', err);
})
}
})
})
.catch(function (error) {
console.error('Service Worker Error', error);
});
} else {
console.warn('Push messaging is not supported');
}
function saveSubscription(subscription) {
let xmlHttp = new XMLHttpRequest();
xmlHttp.open("POST", "/subscribe");
xmlHttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState != 4) return;
if (xmlHttp.status != 200 && xmlHttp.status != 304) {
console.log('HTTP error ' + xmlHttp.status, null);
} else {
console.log("User subscribed to server");
}
};
xmlHttp.send(JSON.stringify(subscription));
}
sw.js file:
let notificationUrl = '';
self.addEventListener('push', function (event) {
console.log('Push received: ', event);
let _data = event.data ? JSON.parse(event.data.text()) : {};
notificationUrl = _data.url;
event.waitUntil(
self.registration.showNotification(_data.title, {
body: _data.message,
icon: _data.icon,
tag: _data.tag
})
);
});
self.addEventListener('notificationclick', function (event) {
event.notification.close();
event.waitUntil(
clients.matchAll({
type: "window"
})
.then(function (clientList) {
if (clients.openWindow) {
return clients.openWindow(notificationUrl);
}
})
);
});
push.js file which pushes notifications:
const express = require('express');
const router = express.Router();
const q = require('q');
const webPush = require('web-push');
const keys = require('./../config/keys');
const mysql = require("mysql");
const pool = mysql.createPool({
connectionLimit: 10,
host: 'localhost',
user: 'root',
password: 'root',
database: 'webpush',
multipleStatements: true,
dateStrings: true
});
router.post('/push', (req, res) => {
const payload = {
title: req.body.title,
message: req.body.message,
url: req.body.url,
ttl: req.body.ttl,
icon: req.body.icon,
image: req.body.image,
badge: req.body.badge,
tag: req.body.tag
};
pool.query('SELECT * FROM subscriber', (err, subscriptions) => {
if (err) {
return console.log(err);
console.error(`Error occurred while getting subscriptions`);
return res.status(500).json({
error: 'Technical error occurred'
});
}
if (!subscriptions.length) {
console.error(`No subscribers found`);
return res.status(500).json({
error: 'Subscribers not found'
});
}
let parallelSubscriptionCalls = subscriptions.map(subscription => {
return new Promise((resolve, reject) => {
const pushSubscription = {
endpoint: subscription.endpoint,
keys: {
p256dh: subscription.p256dh,
auth: subscription.auth
}
};
const pushPayload = JSON.stringify(payload);
const pushOptions = {
vapidDetails: {
subject: 'https://www.mydomainhere.com',
privateKey: keys.privateKey,
publicKey: keys.publicKey
},
TTL: payload.ttl,
headers: {}
};
webPush.sendNotification(pushSubscription, pushPayload, pushOptions)
.then((value) => {
resolve({
status: true,
endpoint: subscription.endpoint,
data: value
});
}).catch((err) => {
reject({
status: false,
endpoint: subscription.endpoint,
data: err
});
});
});
});
q.allSettled(parallelSubscriptionCalls).then((pushResults) => {
console.info(pushResults);
});
res.json({
data: 'Push triggered'
});
})
});
module.exports = router;
subscribe.js file which does the subscription:
const express = require('express');
const router = express.Router();
const mysql = require("mysql");
const pool = mysql.createPool({
connectionLimit: 10,
host: 'localhost',
user: 'root',
password: 'root',
database: 'webpush',
multipleStatements: true,
dateStrings: true
});
router.post('/subscribe', (req, res) => {
const endpoint = req.body.endpoint;
const auth = req.body.keys.auth;
const p256dh = req.body.keys.p256dh;
const subscriptionSet = { endpoint, auth, p256dh }
pool.getConnection((err, connection) => {
if (err) {
console.error(`Error occurred while saving subscription. Err: ${err}`);
return res.status(500).json({
error: 'Technical error occurred'
});
};
connection.query('INSERT INTO subscriber SET ?', subscriptionSet, (err, subscription) => {
if (err) {
console.error(`Error occurred while saving subscription. Err: ${err}`);
return res.status(500).json({
error: 'Technical error occurred'
});
}
res.json({
data: 'Subscription saved.'
})
})
});
});
module.exports = router;
Im trying to do the same, how and where did you export the subscribers auth and P256DH from onesignal? because in the csv export, onesignal provides the push_token (endpoint) and not the auth and P256DH. Where would i get the all the users auth and P256DH?
From my understanding you cannot re register the service worker unless the subscriber comes back and visits your domain. However, the service worker updates every day, so what you can do is edit the existing One signal service worker files - delete their content and add your own code or import scripts. Make sure not to change file location or rename the one signal service worker file, it needs to be the same filename. This will count as an 'update' and browsers should automatically replace the contents without any action from users. If there's two OneSignal service worker files then change both.

How to get a stripe payment charge 'success' from a Cloud Function for Firebase?

I'm working on a project with angular 6, Stripe elements and Google's Firebase (with Cloud Functions). All are new to me!
For the life of me, I'm unable to figure out how I can return 'something' which states that the payment has been successful. Stripe API docs, state that it only returns an error call if there is an 'error'...
I can see that the card is being charged successfully from the charge object in Firebase.
What can I use to query this and return the 'status: paid' value to my front-end...so I can use an *ngIf to display confirmation/failure message?
I know i'm missing something dead simple here...! I really appreciate any help with this guys.
index.js (cloud function)
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')
(functions.config().stripe.testkey)
exports.stripeCharge = functions.database
.ref('/payments/{paymentId}')
.onWrite((change, context) => {
const payment = change.after.val();
const paymentId = context.params.paymentId;
// checks if payment exists or if it has already been charged
if (!payment || payment.charge) {
return
}
return admin.database()
.ref('/users/')
.once('value')
.then(snapshot => {
return snapshot.val()
})
.then(customer => {
const amount = payment.amount;
const idempotency_key = paymentId; // prevent duplicate charges
const source = payment.token.id;
const currency = 'gbp';
const charge = { amount, currency, source };
return stripe.charges.create(charge, { idempotency_key });
})
.then(charge => {
admin.database()
.ref(`/payments/${paymentId}/charge`)
.set(charge)
return true;
})
});
Payment.service.ts
import { Injectable } from '#angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
#Injectable()
export class PaymentService {
constructor(private db: AngularFireDatabase) {}
// save the token to firebase, triggering the cloud function
processPayment(token: any, amount) {
const payment = { token, amount }
return this.db.list('/payments/').push(payment)
}
}
payment.component.ts (here's my onSubmit handler for the checkout)
async onSubmit(form: NgForm) {
//this.paymentProcess = true;
const { token, error } = await stripe.createToken(this.card, {
name: this.contactName,
email: this.contactEmail
});
if (error) {
console.log('Something is wrong:', error);
} else {
console.log('Success!', token);
this.paymentSvc.processPayment(token, this.amount);
}
this.card.clear();
}
You should modify your Cloud Function code as follows, in order to return the promise returned by the asynchronous .set(charge) method.
exports.stripeCharge = functions.database
.ref('/payments/{paymentId}')
.onWrite((change, context) => {
const payment = change.after.val();
const paymentId = context.params.paymentId;
// checks if payment exists or if it has already been charged
if (!payment || payment.charge) {
return
}
return admin.database()
.ref('/users/')
.once('value')
.then(snapshot => {
return snapshot.val()
})
.then(customer => {
const amount = payment.amount;
const idempotency_key = paymentId; // prevent duplicate charges
const source = payment.token.id;
const currency = 'gbp';
const charge = { amount, currency, source };
return stripe.charges.create(charge, { idempotency_key });
})
.then(charge => {
return admin.database() <-- Here return !!!!!!
.ref(`/payments/${paymentId}/charge`)
.set(charge);
//return true; <-- Here don't return !!!!!!
})
});
In order to "return the 'status: paid' value to the front-end..", just set a listener to the /payments/${paymentId}/charge path and as soon as the charge has the correct status value, update your front-end.
Finally, note that with:
...
.then(charge => {
admin.database()
.ref(`/payments/${paymentId}/charge`)
.set(charge)
return true;
})
you were returning the value true to the Cloud Function platform, (indicating that the Function can be terminated) before the set() asynchronous operation was completed.

Resources