Unable to send data from Stripe Webhook to Firebase Firestore - firebase

I have made a Stripe webhook and I want to write data to Firebase when a Stripe purchase happens, and it isn't working although the payment always succeeds but the data is not sent to Firebase database.
In the following I will provide my code:
` import {
buffer
} from "micro";
import * as admin from "firebase-admin";
// <--SECURE A CONNECTION TO FIREBASE FROM THE BACKEND -->
const serviceAccount = require("../../../permissions.json");
const app = !admin.apps.length ?
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
}) :
admin.app();
// Establish connection to stripe. Stripe initialization
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_SIGNING_SECRET;
const fulfillOrder = async(session) => {
console.log("FULFILL 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.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 the DB `);
})
};
export default async(req, res) =>
// In next js if we want to check if we have a get request or post request, etc, we do in the following way
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}`);
}
if (event.type === "checkout.session.completed") {
const session = event.data.object;
return fulfillOrder(session)
.then(() => res.status(200))
.catch((err) => res.status(400).send(`Webhook Error: ${err.message}`));
}
}
};
// in order to implement webhook with NEXTJS we need to DISABLE few features in the config
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
}; `

Related

Stripe JS subscription rerouting

This is my first time using Stripe in a project and I want to set up a subsciption system just to see if its possible. When I am trying to follow a tutorial for how to set it up, it always returns an error. Currently, I am at the part where I am trying to redirect the current user to the purchase page where they can sign up for the subscription, but I cannot seem to figure out a few key details. I am sharing the code for the checout session that I created.
import getStripe from "./initializeStripe";
import { db } from "../configs/firebase-config";
import { addDoc, collection, doc, onSnapshot } from "firebase/firestore";
export async function createCheckoutSession(uid) {
const location = "users/" + uid;
const docRef = collection(db, location, "checkout_sessions");
const checkoutSessionRef = await addDoc(docRef, {
price: "NEXT_PUBLIC_PRICE_KEY",
success_url: window.location.origin,
cancel_url: window.location.origin,
});
console.log();
onSnapshot(checkoutSessionRef, async (doc) => {
const { sessionId } = doc.data();
console.log(sessionId);
if (sessionId) {
const stripe = await getStripe();
stripe.redirectToCheckout({ sessionId });
}
}, [doc]);
}
Please let me know if you have any questions and I would be happy to provide with more code. I am working in NextJs 12 and Firebase Version 9
Edit: Let me add the initalizeStripe function too for more context.
import { Stripe, loadStripe } from "#stripe/stripe-js";
export const initializeStripe = async ({lineItems}) => {
let stripePromise = Stripe | null;
const getStripe = () => {
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE);
return stripePromise;
};
// const stripe = await getStripe();
await stripe.redirectToCheckout({
mode: 'payment',
lineItems,
successUrl: `${window.location.origin}?session_id={CHECKOUT_SESSION_ID}`,
cancelUrl: window.location.origin,
})
return getStripe();
};

Firestore and Firebase Cloud Functions

I have a simple https firebase cloud function which get all user doc from firestore but it actually give me error of
Received RST_STREAM with code 2 triggered by internal client error: Protocol error
This is my index file is
I have initailized the app
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import {helloWorld} from "./apis/recJobs";
// eslint-disable-next-line #typescript-eslint/no-var-requires
const serviceAccount = require("../serviceAccount.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "*************",
});
// APIs
export const hello = functions.region(
"asia-south1"
).https.onRequest(helloWorld);
This is my HelloWorld Function which is straight forward to get all docs from firestore
import * as admin from "firebase-admin";
export const helloWorld = (req :any, res:any) => {
try {
// get all users doc
const db = admin.firestore();
// get all docs from user
const snapshot = db.collection("user").get();
snapshot.then((data) => {
console.log("data is :", data);
res.status(200).send({
ok: "Success",
});
}
).catch((error) => {
console.log("error is :", error);
res.status(400).send({
ok: error,
});
}
);
} catch (error) {
console.log("error is :", error);
res.status(400).send({
ok: "Final error",
});
}
};
It should return all the user docs

Send notification to all user who have a specific location using Firebase Cloud Functions

