Wbcom Credits SDK
This reference covers WP Career Board Pro 1.4.3.
The credit system is built on the Wbcom Credits SDK - a bundled
library at libs/wbcom-credits-sdk/ (kept in libs/, not
vendor/, so it always ships in the release zip). It provides:
- An append-only ledger (the
{prefix}_credit_ledgertable; for this plugin the prefix iswcb, sowp_wcb_credit_ledger). - A consumer pattern (entities that spend credits - job posting, featured upgrade).
- An adapter pattern for e-commerce plugins (WooCommerce, WC Subscriptions, WC Memberships, PMPro, MemberPress).
- A gateway pattern for direct payment processors (Stripe, PayPal).
This doc covers the contract for registering a slug and writing your own consumer, adapter, or gateway.
Architecture
Section titled “Architecture” +-------------------------------------------+ | Wbcom Credits SDK (libs/) | | | | +--------------+ | | | Ledger | <- single source | | | (DB writes) | of truth | | +--------------+ | | ^ | | +----+----+----------+ | | | | | | | Consumers Adapters Gateways | | (job_post, (Woo, (Stripe, | | featured) WCS, WCM, PayPal - | | PMPro, direct) | | MemberPress) | +-----^----------^----------^---------------+ | | | hold/deduct on order on webhook on actions completed verifiedRegistering your slug with the SDK
Section titled “Registering your slug with the SDK”A plugin registers everything (slug, prefix, consumers, settings)
in one call on the wbcom_credits_sdk_registry action, which fires
before the SDK loads. Pro does this in wp-career-board-pro.php:
add_action( 'wbcom_credits_sdk_registry', function ( \Wbcom\Credits\Registry $registry ) { $registry->register( array( 'slug' => 'wp-career-board', 'prefix' => 'wcb', // table prefix: {wp}_wcb_credit_ledger 'version' => WCBP_VERSION, 'file' => WCBP_FILE, 'user_type' => 'employer', 'consumers' => array( /* see below */ ), 'settings' => array( 'low_threshold' => 5, 'purchase_url' => '', 'admin_settings_hook' => 'wcb_admin_settings_tabs', ), ) );} );Consumers - what can spend credits
Section titled “Consumers - what can spend credits”A “consumer” is something the SDK debits credits FOR. Each consumer
declares three lifecycle actions: hold_on (reserve credits),
deduct_on (settle the hold permanently), and refund_on (release
the hold). The SDK adds the listeners; you fire the actions. The
cost callback receives the item id and returns the credit cost.
Pro registers two consumers inside the consumers array of its
register() call:
'consumers' => array( array( 'id' => 'job_post', 'label' => __( 'Job Posting', 'wp-career-board-pro' ), 'cost' => static function ( int $item_id ): int { $board_id = (int) get_post_meta( $item_id, '_wcb_board_id', true ); $base = $board_id ? (int) ( ( new \WCB\Pro\Modules\Boards\BoardSettings() )->get( $board_id )['credit_cost'] ?? 0 ) : 0; return (int) apply_filters( 'wcbp_consumer_cost', $base, get_current_user_id(), $item_id, $board_id, 'job_post' ); }, 'hold_on' => 'wcb_job_created', // Hold credits when this action fires 'deduct_on' => 'wcb_job_approved', // Settle the hold when this fires 'refund_on' => 'wcb_job_rejected', // Release the hold when this fires ), array( 'id' => 'featured_upgrade', 'label' => __( 'Featured Upgrade', 'wp-career-board-pro' ), 'cost' => static function ( int $item_id ): int { $base = (int) get_option( 'wcbp_featured_upgrade_cost', 10 ); return (int) apply_filters( 'wcbp_consumer_cost', $base, get_current_user_id(), $item_id, 0, 'featured_upgrade' ); }, 'hold_on' => 'wcb_featured_upgrade_requested', 'deduct_on' => 'wcb_featured_upgrade_completed', 'refund_on' => 'wcb_featured_upgrade_failed', ),),To add your own consumer, append another entry to the consumers
array in your own register() call (or call $registry->register()
again for a separate slug). The cost callback signature is
function ( int $item_id ): int. Fire your hold_on /
deduct_on / refund_on actions when the relevant lifecycle
events happen in your code - the SDK takes care of the ledger
writes.
Adapters - automatic credit grants from e-commerce plugins
Section titled “Adapters - automatic credit grants from e-commerce plugins”An “adapter” listens for a specific plugin’s “order completed”
event and writes a topup ledger row. The SDK ships five adapters,
which self-register through the SDK’s adapter registry when their
host plugin is active:
| Adapter | File | Listens to |
|---|---|---|
| WooCommerce | libs/wbcom-credits-sdk/src/Adapters/WooCommerce.php |
woocommerce_order_status_completed |
| WC Subscriptions | libs/wbcom-credits-sdk/src/Adapters/WooSubscriptions.php |
WC Subscriptions renewal/payment events |
| WC Memberships | libs/wbcom-credits-sdk/src/Adapters/WooMemberships.php |
WC Memberships grant events |
| Paid Memberships Pro | libs/wbcom-credits-sdk/src/Adapters/PMPro.php |
PMPro membership-change / payment events |
| MemberPress | libs/wbcom-credits-sdk/src/Adapters/MemberPress.php |
MemberPress transaction-completed event |
Each adapter implements AdapterInterface
(libs/wbcom-credits-sdk/src/Adapters/AdapterInterface.php).
Note the methods are instance methods, not static:
interface AdapterInterface { public function get_id(): string; public function get_label(): string; public function is_available(): bool; // host plugin active? public function register_hooks( string $slug ): void; public function get_mappable_items(): array; // products/levels for the admin mapping UI}To add a new adapter (e.g. for Easy Digital Downloads), implement
the interface, hook the host plugin’s purchase event in
register_hooks(), and write a topup row with Credits::topup():
namespace MyAddon\Credits;
use Wbcom\Credits\Adapters\AdapterInterface;use Wbcom\Credits\Credits;
class EDD implements AdapterInterface {
public function get_id(): string { return 'edd'; }
public function get_label(): string { return 'Easy Digital Downloads'; }
public function is_available(): bool { return class_exists( 'Easy_Digital_Downloads' ); }
public function register_hooks( string $slug ): void { add_action( 'edd_complete_purchase', function ( $payment_id ) use ( $slug ) { $user_id = (int) edd_get_payment_user_id( $payment_id ); foreach ( edd_get_payment_meta_cart_details( $payment_id ) as $item ) { $credits = (int) $this->credits_for_product( $slug, (int) $item['id'] ); if ( $credits > 0 ) { // Signature: topup( $slug, $user_id, $amount, $note ) Credits::topup( $slug, $user_id, $credits, 'EDD order #' . $payment_id ); } } }); }
public function get_mappable_items(): array { // Return EDD products in the shape the admin mapping UI expects. return array(); }
private function credits_for_product( string $slug, int $product_id ): int { $mappings = (array) get_option( "{$slug}_credit_mappings", array() ); foreach ( $mappings as $row ) { if ( 'edd' === ( $row['adapter'] ?? '' ) && (int) $row['item_id'] === $product_id ) { return (int) $row['credits']; } } return 0; }}Gateways - direct payment processors
Section titled “Gateways - direct payment processors”A “gateway” is for selling credits directly without an e-commerce
plugin in between (Stripe Checkout, PayPal). The SDK ships two
gateways: Stripe and PayPal
(libs/wbcom-credits-sdk/src/Gateways/).
The checkout and webhook flow is reachable today. The SDK’s
Webhook_Controller registers three REST routes per slug under the
SDK’s own wbcom-credits/v1 namespace (separate from the plugin’s
wcb/v1 namespace):
POST /wbcom-credits/v1/wp-career-board/checkout/{gateway} Create a hosted checkout session.POST /wbcom-credits/v1/wp-career-board/webhook/{gateway} Public, provider-signed. Adjusts the ledger.POST /wbcom-credits/v1/wp-career-board/refund/{gateway} Refund a prior checkout.The webhook is what actually credits the ledger - it is verified by
the gateway’s verify_signature() before any ledger write, so a
forged callback can’t grant credits. Refunds initiated in the
provider dashboard also flow back through the webhook.
If you’re writing a custom gateway, implement GatewayInterface
(libs/wbcom-credits-sdk/src/Gateways/GatewayInterface.php).
Abstract_Gateway provides a default handle_webhook() you can
extend:
interface GatewayInterface { public function get_id(): string; public function get_label(): string; public function is_available(): bool; public function get_settings_fields(): array; public function create_checkout( string $slug, int $user_id, int $credits, int $price_cents, string $currency = 'USD', ?string $return_url = null ): string; public function verify_signature( string $raw_body, array $headers ): bool; public function normalize_event( array $payload ): ?Gateway_Event; public function handle_webhook( string $slug, array $payload ): \WP_REST_Response; public function refund( string $slug, string $session_id, ?int $amount_cents = null ): bool;}Once registered via Gateway_Registry, the admin Credits settings
UI auto-discovers it.
The ledger
Section titled “The ledger”Every credit movement writes one row. The table is named
{wp_prefix}{prefix}_credit_ledger (for this plugin,
wp_wcb_credit_ledger); the SDK creates one ledger table per
registered prefix, so the table itself scopes the data and there is
no slug column. Schema:
| Column | Type | Notes |
|---|---|---|
id |
bigint unsigned PK | Auto-increment |
user_id |
bigint unsigned | The credit holder (employer) |
item_id |
bigint unsigned | The consumed entity (job, etc.); 0 for top-ups/adjustments |
entry_type |
varchar(20) | One of topup, hold, deduction, refund |
amount |
int | Signed: positive for topup/refund, negative for hold/deduction |
note |
varchar(255) | Free-form context string |
created_at |
datetime | Defaults to CURRENT_TIMESTAMP |
Indexes: idx_user_id (user_id), idx_entry_type (entry_type).
The balance is SUM(amount) for the user. The ledger is
append-only; the only physical DELETE is cancel_hold().
To read the ledger:
$balance = \Wbcom\Credits\Credits::get_balance( 'wp-career-board', $user_id );$rows = \Wbcom\Credits\Credits::get_ledger( 'wp-career-board', $user_id, 50 );// or directly: \Wbcom\Credits\Ledger::get_history( 'wcb', $user_id, $limit, $offset )Never INSERT/UPDATE the ledger directly - use the Credits helpers,
which use these exact signatures:
Credits::topup( string $slug, int $user_id, int $amount, string $note = '' ): int|false;Credits::hold( string $slug, int $user_id, int $amount, int $item_id, string $note = '' ): int|false;Credits::deduct( string $slug, int $user_id, int $amount, int $item_id, string $note = '' ): bool;Credits::refund( string $slug, int $user_id, int $amount, int $item_id, string $note = '' ): int|false;Credits::cancel_hold( string $slug, int $user_id, int $item_id ): void;Credits::adjust( string $slug, int $user_id, int $amount, string $note = '' ): int|false;Credits::get_cost( string $slug, string $consumer_id, int $item_id = 0 ): int;Credits::get_purchase_url( string $slug ): string;Note the 4th argument to topup() is a string note, not an
array. When a consumer settles, Ledger::deduct_with_hold_release()
releases the outstanding hold (writing a refund row for the
held amount) and writes a deduction row for the actual cost in one
transaction-safe step.
Where to read further
Section titled “Where to read further”libs/wbcom-credits-sdk/src/- the SDK source (bundled, not loaded over the network).audit/journeys/security/license-required-for-pro-rest.md- the contract that Pro REST (including credit endpoints) stays operational regardless of license status (license gates updates only, never runtime features).- Pro hooks for credits in
03-hooks-reference.md:
wcbp_consumer_cost(cost filter),wcbp_credit_consumed,wcbp_credits_low, andwcbp_credits_topped_up(the last two re-emitted from the SDK’swbcom_credits_low/wbcom_credits_topped_up).

