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:
- An underlying Invoice object is created, via a call to the
v1/invoices
endpoint. It starts in adraft
status. This process fires aninvoice.created
event. - An Invoice Item is created separately, via the
v1/invoiceitems
endpoint, and appended to the Invoice. This process fires aninvoiceitem.created
event, followed by aninvoice.updated
event. - 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
toopen
.
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.
- 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.
- 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:
- By deleting an invoice that’s in
draft
status. This deletes the invoice record from Stripe. - 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.