Lab - Postman Order and Payment Workflow

Plan: B2B Developer

Lesson 16 of 18 · 45 min

Introduction

In the previous lab, you built an API workflow to submit a quote from a storefront user and then initiate a checkout with that quote, ultimately completing an order. Now you’ll complete a common purchasing workflow with tasks to view a company’s orders, initiate a checkout for invoice payment, and inspect existing 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
  • Minimal invoice settings as described below

In this lab, you will:

  • Create API requests to handle viewing orders, viewing and initiating payment on invoices, and viewing payments
  • Implement Postman scripts to automate values used in API requests
  • Practice sending quote-related requests from the perspective of users with different roles/permissions

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

As in the previous lab, you will need to authenticate using the “Admin Login” request, powered by valid credentials in the admin_email and admin_password variables on your collection.

Step 1: Get Orders

If you haven’t already placed an order from your previously created quote, make sure you have at least one placed order using the Purchase Order payment method under the company matching your Admin user.

  1. If necessary, re-run the “Junior Buyer Login” request to regenerate your GraphQL authentication token and other details.
  2. Create a new HTTP request and save it to your collection with the name “Get Orders.”
  3. Set the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created “GraphQL” preset), and Body type.
  4. Enter the following query.
query GetOrders(
$limit: Int,
$after: String,
$orderBy: String
) {
allOrders(
first: $limit,
after: $after,
orderBy: $orderBy
) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
edges {
node {
id
bcOrderId
createdAt
userId
bcCustomerId
companyId {
id
companyName
}
totalIncTax
currencyCode
items
poNumber
status
shippingAddress
shipments
products
customer
companyName
firstName
lastName
}
}
}
}
  1. Enter the following in the GraphQL Variables panel.
{
"limit": 5,
"after": null,
"orderBy": "-createdAt"
}

Like previous requests, the arguments and GraphQL variables set up in this request can easily facilitate pagination with a sufficient number of orders.

  1. 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 result = pm.response.json().data?.allOrders;
pm.test('Total count includes at least one order', () => {
pm.expect(
result?.totalCount
).to.be.greaterThan(0);
});
let firstOrder = null;
if (Array.isArray(result?.edges)) {
firstOrder = result.edges[0]?.node;
}
pm.test('Response includes at least one order with an ID', () => {
pm.expect(
firstOrder?.id
).to.be.a('string');
});
  1. Send the request and verify that all tests succeed.

Observe the order details included in the response, which will automatically be filtered by the company matching the current authentication. Since the results set is sorted descending by date, your recent order from quote should be included in the first page of results.

Step 2: Get Ordered Products

Now let’s craft a request that will give your storefront user aggregate information on the products that have been ordered for the company.

  1. Create a new HTTP request and save it to your collection with the name “Get Ordered Products.”
  2. Set the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created “GraphQL” preset), and Body type.
  3. Enter the following query.
query GetOrderedProducts(
$limit: Int,
$after: String,
$orderBy: String
) {
orderedProducts(
first: $limit,
after: $after,
orderBy: $orderBy
) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
edges {
node {
id
productId
productName
sku
variantSku
variantId
optionList
optionSelections
orderedTimes
firstOrderedAt
lastOrderedAt
imageUrl
productUrl
}
}
}
}
  1. Enter the following in the GraphQL Variables panel.
{
"limit": 5,
"after": null,
"orderBy": "-lastOrderedAt"
}

You’re configuring your ordered products list to be sorted by most recently ordered.

  1. 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 result = pm.response.json().data?.orderedProducts;
pm.test('Total count includes at least one product', () => {
pm.expect(
result?.totalCount
).to.be.greaterThan(0);
});
let firstProduct = null;
if (Array.isArray(result?.edges)) {
firstProduct = result.edges[0]?.node;
}
pm.test('Response includes at least one product with an ID', () => {
pm.expect(
firstProduct?.id
).to.be.a('string');
});
  1. Send the request and verify that all tests succeed.

Step 3: Get Open Invoices

Let’s craft a request to retrieve all of the current company’s invoices that are not fully paid.

The creation of invoice is done on the back-end, so before you can fetch any relevant results, you’ll need to take on the role of a sales rep first, to issue the invoice.

  1. Log in to your BigCommerce control panel and navigate to the B2B Edition admin.
  2. In Invoice Management > Orders, find the order you placed from your quote (or any other un-invoiced order paid with Purchase Order) and Create Invoice from the order’s action menu.
  3. Create a new HTTP request and save it to your collection with the name “Get Open Invoices.”
  4. Set the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created “GraphQL” preset), and Body type.
  5. Enter the following query.
