Reactjs Stripe payment not working with Node/Express - javascript

I am using stripe for the first time in my react project.
I have set up a payment with the test api keys. I have been using Javascript and React for a few years now but I have no-to-little knowledge of node.js / express. So I used a boiler plate from sandbox for the backend, which you can see below;
const cors = require("cors");
const express = require("express");
const stripe = require("stripe")("MY TEST KEY HAS BEEN INSERTED HERE");
const uuid = require("uuid/v4");
const app = express();
app.use(express.json());
app.use(cors());
app.get("/", (req, res) => {
res.send("Add your Stripe Secret Key to the .require('stripe') statement!");
});
app.post("/checkout", async (req, res) => {
console.log("Request:", req.body);
let error;
let status;
try {
const { product, token } = req.body;
const customer = await stripe.customers.create({
email: token.email,
source: token.id
});
const idempotency_key = uuid();
const charge = await stripe.charges.create(
{
amount: 8.00,
currency: "gbp",
customer: customer.id,
receipt_email: token.email,
description: `Purchased the ${product.name}`,
name: token.card.name,
},
{
idempotency_key
}
);
console.log("Charge:", { charge });
status = "success";
} catch (error) {
console.error("Error:", error);
status = "failure";
}
res.json({ error, status });
});
app.listen(8080);
And here is the function within react js which handles the even once the user has entered the card number etc.
async function handleToken(token){
let whichClass = {
name: "Bodytone",
price: 8.00
}
const response = await axios.post("https://s9mh5.sse.codesandbox.io/checkout", {token, whichClass});
const { status } = response.data;
if (status === "success") {
alert("Successful payment");
} else {
alert("failed payment");
}
}
What I am finding is that when I click to send the payment after entering details, I see the green tick to suggest everything went smoothly. But within the stripe dashboard for this account, nothing shows as a result of the test payment.
In the console I see two different error messages logged, which are detailed below:
StripeCheckout.open: Either 'token' or 'source' is a required option, but neither was found.
You can learn about the available configuration options in the Checkout docs:
https://stripe.com/docs/checkout 0.chunk.js:108728:20
As well as.....
**Uncaught TypeError: this.fn is not a function
trigger https://checkout.stripe.com/checkout.js:3
bind https://checkout.stripe.com/checkout.js:3
onToken https://checkout.stripe.com/checkout.js:3
closed https://checkout.stripe.com/checkout.js:3
bind https://checkout.stripe.com/checkout.js:3
processMessage https://checkout.stripe.com/checkout.js:2
bind https://checkout.stripe.com/checkout.js:2
message https://checkout.stripe.com/checkout.js:2
RPC https://checkout.stripe.com/checkout.js:2
checkout.js:3:24013**
Is anyone able to spot the mistake / error?
Your help and feedback are greatly appreciated!!

You should look at using the new Checkout rather than the legacy one you're using now, or possibly Elements to build your own form, for which you could use https://github.com/stripe/react-stripe-js.
That said, you didn't provide enough client-side code to diagnose the issue.

Related

Show user invoices for simultaneously logged in users using Expressjs

