Peeking under the hood of Stripe Invoicing

/Article

Stripe Invoicing offers a no-code solution for sending invoices to customers. Because this option handles the complexity of all underlying API calls, developers sometimes struggle to understand the different phases a Stripe invoice goes through, which is problematic when attempting to debug payment failures.

This post uses the new Stripe Workbench debugging tool to analyze what happens behind the scenes as an Invoice goes through its lifecycle.

What is Workbench?

Workbench is a context-aware tool that allows you to build and debug your Stripe integrations from anywhere in the Dashboard. In response to feedback from Stripe developers, this new feature centralizes previously disparate developer tooling into one central and constantly accessible location. Among other things, you can use Workbench to inspect API objects and run requests on them using the built-in Shell.

To get started, navigate to the Workbench page in your dashboard and turn the feature on.

Invoice creation in the dashboard

Navigate to the Invoices tab in the dashboard and click on the Create test invoice option in the upper right corner. You’re then presented with the Invoice creation wizard, where you can populate all the relevant fields for your invoice. Specifically, make sure to add values for Customer and Items (from your product catalog).

Make sure to have Workbench viewable so you can observe the underlying API calls.

Exit the wizard by clicking the X in the upper left corner. At this point, your invoice is saved in a draft state.

Now take a look at the data map in the Inspector tab of Workbench. This shows you the JSON representation of all the objects created so far, and their hierarchy. Notably:

  • The Invoice is the top level object. It has a two associated objects:
  • Customer - the individual making the transaction
  • Invoice item - the item being purchased

The Invoice object itself comprises numerous fields, like status which tells you what phase it is currently in. This status is initially set to draft.

  • A Price object with an associated Product. This is a standalone object and not a direct child of the Invoice, which makes sense as products have a one to many relationship with invoices.

Next, switch to the Logs tab. This tab shows the recent API calls that have been made in your integration. In this example, there are two calls: one for Invoice creation and another for the Invoice Item creation.

Lastly, look at the Events tab to see which ones get fired as part of the invoice creation process.

The invoice.created and invoiceitem.created events align with what you see in the Logs tabs, namely the fact that an Invoice was created and an Invoice Item added to it.

There’s also an invoice.updated event that’s worth analyzing further. One way to do that is to look at the previous_attributes section at the bottom of the Event JSON. This shows you what attributes changed from the previous variation of the Invoice object, leading to the invoice.updated event being fired. The lines attribute seems particularly interesting for a further look.

In the same JSON for the invoice.updated event, look at the updated lines attribute. It shows that the Invoice Item was added as expected, but there’s also a new Invoice Line Item object. This is a nested resource automatically generated by Stripe to represent the individual line items in the invoice. It cannot be created directly through the API.

To recap, for Invoices created in the dashboard:

  1. An underlying Invoice object is created, via a call to the v1/invoices endpoint. It starts in a draft status. This process fires an invoice.created event.
  2. An Invoice Item is created separately, via the v1/invoiceitems endpoint, and appended to the Invoice. This process fires an invoiceitem.created event, followed by an invoice.updated event.
  3. There’s an underlying Invoice Line Item object automatically generated by the API

With the invoice is created, what happens next?

Sending the invoice to your customer

Navigate back to the previously created invoice in your dashboard, and click the Send invoice button to simulate sending it to your customer.

Check back to the data map in the Inspector tab of Workbench. There’s a new object in the Invoice hierarchy: a Payment Intent. This object is at the core of Stripe’s payment API. It’s a state machine which transitions through different phases over the course of the payment process.

Switching over to the Events tab, you see the events that are fired as part of sending the invoice.

One way to analyze invoice.updated events is to look at the previous_attributes section. In this case, the status is included here, which means its value is updated as part of this event.

The value of the status attribute in the Event JSON has changed to open.

Another noteworthy event is invoice.finalized. The event description indicates that finalizing is an operation on draft invoices. This changes the invoice’s status to open, meaning it’s ready for payment.

