Charge a specific customer card in Stripe - javascript

TLDR; I want to allow customers to place orders using the previously used/saved card of their choice in my app. This requires the ability to specify to Stripe "which" card (previously associated to a customer) to charge. This should be possible in theory according to the docs, but I'm having no luck in practice.
Alright... talk is cheap, here are the codes:
// pseudo code of a customer placing an order for the first time
var stripe = require("stripe")(".....");
(async function(userId, cardToken) {
// Create a Customer AND save the payment method
const customer = await stripe.customers.create({
source: cardToken.id, // e.g. 'tok_mastercard' (retrieved from stripe.js)
email: 'paying.user#example.com',
});
// Charge the Customer instead of the card:
const charge = await stripe.charges.create({
amount: 1000,
currency: 'usd',
customer: customer.id
});
// save customer id to DB
await setCustomerIdToDatabase(userId, customer.id);
// save Card ID (not the card token ID) to DB
await addCustomerCardToDatabase(userId, cardToken.card.id); // <= note cardToken".card".id e.g. card_kldlkns...
})(userId, cardToken /* <= provided by app*/);
So far so good. Stripe saved the card from the specified token as the default payment method for the newly created customer then charges it successfully. But then my pain begins...
// pseudo code of a customer placing subsequent orders (WITH NEW CARDS)
var stripe = require("stripe")(".....");
(async function(userId, cardToken) {
const customerId = await getCustomerIdFromDatabase(userId);
// Create a Customer AND save the payment method
await stripe.customers.createSource(
customerId,
{
source: cardToken.id, // e.g. 'tok_mastercard' (retrieved from stripe.js)
}
);
// Attempt to charge the newly added card instead the Customer:
const charge = await stripe.charges.create({
amount: 1000,
currency: 'usd',
customer: customerId,
source: cardToken.card.id // <= note cardToken".card".id e.g. card_kldlkns...
});
/////////ERROR TRHOWN: No such token: card_kldlkns...
///////// what? I just saved it with .customers.createSource and you said it was OK???
/// unreachable code
// save Card ID (not the card token ID) to DB
await addCustomerCardToDatabase(userId, cardToken.card.id); // <= note cardToken".card".id e.g. card_kldlkns...
})(userId, cardToken /* <= provided by app*/);
Fails miserably... and so does this
// pseudo code of a customer placing subsequent orders (WITH SAVED CARDS)
var stripe = require("stripe")(".....");
(async function(userId, savedCardId) {
const customerId = await getCustomerIdFromDatabase(userId);
// Attempt to charge the newly added card instead the Customer:
const charge = await stripe.charges.create({
amount: 1000,
currency: 'usd',
customer: customerId,
source: savedCardId // e.g card_kldlkns...
});
/////////ERROR TRHOWN: No such token: card_kldlkns...
///////// what??????
})(userId, savedCardId /* <= provided by app*/);
What am I not getting here? The docs clearly says Stripe wants the card.id and the customer.id back if I want to charge a specific card.
Any help is appreciated

Turns out this was not an issue at all.
There was a faulty condition is the codes that was calling the wrong stripe API method

Related

Create Stripe Charge for Connected Account - No Such Customer

