Fetching Redemptions/Seats Available For A License Key or Order(s)

Fetching Redemptions/Seats Available For A License Key or Order(s)

Developer guide for seat usage on license keys: how many seats are available, how many are used, and per-course breakdown. This document is written for integrators using the Bright Platform for WordPress plugin (WooCommerce license flows).

For raw HTTP API details (endpoints, status codes, authorization on the Rails side), see Bright Scoped Stored Queries API Reference. For general $bright->callApi() usage, see the plugin’s md/bright-php-api-reference.md.


What you get

Each redemption row describes one license invitation × course combination:

Field Meaning
license_key Invitation name (the license key string)
order_id WooCommerce order ID stored on the invitation (custom.order_id)
created Invitation created timestamp
seats_available Seats purchased for that course on this invitation
seats_used Active registrations against those seats
title Course title

Typical display: “Seats Used: 2 of 5” (seats_used of seats_available).


Three ways to look up redemptions

Lookup URL / param WordPress helper route Best for
License key invitation_name v2 stored query redemptions_available_for License Key Report links, purchaser-facing pages
One order order_id v3 invitation/redemptions Order confirmation, admin order screens
Many orders order_ids (comma-separated) v3 invitation/redemptions Batch reporting, “my orders” dashboards

At least one of these parameters is required. The plugin does not infer a license key or order ID when none are supplied.


Built-in License Key Report page

On WooCommerce setup, the plugin creates a License Key Report page with two shortcodes:

[bright class="none" type="generic" template="redemptions_available_for"/]
[bright class="none" type="generic" template="invitation_results_matrix" datatables="yadcf"/]

The first shortcode loads seat usage; the second loads the completion matrix for the same license key.

URL parameters

The redemptions_available_for template reads query string parameters and passes them to getRedemptionsAvailableFor():

Query param Example Notes
invitation_name ?invitation_name=13708-14097-56871-91719-163f07ce-c3270a5d Primary path for license-key links
order_id ?order_id=670 Single WooCommerce order
order_ids ?order_ids=670,684 Comma-separated list

If the page is opened with no parameters, visitors see: “Cannot derive license key from URL.”

Linking from WooCommerce

The plugin builds license-key report URLs with:

$url = \Bright\WC\get_license_key_report_url($license_key);
// → {license_key_report_permalink}?invitation_name={license_key}

Used on order detail pages (woocommerce/order-details.php). Customize with the bright_woocommerce_license_key_report_url filter.

For order-based report links, build the permalink yourself:

$report_url = \Bright\WC\bwc_page_permalink('license_key_report');
$report_url = add_query_arg('order_ids', '670,684', $report_url);

PHP API — getRedemptionsAvailableFor()

Obtain the connector singleton:

$bright = \Bright\Wordpress::getInstance();

Call the routing helper (defined in php-connect/base.php):

$raw = $bright->getRedemptionsAvailableFor(array(
  'params' => array(
    'invitation_name' => $license_key,   // OR order_id / order_ids — see routing below
  ),
  'raw' => true,   // default for this helper; returns JSON string
));

Automatic v2 vs v3 routing

params provided Backend call
invitation_name (non-empty) callApi('stored_query/run', …) with name=redemptions_available_for, query_scope=bright
order_id and/or order_ids (and no invitation_name) callV3Api('invitation/redemptions', …)

Important: If invitation_name is set, the helper always uses the v2 stored query. order_id / order_ids in the same call are ignored. To filter by both license key and order on v3, call callV3Api() directly (see below).

Default for this helper is 'raw' => true. Pass 'raw' => false if you want a decoded PHP value (v2 only returns usefully decodable JSON for the stored-query path).


Examples

1. By license key (logged-in purchaser)

Uses the current user’s Bright access token (set during doHeader() for front-end requests). The user must be the invitation owner (initiating_user or listed in reporting_users on the invitation custom JSON).

$bright = \Bright\Wordpress::getInstance();

$raw = $bright->getRedemptionsAvailableFor(array(
  'params' => array(
    'invitation_name' => '13708-14097-56871-91719-163f07ce-c3270a5d',
  ),
));

$rows = json_decode($raw, true);
// v2 shape: array of column-indexed rows — see “Response shapes” below

2. By one order ID (server-side / realm)

Use accessMode => 'realm' in admin hooks, cron, or other server-side code where no user token exists. Realm credentials come from WordPress options (bright_realm_guid, bright_secret_key).

$raw = $bright->getRedemptionsAvailableFor(array(
  'accessMode' => 'realm',
  'params' => array(
    'order_id' => '670',
  ),
));

$body = json_decode($raw, true);
foreach ($body['results'] as $entry) {
  if ($entry['status'] !== 200) {
    continue;
  }
  foreach ($entry['data'] as $row) {
    // $row['seats_used'], $row['seats_available'], $row['title'], …
  }
}

3. By multiple order IDs (batch)

$raw = $bright->getRedemptionsAvailableFor(array(
  'accessMode' => 'realm',
  'params' => array(
    'order_ids' => '670,684',
  ),
));

$body = json_decode($raw, true);

Each element in $body['results'] is one requested order:

