Skip to content

Commit

Permalink
Wallet Integration 👛 (#10)
Browse files Browse the repository at this point in the history
* poc wallet integration

* aded walletOrder

* aded walletOrder
  • Loading branch information
mathieuduffeler authored Feb 15, 2022
1 parent 7bc079d commit 13049ee
Show file tree
Hide file tree
Showing 8 changed files with 1,182 additions and 615 deletions.
1,331 changes: 718 additions & 613 deletions composer.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/Sips/Office/CashManagement/DuplicateResponse.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);

namespace Sips\Office\CashManagement;

Expand Down
80 changes: 80 additions & 0 deletions lib/Sips/Office/Checkout/WalletOrderRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);

namespace Sips\Office\Checkout;

use Sips\Office\OfficeRequest;
use Sips\SipsCurrency;

final class WalletOrderRequest extends OfficeRequest
{
protected function getMethod() : string
{
return '/rs-services/v2/checkout/walletOrder';
}

protected function getFields() : array
{
return [
'amount' => true,
'captureDay' => true,
'captureMode' => true,
'currencyCode' => true,
'customerIpAddress' => false,
'interfaceVersion' => true,
'invoiceReference' => false,
'keyVersion' => true,
'merchantId' => true,
'merchantTransactionDateTime' => false,
'orderChannel' => true,
'merchantWalletId' => true,
'paymentMeanId' => true,
'transactionOrigin' => false,
'transactionReference' => true,
'statementReference' => false,
];
}

public function setTransactionReference(string $transactionReference) : void
{
$this->validateTransactionReference($transactionReference);
$this->setParameter('transactionReference', $transactionReference);
}

public function setCurrency(string $currency) : void
{
$this->validateCurrency($currency);
$this->setParameter('currencyCode', SipsCurrency::convertCurrencyToSipsCurrencyCode($currency));
}

public function setAmount(int $amount) : void
{
$this->validateAmount($amount);
$this->setParameter('amount', $amount);
}

public function setCaptureMode(string $captureMode) : void
{
$this->validateCaptureMode($captureMode);
$this->setParameter('captureMode', $captureMode);
}

public function setInterfaceVersion(string $interfaceVersion) : void
{
if (!\preg_match('/^IR_WS_\d+\.\d+$/', $interfaceVersion)) {
throw new \InvalidArgumentException(\sprintf('"%s" is an invalid interface version. Should be in format IR_WS_x.x', $interfaceVersion));
}
$this->setParameter('interfaceVersion', $interfaceVersion);
}

public function setMerchantTransactionDateTime(\DateTime $merchantTransactionDateTime) : void
{
$this->setParameter('merchantTransactionDateTime', $merchantTransactionDateTime->format(DATE_ATOM));
}

public function setOrderChannel(string $orderChannel) : void
{
$this->validateOrderChannel($orderChannel);
$this->setParameter('orderChannel', $orderChannel);
}
}
79 changes: 79 additions & 0 deletions lib/Sips/Office/Checkout/WalletOrderResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);

namespace Sips\Office\Checkout;

use \InvalidArgumentException;
use Sips\ShaComposer\ShaComposer;

final class WalletOrderResponse
{
public const SHASIGN_FIELD = "SEAL";

/**
* always present if valid: acquirerResponseCode, authorisationId, responseCode, transactionDateTime, seal
*/
private array $parameters;
private string $shaSign;

public function __construct(array $parameters)
{
$this->parameters = $parameters;
$this->shaSign = (string)$this->extractShaSign($parameters);
}

/**
* @return mixed
*/
private function extractShaSign(array $parameters)
{
$parameters = \array_change_key_case($parameters, CASE_UPPER);
$key = \strtoupper(self::SHASIGN_FIELD);

if (empty($parameters[$key])) {
throw new InvalidArgumentException('SHASIGN parameter not present in parameters.');
}

return $parameters[$key];
}

public function getSeal() : string
{
return $this->shaSign;
}

public function isValid(ShaComposer $shaComposer) : bool
{
return $shaComposer->compose($this->parameters) === $this->shaSign;
}

/**
* @return mixed
*/
public function getParam(string $key)
{
$methodName = 'get' . \ucfirst($key);
if (\method_exists($this, $methodName)) {
return $this->{$methodName}();
}

// always use uppercase
$key = \strtoupper($key);
$parameters = \array_change_key_case($this->parameters, CASE_UPPER);
if (!\array_key_exists($key, $parameters)) {
throw new InvalidArgumentException('Parameter ' . $key . ' does not exist.');
}

return $parameters[$key];
}

public function isSuccessful() : bool
{
return \in_array($this->getParam('responseCode'), ['00', '60']);
}

public function toArray() : array
{
return $this->parameters;
}
}
31 changes: 31 additions & 0 deletions lib/Sips/ShaComposer/AllParametersBase64ShaComposer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);

namespace Sips\ShaComposer;

use Sips\Passphrase;