query GetInvoices(
$limit: Int,
$after: String,
$orderBy: String,
$status: [String]
) {
invoices(
first: $limit,
after: $after,
orderBy: $orderBy,
status: $status
) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
edges {
node {
id
invoiceNumber
createdAt
customerId
dueDate
status
orderNumber
purchaseOrderNumber
details
pendingPaymentCount
originalBalance {
code
value
}
openBalance {
code
value
}
}
}
}
}
  1. Enter the following in the GraphQL Variables panel.
{
"limit": 5,
"after": null,
"orderBy": "-createdAt",
"status": ["0","1"]
}

You’re not only sorting the invoice results by most recently created, but also filtering on the open (0) and partially paid (1) statuses.

  1. 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 result = pm.response.json().data?.invoices;
pm.test('Total count includes at least one invoice', () => {
pm.expect(
result?.totalCount
).to.be.greaterThan(0);
});
let firstInvoice = null;
if (Array.isArray(result?.edges)) {
firstInvoice = result.edges[0]?.node;
}
pm.test('Response includes at least one invoice with an ID and open balance', () => {
pm.expect(
firstInvoice?.id
).to.be.a('string');
pm.expect(
firstInvoice?.openBalance?.value
).to.be.a('string');
});
pm.collectionVariables.unset('invoice_id');
pm.collectionVariables.unset('invoice_amount');
pm.collectionVariables.set('invoice_id', firstInvoice?.id);
pm.collectionVariables.set('invoice_amount', firstInvoice?.openBalance?.value);

In addition to tests, this script is capturing both the ID and the open balance of the first returned invoice in collection variables for use in later requests; these values will be needed once you’re ready to initiate payment on an invoice.

You’re ready to run the request, but remember that your authentication credentials should currently match your Junior Buyer user, and Junior Buyers do not have permission to view invoices.

  1. Send the request and verify that you receive a “Permission denied” error.
  2. Re-run the “Admin Login” request to generate new authentication credentials for your Admin user, who should be able to fetch invoice details.
  3. Re-run the “Get Open Invoices” request, verify that all tests succeed, and verify that invoice_id and invoice_amount variables have been set on your collection.

If the first returned invoice in your results is not the one you wish to use in the steps for downloading an invoice PDF and initiating an invoice payment, feel free to manually change the current values of invoice_id and invoice_amount on your collection.

Step 4: Export Invoice CSV and PDF

In addition to viewing their invoices within your storefront application, company users will commonly with to export them in other formats. In this step, you’ll practice generating a CSV of all open invoices and the PDF for your most recent open invoice.

  1. Create a new HTTP request and save it to your collection with the name “Export Open Invoices.”
  2. Set the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created “GraphQL” preset), and Body type.
  3. Enter the following query.
mutation InvoicesExport(
$status: [Int]
) {
invoicesExport(
invoiceFilterData: {
status: $status
}
) {
url
}
}
  1. Enter the following in the GraphQL Variables panel.
{
"status": [0,1]
}

Note the expression of the status filters as integers rather than strings in this case.

  1. 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?.invoicesExport?.url;
pm.test(`Response includes order with ID`, () => {
pm.expect(
url
).to.be.a('string');
});
  1. Send the request and verify that all tests succeed.
  2. Copy the URL from the response and enter it in a browser to verify that a downloaded CSV is received and contains the appropriate details.

Next you’ll generate an invoice PDF using the “most recent invoice” information you previously captured.

  1. Create a new HTTP request and save it to your collection with the name “Get Invoice PDF.”
  2. Set the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created “GraphQL” preset), and Body type.
  3. Enter the following query.
mutation GetInvoicePdf(
$invoiceId: Int!
) {
invoicePdf(
invoiceId: $invoiceId,
isPayNow: true
) {
url
}
}
  1. Enter the following in the GraphQL Variables panel.
{
"invoiceId": {{invoice_id}}
}
  1. 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?.invoicePdf?.url;
pm.test(`Response includes PDF URL`, () => {
pm.expect(
url
).to.be.a('string');
});
  1. Send the request and verify that all tests succeed.
  2. Copy the URL from the response and enter it in a browser to verify that a downloaded PDF is received and contains the appropriate details.

Step 5: Initiate Invoice Checkout

Similar to the process of initiating a checkout from an order, your final invoice request will generate a URL to allow a storefront user to initiate payment of a specific invoice via the BigCommerce checkout.

  1. Create a new HTTP request and save it to your collection with the name “Get Invoice Checkout.”
  2. Set the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created “GraphQL” preset), and Body type.
  3. Enter the following query.
mutation GetInvoiceCheckout(
$invoiceId: Int!,
$amount: String!,
$currency: String!
) {
invoiceCreateBcCart(
bcCartData: {
lineItems: [
{
invoiceId: $invoiceId,
amount: $amount
}
],
currency: $currency,
details: {}
}
) {
result {
checkoutUrl
cartId
}
}
}
  1. Enter the following in the GraphQL Variables panel.
{
"invoiceId": {{invoice_id}},
"amount": "{{invoice_amount}}",
"currency": "{{currency_code}}"
}