I have created a simple invoice application using the MERN stack. The application is great at handling data for the logged in user as long as one user is logged in, but if another user logs in then the invoices for the user that first logged in is shown.
I am currently using app.set() and app.get() to pass data between endpoints and send to my frontend client. Auth0 handles the authentication layer, but would express-session solve this issue? And if it is how would I go about implementing this? Or is there a better solution?
Below is the code that sends the invoices currently to the frontend:
var express = require('express');
var app = express();
var userInvoices = express.Router();
const axios = require('axios');
const InvoiceModel = require('../models/Invoice');
const UserModel = require('../models/User');
//Functions//
async function clientCall() {
const url = `${process.env.REACT_APP_SAVE_USER}`;
const axiosConfig = {
method: 'get',
url
};
await axios(axiosConfig).catch((e) => {console.log(e)});
};
async function fetchUsersFromDB() {
const usersFromDB = await UserModel.find().catch((e) => {console.log(e)});
return usersFromDB;
};
async function saveUser(User) {
const condition = {email: User.email};
const query = {
nickname: User.nickname,
name: User.name,
picture: User.picture,
email: User.email,
email_verified: User.email_verified,
sub: User.sub,
};
const options = { upsert: true };
const update = await UserModel.updateMany(condition, query, options).catch((e) => {console.log(e)});
// Log the amount of documents that where updated in the DB.
if(update.nModified > 0 ) {
console.log('Number of Users added or updated to DB:', update.nModified)
}
};
function findCommonUser(dbUser, User) {
if(dbUser.length <= 0) {
UserModel.create(User, () => {console.log('Users saved to database')});
console.log('An Error has occured with Database')
} else {
dbUser.forEach((DBElement) => {
if(User.email !== DBElement.email) {
saveUser(User);
}
})
}
console.log(' Users match')
};
function matchUserAndInvoice(dbInvoices, loggedInUser) {
let newArray = [];
dbInvoices.forEach(async (DBElement) => {
if(DBElement.customer_email === loggedInUser.email){
newArray.push(DBElement);
app.set('userInvoices', newArray);
}
})
}
// prevents user from having to refresh to get data.
clientCall();
userInvoices.post('/saveUser', async (req, res) => {
try {
const User = req.body;
const usersFromDB = await fetchUsersFromDB().catch((e) => {console.log(e)});
findCommonUser(usersFromDB, User);
app.set('Users', User)
} catch (error) {
console.log(error)
}
})
userInvoices.get('/fetchUserInvoices', async (req,res) => {
try {
const invoices = await InvoiceModel.find().catch((e) => {console.log(e)});
const user = await app.get('Users');
await matchUserAndInvoice(invoices,user);
const userInvoices = await app.get('userInvoices')
res.json(userInvoices);
} catch (error) {
console.log(error)
}
});
;
module.exports = userInvoices;
app.get() is essentially global to your server instance so putting data there to use between requests for individual users will (as you have discovered) get data confused between different users as all users are trying to store data in the same place.
The usual way to solve a problem like this is to use express-session. This cookies the end-user's connection the first time they connect to your server and then creates a server-side object that is automatically associated with that cookie. Then, inside of any request handler, you can read or set data in req.session and that data will uniquely belong to just that user.
If the user changes devices or clears their cookies, then the association with the session object for that user will be lost (creating a new session object for them upon their next connection). But, if you have a persistent data store and you use some sort of login that allows you to populate the session from the user's persistent store, you can even make the session persistent across devices for the same user (though often times this is not required).
In the specific application you describe in your question (tracking invoices), it seems like your invoice data should be tagged with a specific user when it is stored in your database and then any future requests to display invoices should query based on the particular user that is making the request. This requires a login and login cookie so that you can uniquely identify each user and thus show them only the invoices that pertain to them.
The above-described session object should only be used for temporal session state, not for persistent storage such as invoices. If your server implementation is truly RESTFUL, you may not even need any data stored in the session object other than user's logged in userID.

StripeInvalidRequestError: You must provide an account with capabilities when creating an account link of type "account_onboarding"

I am stuck with this error on stripe. I am trying to create a login Link for users. I have tried selecting United Kingdom under 'Select where to onboard accounts' on the stripe settings page but still get the same error.
Full error code:
StripeInvalidRequestError: You must provide an account with capabilities when creating an account link of type "account_onboarding" for an account in a country that is not enabled for Express. You can enable GB in your Country and
Capabilities settings at https://dashboard.stripe.com/settings/applications/express
here's my code.
const User = require('../models/User');
const Stripe = require('stripe');
const queryString = require('query-string');
const stripe = Stripe(process.env.STRIPE_SECRET);
exports.createConnectAccount = async (req, res) => {
const user = await User.findById(req.user._id).exec();
try {
if (!user.stripe_account_id) {
const account = await stripe.accounts.create({
type: 'express',
});
user.stripe_account_id = account.id;
user.save();
}
let accountLink = await stripe.accountLinks.create({
account: user.stripe_account_id,
refresh_url: process.env.STRIPE_REDIRECT_URL,
return_url: process.env.STRIPE_REDIRECT_URL,
type: 'account_onboarding',
});
accountLink = Object.assign(accountLink, {
'stripe_user[email': user.email || undefined,
});
console.log(accountLink);
} catch (err) {
console.log(err);
}
};
The error indicates that the Stripe account which you are creating an account link for does not have any requested capabilities. What you can do is to request for the capabilities [0] when creating the Stripe account :
const account = await stripe.accounts.create({
type: 'express',
capabilities: {
card_payments: {requested: true},
transfers: {requested: true},
},
});
You would probably want to write in to Stripe support who will be able to better guide you on the settings required in your Dashboard so that the capabilities are automatically requested when creating new Express accounts : https://support.stripe.com/contact
[0] https://stripe.com/docs/api/accounts/create#create_account-capabilities

