Stripe JS subscription rerouting - next.js

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();
};

Related

Running an async function with React Native and Firestore returns goofy results

I'm trying to learn react native along with firebase firestore and I'm racking my brain trying to figure this situation out. Below is my code. I'm trying to pull user information stored in firebase. The console.log in the function returns the array with all of the values such as Document data: {"admin": true, "email": "bob#bob.com", "first": "Bob", "last": "BobLastName", "phone": "555-555-5555"}, however if I try to console.log the return outside of the function or try to assign it to a variable I'm getting. LOG {"_A": null, "_x": 0, "_y": 0, "_z": null}. Any help would be much appreciated before I chuck my laptop out of the window.
import { Pressable, Text } from 'react-native';
import { getAuth} from 'firebase/auth';
import { doc, getDoc, setDoc, getDocs, collection, query, where, Firestore } from 'firebase/firestore';
import { db } from '../components/FirebaseConfig';
import { useEffect, useId, useState } from 'react';
import { async } from '#firebase/util';
import { FlatList } from 'react-native-gesture-handler';
function HomeScreen() {
//Create an async function that will pull user details from firebase and return them as an object
async function getUserDetails() {
const user = getAuth().currentUser;
const userRef = doc(db, "users", user.uid);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
console.log("Document data:", docSnap.data());
return docSnap.data();
} else {
console.log("No such document!");
}
}
//Call the getUserDetails function and store the returned object in a variable
const userDetails = getUserDetails();
//log userdetails to the console
console.log(userDetails);
//getUserDetails()
return <Text>Welcome HomeScreen</Text>;
}
export default HomeScreen
I've tried to use the above code to get the data to work. It seems like I am not extracting the data properly.
You are getting this kind of behavior because you are calling getUserDetails() functions directly but it should be called with await or used with .then() because it is returning a Promise<DocumentData> refer this
Basically it will return a promise that will eventually resolve with the data.
To Solve this you can either use
const userDetails = await getUserDetails();
console.log(userDetails);
OR
getUserDetails().then((data) => {
console.log(data);
});
Although using useEffect here is also not a bad option it will look something like this:
function HomeScreen() {
const [userDetails, setUserDetails] = useState(null);
useEffect(() => {
async function getUserDetails() {
const user = getAuth().currentUser;
const userRef = doc(db, "users", user.uid);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
const data = docSnap.data();
console.log("Document data:", data);
setUserDetails(data);
} else {
console.log("No such document!");
}
}
getUserDetails();
}, []);
if (!userDetails) {
return <Text>Loading...</Text>;
}
return <Text>Welcome {userDetails.first}!</Text>;
}
Refer getDoc

Send auth context to firebase callable function in unittest

I have been working on a firebase project in which I created a cloud function that creates documents in firestore. This is the function -
export const createExpenseCategory = functions
.region("europe-west1")
.https.onCall(async (data, context) => { // data is a string
if (!context.auth?.uid) { // check that requesting user is authenticated
throw new functions.https.HttpsError(
"unauthenticated",
"Not Authenticated"
);
}
const res = await admin
.firestore()
.collection("/categories/")
.where("uid", "==", context.auth.uid)
.get();
const categoryExists = res.docs.find((doc) => doc.data().name === data); // check that there are not duplicates.
// doc looks like this -
// {
// "name": "Food",
// "uid": "some_long_uid"
// }
if (categoryExists) {
throw new functions.https.HttpsError(
"already-exists",
`Category ${data} already exists`
);
}
return admin
.firestore()
.collection("/categories/")
.add({ name: data, uid: context.auth.uid });
});
As you can see, at the beginning of the function I check whether the user that sent the request is authenticated with the context parameter. Everything works fine when I play around with it in my web app, but I have been trying to figure out a way to create a unittest for this function. My problem is that I can't really figure out how to create an authenticated request to make sure that my function doesn't fail every time. I tried to look online for any documentation but couldn't seem to find any.
Thanks in advance!
You can unit test your functions using the firebase-functions-test SDK. The guide mentions you can mock the data within the eventContext or context parameter passed to your function. This works for mocking the uid field of the auth object:
// Left out authType as it's only for RTDB
wrapped(data, {
auth: {
uid: 'jckS2Q0'
}
});
The guide uses mocha for testing, but you can use other testing frameworks. I made a simple test to see if it would work and I could send the mock uid to the function, which worked as expected:
index.js
exports.authTest = functions.https.onCall( async (data, context) => {
if(!context.auth.uid){
throw new functions.https.HttpsError('unauthenticated', 'Missing Authentication');
}
const q = await admin.firestore().collection('users').where('uid', '==', context.auth.uid).get();
const userDoc = q.docs.find(doc => doc.data().uid == context.auth.uid);
return admin.firestore().collection('users').doc(userDoc.id).update({name: data.name});
});
index.test.js
const test = require('firebase-functions-test')({
projectId: PROJECT_ID
}, SERVICE_ACCTKEY); //Path to service account file
const admin = require('firebase-admin');
describe('Cloud Functions Test', () => {
let myFunction;
before(() => {
myFunction = require('../index.js');
});
describe('AuthTest', () => {
it('Should update user name in UID document', () => {
const wrapped = test.wrap(myFunction.authTest);
const data = {
name: 'FooBar'
}
const context = {
auth: {
uid: "jckS2Q0" //Mocked uid value
}
}
return wrapped(data, context).then(async () => {
//Asserts that the document is updated with expected value, fetches it after update
const q = await admin.firestore().collection('users').where('uid', '==', context.auth.uid).get();
const userDoc = q.docs.find(doc => doc.data().uid == context.auth.uid);
assert.equal(userDoc.data().name, 'FooBar');
});
});
});
});
Let me know if this was useful.

Unable to send data from Stripe Webhook to Firebase Firestore

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,
},
}; `

Firebase Firestore - Displaying Queried Data in React Native

I require some help that I couldn't find easily in the documentation.
So I've gotten my head around how to create a document in firebase on signup and setting the doc's ID to the current users uid. Now I want to reference the current user's doc and use its data throughout screens.
This is the function I use to retrieve the data:
const user = auth.currentUser;
const GettingUserData = async() => {
const userData = firestore.collection('users').doc(user.uid);
const doc = await userData.get();
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('User data:', doc.data());
}
}
how would I go about using the data inside of doc.data() in something like <Text>{data.displayName}</Text>
Help or a link to read through would be greatly appreciated!
To manage remote data, you need a state to store that information.
If you are using a functional component:
const [userData, setUserData] = React.useState(null)
const user = auth.currentUser;
const GettingUserData = async() => {
const userData = firestore.collection('users').doc(user.uid);
const doc = await userData.get();
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('User data:', doc.data());
setUserData(doc.data)
}
}
return (<View><Text>{userData?.name}</Text></View>)
if you are using class components:
const user = auth.currentUser;
const GettingUserData = async() => {
const userData = firestore.collection('users').doc(user.uid);
const doc = await userData.get();
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('User data:', doc.data());
this.setState({userData: doc.data()})
}
}
return (<View><Text>{this.state.userData?.name}</Text></View>)

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