diff --git a/app/Classes/Modules/Billplzs/ControllersLogic/CallbackBillplzLogic.php b/app/Classes/Modules/Billplzs/ControllersLogic/CallbackBillplzLogic.php new file mode 100644 index 00000000..2230343f --- /dev/null +++ b/app/Classes/Modules/Billplzs/ControllersLogic/CallbackBillplzLogic.php @@ -0,0 +1,105 @@ +getBillplzBill = $getBillplzBill; + $this->fetchesTransaction = $fetchesTransaction; + $this->updatesTransactionStatus = $updatesTransactionStatus; + $this->updatesWalletBalance = $updatesWalletBalance; + } + + + /** + * @param Request $request + * @return bool|\Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws MalformedRequestException + * @throws ResourceNotFoundException + */ + public function execute(Request $request) + { + $billplzXSignatureObject = new BillplzXSignatureObject($request); + + if(!$billplzXSignatureObject->isValidSignature()){ + throw new MalformedRequestException('Billplz Payment validation failed.'); + } + + $billPlz = $this->getBillplzBill->execute($billplzXSignatureObject->getBillPlzId()); + + if(!$billPlz) throw new ResourceNotFoundException('Billplz bill not found.'); + + $transaction = $this->fetchesTransaction->execute(['payment_reference' => $billplzXSignatureObject->getBillPlzId()]); + + if($billPlz->state === 'paid') { + $status = ApprovalStatus::APPROVED; + } + + if($billPlz->state === 'due') { + $status = $billplzXSignatureObject->getStatus() === 'failed' ? ApprovalStatus::REJECTED : ApprovalStatus::PENDING_VERIFICATION; + } + + if($transaction->status !== ApprovalStatus::COMPLETED){ + + if($transaction->owner instanceof Wallet && $transaction->status !== ApprovalStatus::APPROVED && $status === ApprovalStatus::APPROVED) { + $this->updatesWalletBalance->execute($transaction->owner, $transaction->amount); + } + $this->updatesTransactionStatus->execute($transaction, $status); + + } + + + $token = Auth::fromUser(User::find(1)); + $request->headers->set('Authorization', 'Bearer '.$token); + + $marking = $transaction->owner instanceof Booking ? $transaction->booking->marking : $transaction->owner->owner->bookings()->orderBy('id', 'DESC')->first()->marking; + + return $request->method() === 'POST' ? true : view('pages.payments_redirect', ['marking' => $marking, 'transaction' => $transaction, 'status' => $status]); + + } +} \ No newline at end of file diff --git a/app/Classes/Modules/Billplzs/ControllersLogic/CreateBillplzBillLogic.php b/app/Classes/Modules/Billplzs/ControllersLogic/CreateBillplzBillLogic.php new file mode 100644 index 00000000..a7069841 --- /dev/null +++ b/app/Classes/Modules/Billplzs/ControllersLogic/CreateBillplzBillLogic.php @@ -0,0 +1,54 @@ + 'Created Billplz Bill', + 'message' => 'You have successfully created a new Bill' + ]; + } + + /** @var CreatesBillplzBill */ + private $createsBillplzBill; + + /** + * CreateBookingLogic constructor. + * @param CreatesBillplzBill $createsBillplzBill + */ + public function __construct(CreatesBillplzBill $createsBillplzBill) + { + $this->createsBillplzBill = $createsBillplzBill; + } + + + /** + * @param Request $request + * @return JsonResponse + * @throws MalformedRequestException + */ + public function logic(Request $request) : JsonResponse + { + $billPlz = $this->createsBillplzBill->execute($request->user()->name, $request->user()->email, $request->input('description'), 200, $request->input('bankName')); + + if(!$billPlz) throw new MalformedRequestException('Unable to get correct response from billplz server.'); + + return $this->response(['data' => $billPlz]); + + } +} \ No newline at end of file diff --git a/app/Classes/Modules/Billplzs/DataTransferObjects/BillplzXSignatureObject.php b/app/Classes/Modules/Billplzs/DataTransferObjects/BillplzXSignatureObject.php new file mode 100644 index 00000000..c1295886 --- /dev/null +++ b/app/Classes/Modules/Billplzs/DataTransferObjects/BillplzXSignatureObject.php @@ -0,0 +1,120 @@ +type = $request->exists('billplz') ? 'redirect' : 'callback'; + + $this->request = $this->type === 'redirect' ? $request->input('billplz') : $request; + + $this->billPlzId = $this->request['id']; + + $this->status = $this->request['transaction_status']; + + $this->requestXSignature = $this->request['x_signature']; + + $this->_constructBillplzArray()->_natSortBillplzArray()->_constructBillplzString()->_computeBillplzXSignature(); + } + + private function _constructBillplzArray(){ + + $this->billPlzConstructArray = collect($this->request)->forget('x_signature')->map(function($item, $key){ + return $this->type === 'redirect' ? 'billplz'.$key.$item : $key.$item; + })->toArray(); + + return $this; + } + + private function _natCaseSortBillplzArray(){ + natcasesort($this->billPlzConstructArray); + return $this; + } + + private function _natSortBillplzArray(){ + natsort($this->billPlzConstructArray); + return $this; + } + + private function _constructBillplzString(){ + $this->billPlzConstructString = implode('|', $this->billPlzConstructArray); + return $this; + } + + private function _computeBillplzXSignature(){ + $this->billPlzComputedXSignature = hash_hmac('sha256', $this->billPlzConstructString, config('billplz.x_signature_key')); + return $this; + } + + public function getBillPlzId(): string + { + return $this->billPlzId; + } + + public function getStatus(): string + { + return $this->status; + } + + /** + * @return array + */ + public function getBillPlzConstructArray(): array + { + return $this->billPlzConstructArray; + } + + /** + * @return string + */ + public function getBillPlzConstructString(): string + { + return $this->billPlzConstructString; + } + + /** + * @return string + */ + public function getBillPlzComputedXSignature(): string + { + return $this->billPlzComputedXSignature; + } + + public function isValidSignature(): bool + { + return $this->billPlzComputedXSignature === $this->requestXSignature ? true : false; + } + +} \ No newline at end of file diff --git a/app/Classes/Modules/Billplzs/Services/CreatesBillplzBill.php b/app/Classes/Modules/Billplzs/Services/CreatesBillplzBill.php new file mode 100644 index 00000000..5b331fca --- /dev/null +++ b/app/Classes/Modules/Billplzs/Services/CreatesBillplzBill.php @@ -0,0 +1,57 @@ +post(config('billplz.base_url').'/api/v3/bills', [ + 'collection_id' => $wallet ? config('billplz.wallet_collection_id') : config('billplz.collection_id'), + 'name' => $name, + 'email' => $email, + 'description' => $description, + 'amount' => $this->finalizeAmount($amount), + 'redirect_url' => route('online_payment.redirect'), + 'callback_url' => route('api.online_payment.callback'), + 'reference_1_label' => 'Bank Code', + 'reference_1' => $bankCode ? $bankCode : '', + 'reference_2_label' => 'Bill Number', + 'reference_2' => $billNumber + ]); + + if($response->successful()){ + $data = $response->json(); + + $data['url'] = $data['url'].'?auto_submit=true'; + + return (object) $data; + }else{ + return null; + } + }catch(\Exception $exception){ + throw new MalformedRequestException('Unable to get correct response from billplz server' . $exception->getMessage()); + } + } + + protected function finalizeAmount($amount){ + $number = round($amount, 2) * 100; + return (string) $number; + } +} \ No newline at end of file diff --git a/app/Classes/Modules/Billplzs/Services/GetBillplzBill.php b/app/Classes/Modules/Billplzs/Services/GetBillplzBill.php new file mode 100644 index 00000000..2a86b403 --- /dev/null +++ b/app/Classes/Modules/Billplzs/Services/GetBillplzBill.php @@ -0,0 +1,31 @@ +get(config('billplz.base_url').'/api/v3/bills/'.$billPlzId); + + if($response->successful()){ + $data = $response->json(); + + return (object) $data; + }else{ + return null; + } + }catch(\Exception $exception){ + throw new MalformedRequestException('Unable to get correct response from billplz server'); + } + } +} \ No newline at end of file diff --git a/app/Classes/Modules/Transactions/ControllersLogic/CreatePaymentTransactionLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/CreatePaymentTransactionLogic.php new file mode 100644 index 00000000..bd99d86d --- /dev/null +++ b/app/Classes/Modules/Transactions/ControllersLogic/CreatePaymentTransactionLogic.php @@ -0,0 +1,121 @@ + 'Create Payment Transactions', + 'message' => 'You have successfully created currency supplier transactions' + ]; + } + + /** @var FetchesTransaction */ + private $fetchesTransaction; + + /** @var FetchesSegmentConstant */ + private $fetchesSegmentConstant; + + /** @var GeneratesTransactionBillNumber */ + private $generatesTransactionBillNumber; + + /** @var CreatesPaymentTransaction */ + private $createsPaymentTransaction; + + /** @var CreatesBillplzBill */ + private $createsBillplzBill; + + /** @var CreatesTransactionDetail */ + private $createsTransactionDetail; + + public function __construct( + FetchesTransaction $fetchesTransaction, + GeneratesTransactionBillNumber $generatesTransactionBillNumber, + CreatesPaymentTransaction $createsPaymentTransaction, + CreatesTransactionDetail $createsTransactionDetail, + CreatesBillplzBill $createsBillplzBill + ) + { + $this->fetchesTransaction = $fetchesTransaction; + $this->generatesTransactionBillNumber = $generatesTransactionBillNumber; + $this->createsPaymentTransaction = $createsPaymentTransaction; + $this->createsTransactionDetail = $createsTransactionDetail; + $this->createsBillplzBill = $createsBillplzBill; + } + + public function logic(Request $request) : JsonResponse + { + $invoice_transaction = $this->fetchesTransaction->execute(['id' => $request->input('transaction_id')]); + $amount = $request->input('amount'); + $company_module = $invoice_transaction->owner()->first()->owner()->first()->companyModule()->first(); + + $billNumber = $this->generatesTransactionBillNumber->execute('PYMT-'); + $payment_reference = null; + if ($request->input('payment_method') == 5) { + $payment_method = PaymentMethodType::PAYMENT_GATEWAY; + $billPlzBill = $this->createsBillplzBill->execute( + $company_module->name, + $company_module->employees()->first()->email, + 'This payment is credit topup for company ref. ' . $company_module->reference, $amount, $billNumber, + $request->input('bank_code'), true + ); + + $payment_reference = $billPlzBill->id; + } + else { + $payment_method = PaymentMethodType::CASH; + } + + $object = new TransactionObject( + $billNumber, + TransactionType::PAYMENT, + $company_module->id, + 1, + 1, + $payment_method, + $request->input('amount'), + $request->input('amount'), + 1, + 1, + 0, + 0, + 0, + null, + ApprovalStatus::PENDING_SUBMISSION, + null, + $payment_reference + ); + + $payment_transaction = $this->createsPaymentTransaction->execute($invoice_transaction, $object); + + return $this->response([]); + } +} diff --git a/app/Classes/Modules/Transactions/ControllersLogic/CreateShippingInvoiceTransactionLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/CreateShippingInvoiceTransactionLogic.php index e4729533..4da640a0 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/CreateShippingInvoiceTransactionLogic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/CreateShippingInvoiceTransactionLogic.php @@ -21,23 +21,10 @@ use App\Classes\ValueObjects\Constants\PaymentMethodType; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\DocumentType; -// use App\Classes\Modules\Documents\DataTransferObjects\DocumentObject; -// use App\Classes\Modules\Transactions\Services\FetchesTransaction; -// use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus; - -// use App\Models\Document; -// use App\Models\Transaction; -// use Barryvdh\DomPDF\PDF; -// use Carbon\Carbon; - - - use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; - -// use Meneses\LaravelLaravelMpdf\Facades\LaravelLaravelMpdf; use Meneses\LaravelMpdf\Facades\LaravelMpdf; class CreateShippingInvoiceTransactionLogic extends AbstractControllerLogic @@ -74,16 +61,6 @@ class CreateShippingInvoiceTransactionLogic extends AbstractControllerLogic /** @var CreatesFiles */ private $createsFile; - - // /** @var FetchesTransaction */ - // /** @var UpdatesTransactionStatus */ - // private $updatesTransactionStatus; - // /** @var CreatesDocument */ - // private $createsDocument; - // /** @var CreatesFiles */ - // private $createsFile; - // /** @var PDF */ - // private $pdf; public function __construct( FetchesPackingList $fetchesPackingList, @@ -93,10 +70,6 @@ class CreateShippingInvoiceTransactionLogic extends AbstractControllerLogic CreatesTransactionDetail $createsTransactionDetail, CreatesDocument $createsDocument, CreatesFiles $createsFile - - // CreatesDocument $createsDocument, - // CreatesFiles $createsFile, - // PDF $pdf ) { @@ -107,11 +80,6 @@ class CreateShippingInvoiceTransactionLogic extends AbstractControllerLogic $this->createsTransactionDetail = $createsTransactionDetail; $this->createsDocument = $createsDocument; $this->createsFile = $createsFile; - - // $this->updatesTransactionStatus = $updatesTransactionStatus; - // $this->createsDocument = $createsDocument; - // $this->createsFile = $createsFile; - // $this->pdf = $pdf; } public function logic(Request $request) : JsonResponse diff --git a/app/Classes/Modules/Transactions/DataTransferObjects/TransactionObject.php b/app/Classes/Modules/Transactions/DataTransferObjects/TransactionObject.php index b4153751..5e943e29 100644 --- a/app/Classes/Modules/Transactions/DataTransferObjects/TransactionObject.php +++ b/app/Classes/Modules/Transactions/DataTransferObjects/TransactionObject.php @@ -57,6 +57,9 @@ class TransactionObject implements DataTransferObject /** @var array|null */ private $details; + /** @var string */ + private $paymentReference; + /** * TransactionObject constructor. * @param string $billNo @@ -92,7 +95,8 @@ class TransactionObject implements DataTransferObject float $serviceCharge, ?Carbon $expiresOn, ?int $status = ApprovalStatus::PENDING_SUBMISSION, - ?array $details = [] + ?array $details = [], + ?string $paymentReference = null ) { $this->billNo = $billNo; @@ -111,6 +115,7 @@ class TransactionObject implements DataTransferObject $this->expiresOn = $expiresOn; $this->status = $status; $this->details = $details; + $this->paymentReference = $paymentReference; } /** @@ -243,7 +248,11 @@ class TransactionObject implements DataTransferObject }, $this->details); } - - - + /** + * @return string|null + */ + public function getPaymentReference(): ?string + { + return $this->paymentReference; + } } \ No newline at end of file diff --git a/app/Classes/Modules/Transactions/Services/CreatesPaymentTransaction.php b/app/Classes/Modules/Transactions/Services/CreatesPaymentTransaction.php new file mode 100644 index 00000000..54afd914 --- /dev/null +++ b/app/Classes/Modules/Transactions/Services/CreatesPaymentTransaction.php @@ -0,0 +1,37 @@ +bill_no = $object->getBillNo(); + $model->type = $object->getTransactionType(); + $model->issuer = $object->getIssuer(); + $model->receiver = $object->getReceiver(); + $model->recipient_bank_account_id = $object->getRecipientBankAccountId(); + $model->payment_method = $object->getPaymentMethod(); + $model->amount = $object->getAmount(); + $model->original_amount = $object->getOriginalAmount(); + $model->currency_id = $object->getCurrencyId(); + $model->original_currency_id = $object->getOriginalCurrencyId(); + $model->currency_rate = $object->getCurrencyRate(); + $model->tax = $object->getTax(); + $model->service_charge = $object->getServiceCharge(); + $model->expires_on = $object->getExpiresOn(); + $model->status = $object->getStatus(); + + return $this->handler($transaction->transactions(), $model); + } +} \ No newline at end of file diff --git a/app/Classes/ValueObjects/Constants/TransactionType.php b/app/Classes/ValueObjects/Constants/TransactionType.php index d61589fa..07277b64 100644 --- a/app/Classes/ValueObjects/Constants/TransactionType.php +++ b/app/Classes/ValueObjects/Constants/TransactionType.php @@ -6,9 +6,10 @@ final class TransactionType { public const SHIPPING_INVOICE = 1; + public const PAYMENT = 2; + // public const PAYMENT_ATTEMPT = 0; - // public const PAYMENT = 1; // public const INVOICE = 2; diff --git a/app/Http/Controllers/Billplz/CallbackBillplzController.php b/app/Http/Controllers/Billplz/CallbackBillplzController.php new file mode 100644 index 00000000..9ee281e6 --- /dev/null +++ b/app/Http/Controllers/Billplz/CallbackBillplzController.php @@ -0,0 +1,22 @@ +execute($request); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Billplz/CreateBillplzBillController.php b/app/Http/Controllers/Billplz/CreateBillplzBillController.php new file mode 100644 index 00000000..c10bad79 --- /dev/null +++ b/app/Http/Controllers/Billplz/CreateBillplzBillController.php @@ -0,0 +1,20 @@ +execute($request); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Transactions/CreatePaymentTransactionController.php b/app/Http/Controllers/Transactions/CreatePaymentTransactionController.php new file mode 100644 index 00000000..7636b16e --- /dev/null +++ b/app/Http/Controllers/Transactions/CreatePaymentTransactionController.php @@ -0,0 +1,19 @@ +execute($request); + } +} diff --git a/app/Models/CompanyModule.php b/app/Models/CompanyModule.php index 180d8286..615df813 100644 --- a/app/Models/CompanyModule.php +++ b/app/Models/CompanyModule.php @@ -139,7 +139,7 @@ class CompanyModule extends AbstractModel implements Addressable, Documentable, */ public function banks(): HasMany { - return $this->HasMany(Bank::class, 'company_id'); + return $this->HasMany(BankAccount::class, 'company_id'); } /** diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 9ca9c24f..d2432941 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Classes\General\Interfaces\Documentable; +use App\Classes\General\Interfaces\Transactionable; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Classes\ValueObjects\Constants\TransactionType; use Carbon\Carbon; @@ -12,7 +13,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphTo; -class Transaction extends AbstractModel implements Documentable +class Transaction extends AbstractModel implements Documentable, Transactionable { protected $table = 'transactions'; @@ -29,6 +30,14 @@ class Transaction extends AbstractModel implements Documentable return $this->morphMany(Order::class, 'owner'); } + /** + * @return MorphMany + */ + public function transactions(): MorphMany + { + return $this->MorphMany(Transaction::class, 'owner'); + } + /** * @return MorphMany */ diff --git a/config/billplz.php b/config/billplz.php new file mode 100644 index 00000000..904cea31 --- /dev/null +++ b/config/billplz.php @@ -0,0 +1,13 @@ + env('BILLPLZ_BASE_URL', 'https://www.billplz.com'), + 'api_key' => env('BILLPLZ_API_KEY', '0fa4c710-761b-4a7a-a501-c2c2d02643d5'), + 'x_signature_key' => env('BILLPLZ_X_SIGNATURE_KEY', 'S-pbNVthVRsvnPfZlgLwqqOg'), + 'collection_id' => env('BILLPLZ_COLLECTION_ID', 'hev2wdjy'), + 'wallet_collection_id' => env('BILLPLZ_WALLET_COLLECTION_ID', 'hev2wdjy'), + 'redirect_url' => env('BILLPLZ_REDIRECT_URL', 'localhost'), + 'callback_url' => env('BILLPLZ_CALLBACK_URL', 'localhost'), + 'maybank' => 'MB2U0227', + 'cimb' => 'BCBB0235' +]; \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index c8f886e1..3c541439 100644 --- a/routes/api.php +++ b/routes/api.php @@ -26,6 +26,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function Route::get('/storage/{fileName}/fetch', 'Documents\RenderDocumentController@fileStorageServe')->where(['fileName' => '.*'])->name('storage.document.file'); + Route::post('online_payment/callback', 'Billplz\CallbackBillplzController@callback')->name('online_payment.callback'); + require __DIR__ . '/company.php'; require __DIR__ . '/document.php'; diff --git a/routes/transaction.php b/routes/transaction.php index 7b4f7fd0..15504e97 100644 --- a/routes/transaction.php +++ b/routes/transaction.php @@ -7,6 +7,7 @@ Route::group(['prefix' => 'transactions', 'namespace' => 'Transactions', 'as' => Route::get('/list', 'ListTransactionsController@list')->name('list'); // Route::delete('/suspend/{id}', 'SuspendTransactionController@suspend')->name('suspend'); + route::post('/payment/create', 'CreatePaymentTransactionController@create')->name('payment.create'); route::post('/shipping-invoice/create', 'CreateShippingInvoiceTransactionController@create')->name('supplier.create'); diff --git a/routes/web.php b/routes/web.php index 4c3077fe..bd7d2e01 100644 --- a/routes/web.php +++ b/routes/web.php @@ -260,4 +260,6 @@ Route::get('/customers/active/{active_start}/{active_end}/{inactive_start?}/{ina echo $key + 1 .'. '.$marking.'

'; } -}); \ No newline at end of file +}); + +Route::get('/online_payment/redirect', 'Billplz\CallbackBillplzController@callback')->name('online_payment.redirect'); \ No newline at end of file