An error occured: Cannot read property 'stripeId' of null

i changed my stripe and firebase api keys and everything back to test mode for some testing purposes. However, I was unpleasantly presented with this error in my console when trying to redirect to checkout using the firebase stripe api:
An error occured: Cannot read property 'stripeId' of null
however, no where in my code does stripeID exist. interesting. here's the code. oh and keep in mind this only happens in test mode.
var userID = "";
firebase.auth().onAuthStateChanged((user) => {
if(user) {
userID = user.uid
console.log(user.uid)
}
});
export async function createCheckoutSession(activtyStatus){
var price = 'price_1Iav0JKDPaWWeL1yBa9F7Aht'
if (activtyStatus == "canceled") {
// test price
price = 'price_1IelOCKDPaWWeL1ynps36jkc'
}
const checkoutSessionRef = firestore
.collection('customers')
.doc(userID)
.collection('checkout_sessions')
.add({
price: price,
success_url: "https://app.x.com/successPage",
cancel_url: "https://app.x.com/signin",
});
// Wait for the CheckoutSession to get attached by the extension
(await checkoutSessionRef).onSnapshot(function (snap) {
const { error, sessionId } = snap.data();
if (error) {
// Show an error to your customer and
// inspect your Cloud Function logs in the Firebase console.
console.log(`An error occured: ${error.message}`);
}
if (sessionId) {
// We have a session, let's redirect to Checkout
// Init Stripe
const stripe = window.Stripe('pk_test_C');
console.log("going to stripe: ")
stripe.redirectToCheckout({sessionId})
console.log("logged stripe")
}
});
}
export async function goToBilliingPortal(){
var finalRoute = "https://app.x.com/profile"
const functionRef = app
.functions('us-central1')
.httpsCallable('ext-firestore-stripe-subscriptions-createPortalLink');
const {data} = await functionRef({returnUrl : finalRoute});
window.location.assign(data.url);
};
does anyone have any ideas?
Have you checked what the logs of your Stripe dashboard or Firebase Functions say?
I faced similar issue, and looking at the logs it was because of a failure of the createCheckoutSession function. I was having the following message in my Firebase logs:
ext-firestore-stripe-subscriptions-createCheckoutSession
❗️[Error]: Failed to create customer for [XXXXXXXXXXXXXXXXXXXXXX]: This API
call cannot be made with a publishable API key. Please use a secret
API key. You can find a list of your API keys at
https://dashboard.stripe.com/account/apikeys.
I simply had to replace the key in "Stripe API key with restricted access" in Firebase console with a restricted key and it was solved.
I had the same issue, for me it was that my card was declined by google clouds since my card eas expired. I updated my account with another card and no more issues.

TypeError: Cannot set property 'user' of undefined<br> at [file path] sqlite3

I am receiving an undefined error when attempting to set a session for the user upon validation of credentials on login. I am trying to use express-session to create the session for the user but do not have it directly imported into the file (I was guided to not do so) but am unsure how to resolve this error given. Any help and insight would be much appreciated!
End point:
router.post("/login", async (req, res, next) => {
try {
const { username, password } = req.body
// * checks for record existence in db, assigns record to var for access
const user = await users_access.findByFilter({ username })
if (!user) {
return res.status(401).json({ message: 'invalid crededentials' });
}
// * compares the entered password to the hash on record
const passwordValid = await secure.compare(password, user.password)
// * handling invalid responses + creating session
if (!passwordValid) {
return res.status(401).json({ message: 'invalid credentials' });
}
req.session.user = user
res.json({ message: `welcome, ${user.username}!`})
} catch(error) {
next(error)
}
});
application model:
// * add users to the datbase
// * inserts argument into user table in db access
// * returns a user found by id
const add = async (user) => {
const [id] = await database_access("users")
.insert(user)
return findById(id)
}
// * find user record with username and password
const find = () => {
return database_access("users").select("id", "username")
}
// * find user by filter
const findByFilter = (filter) => {
return database_access("users")
.select("id", "username", "password")
.where(filter)
.first()
}
// * find user with id
const findById = (id) => {
return database_access("users")
.select("id", "username")
.where({ id }) // desctructuring id from the request
.first()
}
module.exports = {
add,
find,
findByFilter,
findById
}
if you need to see any additional code to assess I am happy to provide but believe this is the source of issue per the error response. Thank you in advanced!
so I guess you are using the express-session module in your entry file for the server, app.js, server.js, index.js however you call it.
this login handler require to be used in a context where the session is available.
I think what you want is not a unit test for this particular router, but an integration test, to test this router in the context of your app.
This is all I can see from the information you provided. If this was not helpful enough, maybe you can show us your servers main file. and how this router is used.

