Client
I'm using Stripe Checkout custom integration - https://stripe.com/docs/checkout#integration-custom - in a following way:
var handler = StripeCheckout.configure({
key: 'YOUR_KEY_HERE',
image: 'images/logo-48px.png',
token: function(token, args) {
$.post("http://localhost:3000/charge", {token: token}, function(res) {
console.log("response from charge: " + res);
})
}
})
Using custom contrary to simple - How can I modify Stripe Checkout to instead send an AJAX request? - because simple does not allow me to make an AJAX call.
Server
https://stripe.com/docs/tutorials/charges
You've got the token for your user's credit card details, now what? Now you charge them money.
app.post('/charge', function(req, res) {
console.log(JSON.stringify(req.body, null, 2));
var stripeToken = req.body.token;
var charge = stripe.charges.create({
amount: 0005, // amount in cents, again
currency: "usd",
card: stripeToken,
description: "payinguser#example.com"
}, function(err, charge) {
if (err && err.type === 'StripeCardError') {
console.log(JSON.stringify(err, null, 2));
}
res.send("completed payment!")
});
});
Here is the error:
Is seems to me like I have last4, exp_month, exp_year but for some reason I don't have number. Any suggestions / hints / ideas?
Googling for "The card object must have a value for 'number'" - 12 results, not much help.
The "token" you have to give as the card argument should actually just be the token id (e.g.: "tok_425dVa2eZvKYlo2CLCK8DNwq"), not the full object. Using Checkout your app never sees the card number.
You therefeore need to change:
var stripeToken = req.body.token;
to:
var stripeToken = req.body.token.id;
The documentation isn't very clear about this card option, but the Stripe API Reference has an example.
After npm install stripe do this
var stripe = require("stripe")("sk_yourstripeserversecretkey");
var chargeObject = {};
chargeObject.amount = grandTotal * 100;
chargeObject.currency = "usd";
chargeObject.source = token-from-client;
chargeObject.description = "Charge for joe#blow.com";
stripe.charges.create(chargeObject)
.then((charge) => {
// New charge created. record charge object
}).catch((err) => {
// charge failed. Alert user that charge failed somehow
switch (err.type) {
case 'StripeCardError':
// A declined card error
err.message; // => e.g. "Your card's expiration year is invalid."
break;
case 'StripeInvalidRequestError':
// Invalid parameters were supplied to Stripe's API
break;
case 'StripeAPIError':
// An error occurred internally with Stripe's API
break;
case 'StripeConnectionError':
// Some kind of error occurred during the HTTPS communication
break;
case 'StripeAuthenticationError':
// You probably used an incorrect API key
break;
case 'StripeRateLimitError':
// Too many requests hit the API too quickly
break;
}
});
Related
I am integrating stripe with my application. I want to receive a one time payment from a logged in user and once the payment is done maybe save the payment status in the database.
I have set up the stripe-checkout and stripe-webhook. But how would i know which logged in user from the client side has made the payment so that i can set the payment status for that user in the database.
Here is how my checkout and webhook look like.
app.post("/checkout-session", async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: "payment",
line_items: req.body.items.map((item) => {
const storeItem = storeItems.get(item.id);
return {
price_data: {
currency: "usd",
product_data: {
name: storeItem.name,
},
unit_amount: storeItem.priceInCents,
},
quantity: item.quantity,
};
}),
success_url: `${process.env.CLIENT_URL}/success`,
cancel_url: `${process.env.CLIENT_URL}/failure`,
});
res.json({ url: session.url });
} catch (e) {
res.status(500).json({ error: e.message });
}
});
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
let event;
// Only verify the event if you have an endpoint secret defined.
// Otherwise use the basic event deserialized with JSON.parse
if (process.env.STRIPE_WEBHOOK_SECRET) {
// Get the signature sent by Stripe
const signature = req.headers["stripe-signature"];
try {
event = stripe.webhooks.constructEvent(
req.body,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.log(`⚠️ Webhook signature verification failed.`, err.message);
return res.sendStatus(400);
}
}
// Handle the event
switch (event.type) {
case "payment_intent.succeeded":
const paymentIntent = event.data.object;
console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`);
console.log(paymentIntent);
// Then define and call a methstripe loginod to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
break;
case "payment_method.attached":
const paymentMethod = event.data.object;
// Then define and call a method to handle the successful attachment of a PaymentMethod.
// handlePaymentMethodAttached(paymentMethod);
break;
case "payment_intent.payment_failed":
const failedpaymentIntent = event.data.object;
console.log(`PaymentIntent for ${paymentIntent.amount} failed!`);
// Then define and call a methstripe loginod to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
break;
case "checkout.session.completed":
console.log(event.data.object);
// console.log(`PaymentIntent for ${paymentIntent.amount} failed!`);
// Then define and call a methstripe loginod to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
break;
default:
// Unexpected event type
console.log(`Unhandled event type ${event.type}.`);
}
// Return a 200 response to acknowledge receipt of the event
res.send();
});```
In checkout.session.completed event, you will be able to find customer_details.email in the Checkout Session object to identify the customer.
Alternatively, your internal customer ID might be set under metadata parameter in Checkout Session Creation API. Metadata will be present in checkout.session.completed after successful payment which you can use to update the payment status in your database.
At the login time, you can use json web token (jwt) to send logged in user object in encrypted format and use local storage to store that token. and with request, send this token to backend and decode it. You will get user object from that and then you can able to find which user is sending request.
https://www.npmjs.com/package/jsonwebtoken
I'm trying to build an App which allows the customer to download a custom document after his order (so, the product I'm selling it's a digital custom document in pdf).
I'm trying to build that using React for the frontend, Node and Express for the backend, and PayPal Express Checkout (full-stack implementation) as payment.
What is not clear to me is what steps I should take to process an order.
I'm a this point:
Once customer clicks on PayPal button on client side it starts a call on http://localhost/api/paypal/orders endpoint which create the order on the Paypal side and return the PayPal order ID (e.g. id12345)
After the customer approves the payment on the PayPal popup, the client starts a call on http://localhost/api/paypal/orders/id12345/capture endpoint
then? What other endpoints should I crete on the server and what they should return? Maybe... 1 for save the actual store order on my MongoDB and the transaction details, 1 for create the invoice for the order, 1 for allowing the product download ???
Could you please clarify what steps I need to take, what are the endpoints I should create, and what each endpoints should return?
Maybe something like this?
You're trying to do too many things in too many routes. just create a route called something like process-order then you could have an async controller of that route which would contain separate functions for
Storing the order details in mongodb.
Create an invoice
Send a token that allows access to download page.
first , just await the function to complete then call the next.
exports.processOrder = async (req, res, next) => {
try {
const newOrder = await Order.create({
order: req.params.id,
details: req.body.details
})
const createInvoice = () => {
return newOrder._id;
}
const invoice = createInvoice();
const token = jwt.sign(
{
invoiceId = newOrder._id,
},
process.env.JWT_SECRET,
{
expiresIn: '24h'
}
);
return res.status(200).json({status: 'success', message: 'you have 24 hours to download your digital goods', token})
} catch (error) {
res.status(500).send(error)
}
}
This is a very basic idea, but basically store all of your info in one controller, then send a token with the id of the invoice, then when they go to download the book you would have a route where you verify the token. If it succeeds then the book is sent. like this:
app.use(
'/book-download',
expressJwt({ secret: process.env.JWT_SECRET, algorithms: ['HS256'] })
);
app.get('/book-download/success', async (req, res) => {
try{
const invoiceId = req.user.invoiceId;
const invoice = await Invoice.find({_id: invoiceId})
if (invoice) {
return res.status(200).json({status: 'success', message: 'congratulations on your new download', data: {E-book})
} else {
return.res.status(404).json({status: 'fail', message: 'could not find an invoice with that ID'})
} catch (error) {
return res.send(error)
}
});
You can choose to send the pdf via express, or you can allow them to enter a certain part of the website if the return is valid. There you go, that's an idea.
In Express check out, you need to define success / cancel handler
Ref here : https://www.section.io/engineering-education/nodejs-paypal-checkout/
In the success handler, based on the returned URI => you will get a Payer ID and payment ID. Based on that retrieve the transactio information (that internally contains yours order ID). Update your Order id as scucess / cancelled based on the paypal response.
app.get('/success', (req, res) => {
const payerId = req.query.PayerID;
const paymentId = req.query.paymentId;
const execute_payment_json = {
"payer_id": payerId,
"transactions": [{
"amount": {
"currency": "USD",
"total": "25.00"
}
}]
};
// Obtains the transaction details from paypal
paypal.payment.execute(paymentId, execute_payment_json, function (error, payment) {
//When error occurs when due to non-existent transaction, throw an error else log the transaction details in the console then send a Success string reposponse to the user.
if (error) {
console.log(error.response);
throw error;
} else {
console.log(JSON.stringify(payment));
res.send('Success');
}
});
});
I am following the docs here about enabling other business to accept payments directly.
I have successfully created a connected account and accepted a payment from a customer on the connected account, but I have not yet been able to save that customers payment information for future payments.
All across the documentation, especially here it assumes you already created a customer on the platform account WITH a payment method, before you should try and clone the payment method to the connected accounts.
I cannot for the life of me figure out how to create a customer with payment information on the platform account before I clone them on the connected account.
As per the documentation, I started here on the client side where the accountID is the ID of the connected account:
const [stripePromise, setstripePromise] = useState(() =>
loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, {
stripeAccount: uid.accountID,
})
);
My Stripe Elements are created with the stripePromise.
When trying to save the card details I do this on the client side:
This is where I believe my mistake is I am using the connected accounts credentials while trying to create a platform payment method.
const handleRememberMe = async () => {
const { token } = await stripe.createToken(
elements.getElement(CardElement)
);
console.log(token);
const res = await fetchPostJSON("/api/pay_performer", {
accountID: accountID,
amount: value,
cardToken: token,
});
That API call goes to "/api/pay_performer":
//Handle onboarding a new connect user for x
require("dotenv").config();
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: "2020-08-27",
});
import setCookie from "../../../utils/cookies";
export default async function createPayment(req, res) {
if (req.method === "POST") {
// Connected account ID and the token generated on the client to be saved.
const { accountID, amount, cardToken } = req.body;
console.log(`API side cardToken: ${cardToken.used}`);
try {
// Trying to create a customer with that token.
const customer = await stripe.customers.create({
email: "test2#example.com",
source: cardToken.card,
});
customer.sources = cardToken.card;
// Beginning the cloning process for the connected account.
const token = await stripe.tokens.create(
{
customer: customer.id,
},
{
stripeAccount: accountID,
}
);
const clonedCustomer = await stripe.customers.create(
{
source: token.id,
},
{
stripeAccount: accountID,
}
);
console.log(`Default Source: ${clonedCustomer.default_source}`);
console.log(`AccountID: ${accountID}, Amount: ${amount}`);
const paymentIntent = await stripe.paymentIntents.create(
{
payment_method_types: ["card"],
payment_method: clonedCustomer.default_source
? clonedCustomer.default_source
: null,
amount: amount * 100,
currency: "usd",
application_fee_amount: 1,
customer: clonedCustomer.id,
},
{
stripeAccount: accountID,
}
);
const secret = paymentIntent.client_secret;
console.log(secret);
const payment_method = paymentIntent.payment_method;
return res.status(200).send({ secret, payment_method });
} catch (e) {
console.log(e);
return res.status(500).send({ error: e.message });
}
}
}
But I get an error that the token provided is not a valid token. I am assuming this is because I created the token using the credentials for the connected account on the client and then tried to apply it to the platform account.
How do I have a separate UI to create platform customers FIRST, and then come back and clone them to the connected accounts upon purchase.
Should I not pass a token to the server and just pass the card information over HTTPS? Very lost and the documentation has not helped!
Any help is appreciated!
In order to create a Customer, collect card information and attach a PaymentMethod to them, you could for example use a SetupIntent and Elements [1] or create a PaymentMethod [2]. The core point being, if the system should initially store that information on the platform, the connected account isn't coming into play at all yet. Broadly speaking the steps are (not providing the account id of the connected account anywhere as this is only happening on the platform account), using the platform's secret key:
Create a customer
Collect card data using Stripe.js and Elements
Attach the pm_xxx to the customer in the backend
[1] https://stripe.com/docs/payments/save-and-reuse
[2] https://stripe.com/docs/js/payment_methods/create_payment_method
I have a checkout session:
app.post("/create-checkout-session", async (req, res) => {
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: converted_items,
mode: "payment",
success_url: process.env.URL + "/order-success.html",
cancel_url: process.env.URL + `/order-page.html`,
billing_address_collection: 'required',
});
res.json({ id: session.id, order: req.body });
});
And I would like to set up a webhook so that after a payment is successfully made, it collects the data from the customer (name, address, products purchased)
I copied this right from the webhook docs:
const fulfillOrder = (session) => {
// TODO: fill me in
console.log("Fulfilling order", session);
}
app.post('/webhook', bodyParser.raw({ type: 'application/json' }), (request, response) => {
const payload = request.body;
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(payload, sig, endpointSecret);
} catch (err) {
return response.status(400).send(`Webhook Error: ${err.message}`);
}
console.log(event.type)
// Handle the checkout.session.completed event
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
console.log("did order")
// Fulfill the purchase...
fulfillOrder(session);
}
response.status(200);
});
How I am setting it up is I am first running my server on localhost and then I am running the testing webhook command from the terminal
stripe listen --forward-to localhost:3000/webhook --events=checkout.session.completed
When I make a payment, it shows me these messages in the terminal:
It looks like you've made some modifications to that webhook handler code from the docs. If you're using Checkout and following the docs, what made you switch the code to look for charge.succeeded?
You should add some logging to figure out why your server is returning 400, but most likely the verification is failing in constructEvent. The snippet you've shared doesn't show you setting endpointSecret, but you need to update the value using the secret shown to you when you run stripe listen.
You should review the Checkout fulfillment docs and focus on checkout.session.completed events. You can modify your CLI command to listen to online these events:
stripe listen --forward-to localhost:3000/webhook --events=checkout.session.completed
then your handler would look like the docs and you can implement fulfillOrder to meet your needs. If you want the details you listed, then you will need to retrieve the session with expansion to include the line items and payment details:
const session = await stripe.checkout.sessions.retrieve(
'cs_test_123',
{
expand: ['line_items', 'payment_intent.payment_method']
},
);
Then session.line_items will be what you created and the name, address and email will be on session.payment_intent.payment_method.billing_details.
Updated to suggest removing signature verification temporarily to diagnose:
The 400 errors suggest your server is returning an error, most likely as a result of signature verification. I recommend testing a basic endpoint without verification following this example, to ensure everything else is working as intended. When that is settled, re-adding signature verification is strongly recommended.
Working on integrating stipe. Everything seems to work on the front end but on the server side code the token is empty and it is not successfully charging to Stripe. Can't seem to figure out where I'm going wrong.
app.post('/apple-pay', function(req, res, next) {
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = require("stripe")("sk_test_XXX");
// Token is created using Checkout or Elements!
// Get the payment token ID submitted by the form:
const token = req.body.stripeToken;
console.log(token)
const charge = stripe.charges.create({
amount: 999,
currency: 'usd',
description: 'Example charge',
source: token,
}, function(err, charge) {
if(err){
req.flash("error", err.message);
res.redirect("back");
} else {
}
});
});
Before Creating charge, You should create Customer. After Charge Works.
Sample Code. (ES6)
let customer = await payStripe.customers.create({
email: req.body.stripeEmail,
source: req.body.stripeToken
});
//After Created Customer...
if(customer){
let charge = await payStripe.charges.create({
amount: req.body.amount,
description: req.body.description,
currency: 'usd',
customer: customer.id
});
}
I hope it's work fine.
In your frontend code from the other question you passed the POST body as
JSON.stringify({token: ev.token.id})
which means that the Stripe token is actually in the token POST parameter, not stripeToken. So you need to do
const token = req.body.token;
instead.