final class AllParametersBase64ShaComposer implements ShaComposer
{
private Passphrase $passphrase;

public function __construct(Passphrase $passphrase)
{
$this->passphrase = $passphrase;
}

public function compose(array $parameters) : string
{
// compose SHA string
$shaString = '';
foreach ($parameters as $key => $value) {
$shaString .= $key . '=' . $value;
$shaString .= (\array_search($key, \array_keys($parameters), true) !== (\count($parameters)-1)) ?
'|' :
''
;
}

return \hash('sha256', \base64_encode($shaString).$this->passphrase->toString());
}
}
14 changes: 12 additions & 2 deletions lib/Sips/ShaComposer/OfficeShaComposer.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ public function __construct(Passphrase $passphrase)
}

public function compose(array $parameters) : string
{
$seal = $this->getSealFromParameters($parameters);
return \hash_hmac('sha256', $seal, $this->passphrase);
}

private function getSealFromParameters(array $parameters) : string
{
foreach ($this->ignoredParameters as $param) {
if (isset($parameters[$param])) {
Expand All @@ -26,10 +32,14 @@ public function compose(array $parameters) : string
\ksort($parameters);
$seal = '';
foreach ($parameters as $parameterValue) {
$seal .= $parameterValue;
if (\is_array($parameterValue)) {
$seal .= $this->getSealFromParameters($parameterValue);
} else {
$seal .= $parameterValue;
}
}
$seal = \utf8_encode($seal);

return \hash_hmac('sha256', $seal, $this->passphrase);
return $seal;
}
}
143 changes: 143 additions & 0 deletions lib/Sips/WalletRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);

namespace Sips;

use DateTimeInterface;
use Sips\ShaComposer\ShaComposer;
use BadMethodCallException;
use InvalidArgumentException;

final class WalletRequest
{
public const SIMU = "https://payment-webinit.simu.sips-services.com/walletManagementInit";
public const TEST = "https://payment-webinit.test.sips-services.com/walletManagementInit";
public const PRODUCTION = "https://payment-webinit.sips-services.com/walletManagementInit";

public array $allowedLanguages = [
'nl', 'fr', 'de', 'it', 'es', 'cy', 'en',
];

private array $requiredFields = [
'merchantId', 'normalReturnUrl', 'keyVersion', 'merchantWalletId', 'requestDateTime',
];

private array $sipsFields = [
'merchantId', 'normalReturnUrl', 'keyVersion','automaticResponseUrl', 'merchantWalletId', 'requestDateTime',
];

private ShaComposer $shaComposer;
private string $walletUri = self::TEST;
private array $parameters = [];

public function __construct(ShaComposer $shaComposer)
{
$this->shaComposer = $shaComposer;
$this->parameters['requestDateTime'] = (new \DateTimeImmutable('now'))->format(DateTimeInterface::ATOM);
}

public function getShaSign() : string
{
return $this->shaComposer->compose($this->toArray());
}

public function getWalletUri() : string
{
return $this->walletUri;
}

public function setWalletUri(string $walletUri) : void
{
$this->validateUri($walletUri);
$this->walletUri = $walletUri;
}

public function setNormalReturnUrl(string $url) : void
{
$this->validateUri($url);
$this->parameters['normalReturnUrl'] = $url;
}

public function setAutomaticResponseUrl(string $url) : void
{
$this->validateUri($url);
$this->parameters['automaticResponseUrl'] = $url;
}

public function setLanguage(string $language) : void
{
if (!\in_array($language, $this->allowedLanguages, true)) {
throw new InvalidArgumentException("Invalid language locale");
}
$this->parameters['customerLanguage'] = $language;
}

/**
* @param mixed $args
* @return mixed|void
*/
public function __call(string $method, $args)
{
if (\strpos($method, 'set') === 0) {
$field = \lcfirst(\substr($method, 3));
if (\in_array($field, $this->sipsFields)) {
$this->parameters[$field] = $args[0];
return;
}
}

if (\strpos($method, 'get') === 0) {
$field = \lcfirst(\substr($method, 3));
if (\array_key_exists($field, $this->parameters)) {
return $this->parameters[$field];
}
}

throw new BadMethodCallException("Unknown method $method");
}

public function toArray() : array
{
$this->validate();
return $this->parameters;
}

public function toParameterString() : string
{
$parameterString = "";
foreach ($this->parameters as $key => $value) {
$parameterString .= $key . '=' . $value;
$parameterString .= (\array_search($key, \array_keys($this->parameters), true) !== (\count($this->parameters)-1)) ? '|' : '';
}

return $parameterString;
}

public static function createFromArray(ShaComposer $shaComposer, array $parameters) : WalletRequest
{
$instance = new WalletRequest($shaComposer);
foreach ($parameters as $key => $value) {
$instance->{"set$key"}($value);
}
return $instance;
}

public function validate() : void
{
foreach ($this->requiredFields as $field) {
if (empty($this->parameters[$field])) {
throw new \RuntimeException($field . " can not be empty");
}
}
}

protected function validateUri(string $uri) : void
{
if (!\filter_var($uri, FILTER_VALIDATE_URL)) {
throw new InvalidArgumentException("Uri is not valid");
}
if (\strlen($uri) > 200) {
throw new InvalidArgumentException("Uri is too long");
}
}
}
Loading

0 comments on commit 13049ee

Please sign in to comment.