How to fix firebase database initialised multiple times due to React SSR initialised database and cloud function firebase initialised database?

I have updated the question as found the root cause of the issue.
As I have hosted my React SSR app which uses firebase database in the client serving by one of the cloud function named app throwing an error of Error: FIREBASE FATAL ERROR: Database initialized multiple times. Please make sure the format of the database URL matches with each database() call.. When I comment out one by one and deploy, works perfectly. But when I deploy together doesn't work. How do I separate these two keeping both at the same repo?
ORIGINAL Question: Why firebase cloud function throwing an error of 'The default Firebase app does not exist.'?
So I am trying out firebase function for the first time. admin.messaging() throwing me the following error. Help me figure out why?
If I look at the console I get results till console.log('deviceToken', deviceToken);
so whats wrong in const messageDone = await admin.messaging().sendToDevice(deviceToken, payload);?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
exports.updateUnreadCount = functions.database.ref('/chats/{chatId}/{messageId}')
.onCreate(async(snap, context) => {
const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
appOptions.databaseAuthVariableOverride = context.auth;
const adminApp = admin.initializeApp(appOptions, 'app');
const { message, senderId, receiverUid } = snap.val();
console.log(message, senderId, receiverUid);
console.log('------------------------');
const deleteApp = () => adminApp.delete().catch(() => null);
try {
const db = adminApp.database();
const reciverUserRef = await db.ref(`users/${receiverUid}/contacts/${senderId}/`);
console.log('reciverUserRef', reciverUserRef);
const deviceTokenSnapshot = await reciverUserRef.child('deviceToken').once('value');
const deviceToken = await deviceTokenSnapshot.val();
console.log('deviceToken', deviceToken);
const payload = {
notification: {
title: 'Test Notification Title',
body: message,
sound: 'default',
badge: '1'
}
};
const messageDone = await admin.messaging().sendToDevice(deviceToken, payload);
console.log('Successfully sent message: ', JSON.stringify(messageDone));
return deleteApp().then(() => res);
} catch (err) {
console.log('error', err);
return deleteApp().then(() => Promise.reject(err));
}
});
Update1: According to this https://firebase.google.com/docs/cloud-messaging/send-message#send_to_a_topic, admin.messaging().sendToDevice(deviceToken, payload) APIs are only available in the Admin Node.js SDK?
So switched to
const payload = {
data: {
title: 'Test Notification Title',
body: message,
sound: 'default',
badge: '1'
},
token: deviceToken
};
const messageDone = await admin.messaging().send(payload);
Which is not working either. Getting an error Error: The default Firebase app does not exist. Make sure you call initializeApp() before using any of the Firebase services. Any lead will be helpful.
EDIT: Finally got the function working.
My index.js is exporting to functions, follwoing
exports.app = functions.https.onRequest(app); //React SSR
exports.updateChat = functions.database.ref('/chats/{chatId}/{messageId}').onCreate(updateChat);
exports.app is a react ssr function, which I am using to host my site. This uses database too. and throwing error of multiple database instance.
When I comment out one by one and deploy, works perfectly. But when I deploy together doesn't work. How do I separate these two keeping both at the same repo? Any suggestions, please?
You can initialise db outside export function.
const admin = require('firebase-admin');
const adminApp = admin.initializeApp(appOptions, 'app')
//continue code
Update:
const admin = require('firebase-admin');
const adminApp = admin.initializeApp(options);
async function initialize(options, apps = 'app') {
try {
const defaultApp = adminApp.name
if(defaultApp) {
const adminApp1 = admin.initializeApp(apps);
}else {
const adminApp1 = admin.initializeApp(options, apps);
}
}catch(err) {
console.error(err);
}
}
Modify this snippet as per your need and try it out
It abstracts initialize of app in another function. Just call this function at appropriate place in your code.

Categories

Resources