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:
- Credits (lightweight internal currency)
- Stripe Customers (safe tokenization without storing cards)
- 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.