When an invoice is sent to the customer:

  • A Payment Intent object is created, indicating that you’re ready to collect a payment.
  • The Invoice is finalized and its status changes from draft to open.

Now you can simulate collecting payment from the customer.

Charging the customer

Navigate once again to the previously created invoice in your dashboard, and click the Charge customer button.

In the resulting pop-up window, you see a message about the invoice not being editable after payment attempts. This is important to note, as it signifies that whatever state the invoice ends up in after this process is terminal.

Proceed with charging the customer and switch back to the Inspector. There is another new object in the data map: a Charge. This represents an atomic charge operation and is created as part of the Payment Intent flow.

Switching to the Logs tab, there was a call to the Pay endpoint, as expected.

The Events tab shows various activities. The payment_intent. and charge. events confirm what you see in the Logs and Inspector tabs. Take a closer look at the invoice.updated event specifically.

Once again, the status shows up in the previous_attributes hash, which means it changed as part of this event.

The updated status is paid, which aligns with the invoice.payment_succeeded event that is fired.

As part of the invoice payment process:

  • The Payment Intent is captured - which creates a charge
  • The invoice transitions from `open” to “paid”

Canceling an invoice

There are two cases to consider here: an invoice has not been sent to the customer, or an invoice has already been sent.

  1. Invoice has not been sent to the customer

To simulate this, create a new invoice using the wizard. Exit out of the wizard to keep the invoice in draft state. Make sure you can see the invoice in the Inspector.

In the invoices page of the dashboard, click on the ellipsis and select the delete draft invoice option in the dropdown.

You are presented with a message about this action being irreversible, so this is a terminal state for the invoice, meaning that it cannot be further modified.

You can confirm with Workbench that the invoice record is gone from Stripe and is no longer accessible.

  1. Invoice has been sent to the customer

To simulate this, create an invoice using the wizard and send it to the customer, as previously shown. Then, find the invoice in the dashboard and choose the Change invoice status under the ellipsis in the right hand corner.

Here, additional statuses can potentially apply to invoices. Choose the Void status in the list and click on Update status.

The Logs tab confirms a call to the /invoices/void endpoint.

There are a few events triggered as part of this process:

  • The previously created Payment Intent gets canceled (payment_intent.canceled event), given that you no longer want to collect a payment on this invoice.
  • There’s an invoice.voided event corresponding to the /invoices/void call.

In the invoice.updated event details, the status once again changes. It was previously set to open:

After the void operation, the invoice status changes to void:

To recap, there are two ways of canceling invoices:

  1. By deleting an invoice that’s in draft status. This deletes the invoice record from Stripe.
  2. By voiding an invoice that’s in open status. This option preserves the invoice record, so you can use it for bookkeeping as needed.

Wrapping up

Using the various tools in Workbench, you can analyze any object in the Stripe dashboard and map out its hierarchy and observe its different state changes through the payment lifecycle. The approach you followed here with Invoices can be replicated to other parts of the Stripe API.

/About the author

David Edoh-Bedi

After starting his Stripe journey as an integration engineer helping large users build and scale their payment solutions, David pivoted to his current developer advocate role. He began his professional career as an engineer at Microsoft, working on various aspects of the Windows operating system. He’s passionate about all things data and connecting with developers from around the world. Outside of tech, he’s a huge soccer fan, avid reader, travel addict, and amateur triathlete.

/Related Articles
[ Fig. 1 ]
10x
Debugging your Stripe Invoicing integration with Workbench
With Stripe Invoicing, you can create and manage invoices for one-time and recurring payments. Whether caused by infrastructure issues or coding...
Workbench
Invoicing
[ Fig. 2 ]
10x
Bringing your Stripe objects to life with Workbench
This post shows how to use the Stripe Workbench Inspector to examine the lifecycle of a PaymentIntent object....
Workbench
Payment Intents