Skip to content

Commit 8ffeb4d

Browse files
committed
Fix: Update how we handle callback functionality, add tests
1 parent 2a2b1cf commit 8ffeb4d

File tree

2 files changed

+169
-10
lines changed

2 files changed

+169
-10
lines changed

src/Http/Controllers/StatusCallbackController.php

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,47 @@
22

33
namespace RedberryProducts\LaravelBogPayment\Http\Controllers;
44

5-
use HttpException;
65
use Illuminate\Http\Request;
76
use Illuminate\Routing\Controller;
87
use RedberryProducts\LaravelBogPayment\Events\TransactionStatusUpdated;
8+
use Symfony\Component\HttpKernel\Exception\HttpException;
99

1010
class StatusCallbackController extends Controller
1111
{
12+
/**
13+
* @throws HttpException
14+
*/
1215
public function __invoke(Request $request)
1316
{
1417
$requestBody = $request->getContent();
1518

19+
$request->validate([
20+
'body' => 'required|array'
21+
]);
22+
1623
$signature = $request->header('Callback-Signature');
1724
$publicKey = config('bog-payment.public_key');
1825

1926
$this->ensureSignatureIsValid($requestBody, $signature, $publicKey);
2027

21-
event(TransactionStatusUpdated::class, [json_decode($requestBody, true)['body']]);
28+
event(new TransactionStatusUpdated($request->get('body')));
2229

23-
return json_decode($requestBody, true)['body'];
30+
return $request->get('body');
2431
}
2532

2633
/**
2734
* Verifies the signature using the provided public key.
2835
*
2936
* @param string $data The data to verify.
30-
* @param string $signature The provided signature.
31-
* @param string $publicKey The public key to use for verification.
37+
* @param string|null $signature The provided signature.
38+
* @param string|null $publicKey The public key to use for verification.
39+
*
40+
* @return bool
3241
*/
33-
private function verifySignature(string $data, string $signature, string $publicKey): bool
42+
private function verifySignature(string $data, string|null $signature, string|null $publicKey): bool
3443
{
35-
// Decode the signature from base64
3644
$decodedSignature = base64_decode($signature);
45+
3746
$publicKey = openssl_pkey_get_public($publicKey);
3847
$verified = openssl_verify($data, $decodedSignature, $publicKey, 'RSA-SHA256');
3948

@@ -45,9 +54,8 @@ private function verifySignature(string $data, string $signature, string $public
4554
*/
4655
private function ensureSignatureIsValid($body, $signature, $publicKey)
4756
{
48-
if (! $this->verifySignature($body, $signature, $publicKey)) {
49-
throw new HttpException('Invalid Signature', 401);
57+
if (!$this->verifySignature($body, $signature, $publicKey)) {
58+
throw new HttpException(401, 'Invalid Signature');
5059
}
51-
5260
}
5361
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
use Illuminate\Support\Facades\Event;
4+
use Illuminate\Support\Facades\Config;
5+
use RedberryProducts\LaravelBogPayment\Events\TransactionStatusUpdated;
6+
7+
8+
function generateRSAKeyPair(): array
9+
{
10+
$config = [
11+
'private_key_bits' => 2048,
12+
'default_md' => 'sha256',
13+
'private_key_type' => OPENSSL_KEYTYPE_RSA,
14+
];
15+
16+
$resource = openssl_pkey_new($config);
17+
openssl_pkey_export($resource, $privateKey);
18+
$publicKeyDetails = openssl_pkey_get_details($resource);
19+
20+
return [
21+
'private_key' => $privateKey,
22+
'public_key' => $publicKeyDetails['key'],
23+
];
24+
}
25+
26+
function generateValidSignature(string $data, string $privateKey)
27+
{
28+
openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA256);
29+
return base64_encode($signature);
30+
}
31+
32+
beforeEach(function () {
33+
$keys = generateRSAKeyPair();
34+
35+
$this->privateKey = $keys['private_key'];
36+
$this->publicKey = $keys['public_key'];
37+
38+
Config::set('bog-payment.public_key', $keys['public_key']);
39+
});
40+
41+
test('callback with valid signature and payload should trigger event', function () {
42+
Event::fake();
43+
$payload = ['body' => ['transaction_id' => '12345', 'status' => 'success']];
44+
$payloadJson = json_encode($payload);
45+
$signature = generateValidSignature($payloadJson, $this->privateKey);
46+
47+
$response = $this->postJson(route('bog-payment.callback'),
48+
['body' => ['transaction_id' => '12345', 'status' => 'success']], [
49+
'Content-Type' => 'application/json',
50+
'Callback-Signature' => $signature,
51+
]);
52+
53+
$response->assertOk();
54+
Event::assertDispatched(TransactionStatusUpdated::class);
55+
});
56+
57+
test('callback with invalid signature should return 401', function () {
58+
$response = $this->postJson(route('bog-payment.callback'),
59+
['body' => ['transaction_id' => '12345', 'status' => 'success']], [
60+
'Content-Type' => 'application/json',
61+
'Callback-Signature' => 'invalid_signature',
62+
]);
63+
64+
$response->assertStatus(401);
65+
});
66+
67+
test('callback with missing signature should return 401', function () {
68+
$response = $this->postJson(route('bog-payment.callback'),
69+
['body' => ['transaction_id' => '12345', 'status' => 'success']], [
70+
'Content-Type' => 'application/json',
71+
]);
72+
73+
$response->assertStatus(401);
74+
});
75+
76+
test('callback with invalid JSON should return 401', function () {
77+
$payload = ['body' => ['transaction_id' => '12345', 'status' => 'success']];
78+
$response = $this->postJson(route('bog-payment.callback'), $payload, [
79+
'Content-Type' => 'application/json',
80+
'Callback-Signature' => generateValidSignature('invalid_json', $this->privateKey),
81+
]);
82+
83+
$response->assertStatus(401);
84+
});
85+
86+
test('callback with missing required fields should return error', function () {
87+
$payload = json_encode(['unexpected_field' => 'value']);
88+
$signature = generateValidSignature($payload, $this->privateKey);
89+
90+
$response = $this->postJson(route('bog-payment.callback'), [], [
91+
'Content-Type' => 'application/json',
92+
'Callback-Signature' => $signature,
93+
]);
94+
95+
$response->assertStatus(422);
96+
});
97+
98+
test('callback with missing public key should return 500', function () {
99+
Config::set('bog-payment.public_key', null);
100+
101+
$payload = ['body' => ['transaction_id' => '12345', 'status' => 'success']];
102+
$payloadJson = json_encode($payload);
103+
$signature = generateValidSignature($payloadJson, $this->privateKey);
104+
105+
$response = $this->postJson(route('bog-payment.callback'), $payload, [
106+
'Content-Type' => 'application/json',
107+
'Callback-Signature' => $signature,
108+
]);
109+
110+
$response->assertStatus(500);
111+
});
112+
113+
test('callback when signature verification fails should return 401', function () {
114+
$payload = ['body' => ['transaction_id' => '12345', 'status' => 'success']];
115+
116+
$response = $this->postJson(route('bog-payment.callback'), $payload, [
117+
'Content-Type' => 'application/json',
118+
'Callback-Signature' => 'wrong_signature',
119+
]);
120+
121+
$response->assertStatus(401);
122+
});
123+
124+
test('callback when unexpected error occurs should return 500', function () {
125+
Config::set('bog-payment.public_key', 'invalid_key');
126+
127+
$payload = json_encode(['body' => ['transaction_id' => '12345', 'status' => 'success']]);
128+
$signature = generateValidSignature($payload, $this->privateKey);
129+
130+
$response = $this->postJson(route('bog-payment.callback'),
131+
['body' => ['transaction_id' => '12345', 'status' => 'success']], [
132+
'Content-Type' => 'application/json',
133+
'Callback-Signature' => $signature,
134+
]);
135+
136+
$response->assertStatus(500);
137+
});
138+
139+
test('callback with valid data should return correct response', function () {
140+
$payload = ['body' => ['transaction_id' => '12345', 'status' => 'success']];
141+
$payloadJson = json_encode($payload);
142+
$signature = generateValidSignature($payloadJson, $this->privateKey);
143+
144+
$response = $this->postJson(route('bog-payment.callback'), $payload, [
145+
'Content-Type' => 'application/json',
146+
'Callback-Signature' => $signature,
147+
]);
148+
149+
$response->assertOk()
150+
->assertJson(['transaction_id' => '12345', 'status' => 'success']);
151+
});

0 commit comments

Comments
 (0)