4737838021
- T5.1 PaymentGatewayInterface, DTOs, PaymentMethod/PaymentStatus/RefundStatus enums - T5.2 payments/refunds tables, models, factories - T5.3-T5.5 KbzMiniAppGateway: initiate()/verify()/refund(), ported KBZ signing scheme, wired refund_amount through for partial refunds, mTLS options for refund - T5.6 PaymentGatewayFactory resolving gateways by PaymentMethod - T5.7 PaymentService orchestrator delegating to the resolved gateway
73 lines
2.1 KiB
PHP
73 lines
2.1 KiB
PHP
<?php
|
|
|
|
namespace Modules\Payment\Support;
|
|
|
|
/**
|
|
* KBZ's signing scheme, ported verbatim from bnf_event's `KBZPay::joinKeyVal`/
|
|
* `signature` (domain.md §6): flatten the request array (excluding `sign`/
|
|
* `sign_type`, at any nesting level — `biz_content` included) into sorted
|
|
* `key=val` pairs joined by `&`, append `&key={merchant_key}`, SHA-256 hash,
|
|
* uppercase. Shared by initiate()/verify()/refund() on every gateway.
|
|
*/
|
|
class KbzSignature
|
|
{
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @param list<string> $skips Additional top-level/nested keys to exclude beyond sign/sign_type.
|
|
*/
|
|
public static function joinKeyVal(array $data, array $skips = []): string
|
|
{
|
|
$skips = [...$skips, 'sign', 'sign_type'];
|
|
|
|
$fields = [];
|
|
self::collect($data, $skips, $fields);
|
|
|
|
usort($fields, fn (array $a, array $b): int => strcmp($a['key'], $b['key']));
|
|
|
|
$pairs = [];
|
|
foreach ($fields as $field) {
|
|
if ($field['val'] !== null && trim((string) $field['val']) !== '') {
|
|
$pairs[] = $field['key'].'='.$field['val'];
|
|
}
|
|
}
|
|
|
|
return implode('&', $pairs);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $data
|
|
* @param list<string> $skips
|
|
*/
|
|
public static function sign(array $data, string $merchantKey, array $skips = []): string
|
|
{
|
|
$joined = self::joinKeyVal($data, $skips);
|
|
|
|
return strtoupper(hash('sha256', $joined.'&key='.$merchantKey));
|
|
}
|
|
|
|
/**
|
|
* @param list<string> $skips
|
|
* @param list<array{key: string, val: mixed}> $fields
|
|
*/
|
|
private static function collect(mixed $value, array $skips, array &$fields, string $key = ''): void
|
|
{
|
|
if (in_array($key, $skips, true)) {
|
|
return;
|
|
}
|
|
|
|
if (is_array($value)) {
|
|
foreach ($value as $subKey => $subVal) {
|
|
self::collect($subVal, $skips, $fields, (string) $subKey);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ($key === '') {
|
|
return;
|
|
}
|
|
|
|
$fields[] = ['key' => $key, 'val' => $value];
|
|
}
|
|
}
|