Skip to content

Commit c23940e

Browse files
Merge pull request #100 from Flutterwave/dev
Add distributed tracing to logs
2 parents 5210a9b + 154a1da commit c23940e

8 files changed

Lines changed: 751 additions & 151 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
require __DIR__."/../../vendor/autoload.php";
3+
4+
session_start();
5+
6+
\Flutterwave\Flutterwave::bootstrap();
7+
8+
try {
9+
$flw = new \Flutterwave\Flutterwave();
10+
$flw->setAmount('1000')
11+
->setCurrency(\Flutterwave\Util\Currency::NGN)
12+
->setCountry('NG')
13+
->setEmail('test@example.com')
14+
->setFirstname('John')
15+
->setLastname('Doe')
16+
->setPhoneNumber('+2349067985861')
17+
->setRedirectUrl("http://{$_SERVER['HTTP_HOST']}/examples/endpoint/verify.php")
18+
->setTitle('Test Payment') # ->setTitle('</script><script>alert(1)</script>') change title to this for testing XSS fix
19+
->setDescription('Testing initialize XSS fix')
20+
->setLogo('https://mysite.com/logo.png')
21+
->setPaymentOptions('card,banktransfer');
22+
23+
if (!empty($_REQUEST) && isset($_REQUEST['make'])) {
24+
$flw->initialize(); // this renders the full modal page
25+
exit;
26+
}
27+
28+
} catch (Exception $e) {
29+
$error = $e->getMessage();
30+
}
31+
?>
32+
33+
<link rel="stylesheet" href="../assets/css/index.css">
34+
<div class="buttons">
35+
<form method="get">
36+
<h3>Initialize() XSS Fix - Smoke Test</h3>
37+
<span class="error"><?= $error ?? "" ?></span>
38+
<div class="cta">
39+
<button class="make-payment" name="make" value="1">Test Initialize</button>
40+
</div>
41+
</form>
42+
</div>
43+
44+
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
45+
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
46+
crossorigin="anonymous"></script>
47+
<script src="../assets/js/index.js"></script>

setup.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
$flutterwave_installation = 'composer';
77

