Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/usage-guide/images/payment-flow.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 47 additions & 15 deletions src/backend/app/Http/Controllers/AppliancePaymentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,40 @@
use App\Models\AppliancePerson;
use App\Services\AppliancePaymentService;
use App\Services\AppliancePersonService;
use App\Services\CashTransactionService;
use App\Services\PaymentInitializationService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class AppliancePaymentController extends Controller {
public const CASH_TRANSACTION_PROVIVER = 0;

public function __construct(
private AppliancePaymentService $appliancePaymentService,
private AppliancePersonService $appliancePersonService,
private CashTransactionService $cashTransactionService,
private PaymentInitializationService $paymentInitializationService,
) {}

public function store(AppliancePerson $appliancePerson, Request $request): ApiResource {
public function store(AppliancePerson $appliancePerson, Request $request): ApiResource|JsonResponse {
try {
DB::connection('tenant')->beginTransaction();
$result = $this->getPaymentForAppliance($request, $appliancePerson);
DB::connection('tenant')->commit();

return ApiResource::make([
'appliance_person' => $result['appliance_person'],
'transaction_id' => $result['transaction_id'],
]);
return ApiResource::make(array_merge(
[
'appliance_person' => $result['appliance_person'],
'transaction_id' => $result['transaction_id'],
],
$result['provider_data'],
));
} catch (\InvalidArgumentException $e) {
DB::connection('tenant')->rollBack();

return response()->json(['message' => $e->getMessage()], 422);
} catch (\Exception $e) {
DB::connection('tenant')->rollBack();
throw new \Exception($e->getMessage(), $e->getCode(), $e);
throw new \Exception($e->getMessage(), (int) $e->getCode(), $e);
}
}

Expand All @@ -40,32 +50,54 @@ public function checkStatus(int $transactionId): ApiResource {
return ApiResource::make($status);
}

public function paymentProviders(): ApiResource {
$providers = $this->paymentInitializationService->paymentProviders();

return ApiResource::make($providers);
}

/**
* @return array<string, mixed>
*/
public function getPaymentForAppliance(Request $request, AppliancePerson $appliancePerson): array {
private function getPaymentForAppliance(Request $request, AppliancePerson $appliancePerson): array {
$creatorId = auth('api')->user()->id;
$amount = (float) $request->input('amount');
$providerId = (int) $request->input('payment_provider', 0);
$companyId = $request->attributes->get('companyId');

$applianceDetail = $this->appliancePersonService->getApplianceDetails($appliancePerson->id);
$this->appliancePaymentService->validateAmount($applianceDetail, $amount);
$deviceSerial = $applianceDetail->device_serial;
$applianceOwner = $appliancePerson->person;
$companyId = $request->attributes->get('companyId');

if (!$applianceOwner) {
throw new \InvalidArgumentException('Appliance owner not found');
}

$ownerAddress = $applianceOwner->addresses()->where('is_primary', 1)->first();
$sender = $ownerAddress == null ? '-' : $ownerAddress->phone;
$transaction =
$this->cashTransactionService->createCashTransaction($creatorId, $amount, $sender, $deviceSerial, $appliancePerson->id);
$sender = $ownerAddress === null ? '-' : $ownerAddress->phone;

dispatch(new ProcessPayment($companyId, $transaction->id));
$message = $deviceSerial ?? (string) $appliancePerson->id;

$result = $this->paymentInitializationService->initialize(
providerId: $providerId,
amount: $amount,
sender: $sender,
message: $message,
type: 'deferred_payment',
customerId: $applianceOwner->id,
creatorId: $creatorId,
serialId: $deviceSerial,
);

if ($providerId === $this::CASH_TRANSACTION_PROVIVER) {
dispatch(new ProcessPayment($companyId, $result['transaction']->id));
}

return [
'appliance_person' => $appliancePerson,
'transaction_id' => $transaction->id,
'transaction_id' => $result['transaction']->id,
'provider_data' => $result['provider_data'],
];
}
}
57 changes: 38 additions & 19 deletions src/backend/app/Http/Controllers/AppliancePersonController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Events\PaymentSuccessEvent;
use App\Events\TransactionSuccessfulEvent;
use App\Http\Resources\ApiResource;
use App\Jobs\ProcessPayment;
use App\Models\Appliance;
use App\Models\AppliancePerson;
use App\Models\Person\Person;
Expand All @@ -13,15 +14,17 @@
use App\Services\AppliancePersonService;
use App\Services\ApplianceRateService;
use App\Services\ApplianceService;
use App\Services\CashTransactionService;
use App\Services\DeviceService;
use App\Services\GeographicalInformationService;
use App\Services\PaymentInitializationService;
use App\Services\UserAppliancePersonService;
use App\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class AppliancePersonController extends Controller {
public const CASH_TRANSACTION_PROVIVER = 0;

public function __construct(
private AppliancePerson $appliancePerson,
private AppliancePersonService $appliancePersonService,
Expand All @@ -31,7 +34,7 @@ public function __construct(
private AddressesService $addressesService,
private GeographicalInformationService $geographicalInformationService,
private AddressGeographicalInformationService $addressGeographicalInformationService,
private CashTransactionService $cashTransactionService,
private PaymentInitializationService $paymentInitializationService,
private ApplianceService $applianceService,
private ApplianceRateService $applianceRateService,
) {}
Expand Down Expand Up @@ -98,29 +101,45 @@ public function store(
$this->addressGeographicalInformationService->assign();
$this->geographicalInformationService->save($geographicalInformation);
}
$downPaymentInitData = [];
if ($downPayment > 0) {
$paymentProviderId = (int) $request->input('payment_provider', 0);
$companyId = $request->attributes->get('companyId');
$sender = isset($addressData) ? $addressData['phone'] : '-';
$transaction = $this->cashTransactionService->createCashTransaction(
$user->id,
$downPayment,
$sender,
$deviceSerial
$message = $deviceSerial ?? (string) $appliancePerson->id;
$person = $appliancePerson->person;

$result = $this->paymentInitializationService->initialize(
providerId: $paymentProviderId,
amount: $downPayment,
sender: $sender,
message: $message,
type: 'deferred_payment',
customerId: $person->id,
creatorId: $user->id,
serialId: $deviceSerial ?? null,
);
$applianceRate = $this->applianceRateService->getDownPaymentAsApplianceRate($appliancePerson);
event(new PaymentSuccessEvent(
amount: (int) $transaction->amount,
paymentService: 'web',
paymentType: 'down payment',
sender: $transaction->sender,
paidFor: $applianceRate,
payer: $appliancePerson->person,
transaction: $transaction,
));
event(new TransactionSuccessfulEvent($transaction));

if ($paymentProviderId === $this::CASH_TRANSACTION_PROVIVER) {
$applianceRate = $this->applianceRateService->getDownPaymentAsApplianceRate($appliancePerson);
event(new PaymentSuccessEvent(
amount: (int) $result['transaction']->amount,
paymentService: 'web',
paymentType: 'down payment',
sender: $result['transaction']->sender,
paidFor: $applianceRate,
payer: $appliancePerson->person,
transaction: $result['transaction'],
));
event(new TransactionSuccessfulEvent($result['transaction']));
} else {
dispatch(new ProcessPayment($companyId, $result['transaction']->id));
$downPaymentInitData = $result['provider_data'];
}
}
DB::connection('tenant')->commit();

return ApiResource::make($appliancePerson);
return ApiResource::make(array_merge(['appliance_person' => $appliancePerson], $downPaymentInitData));
} catch (\Exception $e) {
DB::connection('tenant')->rollBack();
throw new \Exception($e->getMessage(), (int) $e->getCode(), $e);
Expand Down
6 changes: 6 additions & 0 deletions src/backend/app/Models/Plugins.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Models;

use App\Models\Base\BaseModel;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;

/**
Expand All @@ -15,4 +16,9 @@
class Plugins extends BaseModel {
public const ACTIVE = 1;
public const INACTIVE = 0;

/** @return BelongsTo<MpmPlugin, $this> */
public function mpmPlugin(): BelongsTo {
return $this->belongsTo(MpmPlugin::class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace App\Plugins\PaystackPaymentProvider\Http\Controllers;

use App\Plugins\PaystackPaymentProvider\Http\Requests\TransactionInitializeRequest;
use App\Plugins\PaystackPaymentProvider\Http\Resources\PaystackResource;
use App\Plugins\PaystackPaymentProvider\Http\Resources\PaystackTransactionResource;
use App\Plugins\PaystackPaymentProvider\Models\PaystackTransaction;
use App\Plugins\PaystackPaymentProvider\Modules\Api\PaystackApiService;
Expand All @@ -23,10 +22,28 @@ public function __construct(
private PaystackWebhookService $webhookService,
) {}

public function startTransaction(TransactionInitializeRequest $request): PaystackResource {
$transaction = $request->getPaystackTransaction();

return PaystackResource::make($this->apiService->initializeTransaction($transaction));
public function initializeTransaction(TransactionInitializeRequest $request): JsonResponse {
$customerId = (int) $request->input('customer_id');
$serialId = $request->input('device_serial');
$amount = (float) $request->input('amount');
$sender = $this->transactionService->getCustomerPhoneByCustomerId($customerId) ?? '';

$result = $this->transactionService->initializePayment(
amount: $amount,
sender: $sender,
message: $serialId,
type: 'energy',
customerId: $customerId,
serialId: $serialId,
);

return response()->json([
'data' => [
'redirectionUrl' => $result['provider_data']['redirect_url'],
'reference' => $result['provider_data']['reference'],
'error' => null,
],
]);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Log;
use Ramsey\Uuid\Uuid;

class PaystackPublicController extends Controller {
public function __construct(
Expand Down Expand Up @@ -66,12 +65,7 @@ public function initiatePayment(PublicPaymentRequest $request, string $companyHa
return response()->json(['error' => 'Invalid company identifier'], 400);
}

// Validate device serial and amount
$validatedData = $request->validated();

// Get agent_id from query parameters if present
$agentId = $request->query('agent');

$deviceType = $validatedData['device_type'] ?? 'meter';
$deviceSerial = $validatedData['device_serial'];

Expand All @@ -82,30 +76,26 @@ public function initiatePayment(PublicPaymentRequest $request, string $companyHa
$customerId = $this->transactionService->getCustomerIdByMeterSerial($deviceSerial);
}

// Create Paystack transaction
$transaction = $this->transactionService->createPublicTransaction([
'amount' => $validatedData['amount'],
'currency' => $validatedData['currency'],
'serial_id' => $deviceSerial,
'device_type' => $deviceType,
'customer_id' => $customerId,
'order_id' => Uuid::uuid4()->toString(),
'reference_id' => Uuid::uuid4()->toString(),
'agent_id' => $agentId ? (int) $agentId : null,
]);
if (!$customerId) {
return response()->json(['error' => 'Customer not found for device'], 400);
}

// Initialize Paystack transaction with company ID for callback URL
$result = $this->apiService->initializeTransaction($transaction, $companyId);
$sender = $this->transactionService->getCustomerPhoneByCustomerId($customerId) ?? '';

if ($result['error']) {
return response()->json(['error' => $result['error']], 400);
}
$result = $this->transactionService->initializePayment(
amount: (float) $validatedData['amount'],
sender: $sender,
message: $deviceSerial,
type: 'energy',
customerId: $customerId,
serialId: $deviceSerial,
);

return response()->json([
'success' => true,
'redirection_url' => $result['redirectionUrl'],
'reference' => $result['reference'],
'transaction_id' => $transaction->getId(),
'redirect_url' => $result['provider_data']['redirect_url'],
'reference' => $result['provider_data']['reference'],
'transaction_id' => $result['transaction']->id,
]);
} catch (\Exception $e) {
Log::error('PaystackPublicController: Failed to initiate payment', [
Expand Down Expand Up @@ -167,6 +157,7 @@ public function showResult(Request $request, string $companyHash, ?int $companyI
'currency' => $transaction->getCurrency(),
'serial_id' => $transaction->getDeviceSerial(),
'device_type' => $transaction->getDeviceType(),
'payment_type' => $mainTransaction?->type,
'status' => $transaction->getStatus(),
'created_at' => $transaction->getAttribute('created_at'),
],
Expand Down
Loading
Loading