-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implement midtrans online payment
- Loading branch information
1 parent
0fdd918
commit 481c245
Showing
48 changed files
with
2,027 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
|
||
namespace Illuminate\Contracts\View; | ||
|
||
use Illuminate\Contracts\Support\Renderable; | ||
|
||
interface View extends Renderable | ||
{ | ||
/** @return static */ | ||
public function extends(string $layout); | ||
public function layoutData(); | ||
public function layout(string $layout); | ||
//any other method that throws false error here :) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
<?php | ||
|
||
namespace App\Http\Controllers; | ||
|
||
use App\Models\Bill; | ||
use App\Models\Payment; | ||
use App\Models\PaymentLog; | ||
use Illuminate\Http\Request; | ||
use Illuminate\Support\Facades\DB; | ||
use Sawirricardo\Midtrans\Dto\TransactionStatus; | ||
use Sawirricardo\Midtrans\Laravel\Notification; | ||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; | ||
|
||
class MidtransCallbackController extends Controller | ||
{ | ||
public function receive(Request $request) | ||
{ | ||
$notification = Notification::make($request->all()); | ||
$notification | ||
->whenSettlement(function (TransactionStatus $notification) { | ||
$this->updatePaymentStatus($notification, 'success'); | ||
}) | ||
->whenPending(function (TransactionStatus $notification) { | ||
// create a new payment and payment log | ||
$billId = explode('.', $notification->order_id)[1]; | ||
$bill = Bill::query()->findOrFail($billId); | ||
$payment = Payment::query()->where('bill_id', $bill->id) | ||
->first(); | ||
|
||
if (!$payment) { | ||
$payment = Payment::create([ | ||
'bill_id' => $bill->id, | ||
'amount' => $bill->total_amount, | ||
'payment_date' => \now(), | ||
'status' => 'pending', | ||
'method' => 'midtrans', | ||
]); | ||
} | ||
|
||
/** | ||
* @var ?PaymentLog $log | ||
*/ | ||
$log = PaymentLog::query()->where('payment_id', $payment->id) | ||
->where('transaction_id', $notification->transaction_id) | ||
->first(); | ||
|
||
if ($log) { | ||
$log->touch(); | ||
return; | ||
} | ||
|
||
PaymentLog::create([ | ||
'payment_id' => $payment->id, | ||
'transaction_id' => $notification->transaction_id, | ||
'status_code' => $notification->status_code, | ||
'log_message' => \json_encode($notification->toArray()), | ||
]); | ||
}) | ||
->whenDenied(function (TransactionStatus $notification) { | ||
$this->updatePaymentStatus($notification, 'failed'); | ||
}) | ||
->whenExpired(function (TransactionStatus $notification) { | ||
$this->updatePaymentStatus($notification, 'failed'); | ||
}) | ||
->whenCancelled(function (TransactionStatus $notification) { | ||
$this->updatePaymentStatus($notification, 'failed'); | ||
}) | ||
->listen(); | ||
|
||
|
||
return response()->json(['status' => 'success', 'message' => 'Callback received']); | ||
} | ||
|
||
private function updatePaymentStatus(TransactionStatus $notification, string $status) | ||
{ | ||
$billId = explode('.', $notification->order_id)[1]; | ||
$bill = Bill::query()->findOrFail($billId); | ||
$payment = Payment::query()->where('bill_id', $bill->id) | ||
->firstOrFail(); | ||
|
||
$serverKey = \config('midtrans.is_production') ? | ||
\config('midtrans.server_key') : \config('midtrans.sandbox_server_key'); | ||
|
||
if (!$this->checkSignature($notification->signature_key, $notification->order_id, $notification->status_code, $notification->gross_amount, $serverKey)) { | ||
throw new UnauthorizedHttpException('Unauthorized'); | ||
} | ||
|
||
DB::transaction(function () use ($payment, $bill, $status, $notification) { | ||
$payment->fill([ | ||
'status' => $status, | ||
'payment_date' => now(), | ||
])->save(); | ||
|
||
$billStatus = $status === 'success' ? 'paid' : 'unpaid'; | ||
$bill->fill(['status' => $billStatus])->save(); | ||
|
||
$log = PaymentLog::query()->updateOrCreate([ | ||
'payment_id' => $payment->id, | ||
'transaction_id' => $notification->transaction_id, | ||
], [ | ||
'status_code' => $notification->status_code, | ||
'log_message' => \json_encode($notification->toArray()), | ||
]); | ||
|
||
\info('Payment log created', $log->toArray()); | ||
}); | ||
} | ||
|
||
private function checkSignature(string $signature, string $orderId, string $statusCode, string $grossAmount, string $serverKey): bool | ||
{ | ||
$expectedSignature = hash('sha512', $orderId . $statusCode . $grossAmount . $serverKey); | ||
if ($signature !== $expectedSignature) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
namespace App\Livewire\Forms; | ||
|
||
use Livewire\Attributes\Validate; | ||
use Livewire\Form; | ||
|
||
class BillCheckForm extends Form | ||
{ | ||
public $customer_id; | ||
|
||
public array $rules = [ | ||
'customer_id' => 'required|exists:customers,id', | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
namespace App\Livewire\Forms\Transaction; | ||
|
||
use App\Models\Customer; | ||
use Livewire\Attributes\Validate; | ||
use Livewire\Form; | ||
|
||
class OnlinePaymentForm extends Form | ||
{ | ||
public $id; | ||
public $customer_name; | ||
public $phone_number; | ||
public $address; | ||
public $plan_name; | ||
public $plan_price; | ||
public $billStatus = 'paid'; | ||
|
||
public function setCustomer(Customer $customer) | ||
{ | ||
$this->id = $customer->id; | ||
$this->customer_name = $customer->customer_name; | ||
$this->phone_number = $customer->phone_number; | ||
$this->address = $customer->address; | ||
$this->plan_name = $customer->plan->name; | ||
$this->plan_price = $customer->plan->price; | ||
} | ||
|
||
public function setBill($bill) | ||
{ | ||
$this->billStatus = isset($bill) ? 'unpaid' : 'paid'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?php | ||
|
||
namespace App\Livewire; | ||
|
||
use App\Providers\RouteServiceProvider; | ||
use Livewire\Component; | ||
|
||
use Illuminate\Auth\Events\Lockout; | ||
use Illuminate\Support\Facades\Auth; | ||
use Illuminate\Support\Facades\RateLimiter; | ||
use Illuminate\Support\Str; | ||
use Illuminate\Validation\ValidationException; | ||
|
||
class LoginComponent extends Component | ||
{ | ||
public string $username = ''; | ||
public string $password = ''; | ||
public bool $remember = false; | ||
|
||
public function render() | ||
{ | ||
return view('livewire.login-component') | ||
->layout('layouts.blankLayout'); | ||
} | ||
|
||
/** | ||
* Get the validation rules that apply to the request. | ||
* | ||
* @return array<string, \Illuminate\Contracts\Validation\Rule|array|string> | ||
*/ | ||
public function rules(): array | ||
{ | ||
return [ | ||
'username' => ['required', 'string'], | ||
'password' => ['required', 'string'], | ||
'remember' => ['nullable', 'boolean'], | ||
]; | ||
} | ||
|
||
public function store() | ||
{ | ||
$this->authenticate(); | ||
\request()->session()->regenerate(); | ||
|
||
return $this->redirectIntended(RouteServiceProvider::HOME, navigate: true); | ||
} | ||
|
||
/** | ||
* Ensure the login request is not rate limited. | ||
* | ||
* @throws \Illuminate\Validation\ValidationException | ||
*/ | ||
public function ensureIsNotRateLimited(): void | ||
{ | ||
if (!RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { | ||
return; | ||
} | ||
|
||
event(new Lockout(request())); | ||
|
||
$seconds = RateLimiter::availableIn($this->throttleKey()); | ||
|
||
throw ValidationException::withMessages([ | ||
'username' => trans('auth.throttle', [ | ||
'seconds' => $seconds, | ||
'minutes' => ceil($seconds / 60), | ||
]), | ||
]); | ||
} | ||
|
||
/** | ||
* Get the rate limiting throttle key for the request. | ||
*/ | ||
public function throttleKey(): string | ||
{ | ||
return Str::transliterate(Str::lower($this->username) . '|' . request()->ip()); | ||
} | ||
|
||
/** | ||
* Attempt to authenticate the request's credentials. | ||
* | ||
* @throws \Illuminate\Validation\ValidationException | ||
*/ | ||
public function authenticate(): void | ||
{ | ||
$this->ensureIsNotRateLimited(); | ||
|
||
if (!Auth::attempt($this->only('username', 'password'), $this->remember)) { | ||
RateLimiter::hit($this->throttleKey()); | ||
|
||
throw ValidationException::withMessages([ | ||
'username' => trans('auth.failed'), | ||
]); | ||
} | ||
|
||
RateLimiter::clear($this->throttleKey()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
|
||
namespace App\Livewire; | ||
|
||
use Illuminate\Support\Facades\Auth; | ||
use Livewire\Component; | ||
|
||
class LogoutComponent extends Component | ||
{ | ||
public function render() | ||
{ | ||
return view('livewire.logout-component'); | ||
} | ||
|
||
public function logout() | ||
{ | ||
Auth::guard('web')->logout(); | ||
|
||
request()->session()->invalidate(); | ||
|
||
request()->session()->regenerateToken(); | ||
|
||
return $this->redirectRoute('login'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?php | ||
|
||
namespace App\Livewire\Transaction; | ||
|
||
use App\Livewire\Forms\BillCheckForm; | ||
use App\Models\Customer; | ||
use Livewire\Component; | ||
|
||
class BillCheck extends Component | ||
{ | ||
public BillCheckForm $form; | ||
|
||
public function render() | ||
{ | ||
return view('livewire.transaction.bill-check') | ||
->layout('layouts.blankLayout'); | ||
} | ||
|
||
public function store() | ||
{ | ||
$this->validate(); | ||
return $this->redirectIntended("payment/" . $this->form->customer_id, navigate: true); | ||
} | ||
} |
Oops, something went wrong.