letbar/loketqris-sdk 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

letbar/loketqris-sdk

最新稳定版本:v1.0

Composer 安装命令:

composer require letbar/loketqris-sdk

包简介

PHP SDK for LoketQRIS API integration for Laravel

README 文档

README

A PHP SDK for integrating with LoketQRIS payment gateway APIs. Built for Laravel applications with support for multi-tenant architectures and event-driven workflows.

Features

  • 🔐 Secure Authentication - HMAC-SHA256 signature generation with automatic timestamp handling
  • 🏢 Multi-tenant Support - Pass credentials per-request or use config-based defaults
  • 📡 Event-driven Architecture - Hook into API lifecycle via Laravel events
  • 🔄 Webhook Handling - Built-in controllers for /b2b/token and /qris/notify endpoints
  • Type-safe DTOs - Request/response objects with helper methods
  • 🧪 Fully Tested - Comprehensive test suite with 72 tests

Requirements

  • PHP 8.2+
  • Laravel 11.x or 12.x

Installation

Via Composer

Install loketqris-sdk:

composer require letbar/loketqris-sdk

Publish Configuration

php artisan vendor:publish --tag=loketqris-sdk-config

Configuration

Environment Variables

LOKETQRIS_BASE_URL=https://api.loketqris.com
LOKETQRIS_KODE_LOKET=LK000001
LOKETQRIS_API_KEY=your-api-key
LOKETQRIS_CLIENT_SECRET=your-client-secret
LOKETQRIS_TOKEN_TTL=300

Config File

// config/loketqris-sdk.php

return [
    // LoketQRIS API base URL
    'base_url' => env('LOKETQRIS_BASE_URL'),

    // Default credentials (optional for multi-tenant apps)
    'credentials' => [
        'kode_loket' => env('LOKETQRIS_KODE_LOKET'),
        'api_key' => env('LOKETQRIS_API_KEY'),
        'client_secret' => env('LOKETQRIS_CLIENT_SECRET'),
    ],

    // Webhook routes configuration
    'routes' => [
        'enabled' => true,           // Set false to handle routes yourself
        'prefix' => '',              // Route prefix
        'token_path' => '/b2b/token',
        'notify_path' => '/qris/notify',
        'middleware' => ['api'],
    ],

    // Access token TTL in seconds
    'token_ttl' => env('LOKETQRIS_TOKEN_TTL', 300),

    // Response codes mapping
    'response_codes' => [
        'success' => ['2004700', '2005100'],
        'pending' => ['2005101'],
    ],
];

Usage

Generate QRIS

use Letbar\LoketQrisSdk\Facades\LoketQris;
use Letbar\LoketQrisSdk\DTOs\GenerateQrisRequest;

// Using config credentials
$request = GenerateQrisRequest::make(
    partnerReferenceNo: 'INV-2026-001',
    amount: 50000,
    validTime: 900  // seconds (optional, default: 900)
);

$response = LoketQris::generate($request);

if ($response->isSuccessful()) {
    $qrContent = $response->getQrContent();
    $referenceNo = $response->getPartnerReferenceNo();
}

// Access raw response
$rawData = $response->toArray();
$customField = $response->get('customField', 'default');

Query QRIS Transaction

use Letbar\LoketQrisSdk\Facades\LoketQris;
use Letbar\LoketQrisSdk\DTOs\QueryQrisRequest;

$request = QueryQrisRequest::make('INV-2026-001');

$response = LoketQris::query($request);

if ($response->isSuccessful()) {
    if ($response->isPaid()) {
        $paidTime = $response->getPaidTime();
        $amount = $response->getAmountValue();
        $currency = $response->getAmountCurrency();

        // Additional info
        $issuerId = $response->getAdditionalInfoValue('issuerID');
        $paymentRef = $response->getAdditionalInfoValue('paymentReferenceNo');
    }

    if ($response->isPending()) {
        // Transaction still pending
    }
}

Multi-tenant Usage

For multi-tenant applications, pass credentials per-request:

use Letbar\LoketQrisSdk\DTOs\CredentialData;
use Letbar\LoketQrisSdk\DTOs\GenerateQrisRequest;
use Letbar\LoketQrisSdk\Facades\LoketQris;

// Get tenant credentials from your database
$tenant = Credential::find($tenantId);

$credential = CredentialData::make(
    kodeLoket: $tenant->kode_loket,
    apiKey: $tenant->api_key,
    clientSecret: $tenant->client_secret
);

// Or from array
$credential = CredentialData::fromArray([
    'kode_loket' => $tenant->kode_loket,
    'api_key' => $tenant->api_key,
    'client_secret' => $tenant->client_secret,
]);

