Solutech Engineering
Innovations/Add-on Services

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:

  1. Pre-send Validation: Check available credits before sending
  2. Credit Hold: Reserve credits for pending SMS delivery
  3. SMS Sending: Execute SMS through gateway
  4. Delivery Confirmation: Receive delivery report from gateway
  5. Credit Confirmation: Convert held credits to consumed credits
  6. 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:

  1. Document Upload: User uploads LPO document
  2. Page Count Analysis: System determines number of pages
  3. Credit Check: Verify sufficient credits for processing
  4. Credit Hold: Reserve credits for OCR processing
  5. OCR Processing: Send document to external OCR service
  6. Processing Completion: Receive processed data from OCR
  7. Credit Confirmation: Convert held credits to consumed credits
  8. 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_payments table
  • 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 = 1 in billing_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 = 0 in billing_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.