Transactor::generateTransactionNumber()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 10
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 10
loc 10
ccs 0
cts 7
cp 0
crap 6
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
namespace Samerior\MobileMoney\Equity\Library;
4
5
use Carbon\Carbon;
6
use Samerior\MobileMoney\Exceptions\EquityException;
7
use DOMDocument;
8
use GuzzleHttp\Client;
9
use Ixudra\Curl\Facades\Curl;
10
11
/**
12
 * Class Transactor
13
 * @package Samerior\MobileMoney\Equity\Library
14
 */
15
class Transactor
16
{
17
    protected $bearer;
18
    /**
19
     * The hashed password.
20
     *
21
     * @var string
22
     */
23
    protected $password;
24
    /**
25
     * The transaction timestamp.
26
     *
27
     * @var int
28
     */
29
    protected $timestamp;
30
    /**
31
     * The transaction reference id
32
     *
33
     * @var int
34
     */
35
    protected $referenceId;
36
    /**
37
     * The amount to be deducted
38
     *
39
     * @var int
40
     */
41
    protected $amount;
42
    /**
43
     * The Mobile Subscriber number to be billed.
44
     * Must be in format 2547XXXXXXXX.
45
     *
46
     * @var int
47
     */
48
    protected $number;
49
    /**
50
     * The keys and data to fill in the request body.
51
     *
52
     * @var array
53
     */
54
    protected $keys;
55
    /**
56
     * The request to be sent to the endpoint
57
     *
58
     * @var string
59
     */
60
    protected $request;
61
    /**
62
     * The generated transaction number by the Transactable implementer.
63
     *
64
     * @var string
65
     */
66
    protected $transactionNumber;
67
    /**
68
     * The Guzzle Client used to make the request to the endpoint.
69
     *
70
     * @var Client
71
     */
72
    private $client;
73
    /**
74
     * @var EquityRepository
75
     */
76
    private $repository;
77
78
    /**
79
     * Transactor constructor.
80
     * @param EquityRepository $repository
81
     */
82
    public function __construct(EquityRepository $repository)
83
    {
84
        $this->client = new Client([
85
            'verify' => false,
86
            'timeout' => 60,
87
            'allow_redirects' => false,
88
            'expect' => false,
89
        ]);
90
        $this->repository = $repository;
91
    }
92
93
    /**
94
     * Process the transaction request.
95
     *
96
     * @param $amount
97
     * @param $number
98
     * @param $referenceId
99
     *
100
     * @return mixed|\Psr\Http\Message\ResponseInterface
101
     */
102
    public function process($amount, $number, $referenceId)
103
    {
104
        $this->amount = $amount;
105
        $this->number = $number;
106
        $this->referenceId = $referenceId;
107
        $this->initialize();
108
        return $this->handle();
109
    }
110
111
    /**
112
     * Initialize the transaction.
113
     */
114
    protected function initialize()
115
    {
116
        $this->setTimestamp();
117
        $this->getTransactionNumber();
118
        $this->setBearer();
119
//        $this->setupKeys();
120
    }
121
122
    /**
123
     * @throws EquityException
124
     */
125
    private function setBearer()
126
    {
127
        $response = Curl::to($this->repository->endpoint->identity . '/token')
128
            ->withHeader('Authorization: Basic ' . $this->repository->authBasic)->returnResponseObject()
129
            ->withData([
130
                'username' => $this->repository->username,
131
                'password' => $this->repository->password,
132
                'grant_type' => 'password'])
133
            ->post();
134
        $data = json_decode($response->content);
135
        if (!empty($data->access_token)) {
136
            $this->bearer = $data->access_token;
137
        } else {
138
            throw  new EquityException("Authentication Failed");
139
        }
140
    }
141
142
    /**
143
     * Validate and execute the transaction.
144
     * @todo Check Payment Status
145
     * @return mixed|\Psr\Http\Message\ResponseInterface
146
     */
147
    protected function handle()
148
    {
149
        $result = $this->createPayment();
150
        $status = $this->checkPaymentStatus($result->content);
151
        return (object)['result' => $result->content, 'status' => json_decode($status->content), 'reference' => $this->transactionNumber];
152
    }
153
154
    public function checkPaymentStatus($result)
155
    {
156
        $ref = $result->transactionRef;
157
        $url = $this->repository->endpoint->transaction . '/payments/' . $ref;
158
        return Curl::to($url)
159
            ->withHeader('Authorization: Bearer ' . $this->bearer)
160
            ->returnResponseObject()->get();
161
    }
162
163
    /**
164
     * Create Payment Payload
165
     */
166
    private function createPayment()
167
    {
168
        $url = $this->repository->endpoint->transaction . '/payments';
169
        return Curl::to($url)
170
            ->withHeader('Authorization: Bearer ' . $this->bearer)
171
            ->returnResponseObject()
172
            ->withData([
173
                'customer' => [
174
                    'mobileNumber' => '254763555289'
175
                ],
176
                'transaction' => [
177
                    "amount" => "50000",
178
                    "description" => "Payment",
179
                    "type" => "StkRequest Payment",
180
                    "auditNumber" => $this->transactionNumber,
181
                ]
182
            ])
183
            ->asJson()
184
            ->post();
185
    }
186
187
    /**
188
     * Set the transaction timestamp.
189
     */
190
    private function setTimestamp()
191
    {
192
        if ($this->repository->demo) {
193
            $this->timestamp = '20160510161908';
0 ignored issues
show
Documentation Bug introduced by
The property $timestamp was declared of type integer, but '20160510161908' is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
194
            return $this->timestamp;
195
        }
196
        $this->timestamp = Carbon::now()->format('YmdHis');
0 ignored issues
show
Documentation Bug introduced by
The property $timestamp was declared of type integer, but \Carbon\Carbon::now()->format('YmdHis') is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
197
        return $this->timestamp;
198
    }
199
200
    /**
201
     * Override the config pay bill number and pass key.
202
     *
203
     * @param $payBillNumber
204
     * @param $payBillPassKey
205
     *
206
     * @return $this
207
     */
208
    public function setPayBill($payBillNumber, $payBillPassKey)
209
    {
210
        $this->repository->paybillNumber = $payBillNumber;
211
        $this->repository->passkey = $payBillPassKey;
212
        return $this;
213
    }
214
215
216
    /**
217
     * Map the document fields with the transaction details.
218
     * @throws \Exception
219
     */
220
    protected function setupKeys()
221
    {
222
        $this->keys = [
223
            // 'VA_PAYBILL' => $this->repository->paybillNumber,
224
            'VA_PASSWORD' => $this->password,
225
            'VA_TIMESTAMP' => $this->timestamp,
226
            'VA_TRANS_ID' => $this->getTransactionNumber(),
227
            'VA_REF_ID' => $this->referenceId,
228
            'VA_AMOUNT' => $this->amount,
229
            'VA_NUMBER' => $this->number,
230
            'VA_CALL_URL' => $this->repository->callbackUrl,
231
            'VA_CALL_METHOD' => $this->repository->callbackMethod,
232
        ];
233
    }
234
235
    /**
236
     * Get the transaction number from the  implementer.
237
     *
238
     * @return string
239
     */
240
    private function getTransactionNumber(): string
241
    {
242
        $this->transactionNumber = $this->generateTransactionNumber();
243
        return $this->transactionNumber;
244
    }
245
246
    /**
247
     * @return string
248
     */
249 View Code Duplication
    private function generateTransactionNumber(): string
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
250
    {
251
        $characters = '0123456789';
252
        $charactersLength = strlen($characters);
253
        $randomString = '';
254
        for ($i = 0; $i < 10; $i++) {
255
            $randomString .= $characters[random_int(0, $charactersLength - 1)];
256
        }
257
        return $randomString;
258
    }
259
260
    /**
261
     * Validate the required fields
262
     */
263
    private function validateKeys()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
264
    {
265
        Validator::validate($this->keys);
266
    }
267
268
269
    /**
270
     * Execute the request.
271
     *
272
     * @throws \GuzzleHttp\Exception\GuzzleException
273
     */
274
    private function send()
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
275
    {
276
        $response = $this->client->request('POST', $this->repository->endpoint, [
277
            'body' => $this->request
278
        ]);
279
        $this->validateResponse($response);
0 ignored issues
show
Documentation introduced by
$response is of type object<Psr\Http\Message\ResponseInterface>, but the function expects a object<Samerior\MobileMo...quity\Library\Response>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
280
        return $response;
281
    }
282
283
    /**
284
     * Validate the response is a success, throw error if not.
285
     *
286
     * @param Response $response
287
     *
288
     * @throws TransactionException
289
     * @throws EquityException
290
     */
291
    private function validateResponse($response)
292
    {
293
        $message = $response->getBody()->getContents();
294
        $response->getBody()->rewind();
295
        $doc = new DOMDocument();
296
        $doc->loadXML($message);
297
        $responseCode = $doc->getElementsByTagName('RETURN_CODE')->item(0)->nodeValue;
298
        if ($responseCode != '00') {
299
            $responseDescription = $doc
300
                ->getElementsByTagName('DESCRIPTION')
301
                ->item(0)
302
                ->nodeValue;
303
            throw new EquityException('Failure - ' . $responseDescription);
304
        }
305
    }
306
}
307