Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Airtel payment provider #77

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions Website/htdocs/mpmanager/app/Console/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Inensus\WavecomPaymentProvider\Console\Commands\InstallPackage as InstallWaveComPackage;
use Inensus\AngazaSHS\Console\Commands\InstallPackage as InstallAngazaSHSPackage;
use Inensus\DalyBms\Console\Commands\InstallPackage as InstallDalyBmsPackage;
use Inensus\AirtelPaymentProvider\Console\Commands\InstallPackage as InstallAirtelPaymentProviderPackage;

class Kernel extends ConsoleKernel
{
Expand Down Expand Up @@ -50,6 +51,7 @@ class Kernel extends ConsoleKernel
InstallWaveComPackage::class,
InstallAngazaSHSPackage::class,
InstallDalyBmsPackage::class,
InstallAirtelPaymentProviderPackage::class,
];

/**
Expand Down
5 changes: 0 additions & 5 deletions Website/htdocs/mpmanager/app/Http/Middleware/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ private function determineSender(Request $request): ITransactionProvider
&& in_array($request->ip(), Config::get('services.vodacom.ips'))
) {
return resolve('VodacomPaymentProvider');
} elseif (
preg_match('/\/airtel/', $request->url())
&& in_array($request->ip(), Config::get('services.airtel.ips'))
) {
return resolve('AirtelPaymentProvider');
} elseif (preg_match('/\/agent/', $request->url()) && auth('agent_api')->user()) {
return resolve('AgentPaymentProvider');
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,21 @@ private function handleApiRequest(Request $request, Closure $next)
}
} elseif ($this->resolveThirdPartyApi($request->path())) {
$companyId = $this->apiCompanyResolverService->resolve($request);
} elseif (strpos($request->path(), 'api/airtel-volt-terra') !== false && $request->isMethod('get')) {
//a path for only volt-terra airtel transactions workaround for now
$companyId = 22; //volt-terra
if (!is_numeric($companyId)) {
throw new \Exception("JWT is not provided");
}
if ($companyId === -1) {
$xmlResponse =
'<?xml version="1.0" encoding="UTF-8"?>' .
'<COMMAND>' .
'<STATUS>400</STATUS>' .
'<MESSAGE>Invalid Token</MESSAGE>' .
'</COMMAND>';

echo $xmlResponse;
exit;
}

} else { //web client authenticated user requests
$companyId = auth('api')->payload()->get('companyId');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Models\AssetPerson;
use App\Models\Device;
use App\Models\Manufacturer;
use App\Models\Meter\Meter;
use App\Models\Meter\MeterTariff;
use App\Models\Token;
use App\Models\Transaction\Transaction;
Expand Down Expand Up @@ -44,7 +45,7 @@ public static function initialize(Transaction $transaction): TransactionDataCont
$container->tariff = null;
$container->manufacturer = $container->device->device->manufacturer()->first();

if ($container->device->device_type === 'meter') {
if ($container->device->device_type === Meter::RELATION_NAME) {
$meter = $container->device->device;
$container->tariff = $meter->tariff()->first();
}
Expand Down
2 changes: 2 additions & 0 deletions Website/htdocs/mpmanager/app/Models/MpmPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class MpmPlugin extends MasterModel
public const DALY_BMS = 16;
public const AGAZA_SHS = 17;

public const AIRTEL_PAYMENT_PROVIDER = 18;

protected $table = 'mpm_plugins';

//has many used
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
class AirtelTransaction extends BaseModel implements IRawTransaction, FullySupportedTransactionInterface
{
public const RELATION_NAME = 'airtel_transaction';
public const STATUS_SUCCESS = 1;
public const STATUS_FAILED = -1;

/**
* @return MorphOne
*/
Expand Down
2 changes: 0 additions & 2 deletions Website/htdocs/mpmanager/app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
use App\Models\Transaction\VodacomTransaction;
use App\Models\User;
use MPM\Transaction\Provider\AgentTransactionProvider as AgentTransactionProvider;
use MPM\Transaction\Provider\AirtelVoltTerraProvider;
use MPM\Transaction\Provider\VodacomTransactionProvider as VodacomTransactionProvider;
use App\Sms\AndroidGateway;
use App\Utils\AccessRatePayer;
Expand Down Expand Up @@ -113,7 +112,6 @@ public function register(): void
$this->app->singleton('AndroidGateway', AndroidGateway::class);
$this->app->singleton('LoanDataContainerProvider', LoanDataContainer::class);
$this->app->singleton('AgentPaymentProvider', AgentTransactionProvider::class);
$this->app->singleton('AirtelVoltTerra', AirtelVoltTerraProvider::class); // workaround until airtel problem
$this->app->singleton('VodacomPaymentProvider', VodacomTransactionProvider::class);
$this->app->bind('MinimumPurchaseAmountValidator', MinimumPurchaseAmountValidator::class);
$this->app->bind('TariffPriceCalculator', TariffPriceCalculator::class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,60 @@
use App\Misc\TransactionDataContainer;
use App\Models\Address\Address;
use App\Models\BaseModel;
use App\Models\Device;
use App\Models\Meter\Meter;
use App\Models\Meter\MeterParameter;
use App\Models\Person\Person;
use App\Models\Transaction\IRawTransaction;
use App\Models\Transaction\Transaction;
use Inensus\SteamaMeter\Exceptions\ModelNotFoundException;
use MPM\Device\DeviceService;

abstract class AbstractPaymentAggregatorTransactionService
{
protected Person $owner;
private const MINIMUM_TRANSACTION_AMOUNT = 0;
protected string $payerPhoneNumber;
protected string $meterSerialNumber;
protected float $minimumPurchaseAmount;
protected int $customerId;
protected string $serialNumber;
protected float $minimumPurchaseAmount = 0;
protected float $amount;

public function __construct(
private Meter $meter,
private DeviceService $deviceService,
private Address $address,
private Transaction $transaction,
private MeterParameter $meterParameter,
private IRawTransaction $paymentAggregatorTransaction,
) {
}

public function validatePaymentOwner(string $meterSerialNumber, float $amount): void
public function validatePaymentOwner(string $serialNumber, float $amount): void
{
if (!$meter = $this->meter->findBySerialNumber($meterSerialNumber)) {
throw new ModelNotFoundException('Meter not found with serial number you entered');
if (!$device = $this->deviceService->getBySerialNumber($serialNumber)) {
throw new ModelNotFoundException('Device not found with serial number you entered');
}

if (!$meterTariff = $meter->meterParameter->tariff) {
throw new ModelNotFoundException('Tariff not found with meter serial number you entered');
if (!$owner = $device->person) {
throw new ModelNotFoundException('Customer not found with serial number you entered');
}

$customerId = $meter->MeterParameter->owner_id;
$deviceType = $device->device_type;
$isMeter = $deviceType == Meter::RELATION_NAME;

if (!$customerId) {
throw new ModelNotFoundException('Customer not found with meter serial number you entered');
if ($isMeter) {
$tariff = $device->device->tariff()->first();

if (!$tariff) {
throw new ModelNotFoundException('Tariff not found with meter serial number you entered');
}
$this->minimumPurchaseAmount = $tariff->minimum_purchase_amount ?? self::MINIMUM_TRANSACTION_AMOUNT;
}

$this->meterSerialNumber = $meterSerialNumber;
$this->minimumPurchaseAmount = $meterTariff->minimum_purchase_amount ?? self::MINIMUM_TRANSACTION_AMOUNT;
$this->customerId = $customerId;
$this->owner = $owner;
$this->serialNumber = $serialNumber;
$this->amount = $amount;

try {
$this->payerPhoneNumber = $this->getTransactionSender($meterSerialNumber);
$this->payerPhoneNumber = $this->getTransactionSender();
} catch (\Exception $exception) {
throw new \Exception($exception->getMessage());
}
Expand All @@ -71,7 +77,7 @@ public function imitateTransactionForValidation(array $transactionData)
$this->transaction = $this->transaction->newQuery()->make([
'amount' => $transactionData['amount'],
'sender' => $this->payerPhoneNumber,
'message' => $this->meterSerialNumber,
'message' => $this->serialNumber,
'type' => 'energy',
'original_transaction_type' => $this->paymentAggregatorTransaction::class,
]);
Expand Down Expand Up @@ -106,17 +112,9 @@ private function isImitationTransactionValid($transaction)
}
}

private function getTransactionSender($meterSerialNumber)
private function getTransactionSender()
{
$meterParameter = $this->meterParameter->newQuery()
->whereHas(
'meter',
function ($q) use ($meterSerialNumber) {
$q->where('serial_number', $meterSerialNumber);
}
)->first();

$personId = $meterParameter->owner_id;
$personId = $this->owner->id;
try {
$address = $this->address->newQuery()
->whereHasMorph(
Expand All @@ -134,12 +132,12 @@ function ($q) use ($personId) {

public function getCustomerId(): int
{
return $this->customerId;
return $this->owner->id;
}

public function getMeterSerialNumber()
public function getSerialNumber()
{
return $this->meterSerialNumber;
return $this->serialNumber;
}

public function getAmount()
Expand All @@ -156,4 +154,14 @@ public function getPaymentAggregatorTransaction(): IRawTransaction
{
return $this->paymentAggregatorTransaction;
}

public function getTransaction(): Transaction
{
return $this->transaction;
}

public function getPayerPhoneNumber()
{
return $this->payerPhoneNumber;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace MPM\Sharding\ApiResolvers;

use Carbon\Carbon;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Illuminate\Support\Facades\Log;
use MPM\Sharding\ApiResolvers\ApiResolverInterface;
use Illuminate\Http\Request;

class AirtelPaymentApiResolver implements ApiResolverInterface
{
private $secretKey = 'KbPeShVmYq3t6w9z$C&F)J@NcQfTjWnZr4u7x!A%D*G-KaPdSgVkXp2s5v8y/B?E';

public function resolveCompanyId(Request $request): int
{
Log::debug('Resolving company ID for Airtel Payment API');
$authHeader = $request->header('Authorization');

if (!$this->isValidAuthHeader($authHeader)) {
throw new \Exception('Authorization header is missing or invalid');
}

$token = $this->extractToken($authHeader);

try {
$decoded = $this->decodeToken($token);
$companyId = $this->extractCompanyId($decoded);

return (int)$companyId;
} catch (\Exception $e) {
Log::error('Error while decoding JWT token: ' . $e->getMessage());
return -1;
}
}

private function isValidAuthHeader($authHeader)
{
return $authHeader && str_starts_with($authHeader, 'Bearer ');
}

private function extractToken($authHeader)
{
return substr($authHeader, 7); // Remove 'Bearer ' from the beginning
}

private function decodeToken($token)
{
return JWT::decode($token, new Key($this->secretKey, 'HS512'));
}

private function extractCompanyId($decoded)
{
$decodedArray = (array)$decoded;
return $decodedArray['sub'] ?? false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace MPM\Sharding\ApiResolvers\Data;

use MPM\Sharding\ApiResolvers\AirtelPaymentApiResolver;
use MPM\Sharding\ApiResolvers\AndroidGatewayCallbackApiResolver;
use MPM\Sharding\ApiResolvers\DataExportResolver;
use MPM\Sharding\ApiResolvers\DownloadingReportsResolver;
Expand All @@ -23,6 +24,8 @@ class ApiResolverMap
public const JETSON_API = 'api/jetson';
public const REPORT_DOWNLOADING_API = 'api/report-downloading';
public const DATA_EXPORTING_API = 'api/export';
public const AIRTEL_PAYMENT_API = 'api/airtel';

public const RESOLVABLE_APIS = [
self::TEST_API,
self::VIBER_API,
Expand All @@ -31,7 +34,8 @@ class ApiResolverMap
self::SWIFTA_PAYMENT_API,
self::JETSON_API,
self::REPORT_DOWNLOADING_API,
self::DATA_EXPORTING_API
self::DATA_EXPORTING_API,
self::AIRTEL_PAYMENT_API
];

private const API_RESOLVER = [
Expand All @@ -42,10 +46,10 @@ class ApiResolverMap
self::SWIFTA_PAYMENT_API => SwiftaPaymentApiResolver::class,
self::JETSON_API => JetsonApiResolver::class,
self::REPORT_DOWNLOADING_API => DownloadingReportsResolver::class,
self::DATA_EXPORTING_API => DataExportResolver::class
self::DATA_EXPORTING_API => DataExportResolver::class,
self::AIRTEL_PAYMENT_API => AirtelPaymentApiResolver::class
];


public function getResolvableApis(): array
{
return self::RESOLVABLE_APIS;
Expand Down
Loading