Skip to content

Commit

Permalink
Merge pull request #22 from TransbankDevelopers/feat/channel-callback…
Browse files Browse the repository at this point in the history
…url-appscheme-support

Channel callbackurl appscheme support
  • Loading branch information
goncafa authored Aug 16, 2018
2 parents 12f25cc + 6fdd4ab commit af5deee
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 12 deletions.
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ automaticamente, pero si usas el SDK de manera directa requerirás también:
Para usar el SDK en tu proyecto puedes usar Composer, añadiendo el SDK como dependencia a tu proyecto:
```json
"require": {
"transbank/transbank-sdk": "1.0.1"
"transbank/transbank-sdk": "1.1.0"
}
```

Expand All @@ -50,6 +50,8 @@ Existen varias formas de configurar esta información, la cual identifica a cada
```bash
export ONEPAY_SHARED_SECRET = "valor de tu shared secret"
export ONEPAY_API_KEY = "valor de tu api key"
export ONEPAY_CALLBACK_URL = "valor de tu callback url"
export ONEPAY_APP_SCHEME = "valor de tu app scheme"
```

##### 2. Configurando tu API_KEY y SHARED_SECRET al inicializar tu proyecto
Expand All @@ -59,6 +61,8 @@ use Transbank\Onepay\OnepayBase;

OnepayBase::setSharedSecret('valor de tu shared secret');
OnepayBase::setApiKey('valor de tu api key');
OnepayBase::setCallbackUrl('valor de tu callback url');
OnepayBase::setAppScheme('valor de tu app scheme');
```

##### 3. Pasando el API_KEY y SHARED_SECRET a cada petición
Expand All @@ -69,7 +73,7 @@ use Transbank\Onepay\Options;
$options = new Options('otro-api-key', 'otro-shared-secret');

# Al crear transacción
$transaction = Transaction::create($carro, $options);
$transaction = Transaction::create($carro, $options, $channel);

# Al confirmar transacción
$commitTransaction = Transaction::commit($occ, $externalUniqueNumber, $options)
Expand Down Expand Up @@ -125,7 +129,7 @@ $segundoCarro = ShoppingCart::fromJSON($losObjetos);

Teniendo un carro, se puede crear una `Transaction`
```php
$transaction = Transaction::create($carro);
$transaction = Transaction::create($carro, null, $channel);

# Retorna un objeto TransactionCreateResponse con getters (getNombreAtributo) y setters(setNombreAtributo) para:

Expand Down Expand Up @@ -156,8 +160,13 @@ json_encode($transaction);
}
```

En caso de que falle el `create` de una `Transaction` se devuelve un objeto de tipo `TransactionCreateException`, donde la propiedad `message`contiene la razón del fallo.
En caso de que falle el `create` de una `Transaction` se devuelve un objeto de tipo `TransactionCreateException`, donde
la propiedad `message`contiene la razón del fallo.

El parametro `$channel` puede ser `WEB`, `MOBILE` o `APP` dependiendo si quien esta realizando el pago esta usando un
browser en versión Desktop, Móvil o esta utilizando alguna aplicación móvil nativa.

En caso que `$channel` sea `APP` es obligatorio que este previamente configurado el `appScheme`:

Posteriormente, se debe presentar al usuario el código QR y el número OTT para que pueda proceder al pago mediante la aplicación móvil.
##### Confirmar una transacción
Expand Down
19 changes: 19 additions & 0 deletions lib/ChannelEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Transbank\Onepay;

class ChannelEnum
{

public static function WEB() {
return 'WEB';
}

public static function MOBILE() {
return 'MOBILE';
}

public static function APP() {
return 'APP';
}
}
44 changes: 41 additions & 3 deletions lib/OnepayBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
*/
class OnepayBase
{

const DEFAULT_CALLBACK = "http://no.callback.has/been.set";

public static $appKey = "04533c31-fe7e-43ed-bbc4-1c8ab1538afp";
public static $callbackUrl = "http://nourlcallbackneededhere";
public static $callbackUrl = null;

public static $serverBasePath;
public static $scriptPath;
public static $apiKey;
public static $sharedSecret;
private static $integrationType = "TEST";
private static $appScheme = null;

public static function integrationTypes($type = null) {

Expand Down Expand Up @@ -62,12 +63,24 @@ public static function getAppKey()
{
return self::$appKey;
}

/**
* @param mixed $callbackUrl
* @throws \Exception
*/
public static function setCallbackUrl($callbackUrl)
{
self::$callbackUrl = $callbackUrl;
}
/**
* Get the callback URL. Not necessary nor used by integrators, but must be
* kept for legacy reasons
*/
public static function getCallbackUrl()
{
if(!self::$callbackUrl) {
return getenv("ONEPAY_CALLBACK_URL");
}
return self::$callbackUrl;
}
/**
Expand Down Expand Up @@ -98,7 +111,7 @@ public static function getCurrentIntegrationTypeUrl()
return self::integrationTypes()[self::$integrationType];
}
/**
*
*
* Get the integration URL of $type
*/

Expand Down Expand Up @@ -135,4 +148,29 @@ public static function setCurrentIntegrationType($type)
}
self::$integrationType = $type;
}

/**
* @return mixed
*/
public static function getAppScheme()
{
if(!self::$appScheme) {
return getenv("ONEPAY_APP_SCHEME");
}

return self::$appScheme;
}

/**
* @param mixed $appScheme
*/
public static function setAppScheme($appScheme)
{
self::$appScheme = $appScheme;
}

public static function DEFAULT_CHANNEL() {
return ChannelEnum::WEB();
}

}
20 changes: 18 additions & 2 deletions lib/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,30 @@ private static function getHttpClient()
return self::$httpClient;
}

public static function create($shoppingCart, $options = null)
/**
* @param $shoppingCart
* @param null $options
* @param ChannelEnum|null $channel
* @return TransactionCreateResponse
* @throws SignException
* @throws TransactionCreateException
* @throws \Exception
*/
public static function create($shoppingCart, $options = null, $channel = null)
{
if (null != $channel && $channel == ChannelEnum::APP() && null == OnepayBase::getAppScheme())
throw new TransactionCreateException('You need to set an appScheme if you want to use the APP channel');

if (null != $channel && $channel == ChannelEnum::MOBILE() && null == OnepayBase::getCallbackUrl())
throw new TransactionCreateException('You need to set a valid callback if you want to use the MOBILE channel');

if(!$shoppingCart instanceof ShoppingCart) {
throw new \Exception("Shopping cart is null or empty");
}
$http = self::getHttpClient();
$options = OnepayRequestBuilder::getInstance()->buildOptions($options);
$request = json_encode(OnepayRequestBuilder::getInstance()->buildCreateRequest($shoppingCart, $options), JSON_UNESCAPED_SLASHES);
$request = json_encode(OnepayRequestBuilder::getInstance()->buildCreateRequest($shoppingCart, $channel, $options), JSON_UNESCAPED_SLASHES);
echo $request;
$path = self::TRANSACTION_BASE_PATH . self::SEND_TRANSACTION;
$httpResponse = json_decode($http->post(OnepayBase::getCurrentIntegrationTypeUrl(), $path ,$request), true);

Expand Down
26 changes: 25 additions & 1 deletion lib/TransactionCreateRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ class TransactionCreateRequest extends BaseRequest implements \JsonSerializable
private $items; # Array not null
private $callbackUrl; # String not null
private $channel; # String not null
private $appScheme;
private $signature; # String
private $generateOttQrCode = true;

function __construct($externalUniqueNumber, $total, $itemsQuantity, $issuedAt,
$items, $callbackUrl = null, $channel = 'WEB')
$items, $callbackUrl = null, $channel = 'WEB', $appScheme = null)
{
if (!$externalUniqueNumber) {
throw new \Exception('External unique number cannot be null.');
Expand Down Expand Up @@ -57,6 +58,11 @@ function __construct($externalUniqueNumber, $total, $itemsQuantity, $issuedAt,
throw new \Exception('channel cannot be null.');
}
$this->channel = $channel;

if (null == $appScheme) {
$appScheme = '';
}
$this->appScheme = $appScheme;
}

public function jsonSerialize()
Expand Down Expand Up @@ -166,6 +172,24 @@ public function getChannel($channel)
return $this->channel;
}

/**
* @return null|string
*/
public function getAppScheme()
{
return $this->appScheme;
}

/**
* @param null|string $appScheme
* @return TransactionCreateRequest
*/
public function setAppScheme($appScheme)
{
$this->appScheme = $appScheme;
return $this;
}


public function setSignature($signature)
{
Expand Down
13 changes: 11 additions & 2 deletions lib/utils/OnepayRequestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,16 @@ public static function getInstance()
return static::$instance;
}

public function buildCreateRequest($shoppingCart, $options = null)
public function buildCreateRequest($shoppingCart, $channel, $options = null)
{
if (null == OnepayBase::getCallBackUrl()) {
OnepayBase::setCallbackUrl(OnepayBase::DEFAULT_CALLBACK);
}

if (null == $channel) {
$channel = OnepayBase::DEFAULT_CHANNEL();
}

$options = self::buildOptions($options);
$issuedAt = time();
$externalUniqueNumber = (int)(microtime(true) * 1000);
Expand All @@ -33,7 +41,8 @@ public function buildCreateRequest($shoppingCart, $options = null)
$issuedAt,
$shoppingCart->getItems(),
OnepayBase::getCallBackUrl(),
'WEB'); # Channel, can be 'web' or 'mobile' for now
$channel,
OnepayBase::getAppScheme()); # Channel, can be 'WEB', 'MOBILE' or 'APP'

self::setKeys($request, $options);
return OnepaySignUtil::getInstance()->sign($request, $options->getSharedSecret());
Expand Down
103 changes: 103 additions & 0 deletions tests/TransactionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -341,4 +341,107 @@ public function testTransactionCommitRaisesWhenResponseSignatureIsNotValid()
$reflectedHttpClient->setAccessible(false);
}
}

public function testTransactionFailsWhenChannelMobileAndCallbackUrlNull() {

OnepayBase::setCallbackUrl(null);
// Create a mock http client that will return Null
$httpClientStub = $this->getMock(HttpClient::class, array('post'));
$httpClientStub->expects($this->any())->method('post')->willReturn(null);

// Alter the private static property of Transaction 'httpClient'
// to be the httpClientStub
$reflectedClass = new \ReflectionClass(Transaction::class);
$reflectedHttpClient = $reflectedClass->getProperty('httpClient');
$reflectedHttpClient->setAccessible(true);
$reflectedHttpClient->setValue($httpClientStub);

// Execute the transaction expecting it to raise TransactionCreateException
// because the mock will make the HttpClient return Null
$shoppingCart = ShoppingCartMocks::get();

// This should raise a TransactionCreateException
try {
$this->setExpectedException(TransactionCreateException::class, 'You need to set a valid callback if you want to use the MOBILE channel');
$response = Transaction::create($shoppingCart, null, ChannelEnum::MOBILE());
}
finally {
// Reset the HttpClient static property to its original state
$reflectedHttpClient->setValue(null);
$reflectedHttpClient->setAccessible(false);
}
}

public function testTransactionWhenChannelMobileAndCallbackUrlNotNull() {

OnepayBase::setCallbackUrl("http://some.callback.url");
$shoppingCart = new ShoppingCart();
$options = new Options("mUc0GxYGor6X8u-_oB3e-HWJulRG01WoC96-_tUA3Bg",
"P4DCPS55QB2QLT56SQH6#W#LV76IAPYX");
$firstItem = new Item("Zapatos", 1, 15000, null, -1);
$secondItem = new Item("Pantalon", 1, 12500, null, -1);

$shoppingCart->add($firstItem);
$shoppingCart->add($secondItem);

$this->assertEquals('Zapatos', $firstItem->getDescription());
$this->assertEquals('Pantalon', $secondItem->getDescription());

$response = Transaction::create($shoppingCart, null, ChannelEnum::MOBILE());

$this->assertEquals($response instanceof TransactionCreateResponse, true);
$this->assertEquals($response->getResponseCode(), "OK");
$this->assertEquals($response->getDescription(), "OK");
$this->assertNotNull($response->getQrCodeAsBase64());
}

public function testTransactionFailsWhenChannelAPPAndAppSchemeNull() {
// Create a mock http client that will return Null
$httpClientStub = $this->getMock(HttpClient::class, array('post'));
$httpClientStub->expects($this->any())->method('post')->willReturn(null);

// Alter the private static property of Transaction 'httpClient'
// to be the httpClientStub
$reflectedClass = new \ReflectionClass(Transaction::class);
$reflectedHttpClient = $reflectedClass->getProperty('httpClient');
$reflectedHttpClient->setAccessible(true);
$reflectedHttpClient->setValue($httpClientStub);

// Execute the transaction expecting it to raise TransactionCreateException
// because the mock will make the HttpClient return Null
$shoppingCart = ShoppingCartMocks::get();

// This should raise a TransactionCreateException
try {
$this->setExpectedException(TransactionCreateException::class, 'You need to set an appScheme if you want to use the APP channel');
$response = Transaction::create($shoppingCart, null, ChannelEnum::APP());
}
finally {
// Reset the HttpClient static property to its original state
$reflectedHttpClient->setValue(null);
$reflectedHttpClient->setAccessible(false);
}
}

public function testTransactionWhenChannelAPPAndAppSchemeNotNull() {
OnepayBase::setAppScheme('somescheme');
$shoppingCart = new ShoppingCart();
$options = new Options("mUc0GxYGor6X8u-_oB3e-HWJulRG01WoC96-_tUA3Bg",
"P4DCPS55QB2QLT56SQH6#W#LV76IAPYX");
$firstItem = new Item("Zapatos", 1, 15000, null, -1);
$secondItem = new Item("Pantalon", 1, 12500, null, -1);

$shoppingCart->add($firstItem);
$shoppingCart->add($secondItem);

$this->assertEquals('Zapatos', $firstItem->getDescription());
$this->assertEquals('Pantalon', $secondItem->getDescription());

$response = Transaction::create($shoppingCart, null, ChannelEnum::APP());

$this->assertEquals($response instanceof TransactionCreateResponse, true);
$this->assertEquals($response->getResponseCode(), "OK");
$this->assertEquals($response->getDescription(), "OK");
$this->assertNotNull($response->getQrCodeAsBase64());
}
}

0 comments on commit af5deee

Please sign in to comment.