Skip to content

Commit

Permalink
Поддержка запроса генерации QR-кода для оплаты через СБП (#24)
Browse files Browse the repository at this point in the history
* SBP QR payment support for AlfaBank

* Minor tweaks here and there

* Update README

Co-authored-by: Sergey Danilchenko <[email protected]>
  • Loading branch information
daniser and Sergey Danilchenko authored Feb 4, 2022
1 parent 0388ed9 commit 75552f5
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 1 deletion.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,36 @@ $paymentToken = 'token';
$result = $client->payWithSamsungPay($orderNumber, $merchant, $paymentToken);
```

### SBP payments using QR codes

_currently only supported by Alfabank, see [docs](https://pay.alfabank.ru/ecommerce/instructions/SBP_C2B.pdf)._

```php
<?php

use Voronkovich\SberbankAcquiring\Client;

// Specifying "apiUri" and "prefixSbpQr" is mandatory
$client = new Client([
'apiUri' => 'https://pay.alfabank.ru',
'prefixSbpQr' => '/payment/rest/sbp/c2b/qr/',
'userName' => 'username',
'password' => 'password',
]);

$result = $client->getSbpDynamicQr($orderId, [
'qrHeight' => 100,
'qrWidth' => 100,
'qrFormat' => 'image',
]);

echo sprintf(
'<a href="%s"><img src="%s" /></a>',
$result['payload'],
'data:image/png;base64,' . $result['renderedQr']
);
```

---
See `Client` source code to find methods for payment bindings and dealing with 2-step payments.

Expand Down
51 changes: 50 additions & 1 deletion src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ class Client
*/
private $prefixSamsung;

/**
* SBP QR endpoint prefix.
*
* @var string
*/
private $prefixSbpQr;

/**
* An HTTP method.
*
Expand Down Expand Up @@ -136,6 +143,7 @@ public function __construct(array $options = [])
'prefixApple',
'prefixGoogle',
'prefixSamsung',
'prefixSbpQr',
];

$unknownOptions = \array_diff(\array_keys($options), $allowedOptions);
Expand Down Expand Up @@ -171,6 +179,7 @@ public function __construct(array $options = [])
$this->prefixApple = $options['prefixApple'] ?? self::API_PREFIX_APPLE;
$this->prefixGoogle = $options['prefixGoogle'] ?? self::API_PREFIX_GOOGLE;
$this->prefixSamsung = $options['prefixSamsung'] ?? self::API_PREFIX_SAMSUNG;
$this->prefixSbpQr = $options['prefixSbpQr'] ?? null;

if (isset($options['httpMethod'])) {
if (!\in_array($options['httpMethod'], [ HttpClientInterface::METHOD_GET, HttpClientInterface::METHOD_POST ])) {
Expand Down Expand Up @@ -638,11 +647,51 @@ public function payWithSamsungPay($orderNumber, string $merchant, string $paymen
return $this->execute($this->prefixSamsung . 'payment.do', $data);
}

/**
* Get QR code for payment through SBP.
*
* @param int|string $orderId An order identifier
* @param array $data Additional data
*
* @return array A server's response
*/
public function getSbpDynamicQr($orderId, array $data = []): array
{
if (empty($this->prefixSbpQr)) {
throw new \RuntimeException('The "prefixSbpQr" option is unspecified.');
}

$data['mdOrder'] = $orderId;

return $this->execute($this->prefixSbpQr . 'dynamic/get.do', $data);
}

/**
* Get QR code status.
*
* @param int|string $orderId An order identifier
* @param string $qrId A QR code identifier
* @param array $data Additional data
*
* @return array A server's response
*/
public function getSbpQrStatus($orderId, string $qrId, array $data = []): array
{
if (empty($this->prefixSbpQr)) {
throw new \RuntimeException('The "prefixSbpQr" option is unspecified.');
}

$data['mdOrder'] = $orderId;
$data['qrId'] = $qrId;

return $this->execute($this->prefixSbpQr . 'status.do', $data);
}

/**
* Execute an action.
*
* @param string $action An action's name e.g. 'register.do'
* @param array $data An actions's data
* @param array $data An action's data
*
* @throws NetworkException
*
Expand Down
56 changes: 56 additions & 0 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,62 @@ public function testPaysWithASamsungPayWithCustomPrefix()
$client->payWithSamsungPay('eee-eee', 'my_merchant', 'token_zzz', ['language' => 'en']);
}

public function testGetSbpDynamicQrThrowsAnExceptionIfPrefixSbpQrOptionIsUnspecified()
{
$client = new Client(['token' => 'abrakadabra']);

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('The "prefixSbpQr" option is unspecified.');

$client->getSbpDynamicQr('xxx-yyy-zzz', ['qrHeight' => 100, 'qrWidth' => 100, 'qrFormat' => 'image']);
}

public function testGetsSbpDynamicQrWithCustomPrefix()
{
$httpClient = $this->getHttpClientToTestSendingData(
'/payment/rest/sbp/c2b/qr/dynamic/get.do',
'POST',
[ 'Content-Type' => 'application/x-www-form-urlencoded' ],
'qrHeight=100&qrWidth=100&qrFormat=image&mdOrder=xxx-yyy-zzz&token=abrakadabra'
);

$client = new Client([
'token' => 'abrakadabra',
'httpClient' => $httpClient,
'prefixSbpQr' => '/payment/rest/sbp/c2b/qr/',
]);

$client->getSbpDynamicQr('xxx-yyy-zzz', ['qrHeight' => 100, 'qrWidth' => 100, 'qrFormat' => 'image']);
}

public function testGetSbpQrStatusThrowsAnExceptionIfPrefixSbpQrOptionIsUnspecified()
{
$client = new Client(['token' => 'abrakadabra']);

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('The "prefixSbpQr" option is unspecified.');

$client->getSbpQrStatus('xxx-yyy-zzz', 'meh');
}

public function testGetsSbpQrStatusWithCustomPrefix()
{
$httpClient = $this->getHttpClientToTestSendingData(
'/payment/rest/sbp/c2b/qr/status.do',
'POST',
[ 'Content-Type' => 'application/x-www-form-urlencoded' ],
'mdOrder=xxx-yyy-zzz&qrId=meh&token=abrakadabra'
);

$client = new Client([
'token' => 'abrakadabra',
'httpClient' => $httpClient,
'prefixSbpQr' => '/payment/rest/sbp/c2b/qr/',
]);

$client->getSbpQrStatus('xxx-yyy-zzz', 'meh');
}

public function testAddsASpecialPrefixToActionForBackwardCompatibility()
{
$httpClient = $this->mockHttpClient();
Expand Down

0 comments on commit 75552f5

Please sign in to comment.