$request = GenerateQrisRequest::make('INV-001', 50000);

// Pass credential as second argument
$response = LoketQris::generate($request, $credential);

Using the Client Directly

use Letbar\LoketQrisSdk\LoketQrisClient;

$client = app(LoketQrisClient::class);

$response = $client->generate($request, $credential);

Events

The SDK dispatches events throughout the API lifecycle, allowing you to hook in for logging, monitoring, or custom business logic.

Available Events

Event Description Properties
QrisGenerating Before generate API call $request, $credential
QrisGenerated After successful generate $request, $response, $credential
QrisQuerying Before query API call $request, $credential
QrisQueried After successful query $request, $response, $credential
TokenRequested Webhook: token requested $request, $kodeLoket, $apiKey
TokenIssued Webhook: token issued $token, $kodeLoket, $apiKey, $expiresIn
NotificationReceived Webhook: payment notification $payload, $request

Listening to Events

// app/Providers/EventServiceProvider.php
use Letbar\LoketQrisSdk\Events\QrisGenerated;
use Letbar\LoketQrisSdk\Events\NotificationReceived;

protected $listen = [
    QrisGenerated::class => [
        LogQrisGenerated::class,
    ],
    NotificationReceived::class => [
        ProcessPaymentNotification::class,
    ],
];

Example Listeners

// app/Listeners/LogQrisGenerated.php
namespace App\Listeners;

use Illuminate\Support\Facades\Log;
use Letbar\LoketQrisSdk\Events\QrisGenerated;

class LogQrisGenerated
{
    public function handle(QrisGenerated $event): void
    {
        Log::info('QRIS generated', [
            'reference' => $event->request->partnerReferenceNo,
            'amount' => $event->request->amount,
            'qr_content' => $event->response->getQrContent(),
            'kode_loket' => $event->credential->kodeLoket,
        ]);
    }
}
// app/Listeners/ProcessPaymentNotification.php
namespace App\Listeners;

use App\Models\Transaction;
use Letbar\LoketQrisSdk\Events\NotificationReceived;

class ProcessPaymentNotification
{
    public function handle(NotificationReceived $event): void
    {
        $payload = $event->payload;

        if (!$payload->isSuccessful()) {
            return;
        }

        $transaction = Transaction::where(
            'reference_no',
            $payload->getOriginalPartnerReferenceNo()
        )->first();

        if ($transaction) {
            $transaction->update([
                'status' => 'paid',
                'paid_at' => $payload->getPaymentDate(),
                'payment_reference' => $payload->getPaymentReferenceNo(),
            ]);
        }
    }
}

Webhook Handling

The SDK automatically registers webhook routes for receiving callbacks from LoketQRIS.

Default Routes

Method Path Description
POST /b2b/token Token exchange endpoint
POST /qris/notify Payment notification endpoint

Customizing Routes

// config/loketqris-sdk.php
'routes' => [
    'enabled' => true,
    'prefix' => 'api/v1',           // Routes: /api/v1/b2b/token
    'token_path' => '/auth/token',   // Custom path
    'notify_path' => '/webhooks/qris',
    'middleware' => ['api', 'throttle:60,1'],
],

Disabling SDK Routes

If you need full control over routing:

// config/loketqris-sdk.php
'routes' => [
    'enabled' => false,
],

Then define your own routes using the SDK controllers:

// routes/api.php
use Letbar\LoketQrisSdk\Http\Controllers\TokenController;
use Letbar\LoketQrisSdk\Http\Controllers\NotificationController;
use Letbar\LoketQrisSdk\Http\Middleware\VerifyLoketQrisSignature;
use Letbar\LoketQrisSdk\Http\Middleware\VerifyBearerToken;

Route::post('/custom/token', [TokenController::class, 'store'])
    ->middleware(VerifyLoketQrisSignature::class);

Route::post('/custom/notify', [NotificationController::class, 'store'])
    ->middleware(VerifyBearerToken::class);

Multi-tenant Webhook Support

For multi-tenant apps, implement the CredentialResolver contract to resolve credentials for incoming webhooks:

// app/Services/TenantCredentialResolver.php
namespace App\Services;

use App\Models\Credential;
use Letbar\LoketQrisSdk\Contracts\CredentialResolver;

class TenantCredentialResolver implements CredentialResolver
{
    public function resolveClientSecret(string $kodeLoket, string $apiKey): ?string
    {
        $credential = Credential::query()
            ->where('kode_loket', $kodeLoket)
            ->where('api_key', $apiKey)
            ->first();

        return $credential?->client_secret;
    }
}

Register the resolver in your service provider:

// app/Providers/AppServiceProvider.php
use App\Services\TenantCredentialResolver;
use Letbar\LoketQrisSdk\Contracts\CredentialResolver;

