From add1d901b9237cac88def6cb310bc72da39dab33 Mon Sep 17 00:00:00 2001 From: Honza Machala Date: Fri, 26 Aug 2016 07:06:17 +0200 Subject: [PATCH 1/5] Added posAuthKey for signature verification Added httpRequest to accepting notifications --- .env-default | 1 + src/Gateway.php | 22 +++++++++++++++------- src/GatewayFactory.php | 10 +++++++--- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.env-default b/.env-default index b021d44..5aa6b3d 100644 --- a/.env-default +++ b/.env-default @@ -1,3 +1,4 @@ POS_ID= SECOND_KEY= OAUTH_CLIENT_SECRET= +POS_AUTH_KEY= \ No newline at end of file diff --git a/src/Gateway.php b/src/Gateway.php index aac900a..fd7736c 100644 --- a/src/Gateway.php +++ b/src/Gateway.php @@ -82,6 +82,7 @@ public function getDefaultParameters() 'secondKey' => '', 'clientSecret' => '', 'testMode' => true, + 'posAuthKey' => null, ]; } @@ -109,6 +110,20 @@ public function setClientSecret($clientSecret) $this->setParameter('clientSecret', $clientSecret); } + /** + * @param string|null $posAuthKey + */ + public function setPosAuthKey($posAuthKey = null) { + $this->setParameter('posAuthKey', $posAuthKey); + } + + public function initialize(array $parameters = array()) + { + parent::initialize($parameters); + $this->setApiUrl($this->getApiUrl()); + return $this; + } + private function setApiUrl($apiUrl) { $this->setParameter('apiUrl', $apiUrl); @@ -118,11 +133,4 @@ private function setAccessToken($accessToken) { $this->setParameter('accessToken', $accessToken); } - - public function initialize(array $parameters = []) - { - parent::initialize($parameters); - $this->setApiUrl($this->getApiUrl()); - return $this; - } } \ No newline at end of file diff --git a/src/GatewayFactory.php b/src/GatewayFactory.php index f1626a0..dd29d09 100644 --- a/src/GatewayFactory.php +++ b/src/GatewayFactory.php @@ -3,6 +3,7 @@ namespace Omnipay\PayU; use Omnipay\Omnipay; +use Symfony\Component\HttpFoundation\Request; class GatewayFactory { @@ -11,17 +12,20 @@ class GatewayFactory * @param string $secondKey (MD5) * @param string $oAuthClientSecret (MD5) * @param bool $isSandbox + * @param string|null $posAuthKey + * @param Request|null $httpRequest * @return Gateway */ - public static function createInstance($posId, $secondKey, $oAuthClientSecret, $isSandbox = false) + public static function createInstance($posId, $secondKey, $oAuthClientSecret, $isSandbox = false, $posAuthKey = null, Request $httpRequest = null) { /** @var \Omnipay\PayU\Gateway $gateway */ - $gateway = Omnipay::create('PayU'); + $gateway = Omnipay::create('PayU', null, $httpRequest); $gateway->initialize([ 'posId' => $posId, 'secondKey' => $secondKey, 'clientSecret' => $oAuthClientSecret, - 'testMode' => $isSandbox + 'testMode' => $isSandbox, + 'posAuthKey' => $posAuthKey, ]); return $gateway; } From f03d24e1c94b64964b8d838bd20a9c6f2e12628c Mon Sep 17 00:00:00 2001 From: Honza Machala Date: Fri, 26 Aug 2016 10:51:11 +0200 Subject: [PATCH 2/5] Allow to write phpunit tests --- composer.json | 4 +++- phpunit.xml | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 phpunit.xml diff --git a/composer.json b/composer.json index a069850..30a486d 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "email": "jan.machala@bileto.com" } ], + "minimum-stability": "stable", "autoload": { "psr-4": { "Omnipay\\PayU\\" : "src/" } }, @@ -29,6 +30,7 @@ "omnipay/tests": "^2.1", "symfony/var-dumper": "^2.7", "mockery/mockery": "^0.9.5", - "vlucas/phpdotenv": "^2.3" + "vlucas/phpdotenv": "^2.3", + "phpunit/phpunit": "3.7.*" } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..52841d3 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,14 @@ + + + + tests + + + + + src + + + From f7bb2ff4aa458445b70c43f50579c42e244648d8 Mon Sep 17 00:00:00 2001 From: Honza Machala Date: Fri, 26 Aug 2016 10:51:47 +0200 Subject: [PATCH 3/5] More info from completePurchase --- examples/test-finish.php | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/test-finish.php b/examples/test-finish.php index c20a7c9..4e08549 100644 --- a/examples/test-finish.php +++ b/examples/test-finish.php @@ -22,6 +22,7 @@ echo "TransactionId: " . $response->getTransactionId() . PHP_EOL; echo "State code: " . $response->getCode() . PHP_EOL; echo "PaymentId: " , $response->getTransactionReference() . PHP_EOL; + echo "Data: " . var_export($response->getData(), true) . PHP_EOL; } catch (\Exception $e) { dump($e->getResponse()->getBody(true)); From bbc7d81507747735d215715cb2a1b24ca007de83 Mon Sep 17 00:00:00 2001 From: Honza Machala Date: Tue, 30 Aug 2016 14:28:29 +0200 Subject: [PATCH 4/5] Accept notifications from payment gateway --- composer.json | 3 +- examples/test-finish.php | 2 +- examples/test-notification.php | 42 ++++++ examples/test.php | 2 + src/Gateway.php | 6 + src/Messages/CompletePurchaseResponse.php | 2 +- src/Messages/Notification.php | 159 ++++++++++++++++++++++ 7 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 examples/test-notification.php create mode 100644 src/Messages/Notification.php diff --git a/composer.json b/composer.json index 30a486d..adec9aa 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ "psr-4": { "Omnipay\\PayU\\" : "src/" } }, "require": { - "omnipay/common": "^2.3" + "omnipay/common": "^2.3", + "openpayu/openpayu_php_sdk": "^2.2" }, "require-dev": { "omnipay/tests": "^2.1", diff --git a/examples/test-finish.php b/examples/test-finish.php index 4e08549..6118695 100644 --- a/examples/test-finish.php +++ b/examples/test-finish.php @@ -15,7 +15,7 @@ $gateway = GatewayFactory::createInstance($posId, $secondKey, $oAuthClientSecret, true); try { - $completeRequest = ['transactionId' => 'GD8J8WF8Z2160822GUEST000P01']; + $completeRequest = ['transactionId' => 'J9R4JP3F2G160825GUEST000P01']; $response = $gateway->completePurchase($completeRequest); echo "Is Successful: " . $response->isSuccessful() . PHP_EOL; diff --git a/examples/test-notification.php b/examples/test-notification.php new file mode 100644 index 0000000..cd174ba --- /dev/null +++ b/examples/test-notification.php @@ -0,0 +1,42 @@ +load(); + +// default is official sandbox +$posId = isset($_ENV['POS_ID']) ? $_ENV['POS_ID'] : '300046'; +$secondKey = isset($_ENV['SECOND_KEY']) ? $_ENV['SECOND_KEY'] : '0c017495773278c50c7b35434017b2ca'; +$oAuthClientSecret = isset($_ENV['OAUTH_CLIENT_SECRET']) ? $_ENV['OAUTH_CLIENT_SECRET'] : 'c8d4b7ac61758704f38ed5564d8c0ae0'; +$posAuthKey = isset($_ENV['POS_AUTH_KEY']) ? $_ENV['POS_AUTH_KEY'] : null; // Official sandbox does not provide signature key + +$content = '{"order":{"orderId":"NN18KW7XJG160830GUEST000P01","extOrderId":"57c56b16d22e1","orderCreateDate":"2016-08-30T13:16:39.641+02:00","notifyUrl":"http://52.58.177.216/online-payments/uuid/notify","customerIp":"80.188.133.34","merchantPosId":"300293","description":"Shopping at myStore.com","currencyCode":"PLN","totalAmount":"15000","status":"PENDING","products":[{"name":"Lenovo ThinkPad Edge E540","unitPrice":"15000","quantity":"1"}]},"localReceiptDateTime":"2016-08-30T13:17:14.502+02:00","properties":[{"name":"PAYMENT_ID","value":"72829425"}]}'; +$httpRequest = Request::create('/notify', 'POST', [], [], [], [], $content); +$httpRequest->headers->add( + [ + 'X-OpenPayU-Signature' => 'sender=checkout;signature=b640fa4baa73bb9e34f1cb8e5a5d4301;algorithm=MD5;content=DOCUMENT', + 'ContentType' => 'application/json' + ]); +$gateway = GatewayFactory::createInstance($posId, $secondKey, $oAuthClientSecret, true, $posAuthKey, $httpRequest); + +try { + if (!$gateway->supportsAcceptNotification()) { + echo "This Gateway does not support notifications"; + } + + $response = $gateway->acceptNotification(); + + echo "PaymentId: " , $response->getTransactionReference() . PHP_EOL; + echo "Message: " . $response->getMessage() . PHP_EOL; + echo "Status: " . $response->getTransactionStatus() . PHP_EOL; + echo "Data: " . var_export($response->getData(), true) . PHP_EOL; + +} catch (\Exception $e) { +// dump($e->getResponse()->getBody(true)); + dump((string)$e); +} \ No newline at end of file diff --git a/examples/test.php b/examples/test.php index 4243e36..1d12c12 100644 --- a/examples/test.php +++ b/examples/test.php @@ -18,12 +18,14 @@ try { $orderNo = uniqid(); $returnUrl = 'http://localhost:8000/gateway-return.php'; + $notifyUrl = 'http://127.0.0.1/online-payments/uuid/notify'; $description = 'Shopping at myStore.com'; $purchaseRequest = [ 'purchaseData' => [ 'customerIp' => '127.0.0.1', 'continueUrl' => $returnUrl, + 'notifyUrl' => $notifyUrl, 'merchantPosId' => $posId, 'description' => $description, 'currencyCode' => 'PLN', diff --git a/src/Gateway.php b/src/Gateway.php index fd7736c..c8b3e26 100644 --- a/src/Gateway.php +++ b/src/Gateway.php @@ -7,6 +7,7 @@ use Omnipay\PayU\Messages\AccessTokenResponse; use Omnipay\PayU\Messages\CompletePurchaseRequest; use Omnipay\PayU\Messages\CompletePurchaseResponse; +use Omnipay\PayU\Messages\Notification; use Omnipay\PayU\Messages\PurchaseRequest; use Omnipay\PayU\Messages\PurchaseResponse; @@ -63,6 +64,11 @@ public function completePurchase(array $parameters = array()) return $response; } + public function acceptNotification() + { + return new Notification($this->httpRequest, $this->httpClient, $this->getParameter('secondKey')); + } + /** * @return string */ diff --git a/src/Messages/CompletePurchaseResponse.php b/src/Messages/CompletePurchaseResponse.php index 42782b9..56aa2f9 100644 --- a/src/Messages/CompletePurchaseResponse.php +++ b/src/Messages/CompletePurchaseResponse.php @@ -19,7 +19,7 @@ public function isSuccessful() */ public function getTransactionId() { - return (string) $this->data['orders'][0]['orderId']; + return (string) $this->data['orders'][0]['extOrderId']; } /** diff --git a/src/Messages/Notification.php b/src/Messages/Notification.php new file mode 100644 index 0000000..6f72865 --- /dev/null +++ b/src/Messages/Notification.php @@ -0,0 +1,159 @@ +httpRequest = $httpRequest; + $this->httpClient = $httpClient; + $this->secondKey = $secondKey; + } + + /** + * { + * "order":{ + * "orderId":"LDLW5N7MF4140324GUEST000P01", + * "extOrderId":"Order id in your shop", + * "orderCreateDate":"2012-12-31T12:00:00", + * "notifyUrl":"http://tempuri.org/notify", + * "customerIp":"127.0.0.1", + * "merchantPosId":"{POS ID (pos_id)}", + * "description":"My order description", + * "currencyCode":"PLN", + * "totalAmount":"200", + * "buyer":{ + * "email":"john.doe@example.org", + * "phone":"111111111", + * "firstName":"John", + * "lastName":"Doe", + * "language":"en" + * }, + * "products":[ + * { + * "name":"Product 1", + * "unitPrice":"200", + * "quantity":"1" + * } + * ], + * "status":"COMPLETED" + * }, + * "localReceiptDateTime": "2016-03-02T12:58:14.828+01:00", + * "properties": [ + * { + * "name": "PAYMENT_ID", + * "value": "151471228" + * } + * ] + * }*/ + + /** + * Gateway Reference + * + * @return string A reference provided by the gateway to represent this transaction + */ + public function getTransactionReference() + { + if (isset($this->getData()->order->extOrderId)) { + return $this->getData()->order->extOrderId; + } + + return null; + } + + /** + * Get the raw data array for this message. The format of this varies from gateway to + * gateway, but will usually be either an associative array, or a SimpleXMLElement. + * @return mixed + * @throws InvalidRequestException + */ + public function getData() + { + if (!$this->cachedData) { + $content = trim($this->httpRequest->getContent()); + + $incomingSignature = $this->getSignature($this->httpRequest); + + $sign = OpenPayU_Util::parseSignature($incomingSignature); + + if (OpenPayU_Util::verifySignature($content, $sign->signature, $this->secondKey, $sign->algorithm)) { + $this->cachedData = json_decode($content); + } else { + throw new InvalidRequestException('Invalid signature - ' . $sign->signature); + } + } + + return $this->cachedData; + } + + /** + * @param Request $request + * @return string + * @throws InvalidRequestException + */ + private function getSignature(Request $request) + { + if ($request->headers->has(self::OPEN_PAY_U_SIGNATURE)) { + return $request->headers->get(self::OPEN_PAY_U_SIGNATURE); + } elseif ($request->headers->has(self::X_OPEN_PAY_U_SIGNATURE)) { + return $request->headers->get(self::X_OPEN_PAY_U_SIGNATURE); + } + throw new InvalidRequestException('There is no ' . self::OPEN_PAY_U_SIGNATURE . ' or ' . self::X_OPEN_PAY_U_SIGNATURE . ' header present in request'); + } + + /** + * Was the transaction successful? + * + * @return string Transaction status, one of {@see STATUS_COMPLETED}, {@see #STATUS_PENDING}, + * or {@see #STATUS_FAILED}. + */ + public function getTransactionStatus() + { + if ($this->getData()) { + $status = $this->getData()->order->status; + if (in_array($status, ['COMPLETED', 'CANCELED'], true)) { + return self::STATUS_COMPLETED; + } elseif ($status === 'PENDING') { + return self::STATUS_PENDING; + } + } + + return self::STATUS_FAILED; + } + + /** + * Response Message + * + * @return string A response message from the payment gateway + */ + public function getMessage() + { + return $this->getData(); + } +} From b084de66d15f4ea8e7a870a02a9e918d5d7b3527 Mon Sep 17 00:00:00 2001 From: Honza Machala Date: Wed, 31 Aug 2016 17:31:18 +0200 Subject: [PATCH 5/5] Added pending transactions flag --- examples/test-finish.php | 2 +- src/Messages/CompletePurchaseRequest.php | 2 +- src/Messages/CompletePurchaseResponse.php | 18 +++++++++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/examples/test-finish.php b/examples/test-finish.php index 6118695..28abe83 100644 --- a/examples/test-finish.php +++ b/examples/test-finish.php @@ -15,7 +15,7 @@ $gateway = GatewayFactory::createInstance($posId, $secondKey, $oAuthClientSecret, true); try { - $completeRequest = ['transactionId' => 'J9R4JP3F2G160825GUEST000P01']; + $completeRequest = ['transactionReference' => 'J9R4JP3F2G160825GUEST000P01']; $response = $gateway->completePurchase($completeRequest); echo "Is Successful: " . $response->isSuccessful() . PHP_EOL; diff --git a/src/Messages/CompletePurchaseRequest.php b/src/Messages/CompletePurchaseRequest.php index 28e7a61..797059b 100644 --- a/src/Messages/CompletePurchaseRequest.php +++ b/src/Messages/CompletePurchaseRequest.php @@ -36,7 +36,7 @@ public function sendData($data) 'Content-Type' => 'application/json', 'Authorization' => $data['accessToken'] ]; - $url = $data['apiUrl'] . '/api/v2_1/orders/' . urlencode($this->getTransactionId()); + $url = $data['apiUrl'] . '/api/v2_1/orders/' . urlencode($this->getTransactionReference()); $httpRequest = $this->httpClient->get($url, $headers); $httpResponse = $httpRequest->send(); diff --git a/src/Messages/CompletePurchaseResponse.php b/src/Messages/CompletePurchaseResponse.php index 56aa2f9..85ffbe5 100644 --- a/src/Messages/CompletePurchaseResponse.php +++ b/src/Messages/CompletePurchaseResponse.php @@ -11,15 +11,23 @@ class CompletePurchaseResponse extends AbstractResponse */ public function isSuccessful() { - return 'SUCCESS' === $this->data['status']['statusCode'] ? true : false; + return 'SUCCESS' === $this->data['status']['statusCode']; } /** - * @return string + * @return string|null */ public function getTransactionId() { - return (string) $this->data['orders'][0]['extOrderId']; + if (isset($this->data['orders'][0]['extOrderId'])) { + return (string) $this->data['orders'][0]['extOrderId']; + } + return null; + } + + public function isCancelled() + { + return 'CANCELED' === $this->data['status']['statusCode']; } /** @@ -51,4 +59,8 @@ public function getCode() return $this->data['orders'][0]['status']; } + public function isPending() + { + return 'PENDING' === $this->data['status']['statusCode']; + } } \ No newline at end of file