Again, you can change the value of the invoice_id variable on your collection to initiate a payment for a different invoice than the most recent.

You can also try changing the invoice_amount collection variable to a value less than the full open balance of the invoice, in order to test out a partial payment. (Don’t forget to save the collection.)

  1. 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?.invoiceCreateBcCart?.result?.checkoutUrl;
pm.test(`Response includes checkout URL`, () => {
pm.expect(
url
).to.be.a('string');
});
  1. If necessary, re-send your previous “Get Currencies” request to make sure the default currency code is captured in your collection variables.
  2. Send the request and verify that all tests succeed.
  3. Copy the checkoutUrl from the response and enter it in a browser to verify that the checkout is initiated.
  4. Complete the checkout and place an order for the invoice payment.

With the invoice now paid, you can try re-running your “Get Open Invoices” request to observe the effects. Try removing the “status” filter and examining the updated status and balance of the paid invoice.

Step 6: Get Payment Details

  1. Create a new HTTP request and save it to your collection with the name “Get Payments.”
  2. Set the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created “GraphQL” preset), and Body type.
  3. Enter the following query.
query GetPayments(
$limit: Int,
$after: String
) {
invoicePayments(
first: $limit,
after: $after
) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
edges {
node {
id
createdAt
totalCode
totalAmount
payerName
processingStatus
companyId
companyName
paymentReceiptSet {
edges {
node {
id
createdAt
customerId
totalCode
totalAmount
payerName
paymentId
paymentType
receiptLineSet {
edges {
node {
id
createdAt
customerId
receiptId
paymentId
invoiceId
invoiceNumber
amount
paymentType
}
}
}
}
}
}
}
}
}
}
  1. Enter the following in the GraphQL Variables panel.
{
"limit": 5,
"after": null
}
  1. 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 result = pm.response.json().data?.invoicePayments;
pm.test('Total count includes at least one payment', () => {
pm.expect(
result?.totalCount
).to.be.greaterThan(0);
});
let firstPayment = null;
if (Array.isArray(result?.edges)) {
firstPayment = result.edges[0]?.node;
}
pm.test('Response includes at least one payment with an ID', () => {
pm.expect(
firstPayment?.id
).to.be.a('string');
});
  1. Send the request and verify that all tests succeed.

If needed, you can manipulate the after value in the GraphQL Variables panel using the endCursor value from the response to page through results.

  1. Create a new HTTP request and save it to your collection with the name “Get Invoice Receipt Lines.”
  2. Set the same configuration that was used for the previous requests, including the HTTP method, URL, Auth Type, Headers (using your previously created “GraphQL” preset), and Body type.
  3. Enter the following query.
query GetReceiptLines(
$invoiceId: String,
$limit: Int,
$after: String
) {
allReceiptLines(
invoiceId: $invoiceId,
first: $limit,
after: $after
) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
edges {
node {
id
createdAt
customerId
receiptId
paymentId
invoiceId
invoiceNumber
amount
paymentStatus
paymentType
}
}
}
}
  1. Enter the following in the GraphQL Variables panel.
{
"invoiceId": "{{invoice_id}}",
"limit": 5,
"after": null
}
  1. 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 result = pm.response.json().data?.allReceiptLines;
pm.test('Total count includes at least one receipt line', () => {
pm.expect(
result?.totalCount
).to.be.greaterThan(0);
});
let firstLine = null;
if (Array.isArray(result?.edges)) {
firstLine = result.edges[0]?.node;
}
pm.test('Response includes at least one receipt line with an ID', () => {
pm.expect(
firstLine?.id
).to.be.a('string');
});
  1. Send the request and verify that all tests succeed.

If you previously paid only a partial amount on the invoice, you can try using the “Get Invoice Checkout” request again to initiate a further payment, then re-run your “Get Invoice Receipt Lines” request to observe the expanded details.

With the workflow you followed (making a payment on a single invoice), there aren’t many differences between the details returned in your “Get Payments” and “Get Invoice Receipt Lines” requests. Remember, however, that a checkout can be initiated for multiple open invoices at once. When this is supported, this introduces different use cases for “Get Payments” (full details by payment) and “Get Invoice Receipt Lines” (payment info for a specific invoice).

Taking It Further

Try out the following steps to further explore the purchasing workflow:

  • Create multiple open invoices on a company and configure a “Get Invoice Checkout” request to initiate a checkout for multiple invoices. Observe the effects in the responses for “Get Payments” and “Get Invoice Receipt Lines.”
  • Create a request for a single order using the order query. Remember to pass the BigCommerce order ID in for the id argument.
  • Create a request for a single invoice using the invoice query, and configure it to automatically use the most recent invoice from “Get Open Invoices” using the invoice_id collection variable.