Payment Options View Controller (for Stripe) Not Showing Up - node.js

I am fairly new to this. I have used cloud functions and firebase to integrate stripe into my iOS project. Here is my code for the payment options (in node.js).
exports.addPaymentMethodDetails = functions.firestore
.document('/stripe_customers/{userId}/payment_methods/{pushId}')
.onCreate(async (snap, context) => {
try {
const paymentMethodId = snap.data().id;
const paymentMethod = await stripe.paymentMethods.retrieve(
paymentMethodId
);
await snap.ref.set(paymentMethod);
// Create a new SetupIntent so the customer can add a new method next time.
const intent = await stripe.setupIntents.create({
customer: paymentMethod.customer,
});
await snap.ref.parent.parent.set(
{
setup_secret: intent.client_secret,
},
{ merge: true }
);
return;
} catch (error) {
await snap.ref.set({ error: userFacingMessage(error) }, { merge: true });
await reportError(error, { user: context.params.userId });
}
})
Here is my code in Xcode:
``let customerContext = STPCustomerContext(keyProvider: StripeApi)
var paymentContext: STPPaymentContext!
init() {
self.paymentContext = STPPaymentContext(customerContext: customerContext)
super.init(nibName: nil, bundle: nil)
self.paymentContext.delegate = self
self.paymentContext.hostViewController = self
self.paymentContext.paymentAmount = 5000 // This is in cents, i.e. $50 USD
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
#IBAction func paymentMethodClicked(_ sender: Any) {
paymentContext.pushPaymentOptionsViewController()
}
func setupStripeConfig() {
let config = STPPaymentConfiguration.shared()
config.additionalPaymentOptions = .default
config.requiredBillingAddressFields = .none
let customerContext = STPCustomerContext(keyProvider: StripeApi)
paymentContext = STPPaymentContext(customerContext: customerContext, configuration: config, theme: .default())
paymentContext.delegate = self
paymentContext.hostViewController = self
When I run the simulator of this and click the button to pull up payment options, nothing happens and my console (in Xcode) reads "INTERNAL". Can someone please show me how to bring up the Payment Options View Controller, or if there is something I need to or add/fix in order to do so? Also here is my node.js code for getting an ephemeral key:
exports.createEphemeralKey = functions.https.onCall(async (data, context) => {
const customerID = data.customer_id;
const stripeVersion = data.stripe_version;
const uid = context.auth.uid;
if (uid === null) {
console.log('Illegal access attempt due to unauthenticated user');
throw new functions.https.HtppsError('permission-denied', 'Illegal access attempt')
}
let key = await stripe.ephemeralKeys.create(
{customer: '{{CUSTOMER_ID}}'},
{stripe_version: '{{API_VERSION}}'}
);
return stripe.ephemeralKeys.create(
{customer: customerID},
{stripe_version: stripeVersion}).then((key) => {
return key
}).catch((err) => {
console.log(err)
throw new functions.https.HttpsError9('internal', 'Unable to create ephemeral key.')
});
});

Related

How to reduce email sending time (using nodemailer and firebase)?

We have written code that sends emails to a user and their contacts, when a new node is added to a specific path in Firebase realtime database.
The average time to send the emails is 4 minutes.
We think the problem is due to awaiting for some needed promises.
We would like to get the run time down.
Do you have any advice? Thanks in advance!
This is our code:
const functions = require("firebase-functions");
const nodemailer = require('nodemailer');
require('dotenv').config()
//for fire store
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
const { SENDER_EMAIL, SENDER_PASSWORD } = process.env;
exports.sendEmails = functions.database.ref("/devices/{device_ID}/history/{alert_ID}")
.onWrite(
(snapshot, context) => {
sendMail(snapshot, context);
return true;
}
);
async function sendMail(snapshot, context){
const { before, after } = snapshot;
// new alert created
if (before.val() == null) {
console.log('DEBUG:: NEW ALERT');
// get owners uID from device ID
const deviceRef = db.collection('deviceToUid').doc(context.params.device_ID);
const uidDoc = await deviceRef.get();
if(!uidDoc.exists){
functions.logger.info("No such document!");
return;
}
// get users email from uID
const userRef = db.collection('users').doc(uidDoc.data()[context.params.device_ID]).collection('user-info');
// get users contact
const contactRef = db.collection('users').doc(uidDoc.data()[context.params.device_ID]).collection('contacts');
const [userInfo, contactList] = await Promise.all([userRef.get(), contactRef.get()]);
if(userInfo.empty){
functions.logger.info("No such collection!");
return;
}
const email = userInfo.docs[0].id; // owners email
let contacts = []; // initialize contact list
contactList.forEach(
(doc) => {
if(doc.data().confirmed){
contacts.push(doc.id);
}
}
)
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: SENDER_EMAIL,
pass: SENDER_PASSWORD,
},
});
const mailOptions = {
from: 'ALERT <noreply#firebase.com>',
to: email,
bcc: contacts,
subject: `...Motion detected`,
html: `<p dir=ltr>New Alert...</p>`
};
mailTransport.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
}
}
I'd also recommend learning a bit about list comprehensions, as this:
let contacts = []; // initialize contact list
contactList.forEach(
(doc) => {
if(doc.data().confirmed){
contacts.push(doc.id);
}
}
)
Can be reduced to a more concise:
let contacts = contactList.docs
.filter((doc) => doc.data().confirmed)
.map((doc) => doc.id);
You were getting pretty close, but were missing an await in the top-level function, and one inside sendMail for the call to mailTransport.sendMail.
I think this should be it:
exports.sendEmails = functions.database.ref("/devices/{device_ID}/history/{alert_ID}")
.onWrite(
async (snapshot, context) => {
await sendMail(snapshot, context);
return true;
}
);
async function sendMail(snapshot, context){
const { before, after } = snapshot;
// new alert created
if (before.val() == null) {
console.log('DEBUG:: NEW ALERT');
// get owners uID from device ID
const deviceRef = db.collection('deviceToUid').doc(context.params.device_ID);
const uidDoc = await deviceRef.get();
if(!uidDoc.exists){
functions.logger.info("No such document!");
return;
}
// get users email from uID
const userRef = db.collection('users').doc(uidDoc.data()[context.params.device_ID]).collection('user-info');
// get users contact
const contactRef = db.collection('users').doc(uidDoc.data()[context.params.device_ID]).collection('contacts');
const [userInfo, contactList] = await Promise.all([userRef.get(), contactRef.get()]);
if(userInfo.empty){
functions.logger.info("No such collection!");
return;
}
const email = userInfo.docs[0].id; // owners email
let contacts = []; // initialize contact list
contactList.forEach(
(doc) => {
if(doc.data().confirmed){
contacts.push(doc.id);
}
}
)
const mailTransport = nodemailer.createTransport({
service: 'gmail',
auth: {
user: SENDER_EMAIL,
pass: SENDER_PASSWORD,
},
});
const mailOptions = {
from: 'ALERT <noreply#firebase.com>',
to: email,
bcc: contacts,
subject: `...Motion detected`,
html: `<p dir=ltr>New Alert...</p>`
};
await mailTransport.sendMail(mailOptions, function (error, info) {
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
return true;
}
}
Since you were not using await in the top-level call, the Cloud Functions contains will/may shut down the container before the asynchronous calls have completed. For more on this, see the documentation on sync, async and promises - and how Cloud Functions are terminated.

Microsoft bot framework save separately conversations or sessions

I got a Microsoft bot framework chatbot deployed on Azure and I´m using Tedious to save my conversations, thing is, bot it's being used on a web and many persons can open it to interact simultaneosly, but when I save a conversation from an user, it saves all the other interactions that have been made by other users at the same time, I need that each user has it's own conversation saved separately even if they are interacting with the chatbot at the same time...
Here's my code, maybe I'm missing something:
Bot.js
//SQL Connection
var Connection = require('tedious').Connection;
var config = {
server: 'myserver',
authentication: {
type: 'default',
options: {
userName: 'myuser',
password: 'mypass'
}
},
options: {
encrypt: true,
database: 'mydatabase'
}
};
const connection = new Connection(config);
connection.on('connect', function(err) {
console.log("Connected");
});
var Request = require('tedious').Request
var TYPES = require('tedious').TYPES;
// Function to save the conversation and bot ids
function executeConversationStatement(bot, cid, ulg ) {
request = new Request("INSERT INTO table (bot_id, conversationID, conversation_date, userlogged) VALUES (#bot, #cid, CURRENT_TIMESTAMP, #ulg); SELECT ##IDENTITY AS ID",function(err) {
if (err) {
console.log(err);}
});
request.addParameter('bot', TYPES.Int, bot);
request.addParameter('cid', TYPES.NVarChar, cid);
request.addParameter('ulg', TYPES.NVarChar, ulg);
request.on('row', function(columns) {
insertedcid = columns[0].value; // This is the id I pass later
columns.forEach(function(column) {
if (column.value === null) {
console.log('NULL');
} else {
console.log("Conversation id of inserted item is " + column.value);
}
});
});
connection.execSql(request);
}
// Here on members added I save the conversation id generated by the framework
class BOT extends ActivityHandler {
constructor(conversationState,userState,telemetryClient) {
super();
this.conversationState = conversationState;
this.userState = userState;
this.dialogState = conversationState.createProperty("dialogState");
this.previousIntent = this.conversationState.createProperty("previousIntent");
this.conversationData = this.conversationState.createProperty('conservationData');
const qnaMaker = new QnAMaker({
knowledgeBaseId: process.env.QnAKnowledgebaseId,
endpointKey: process.env.QnAEndpointKey,
host: process.env.QnAEndpointHostName
});
this.qnaMaker = qnaMaker;
this.onMessage(async (context, next) => {
await this.dispatchToIntentAsync(context);
await next();
});
this.onDialog(async (context, next) => {
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
await next();
});
this.onMembersAdded(async (context, next) => {
const { channelId, membersAdded } = context.activity;
actid = context._activity.id;
if (channelId === 'directline' || channelId === 'webchat') {
for (let member of membersAdded) {
if (member.id === context.activity.recipient.id) {
await context.sendActivity("Hi, I´m a chatbot to guide You");
try{
var saveqna = new executeConversationStatement(context._activity.id , 'Invitado');
}
catch{
console.log('Not saved');
}
}
}
}
await next();
});
}
//Finally, here I save the interaction:
async dispatchToIntentAsync(context) {
var result = await this.qnaMaker.getAnswers(context);
// Statement to save interaction with the insertedcid obtained above
var saveqnaint = new executeInteractionStatement(insertedcid, context._activity.text, result);
}
No matter if I use thet generated Id or the databse pk, I always keep the same identifier when multiple users are chatting, how can I got a separately Id for each session ?

Invoke different contract in a single Chaincode

I am using hyperledger-fabric 1.4 for developing a simple chaincode that saves the project information.
I am working on a existing code repository that contains chain code function calls using, facing many difficulties already in running the network. I managed to get the chaincode installed on 2 peers.
Now i am on invoking the chain code part. I know that in fabric-client two types there are bootstrap identities and then enrollment strategies for users and admins.
What I am trying to achieve is to write separate contract files say Project and Folder, install these contract files as single chaincode and then invoke these contract functions using client.
Originally the repository contain only one contract that was built using shim:
const util = require('util');
var Chaincode = class {
async Init(stub) {
return shim.success();
}
async Invoke(stub) {
let ret = stub.getFunctionAndParameters();
console.info(ret);
let method = this[ret.fcn];
if (!method) {
return shim.success();
}
try {
let payload = await method(stub, ret.params, this);
return shim.success(payload);
} catch (err) {
return shim.error(err);
}
}
async save(stub, args, thisClass) {
if (args.length != 1) {
return Buffer.from(JSON.stringify({result: 400, message: "Invalid arguments"}));
}
let transactionHash = stub.getTxID();
let timeStamp = stub
.getTxTimestamp()
.seconds
.low;
try {
let order = JSON.parse(args[0]);
order.updateLogs = [
{
transactionHash: transactionHash,
createdAt: timeStamp,
completedMilestone: order.completedMilestone
}
]
let orderAsByte = await stub.getState(order.orderId);
if (orderAsByte && orderAsByte.length > 0) {
return Buffer.from(
JSON.stringify({result: 409, message: "order already exist"})
);
}
await stub.putState(order.orderId, Buffer.from(JSON.stringify(order)));
return Buffer.from(
JSON.stringify({result: 200, message: 'successfully Save', transactionHash})
);
} catch (err) {
return Buffer.from(
JSON.stringify({result: 400, message: err.message, transactionHash})
);
}
}
//Change modified of PO
async update(stub, args, thisClass) {
// args[0] = order id, args[1] = completedMilestone
if (args.length != 2) { //
return Buffer.from(JSON.stringify({result: 400, message: "Invalid arguments"}));
}
let timeStamp = stub
.getTxTimestamp()
.seconds
.low;
let orderAsByte = await stub.getState(args[0]);
if (!orderAsByte || orderAsByte.length === 0) {
return Buffer.from(
JSON.stringify({result: 404, message: "order does not exist"})
);
}
let transactionHash = stub.getTxID();
let order = JSON.parse(orderAsByte);
order.completedMilestone = args[1];
order
.updateLogs
.push(
{completedMilestone: order.completedMilestone, transactionHash: transactionHash, createdAt: timeStamp}
)
try {
await stub.putState(args[0], Buffer.from(JSON.stringify(order)));
return Buffer.from(
JSON.stringify({result: 200, message: 'successfully update', transactionHash})
);
} catch (err) {
return Buffer.from(JSON.stringify({result: 400, message: err.message}));
}
}
async queryOrder(stub, args, thisClass) {
if (args.length < 1) {
return Buffer.from(JSON.stringify({result: 400, message: "Invalid arguments"}));
}
let queryString = args[0];
let method = thisClass['getQueryResultForQueryString'];
let queryResults = await method(stub, queryString, thisClass);
return queryResults;
}
async getQueryResultForQueryString(stub, queryString, thisClass) {
let resultsIterator = await stub.getQueryResult(queryString);
let method = thisClass['getAllResults'];
let results = await method(resultsIterator, false);
return Buffer.from(JSON.stringify(results));
}
async getAllResults(iterator) {
let allResults = [];
while (true) {
let res = await iterator.next();
if (res.value && res.value.value.toString()) {
let jsonRes = {};
jsonRes.Key = res.value.key;
try {
jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
} catch (err) {
jsonRes.Record = res
.value
.value
.toString('utf8');
}
allResults.push(jsonRes);
}
if (res.done) {
await iterator.close();
console.info(allResults);
return allResults;
}
}
}
async get(stub, args, thisClass) {
if (args.length != 1) { //
return Buffer.from(JSON.stringify({result: 400, message: "Invalid arguments"}));
}
try {
let orderAsByte = await stub.getState(args[0]);
let order = JSON.parse(orderAsByte);
console.info('GET <--> ', order);
return Buffer.from(JSON.stringify({result: 200, order: JSON.stringify(order)}));
} catch (err) {
return Buffer.from(JSON.stringify({result: 404, message: 'Order Not Found'}));
}
}
};
shim.start(new Chaincode());
and it was working flawlessly with following snippet.
async function loadAdmin() {
const adminKeystorePath = path.join(
process.cwd(),
"../",
"network",
"crypto-config",
"peerOrganizations",
"blockchain.kwiktrust.com",
"users",
"Admin#blockchain.kwiktrust.com",
"msp",
"keystore",
"509c6e0476760a3eb9a6b37b7788f2178bb708e651e7f3f89a05d10eefc48522_sk");
const keystore = fs.readFileSync(adminKeystorePath);
const adminCertPath = path.join(
process.cwd(),
"../",
"network",
"crypto-config",
"peerOrganizations",
"blockchain.kwiktrust.com",
"users",
"Admin#blockchain.kwiktrust.com",
"msp",
"signcerts",
"Admin#blockchain.kwiktrust.com-cert.pem"
);
const signCert = fs.readFileSync(adminCertPath)
console.log('fabricHelper.js.loadAdmin().adminKeystorePath => ',adminKeystorePath);
console.log('fabricHelper.js.loadAdmin().adminCertPath => ', adminCertPath);
const state_store = await Fabric_Client.newDefaultKeyValueStore(
{path: store_path}
)
fabric_client.setStateStore(state_store)
const crypto_suite = Fabric_Client.newCryptoSuite()
const crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path})
crypto_suite.setCryptoKeyStore(crypto_store)
fabric_client.setCryptoSuite(crypto_suite)
let admin = await fabric_client.getUserContext("admin", true)
if (!admin) {
admin = await fabric_client.createUser({
username: "admin",
mspid: process.env.MSP,
cryptoContent: {
privateKeyPEM: keystore.toString(),
signedCertPEM: signCert.toString()
}
})
console.log('fabricHelper.js.loadAdmin.createUser =>', admin.getIdentity().toString())
fabric_client.setUserContext(admin, true)
}
else{
console.log('fabricHelper.js.loadAdmin.alreadyCreated =>', admin.getIdentity().toString())
}
}
Above loads the user context in fabric client SDK and then the saveOrder to execute contract and submit transaction
function saveOrder(orderObj) {
return new Promise(async (resolve, reject) => {
await loadAdmin()
const tx_id = fabric_client.newTransactionID()
let request = {
chaincodeId: process.env.CHAIN_CODE_ID,
fcn: "save",
args: [JSON.stringify(orderObj)],
chainId: process.env.CHANNEL,
txId: tx_id
}
const results = await channel.sendTransactionProposal(request)
const [proposalResponses,proposal] = results
if (proposalResponses && proposalResponses[0].response && proposalResponses[0].response.status === 200) {
const payload = JSON.parse(proposalResponses[0].response.payload.toString())
console.log("channel.sendTransactionProposal:proposalResponses[0].response.status =>", proposalResponses[0].response.status)
console.log("channel.sendTransactionProposal:proposalResponses[0].response.payload =>", payload.result)
if (payload.result == 200) {
request = {
proposalResponses,
proposal
}
const sendRequest = await channel.sendTransaction(request)
console.log('sendRequest.Status =>', sendRequest);
if (sendRequest && sendRequest.status === "SUCCESS") {
resolve(true)
} else {
reject(false)
}
} else {
console.log("Unable to get payload success in porposal response")
reject(false)
}
} else {
console.log("Unable to get payload success in porposal response")
reject(false)
}
})
}
Now I have changed shim to fabric-contract-api:-1.4.0 and exporting two contracts Project and Folder.
As i stated I want more then one contract files, but I have no idea how to execute the transaction the same way that I did for one contract, as channel.getContract is not any function. I tried another way that uses FileSystemWallet in fabric-sample 1.4 fabcar which required enrollAdmin and registerUser options but there i get authentication failure when I even try to enroll admin and probably because of my lack of knowledge on how to setup connections.json.
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'http','utils','hfc-key-store');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "admin" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('Project');
This way i get to submit transaction on contract but when i run enrollAdmin.js before running registerUser.js script it gives me unable to enroll Authentication Failure.
Its been only four days since i started working on hyper-ledger so any help is appriciated..

