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 or trial store, or a full production store
  • B2B Edition enabled in your store
  • Postman 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.

    FieldValue
    MethodPOST
    URLhttps://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.

    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.

    FieldValue
    MethodGET
    URLhttps://api-b2b.bigcommerce.com/api/v3/io/ip/invoices
    Headers”B2B REST” preset
  3. In the Params tab, add the following Query Params.

    Variable NameValue
    status0
    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.

    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).

    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.

    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.

    FieldValue
    MethodGET
    URLhttps://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.

    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.

    FieldValue
    MethodGET
    URLhttps://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.

    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.

    FieldValue
    MethodPOST
    URLhttps://api-b2b.bigcommerce.com/api/v3/io/ip/payments/offline
    Headers”B2B REST” preset
    Bodyraw / JSON
  3. In the Body tab, enter the following content.

    {
    "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.

    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.

    FieldValue
    MethodGET
    URLhttps://api-b2b.bigcommerce.com/api/v3/io/ip/payments
    Headers”B2B REST” preset
  3. In the Params tab, add the following Query Param.

    Variable NameValue
    invoiceId{{invoice_id}}
  4. In the Scripts tab, enter the following “Post-response” code.

    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.

    FieldValue
    MethodPUT
    URLhttps://api-b2b.bigcommerce.com/api/v3/io/ip/payments/:payment_id/processing-status
    Params”payment_id” value: {{payment_id}}
    Headers”B2B REST” preset
    Bodyraw / JSON
  3. In the Body tab, enter the following content.

    {
    "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.

    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.
  • Complete your invoices workflow by creating requests to update and delete an invoice.
  • Complete your payments workflow by creating requests to fetch an individual payment and delete a payment.

Resources