@@ -32,6 +32,12 @@ class KbzMiniAppGateway implements PaymentGatewayInterface
|
||||
|
||||
private readonly string $baseUrl;
|
||||
|
||||
private readonly string $createOrderUrl;
|
||||
|
||||
private readonly string $queryOrderUrl;
|
||||
|
||||
private readonly string $refundOrderUrl;
|
||||
|
||||
private readonly ?string $notifyUrl;
|
||||
|
||||
private readonly ?string $certPath;
|
||||
@@ -53,6 +59,11 @@ class KbzMiniAppGateway implements PaymentGatewayInterface
|
||||
$this->merchantCode = (string) ($config['merchant_code'] ?? '');
|
||||
$this->merchantKey = (string) ($config['merchant_key'] ?? '');
|
||||
$this->baseUrl = (string) ($config['base_url'] ?? '');
|
||||
// Falls back to base_url for gateways/environments that haven't
|
||||
// configured per-operation endpoints yet.
|
||||
$this->createOrderUrl = (string) ($config['create_order_url'] ?? $this->baseUrl);
|
||||
$this->queryOrderUrl = (string) ($config['query_order_url'] ?? $this->baseUrl);
|
||||
$this->refundOrderUrl = (string) ($config['refund_order_url'] ?? $this->baseUrl);
|
||||
$this->notifyUrl = $config['notify_url'] ?? null;
|
||||
$this->certPath = $config['cert_path'] ?? null;
|
||||
$this->certKeyPath = $config['cert_key_path'] ?? null;
|
||||
@@ -63,10 +74,18 @@ class KbzMiniAppGateway implements PaymentGatewayInterface
|
||||
public function initiate(PaymentRequestData $data): PaymentResultData
|
||||
{
|
||||
$params = $this->buildPrecreateParams($data);
|
||||
|
||||
logger($params);
|
||||
try {
|
||||
$response = Http::asJson()->post($this->baseUrl, ['Request' => $params]);
|
||||
$response = Http::post($this->createOrderUrl, ['Request' => $params]);
|
||||
|
||||
logger($response);
|
||||
} catch (ConnectionException $exception) {
|
||||
\Log::error('KBZ Mini App precreate connection error: '.$exception->getMessage(), [
|
||||
'merchant_order_id' => $data->merchantOrderId,
|
||||
'amount' => $data->amount,
|
||||
'currency' => $data->currency,
|
||||
]);
|
||||
|
||||
return new PaymentResultData(
|
||||
status: PaymentStatus::Failed,
|
||||
gatewayTransactionId: null,
|
||||
@@ -79,6 +98,14 @@ class KbzMiniAppGateway implements PaymentGatewayInterface
|
||||
$body = $response->json('Response', []);
|
||||
|
||||
if (! $response->successful() || ($body['result'] ?? null) !== 'SUCCESS') {
|
||||
\Log::error('KBZ Mini App precreate failed: '.($body['msg'] ?? 'Unknown error'), [
|
||||
'merchant_order_id' => $data->merchantOrderId,
|
||||
'amount' => $data->amount,
|
||||
'currency' => $data->currency,
|
||||
'http_status' => $response->status(),
|
||||
'raw_body' => $response->body(),
|
||||
]);
|
||||
|
||||
return new PaymentResultData(
|
||||
status: PaymentStatus::Failed,
|
||||
gatewayTransactionId: $body['prepay_id'] ?? null,
|
||||
@@ -102,8 +129,12 @@ class KbzMiniAppGateway implements PaymentGatewayInterface
|
||||
$params = $this->buildQueryOrderParams($gatewayTransactionId);
|
||||
|
||||
try {
|
||||
$response = Http::asJson()->post($this->baseUrl, ['Request' => $params]);
|
||||
$response = Http::asJson()->post($this->queryOrderUrl, ['Request' => $params]);
|
||||
} catch (ConnectionException $exception) {
|
||||
\Log::error('KBZ Mini App verify connection error: '.$exception->getMessage(), [
|
||||
'gateway_transaction_id' => $gatewayTransactionId,
|
||||
]);
|
||||
|
||||
return new PaymentResultData(
|
||||
status: PaymentStatus::Failed,
|
||||
gatewayTransactionId: $gatewayTransactionId,
|
||||
@@ -130,7 +161,7 @@ class KbzMiniAppGateway implements PaymentGatewayInterface
|
||||
try {
|
||||
$response = Http::asJson()
|
||||
->withOptions($this->mtlsOptions())
|
||||
->post($this->baseUrl, ['Request' => $params]);
|
||||
->post($this->refundOrderUrl, ['Request' => $params]);
|
||||
} catch (ConnectionException $exception) {
|
||||
return new RefundResultData(
|
||||
status: RefundStatus::Failed,
|
||||
@@ -208,7 +239,7 @@ class KbzMiniAppGateway implements PaymentGatewayInterface
|
||||
'timestamp' => (string) now()->timestamp,
|
||||
'method' => 'kbz.payment.precreate',
|
||||
'notify_url' => $data->notifyUrl ?? $this->notifyUrl,
|
||||
'nonce_str' => (string) Str::uuid(),
|
||||
'nonce_str' => $this->nonceStr(),
|
||||
'version' => '1.0',
|
||||
'biz_content' => [
|
||||
'appid' => $this->appId,
|
||||
@@ -235,7 +266,7 @@ class KbzMiniAppGateway implements PaymentGatewayInterface
|
||||
$params = [
|
||||
'timestamp' => (string) now()->timestamp,
|
||||
'method' => 'kbz.payment.queryorder',
|
||||
'nonce_str' => (string) Str::uuid(),
|
||||
'nonce_str' => $this->nonceStr(),
|
||||
'version' => '1.0',
|
||||
'biz_content' => [
|
||||
'appid' => $this->appId,
|
||||
@@ -272,7 +303,7 @@ class KbzMiniAppGateway implements PaymentGatewayInterface
|
||||
$params = [
|
||||
'timestamp' => (string) now()->timestamp,
|
||||
'method' => 'kbz.payment.refund',
|
||||
'nonce_str' => (string) Str::uuid(),
|
||||
'nonce_str' => $this->nonceStr(),
|
||||
'version' => '1.0',
|
||||
'biz_content' => [
|
||||
'appid' => $this->appId,
|
||||
@@ -298,6 +329,18 @@ class KbzMiniAppGateway implements PaymentGatewayInterface
|
||||
return now()->format('YmdHi').strtoupper(Str::random(8));
|
||||
}
|
||||
|
||||
/**
|
||||
* KBZ requires `nonce_str` to be a plain alphanumeric string of at most
|
||||
* 32 characters — no hyphens or other special characters (confirmed
|
||||
* against KBZ's "Query Order" field spec). `Str::uuid()` violates both
|
||||
* constraints (36 chars, hyphenated), which silently broke `precreate`
|
||||
* downstream even though the request's own signature still validated.
|
||||
*/
|
||||
private function nonceStr(): string
|
||||
{
|
||||
return strtoupper(Str::random(32));
|
||||
}
|
||||
|
||||
/**
|
||||
* mTLS options for the refund call — KBZ requires a client cert/key +
|
||||
* CA bundle on `kbz.payment.refund` specifically (domain.md §6).
|
||||
|
||||
Reference in New Issue
Block a user