Session expiring in Dialogflow

I came to know that context expires in 15 minutes but is there any way to solve it manually i.e by storing the previous conversation in dB so can we handle that session expiring issue or else the whole conversation(output context) under that session ID will get clear and need to start from the first.
exports.fulfillmenttext = functions.https.onRequest((req,res) =>{
const answer1 = req.body.Text;
console.log("Text said by the user",answer1);
const uid = answer1.substring(0,28);
console.log("uid1 is",uid);
const answer = answer1.substring(28);
console.log("answer is",answer);
const sessionId = uid;
var count,questvalue;
runSample();
async function runSample(projectId = 'xxxxxxx') {
const languageCode = 'en-US';
const credentials = {
client_email: 'xxxxxxxxxx',
private_key: 'xxxxxxxxx'
};
//Instantiate a DialogFlow client.
const dialogflow = require('dialogflow');
const sessionClient = new dialogflow.SessionsClient({
projectId,
credentials,
});
// Define session path
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
text: answer,
languageCode,
},
},
};
const responses = await sessionClient.detectIntent(request);
console.log('Detected intent');
const result = responses[0].queryResult;
let action = result.action;
console.log("action is"+action);
console.log(` Query: ${result.queryText}`);
console.log(` Response: ${result.fulfillmentText}`);
if (result.intent) {
const question = result.fulfillmentText;
console.log("question is",question);
const actionHandlers = {
'early': () => {
console.log('earlyaction1', action);
let name1 = JSON.stringify(result.parameters.fields.Name.stringValue);
name1 = name1.toString().replace(/"/g,"");
var data1 = {
Name: name1
};
var setDoc1 = admin.firestore().collection('User').doc(uid).collection("Popop").doc(uid).collection('Answers').doc('Earlyyears').update(data1);
},
'family': () => {
console.log('familyaction1', action);
let mname1 = JSON.stringify(result.parameters.fields.M_Name.stringValue);
let mname_string = mname1.toString().replace(/"/g,"");
var data20 = {
MName: mname_string
};
var setDoc20 = admin.firestore().collection('User').doc(uid).collection("Popop").doc(uid).collection('Answers').doc('Family').update(data20);
}
};
if (action === 'early') {
console.log('1');
actionHandlers[action]();
}
else if (action === 'family') {
console.log('2');
actionHandlers[action]();
}
res.status(200).send({"question":result.fulfillmentText,"action":action});
} else {
console.log(` No intent matched.`);
res.status(400).send({"action":"empty"});
}
}
});
I stumbled upon this problem as well. My solution was to save the userID and save the contexts to Firestore.
UPDATE:
This is how I stored Dialogflow's contexts in Firestore:
function saveContexts(userId, contexts) {
let UID = userId;
//get all contexts + parameters
if (contexts === undefined) {
console.log("contexts are undefined! returning");
return false;
}
db.collection("user-contexts-prod").doc(UID).set({
dateCreated: new Date(),
contexts: JSON.stringify(contexts)
})
.then(function () {
console.log("success!");
return true;
})
.catch(function (error) {
console.log("error writing document..", error);
return false;
});
}
Retrieving user contexts:
async function getContexts(userId) {
let UID = userId;
let docRef = db.collection("user-contexts-prod").doc(UID);
return docRef.get()
.then(res => {
if (res.exists) {
let contexts = JSON.parse(res.data().contexts);
console.log("<><> parsed contexts <><>: ");
console.log(contexts);
return contexts;
} else {
console.log(" UID DOES NOT EXIST!");
return false;
}
})
}
You can set the contexts again by looping over them and using the contextClient to create new contexts. Or use this method to loop through the contexts and find the one you need:
contexts.forEach(function(context) {
if (context.name === 'projects/{DIALOGFLOWPROJECTID}/agent/sessions/' + senderId + '/contexts/{CONTEXTNAME}') {
sessionData = context.parameters;
// all data that you saved in CONTEXTNAME is now available in the sessionData variable
}
});
Original answer:
Whenever a user started talking that didn't have any active contexts I check if I had the userID stored in my Database. If this user existed in my DB I retrieved the user information with all his data like this:
knownUser = await db.isKnownUser(senderId);
if (knownUser) {
//knownUser
console.log("Known user");
let userData = db.getUserDataById(senderId)
//initialize contexts with data you need
payload = returningUser_useSameData();
messenger.send(payload, senderId);
dashbot.logBotMessage(payload.toString, sessionId, intentName);
break;
} else {
//newUser
console.log("new user");
createContext('await_fillInTogether', '', sessionPath, sessionId, 1);
createContext('session', '', sessionPath, sessionId, 500);
payload = fillInTogetherNewUser();
messenger.send(payload, senderId);
dashbot.logBotMessage(payload.toString, sessionId, intentName);
break;
}

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