I have a trigger that fires when an item is creating. Therefore, i want to see that when the item is created, all users who have a location that was saved in the item will receive a notification
import functions = require('firebase-functions');
import admin = require('firebase-admin');
admin.initializeApp();
exports.sendNotification = functions.firestore
.document('itens/{itenId}').onCreate((snap, context) => {
//You get the values of the newly created doc as follows:
const data = snap.data();
const name = data.name;
const location = data.location;
const payload = {
notification: {
title: "Avana - Está bater",
body: `Podes encontrar : ${name} em ${location}`, //Here we use value
sound: "default"
}
};
const options = {
priority: "high",
};
admin.messaging().sendToTopic("newItem", payload, options)
.then(function (response) {
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
console.log("Notification sent!");
return null;
});
your solution is Firebase Geofire:
https://firebase.googleblog.com/2013/09/geofire-location-queries-for-fun-and.html
https://medium.com/google-cloud/firebase-is-cool-geofire-is-just-awesome-b7f2be5e0f0f#.x78gjws28

Authenticate Firebase with Auth0 using Netlify Lambda Functions

I have a web app built with Gatsby that has client-side authentication through Auth0. I want to use Firebase as a database for my project, but I need to authenticate users first before they can read/write to Firebase.
The Firebase SDK (firebase-admin) has a function called signInWithCustomToken(token) that I thought I could pass the token from Auth0 into, but this doesn't work (see: https://community.auth0.com/t/react-auth0-firebase/11392).
Instead, I need to proxy Auth0's token through an API which will use firebase-admin to issue a token. Because my Gatsby site is hosted on Netlify, I'm planning to use Netlify Lambda Functions to get proxy Auth0's token. This is where I'm getting stuck.
I've followed this tutorial on how to use Netlify Lambda Functions with Gastsby: https://www.gatsbyjs.org/blog/2018-12-17-turning-the-static-dynamic/
I then went into my Auth.js file where my Auth0 code is and dropped a fetch call in the setSession. I passed the idToken from Auth0 into the url in the fetch function. I'm not sure if this is the right thing to do. I've read in the tutorial that it would be passed in an authorization header, but I'm unclear what that means. Anyways, here's the complete auth.js file:
import auth0 from 'auth0-js';
const windowGlobal = typeof window !== 'undefined' && window;
class Auth {
auth0 = new auth0.WebAuth({
domain: process.env.Auth_Domain,
clientID: process.env.Auth_ClientId,
redirectUri: process.env.Auth_Callback,
responseType: 'token id_token',
scope: 'openid profile email',
});
constructor() {
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
this.handleAuthentication = this.handleAuthentication.bind(this);
this.isAuthenticated = this.isAuthenticated.bind(this);
}
login() {
this.auth0.authorize();
}
logout() {
// Remove the locally cached profile to avoid confusing errors.
localStorage.removeItem('access_token');
localStorage.removeItem('id_token');
localStorage.removeItem('expires_at');
localStorage.removeItem('user');
windowGlobal.window.location.replace(`https://login.skillthrive.com/v2/logout/?returnTo=http%3A%2F%2Flocalhost:8000`)
}
handleAuthentication() {
if (typeof window !== 'undefined') {
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult)
} else if (err) {
console.log(err);
}
});
}
}
isAuthenticated() {
const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
return new Date().getTime() < expiresAt;
}
setSession(authResult) {
const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
localStorage.setItem('access_token', authResult.accessToken);
localStorage.setItem('id_token', authResult.idToken);
localStorage.setItem('expires_at', expiresAt);
fetch(`/.netlify/functions/firebase?id=${authResult.idToken}`)
.then(response => console.log(response))
this.auth0.client.userInfo(authResult.accessToken, (err, user) => {
localStorage.setItem('user', JSON.stringify(user));
})
}
getUser() {
if (localStorage.getItem('user')) {
return JSON.parse(localStorage.getItem('user'));
}
}
getUserName() {
if (this.getUser()) {
return this.getUser().name;
}
}
}
export default Auth;
I found a tutorial called How to Authenticate Firebase and Angular with Auth0 that has a function that mints a token for Firebase:
const jwt = require('express-jwt');
const jwks = require('jwks-rsa');
const firebaseAdmin = require('firebase-admin');
// Config
const config = require('./config');
module.exports = function(app) {
// Auth0 athentication middleware
const jwtCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${config.AUTH0_DOMAIN}/.well-known/jwks.json`
}),
audience: config.AUTH0_API_AUDIENCE,
issuer: `https://${config.AUTH0_DOMAIN}/`,
algorithm: 'RS256'
});
// Initialize Firebase Admin with service account
const serviceAccount = require(config.FIREBASE_KEY);
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(serviceAccount),
databaseURL: config.FIREBASE_DB
});
app.get('/auth/firebase', jwtCheck, (req, res) => {
// Create UID from authenticated Auth0 user
const uid = req.user.sub;
// Mint token using Firebase Admin SDK
firebaseAdmin.auth().createCustomToken(uid)
.then(customToken =>
// Response must be an object or Firebase errors
res.json({firebaseToken: customToken})
)
.catch(err =>
res.status(500).send({
message: 'Something went wrong acquiring a Firebase token.',
error: err
})
);
});
I tried to incorporate small parts at a time into my Lambda function:
var admin = require("firebase-admin");
const jwt = require('express-jwt');
const jwks = require('jwks-rsa');
// For more info, check https://www.netlify.com/docs/functions/#javascript-lambda-functions
export function handler(event, context, callback) {
console.log("queryStringParameters", event.queryStringParameters);
const jwtCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${process.env.Auth_Domain}/.well-known/jwks.json`
}),
audience: process.env.Auth_Audience,
issuer: `https://${process.env.Auth_Domain}/`,
algorithm: 'RS256'
});
callback(null, {
// return null to show no errors
statusCode: 200, // http status code
body: JSON.stringify({
msg: "Hello, World! " + Math.round(Math.random() * 10),
}),
})
}
I tried checking to see what came back for jwtCheck by console logging it, but all I got was something weird { [Function: d] unless: [Function], UnauthorizedError: [Function: r] }
How should I go about incorporating this into my Lambda function?
I found a module called serverless-http that allows me to write Lambda Function as if it were written in Express. This made it easy for me to wrap my head around what was happening, so I finally got this code to return the new minted token from Firebase:
const express = require('express');
const serverless = require('serverless-http');
const cors = require('cors');
const jwt = require('express-jwt');
const jwks = require('jwks-rsa');
const firebaseAdmin = require('firebase-admin');
const app = express();
app.use(cors());
const jwtCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${process.env.Auth_Domain}/.well-known/jwks.json`
}),
audience: `${process.env.Auth_ClientId}`,
issuer: `${process.env.Auth_Domain}`,
algorithm: 'RS256'
});
const serviceAccount = require('../firebase/firebase-keys.json');
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(serviceAccount),
databaseURL: `https://${serviceAccount.project_id}.firebaseio.com`
});
// GET object containing Firebase custom token
app.get('/firebase', jwtCheck, async (req, res) => {
const {sub: uid} = req.user;
try {
const firebaseToken = await firebaseAdmin.auth().createCustomToken(uid);
res.json({firebaseToken});
} catch (err) {
res.status(500).send({
message: 'Something went wrong acquiring a Firebase token.',
error: err
});
}
});
module.exports.handler = serverless(app);
Then on the client side I wrapped the fetch call into a function like this and used it when needed:
async setFirebaseCustomToken() {
const response = await fetch('/.netlify/functions/firebase', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('id_token')}`,
},
});
const data = await response.json();
console.log(data.firebaseToken);
}
This code is just going to console.log the new token, but now you'll have the response to do what you want with in Firebase client-side. Hope this helps!

I can't replace the sk_test key with the sk_live key on Stripe using Firebase cloud functions

I have a React Native application, running on a firebase backend. I have integrated with Stripe. The token is created by the client, and the firebase cloud function creates the charge with that token. I have built the app and tested payments using the test keys in Stripe.
I have now replaced the test keys with the live keys.
The live public key is working in the React Native application, and is creating a token successfully.
here is the function for creating the token code in the React Native application
import Stripe from 'react-native-stripe-api';
async payByCard() {
const { user } = this.props;
const uid = user.uid;
const { number, exp_month, exp_year, cvc } = this.state;
this.setState({ loading: true });
const apiKey = 'pk_live_#######################';
const client = new Stripe(apiKey);
try {
const token = await client.createToken({
number,
exp_month,
exp_year,
cvc,
});
this.props.addToken({ token }, uid);
} catch (error) {
this.setState({ error: error.message, loading: false });
}
}
The firebase cloud functions, however, is still using the secret test key.
here is the loud function for creating a charge.
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')(functions.config().stripe.testkey)
export const stripeCharge = functions.database
.ref('/payments/{userUid}/{paymentUid}')
.onWrite((change, context) => {
const payment = change.after.val();
const userUid = context.params.userUid;
const paymentUid = context.params.paymentUid;
if (!payment || payment.charge || !payment.pendingBasket) return;
return admin.database()
.ref(`/users/${userUid}`)
.once('value')
.then(snapshot => {
return snapshot.val();
})
.then(customer => {
const amount = Number(payment.pendingBasket.total * 100).toFixed(0)
const idempotency_key = paymentUid;
const source = payment.token.id;
const currency = 'gbp';
const description = `Athalens ${customer.address.FirstName} ${customer.address.LastName} - ${customer.address.PostCode}`
const charge = {amount, currency, description, source};
return stripe.charges.create(charge, { idempotency_key });
}).catch((error) => {
console.log('error 1 =' + error.message);
admin.database()
.ref(`/payments/${userUid}/${paymentUid}/status`)
.set(error.message)
})
.then(charge => {
admin.database()
.ref(`/payments/${userUid}/${paymentUid}/charge`)
.set(charge)
if (charge.status === "succeeded") {
customerOrders(userUid, paymentUid)
photographerUid(userUid, paymentUid)
clearBasket(userUid)
confirmation(userUid, paymentUid);
} else {
decline(userUid, paymentUid)
}
}).catch((error) => {
console.log('error 2 =' + error.message);
})
})
The process I am doing to upload the Secret key to firebase:
1. Williams-MBP:~ williamgoodhew$ cd /Users/williamgoodhew/projects/athalens/athalens_server_code/basket/functions
2. Williams-MBP:functions williamgoodhew$ firebase functions:config:set stripe.token=“sk_live_#################”
3. Williams-MBP:functions williamgoodhew$ firebase deploy --only functions
When I test the live payment system, a token is created, but no charge is created. and I receive the following error in the cloud functions log:
No such token: tok_############; a similar object exists in live mode, but a test mode key was used to make this request.
I have got in contact with Firebase and it was a silly error my end.
In my cloud function, I had initialized my test key "
const stripe = require('stripe')(functions.config().stripe.testkey)
" instead of using "stripe.token".
I changed stripe.testkey to stripe.token.and everything worked out fine.

Resources