8-
if( !file_exists( '.env' ) && !is_dir('vendor')) {
9-
$dotenv = Dotenv::createImmutable(__DIR__."/../../../"); # on the event that the package is install via composer.
8+
if( !file_exists( __DIR__.'/.env' ) && !is_dir(__DIR__.'/vendor')) {
9+
$dotenv = Dotenv::createImmutable(__DIR__."/../../../");
1010
} else {
1111
$flutterwave_installation = "manual";
12-
$dotenv = Dotenv::createImmutable(__DIR__); # on the event that the package is forked or donwload directly from Github.
12+
$dotenv = Dotenv::createImmutable(__DIR__);
1313
}
1414

1515
$dotenv->safeLoad();

src/Flutterwave.php

Lines changed: 82 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class Flutterwave extends AbstractPayment
2929
use PaymentFactory;
3030

3131
private SignozServiceLogger $signoz;
32+
private ?array $traceContext = null;
3233

3334
/**
3435
* Flutterwave Construct
@@ -49,6 +50,9 @@ public function __construct()
4950
} else {
5051
$this->signoz = self::$config->getSignoz();
5152
}
53+
54+
$this->traceContext = $this->buildTraceContext();
55+
$this->signoz->setDefaultTraceContext($this->traceContext);
5256
}
5357

5458
private function checkPageIsSecure()
@@ -58,6 +62,40 @@ private function checkPageIsSecure()
5862
}
5963
}
6064

65+
private function buildTraceContext(?array $parentContext = null): array
66+
{
67+
$timestamp = gmdate('Y-m-d\TH:i:s.v\Z');
68+
$traceId = $parentContext['trace_id'] ?? $this->generateTraceId();
69+
$parentSpanId = $parentContext['span_id'] ?? null;
70+
71+
return [
72+
'trace_id' => $traceId,
73+
'span_id' => $this->generateSpanId(),
74+
'parent_span_id' => $parentSpanId,
75+
'span_start_time' => $timestamp,
76+
'span_end_time' => $timestamp,
77+
];
78+
}
79+
80+
private function getTraceContextForEvent(): array
81+
{
82+
$nextContext = $this->buildTraceContext($this->traceContext);
83+
$this->traceContext = $nextContext;
84+
$this->signoz->setDefaultTraceContext($this->traceContext);
85+
86+
return $nextContext;
87+
}
88+
89+
private function generateTraceId(): string
90+
{
91+
return bin2hex(random_bytes(16));
92+
}
93+
94+
private function generateSpanId(): string
95+
{
96+
return bin2hex(random_bytes(8));
97+
}
98+
6199
/**
62100
* Sets the transaction amount
63101
*
@@ -227,6 +265,16 @@ public function setMetaData(array $meta): object
227265
return $this;
228266
}
229267

268+
/**
269+
* Enforce the same trace context for request, transaction, and error events.
270+
*/
271+
public function setTraceContext(array $traceContext): object
272+
{
273+
$this->traceContext = $this->buildTraceContext($traceContext);
274+
$this->signoz->setDefaultTraceContext($this->traceContext);
275+
return $this;
276+
}
277+
230278
/**
231279
* Sets the event hooks for all available triggers
232280
*
@@ -257,6 +305,7 @@ public function requeryTransaction(string $referenceNumber): object
257305

258306
$appId = $this->signoz->getAppId();
259307
$environment = $this->signoz->getCurrentEnvironment();
308+
$traceContext = $this->getTraceContextForEvent();
260309

261310
$data = [
262311
'id' => (int) $referenceNumber,
@@ -274,13 +323,13 @@ public function requeryTransaction(string $referenceNumber): object
274323
// Handle successful.
275324
if (isset($this->handler)) {
276325
$final_tx_ref = $response->data->tx_ref;
277-
$this->signoz->trackRequestSent($appId, $environment, 'GET', $referenceNumber, $url );
326+
$this->signoz->trackRequestSent($appId, $environment, 'GET', $final_tx_ref, $url, $traceContext);
278327
if( 'production' === $environment ) {
279328
$final_currency = $response->data->currency;
280329
$final_amount = $response->data->amount;
281330
$payment_type = $response->data->payment_type;
282331
$final_fee = $response->data->app_fee;
283-
$this->signoz->trackTransaction($appId,$final_tx_ref, $final_currency, (float) $final_amount, $payment_type, (float) $final_fee);
332+
$this->signoz->trackTransaction($appId,$final_tx_ref, $final_currency, (float) $final_amount, $payment_type, (float) $final_fee, $traceContext);
284333
}
285334
$this->handler->onSuccessful($response->data);
286335
}
@@ -300,7 +349,7 @@ public function requeryTransaction(string $referenceNumber): object
300349
if ($this->requeryCount > 4) {
301350
// Now you have to setup a queue by force. We couldn't get a status in 5 requeries.
302351
if (isset($this->handler)) {
303-
$this->signoz->trackError($appId, 'TIMEOUT_ERROR', 'timedout while requerying transaction with id: ' . $referenceNumber);
352+
$this->signoz->trackError($appId, 'TIMEOUT_ERROR', 'timedout while requerying transaction with id: ' . $referenceNumber, $traceContext);
304353
$this->handler->onTimeout($this->txref, $response->data);
305354
}
306355
} else {
@@ -312,7 +361,7 @@ public function requeryTransaction(string $referenceNumber): object
312361
}
313362
} else {
314363
// Handle Requery Error.
315-
$this->signoz->trackError($appId, 'REQUERY_ERROR', 'Failed to requery transaction with id: ' . $referenceNumber);
364+
$this->signoz->trackError($appId, 'REQUERY_ERROR', 'Failed to requery transaction with id: ' . $referenceNumber, $traceContext);
316365
if (isset($this->handler)) {
317366
$this->handler->onRequeryError($response->data);
318367
}
@@ -321,60 +370,41 @@ public function requeryTransaction(string $referenceNumber): object
321370
}
322371

323372
/**
324-
* Generates the final json to be used in configuring the payment call to the rave payment gateway
373+
* @deprecated Use render('inline')->getHtml() instead.
374+
* Will be removed in a future version.
325375
*/
326376
public function initialize(): void
327377
{
378+
$this->traceContext = $this->buildTraceContext($this->traceContext);
379+
$this->signoz->setDefaultTraceContext($this->traceContext);
380+
381+
@trigger_error(
382+
'initialize() is deprecated and will be removed in a future version. Use render(\'inline\')->with([...])->getHtml() instead.',
383+
E_USER_DEPRECATED
384+
);
385+
328386
$this->createCheckSum();
329387

330-
$appId = $this->signoz->getAppId();
331-
$environment = $this->signoz->getCurrentEnvironment();
388+
$checkoutConfig = [
389+
'public_key' => self::$config->getPublicKey(),
390+
'tx_ref' => $this->txref,
391+
'amount' => (float) $this->amount,
392+
'currency' => $this->currency,
393+
'country' => $this->country,
394+
'redirect_url' => $this->redirectUrl,
395+
'payment_method' => $this->paymentOptions,
396+
'email' => $this->customerEmail,
397+
'phone_number' => $this->customerPhone,
398+
'first_name' => $this->customerFirstname,
399+
'last_name' => $this->customerLastname,
400+
'customizations' => [
401+
'title' => $this->customTitle,
402+
'description' => $this->customDescription,
403+
'logo' => $this->customLogo,
404+
],
405+
];
332406

333-
$this->signoz->trackRequestSent($appId, $environment, 'GET', $this->txref, '/inline');
334-
335-
$this->logger->info('Rendering Payment Modal..');
336-
337-
echo '<html lang="en">';
338-
echo '<body>';
339-
// $loader_img_src = FLW_PHP_ASSET_DIR."js/v3.js";
340-
echo '<div style="display: flex; flex-direction: row;justify-content: center; align-content: center ">
341-
Proccessing...<img src="../assets/images/ajax-loader.gif" alt="loading-gif"/></div>';
342-
// $script_src = FLW_PHP_ASSET_DIR."js/v3.js";
343-
echo '<script type="text/javascript" src="https://checkout.flutterwave.com/v3.js"></script>';
344-
345-
echo '<script>';
346-
echo 'document.addEventListener("DOMContentLoaded", function(event) {';
347-
echo 'FlutterwaveCheckout({
348-
public_key: "' . self::$config->getPublicKey() . '",
349-
tx_ref: "' . $this->txref . '",
350-
amount: ' . $this->amount . ',
351-
currency: "' . $this->currency . '",
352-
country: "' . $this->country . '",
353-
payment_options: "card,ussd,mpesa,barter,mobilemoneyghana,
354-
mobilemoneyrwanda,mobilemoneyzambia,mobilemoneyuganda,banktransfer,account",
355-
redirect_url:"' . $this->redirectUrl . '",
356-
customer: {
357-
email: "' . $this->customerEmail . '",
358-
phone_number: "' . $this->customerPhone . '",
359-
name: "' . $this->customerFirstname . ' ' . $this->customerLastname . '",
360-
},
361-
callback: function (data) {
362-
console.log(data);
363-
},
364-
onclose: function() {
365-
window.location = "?cancelled=cancelled&cancel_ref=' . $this->txref . '";
366-
},
367-
customizations: {
368-
title: "' . $this->customTitle . '",
369-
description: "' . $this->customDescription . '",
370-
logo: "' . $this->customLogo . '",
371-
}
372-
});';
373-
echo '});';
374-
echo '</script>';
375-
echo '</body>';
376-
echo '</html>';
377-
$this->logger->info('Rendered Payment Modal Successfully..');
407+
echo $this->render(Modal::POPUP)->with($checkoutConfig)->getHtml();
378408
}
379409

