'1700000000', 'method' => 'kbz.payment.precreate', 'notify_url' => 'https://example.com/api/v1/webhooks/kbz', 'nonce_str' => 'fixture-nonce', 'version' => '1.0', 'biz_content' => [ 'appid' => 'APPID123', 'merch_code' => 'MERCH001', 'merch_order_id' => 'EVB-FIXTURE-001', 'trade_type' => 'MINIAPP', 'total_amount' => '15000', 'trans_currency' => 'MMK', 'callback_info' => 'urlencode', ], ]; $fixtureKey = 'test-merchant-key'; test('joinKeyVal flattens nested biz_content and sorts keys, matching the old algorithm', function () use ($fixtureParams) { expect(KbzSignature::joinKeyVal($fixtureParams))->toBe( 'appid=APPID123&callback_info=urlencode&merch_code=MERCH001&merch_order_id=EVB-FIXTURE-001' .'&method=kbz.payment.precreate&nonce_str=fixture-nonce¬ify_url=https://example.com/api/v1/webhooks/kbz' .'×tamp=1700000000&total_amount=15000&trade_type=MINIAPP&trans_currency=MMK&version=1.0' ); }); test('sign matches the known fixture hash produced by the old KBZPay::signature', function () use ($fixtureParams, $fixtureKey) { expect(KbzSignature::sign($fixtureParams, $fixtureKey)) ->toBe('29F95FB3DCCEC866A68A07E9A1B25BEEE9F355529B5D37395A04B2282FC48BB1'); }); test('sign is uppercase SHA-256 and stable for the same input', function () use ($fixtureParams, $fixtureKey) { $signature = KbzSignature::sign($fixtureParams, $fixtureKey); expect($signature)->toBe(strtoupper($signature)) ->and($signature)->toHaveLength(64) ->and(KbzSignature::sign($fixtureParams, $fixtureKey))->toBe($signature); }); test('sign and joinKeyVal ignore any pre-existing sign/sign_type values', function () use ($fixtureParams, $fixtureKey) { $withStaleSign = [...$fixtureParams, 'sign' => 'stale', 'sign_type' => 'SHA256']; expect(KbzSignature::sign($withStaleSign, $fixtureKey)) ->toBe(KbzSignature::sign($fixtureParams, $fixtureKey)); }); test('joinKeyVal drops null and empty-string values', function () { $params = ['a' => 'x', 'b' => null, 'c' => '', 'd' => ' ']; expect(KbzSignature::joinKeyVal($params))->toBe('a=x'); });