Service Types & Credit Lifecycle
Detailed overview of supported service types and credit lifecycle management
Service Types & Credit Lifecycle
Current Service Types
The Add-on Services system currently supports two primary service types, with architecture designed for easy extension to additional services.
1. SMS Messaging Service
Overview: The SMS Messaging Service enables users to send SMS messages through the application using prepaid credits.
Technical Details:
- Service Key:
'SMS Messaging' - Credit Label:
'sms' - Usage Pattern: One credit per SMS sent
- Hold Mechanism: Credits held until delivery confirmation
- Integration: Direct integration with SMS gateway APIs
Business Logic:
trait SmsCreditsUsageHelpersTrait
{
protected $service_key = 'SMS Messaging';
public function checkSmsCredits(array $sms, object $recipients = null): array
{
// Count SMS to be sent
$sms_count = $this->countSMStoBeSent($sms);
// Check credit availability
$check_sms = (new CheckCreditsAddOnServiceService($this->service_key, $sms_count))->handle();
if (!$check_sms['status']) {
abort(403, $check_sms['message'] ?? "Insufficient SMS credits.");
}
return ['sms_no' => $sms_count, 'status' => $check_sms];
}
public function removeCredits(bool $hold = false, ?int $sms_no = null): void
{
if ($sms_no === null) $sms_no = $this->sms_no;
(new RemoveCreditsFromAddOnServiceService($this->service_key, $sms_no, $hold))->handle();
}
}Credit Usage Flow:
- Pre-send Validation: Check available credits before sending
- Credit Hold: Reserve credits for pending SMS delivery
- SMS Sending: Execute SMS through gateway
- Delivery Confirmation: Receive delivery report from gateway
- Credit Confirmation: Convert held credits to consumed credits
- Failure Handling: Release held credits if sending fails
2. EVA Docs (LPO Processing) Service
Overview: The EVA Docs service processes Local Purchase Order (LPO) documents through Optical Character Recognition (OCR), charging credits based on document page count.
Technical Details:
- Service Key:
'Eva Docs' - Credit Label:
'pages' - Usage Pattern: Credits based on document page count
- Hold Mechanism: Credits held until OCR processing completion
- Integration: External OCR service processing
Processing Flow:
- Document Upload: User uploads LPO document
- Page Count Analysis: System determines number of pages
- Credit Check: Verify sufficient credits for processing
- Credit Hold: Reserve credits for OCR processing
- OCR Processing: Send document to external OCR service
- Processing Completion: Receive processed data from OCR
- Credit Confirmation: Convert held credits to consumed credits
- Result Delivery: Provide processed LPO data to user
Page-Based Pricing:
// Example pricing structure
$pages_detected = $document->getPageCount();
$credits_required = $pages_detected; // 1 credit per page
// Check and hold credits
$check = (new CheckCreditsAddOnServiceService('Eva Docs', $credits_required))->handle();
if ($check['status']) {
(new RemoveCreditsFromAddOnServiceService('Eva Docs', $credits_required, true))->handle();
}Credit Lifecycle States
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ PURCHASED │───▶│ AVAILABLE │───▶│ HELD │───▶│ CONSUMED │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────┐ │
│ │ │ RELEASED │ │
│ │ └─────────────┘ │
│ │ │ │
│ ▲───────────────────┘ │
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ AUDIT TRAIL │
│ • Issuance Records • Usage Records • History Tracking │
└─────────────────────────────────────────────────────────────────────┘State Definitions
PURCHASED
Description: Credits bought through payment system
- Recorded in
billing_add_ons_paymentstable - Linked to specific pricing plan and user
- Triggers credit issuance process
- Invoice generated and marked as "Closed"
Database Tracking:
-- Payment record
INSERT INTO billing_add_ons_payments (
billing_add_ons_plans_id, payment_method, amount, credits, user_id
) VALUES (1, 'mpesa', 5000.00, 1000, 123);AVAILABLE
Description: Credits ready for consumption
- Tracked in
billing_add_ons_plans.available_quantity - Visible to users in dashboard
- Used for credit availability checks
- Decremented when credits are held or consumed
Balance Calculation:
public function getAvailableCredits($subscriptionId)
{
return BillingAddOnsPlan::where('subscription_id', $subscriptionId)
->where('status_name', 'Active')
->sum('available_quantity');
}HELD
Description: Credits reserved for pending operations
- Marked with
hold = 1inbilling_credits_usages - Prevents double-spending during async operations
- Temporary state until operation completes
- Can be released back to available if operation fails
Hold Implementation:
// Hold credits for async operation
BillingCreditsUsage::create([
'subscription_id' => $subscriptionId,
'product_item_plan_id' => $planId,
'quantity' => $credits,
'hold' => 1,
'created_by' => $userId
]);CONSUMED
Description: Credits permanently used for completed services
- Marked with
hold = 0inbilling_credits_usages - Counted in usage analytics and reporting
- Triggers threshold checks
Consumption Confirmation:
// Convert held credits to consumed
BillingCreditsUsage::where('id', $usageId)
->update(['hold' => 0]);RELEASED
Description: Held credits returned to available pool
- Happens when held operations fail or timeout
- Maintains credit balance integrity
- No permanent record kept (usage record deleted)
- Credits become available again for consumption
Release Implementation:
// Release held credits back to available
$usage = BillingCreditsUsage::where('id', $usageId)->first();
$plan = BillingAddOnsPlan::findOrFail($usage->product_item_plan_id);
$plan->increment('available_quantity', $usage->quantity);
$usage->delete();Credit Management Best Practices
Credit Balance Consistency
Implement database transactions for credit operations:
public function handle(): array
{
try {
DB::beginTransaction();
if(!$this->servicePaymentPlan) throw new \Exception('Service or Pricing Plan not found');
$data = BillingAddOnsPlan::query()
->where('id', $this->servicePaymentPlan->billing_add_ons_plans_id)
->increment('available_quantity', $this->servicePaymentPlan->credits);
// log the credits added to the service
$log = $this->log($this->servicePaymentPlan);
// throw exception if log status is false (error occured while logging the credits added to the service)
if (!$log['status']) throw new \Exception($log['message']);
// update last sent notification that credits have been added
$this->resetNotificationDefer();
} catch (\Throwable $th) {
Log::info($th->getMessage());
DB::rollBack();
return $this->to_method_status('Error adding credits to service. Please try again or contact support.', false, ['error' => $th->getMessage()]);
}
DB::commit();
return $this->to_method_status('Credits added successfully', true, []);
}This comprehensive service type and lifecycle management ensures reliable credit handling across all supported services while providing a clear framework for extending to new service types.