380410
/**

src/Library/Modal.php

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public function with(array $args)
7070
}
7171

7272
$this->customer = (new \Flutterwave\Factories\CustomerFactory())->create($args['customer']);
73-
73+
7474
$args['customer'] = $this->customer;
7575

7676
if (isset($args['tx_ref'])) {
@@ -83,10 +83,15 @@ public function with(array $args)
8383
} else {
8484
$args = array_merge($args, $this->generatedTransactionData);
8585
}
86-
$this->payload = (new \Flutterwave\Factories\PayloadFactory())->create($args);
8786

87+
$this->payload = (new \Flutterwave\Factories\PayloadFactory())->create($args);
88+
8889
$this->payload->set('redirect_url', $args['redirect_url']);
8990
$this->payload->set('payment_method', $args['payment_method']);
91+
92+
$this->payload->set('custom_title', $args['customizations']['title'] ?? '');
93+
$this->payload->set('custom_description', $args['customizations']['description'] ?? '');
94+
$this->payload->set('custom_logo', $args['customizations']['logo'] ?? '');
9095

9196
$dataToHash = [
9297
'amount' => $args['amount'],
@@ -121,43 +126,52 @@ public function getHtml()
121126
$payment_method = $payload['payment_method'] ?? $default_options;
122127

123128
$this->logger->info('Rendering Payment Modal..');
129+
130+
$checkoutConfig = json_encode([
131+
'public_key' => self::$config->getPublicKey(),
132+
'tx_ref' => $payload['tx_ref'],
133+
'amount' => $payload['amount'],
134+
'currency' => $currency,
135+
'country' => $country,
136+
'payment_options' => $payment_method,
137+
'redirect_url' => $payload['redirect_url'],
138+
'payload_hash' => $payload['payload_hash'],
139+
'customer' => [
140+
'email' => $payload['email'],
141+
'phone_number' => $payload['phone_number'],
142+
'name' => $payload['fullname']
143+
],
144+
'customizations' => [
145+
'title' => $payload['custom_title'],
146+
'description' => $payload['custom_description'],
147+
'logo' => $payload['custom_logo'],
148+
],
149+
], JSON_HEX_TAG | JSON_PRESERVE_ZERO_FRACTION | JSON_HEX_QUOT | JSON_HEX_APOS | JSON_THROW_ON_ERROR);
150+
124151
$html = '';
125152

153+
$html .= '<!DOCTYPE html>';
126154
$html .= '<html lang="en">';
127155
$html .= '<body>';
128156
$html .= '<div style="display: flex; flex-direction: row;justify-content: center; align-content: center ">
129-
Proccessing...<img src="../assets/images/ajax-loader.gif" alt="loading-gif"/></div>';
157+
Processing...<img src="../assets/images/ajax-loader.gif" alt="loading-gif"/></div>';
130158
$html .= '<script type="text/javascript" src="https://checkout.flutterwave.com/v3.js"></script>';
131159
$html .= '<script>';
132160
$html .= 'document.addEventListener("DOMContentLoaded", function(event) {';
133-
$html .= 'FlutterwaveCheckout({
134-
public_key: "' . self::$config->getPublicKey() . '",
135-
tx_ref: "' . $payload['tx_ref'] . '",
136-
amount: ' . $payload['amount'] . ',
137-
currency: "' . $currency . '",
138-
country: "' . $country . '",
139-
payment_options: "' . $payment_method . '",
140-
redirect_url:"' . $payload['redirect_url'] . '",
141-
payload_hash:"' . $payload['payload_hash'] . '",
142-
customer: {
143-
email: "' . $payload['email'] . '",
144-
phone_number: "' . $payload['phone_number'] . '",
145-
name: "' . $payload['fullname'] . '",
146-
},
147-
callback: function (data) {
148-
console.log(data);
149-
},
150-
onclose: function() {
151-
window.location = "?status=cancelled&tx_ref=' . $payload['tx_ref'] . '";
152-
}
153-
});';
161+
$html .= ' var config = ' . $checkoutConfig . ';';
162+
$html .= ' config.callback = function(data) { console.log(data); };';
163+
$html .= ' config.onclose = function() {';
164+
$html .= ' window.location = "?status=cancelled&tx_ref=' . urlencode($payload['tx_ref']) . '";';
165+
$html .= ' };';
166+
$html .= ' FlutterwaveCheckout(config);';
154167
$html .= '});';
155168
$html .= '</script>';
156169
$html .= '</body>';
157170
$html .= '</html>';
158171

159172
$this->logger->info('Rendered Payment Modal Successfully..');
160173
$signoz->trackRequestSent($appId, $environment, 'GET', $payload['tx_ref'], '/inline');
174+
161175
return $html;
162176
}
163177

0 commit comments

Comments
 (0)