I'm currently using Stripe Webhooks to get notified when user pays for a product. This is working fine. From the payment intent I can get the Seesion and the Customer Object. But I don't find a way to get the product_id or price_id for what the user paid.
Does someone know a way to get product_id or price_id ?
Thanks for the question. As you noticed the Session data included in the checkout.session.completed event does not include the line_items where the Price ID is associated to the Checkout Session.
line_items is one of the expandable properties, so to retrieve the Price ID you'd retrieve the Checkout Session and use expand to include the line items in the response. There is not a way to configure your webhook to have the data sent to you include this data.
There are two approaches to associating a customer's purchase with a Checkout Session. First, you could store the ID of the Checkout Session in your database alongside the cart or list of items purchased by the customer. That way you if a checkout session is successful, you can look up the cart by ID and know which items were purchased.
Alternatively you could listen for the checkout.session.completed webhook event, then when you receive a new notification for a successful checkout, retrieve the Session with expand then use the related price data.
Using stripe-node that would look like the following:
const session = await stripe.checkout.sessions.retrieve(
'cs_test_xxx', {
expand: ['line_items'],
},
);
// note there may be more than one line item, but this is how you access the price ID.
console.log(session.line_items.data[0].price.id);
// the product ID is accessible on the Price object.
console.log(session.line_items.data[0].price.product);
To take this a step further, if you wanted more than just the ID of the product, you could also expand that by passing line_items.data.price.product which would include the line items their related prices and the full product objects for those prices.
While creating the Payment Intent, you can store additional information about the object in the metadata field.
const paymentIntent = await stripe.paymentIntents.create({
amount: 1099,
currency: 'usd',
payment_method_types: ['card'],
metadata: {
product_id: '123',
price_id: '20',
},
});
Once the payment is done, you can retrieve this information from the metadata field.
You can do the same for the Session Object as well.
cjav_dev answer is great! Here is the PHP code for the same.
$event = \Stripe\Event::constructFrom(
json_decode($payload, true), $sig_header, $endpoint_secret
);
$eventObject = $event->data->object;
$stripe = new StripeClient('testsk_ssdfd...sdfs');
$csr = $stripe->checkout->sessions->retrieve($eventObject->id,
['expand' => ['line_items']]
);
$priceid = $csr->line_items['data'][0]['price']['id'];
Note the above is only retrieving the 1st line item. You may have to do a loop for all items.
Related
I'm doing a small project in Node and React
And I'm building a payment system with the help of Stripe
Now everything works fine I have only one small problem I want at the end of the process to update the quantity in the database the problem is that I can't make the right condition to do it
and I must a condition Because if I don't set a condition as soon as I activated Stripe and got to the payment page, even if I didn't pay and did a return to the previous page, my quantity is updated
I add here the code I try to implement
exports.getCheckoutSessionBook = async (req, res, next) => {
const book = req.body.bookID[0]
console.log(book._id)
const quantity = req.body.quantityBooks
if (book.quantity < quantity) return next(new AppError(`there is only ${book.quantity} in stuck `))
if (book.quantity <= 0) return next(new AppError(`this book is over please try another book `))
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
success_url: `http://127.0.0.1:3001`,
cancel_url: `http://127.0.0.1:3001/account`,
customer_email: req.user.email,
client_reference_id: req.user.id,
line_items: [{
price_data: {
currency: 'usd',
product_data: {
name: book.title,
description: book.description,
images: [book.image],
},
unit_amount: book.price * 100,
},
quantity: quantity,
}],
mode: 'payment',
});
res.status(200).json({
status: 'suceess',
session
})
//here is my problem
if (res.status == 200) {
console.log(321)
const doc = await Book.findByIdAndUpdate(book._id, { $inc: { quantity: - quantity } }, {
new: true,
runValidators: true
})
console.log(333)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The code you shared calls the Create Checkout Session API first. This creates a Session that indicates what you are trying to sell to your Customer. At that point, no payment has been taken yet though. After that call, you seem to immediately increase the quantity in your database, before the payment happened.
There's no condition you can add here because the payment will happen later so you need to delete that part of the code or move it elsewhere entirely.
After the Checkout Session is created, you get the url property back in the response. That is the URL that you should redirect your customer to in the browser. There, they'll see Checkout's UI and decide if they want to pay.
What you should do, is listen for the checkout.session.completed Event that will be sent to your webhook endpoint when a Session gets paid/completed successfully. At that point, you can update the quantity in your database.
Stripe has a detailed documentation on fulfillment with Checkout in this doc.
I must confess I'm not familiar with the Stripe API, but your approach to what is essentially a distributed transaction is giving me anxiety.
Your system needs assume it may crash at any point and still be able to any transaction.
Basically you need a series of interconnected backend systems using a ledger system in the database.
A ledger is an ordered set of actions taken on a transaction that allows you to see what has happened so far and pick up the transaction from where it was left.
Basic steps in an order transaction could be:
Create the order.
Allocate the inventory to the order.
Get an ID from the payment provider and associate it with the order.
Send the customer to complete the payment.
When payment is complete move the order ready for shipping.
Handle shipping.
Close the order and update inventory.
Some notes:
Inventory is not changed up/down for every order open/cancelled/closed inventory is updated once the item is actually shipped. Inventory for sale is inventory on hand minus any inventory allocated to orders.
Note also that if your system crashes (or the client disconnects) at any point you can reconcile the orders.
Failure after step 1 -> close the order.
Failure after step 2 -> close the order (inventory becomes immediately available since it is no longer allocated).
Failure after step 3 -> check the status of the payment using the session ID with the payment provider and either close the order or continue to step 4.
etc.
Final note: whether orders fails if inventory cannot be allocated to orders varies based on whether you can order more inventory or not.
If all this sounds complex it's because it is. Making an online store front system is not a weekend project.
I'm trying to get the Customer ID/Charge ID after the purchase and to send the Customer ID/Charge ID in database for future use.
import StripeCheckout from 'react-stripe-checkout';
onToken = async (token) => {}
<StripeCheckout
stripeKey='pk_test_51JG'
token={this.onToken}
amount={this.state.grandTotal * 100}
name='Payment'/>
The recommended way is to set up a webhook endpoint and subscribe to checkout.session.completed events: https://stripe.com/docs/payments/checkout/fulfill-orders#handle-the-event
Depending on what exactly you mean by "transaction ID" the information you need is likely already contained there, or can be retrieved from Stripe using the IDs available.
I'm trying to use SetupIntents to validate and save a Payment Method and then create a Subscription using it to charge the customer (inmediately and then monthly) for the required amount.
This appears to be working fine:
The Card is validated (including SCA if needed)
The Payment Method is created, attached to customer as default and enabled for future usage with the status SUCEEDED.
The Subscription is created and uses the above payment method
The problem is that Stripe then generates the corresponding Invoice and Payment Intent but the latter with the status "requires_action" whenever the provided Card requires Secure Customer Authorization (SCA), even though the right payment method (enabled for future usage) is being used and the card validation has been already performed.
I thought the whole point of using SetupIntents was precisely to validate the payment method beforehand and be able to charge the customer afterwards.
Is my assumption simply wrong or this is actually possible and I might just be missing something?
Thanks in advance
EDIT: This is the subscription creation code in the backend:
# Set the default payment method on the customer
Stripe::Customer.update(
stripe_customer_id,
invoice_settings: {
default_payment_method: #requested_source
}
)
subscription = Stripe::Subscription.create({
"customer" => stripe_customer_id,
"proration_behavior" => 'create_prorations',
"items" => [
[
"plan" => "#{#stripe_plan_api_id}",
],
],
'default_tax_rates' => [
"#{#stripe_tax_rate_id}",
],
"expand" => ["latest_invoice.payment_intent"]
});
Thanks for the question, Eduardo.
There are a couple ways to create a Subscription while gaining the Customers SCA authentication and permission to charge the card later. Assuming we already have a Stripe Customer object created and their ID is stored in stripe_customer_id. With the flow you have now there are a couple steps:
Create the SetupIntent. Note that if you create it with usage: 'off_session' and the Customer it'll attach the resulting PaymentMethod when confirmed.
setup_intent = Stripe::SetupIntent.create({
customer: stripe_customer_id,
usage: 'off_session',
})
Collect payment details and confirm the SetupIntent using its client_secret on the client which will attach the PaymentMethod to the customer. Note that it will attach but will not be set as the invoice_settings.default_payment_method by default, so you'll need to make a separate API call later to update the Customer (see step 3).
stripe.confirmCardSetup(
'{{setup_intent.client_secret}}',
{
payment_method: {
card: cardElement,
},
}
).then(function(result) {
// Handle result.error or result.setupIntent
});
Update the Customer and set its invoice_settings.default_payment_method equal to the ID of the PaymentMethod on the successfully confirmed SetupIntent.
Stripe::Customer.update(
stripe_customer_id, {
invoice_settings: {
default_payment_method: 'pm_xxxx', # passed to server from client. On the client this is available on the result of confirmCardSetup
}
})
Create the Subscription with off_session: true
subscription = Stripe::Subscription.create({
customer: stripe_customer_id,
proration_behavior: 'create_prorations',
items: [{
plan: "#{#stripe_plan_api_id}",
}],
default_tax_rates: [
"#{#stripe_tax_rate_id}",
],
expand: ["latest_invoice.payment_intent"],
off_session: true,
})
This uses what's called a "Merchant Initiated Transaction" (MIT) for the Subscription's first payment. This is technically okay if the Subscription is created later after the Customer leaves and should technically work.
If the customer is on your site/app when you create the Subscription, there's another flow that is a bit more correct and doesn't require using a MIT exemption for SCA. The flow is the following for a Subscription without a trial:
Collect card details with createPaymentMethod on the client (no SetupIntent)
stripe.createPaymentMethod({
type: 'card',
card: cardElement,
}).then(function(result) {
//pass result to your server.
})
Attach those card details to the Customer
Stripe::PaymentMethod.attach(
"pm_xxx", {
customer: stripe_customer_id
}
)
Update the Customer's invoice_settings.default_payment_method
Stripe::Customer.update(
stripe_customer_id,
invoice_settings: {
default_payment_method: #requested_source
}
)
Create the Subscription (without off_session: true)
subscription = Stripe::Subscription.create(
customer: data['customerId'],
items: [
{
price: 'price_H1NlVtpo6ubk0m'
}
],
expand: ['latest_invoice.payment_intent']
)
Use the Subscription's latest_invoice's payment_intent's client_secret to collect payment details and confirm on the client.
stripe.confirmCardPayment(
'{{subscription.latest_invoice.payment_intent.client_secret}}', { ......
This second payment flow is a bit more correct from an SCA standpoint for getting authorization to charge the card. The second approach is outlined in the guide here: https://stripe.com/docs/billing/subscriptions/fixed-price
We also have a Stripe Sample you can use to experiment here: https://github.com/stripe-samples/subscription-use-cases/tree/master/fixed-price-subscriptions
As the title says, I am trying to make a payment using a one-time source (credit card) that is not being saved in the customer's profile. I still want to be able to record that charge on the customer's profile on Stripe.
According to the docs, this can be done by passing the customer's id in customer key in the payload sent to Stripe.customers.createCharge. However, on doing that, I receive an error stating the card is not linked with the customer (which obviously is not, and I don't want it to be).
Customer cus_*** does not have card with ID tok_visa
To get around this, temporarily I have applied the fix mentioned in this answer which basically involves creating a temporary card for the payment and then deleting it.
I was wondering how does the API not work when it is clearly documented otherwise, hope someone experienced with Stripe chimes in on the same.
Here is the code I'm trying to get to run:
await stripeService.charges.create({
source: token, // temporary token received from Checkout
customer: user.stripeCustomerId, // the Stripe customer ID from my database
amount,
currency: 'usd',
description: 'Test charge'
});
The single-use sources document that you link to explicitly only works with Sources, but tok_visa will create a Card object instead. I believe that's why you get the error.
If you try the code with a Source(it has an ID like 'src_xxx') that you obtain through Elements/Checkout on your frontend with, for example, createSource, it will succeed, I've just tested it. You can also test with this code :
const src = await stripe.sources.create({
type : "card",
token : "tok_visa"
});
const charge = await stripe.charges.create({
source : src.id,
customer : "cus_xxxx",
amount : 1000,
currency : "usd"
});
I was able to achieve this by creating the source first followed by the charge where you specify the customerId and sourceId as below:
//Create the source first
var options = new SourceCreateOptions
{
Type = SourceType.Card,
Card = new CreditCardOptions
{
Number = Number,
ExpYear = ExpYear,
ExpMonth = ExpMonth,
Cvc = Cvc
},
Currency = "gbp"
};
var serviceSource = new SourceService();
Source source = serviceSource.Create(options);
//Now do the payment
var optionsCharge = new ChargeCreateOptions
{
Amount = 500,
Currency = "gbp",
Description = "Your description",
SourceId = source.Id,
CustomerId = "yourcustomerid"
};
var service = new ChargeService();
service.Create(optionsCharge);
result = "success";
Using node-recurly. The idea is to create a charge without generating an invoice, then create a subscription and have recurly attach the charge to the subscription invoice. However, when I create a charge, the invoice gets generated automatically for it, so the user gets two separate invoices in the email: one for the charge, and one for the subscription.
This is the charge object that I use:
const shippingCharge = {
amount_in_cents: parseFloat(shippingMethod.amount) * 100,
currency: 'USD',
description: `${shippingMethod.provider} ${shippingMethod.servicelevel_name} shipping`,
account: {
account_code: activationCode,
},
};
I pass it to this function that creates a charge:
recurly.transactions.create(chargeObject, (response) => {
... blah blah blah
});
recurly.subscriptions.create is being called next (calls are being made sequentially using promises). The end result is two invoices instead of one.
Recurly's documentation is confusing. When I was trying to create a charge, I made an assumption that I have to create a transaction. After contacting support, I was provided with the link to create a charge. If you look at the code examples on the right, they reference Recurly_Adjustment, not transaction object. So to create a charge I have to create an adjustment, not a transaction. Switching to proper API call fixed the issue and I received a single invoice.
Alex is correct. You will also need to use revenue_schedule_type: at_invoice if you want the charges together. The Recurly API docs do not include NodeJS examples. Here you go:
return recurly.adjustments.create(accountId, {
unit_amount_in_cents: parseFloat(shippingMethod.amount) * 100,
currency: 'USD',
description: `${shippingMethod.provider} ${shippingMethod.servicelevel_name} shipping`,
revenue_schedule_type: 'at_invoice',
accounting_code: accountingCode,
}).then(() => {
// ...create an invoice, subscription, or whatever
});