12

I just finished setting up payments through stripe on PHP using their payment intents API and forgot about having to add the products/items the user has just purchased.

Looking through the API Reference I didn't see anything where I could add items to the payment intent so I could retreive what was purchased later on in the webhook.

Do I need to do my own magic and somehow add the items to the metadata array or is there something that i'm missing here?

dobson
  • 461
  • 6
  • 14
  • 1
    PaymentIntents don't have a list of line items. I think you're mixing things up with a Checkout session. – ceejayoz Jan 10 '20 at 18:29

3 Answers3

16

PaymentIntents do not track any sort of product that's connected to the purchase (just an amount). In order to link Products to a payment, you'd want to use Invoices which will generate a PaymentIntent as part of the process.

taintedzodiac
  • 2,658
  • 1
  • 18
  • 16
  • 10
    It looks like you'd have to (1) create an invoice, (2) create an invoice item for each product in the order, (3) finalize the invoice to get a PaymentIntent, and (4) confirm the intent with a PaymentMethod. It'd be easier if they just added a line_items parameter when creating a PaymentIntent. – brandav Jan 11 '21 at 01:31
10

I'm using node.js. Here's what worked for me:

Steps

  1. Create customer object
  2. Create invoice item
  3. Create invoice
  4. Finalize invoice (returns a payment intent id but not a secret)
  5. Retrieve the payment intent object by id (secret key must be used).

Step #5 response will contain the client_secret value that can be sent to the client for payment capture.

Nodes.js code

const customer = ...
const invoiceItem = await stripe.invoiceItems.create({
  customer: ...
  price_data: { ... }
})
const invoice = await stripe.invoices.create({
  customer: ...
})
const finalInvoice = await stripe.invoices.finalizeInvoice(
  invoice.id
)
const paymentIntent = await stripe.paymentIntents.retrieve(
  finalInvoice.payment_intent
)

res.send({ secret: paymentIntent.client_secret })
srmark
  • 7,942
  • 13
  • 63
  • 74
1

The previous answer is not entirely correct. Perhaps the Stripe API documentation has changed. So what needs to be done:

  1. Create an invoice by passing the customer_id. Docs
  2. Create an invoice element by passing the customer_id, invoice_id (from step 1) + either price_id or amount and currency. Docs
  3. Then finalize the invoice (it will receive the status="open") and automatically receive the payment intent. Docs
  4. Request the desired payment intent, and get its secret_key. Docs
// Step 1: Create an invoice by passing the customer_id.
const stripe = require('stripe')('YOUR_STRIPE_SECRET_KEY');

async function createInvoice(customer_id) {
  try {
    const invoice = await stripe.invoices.create({
      customer: customer_id,
    });
    return invoice;
  } catch (error) {
    console.error('Error creating invoice:', error);
    throw error;
  }
}

// Step 2: Create an invoice item for the invoice obtained from the previous step, by passing the customer_id, invoice_id, and either price_id or amount and currency.
async function createInvoiceItem(customer_id, invoice_id, price_id, amount, currency) {
  try {
    const invoiceItem = await stripe.invoiceItems.create({
      customer: customer_id,
      invoice: invoice_id,
      price: price_id, // or amount and currency, depending on your choice
    });
    return invoiceItem;
  } catch (error) {
    console.error('Error creating invoice item:', error);
    throw error;
  }
}

// Step 3: Finalize the invoice to set its status to "open" and automatically receive the payment intent.
async function finalizeInvoice(invoice_id) {
  try {
    const finalizedInvoice = await stripe.invoices.finalizeInvoice(invoice_id);
    return finalizedInvoice;
  } catch (error) {
    console.error('Error finalizing invoice:', error);
    throw error;
  }
}

// Step 4: Request the payment intent for the invoice.
async function getPaymentIntent(invoice_id) {
  try {
    const paymentIntent = await stripe.paymentIntents.create({
      invoice: invoice_id,
    });
    return paymentIntent;
  } catch (error) {
    console.error('Error getting payment intent:', error);
    throw error;
  }
}

// Usage of all steps:
async function main() {
  try {
    const customer_id = 'YOUR_CUSTOMER_ID';
    
    // Step 1: Create an invoice
    const invoice = await createInvoice(customer_id);
    console.log('Created invoice:', invoice);

    // Step 2: Create an invoice item
    const price_id = 'YOUR_PRICE_ID'; // or use amount and currency if not using Price API
    const invoiceItem = await createInvoiceItem(customer_id, invoice.id, price_id, 1000, 'usd');
    console.log('Created invoice item:', invoiceItem);

    // Step 3: Finalize the invoice and get the payment intent
    const finalizedInvoice = await finalizeInvoice(invoice.id);
    console.log('Finalized invoice:', finalizedInvoice);

    // Step 4: Request the payment intent
    const paymentIntent = await getPaymentIntent(invoice.id);
    console.log('Payment intent:', paymentIntent);
  } catch (error) {
    console.error('Error:', error);
  }
}

main();
  • I was going to say bravo! However one issue I found, for step #4, it looks like you call Payment Intents 'create' which requires an amount. This is creating a new payment intent when step #3 creates one for you. I just altered step #4 to retrieve a payment intent using finalizeInvoice's provided payment intent. And then you can retrieve the client secret to pass to the client side to submit payment. Step #4 const paymentIntent = await stripe.paymentIntents.retrieve(finalizedInvoice.payment_intent); console.log('Payment intent:', paymentIntent.client_secret); – Andrew Jul 05 '23 at 01:53