diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f21777..e41029a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
# CHANGELOG
+## 1.1.0 (2014-07-24)
+* add Shopping Cart Protocol 3.0 http://xsolla.github.io/en/shopingcart3.html
+
## 1.0.4 (2014-06-04)
* fix incorrect sign code for Shopping Cart Protocol 2.0
* add `$reasonCode` and `$reasonDescription` optional arguments to `PaymentStorageInterface::cancel`
diff --git a/example/ipn_shopping_cart_3.php b/example/ipn_shopping_cart_3.php
new file mode 100644
index 0000000..9f040a8
--- /dev/null
+++ b/example/ipn_shopping_cart_3.php
@@ -0,0 +1,57 @@
+getV1();
+ }
+
+ public function getAdditionalUserFields(User $user)
+ {
+ return array();
+ }
+}
+
+$userStorage = new UsersDemoStorage;
+$paymentStorage = new PaymentShoppingCart3DemoStorage;
+
+$demoProject = new Project(
+ '4783',//demo project id
+ 'key'//demo project secret key
+);
+$protocolBuilder = new ProtocolFactory($demoProject);
+$protocol = $protocolBuilder->getShoppingCart3Protocol($userStorage, $paymentStorage);
+
+$request = Request::createFromGlobals();
+$response = $protocol->run($request);
+$response->send();
diff --git a/resources/mysql/shopping_cart3.sql b/resources/mysql/shopping_cart3.sql
new file mode 100644
index 0000000..a2d5fd8
--- /dev/null
+++ b/resources/mysql/shopping_cart3.sql
@@ -0,0 +1,25 @@
+CREATE TABLE `xsolla_standard_user` (
+ `v1` VARCHAR(255) NOT NULL,
+ `v2` VARCHAR(200) NULL DEFAULT NULL,
+ `v3` VARCHAR(100) NULL DEFAULT NULL,
+ PRIMARY KEY (`v1`))
+ ENGINE = InnoDB
+ DEFAULT CHARACTER SET = utf8
+ COLLATE = utf8_general_ci;
+
+CREATE TABLE `xsolla_shoppingcart3_invoice` (
+ `id` INT NOT NULL AUTO_INCREMENT,
+ `v1` VARCHAR(255) NOT NULL,
+ `id_xsolla` INT UNSIGNED NOT NULL,
+ `timestamp_ipn` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `timestamp_xsolla_ipn` TIMESTAMP NOT NULL,
+ `payment_amount` DECIMAL(12,2) UNSIGNED NOT NULL,
+ `payment_currency` CHAR(3) NOT NULL,
+ `is_dry_run` TINYINT(1) NOT NULL,
+ `is_canceled` TINYINT(1) NOT NULL DEFAULT 0,
+ `timestamp_canceled` TIMESTAMP NULL DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE INDEX `id_xsolla_UNIQUE` (`id_xsolla` ASC))
+ ENGINE = InnoDB
+ DEFAULT CHARACTER SET = utf8
+ COLLATE = utf8_general_ci;
diff --git a/src/Invoice.php b/src/Invoice.php
index 4116cec..e2dc68b 100644
--- a/src/Invoice.php
+++ b/src/Invoice.php
@@ -8,13 +8,23 @@ class Invoice
protected $amount;
protected $currency;
protected $id;
+ protected $amountShoppingCart3;
+ protected $currencyShoppingCart3;
- public function __construct($virtualCurrencyAmount = null, $amount = null, $currency = null, $id = null)
- {
+ public function __construct(
+ $virtualCurrencyAmount = null,
+ $amount = null,
+ $currency = null,
+ $id = null,
+ $amountShoppingCart3 = null,
+ $currencyShoppingCart3 = null
+ ) {
$this->amount = $amount;
$this->virtualCurrencyAmount = $virtualCurrencyAmount;
$this->currency = $currency;
$this->id = $id;
+ $this->amountShoppingCart3 = $amountShoppingCart3;
+ $this->currencyShoppingCart3 = $currencyShoppingCart3;
}
public function getVirtualCurrencyAmount()
@@ -64,4 +74,27 @@ public function setAmount($sum)
return $this;
}
+
+ public function setAmountShoppingCart3($amountShoppingCart3)
+ {
+ $this->amountShoppingCart3 = $amountShoppingCart3;
+ return $this;
+ }
+
+ public function getAmountShoppingCart3()
+ {
+ return $this->amountShoppingCart3;
+ }
+
+ public function setCurrencyShoppingCart3($currencyShoppingCart3)
+ {
+ $this->currencyShoppingCart3 = $currencyShoppingCart3;
+ return $this;
+ }
+
+ public function getCurrencyShoppingCart3()
+ {
+ return $this->currencyShoppingCart3;
+ }
+
}
diff --git a/src/PaymentPage/UrlBuilder.php b/src/PaymentPage/UrlBuilder.php
index 8b3434b..f551c39 100644
--- a/src/PaymentPage/UrlBuilder.php
+++ b/src/PaymentPage/UrlBuilder.php
@@ -70,7 +70,8 @@ public function setInvoice(Invoice $invoice, $lockForUser = true, $hideFromUser
$this->setParameter('out', $invoice->getVirtualCurrencyAmount(), $lockForUser, $hideFromUser);
$this->setParameter('currency', $invoice->getCurrency(), $lockForUser, $hideFromUser);
$this->setParameter('sum', $invoice->getAmount(), $lockForUser, $hideFromUser);
-
+ $this->setParameter('payment_amount', $invoice->getAmountShoppingCart3(), $lockForUser, $hideFromUser);
+ $this->setParameter('payment_currency', $invoice->getCurrencyShoppingCart3(), $lockForUser, $hideFromUser);
return $this;
}
diff --git a/src/Protocol/Command/Cancel.php b/src/Protocol/Command/Cancel.php
index f766a89..137e3a1 100644
--- a/src/Protocol/Command/Cancel.php
+++ b/src/Protocol/Command/Cancel.php
@@ -40,7 +40,7 @@ public function process(Request $request)
public function checkSign(Request $request)
{
- return $this->generateSign($request, array('command', 'id')) == $request->query->get('md5');
+ return $this->generateSign($request, array('command', 'id')) === $request->query->get('md5');
}
public function getRequiredParams()
diff --git a/src/Protocol/Command/Check.php b/src/Protocol/Command/Check.php
index b90dcb7..48a1e9e 100644
--- a/src/Protocol/Command/Check.php
+++ b/src/Protocol/Command/Check.php
@@ -41,7 +41,7 @@ public function process(Request $request)
public function checkSign(Request $request)
{
- return $this->generateSign($request, array('command', 'v1')) == $request->query->get('md5');
+ return $this->generateSign($request, array('command', 'v1')) === $request->query->get('md5');
}
public function getRequiredParams()
diff --git a/src/Protocol/Command/CheckShoppingCart3.php b/src/Protocol/Command/CheckShoppingCart3.php
new file mode 100644
index 0000000..c69ca56
--- /dev/null
+++ b/src/Protocol/Command/CheckShoppingCart3.php
@@ -0,0 +1,19 @@
+userStorage = $protocol->getUserStorage();
+ $this->project = $protocol->getProject();
+ }
+
+ public function checkSign(Request $request)
+ {
+ return $this->generateSign($request, array('command', 'v1', 'foreignInvoice')) === $request->query->get('md5');
+ }
+}
\ No newline at end of file
diff --git a/src/Protocol/Command/PayShoppingCart.php b/src/Protocol/Command/PayShoppingCart.php
index 49b348e..5ceb88a 100644
--- a/src/Protocol/Command/PayShoppingCart.php
+++ b/src/Protocol/Command/PayShoppingCart.php
@@ -57,7 +57,7 @@ public function checkSign(Request $request)
{
$actualSign = $this->generateSign($request, array('v1', 'amount', 'currency', 'id'));
- return $actualSign == $request->query->get('sign');
+ return $actualSign === $request->query->get('sign');
}
public function getCommentFieldName()
diff --git a/src/Protocol/Command/PayShoppingCart3.php b/src/Protocol/Command/PayShoppingCart3.php
new file mode 100644
index 0000000..720cfe5
--- /dev/null
+++ b/src/Protocol/Command/PayShoppingCart3.php
@@ -0,0 +1,62 @@
+userStorage = $protocol->getUserStorage();
+ $this->project = $protocol->getProject();
+ $this->paymentStorage = $protocol->getPaymentShoppingCart3Storage();
+ }
+
+ public function checkSign(Request $request)
+ {
+ return $this->generateSign($request, array('command', 'v1', 'foreignInvoice', 'id')) === $request->query->get('md5');
+ }
+
+ public function process(Request $request)
+ {
+ $user = $this->createUser($request);
+ if (!$this->userStorage->isUserExists($user)) {
+ return array(
+ 'result' => self::CODE_INVALID_ORDER_DETAILS,
+ self::COMMENT_FIELD_NAME => 'User not found'
+ );
+ }
+ $datetime = $this->getDateTimeXsolla('Y-m-d H:i:s', $request->query->get('date'));
+ $id = $this->paymentStorage->pay(
+ $request->query->get('id'),
+ $request->query->get('payment_amount'),
+ $request->query->get('payment_currency'),
+ $user,
+ $datetime,
+ (bool) $request->query->get('dry_run')
+ );
+ return array(
+ 'result' => self::CODE_SUCCESS,
+ self::COMMENT_FIELD_NAME => '',
+ 'id_shop' => $id
+ );
+ }
+
+ public function getRequiredParams()
+ {
+ return array('command', 'md5', 'id', 'v1', 'date', 'payment_amount', 'payment_currency');
+ }
+}
\ No newline at end of file
diff --git a/src/Protocol/Command/PayStandard.php b/src/Protocol/Command/PayStandard.php
index c398e20..59f4f25 100644
--- a/src/Protocol/Command/PayStandard.php
+++ b/src/Protocol/Command/PayStandard.php
@@ -58,6 +58,6 @@ public function process(Request $request)
public function checkSign(Request $request)
{
- return $this->generateSign($request, array('command', 'v1', 'id')) == $request->query->get('md5');
+ return $this->generateSign($request, array('command', 'v1', 'id')) === $request->query->get('md5');
}
}
diff --git a/src/Protocol/CommandFactory/ShoppingCart3Factory.php b/src/Protocol/CommandFactory/ShoppingCart3Factory.php
new file mode 100644
index 0000000..6f5da0d
--- /dev/null
+++ b/src/Protocol/CommandFactory/ShoppingCart3Factory.php
@@ -0,0 +1,36 @@
+getPaymentShoppingCart3Storage());
+ default:
+ throw new WrongCommandException(sprintf(
+ 'Wrong command: "%s". Available commands for Standard protocol are: "%s".',
+ $commandName,
+ join('", "', $protocol->getProtocolCommands())
+ ));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Protocol/ProtocolFactory.php b/src/Protocol/ProtocolFactory.php
index 6785265..65a824b 100644
--- a/src/Protocol/ProtocolFactory.php
+++ b/src/Protocol/ProtocolFactory.php
@@ -2,8 +2,10 @@
namespace Xsolla\SDK\Protocol;
+use Xsolla\SDK\Protocol\CommandFactory\ShoppingCart3Factory;
use Xsolla\SDK\Protocol\CommandFactory\ShoppingCartFactory;
use Xsolla\SDK\Protocol\CommandFactory\StandardFactory;
+use Xsolla\SDK\Protocol\Storage\PaymentShoppingCart3StorageInterface;
use Xsolla\SDK\Protocol\Storage\PaymentShoppingCartStorageInterface;
use Xsolla\SDK\Protocol\Storage\PaymentStandardStorageInterface;
use Xsolla\SDK\Protocol\Storage\UserStorageInterface;
@@ -20,6 +22,11 @@ public function __construct($project, $ipChecker = null)
$this->ipChecker = $ipChecker;
}
+ /**
+ * @param PaymentShoppingCartStorageInterface $paymentStorage
+ * @return ShoppingCart
+ * @see http://xsolla.github.io/en/cash.html
+ */
public function getShoppingCartProtocol(PaymentShoppingCartStorageInterface $paymentStorage)
{
return new ShoppingCart(
@@ -31,6 +38,12 @@ public function getShoppingCartProtocol(PaymentShoppingCartStorageInterface $pay
);
}
+ /**
+ * @param UserStorageInterface $userStorage
+ * @param PaymentStandardStorageInterface $paymentStorage
+ * @return Standard
+ * @see http://xsolla.github.io/en/currency.html
+ */
public function getStandardProtocol(UserStorageInterface $userStorage, PaymentStandardStorageInterface $paymentStorage)
{
return new Standard(
@@ -42,4 +55,22 @@ public function getStandardProtocol(UserStorageInterface $userStorage, PaymentSt
$this->ipChecker
);
}
+
+ /**
+ * @param UserStorageInterface $userStorage
+ * @param PaymentShoppingCart3StorageInterface $paymentStorage
+ * @return ShoppingCart3
+ * @see http://xsolla.github.io/en/shopingcart3.html
+ */
+ public function getShoppingCart3Protocol(UserStorageInterface $userStorage, PaymentShoppingCart3StorageInterface $paymentStorage)
+ {
+ return new ShoppingCart3(
+ $this->project,
+ new XmlResponseBuilder($this->enableVersionHeader),
+ new ShoppingCart3Factory(),
+ $userStorage,
+ $paymentStorage,
+ $this->ipChecker
+ );
+ }
}
diff --git a/src/Protocol/ShoppingCart3.php b/src/Protocol/ShoppingCart3.php
new file mode 100644
index 0000000..abe8283
--- /dev/null
+++ b/src/Protocol/ShoppingCart3.php
@@ -0,0 +1,83 @@
+commandFactory = $commandFactory;
+ $this->userStorage = $userStorage;
+ $this->paymentShoppingCart3Storage = $paymentShoppingCart3Storage;
+ }
+
+ /**
+ * @return array
+ */
+ public function getProtocolCommands()
+ {
+ return array('check', 'pay', 'cancel');
+ }
+
+ /**
+ * @return \Xsolla\SDK\Protocol\Storage\PaymentShoppingCart3StorageInterface
+ */
+ public function getPaymentShoppingCart3Storage()
+ {
+ return $this->paymentShoppingCart3Storage;
+ }
+
+ /**
+ * @return \Xsolla\SDK\Protocol\Storage\UserStorageInterface
+ */
+ public function getUserStorage()
+ {
+ return $this->userStorage;
+ }
+
+ /**
+ * @param $message
+ * @return array
+ */
+ public function getResponseForWrongCommand($message)
+ {
+ return array(
+ 'result' => StandardCommand::CODE_INVALID_REQUEST,
+ StandardCommand::COMMENT_FIELD_NAME => $message
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/Protocol/Storage/PaymentShoppingCart3StorageInterface.php b/src/Protocol/Storage/PaymentShoppingCart3StorageInterface.php
new file mode 100644
index 0000000..a909ecf
--- /dev/null
+++ b/src/Protocol/Storage/PaymentShoppingCart3StorageInterface.php
@@ -0,0 +1,19 @@
+insertPay($xsollaPaymentId, $paymentAmount, $paymentCurrency, $user, $date, $dryRun);
+ if ($id > 0) {
+ return $id;
+ }
+ return $this->selectPayId($xsollaPaymentId, $paymentAmount, $paymentCurrency, $user);
+ }
+
+ protected function insertPay($xsollaPaymentId, $paymentAmount, $paymentCurrency, User $user, \DateTime $date, $dryRun)
+ {
+ $insert = $this->pdo->prepare(
+ "INSERT INTO `xsolla_shoppingcart3_invoice`
+ (`v1`, `id_xsolla`, `payment_amount`, `payment_currency`, `timestamp_xsolla_ipn`, `is_dry_run`)
+ VALUES (:v1, :id_xsolla, :payment_amount, :payment_currency, :timestamp_xsolla_ipn, :is_dry_run)"
+ );
+ $insert->bindValue(':v1', $user->getV1());
+ $insert->bindValue(':id_xsolla', $xsollaPaymentId, \PDO::PARAM_INT);
+ $insert->bindValue(':payment_amount', $paymentAmount);
+ $insert->bindValue(':payment_currency', $paymentCurrency, \PDO::PARAM_STR);
+ $insert->bindValue(':timestamp_xsolla_ipn', $date->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
+ $insert->bindValue(':is_dry_run', $dryRun, \PDO::PARAM_BOOL);
+ $insert->execute();
+
+ return $this->pdo->lastInsertId();
+ }
+
+ protected function selectPayId($xsollaPaymentId, $paymentAmount, $paymentCurrency, User $user)
+ {
+ $select = $this->pdo->prepare(
+ "SELECT id, v1, `payment_amount`, `payment_currency`, timestamp_xsolla_ipn, is_dry_run
+ FROM `xsolla_standard_invoice`
+ WHERE id_xsolla = :id_xsolla
+ ");
+ $select->bindValue(':id_xsolla', $xsollaPaymentId, \PDO::PARAM_INT);
+ $select->execute();
+ $result = $select->fetch(\PDO::FETCH_ASSOC);
+ if ($result === false) {
+ throw new \Exception('Temporary error.');
+ }
+ $diffMessage = '';
+ if ($result['v1'] != $user->getV1()) {
+ $diffMessage .= sprintf(' and v1=%s (must be "%s")', $result['v1'], $user->getV1());
+ }
+ if ($result['payment_amount'] != $paymentAmount) {
+ $diffMessage .= sprintf(' and payment_amount=%0.2f (must be %0.2f)', $result['payment_amount'], $paymentAmount);
+ }
+ if ($result['payment_currency'] != $paymentCurrency) {
+ $diffMessage .= sprintf(' and payment_currency=%s (must be %s)', $result['payment_currency'], $paymentCurrency);
+ }
+ if ($diffMessage !== '') {
+ throw new UnprocessableRequestException(sprintf(
+ 'Found payment with xsollaPaymentId=%s%s.',
+ $xsollaPaymentId,
+ $diffMessage
+ ));
+ }
+
+ return $result['id'];
+ }
+
+ protected function getTable()
+ {
+ return self::TABLE;
+ }
+}
\ No newline at end of file
diff --git a/tests/PaymentPage/UrlBuilderTest.php b/tests/PaymentPage/UrlBuilderTest.php
index fe30916..3f24f58 100644
--- a/tests/PaymentPage/UrlBuilderTest.php
+++ b/tests/PaymentPage/UrlBuilderTest.php
@@ -40,6 +40,8 @@ class UrlBuilderTest extends \PHPUnit_Framework_TestCase
protected $fullUrl = 'https://secure.xsolla.com/paystation2/?local=EN&country=US&limit=14&v1=user_v1&v2=user_v2&v3=user_v3&email=email%40example.com&userip=user_userIp&phone=user_phone&out=1.11¤cy=EUR&sum=0.1&project=7096&hidden=limit&signparams=allowSubscription%2Ccurrency%2Cemail%2Cfastcheckout%2Cid_package%2Climit%2Cout%2Cphone%2Cproject%2Csignparams%2Csum%2Ctheme%2Cuserip%2Cv0%2Cv2%2Cv3&sign=a56aaed28a810577e075f4d665031d30';
+ protected $fullUrlShoppingCart3 = 'https://secure.xsolla.com/paystation2/?local=EN&country=US&limit=14&v1=user_v1&v2=user_v2&v3=user_v3&email=email%40example.com&userip=user_userIp&phone=user_phone&payment_amount=0.1&payment_currency=EUR&project=7096&hidden=limit&signparams=allowSubscription%2Ccurrency%2Cemail%2Cfastcheckout%2Cid_package%2Climit%2Cout%2Cpayment_amount%2Cpayment_currency%2Cphone%2Cproject%2Csignparams%2Ctheme%2Cuserip%2Cv0%2Cv2%2Cv3&sign=1d2513e200bc7c94cc949304029af0ad';
+
public function setUp()
{
$this->user = new User('user_v1');
@@ -117,4 +119,22 @@ public function testFluentInterface()
->getUrl();
$this->assertSame($this->fullUrl, $url);
}
+
+ public function testShoppingCart3()
+ {
+ $this->invoice->setAmountShoppingCart3(0.1);
+ $this->invoice->setCurrencyShoppingCart3('EUR');
+ $this->invoice->setVirtualCurrencyAmount(null);
+ $this->invoice->setCurrency(null);
+ $url = $this->urlBuilder->clear()
+ ->setLocale('EN')
+ ->setCountry('US')
+ ->setParameter('limit', 14, true, true)
+ ->setUser($this->user, true, false)
+ ->setInvoice($this->invoice, true, false)
+ ->unlockParameterForUser('v1')
+ ->lockParameterForUser('limit')
+ ->getUrl();
+ $this->assertSame($this->fullUrlShoppingCart3, $url);
+ }
}
diff --git a/tests/Protocol/ShoppingCart3ProtocolFullTest.php b/tests/Protocol/ShoppingCart3ProtocolFullTest.php
new file mode 100644
index 0000000..6b63df2
--- /dev/null
+++ b/tests/Protocol/ShoppingCart3ProtocolFullTest.php
@@ -0,0 +1,257 @@
+userStorageMock = $this->getMock('Xsolla\SDK\Protocol\Storage\UserStorageInterface', array(), array(), '', false);
+ $this->paymentStorageMock = $this->getMock('Xsolla\SDK\Protocol\Storage\PaymentShoppingCart3StorageInterface', array(), array(), '', false);
+ $this->addCancelHandler($this->paymentStorageMock);
+ $this->addPayHandler($this->paymentStorageMock);
+ $this->addCheckHandler($this->userStorageMock);
+ $this->protocol = $this->protocolBuilder->getShoppingCart3Protocol($this->userStorageMock, $this->paymentStorageMock);
+ }
+
+ /**
+ * @dataProvider checkProvider
+ */
+ public function testCheck(array $params, $expectedXml)
+ {
+ $this->protocolTest($params, $expectedXml);
+ }
+
+ public function checkProvider()
+ {
+ return array(
+ array(
+ array(
+ 'command' => 'check',
+ 'v1' => self::CHECK_V1_ANY_EXCEPTION,
+ 'md5' => '22a0964fb976608f25f2d55ee44c01d3'
+ ),
+ '' . PHP_EOL .
+ '1Any exception' . PHP_EOL
+ ),
+ array(
+ array(
+ 'command' => 'check',
+ 'v1' => self::CHECK_V1_NOT_EXISTS,
+ 'md5' => '7c7fce6806ae451419dede822033dd9e'
+ ),
+ '' . PHP_EOL .
+ '7' . PHP_EOL
+ ),
+ array(
+ array(
+ 'command' => 'check',
+ 'v1' => self::CHECK_V1_SUCCESS,
+ 'md5' => 'a3561b90df78828133eb285e36965419'
+ ),
+ '' . PHP_EOL .
+ '0value1value2' . PHP_EOL
+ ),
+ );
+ }
+
+ public function noCommandProvider()
+ {
+ return array(
+ array(
+ array(
+ 'id' => '100',
+ 'sign' => 'bc763ff5a8665c7c4c5fa0d8eba75ac8'
+ ),
+ '' . PHP_EOL .
+ '4' .
+ 'No command in request. Available commands are: "check", "pay", "cancel".' .
+ '' . PHP_EOL
+ )
+ );
+ }
+
+ public function wrongCommandProvider()
+ {
+ return array(
+ array(
+ array(
+ 'command' => 'not_exist',
+ ),
+ '' . PHP_EOL .
+ '4' .
+ 'Wrong command: "not_exist". Available commands for Standard protocol are: "check", "pay", "cancel".' .
+ '' . PHP_EOL
+ )
+ );
+ }
+
+ public function ipCheckerProvider()
+ {
+ return array(
+ array(
+ array(
+ 'command' => 'pay',
+ ),
+ '' . PHP_EOL .
+ '7' .
+ 'IP whitelist doesn\'t contain client IP address' .
+ '' . PHP_EOL
+ )
+ );
+ }
+
+ public function payProvider()
+ {
+ return array(
+ array(
+ array(
+ 'command' => 'pay'
+ ),
+ '' . PHP_EOL .
+ '4' .
+ 'Invalid request format. Missed parameters: md5, id, v1, date, payment_amount, payment_currency' .
+ '' . PHP_EOL
+ ),
+ array(
+ array(
+ 'command' => 'pay',
+ 'md5' => '11111ff5a8661171111111d8e1171111',
+ 'id' => self::PAY_ID_SUCCESS,
+ 'v1' => 'demo',
+ 'payment_amount' => '100.20',
+ 'payment_currency' => 'EUR',
+ 'date' => '2014-02-19 20:07:08'
+ ),
+ '' . PHP_EOL .
+ '3Invalid md5 signature' . PHP_EOL
+ ),
+ array(
+ array(
+ 'command' => 'pay',
+ 'md5' => '151783427f854e7c61e54fae5361cacb',
+ 'id' => self::PAY_ID_SUCCESS,
+ 'v1' => 'demo',
+ 'payment_amount' => '100.20',
+ 'payment_currency' => 'EUR',
+ 'date' => 'DATE'
+ ),
+ '' . PHP_EOL .
+ '4' .
+ 'Datetime string DATE could not be converted to DateTime object from format \'Y-m-d H:i:s\'' .
+ '' . PHP_EOL
+ ),
+ array(
+ array(
+ 'command' => 'pay',
+ 'md5' => 'b03894e48c0dfa6a9adda90739ca986c',
+ 'id' => self::PAY_ID_ANY_EXCEPTION,
+ 'v1' => 'demo',
+ 'payment_amount' => '100.20',
+ 'payment_currency' => 'EUR',
+ 'date' => '2014-02-19 20:07:08'
+ ),
+ '' . PHP_EOL .
+ '1Any exception' . PHP_EOL
+ ),
+ array(
+ array(
+ 'command' => 'pay',
+ 'md5' => '151783427f854e7c61e54fae5361cacb',
+ 'id' => self::PAY_ID_SUCCESS,
+ 'v1' => 'demo',
+ 'payment_amount' => '100.20',
+ 'payment_currency' => 'EUR',
+ 'date' => '2014-02-19 20:07:08'
+ ),
+ '' . PHP_EOL .
+ '0' . self::PAY_SHOP_ID . '' . PHP_EOL
+ ),
+ array(
+ array(
+ 'command' => 'pay',
+ 'md5' => '6d2228aaf135ea197aaaecf2d1260f13',
+ 'id' => self::ZERO,
+ 'v1' => 'demo',
+ 'payment_amount' => '100.20',
+ 'payment_currency' => 'EUR',
+ 'date' => '2014-02-19 20:07:08'
+ ),
+ '' . PHP_EOL .
+ '0' . self::PAY_SHOP_ID . '' . PHP_EOL
+ ),
+ array(
+ array(
+ 'command' => 'pay',
+ 'v1' => self::CHECK_V1_NOT_EXISTS,
+ 'md5' => '19d8988edfae51f90a1c78180d099a17',
+ 'id' => self::PAY_ID_SUCCESS,
+ 'payment_amount' => '100.20',
+ 'payment_currency' => 'EUR',
+ 'date' => '2014-02-19 20:07:08'
+ ),
+ '' . PHP_EOL .
+ '2User not found' . PHP_EOL
+ ),
+ );
+ }
+
+ public function addCheckHandler(UserStorageInterface $userStorageMock)
+ {
+ $userStorageMock->expects($this->any())
+ ->method('isUserExists')
+ ->with($this->isInstanceOf('Xsolla\SDK\User'))
+ ->will($this->returnCallback(array($this, 'isUserExistsCallback')));
+ $userStorageMock->expects($this->any())
+ ->method('getAdditionalUserFields')
+ ->with($this->isInstanceOf('Xsolla\SDK\User'))
+ ->will($this->returnValue(array(
+ 'parameter1' => 'value1',
+ 'parameter2' => 'value2',
+ )));
+ }
+
+ public function isUserExistsCallback(User $user)
+ {
+ switch ($user->getV1()) {
+ case self::CHECK_V1_ANY_EXCEPTION:
+ throw new \Exception('Any exception');
+ case self::CHECK_V1_NOT_EXISTS:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ public function addPayHandler(PaymentShoppingCart3StorageInterface $paymentStorageMock)
+ {
+ $paymentStorageMock->expects($this->any())
+ ->method('pay')
+ ->will($this->returnCallback(array($this, 'payCallback')));
+ }
+
+ public function payCallback($invoiceId)
+ {
+ if ($invoiceId == self::PAY_ID_ANY_EXCEPTION) {
+ throw new \Exception('Any exception');
+ } else {
+ return self::PAY_SHOP_ID;
+ }
+ }
+}
diff --git a/tests/Protocol/StandardProtocolFullTest.php b/tests/Protocol/StandardProtocolFullTest.php
index dc7408b..ddaef7c 100644
--- a/tests/Protocol/StandardProtocolFullTest.php
+++ b/tests/Protocol/StandardProtocolFullTest.php
@@ -191,6 +191,18 @@ public function payProvider()
'' . PHP_EOL .
'0' . self::PAY_SHOP_ID . '' . PHP_EOL
),
+ array(
+ array(
+ 'command' => 'pay',
+ 'v1' => self::CHECK_V1_NOT_EXISTS,
+ 'md5' => '19d8988edfae51f90a1c78180d099a17',
+ 'id' => self::PAY_ID_SUCCESS,
+ 'sum' => '100.20',
+ 'date' => '2014-02-19 20:07:08'
+ ),
+ '' . PHP_EOL .
+ '2User not found' . PHP_EOL
+ ),
);
}
diff --git a/tests/Protocol/Storage/Pdo/PaymentShoppingCart3StorageTest.php b/tests/Protocol/Storage/Pdo/PaymentShoppingCart3StorageTest.php
new file mode 100644
index 0000000..394e0d8
--- /dev/null
+++ b/tests/Protocol/Storage/Pdo/PaymentShoppingCart3StorageTest.php
@@ -0,0 +1,126 @@
+paymentStorage = new PaymentShoppingCart3Storage($this->pdoMock);
+ $this->datetimeObj = \DateTime::createFromFormat('Y-m-d H:i:s', '2013-03-25 18:48:22', $this->xsollaTimeZone);
+ }
+
+ protected function setUpPayPdoMock()
+ {
+ $this->pdoMock->expects($this->at(0))
+ ->method('prepare')
+ ->with($this->anything())
+ ->will($this->returnValue($this->insertMock));
+ }
+
+ protected function setUpPayInsertMock()
+ {
+ $this->insertMock->expects($this->exactly(6))
+ ->method('bindValue')
+ ->with($this->anything(), $this->anything());
+ $this->insertMock->expects($this->at(6))
+ ->method('execute');
+ }
+
+ public function testPaySuccess()
+ {
+ $this->setUpPayInsertMock();
+
+ $this->pdoMock->expects($this->at(0))
+ ->method('prepare')
+ ->with($this->anything())
+ ->will($this->returnValue($this->insertMock));
+ $this->pdoMock->expects($this->at(1))
+ ->method('lastInsertId')
+ ->will($this->returnValue(555));
+
+ $this->assertEquals(555, $this->paymentStorage->pay(10, 10, 'EUR', $this->userMock, $this->datetimeObj, false));
+ }
+
+ public function testPayExists()
+ {
+ $this->setUpSelectMock();
+ $this->selectMock->expects($this->once())
+ ->method('fetch')
+ ->with($this->equalTo(\PDO::FETCH_ASSOC))
+ ->will($this->returnValue(array('id' => self::INVOICE_ID, 'v1' => $this->userMock->getV1(), 'payment_amount' => self::INVOICE_AMOUNT, 'payment_currency' => self::INVOICE_CURRENCY)));
+
+ $this->setUpPayInsertMock();
+ $this->pdoMock->expects($this->at(1))
+ ->method('lastInsertId')
+ ->will($this->returnValue(0));
+
+ $this->setUpPayPdoMock();
+ $this->pdoMock->expects($this->at(2))
+ ->method('prepare')
+ ->with($this->anything())
+ ->will($this->returnValue($this->selectMock));
+
+ $this->assertEquals(self::INVOICE_ID, $this->paymentStorage->pay(self::INVOICE_ID, self::INVOICE_AMOUNT, self::INVOICE_CURRENCY, $this->userMock, $this->datetimeObj, false));
+ }
+
+ /**
+ * @dataProvider payErrorProvider
+ */
+ public function testPayError($result, $exceptionDesc)
+ {
+ $this->setUpSelectMock();
+ $this->selectMock->expects($this->once())
+ ->method('fetch')
+ ->with($this->equalTo(\PDO::FETCH_ASSOC))
+ ->will($this->returnValue($result));
+
+ $this->setUpPayInsertMock();
+
+ $this->setUpPayPdoMock();
+ $this->pdoMock->expects($this->at(1))
+ ->method('lastInsertId')
+ ->will($this->returnValue(0));
+ $this->pdoMock->expects($this->at(2))
+ ->method('prepare')
+ ->with($this->anything())
+ ->will($this->returnValue($this->selectMock));
+
+ $this->setExpectedException($exceptionDesc[0], $exceptionDesc[1]);
+ $this->paymentStorage->pay($result['id'], self::INVOICE_AMOUNT, self::INVOICE_CURRENCY, $this->userMock, $this->datetimeObj, false);
+ }
+
+ public function payErrorProvider()
+ {
+ return array(
+ array(
+ array('id' => self::INVOICE_ID, 'v1' => 'demo', 'payment_amount' => 0, 'payment_currency' => 'USD'),
+ array('Xsolla\SDK\Exception\UnprocessableRequestException', 'Found payment with xsollaPaymentId=101 and payment_amount=0.00 (must be 100.20) and payment_currency=USD (must be EUR).')
+ ),
+ array(
+ array('id' => self::INVOICE_ID, 'v1' => 'demo1', 'payment_amount' => 100.20, 'payment_currency' => 'EUR'),
+ array('Xsolla\SDK\Exception\UnprocessableRequestException', 'Found payment with xsollaPaymentId=101 and v1=demo1 (must be "demo").')
+ ),
+ array(
+ array('id' => self::INVOICE_ID, 'v1' => 'demo1', 'payment_amount' => 0, 'payment_currency' => 'EUR'),
+ array('Xsolla\SDK\Exception\UnprocessableRequestException', 'Found payment with xsollaPaymentId=101 and v1=demo1 (must be "demo") and payment_amount=0.00 (must be 100.20).')
+ ),
+ array(
+ false,
+ array('Exception', 'Temporary error.')
+ )
+ );
+ }
+}