Merge branch 'online-payment-billplz-integration' into development

This commit is contained in:
ahmedsophyudden
2021-11-08 09:43:09 +08:00
17 changed files with 517 additions and 4 deletions
+8 -1
View File
@@ -50,4 +50,11 @@ FILESYSTEM_DRIVER="documents"
JWT_SECRET=
JWT_TTL=1440
IS_PRODUCTION=false
IS_PRODUCTION=false
BILLPLZ_BASE_URL="https://www.billplz-sandbox.com"
BILLPLZ_API_KEY="0fa4c710-761b-4a7a-a501-c2c2d02643d5"
BILLPLZ_X_SIGNATURE_KEY="S-pbNVthVRsvnPfZlgLwqqOg"
BILLPLZ_COLLECTION_ID="hev2wdjy"
BILLPLZ_REDIRECT_URL="http://localhost:9003/bookings/billplz"
BILLPLZ_CALLBACK_URL="localhost:9003/api/v1/billplz/callback"
@@ -0,0 +1,20 @@
<?php
namespace App\Classes\General\Eloquent\Filters;
use Illuminate\Database\Eloquent\Builder;
class PaymentReference implements Filter
{
/**
* @param Builder $builder
* @param $value
* @return Builder|mixed
*/
public static function apply(Builder $builder, $value)
{
return $builder->where('payment_reference', $value);
}
}
@@ -0,0 +1,80 @@
<?php
namespace App\Classes\Modules\Billplzs\ControllersLogic;
use ErrorException;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Classes\Exceptions\MalformedRequestException;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\Modules\Billplzs\Services\GetBillplzBill;
use App\Classes\Modules\Billplzs\DataTransferObjects\BillplzXSignatureObject;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Transactions\Services\FetchesTransaction;
use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus;
class CallbackBillplzLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => 'Callback Billplz',
'message' => 'You have successfully receive Billplz callback'
];
}
/** @var GetBillplzBill */
private $getBillplzBill;
/** @var FetchesTransaction */
private $fetchesTransaction;
/** @var UpdatesTransactionStatus */
private $updatesTransactionStatus;
/**
* CreateBookingLogic constructor.
* @param CreateGetBillplzBillsBillplzBill $getBillplzBill
* @param FetchesTransaction $fetchesTransaction
* @param UpdatesTransactionStatus $updatesTransactionStatus
*/
public function __construct(GetBillplzBill $getBillplzBill, FetchesTransaction $fetchesTransaction, UpdatesTransactionStatus $updatesTransactionStatus)
{
$this->getBillplzBill = $getBillplzBill;
$this->fetchesTransaction = $fetchesTransaction;
$this->updatesTransactionStatus = $updatesTransactionStatus;
}
/**
* @param Request $request
* @return JsonResponse
* @throws \App\Classes\Exceptions\AccessForbiddenException
* @throws \App\Classes\Exceptions\MalformedRequestException
* @throws \App\Classes\Exceptions\RequestValidationException
*/
public function logic(Request $request) : JsonResponse
{
$billplzXSignatureObject = new BillplzXSignatureObject($request);
if(!$billplzXSignatureObject->isValidSignature()) throw new MalformedRequestException('Unable to get correct response from billplz server.');
$billPlz = $this->getBillplzBill->execute($billplzXSignatureObject->getBillPlzId());
if(!$billPlz) throw new MalformedRequestException('Unable to get correct response from billplz server.');
$transaction = $this->fetchesTransaction->execute(['payment_reference' => $billplzXSignatureObject->getBillPlzId()]);
if($billPlz->state == 'paid') $this->updatesTransactionStatus->execute($transaction, ApprovalStatus::APPROVED);
return $this->response(['data' => $billPlz]);
}
}
@@ -0,0 +1,56 @@
<?php
namespace App\Classes\Modules\Billplzs\ControllersLogic;
use App\Classes\General\Abstracts\AbstractControllerLogic;
use App\Classes\Modules\Billplzs\Services\CreatesBillplzBill;
use ErrorException;
use App\Classes\Exceptions\MalformedRequestException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CreateBillplzBillLogic extends AbstractControllerLogic
{
/**
* @return array
*/
protected function notification():array {
return [
'title' => '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 \App\Classes\Exceptions\AccessForbiddenException
* @throws \App\Classes\Exceptions\MalformedRequestException
* @throws \App\Classes\Exceptions\RequestValidationException
*/
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]);
}
}
@@ -0,0 +1,104 @@
<?php
namespace App\Classes\Modules\Billplzs\DataTransferObjects;
use Illuminate\Http\Request;
use App\Classes\General\Interfaces\DataTransferObject;
class BillplzXSignatureObject implements DataTransferObject
{
/** @var string */
private $billPlzId;
/** @var array */
private $billPlzConstructArray;
/** @var string */
private $billPlzConstructString;
/** @var string */
private $billPlzComputedXSignature;
/** @var Request */
private $request;
/** @var string */
private $requestXSignature;
public function __construct(Request $request)
{
$this->request = $request;
$this->billPlzId = $request->id ? $request->id : $request->{'billplz[id]'};
$this->requestXSignature = $request->x_signature ? $request->x_signature : $request->{'billplz[x_signature]'};
$this->_constructBillplzArray()->_natSortBillplzArray()->_constructBillplzString()->_computeBillplzXSignature();
}
private function _constructBillplzArray(){
foreach($this->request->all() as $key => $value){
if($key != 'x_signature' && $key != 'billplz[x_signature]'){
$key = str_replace(']', '', str_replace('[', '', $key));
$this->billPlzConstructArray[] = $key.$value;
}
}
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;
}
/**
* @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;
}
}
@@ -0,0 +1,42 @@
<?php
namespace App\Classes\Modules\Billplzs\Services;
use Illuminate\Support\Facades\Http;
class CreatesBillplzBill
{
/**
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(string $name, string $email, string $description, float $amount, string $billNumber, ?string $bankCode = null) {
try{
$response = Http::withBasicAuth(config('billplz.api_key').':', '')->post(config('billplz.base_url').'/api/v3/bills', [
'collection_id' => config('billplz.collection_id'),
'name' => $name,
'email' => $email,
'description' => $description,
'amount' => $amount,
'redirect_url' => config('billplz.redirect_url'),
'callback_url' => config('billplz.callback_url'),
'reference_1_label' => 'Bank Code',
'reference_1' => $bankCode ? $bankCode : config('billplz.maybank'),
'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');
}
}
}
@@ -0,0 +1,28 @@
<?php
namespace App\Classes\Modules\Billplzs\Services;
use Illuminate\Support\Facades\Http;
class GetBillplzBill
{
/**
* @return \Illuminate\Database\Eloquent\Model
* @throws \App\Classes\Exceptions\MalformedRequestException
*/
public function execute(string $billPlzId) {
try{
$response = Http::withBasicAuth(config('billplz.api_key').':', '')->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');
}
}
}
@@ -11,7 +11,9 @@ use App\Classes\Modules\Companies\Services\FetchesCompanyPaymentAttemptLimit;
use App\Classes\Modules\Currencies\DataTransferObjects\CurrencyConversionObject;
use App\Classes\Modules\Transactions\DataTransferObjects\TransactionObject;
use App\Classes\Modules\Transactions\Services\CreatesTransaction;
use App\Classes\Modules\Transactions\Services\UpdatesTransaction;
use App\Classes\Modules\Transactions\Services\GeneratesTransactionBillNumber;
use App\Classes\Modules\Billplzs\Services\CreatesBillplzBill;
use App\Classes\ValueObjects\Constants\ApprovalStatus;
use App\Classes\ValueObjects\Constants\PaymentMethodType;
use App\Classes\ValueObjects\Constants\TransactionType;
@@ -46,9 +48,15 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic
/** @var CreatesTransaction */
private $createsTransaction;
/** @var UpdatesTransaction */
private $updatesTransaction;
/** @var CalculatesBookingOutstanding */
private $calculatesBookingOutstanding;
/** @var CreatesBillplzBill */
private $createsBillplzBill;
/**
* CreateBookingPaymentLogic constructor.
* @param FetchesBookingQuotation $fetchBookingQuotation
@@ -56,14 +64,17 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic
* @param GeneratesTransactionBillNumber $generatesTransactionBillNumber
* @param CreatesTransaction $createsTransaction
* @param CalculatesBookingOutstanding $calculatesBookingOutstanding
* @param CreatesBillplzBill $createsBillplzBill
*/
public function __construct(FetchesBookingQuotation $fetchBookingQuotation, FetchesCompanyPaymentAttemptLimit $fetchesCompanyPaymentAttemptLimit, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatesTransaction $createsTransaction, CalculatesBookingOutstanding $calculatesBookingOutstanding)
public function __construct(FetchesBookingQuotation $fetchBookingQuotation, FetchesCompanyPaymentAttemptLimit $fetchesCompanyPaymentAttemptLimit, GeneratesTransactionBillNumber $generatesTransactionBillNumber, CreatesTransaction $createsTransaction, UpdatesTransaction $updatesTransaction, CalculatesBookingOutstanding $calculatesBookingOutstanding, CreatesBillplzBill $createsBillplzBill)
{
$this->fetchBookingQuotation = $fetchBookingQuotation;
$this->fetchesCompanyPaymentAttemptLimit = $fetchesCompanyPaymentAttemptLimit;
$this->generatesTransactionBillNumber = $generatesTransactionBillNumber;
$this->createsTransaction = $createsTransaction;
$this->updatesTransaction = $updatesTransaction;
$this->calculatesBookingOutstanding = $calculatesBookingOutstanding;
$this->createsBillplzBill = $createsBillplzBill;
}
/**
@@ -88,11 +99,15 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic
$billNumber = $this->generatesTransactionBillNumber->execute('PYMT-');
if(PaymentMethodType::PAYMENT_METHODS[$request->input('payment_method')] == PaymentMethodType::PAYMENT_GATEWAY){
$billPlzBill = $this->createsBillplzBill->execute($request->user()->name, $request->user()->email, 'This payment is made on behave '.$booking->company->name, $configurations->getTotal(), $billNumber, $request->input('bank_code'));
}
$object = new TransactionObject($billNumber, TransactionType::PAYMENT, 1, $booking->company->id,
$configurations->getConfigurations()->getBankId(), $configurations->getConversionObject()->getPaymentMethod(),
$configurations->getTotal(), $configurations->getForeignTotal(), 1,
$configurations->getConversionObject()->getCurrencyId(), $configurations->getConfigurations()->getRate(),
$configurations->getTax(), $configurations->getServiceCharge(), Carbon::now()->addMinutes($paymentAttemptLimit), ApprovalStatus::PENDING_SUBMISSION);
$configurations->getTax(), $configurations->getServiceCharge(), Carbon::now()->addMinutes($paymentAttemptLimit), ApprovalStatus::PENDING_SUBMISSION, [], isset($billPlzBill) ? $billPlzBill->id : NULL);
$transaction = $this->createsTransaction->execute($booking, $object);
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Billplzs;
use App\Classes\Modules\Billplzs\ControllersLogic\CallbackBillplzLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CallbackBillplzController
{
/**
* @param Request $request
* @param BillplzBillLogic $logic
* @return JsonResponse
*/
public function callback(Request $request, CallbackBillplzLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Billplzs;
use App\Classes\Modules\Billplzs\ControllersLogic\CreateBillplzBillLogic;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CreateBillplzBillController
{
/**
* @param Request $request
* @param BillplzBillLogic $logic
* @return JsonResponse
*/
public function create(Request $request, CreateBillplzBillLogic $logic): JsonResponse {
return $logic->execute($request);
}
}
+1
View File
@@ -23,6 +23,7 @@ class BookingResource extends JsonResource
*/
public function toArray($request)
{
// dd($this->service);
return [
'id' => $this->id,
'company' => new CompanyResource($this->company),
+12
View File
@@ -0,0 +1,12 @@
<?php
return [
'base_url' => env('BILLPLZ_BASE_URL', 'https://www.billplz-sandbox.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'),
'redirect_url' => env('BILLPLZ_REDIRECT_URL', 'localhost'),
'callback_url' => env('BILLPLZ_CALLBACK_URL', 'localhost'),
'maybank' => 'MB2U0227',
'cimb' => 'BCBB0235'
];
@@ -169,6 +169,15 @@
</div>
</div>
</div>
<div class="row no-margin" :class="[{'bg-complete-light': paymentMethod.name === 'Online Transfer'}, {'text-white': paymentMethod.name === 'Online Transfer'}, {'hover-complete': paymentMethod.name !== 'Online Transfer'}]" @click="updatePaymentType({name: 'Online Transfer', id: 'ot'})">
<div class="col b-b b-grey p-t-10 p-b-10 pointer hover-t-10 p-b-10">
<div class="row align-items-center justify-content-center">
<div class="col">
<div class="font-heading fs-10">Online Transfer</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -190,6 +199,38 @@
</div>
</div>
</div>
<div class="row" v-if="paymentMethod.name == 'Online Transfer'">
<div class="col">
<div class="row m-t-10">
<div class="col">
<div class="row">
<div class="col-8">
<div class="btn btn-xs btn-block text-left b-rad-none p-t-10 p-b-10 p-l-15 p-r-15" :class="[{'b-complete': onlinePayment.bankName === 'Maybank'}]" @click="selectOnlinePaymentBank({bankName: 'Maybank', id: 'maybank'})">
<img src="https://shopee.com.my/static/images/bank_logo/img_bankmy_maybank.png" alt="">
Maybank2u
</div>
</div>
</div>
</div>
</div>
<div class="row m-t-10">
<div class="col">
<div class="row">
<div class="col-8">
<div class="btn btn-xs btn-block text-left b-rad-none p-t-10 p-b-10 p-l-15 p-r-15" :class="[{'b-complete': onlinePayment.bankName === 'Cimb'}]" @click="selectOnlinePaymentBank({bankName: 'Cimb', id: 'cimb'})">
<img src="https://shopee.com.my/static/images/bank_logo/img_bankmy_cimb.png" alt="">
Cimb
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
@@ -197,7 +238,7 @@
<button class="btn btn-xs all-caps b-rad-none btn-default bg-master-lighter btn-block" @click="expandPayment = false">Cancel</button>
</div>
<div class="col p-l-0">
<button class="btn btn-xs all-caps b-rad-none btn-success btn-block" @click="submitForm()">Create Booking</button>
<button class="btn btn-xs all-caps b-rad-none btn-success btn-block" v-if="!(paymentMethod.name === 'Online Transfer' && onlinePayment.status === false)" @click="submitForm()">Create Booking</button>
</div>
</div>
</div>
@@ -311,6 +352,11 @@
id: 'cash',
status: false
},
onlinePayment: {
bankName: '',
id: '',
status: false
},
amount: (Math.round((this.data.outstanding_amount + Number.EPSILON) * 100) / 100).toFixed(2),
calculation: null
}
@@ -331,6 +377,14 @@
status: false,
}
},
selectOnlinePaymentBank(bankName){
this.onlinePayment = {
bankName: bankName.bankName,
id: bankName.id,
status: true,
}
},
submitForm(){
this.parameters = {
@@ -0,0 +1,33 @@
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// var url_string = window.location.href
// var url = new URL(url_string);
// var billplz = url.searchParams.get("billplz[id]");
// console.log(billplz);
function getQueryParams(qs) {
qs = qs.split('+').join(' ');
var params = {},
tokens,
re = /[?&]?([^=]+)=([^&]*)/g;
while (tokens = re.exec(qs)) {
params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
}
return params;
}
var query = getQueryParams(document.location.search);
console.log(query);
axios.post('/api/v1/billplz/bill/callback', query)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
</script>
+8
View File
@@ -23,6 +23,12 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
Route::get('countries/list', 'Services\CountriesListController@index')->name('list.countries');
});
Route::group(['prefix' => 'billplz', 'as' => 'billplz.', 'namespace' => 'Billplzs'], function () {
Route::group(['prefix' => 'bill', 'as' => 'bill.'], function () {
Route::post('/callback', 'CallbackBillplzController@callback')->name('callback');
});
});
Route::group(['middleware' => 'valid.token'], function () {
@@ -47,6 +53,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function
require __DIR__ . '/announcement.php';
require __DIR__ . '/billplz.php';
// require __DIR__ . '/wallet.php';
// require __DIR__ . '/rate.php';
// require __DIR__ . '/receipt.php';
+9
View File
@@ -0,0 +1,9 @@
<?php
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'billplz', 'as' => 'billplz.', 'namespace' => 'Billplzs'], function () {
Route::group(['prefix' => 'bill', 'as' => 'bill.'], function () {
Route::post('/create', 'CreateBillplzBillController@create')->name('create');
});
});
+4
View File
@@ -100,6 +100,10 @@ Route::get('/test', function(){
});
Route::get('/bookings/billplz', function () {
return view('pages.billplz_redirect');
})->name('bookings.billplz');
Route::get('/export/customers/f614e339d7058904a831aad742e24d55', 'Exports\ExportCustomersToExcelController@export');
Route::get('/export/transactions/f614e339d7058904a831aad742e24d55', 'Exports\ExportCustomersToExcelController@transactions');