I’ve successfully setup the Customers payment methods and am able to retrieve them using the following code:
return stripe.paymentMethods
.list({ customer: customerId, type: 'card' })
.then((cards) => {
if (cards) {
return { cards: cards.data, error: null };
}
return {
error: 'Error creating client intent',
cards: null,
};
})
.catch((e) => {
console.log(e);
return { cards: null, error: 'Error fetching user cards' };
});
I’m now trying to create a direct PaymentIntent that will route the payment to a Stripe Connect connected account.
To do this I’m running this code:
if (cards && cards.cards && cards.cards.length > 0) {
const card = cards.cards[0];
const paymentIntent = await stripe.paymentIntents.create(
{
amount: amount,
customer: card.customer,
receipt_email: userEmail,
currency,
metadata: {
amount,
paymentMode: chargeType,
orderId,
},
description:
'My First Test Charge (created for API docs)',
application_fee_amount: 0,
},
{
stripeAccount: vendorStripeAccount,
}
);
const confirmedPaymentIntent = await stripe.paymentIntents.confirm(
paymentIntent.id,
{ payment_method: card.id }
);
This gives me the error ‘No such customer’, even though the customer ID is defined and I can find the customer in my Stripe dashboard. I also see the customer’s payment methods there.
What am I doing wrong?
The problem is that the Customer exists on your platform account, not the connected account you're trying to create the Payment Intent on.
In your first code snippet you don't specify a stripeAccount, so that API request is being made on your platform account. The Customer exists there, which is why that works as expected.
In your second code snippet you do specify a stripeAccount, which means that API request is being made on the connected account specified, not your platform account. You can read more about making API calls on connected accounts in Stripe's documentation.
To resolve the situation you either need to create the Payment Intent on your platform account as a destination charge, or create the Customer object on the connected account so it can be used there.

Why is snap.data().id not defined?

I can't seem to get the id of the document I'm trying to retrieve.
I have looked at a lot of examples on the web and they all seem to be doing exactly what i'm doing.
exports.moveToProfile = functions.firestore
.document("tempProfiles/{id}")
.onCreate(async (snap, context) => {
const id = snap.data().id;
const displayName = snap.data().displayName;
const profile = await db
.collection("profiles")
.doc(id)
.set({
displayName: displayName,
points: 0
});
return profile;
});
In your code, data is a DocumentSnapshot type object. As you can see from the linked API documentation, the ID of the document represented by that object is its id property. data() gives you all of its fields (and the formal doucment ID is not one of them, unless you wrote it as a field.) So, you can get the ID with data.id.
If you want to use wildcards and parameters then you can use context.params.
See https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters .
// Listen for changes in all documents in the 'users' collection
exports.useWildcard = functions.firestore
.document('users/{userId}')
.onWrite((change, context) => {
// If we set `/users/marie` to {name: "Marie"} then
// context.params.userId == "marie"
// ... and ...
// change.after.denter code hereata() == {name: "Marie"}
});

calling json file from firebase function

I have 3 groups( html ) inside every group is four people and those people are the same in all 3 groups but just different numbers (votes/credits whatever), and I have a json file where is values for those people inside group. My HTML file reads my json file without problem.
I'm using Dialogflows Inline Editor to work with the Google Assistant. What I want is that the same way my html( javascript ) is reading those values from json file, I want to be able to load the person.json file as well. I have edited that many times could not manage to call person.json url.
For example:
"Hey google, tell me Alex credits"
< here it should read from my json file which is 73 >
Here are codes: person1.html
var response = await fetch("laurel.json");
var arr = await response.json();
var laurel= arr[1];
var dflt = {
min: 0,
max: 100,
// donut: true,
gaugeWidthScale: 1.1,
counter: true,
hideInnerShadow: true
}
var ee1 = new r({
id: 'ee1',
value: laurel['Jennifer'],
title: 'Jennifer ',
defaults: dflt
});
var ee2 = new r({
id: 'ee2',
value: laurel['Peter'],
title: 'Peter',
defaults: dflt
});
var ee3 = new r({
id: 'ee3',
value: laurel['Justin'],
title: 'Justin',
defaults: dflt
});
var ee4 = new r({
id: 'ee4',
value: laurel['Alex'],
title: 'Alex',
defaults: dflt
});
});
inline editors index.js :
intentMap.set('persons1', someFunction);
function someFunction(agent) {
agent.add(`Alex credits are 73 `);
}
// // below to get this function to be run when a Dialogflow intent is matched
// function yourFunctionHandler(agent) {
// agent.add(`This message is from Dialogflow's Cloud Functions for Firebase editor!`);
// agent.add(new Card({
// title: `Title: this is a card title`,
// imageUrl: 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png',
// text: `This is the body text of a card. You can even use line\n breaks and emoji! 💁`,
// buttonText: 'This is a button',
// buttonUrl: 'https://assistant.google.com/'
// })
// );
// agent.add(new Suggestion(`Quick Reply`));
// agent.add(new Suggestion(`Suggestion`));
// agent.setContext({ name: 'weather', lifespan: 2, parameters: { city: 'Rome' }});
// }
// // Uncomment and edit to make your own Google Assistant intent handler
// // uncomment `intentMap.set('your intent name here', googleAssistantHandler);`
// // below to get this function to be run when a Dialogflow intent is matched
// function googleAssistantHandler(agent) {
// let conv = agent.conv(); // Get Actions on Google library conv instance
// conv.ask('Hello from the Actions on Google client library!') // Use Actions on Google library
// agent.add(conv); // Add Actions on Google library responses to your agent's response
// }
// // See https://github.com/dialogflow/dialogflow-fulfillment-nodejs/tree/master/samples/actions-on-google
// // for a complete Dialogflow fulfillment library Actions on Google client library v2 integration sample
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
// intentMap.set('your intent name here', yourFunctionHandler);
// intentMap.set('your intent name here', googleAssistantHandler);
agent.handleRequest(intentMap);
});
You've got a couple of options here. Either you switch your plan to Blaze and make a network request for the JSON data that is hosted on your public URL mentioned earlier.
Or you can also just put the JSON data into the Firebase realtime database as it is also accessible as a JSON document on a public URL if you set the database rules to publicly readable.
database.rules.json
{
"rules": {
"persons": {
".read": true
}
}
}
After that you can access your JSON data in Firebase on this URL: https://<your-project-id>.firebaseio.com/persons.json
That should be effectively the same thing for accessing in your web app.
For your AoG fulfillment you can simply use the Firebase SDK to access the data
firebase.database().ref('persons').child('personId');