public function register(): void
{
    $this->app->bind(CredentialResolver::class, TenantCredentialResolver::class);
}

Exception Handling

The SDK throws specific exceptions for different error scenarios:

use Letbar\LoketQrisSdk\Exceptions\ApiRequestException;
use Letbar\LoketQrisSdk\Exceptions\ConfigurationException;
use Letbar\LoketQrisSdk\Exceptions\InvalidCredentialException;
use Letbar\LoketQrisSdk\Exceptions\InvalidSignatureException;

try {
    $response = LoketQris::generate($request);
} catch (ConfigurationException $e) {
    // Missing base_url or credentials
} catch (InvalidCredentialException $e) {
    // Empty or invalid credential fields
} catch (ApiRequestException $e) {
    // API returned an error
    $httpStatus = $e->getHttpStatusCode();
    $responseCode = $e->getResponseCode();
    $responseBody = $e->getResponseBody();
}

Signature Generation

The SDK handles signature generation automatically, but you can also use it directly:

use Carbon\Carbon;
use Letbar\LoketQrisSdk\SignatureGenerator;
use Letbar\LoketQrisSdk\DTOs\CredentialData;

// Generate signature
$result = SignatureGenerator::generate(
    kodeLoket: 'LK000001',
    clientSecret: 'your-secret',
    timestamp: Carbon::now()  // optional
);

$timestamp = $result['timestamp'];   // ISO 8601 format
$signature = $result['signature'];   // Base64 encoded

// Or from credential
$credential = CredentialData::make('LK000001', 'api-key', 'secret');
$result = SignatureGenerator::fromCredential($credential);

// Verify signature
$isValid = SignatureGenerator::verify(
    kodeLoket: 'LK000001',
    clientSecret: 'your-secret',
    timestamp: '2026-01-28T12:00:00+07:00',
    signature: 'base64-signature'
);

DTOs Reference

CredentialData

CredentialData::make(string $kodeLoket, string $apiKey, string $clientSecret)
CredentialData::fromArray(array $data)
CredentialData::fromConfig()

$credential->kodeLoket;
$credential->apiKey;
$credential->clientSecret;
$credential->toArray();
$credential->validate();  // throws InvalidCredentialException

GenerateQrisRequest

GenerateQrisRequest::make(
    string $partnerReferenceNo,
    string|float|int $amount,
    string|int $validTime = '900'
)

$request->partnerReferenceNo;
$request->amount;          // Formatted as "10000.00"
$request->validTime;
$request->toArray();

GenerateQrisResponse

$response->isSuccessful(): bool
$response->getResponseCode(): ?string
$response->getResponseMessage(): ?string
$response->getPartnerReferenceNo(): ?string
$response->getQrContent(): ?string
$response->toArray(): array
$response->get(string $key, mixed $default = null): mixed

QueryQrisRequest

QueryQrisRequest::make(string $originalPartnerReferenceNo)

$request->originalPartnerReferenceNo;
$request->toArray();

QueryQrisResponse

$response->isSuccessful(): bool
$response->isPending(): bool
$response->isPaid(): bool
$response->getResponseCode(): ?string
$response->getResponseMessage(): ?string
$response->getOriginalPartnerReferenceNo(): ?string
$response->getServiceCode(): ?string
$response->getTransactionStatusDesc(): ?string
$response->getLatestTransactionStatus(): ?string
$response->getPaidTime(): ?string
$response->getAmountValue(): ?string
$response->getAmountCurrency(): ?string
$response->getAdditionalInfo(): array
$response->getAdditionalInfoValue(string $key, mixed $default = null): mixed
$response->toArray(): array
$response->get(string $key, mixed $default = null): mixed

NotificationPayload

NotificationPayload::fromArray(array $data)

$payload->isSuccessful(): bool
$payload->getOriginalReferenceNo(): ?string
$payload->getOriginalPartnerReferenceNo(): ?string
$payload->getLatestTransactionStatus(): ?string
$payload->getTransactionStatusDesc(): ?string
$payload->getAmountValue(): ?string
$payload->getAmountCurrency(): ?string
$payload->getExternalStoreId(): ?string
$payload->getAdditionalInfo(): array
$payload->getCallbackUrl(): ?string
$payload->getIssuerId(): ?string
$payload->getMerchantId(): ?string
$payload->getPaymentDate(): ?string
$payload->getRetrievalReferenceNo(): ?string
$payload->getPaymentReferenceNo(): ?string
$payload->toArray(): array
$payload->get(string $key, mixed $default = null): mixed

License

MIT License. See LICENSE for details.

统计信息

  • 总下载量: 40
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 3
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-01-30

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固