I'm working with charging credit cards on an ionic application. If I check the Stripe website, only the token is created successfully. There is no event for the charge. When I check Firebase everything seems to be working (previously I had error message "An error occurred with our connection to Stripe" which was eliminated by upgrading to paid version Blaze).
I've structured the following code so you can see the sequence of events.
Here is the sequence of events:
1) Here I get the token and pass to payByStripe function:
checkOut() {
let alert = this.alertCtrl.create({
title: 'card information',
cssClass:'alert-css',
inputs: [{
name: 'cardNumber',
placeholder: 'card number',
},
{
name: 'expMonth',
placeholder: 'expMonth',
},
{
name: 'expYear',
placeholder: 'expYear',
},
{
name: 'cvc',
placeholder: 'cvc',
}],
buttons: [{
text: 'Cancel',
role: 'cancel',
handler: data => {
console.log('Cancel clicked');
}
},
{
text: 'PAY',
handler: data => {
this.stripe.setPublishableKey('pk_test_abcdef1BC');
let card = {
number: data.cardNumber,
expMonth: data.expMonth,
expYear: data.expYear,
cvc: data.cvc
};
this.stripe.createCardToken(card)
.then(token => {
this.goodsData.payByStripe(this.totalMoniesPlusTaxAndCharge,this.userEmail,token);
let alert = this.alertCtrl.create({
title: 'Charged',
cssClass:'alert-css',
subTitle: 'Successful Charge!',
buttons: ['Dismiss']
});
alert.present();
}, error => {
this.loading.dismiss();
let alert = this.alertCtrl.create({
title: 'ERROR',
cssClass:'alert-css',
subTitle: JSON.stringify(error),
buttons: ['Dismiss']
});
alert.present();
})
.catch(error => console.log(JSON.stringify(error)) );
}
}]
});
alert.present();
}
2) Here, I push the charge to Firebase, where cloud functions will handle it:
payByStripe(amount,email,token): firebase.database.Reference {
return firebase.database().ref('/stripe_customers/charges/').push({
amount:amount,
email:email,
token:token
});
}
3) Here is what the cloud functions are doing:
const functions = require('firebase-functions'),
admin = require('firebase-admin'),
logging = require('#google-cloud/logging')();
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')(functions.config().stripe.token),
currency = functions.config().stripe.currency || 'USD';
// [START chargecustomer]
// Charge the Stripe customer whenever an amount is written to the Realtime database
exports.createStripeCharge = functions.database.ref('/stripe_customers/charges/').onWrite(event => {
const val = event.data.val();
// This onWrite will trigger whenever anything is written to the path, so
// noop if the charge was deleted, errored out, or the Stripe API returned a result (id exists)
if (val === null || val.id || val.error) return null;
// Look up the Stripe customer id written in createStripeCustomer
// Create a charge using the pushId as the idempotency key, protecting against double charges
const amount = val.amount;
const email = val.email;
const token = val.token;
stripe.charges.create({
amount: amount,
currency: "usd",
source: token,
description: "Charge for " + email
}).then(response => {
// If the result is successful, write it back to the database
return event.data.adminRef.set(response);
}, error => {
// We want to capture errors and render them in a user-friendly way, while
// still logging an exception with Stackdriver
return event.data.adminRef.child('error').set(userFacingMessage(error)).then(() => {
return reportError(error, {user: event.params.userId});
});
});
});
// [END chargecustomer]]
I'm pretty sure that's a Firebase-specific error, so if the cause isn't clear, you may want to reach out to them - though someone else with more Firebase experience may have a more useful answer for you!
Related
I am writing a chrome extension that makes requests to an API and I have noticed that after I create a notification from background script using chrome's notification API, the listeners on the buttons from the notification are executed multiple times. on the first run only once and then increasing. I figured that the listeners just add up on the page but I couldn't find a way to sort of refresh the background page.
This is the function that creates the notification and it's listeners.
var myNotificationID
const displayNotification=(userEmail, password, website,username) =>{
chrome.notifications.create("", {
type: "basic",
iconUrl: "./icon128.png",
title: "PERMISSION",
requireInteraction: true,
message: "question",
buttons: [{
title: "YES",
}, {
title: "NO",
}]
}, function(id) {
myNotificationID = id;
})
chrome.notifications.onButtonClicked.addListener(function(notifId, btnIdx) {
if (notifId === myNotificationID) {
if (btnIdx === 0) {
console.log('inserting')
try{
fetch (`http://localhost:8080/users/${userEmail}/accounts`,{
})
}catch(err){
}
} else if (btnIdx === 1) {
console.log('clearing')
chrome.notifications.clear(myNotificationID)
}
}
});
}
And this is where the function is called
chrome.runtime.onMessage.addListener((message, sender, response)=>{
if(message.message === 'showNotification'){
console.log('received insert')
displayNotification(message.userEmail,message.password, message.currentSite,message.username)
response({status:"received"})
}
})
the fetch within the listener is executed multiple times but the log from the onMessage listener is only displayed once, so the listener is the problem here.
I tried chrome.notifications.onButtonClicked.removeListener(), but as i mentioned there was no success.
Are there any other ways in which i could clean the listeners from the background script once they are used?
Using a notification store:
const notificationsByID = {};
chrome.notifications.onButtonClicked.addListener((notifId, btnIdx) => {
// Avoid access to the notification if not registered by displayNotification
if (!notificationsByID[ notifId ]) { return null; }
if (btnIdx === 0) {
console.log('inserting')
try{
fetch (`http://localhost:8080/users/${ notificationsByID[ notifId ].userEmail }/accounts`,{ /**/ });
}catch(err){
console.log(err);
}
delete notificationsByID[ notifId ]; // Cleanup
} else if (btnIdx === 1) {
console.log('clearing')
chrome.notifications.clear(myNotificationID);
delete notificationsByID[ notifId ]; // Cleanup
}
});
chrome.notifications.onClosed.addListener((notifId) => {
if (notificationsByID[ notifId ]) { delete notificationsByID[ notifId ]; }
});
const displayNotification=(userEmail, password, website,username) =>{
chrome.notifications.create("", {
type: "basic",
iconUrl: "./icon128.png",
title: "PERMISSION",
requireInteraction: true,
message: "question",
buttons: [{ title: "YES", }, { title: "NO", }]
}, function(id) {
// Insertion
notificationsByID[ id ] = { userEmail, password, website,username };
})
}
I finished my app and I am planning to host it online. Can my clients use sandbox accounts to make real transactions? This is my first time developing an app and making it have real online transactions using Paypal. Does it have something to do with coding or I have to change a setting in Paypal itself?
Payments.vue
<template>
<div>
<div ref="paypal"></div>
</div>
</template>
mounted()
{
const script = document.createElement("script");
script.src =
"https://www.paypal.com/sdk/js?client-MY-CLIENT-ID";
script.addEventListener("load", this.setLoaded);
document.body.appendChild(script);
}
methods: {
setLoaded: function () {
this.loaded = true;
window.paypal
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
// description: this.product.description,
amount: {
currency_code: "USD",
value: this.product.price,
},
},
],
});
},
onApprove: async (data, actions) => {
const order = await actions.order.capture();
this.$q.notify({
message: "Transaction Successful!",
color: "green",
actions: [
{
label: "Dismiss",
color: "white",
handler: () => {
/* ... */
},
},
],
});
let dateObj = new Date();
let month = dateObj.getMonth();
let day = dateObj.getDate();
let year = dateObj.getFullYear();
let output = month + "" + day + "" + year;
this.$store
.dispatch("SAVE_ENTRY", {
username: this.username,
password: this.password,
confirmPass: this.confirmPass,
access_id: output + this.newAccData,
chosenSchoolId: this.chosenSchoolId,
})
.then((res) => {
if (res.data === "Success1") {
this.$q.notify({
message:
"Transaction Successful! Please complete your registration process inside the website.",
color: "green",
actions: [
{
label: "Dismiss",
color: "white",
handler: () => {
/* ... */
},
},
],
});
}
});
console.log(order);
},
onError: (err) => {
console.log(err);
},
})
.render(this.$refs.paypal);
},
}
I believe there's a sandbox environment, on https://sandbox.paypal.com/ . You have to change the API endpoint to something like that. The sandbox uses separate accounts that can be made in the developer environment on PayPal API pages.
You should be able to set up sandbox accounts here:
https://developer.paypal.com/developer/accounts/
However I dont think this is supposed to be a Javascript/Vue question, you'd want your PayPal API calls to take place on the server side as much as possible.
I'm developing app with Ionic 4 / Angular 8 / Cordova
I have installed In App Purchase 2 and setup it. Apple Developer and Sandbox accounts are ok.
Product registration is ok:
registerProduct() {
this.iap.verbosity = this.iap.DEBUG;
this.iap.register({
id: MONEYCOMBO_KEY,
type: this.iap.CONSUMABLE
})
this.registerHandlersForPurchase(MONEYCOMBO_KEY)
this.product = this.iap.get(MONEYCOMBO_KEY)
this.iap.refresh()
this.iap.when(MONEYCOMBO_KEY).updated((p) => {
this.product = p
this.title = p.title
this.price = p.price
})
}
Event handlers:
registerHandlersForPurchase(productId) {
let self = this.iap;
this.iap.when(productId).updated(function (product) {
if (product.loaded && product.valid && product.state === self.APPROVED && product.transaction != null) {
product.finish();
}
});
this.iap.when(productId).registered((product: IAPProduct) => {
// alert(` owned ${product.owned}`);
});
this.iap.when(productId).owned((product: IAPProduct) => {
console.error('finished')
product.finish();
});
this.iap.when(productId).approved((product: IAPProduct) => {
// alert('approved');
product.finish();
});
this.iap.when(productId).refunded((product: IAPProduct) => {
// alert('refunded');
});
this.iap.when(productId).expired((product: IAPProduct) => {
// alert('expired');
});
}
Purchase method:
buyMoneyCombo(form: NgForm) {
this.registerHandlersForPurchase(MONEYCOMBO_KEY)
this.date = form.value.date
this.iap.order(MONEYCOMBO_KEY)
this.iap.refresh()
}
The problem:
Console says:
"InAppPurchase[js]: product test has a transaction in progress: 1000000628239595"
Transaction cannot be finished. Why?
So, after a weeks researching, I found the solution.
And there is no problem:
Message from Apple is displaying after transaction is finished:
"InAppPurchase[js]: product test has a transaction in progress: 1000000628239595"
And all purchase logic must be declared inside:
this.iap.when(MONEYCOMBO_KEY).approved((product: IAPProduct) => {
product.finish()
this.getMoney()
})
Currently I try to integrate amazon payment.
I want to display the button, created with the function:
OffAmazonPayments.Button("AmazonPayButton", myAmznMerchantID
and after the User has logged in, I want to display the AdressBook Widget and the Payment Widget on the same Page.
The Button and the Widgets are displayed correctly, but when I try to confirm the Payment, I'm getting the Error-Constrains Message:
PaymentPlanNotSet The buyer has not been able to select a Payment
method for the given Order Reference.
But the Payment was selected.
I hope you can help me, to find my failure in this Javascript code:
window.onAmazonLoginReady = function () {
amazon.Login.setClientId(myAmznClientID);
};
window.onAmazonPaymentsReady = function() {
var __accessToken = 0;
var __orderReferenceId = 0;
show_amazon_Button();
function show_amazon_Button() {
OffAmazonPayments.Button("AmazonPayButton", ibuiAmazonPaymentsInfos.ibAmznMerchantID, {
type: "PwA",
color: "Gold",
size: "small", // "medium",
language: "de-DE",
authorization: function () {
loginOptions = { scope: "profile:user_id", popup: true };
authRequest = amazon.Login.authorize(loginOptions, function(response) {
if (response.error) {
//show Error
return;
} else {
__accessToken = response.access_token;
show_Adress_Widget();
}
});
},
onError: function(error) {
//handleError
}
});
}
function show_Adress_Widget() {
new OffAmazonPayments.Widgets.AddressBook({
sellerId: ibuiAmazonPaymentsInfos.ibAmznMerchantID,
onOrderReferenceCreate: function (orderReference) {
__orderReferenceId = orderReference.getAmazonOrderReferenceId();
//do Stuff
},
onAddressSelect: function (orderReference) {
show_Amazon_Wallet();
},
design: {
designMode: 'responsive'
},
onError: function (error) {
//handle Error
}
}).bind("readOnlyAddressBookWidgetDiv");
}
function show_Amazon_Wallet() {
new OffAmazonPayments.Widgets.Wallet({
sellerId: myAmznMerchantID,
onOrderReferenceCreate: function(orderReference) {
//do Stuff
},
design: {
designMode: 'responsive'
},
onPaymentSelect: function(orderReference) {
//activate buy button
},
onError: function(error) {
//handle error
}
}).bind('AmazonWalletWidgetDiv');
}
} //onAmazonPaymentsReady
In PHP I create the OrderReference over:
$params = array(
'order_reference_id' => $orderReferenceId,
'amount' => $amount,
'seller_order_id' => $buchungsKopf->getBuchung_nr(),
);
$responseObject = $client->setOrderReferenceDetails($params);
And even if I had selected a payment method before, I'm getting the "PaymentPlanNotSet" in the responseObject.
Same error, when I try to confirm the orderReference
$responseObject2 = $client->confirmOrderReference($params);
Before I had integrate the AdressWidget, I was able to do the payment.
Can you see what I'm doing wrong?
It seems I have found my failure.
When I want to display both Widgets (Adress and Wallet) on the same page, then I can not use the onOrderReferenceCreate-Method in the OffAmazonPayments.Widget.Wallet.
It seems like then it create a new order reference wich is not the same as the orderreference created by the AdressBook Widget.
Now I have delete this part of the Wallet Widget and everything seems to work fine.
Here is my function used to retrieve data from the database depending on the parameter idCats:
this.getSubcat = function(){
//Load products on scroll.
this.subscribe('SubcatIndex', () => [ $stateParams.idCats, self.loaded ], {
onReady: function() {
Meteor.call('allSubcats', $stateParams.idCats, function(err, count) {
self.allsubcats = count;
self.limit = self.loaded;
console.log("Test Log: " + $stateParams.idCats);
self.subcats = Products.find({
catLinkID : $stateParams.idCats
},{
fields: {
_id: true,
name: true,
catLinkID: true,
idCat: true,
image: true,
listingsCount: true,
productOffersCount: true,
productSoldCount: true
}
}).fetch();
window.localStorage.setItem('subcats', JSON.stringify(self.subcats) );
self.contentLoaded = true;
self.noPosts = 'No posts available.';
$ionicLoading.hide();
return;
});
},
onStop: function(err){
if(err){
self.contentLoaded = true;
self.noPosts = "No internet connection.";
console.log(JSON.stringify(err));
return;
}
}
});
}
this.getSubcat();
When i change this line:
self.subcats = Products.find({
catLinkID : $stateParams.idCats
}
To:
self.subcats = Products.find({
catLinkID : 7 // 7 for example
}
It is working well ! But as soon as I replace it with $stateParams.idCats, I receive this message coming from the function: No posts available.
Note that there are products using the idCats: 7.
When I log it:
console.log("Test Log: " + $stateParams.idCats);
This returns the same number: Test Log: 7.
If you have any suggestion or a starting point to solve this issue, it will be welcome !
Notice that there are no error in the Console (Both server and client side).
Thank you.