Clarification on functionality of the stripe token

So I'm currently in the final stages of designing a small online shop and I'm having a bit of difficulty understanding what the stripe token contains, how to pick it up on the node.js server and that kind of thing.
currently, my client-side code looks like this:
<div style="text-align: center;">
<form id="paymentForm" action="//httpbin.org/post" method="POST">
<input type="hidden" id="stripeToken" name="stripeToken" />
<input type="hidden" id="stripeEmail" name="stripeEmail" />
<input type="hidden" id="cartTotal" name="cartTotal" />
<input type="hidden" id="cartContents" name="cartContents" />
</form>
<p><input type="button" class="button" id="purchaseButton" value="チェックアウト"></p>
<script>
var totalCost = 0;
var totalCartLoad = "";
totalCost = localStorage.getItem('totalCartPrice');
totalCartLoad = localStorage.getItem('whatsInCart');
totalCartLoad = totalCartLoad.replace('undefined','');
totalCartLoad = '_____________________________________' + totalCartLoad;
var finalCartLoad = String(totalCartLoad); //convert it to a string for display
var handler = StripeCheckout.configure({
key: 'pk_test_6pRNASCoBOKtIshFeQd4XMUh',
token: function(token) {
$("#stripeToken").val(token.id);
$("#stripeEmail").val(token.email);
$("#cartTotal").val(totalCost);
$("#cartContents").val(finalCartLoad);
$("#paymentForm").submit();
}
});
$('#purchaseButton').on('click', function(e) {
// Open Checkout with further options
handler.open({
name: "チェックアウト",
description: finalCartLoad,
shippingAddress: true,
billingAddress: true,
zipCode: true,
allowRememberMe: true,
currency: 'JPY',
amount: totalCost
});
e.preventDefault();
});
// Close Checkout on page navigation
$(window).on('popstate', function() {
handler.close();
});
</script>
</div>
and my server code looks like this:
const stripe = require("stripe")("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
module.exports = (req) => {
// the token is generated by Stripe and POST'ed
// to the `action` URL in our form
const token = req.body.stripeToken;
// now we create a charge which returns a `promise`
// so we need to make sure we correctly handle
// success and failure, but that's outside of this
// function (and we'll see it later)
return stripe.charges.create({
// ensures we send a number, and not a string
amount: parseInt(process.env.STRIPE_COST, 10),
currency: process.env.STRIPE_CCY,
source: token,
description: process.env.STRIPE_DESCRIPTION, // 👈 remember to change this!
// this metadata object property can hold any
// extra private meta data (like IP address)
metadata: {},
});
}
However I am uncertain how to make sure that the details I need such as shipping address, customer email, product manifest and that kind of thing, which I have collected on my client, end up where I need it, on the invoice or somewhere in my account on stripe. I am also uncertain exactly how the charge is made (I know I need an app.js file to go with this as well, so I'd appreciate some pointers at this point cause its really been doing my head in.
The Token.id is what you want to use as the source when creating the Charge, and it looks like that's what you're doing, so you should be good to go from that side.
You should currently find the email at req.body.stripeEmail; in fact, you should find all of the following in req.body:
$("#stripeToken").val(token.id); // req.body.stripeToken
$("#stripeEmail").val(token.email); // req.body.stripeEmail
$("#cartTotal").val(totalCost); // req.body.cartTotal
$("#cartContents").val(finalCartLoad); // req.body.cartContents
In order to get the Shipping address, you'll need to pass those along too; you can find them in the args argument of the token() function, so you just need to pull what you need from there and send it along in your form as well.
var handler = StripeCheckout.configure({
key: 'pk_test_6pRNASCoBOKtIshFeQd4XMUh',
token: function(token) {
$("#stripeToken").val(token.id);
$("#stripeEmail").val(token.email);
$("#cartTotal").val(totalCost);
$("#cartContents").val(finalCartLoad);
$("#userShippingA").val(token.shippingAddress);
$("#userBillingA").val(token.billingAddress);
$("#paymentForm").submit();
}
});
return stripe.charges.create({
// ensures we send a number, and not a string
amount: parseInt(process.env.STRIPE_COST, 10),
currency: process.env.STRIPE_CCY,
source: token,
description: req.body.cartContents,
shippingAddress: req.body.shippingAddress,
billingAddress: req.body.billingAddress,
email: req.body.stripeEmail,

Laravel 5.1 Cashier - Adds customer but without info

I am using Laravel 5.1 trying to set it up with Stripe using Cashier.
I am using a custom button to execute the javascript (using Angular):
$scope.subscribe = function(plan){
var handler = StripeCheckout.configure({
key: 'pk_test_*************',
image: '/img/documentation/checkout/marketplace.png',
locale: 'auto',
token: function(token) {
// Use the token to create the charge with a server-side script.
// You can access the token ID with `token.id`
var data = 'stripeToken=' + token.id;
$http.post("/createSubscription", data).success(function(data, status) {
console.log(data);
});
}
});
handler.open({
name: 'basic',
description: 'basic monthly $100',
currency: "usd",
amount: 10000
});
$(window).on('popstate', function() {
handler.close();
});
};
And in my Laravel code I have:
public function createSubscription(Request $request)
{
$user = JWTAuth::parseToken()->authenticate();
$token = Input::get('stripeToken');
$user->subscription("basic_plan")->create($token);
return 'test';
}
But I keep getting an error saying "this customer has no attached payment source".
The returned token at this line:
token: function(token) {
...
Does contain the users email, card info (+ card token), and stripe token. But when I check my Stripe dashboard, a customer is added without any data (no card, no email and not set up with the subscription).
I am trying to create customers with a subscription plan via this form.
(I do have the Billable trait set up and included in the controller)

Categories