• log out

Assets Payments: Credits, Customers, and Background Charges

The Assets plugin provides a complete payment layer for Qbix applications. It integrates Stripe and Authorize.net but—most importantly—supports a native Credits System that apps can use for purchases, subscriptions, pay-per-use flows, donations, and internal transfers.

Developers don’t need to store card numbers or PCI-sensitive data. Instead, the platform uses Stripe Customers (cus_XXXX) and PaymentIntents (with setup_future_usage) to automatically vault cards for future, off-session charges. All payment info is stored safely on Stripe, tied to the assets_customer table.

Credits: The Internal Billing Currency

Every user has a credits stream:

Assets/credits
[publisherId = communityId]
[streamName = <userId>]

This stream's amount attribute tracks how many credits a user has. Credits are spent using Assets::transfer(), and can be automatically replenished by charging Stripe in the background.

Here is the relevant PHP signature:

static function transfer($communityId, $amount, $reason, $toUserId, $fromUserId, $more);

If the user does not have enough credits, you can pass:

$more['forcePayment'] = true;

This triggers an automatic charge:

// If credits are insufficient and forcePayment=true:
// Convert credits → USD, then charge Stripe.
Assets::charge('stripe', Assets_Credits::convert($amount, 'credits', 'USD'));

This is the foundation of subscriptions, auto-refills, and pay-per-use billing.

Stripe Customer Management

The assets_customer table is where Qbix stores each user’s Stripe Customer ID. This ID serves as a reusable payment token without storing card details locally.

CREATE TABLE assets_customer (
    userId      VARBINARY(31),
    payments    ENUM('stripe','authnet'),
    customerId  VARBINARY(255),  -- e.g. cus_Q9b8...O6eLGI
    hash        VARCHAR(32),     -- identifies environment keys
    insertedTime TIMESTAMP,
    updatedTime TIMESTAMP
    PRIMARY KEY (userId, payments, hash)
);

Creating a customer happens transparently on the first checkout:

$customer = new Assets_Customer();
$customer->userId = $user->id;
$customer->payments = 'stripe';
$customer->hash = Assets_Customer::getHash();

if (!$customer->retrieve()) {
    $stripeCustomer = \Stripe\Customer::create([
        'email' => $user->emailAddress
    ]);
    $customer->customerId = $stripeCustomer->id;
    $customer->save();
}

Once a Stripe Customer exists, it can be charged off-session at any time.

Frontend: Creating a PaymentIntent

On the client side (JS), you call:

Q.req('Assets/payment', 'intent', {
    amount: 20,
    currency: 'usd'
}, function (err, data) {
    const clientSecret = data.slots.intent.client_secret;
    stripe.confirmPayment({ elements, confirmParams });
});

On the server, Assets_payment_response_intent builds the PaymentIntent:

$intent = \Stripe\PaymentIntent::create([
    'customer' => $customer->customerId,
    'amount' => $amount * 100,
    'currency' => $currency,
    'setup_future_usage' => 'off_session',
    'automatic_payment_methods' => ['enabled' => true],
    'metadata' => ['userId' => $user->id]
]);

The critical flags:

  • setup_future_usage = 'off_session' → card is vaulted automatically
  • automatic_payment_methods.enabled = true → Stripe handles card types and wallet support

Webhook: Handling Successful Payments

Stripe notifies your server of completed charges through:

/Assets/stripeWebhook

The webhook processes payment_intent.succeeded:

case 'payment_intent.succeeded':
    $pi = $event->data->object;
    $metadata = $pi->metadata;

    // Fetch user
    $userId = $metadata['userId'];
    $user = Users::fetch($userId, true);

    // Log charge + issue credits:
    Assets::charge('stripe', $amount, $currency, [
        'user' => $user,
        'chargeId' => $pi->id,
        'customerId' => $pi->customer
    ]);

This is where credits get added when a user checks out. The webhook also logs the charge into:

assets_charge

Off-Session Charging (Recurring Billing)

Qbix apps can charge users in the background without any UI by using:

Assets::charge("stripe", $amount, "USD", ['user' => $user]);

This calls Assets_Payments_Stripe::charge():

// Load saved Stripe customerId
$customerId = $customer->customerId;

// Find saved cards for the customer
$methods = $stripe->paymentMethods->all([
    'customer' => $customerId,
    'type' => 'card'
]);

// Charge the default card
\Stripe\PaymentIntent::create([
    'customer' => $customerId,
    'payment_method' => $methods->data[0]->id,
    'off_session' => true,
    'confirm' => true,
    'amount' => $amount * 100,
    'currency' => $currency
]);

Because earlier checkouts saved the card with setup_future_usage, this background billing works automatically.

Subscription Renewal (Cron Script)

The subscription engine uses credits + off-session charges through cron jobs:

if (!Assets_Subscription::isCurrent($subStream)) {
    Q::event("Assets/credits/post", [
        "amount" => $plan->getAttribute('amount'),
        "currency" => "USD",
        "forcePayment" => true
    ]);

    // if successful → start subscription
    // if failed → unsubscribe + remove permissions
}

The cron script charges credits first; if insufficient, it triggers Assets::charge('stripe').

Auto-Refill Credits (Cron Script)

A second cron maintains a user’s minimum credits:

if ($creditsAmount <= $creditsMin) {
    Assets::charge("stripe",
        Assets_Credits::convert($creditsAdd, 'credits', 'USD'),
        'USD',
        ['user' => $user]
    );
}

If charging fails, the script posts an alert message to the user’s credits stream.

Database Summary

These tables form the core of the Assets payment system:

  • assets_customer Stores cus_xxx for each user
  • assets_credits Journal of all credit transfers
  • assets_charge Log of all Stripe charges handled via backend

Architecture Summary

The Assets Payments system is built around three pillars:

  1. Credits (lightweight internal currency)
  2. Stripe Customers (safe tokenization without storing cards)
  3. Off-Session Billing (auto-renewals + auto-refills)

The result is a fully automatic billing engine:

  • Users check out once → card saved automatically
  • Credits are awarded via webhook
  • Subscriptions renew via cron
  • Low credits trigger background charges
  • No PCI-sensitive data stored locally

This design fits naturally into Qbix Streams and provides a foundation for stores, markets, paywalls, membership sites, SaaS products, and community-based economies.