Field Meaning
order_id Requested WooCommerce order ID
status Per-order HTTP-style status (200, 403, 404)
message Human-readable outcome
data Array of redemption rows on success; null on error

Check every entry’s status before reading data. The overall HTTP response may be 207 when some orders fail authorization or are not found.

4. Direct v3 call (combine filters)

When you need invitation_name and order_id / order_ids together, bypass the helper:

$raw = $bright->callV3Api('invitation/redemptions', array(
  'accessMode' => 'realm',
  'params' => array(
    'invitation_name' => $license_key,
    'order_ids'       => '670',
  ),
  'raw' => true,
));

5. Custom shortcode or theme template

Mirror the built-in template pattern (woocommerce/bright/templates/redemptions_available_for.php):

add_filter('bright_extend_on_generic', function ($coursedata, $attr) {
  if (($attr['template'] ?? '') !== 'my_redemptions_widget') {
    return $coursedata;
  }

  $order_ids = isset($_GET['order_ids']) ? sanitize_text_field(wp_unslash($_GET['order_ids'])) : '';
  if ($order_ids === '') {
    return $coursedata;
  }

  $bright = \Bright\Wordpress::getInstance();
  return $bright->getRedemptionsAvailableFor(array(
    'accessMode' => 'realm',
    'params'     => array('order_ids' => $order_ids),
    'raw'        => true,
  ));
}, 10, 2);

Register a matching Handlebars template if you render through the Bright template engine, or decode JSON in PHP and return HTML.


Response shapes

v2 — invitation_name only (stored query)

Top-level JSON is a flat array of rows. Each row is an array of column values (not keyed objects), in this order:

Index Column
0 license_key
1 order_id
2 created
3 seats_available
4 seats_used
5 title

The stock Handlebars template uses <code class="kb-btn">{this.[4]}</code> and <code class="kb-btn">{this.[3]}</code> for the “Seats Used: X of Y” line.

v3 — order_id / order_ids (batch mode)

Top-level JSON object with a results array only (no meta):

{
  "results": [
    {
      "order_id": 670,
      "status": 200,
      "message": "Success",
      "data": [
        {
          "license_key": "...",
          "order_id": 670,
          "created": "2024-06-01 10:15:00",
          "seats_available": 5,
          "seats_used": 2,
          "title": "Example Course A"
        }
      ]
    }
  ]
}

v3 — invitation_name only (flat mode)

If you call v3 directly with only invitation_name, results is a flat array of keyed row objects (same fields as data[] above), not the batch wrapper.


Authentication and access

accessMode

Mode When to use Credentials
accessToken (default) Front-end pages for logged-in purchasers User API key from getAuthenticationCodeForUser()
realm Server-side PHP (hooks, CLI, batch jobs) bright_realm_guid + bright_secret_key from options

Never expose realm secret keys in browser JavaScript.

Who can see which data

invitation_name (v2 stored query or v3 flat mode)

The API key’s user email must match the invitation’s:

  • custom.initiating_user (purchaser), or
  • one of the emails in custom.reporting_users (comma-separated)

Otherwise the call returns 401.

order_id / order_ids (v3 batch mode)

Any authenticated user API key may call. Each order is evaluated separately:

Per-order status Meaning
200 Rows returned, or order is accessible but has no license rows (data: [])
403 Order exists in the realm, but this user is not an invitation owner for it
404 No license invitation in the realm has that order_id in custom

With realm credentials, all matching license invitations in the realm are returned; unknown order IDs appear as 404 entries.

Invitation custom fields used for access

{
  "initiating_user": "buyer@example.com",
  "license": true,
  "order_id": 670,
  "reporting_users": "manager@example.com,other@example.com"
}

V3 API root URL

callV3Api() resolves the v3 base URL from plugin options:

  1. bright_api_v3_url if set
  2. Else replace /v2 with /v3 on bright_api_url
  3. Else append /v3 to the v2 root

Example: https://bright.example.com/bright/api/v2https://bright.example.com/bright/api/v3


Location Role
woocommerce/bright/templates/redemptions_available_for.php Built-in seat-usage shortcode template
php-connect/reports.php invitation_results_matrix template (completion detail; uses same invitation_name URL param)
woocommerce/admin/create-pages.php Creates License Key Report page on setup
woocommerce/redirection.php get_license_key_report_url()
php-connect/base.php getRedemptionsAvailableFor(), callV3Api()
tests/test-redemptions-v3-api.php Unit tests for routing and response shapes

Quick decision guide

Need seat usage for one license key on a purchaser-facing page?
  → URL: ?invitation_name={key}
  → PHP: getRedemptionsAvailableFor(['params' => ['invitation_name' => $key]])
  → Default accessMode (user token) if the viewer is the purchaser

Need seat usage for WooCommerce order(s) in server-side code?
  → PHP: getRedemptionsAvailableFor(['accessMode' => 'realm', 'params' => ['order_ids' => '670,684']])
  → Loop results[]; check status per order

Need both license key and order filter?
  → callV3Api('invitation/redemptions', …) with both params

Need completion matrix (learners, scores) for the same key?
  → Same page already includes invitation_results_matrix; requires invitation_name in URL