Payment Processing
Payment methods, transaction handling, and invoice generation
Payment Processing
The Add-on Services system supports multiple payment methods with comprehensive transaction handling and invoice generation.
Supported Payment Methods
1. MPesa (Mobile Money)
- STK Push integration for seamless mobile payments
- Real-time payment verification via transaction reference
- Automatic retry mechanisms for failed transactions
2 Complimentary Credits
- Admin-granted credits with proper audit trails
- No payment required for promotional or support credits
- Full transaction logging for compliance
Future Supported Payment Methods
3. Card Payments
- Multiple gateway support (Flutterwave, Stripe, etc.)
- Secure payment processing with PCI compliance
- 3D Secure authentication for enhanced security
Payment Flow Architecture
sequenceDiagram
participant User as User
participant Frontend as Vue Component
participant API as Payment Controller
participant Service as Payment Service
participant Gateway as Payment Gateway
participant Credits as Credit Service
User->>Frontend: Select plan & payment method
Frontend->>API: POST /purchase-service-plan
API->>Service: handle(request, user)
alt MPesa Payment
Service->>Gateway: initiateSTKPush()
Gateway-->>Service: payment_reference
Service-->>API: payment_initiated
API-->>Frontend: payment_reference
Frontend->>API: POST /verify-purchase-service-plan
API->>Service: verify(payment_reference)
Service->>Gateway: queryTransaction(reference)
Gateway-->>Service: payment_status
end
alt Payment Successful
Service->>Credits: addCreditsToAccount()
Credits-->>Service: credits_added
Service-->>API: payment_completed
API-->>Frontend: success_response
endPayment Controllers
AddOnServicesPaymentController
Main controller handling payment requests from the frontend.
<?php
namespace App\Http\Controllers\V2\Controllers\Services;
class AddOnServicesPaymentController extends Controller
{
public function store(StoreRequest $request): JsonResponse
{
$data = (object) $request->validated();
$user = $request->user();
// Process payment through service
$result = $this->storeAddonService->handle($data, $user);
if (!$result['status']) {
throw new \Exception($result['message'], 400);
}
return $this->success($result['data'], $result['message'], 201);
}
public function verify(VerifyRequest $request): JsonResponse
{
$data = (object) $request->validated();
$user = $request->user();
// Verify payment through service
$result = $this->storeAddonService->verify($data, $user, $servicePayment);
return $this->success($result, 'Payment verified successfully');
}
}BillingAddOnsPaymentController
Administrative controller for payment management and reporting.
<?php
namespace App\Http\Controllers\V2\Controllers\AddonServices;
class BillingAddOnsPaymentController extends Controller
{
public function index(): JsonResponse
{
// List payments with filtering and pagination
$payments = BillingAddOnsPayment::with(['plan', 'user'])
->when(request('status'), function ($query, $status) {
return $query->where('invoice_status', $status);
})
->when(request('user_id'), function ($query, $userId) {
return $query->where('user_id', $userId);
})
->orderBy('created_at', 'desc')
->paginate(50);
return response()->json($payments);
}
public function generateInvoice(GenerateInvoiceRequest $request): JsonResponse
{
// Generate invoice for payment
$invoice = $this->invoiceService->generate($request->validated());
return $this->success($invoice, 'Invoice generated successfully');
}
}Payment Service Implementation
StoreAddOnServicesPaymentTransactionService
Core service handling payment transactions.
<?php
namespace App\Services\AddOnServices\Payments;
class StoreAddOnServicesPaymentTransactionService
{
public function handle(object $request, User $user): array
{
// Validate payment action and user permissions
$this->validatePaymentRequest($request, $user);
// Get selected plan details
$plan = BillingAddOnsPlan::findOrFail($request->billing_add_ons_plans_id);
try {
DB::beginTransaction();
// Create payment record
$payment = $this->createPaymentRecord($request, $plan, $user);
// Process payment based on method
$result = $this->processPaymentByMethod($request, $payment, $user);
DB::commit();
return [
'status' => true,
'data' => $result,
'message' => 'Payment initiated successfully'
];
} catch (Exception $e) {
DB::rollBack();
Log::error('Payment processing failed', [
'user_id' => $user->id,
'plan_id' => $request->billing_add_ons_plans_id,
'error' => $e->getMessage()
]);
throw $e;
}
}
public function verify(object $request, User $user, BillingAddOnsPlan $servicePlan): BillingAddOnsPayment
{
// Verify payment with gateway
$paymentStatus = $this->verifyWithGateway($request->payment_reference);
if ($paymentStatus['success']) {
// Update payment status
$payment = BillingAddOnsPayment::where('payment_reference', $request->payment_reference)
->firstOrFail();
$payment->update([
'invoice_status' => 'Closed',
'verified_at' => now()
]);
// Allocate credits
$this->allocateCredits($payment, $servicePlan);
return $payment;
}
throw new PaymentVerificationException('Payment verification failed');
}
private function processPaymentByMethod(object $request, BillingAddOnsPayment $payment, User $user)
{
switch ($request->payment_method) {
case 'mpesa':
return $this->processMpesaPayment($request, $payment);
case 'card':
return $this->processCardPayment($request, $payment);
case 'complimentary':
return $this->processComplimentaryPayment($payment, $user);
default:
throw new InvalidPaymentMethodException('Unsupported payment method');
}
}
}MPesa Integration
STK Push Implementation
<?php
namespace App\Services\Payments;
class MpesaPaymentService
{
public function initiateStkPush($amount, $phoneNumber, $reference): array
{
$endpoint = config('mpesa.base_url') . '/mpesa/stkpush/v1/processrequest';
$payload = [
'BusinessShortCode' => config('mpesa.shortcode'),
'Password' => $this->generatePassword(),
'Timestamp' => $this->getTimestamp(),
'TransactionType' => 'CustomerPayBillOnline',
'Amount' => $amount,
'PartyA' => $phoneNumber,
'PartyB' => config('mpesa.shortcode'),
'PhoneNumber' => $phoneNumber,
'CallBackURL' => route('mpesa.callback'),
'AccountReference' => $reference,
'TransactionDesc' => 'Add-on Services Payment'
];
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $this->getAccessToken(),
'Content-Type' => 'application/json'
])->post($endpoint, $payload);
if ($response->successful()) {
$data = $response->json();
return [
'success' => true,
'checkout_request_id' => $data['CheckoutRequestID'],
'merchant_request_id' => $data['MerchantRequestID'],
'response_code' => $data['ResponseCode']
];
}
throw new MpesaException('STK Push failed: ' . $response->body());
}
public function verifyTransaction($checkoutRequestId): array
{
$endpoint = config('mpesa.base_url') . '/mpesa/stkpushquery/v1/query';
$payload = [
'BusinessShortCode' => config('mpesa.shortcode'),
'Password' => $this->generatePassword(),
'Timestamp' => $this->getTimestamp(),
'CheckoutRequestID' => $checkoutRequestId
];
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $this->getAccessToken(),
'Content-Type' => 'application/json'
])->post($endpoint, $payload);
if ($response->successful()) {
$data = $response->json();
return [
'success' => $data['ResultCode'] === '0',
'result_desc' => $data['ResultDesc'],
'amount' => $data['Amount'] ?? null,
'mpesa_receipt' => $data['MpesaReceiptNumber'] ?? null
];
}
throw new MpesaException('Transaction verification failed: ' . $response->body());
}
private function generatePassword(): string
{
$shortcode = config('mpesa.shortcode');
$passkey = config('mpesa.passkey');
$timestamp = $this->getTimestamp();
return base64_encode($shortcode . $passkey . $timestamp);
}
private function getTimestamp(): string
{
return date('YmdHis');
}
private function getAccessToken(): string
{
// Cache access token for 1 hour
return Cache::remember('mpesa_access_token', 3600, function () {
return $this->requestAccessToken();
});
}
}Invoice Generation System
<?php
namespace App\Services\AddOnServices\Payments;
class GenerateAddOnInvoiceService
{
public function handle(object $request): array
{
try {
DB::beginTransaction();
// Step 1: Validate request and check for duplicates
$this->validateInvoiceRequest($request);
// Step 2: Get plan and create payment record
$plan = $this->getAddOnPlan($request);
$payment = $this->createPaymentRecord($request, $plan);
// Step 3: Generate invoice through external service
$invoiceData = $this->generateInvoiceDocument($payment, $plan);
// Step 4: Update payment with invoice details
$payment->update([
'invoice_number' => $invoiceData['invoice_number'],
'invoice_document' => $invoiceData['document_path'],
'invoiced_at' => now()
]);
DB::commit();
return [
'success' => true,
'invoice_number' => $invoiceData['invoice_number'],
'document_path' => $invoiceData['document_path']
];
} catch (Exception $e) {
DB::rollBack();
Log::error('Invoice generation failed', [
'request' => $request,
'error' => $e->getMessage()
]);
throw $e;
}
}
private function generateInvoiceDocument($payment, $plan): array
{
$invoiceController = new InvoiceController();
$invoiceData = [
'invoice_type' => 'proforma',
'client_id' => $payment->user->client_id,
'invoice_date' => now()->format('Y-m-d'),
'due_date' => now()->addDays(30)->format('Y-m-d'),
'discount_percent' => 0,
'status_id' => Status::where('name', 'Pending')->firstOrFail()->id,
'sync_to_kra' => false,
'adjustment_amount' => 0,
'custom_subject' => "Add-on Services Invoice",
'invoice_items' => [
[
'product_item_id' => $plan->product_item_id,
'qty' => $payment->credits,
'unit_price' => $payment->amount / $payment->credits,
'description' => "{$plan->name} - {$payment->credits} credits",
'tax_rate' => 0
]
]
];
$request = new Request($invoiceData);
$response = $invoiceController->store($request);
return json_decode($response->getContent(), true);
}
}This comprehensive payment processing system ensures secure, reliable handling of transactions across multiple payment methods while maintaining proper audit trails and error handling.