Skip to content

Instantly share code, notes, and snippets.

@kura1420
Created June 9, 2025 03:29
Show Gist options
  • Select an option

  • Save kura1420/f0634265f4c111dff55455625fa88763 to your computer and use it in GitHub Desktop.

Select an option

Save kura1420/f0634265f4c111dff55455625fa88763 to your computer and use it in GitHub Desktop.
Laravel Webhook Xendit
<?php
class WebhookController extends Controller
{
/**
* Paymentlink: https://developers.xendit.co/api-reference/#invoice-callback
* Virtual Account: https://developers.xendit.co/api-reference/#virtual-account-callback
* Retail: https://developers.xendit.co/api-reference/#fixed-payment-code-callback
* QRCode: https://developers.xendit.co/api-reference/id/#callback-pembayaran-qr
*/
public function xendit(Request $request): JsonResponse
{
Log::channel('xendit')->error('Xendit Webhook: ' . $request->all());
$signature = $request->header('X-Callback-Token');
$expectedSignature = env('XENDIT_WEBHOOK_SECRET');
if (is_null($signature)) {
return response()->json(['message' => 'Invalid signature'], 403);
}
if (! hash_equals($signature, $expectedSignature)) {
return response()->json(['message' => 'Invalid signature'], 403);
}
$id = $request->id ?: null;
$external_id = $request->external_id ?: null;
if ($id) {
XenditJob::dispatch($id, $request->all(), $external_id);
}
return response()->json('OK');
}
}
<?php
class XenditJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct(public string $id, public array $payload, public ?string $external_id)
{
//
}
/**
* Execute the job.
*/
public function handle(): void
{
//
$row = Invoice::find($this->external_id);
if (isset($row) && ($row->payment_status == PaymentStatusEnum::UNPAID->value)) {
$row->update([
'payment_status' => PaymentStatusEnum::PAID->value,
'payment_method' => PaymentMethodEnum::PAYMENT_GATEWAY->value,
]);
$this->xenditUpdate(id: $this->id, payload: $this->payload);
} else {
Telegram::sendMessage([
'chat_id' => env('TELEGRAM_ID_PERSONAL'),
'text' => "XenditJob, external_id: {$this->external_id} NOT FOUND on Invoice"
]);
}
}
protected function xenditUpdate(string $id, array $payload): void
{
$paymentLink = XenditPaymentlink::where('xendit_id', $id)->first();
if ($paymentLink) {
$paymentLink
->update([
'status' => $payload['status'],
// 'amount' => $payload['amount'],
// 'description' => $payload['description'],
'created' => $payload['created'],
'updated' => $payload['updated'],
'transaction_timestamp' => $payload['paid_at'],
'payload' => json_encode($payload),
]);
}
if (isset($payload['callback_virtual_account_id'])) {
$virtualAccount = XenditVirtualaccount::where('xendit_id', $payload['callback_virtual_account_id'])->first();
if ($virtualAccount) {
$virtualAccount
->update([
'bank_code' => $payload['bank_code'],
// 'name' => $payload['name'],
// 'is_closed' => $payload['is_closed'],
// 'is_single_use' => $payload['is_single_use'],
'status' => 'PAID',
// 'expected_amount' => $payload['amount'],
// 'expiration_date' => $payload['expiration_date'],
'transaction_timestamp' => $payload['transaction_timestamp'],
'payload' => json_encode($payload),
]);
}
}
if (isset($payload['fixed_payment_code_id'])) {
$retail = XenditRetail::where('xendit_id', $payload['fixed_payment_code_id'])->first();
if ($retail) {
$retail
->update([
'payment_code' => $payload['payment_code'],
// 'retail_outlet_name' => $payload['retail_outlet_name'],
// 'prefix' => $payload['prefix'],
// 'type' => $payload['type'],
// 'expected_amount' => $payload['amount'],
// 'is_single_use' => $payload['is_single_use'],
'status' => $payload['status'],
// 'expiration_date' => $payload['expiration_date'],
'transaction_timestamp' => $payload['transaction_timestamp'],
'payload' => json_encode($payload),
]);
}
}
$qrcode = XenditQrcode::where('xendit_id', $payload['qr_code']['id'])->first();
if ($qrcode) {
$qrcode
->update([
'status' => $payload['status'],
'transaction_timestamp' => $payload['created'],
'payload' => json_encode($payload),
]);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment