I saw some other answers and I tried some of those unsuccessfully.
In my case, I created an iOS app and integrated Stripe payment method, so it reaches my javascript function at cloud functions. I'm actually able to see the payments I realizes within my stripe account but I couldn't manage to save it into our firestore database.
this is my setup at Google side:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const express = require('express');
const cors = require('cors')({origin: true});
const app = express();
const stripe = require('stripe')(functions.config().stripe.token);
//I've preset this data to firebase functions middleware
function charge(req, res) {
const body = (req.body);
const userId = body.userId;
const token = body.token;
const amount = body.amount;
const currency = body.currency;
// Charge card
stripe.charges.create({
amount,
currency,
description: 'Firebase Example',
source: token,
}).then(charge => {
send(res, 200, {
// I WANNA RECORD DATA INTO MY DATABASE HERE!!
message: 'Success',
charge,
});
}).catch(err => {
console.log(err);
send(res, 500, {
error: err.message,
});
});
}
function send(res, code, body) {
res.send({
statusCode: code,
body: JSON.stringify(body),
});
}
app.use(cors);
app.post('/', (req, res) => {
// Catch any unexpected errors to prevent crashing
try {
charge(req, res);
} catch(e) {
console.log(e);
send(res, 500, {
error: `The server received an unexpected error. Please
try again and contact the site admin if the error persists.`,
});
}
});
exports.charge = functions.https.onRequest(app);
And this is our database setup where I want to save like this:
- into payments > userId... I'll save each transaction this userId does with the fields: "token", "currency" and "amount".
Note: I already have all this values in my function and also have userId.
enter image description here
Related
I need to fetch the user token from the firestore in a cloud function.
the user token was stored as follows:
void saveToken(String token) async {
await FirebaseFirestore.instance
.collection("User tokens")
.doc(userId)
.set({'token': token});
}
here is the goal.
When a message is created on the collection 'chat messages',
grab the "Chat id" value and the user who sends the message "User id".
query the collection "chat" using the "Chat id" value,
grab the "Job users data" value (this is an array with two objects, each object contains the users involved in the chat (userName,userId) ).
from the "Job users data", I need to grab the userId of the member who should be receiving the message.
query "User tokens" collection to grab the "token" value.
use the "token" value, to send a notification to
here is my cloud function:
as you see, I have hardcoded the token to see if I could send that device a notification.... that works perfect. now I need to to make this dynamic...
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const { database } = require("firebase-admin");
// eslint-disable-next-line max-len
const tokens = ["JNKDNASNDAUIU324234....."];
admin.initializeApp();
// exports.onCreate = functions.firestore
// .document("chat/{docId}")
// .onCreate((snapshot, context) => {
// console.log(snapshot.data());
// console.log("fake data");
// });
exports.onChatMessageCreate = functions.firestore
.document("chat messages/{docId}")
.onCreate( (snapshot, context) => {
console.log(snapshot.data());
// fetch user to send message to
// admin.database().ref("/")
const payload = {
// eslint-disable-next-line max-len
notification: {title: snapshot.data()["userName"], body: snapshot.data()["Chat message"], sound: "default"},
// eslint-disable-next-line max-len
data: {click_action: "FLUTTER_NOTIFICATION_CLICK", message: "Sample Push Message"},
};
try {
admin.messaging().sendToDevice(tokens, payload);
console.log("NOTIFICATION SEND SUCCESSFULLY");
} catch (e) {
console.log("ERROR SENDING NOTIFICATION");
console.log(e);
}
});
So all i need to know is how to query collections from a cloud function
There are 2 ways to query a collection in Node.js. either through then() or async/await.
to query using promise:
const collectionRef = admin.firestore().collection("yourCollection");
return collectionRef.get().then((collections) => {
//you can now use your collections here
});
using async/await:
const collectionRef = admin.firestore().collection("yourCollection");
const collections = await collectionRef.get();
In my app's main.dart, I ran the following code:
final fcmToken = await FirebaseMessaging.instance.getToken();
I took the token and used it in my cloud function:
exports.notifyUserAddedToGroup = functions.firestore
.document("groups/{groupDocID}/groupMembers/{groupMembersDocID}")
.onWrite((change, context) => {
const FCMToken = `loooooooooooooooooooooooooooooooong
fcmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
tokennnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn`;
const payload = {
token: FCMToken,
notification: {
title: "Title",
body: "Body",
},
data: {
body: "data body",
},
};
admin.messaging().send(payload)
.then((response) => {
console.info("##MyApp## function executed successfully");
return {msg: "##MyApp## function executed succesfully"};
})
.catch((error) => {
console.info("##MyApp## error in execution");
console.log(error);
return {msg: "##MyApp## error in execution"};
});
});
I then went to Firestore and added a document into the correct collection to trigger the cloud function. When I went to the google cloud console logs, I saw the following error:
The registration token is not a valid FCM registration token
Why is my token invalid if I just generated it a few minutes before triggering the cloud function?
The problem was the hard coded FCM token in the function. I put the token in Firestore instead and queried it to use it in the function and it worked.
I'm using Firebase functions for creating seller account but I don't know how to create seller account and what to put in the redirect_url
I followed some tutorials and wrote the below code
Let me know what changes should I do to open seller account registration with url_launcher
Thanks
const stripeAccount = functions.https.onRequest(async (req, res) => {
const { method } = req
if (method === "GET") {
// CREATE CONNECTED ACCOUNT
const { mobile } = req.query
const account = await stripe.accounts.create({
type: "express",
})
const accountLinks = await stripe.accountLinks.create({
account: account.id,
refresh_url:, <-- What to put here
return_url:, <-- What to put here
type: "account_onboarding",
})
if (mobile) {
// In case of request generated from the flutter app, return a json response
res.status(200).json({ success: true, url: accountLinks.url })
} else {
// In case of request generated from the web app, redirect
res.redirect(accountLinks.url)
}
} else if (method === "DELETE") {
// Delete the Connected Account having provided ID
const {
query: { id },
} = req
console.log(id)
const deleted = await stripe.accounts.del(id)
res.status(200).json({ message: "account deleted successfully", deleted })
} else if (method === "POST") {
// Retrieve the Connected Account for the provided ID
// I know it shouldn't be a POST call. Don't judge :D I had a lot on my plate
const account = await stripe.accounts.retrieve(req.query.id)
res.status(200).json({ account })
}
const stripeReAuth = async (req, res) => {
const { account_id: accountId } = req.query
const accountLinks = await stripe.accountLinks.create({
account: accountId,
refresh_url: <-- Here
return_url: , <-- Here
type: "account_onboarding",
})
res.redirect(accountLinks.url)
}
})
This is my flutter code, I'm retrieving the return_url and launching it with url_launcher
class StripeBackendService {
static String apiBase = '{function address}/stripeAccount';
static String createAccountUrl =
'$apiBase/account?mobile=true';
static String checkoutSessionUrl =
'${StripeBackendService.apiBase}/checkout-session?mobile=true';
static Map<String, String> headers = {'Content-Type': 'application/json'};
void createSellerAccount() async {
var url = Uri.parse(StripeBackendService.createAccountUrl);
var response = await http.get(url, headers: StripeBackendService.headers);
Map<String, dynamic> body = jsonDecode(response.body.toString());
await canLaunch(body['url']) ? await launch(body['url']) : throw 'Error'
}
}
The refresh url should point to an address that retries the creation of the stripe connect account, in case your current http function returns an expired link. The return url is the address that the potential stripe connect user gets sent to after the stripe onboarding is complete. In knowing that address you can use the webview controller to jump back to the app when reaching that return-url endpoint.
I use Postman and the ReactJS UI to call this registration execution and it works as I expect. Ironically, the Jest and Supertest integration tests do not produce expected results. When integration testing, the Profile is created and the User is not.
The architecture is pretty simple. MongoDB in a Docker container, and Node using nodemon in VSCode.
I have to be doing something wrong, I just can't spot what it is.
// The Integration Test __test__/users/../user.test.js
const app = require('../../app');
const uuidv4 = require('uuid/v4');
const User = require('../../src/models/User');
const Profile = require('../../src/models/Profile');
const bcrypt = require('bcryptjs');
const mongoose = require('mongoose');
const request = require("supertest");
const {
MONGO_URI,
TEST_DB_NAME
} = process.env;
let DB_URI = MONGO_URI + TEST_DB_NAME;
let NAME = TEST_DB_NAME;
mongoose.connect(DB_URI, {
useNewUrlParser: true,
useCreateIndex: true,
dbName: NAME
});
describe('User Integration Test', () => {
// make sure app is imported without issues
it('Has App Defined', () => {
expect(app).toBeDefined();
});
let server;
beforeAll(async () => {
// Clear Test Data
await User.deleteMany({});
await Profile.deleteMany({});
server = await app.listen(3001);
});
afterAll(async (done) => {
// Clear Test Data
await User.deleteMany({});
await Profile.deleteMany({});
// Close server
await server.close(done);
});
describe('User route tests', () => {
it('Can Register a User', async () => {
const body = {
"username": "User21",
"email": "user21#user.com",
"password": "123456",
"avatar": "image.jpg"
}
await request(server)
.post('/api/v1/users')
.send(body)
.set('Accept', 'application/json')
.set('Content-Type', 'application/json')
.expect(200)
});
});
// THE EXPRESS ROUTE in api/v1/users.js
const express = require('express');
const auth = require('../../middleware/auth');
const router = express.Router();
const { UserService } = require('../../services');
const {
check,
validationResult
} = require('express-validator/check');
// #route POST api/users
// #desc Register User
// #access Public
// #return status message
router.post('/', [
check('email', 'Please provide a valid email address').isEmail(),
check('password', 'Please enter a password with 6 or more characters').isLength({ min: 6 }),
check('username', 'Username is Required.').not().isEmpty()
], async (req, res, next) => {
try {
//--Validate
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
const message = await UserService.register(req.body);
return res.status(200).json(message)
} catch (err) {
next(err);
}
});
// THE register METHOD found in ../../services/UserService.js
const register = async (data) => {
try {
// Destructure the data
const {
username,
email,
password,
avatar
} = data;
// remove spaces from username and lcase it
let user_name = username.replace(/\s/g, '').toLowerCase();
// Check if the username or email already exists
await doesUserExist(user_name, email);
// Create a new user
const token = uuidv4();
user = new User({
email: email.toLowerCase(),
username: user_name,
avatar: avatar,
verifyEmailToken: token
});
// encrypt the password
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
// Save the user
// (Works Unless Running Jest Integration Tests)
await user.save();
// Create and save an empty Profile for the new user
profile = new Profile();
profile.user = user;
// (Always Works)
await profile.save();
// Send verification email
await send(user, 'Verify Your Email', token, 'verify-email.html');
return { message: 'User was registered successfully.' };
} catch (err) {
throw err;
}
}
// Does user exist method found in ./UserService.js
const doesUserExist = async (username, email) => {
// Check if user exists by email
let message = await checkEmail(email);
if (!message.email_available) {
throw new Error('Email already exists');
}
// Check if user exists by username
message = await checkUserName(username.toLowerCase())
if (!message.username_available) {
throw new Error('Username already exists');
}
return false;
}
When I call this code via the UI, Postman, or curl both the User and Profile are created, as expected.
When I run the Integration Test, npm run test:integration or npm test,
Only the Profile is created.
my package.json scripts:
"test": "jest",
"test:integration": "jest --testPathPattern integration.test",
Finally, no errors are reported anywhere. User simply isn't created.
After a few hours of work and testing this issue I found that the afterAll() event was raise randomly. Sometimes after all the tests in the inner describe() ran and sometimes not. Of course, when afterAll() ran all Users where deleted from the data store.
If moved beforeAll() and AfterAll() to the inner describe(). This worked very well until I included other integration tests, like authorization, for example that also used the User table. I noticed that the test suites are not executed synchronously. As one test suit was blowing out the data in another test suite as the other test suite was executing.
I now set up a database per test suite. Clunky, wonky, hacky and wrong I know but I have to have move on. Does anyone know if you can control the synchronous and/or asynchronous behavior of Jest? Please don't suggest Mocha and/or Chai.
I ran into a similar issue, where there appeared to be an intermittent race condition between seeding a document and then retrieving it.
I fixed it by running jest with the --runInBand flag.
How to create one route for receiving non graphql post requests?
I have my graphql server, and want to receive some non graphql data on it.
const server = new GraphQLServer({ ... })
server.express.get('/route', async (req, res, done) => {
const params = req.body;
// do some actions with ctx..
})
How can we access to ctx.db.query or ctx.db.mutation from this route?
Thanks!
Related question: https://github.com/prisma/graphql-yoga/issues/482
https://www.prisma.io/forum/t/how-to-create-one-route-for-receiving-rest-api-post-requests/7239
You can use the same variable you passed in the context:
const { prisma } = require('./generated/prisma-client')
const { GraphQLServer } = require('graphql-yoga')
const server = new GraphQLServer({
typeDefs: './schema.graphql',
resolvers,
context: {
prisma,
},
})
server.express.get('/route', async (req, res, done) => {
const params = req.body;
const user = prisma.user({where: {id: params.id} })
res.send(user)
})