> This page is for Developer.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.bigcommerce.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.bigcommerce.com/_mcp/server.

# Lab - Postman Invoices and Payments Workflow

Plan: B2B Developer

Lesson 16 of 19 · 45 min

## Introduction

In this lab, you'll complete a standard purchasing workflow by managing invoices and their payments.

### Prerequisites

* A BigCommerce [sandbox store](/developer/docs/overview/sandboxes) or [trial store](https://www.bigcommerce.com/essentials/), or a full production store
* B2B Edition enabled in your store
* [Postman](https://www.postman.com/) or a similar API client
* An existing Postman environment, collection, and headers preset as configured in previous labs
* An existing company and Admin or Senior Buyer user login
* Minimal invoice settings as described below

### In this lab, you will:

* Create API requests to inspect and manage invoices and payments
* Implement Postman scripts to automate values used in API requests

### Required Invoice Settings

The lab will involve logging into your storefront as a company user and placing an order to pay an invoice. The following minimal settings are required in the B2B Edition section of your control panel:

* In Settings > General, "Invoices" must be checked under "Feature management."
* In Settings > Invoice, at least one valid option must be enabled under "Payment methods for paying invoices."

If you don't have a valid payment method option available in the invoice settings, make sure you have enabled a method other than Check/Purchase Order in the main Settings > Payments section of the BigCommerce control panel.

## Postman Recap

In addition to the usual environment variables and headers preset, make sure you have the following collection variables with valid values:

* `company_id`, which was originally set by your "Create Company" request
* `order_id`, which was set by the "Get Recent Orders" request in the previous lab

If these collection variables are not set, you can manually set the values using the order you recently placed and the company it belongs to. (Use the "Get Companies" request if you need to find the company ID value.)

## Step 1: Create an Invoice

Sales reps can create invoices for orders at any time from your B2B Edition admin, but if your implementation involves managing orders and payments in an external system, it's common for invoice creation to be handled via API instead.

1. **Create** a new HTTP request and **save** it to your collection with the name "Create Invoice from Order."

2. **Configure** the request with the following details.
   | Field   | Value                                                                    |
   | ------- | ------------------------------------------------------------------------ |
   | Method  | POST                                                                     |
   | URL     | `https://api-b2b.bigcommerce.com/api/v3/io/ip/orders/:order_id/invoices` |
   | Params  | "order\_id" value: `{{order_id}}`                                        |
   | Headers | "B2B REST" preset                                                        |

3. In the Scripts tab, **enter** the following "Post-response" code.

   ```js showLineNumbers={false}
   pm.test('Response is not an error', () => {
       pm.response.to.not.be.error;
       pm.response.to.not.have.jsonBody("errors");
   });
   pm.test('Response is JSON with data', () => {
       pm.response.to.have.jsonBody("data");
   });

   const message = pm.response.json().meta?.message;
   pm.test('Operation was successful', () => {
       pm.expect(
           message
       ).to.be.eq('SUCCESS');
   });

   const invoice = pm.response.json().data;
   pm.test('Response includes invoice with ID', () => {
       pm.expect(
           invoice?.invoiceId
       ).to.be.a('number');
   });

   pm.collectionVariables.unset('invoice_id');
   pm.collectionVariables.set('invoice_id', invoice?.invoiceId);
   ```

   In this case, you're taking advantage of the fact that the invoice originates with your API request to capture the `invoice_id` that will be used in subsequent requests in a collection variable.

   Note that although this is a POST request, there is no request body. This request is creating an invoice from the details of the order, so beyond the order ID in the URL, there is no required input.

4. **Send** the request, **verify** that all tests succeed, and **verify** that the `invoice_id` variable is set on your collection.

With the new invoice created, you should also be able to see the details in the B2B Edition admin, or in the Buyer Portal when logged in as one of the company's users.

## Step 2: Get Invoices

The next two requests will be dedicated to the retrieval of invoice data.

With quotes and orders, we practiced fetching *recent* records as a likely common workflow. For invoices, we'll take the opposite approach: a common scenario for many integrations would be to inspect invoices that are *older* than a certain date and still unpaid. The initial configuration of the request will fetch invoices older than 30 days.

1. **Create** a new HTTP request and **save** it to your collection with the name "Get Old Open Invoices."

2. **Configure** the request with the following details.
   | Field   | Value                                                   |
   | ------- | ------------------------------------------------------- |
   | Method  | GET                                                     |
   | URL     | `https://api-b2b.bigcommerce.com/api/v3/io/ip/invoices` |
   | Headers | "B2B REST" preset                                       |

3. In the Params tab, **add** the following Query Params.

   | Variable Name | Value                        |
   | ------------- | ---------------------------- |
   | status        | 0                            |
   | endDateAt     | `{{invoice_max_created_at}}` |

   Like in past requests, you're referencing a collection variable that doesn't exist here as the value of "endDateAt". The "Pre-request" you'll set up next will take care of calculating and setting this variable before the request.

   > **Info**
   >
   > Try out the effects of other filter params as well, including `beginDateAt`, `limit`, `offset`, `customerId`, and `customerName`.

4. In the Scripts tab, **enter** the following "Pre-request" code (not "Post-response" as usual).
   ```js showLineNumbers={false}
   const days = 30;

   const d = new Date();
   d.setSeconds(d.getSeconds() - (days * 24 * 60 * 60));

   pm.collectionVariables.unset('invoice_max_created_at');
   pm.collectionVariables.set('invoice_max_created_at', Math.round(d.valueOf() / 1000));
   ```

5. In the Scripts tab, **enter** the following "Post-response" code.
   ```js showLineNumbers={false}
   pm.test('Response is not an error', () => {
       pm.response.to.not.be.error;
       pm.response.to.not.have.jsonBody("errors");
   });
   pm.test('Response is JSON with data', () => {
       pm.response.to.have.jsonBody("data");
   });

   const invoices = pm.response.json().data;
   pm.test('Response includes at least one invoice', () => {
       pm.expect(
           invoices
       ).to.be.a('array');

       pm.expect(
           invoices?.length
       ).to.be.greaterThan(0);
   });
   ```

6. **Send** the request.

   Remember that the filter applied to the request will exclude any invoices newer than 30 days. If you happen to have invoices older than that in your store, you may have results, but you definitely won't see your newly created invoice.

7. Temporarily **un-check** the "endDateAt" param in the Params tab, **re-send** the request, and **verify** that all tests succeed.

   The next request will fetch an individual invoice's details, and it's here that you'll take advantage of your stored ID to automatically fetch the recently created invoice.

8. **Create** a new HTTP request and **save** it to your collection with the name "Get Invoice."

9. **Configure** the request with the following details.
   | Field   | Value                                                               |
   | ------- | ------------------------------------------------------------------- |
   | Method  | GET                                                                 |
   | URL     | `https://api-b2b.bigcommerce.com/api/v3/io/ip/invoices/:invoice_id` |
   | Params  | "invoice\_id" value: *`{{invoice_id}}`*                             |
   | Headers | "B2B REST" preset                                                   |

10. In the Scripts tab, **enter** the following "Post-response" code.
    ```js showLineNumbers={false}
    pm.test('Response is not an error', () => {
        pm.response.to.not.be.error;
        pm.response.to.not.have.jsonBody("errors");
    });
    pm.test('Response is JSON with data', () => {
        pm.response.to.have.jsonBody("data");
    });

    const invoice = pm.response.json().data;
    pm.test('Response contained an invoice with ID', () => {
        pm.expect(
            invoice?.id
        ).to.be.a('number');
    });
    ```

11. **Send** the request and **verify** that all tests succeed.

## Step 3: Export Invoice PDF

1. **Create** a new HTTP request and **save** it to your collection with the name "Export Invoice PDF."

2. **Configure** the request with the following details.
   | Field   | Value                                                                            |
   | ------- | -------------------------------------------------------------------------------- |
   | Method  | GET                                                                              |
   | URL     | `https://api-b2b.bigcommerce.com/api/v3/io/ip/invoices/:invoice_id/download-pdf` |
   | Params  | "invoice\_id" value: *`{{invoice_id}}`*                                          |
   | Headers | "B2B REST" preset                                                                |

3. In the Scripts tab, **enter** the following "Post-response" code.
   ```js showLineNumbers={false}
   pm.test('Response is not an error', () => {
       pm.response.to.not.be.error;
       pm.response.to.not.have.jsonBody("errors");
   });
   pm.test('Response is JSON with data', () => {
       pm.response.to.have.jsonBody("data");
   });

   const url = pm.response.json().data?.url;
   pm.test('Response contained a download URL', () => {
       pm.expect(
           url
       ).to.be.a('string');
   });
   ```

4. **Send** the request and **verify** that all tests succeed.

5. **Copy** the URL value from the response and **enter** it in a browser to verify that the URL results in downloading a PDF with the details of the invoice.

## Step 4: Apply an Invoice Payment

Invoice payment will commonly be handled directly in the shopper-facing Buyer Portal by company users. However, if your purchasing workflow involves handling payment in an external system, an API flow to import such payments as "offline payments" in B2B Edition will likely be part of your integration.

In this request, you'll apply a *partial* payment to the recently created invoice.

1. **Create** a new HTTP request and **save** it to your collection with the name "Create Offline Payment."

2. **Configure** the request with the following details.
   | Field   | Value                                                           |
   | ------- | --------------------------------------------------------------- |
   | Method  | POST                                                            |
   | URL     | `https://api-b2b.bigcommerce.com/api/v3/io/ip/payments/offline` |
   | Headers | "B2B REST" preset                                               |
   | Body    | raw / JSON                                                      |

3. In the Body tab, **enter** the following content.

   ```json showLineNumbers={false}
   {
       "lineItems": [
           {
               "invoiceId": {{invoice_id}},
               "amount": <Amount>
           }
       ],
       "currency": "USD",
       "details": {
           "memo": "Paid by check"
       },
       "customerId": "{{company_id}}",
       "payerName": "John Doe",
       "processingStatus": 2
   }
   ```

   Note that the body is setting the payment's `processingStatus` to 2, which indicates a "processing" status.

4. **Replace** `<Amount>` in the request body with a numerical value that is *less* than the full invoice amount. (View the invoice in the B2B Edition admin or with your "Get Invoice" request if you need a reminder of the full open balance.)

5. In the Scripts tab, **enter** the following "Post-response" code.

   ```js showLineNumbers={false}
   pm.test('Response is not an error', () => {
       pm.response.to.not.be.error;
       pm.response.to.not.have.jsonBody("errors");
   });
   pm.test('Response is JSON with data', () => {
       pm.response.to.have.jsonBody("data");
   });

   const message = pm.response.json().meta?.message;
   pm.test('Operation was successful', () => {
       pm.expect(
           message
       ).to.be.eq('SUCCESS');
   });

   const paymentId = pm.response.json().data?.paymentId;
   pm.test('Response included payment ID', () => {
       pm.expect(
           paymentId
       ).to.be.a('number');
   });

   pm.collectionVariables.unset('payment_id');
   pm.collectionVariables.set('payment_id', paymentId);
   ```

   This script captures the `payment_id` of the new payment in a collection variable, for use in a later request to update its status.

6. **Send** the request and **verify** that all tests succeed.

7. **Re-run** the "Get Invoice" request and **observe** the updated details of the invoice's open balance and `pendingPaymentCount`.

You can also log into your storefront as an Admin user for the company associated with the invoice and view the details of the invoice on the Invoice page. The amount due should reflect the partial payment.

## Step 5: Get an Invoice's Payments

The next request will fetch all payments related to the invoice you've been working with.

1. **Create** a new HTTP request and **save** it to your collection with the name "Get Invoice Payments."

2. **Configure** the request with the following details.
   | Field   | Value                                                   |
   | ------- | ------------------------------------------------------- |
   | Method  | GET                                                     |
   | URL     | `https://api-b2b.bigcommerce.com/api/v3/io/ip/payments` |
   | Headers | "B2B REST" preset                                       |

3. In the Params tab, **add** the following Query Param.
   | Variable Name | Value              |
   | ------------- | ------------------ |
   | invoiceId     | *`{{invoice_id}}`* |

4. In the Scripts tab, **enter** the following "Post-response" code.
   ```js showLineNumbers={false}
   pm.test('Response is not an error', () => {
       pm.response.to.not.be.error;
       pm.response.to.not.have.jsonBody("errors");
   });
   pm.test('Response is JSON with data', () => {
       pm.response.to.have.jsonBody("data");
   });

   const payments = pm.response.json().data;
   pm.test('Response contains at least one payment', () => {
       pm.expect(
           payments
       ).to.be.a('array');

       pm.expect(
           payments?.length
       ).to.be.greaterThan(0);
   });
   ```

5. **Send** the request and **verify** that all tests succeed.

## Step 6: Apply Payment as a User

If you used a payment amount less than the invoice's total open balance, the invoice still has a balance remaining.

Instead of applying an offline payment for the remaining amount, let's perform a BigCommerce checkout to pay it "online" as a company user.

1. **Log in** to your storefront as an Admin user for the company the invoice is associated with.

2. **Find** the invoice in the Invoice tab and **select** "Pay" from the invoice's action menu.

3. **Complete** the BigCommerce checkout to finish paying the open invoice balance.

4. **Re-run** the "Get Invoice Payments" request.

   You should now observe the multiple existing payments for the current invoice, with the most recent reflecting the details of an online transaction.

5. **Re-run** the "Get Invoice" request to observe the new *status* and open balance.

## Step 7: Update a Payment Status

You previously created an offline payment in "processing" status, and your invoice details should reflect one "pending" payment in the `pendingPaymentCount`.

Let's create a final request to simulate the update you would send to B2B Edition in response to the payment being settled in your external system. This will rely on the `payment_id` captured when the offline payment was first created.

1. **Create** a new HTTP request and **save** it to your collection with the name "Update Payment Status."

2. **Configure** the request with the following details.
   | Field   | Value                                                                                 |
   | ------- | ------------------------------------------------------------------------------------- |
   | Method  | PUT                                                                                   |
   | URL     | `https://api-b2b.bigcommerce.com/api/v3/io/ip/payments/:payment_id/processing-status` |
   | Params  | "payment\_id" value: *`{{payment_id}}`*                                               |
   | Headers | "B2B REST" preset                                                                     |
   | Body    | raw / JSON                                                                            |

3. In the Body tab, **enter** the following content.

   ```json showLineNumbers={false}
   {
       "processingStatus": 3
   }
   ```

   You're setting the status code 3, indicating the payment is now complete.

4. In the Scripts tab, **enter** the following "Post-response" code.
   ```js showLineNumbers={false}
   pm.test('Response is not an error', () => {
       pm.response.to.not.be.error;
       pm.response.to.not.have.jsonBody("errors");
   });
   pm.test('Response is JSON with data', () => {
       pm.response.to.have.jsonBody("data");
   });

   const message = pm.response.json().meta?.message;
   pm.test('Operation was successful', () => {
       pm.expect(
           message
       ).to.be.eq('SUCCESS');
   });
   ```

5. **Send** the request and **verify** that all tests succeed.

6. **Re-run** the "Get Invoice" request and **observe** the `pendingPaymentCount`, which should now reflect no pending payments.

## Taking It Further

* It's also possible to create an ad-hoc invoice for an order with full control over the details. Create a request to [create a full invoice](/developer/api-reference/rest/b2b/management/invoice-management/invoice/post-invoices).
* Complete your invoices workflow by creating requests to [update](/developer/api-reference/rest/b2b/management/invoice-management/invoice/put-invoices-invoice-id) and [delete](/developer/api-reference/rest/b2b/management/invoice-management/invoice/delete-invoices-invoice-id) an invoice.
* Complete your payments workflow by creating requests to fetch an [individual payment](/developer/api-reference/rest/b2b/management/invoice-management/payments/get-payments-payment-id) and [delete](/developer/api-reference/rest/b2b/management/invoice-management/payments/delete-a-payment) a payment.

## Resources

* [Invoice Management](/developer/api-reference/rest/b2b/management/invoice-management/invoice)
* [Payments](/developer/api-reference/rest/b2b/management/payment)