diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3335885 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8a282a5..0d77fdb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ composer.lock composer.phar phpunit.xml +.idea diff --git a/src/Message/SecureXMLAbstractRequest.php b/src/Message/SecureXMLAbstractRequest.php index 42a60f4..d549642 100644 --- a/src/Message/SecureXMLAbstractRequest.php +++ b/src/Message/SecureXMLAbstractRequest.php @@ -27,6 +27,11 @@ abstract class SecureXMLAbstractRequest extends AbstractRequest */ protected $requiredFields = []; + /** + * @var array + */ + protected $apiVersion = 'xml-4.2'; + /** * Set the messageID on the request. * @@ -84,7 +89,7 @@ protected function getBaseXML() $messageInfo->messageID = $this->getMessageId(); $messageInfo->addChild('messageTimestamp', $this->generateTimestamp()); $messageInfo->addChild('timeoutValue', 60); - $messageInfo->addChild('apiVersion', 'xml-4.2'); + $messageInfo->addChild('apiVersion', $this->apiVersion); $merchantInfo = $xml->addChild('MerchantInfo'); $merchantInfo->addChild('merchantID', $this->getMerchantId()); @@ -114,7 +119,27 @@ protected function getBasePaymentXML() $transaction->addChild('txnSource', 23); // Must always be 23 for SecureXML. $transaction->addChild('amount', $this->getAmountInteger()); $transaction->addChild('currency', $this->getCurrency()); - $transaction->purchaseOrderNo = $this->getTransactionId(); + $transaction->addChild('purchaseOrderNo', $this->getTransactionId()); + + return $xml; + } + + /** + * XML template of a SecurePayMessage Payment using a stored card (Payor ID). + * + * @return \SimpleXMLElement SecurePayMessage with transaction details. + */ + protected function getBaseStoredCardXML($actionType) + { + $xml = $this->getBaseXML(); + + $periodic= $xml->addChild('Periodic'); + $periodicList = $periodic->addChild('PeriodicList'); + $periodicList->addAttribute('count', 1); + $periodicItem = $periodicList->addChild('PeriodicItem'); + $periodicItem->addAttribute('ID', 1); // One transaction per request supported by current API. + $periodicItem->addChild('actionType', $actionType); + $periodicItem->addChild('clientID', $this->getCardReference()); return $xml; } diff --git a/src/Message/SecureXMLDeleteCardRequest.php b/src/Message/SecureXMLDeleteCardRequest.php new file mode 100644 index 0000000..7d06810 --- /dev/null +++ b/src/Message/SecureXMLDeleteCardRequest.php @@ -0,0 +1,40 @@ +getBaseStoredCardXML('delete'); + } +} diff --git a/src/Message/SecureXMLEditCardRequest.php b/src/Message/SecureXMLEditCardRequest.php new file mode 100644 index 0000000..dc6cf4c --- /dev/null +++ b/src/Message/SecureXMLEditCardRequest.php @@ -0,0 +1,48 @@ +getBaseStoredCardXML('edit'); + + $this->getCard()->validate(); + $card = $xml->Periodic->PeriodicList->PeriodicItem->addChild('CreditCardInfo'); + $card->addChild('cardNumber', $this->getCard()->getNumber()); + $card->addChild('cvv', $this->getCard()->getCvv()); + $card->addChild('expiryDate', $this->getCard()->getExpiryDate('m/y')); + + return $xml; + } +} diff --git a/src/Message/SecureXMLResponse.php b/src/Message/SecureXMLResponse.php index 786525f..d08ae47 100644 --- a/src/Message/SecureXMLResponse.php +++ b/src/Message/SecureXMLResponse.php @@ -18,8 +18,13 @@ public function isSuccessful() { // As per appendix F, 000 means the message was processed correctly if ((string) $this->data->Status->statusCode !== '000' + && ((string) $this->data->Status->statusCode !== '0' + && (string) $this->data->Status->statusDescription !== 'Normal') || ($this->hasTransaction() - && (string) $this->data->Payment->TxnList->Txn->approved !== 'Yes')) { + && (string) $this->data->Payment->TxnList->Txn->approved !== 'Yes') + || ($this->hasPeriodic() + && (string) $this->data->Periodic->PeriodicList->PeriodicItem->successful !== 'yes' + && (string) $this->data->Periodic->PeriodicList->PeriodicItem->responseCode !== '00')) { return false; } @@ -39,6 +44,20 @@ protected function hasTransaction() return isset($this->data->Payment->TxnList->Txn); } + /** + * Determine if we have had periodic information returned (for storage and retrieval of Payor IDs).\ + * + * @return bool True if we have a periodic item. + */ + protected function hasPeriodic() + { + return (($this->data->RequestType == 'Periodic') + && isset($this->data->Periodic) + && isset($this->data->Periodic->PeriodicList) + && isset($this->data->Periodic->PeriodicList->PeriodicItem) + ); + } + /** * @link https://www.securepay.com.au/_uploads/files/SecurePay_Response_Codes.pdf * @@ -48,7 +67,9 @@ public function getCode() { return $this->hasTransaction() ? (string) $this->data->Payment->TxnList->Txn->responseCode - : (string) $this->data->Status->statusCode; + : ($this->hasPeriodic() + ? (string) $this->data->Periodic->PeriodicList->PeriodicItem->responseCode + : (string) $this->data->Status->statusCode); } /** @@ -59,7 +80,9 @@ public function getMessage() { return $this->hasTransaction() ? (string) $this->data->Payment->TxnList->Txn->responseText - : (string) $this->data->Status->statusDescription; + : ($this->hasPeriodic() + ? (string) $this->data->Periodic->PeriodicList->PeriodicItem->responseText + : (string) $this->data->Status->statusDescription); } /** @@ -69,7 +92,9 @@ public function getTransactionReference() { return $this->hasTransaction() ? (string) $this->data->Payment->TxnList->Txn->txnID - : null; + : ($this->hasPeriodic() + ? (string) $this->data->Periodic->PeriodicList->PeriodicItem->txnID + : null); } /** @@ -80,6 +105,8 @@ public function getSettlementDate() { return $this->hasTransaction() ? (string) $this->data->Payment->TxnList->Txn->settlementDate - : null; + : ($this->hasPeriodic() + ? (string) $this->data->Periodic->PeriodicList->PeriodicItem->settlementDate + : null); } } diff --git a/src/Message/SecureXMLStoreCardRequest.php b/src/Message/SecureXMLStoreCardRequest.php new file mode 100644 index 0000000..25d587b --- /dev/null +++ b/src/Message/SecureXMLStoreCardRequest.php @@ -0,0 +1,53 @@ +getBaseStoredCardXML('add'); + + $this->getCard()->validate(); + $periodicItem = $xml->Periodic->PeriodicList->PeriodicItem; + $periodicItem->addChild('amount', $this->getAmountInteger()); + $periodicItem->addChild('periodicType', 4); // Appendix A "Add a Payor ID" + $card = $periodicItem->addChild('CreditCardInfo'); + $card->addChild('cardNumber', $this->getCard()->getNumber()); + $card->addChild('cvv', $this->getCard()->getCvv()); + $card->addChild('expiryDate', $this->getCard()->getExpiryDate('m/y')); + $card->addChild('recurringFlag', 'no'); + + return $xml; + } +} diff --git a/src/Message/SecureXMLStoredCardPurchaseRequest.php b/src/Message/SecureXMLStoredCardPurchaseRequest.php new file mode 100644 index 0000000..234a52c --- /dev/null +++ b/src/Message/SecureXMLStoredCardPurchaseRequest.php @@ -0,0 +1,46 @@ +getBaseStoredCardXML('trigger'); + $periodicItem = $xml->Periodic->PeriodicList->PeriodicItem; + $periodicItem->addChild('transactionReference', $this->getTransactionId()); + $periodicItem->addChild('amount', $this->getAmountInteger()); + $periodicItem->addChild('periodicType', 1); // Appendix A "Add a Payor ID" + + return $xml; + } +} diff --git a/src/SecureXMLGateway.php b/src/SecureXMLGateway.php index e297bd2..8350e67 100644 --- a/src/SecureXMLGateway.php +++ b/src/SecureXMLGateway.php @@ -95,6 +95,9 @@ public function capture(array $parameters = array()) public function purchase(array $parameters = array()) { + if (array_key_exists('cardReference', $parameters)) { + return $this->createRequest('\Omnipay\SecurePay\Message\SecureXMLStoredCardPurchaseRequest', $parameters); + } return $this->createRequest('\Omnipay\SecurePay\Message\SecureXMLPurchaseRequest', $parameters); } @@ -107,4 +110,19 @@ public function echoTest(array $parameters = array()) { return $this->createRequest('\Omnipay\SecurePay\Message\SecureXMLEchoTestRequest', $parameters); } + + public function createCard(array $parameters = array()) + { + return $this->createRequest('\Omnipay\SecurePay\Message\SecureXMLStoreCardRequest', $parameters); + } + + public function deleteCard(array $parameters = array()) + { + return $this->createRequest('\Omnipay\SecurePay\Message\SecureXMLDeleteCardRequest', $parameters); + } + + public function updateCard(array $parameters = array()) + { + return $this->createRequest('\Omnipay\SecurePay\Message\SecureXMLEditCardRequest', $parameters); + } } diff --git a/tests/Message/SecureXMLStoredCardPurchaseRequestTest.php b/tests/Message/SecureXMLStoredCardPurchaseRequestTest.php new file mode 100644 index 0000000..3efddfd --- /dev/null +++ b/tests/Message/SecureXMLStoredCardPurchaseRequestTest.php @@ -0,0 +1,65 @@ +request = new SecureXMLStoredCardPurchaseRequest($this->getHttpClient(), $this->getHttpRequest()); + + $this->request->initialize([ + 'merchantId' => 'ABC0030', + 'transactionPassword' => 'abc123', + 'amount' => '12.00', + 'transactionId' => '1234', + 'cardReference' => 'CRN00110011', + ]); + } + + public function testSendSuccess() + { + $this->setMockHttpResponse('SecureXMLStoredCardPurchaseRequestSendSuccess.txt'); + $response = $this->request->send(); + $data = $response->getData(); + + $this->assertInstanceOf('Omnipay\\SecurePay\\Message\\SecureXMLResponse', $response);; + + $this->assertTrue($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertSame('3', (string) $data->Periodic->PeriodicList->PeriodicItem->txnType); //can't find a reference but seems txntype for stored cards is 3. + $this->assertSame('009729', $response->getTransactionReference()); + $this->assertSame('00', $response->getCode()); + $this->assertSame('Approved', $response->getMessage()); + } + + public function testSendFailure() + { + $this->setMockHttpResponse('SecureXMLStoredCardPurchaseRequestSendFailure.txt'); + $response = $this->request->send(); + + $this->assertInstanceOf('Omnipay\\SecurePay\\Message\\SecureXMLResponse', $response); + + $this->assertFalse($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNull($response->getTransactionReference()); + $this->assertSame('510', $response->getCode()); + $this->assertSame('Unable To Connect To Server', $response->getMessage()); + } + + public function testInsufficientFundsFailure() + { + $this->setMockHttpResponse('SecureXMLStoredCardPurchaseRequestInsufficientFundsFailure.txt'); + $response = $this->request->send(); + + $this->assertInstanceOf('Omnipay\\SecurePay\\Message\\SecureXMLResponse', $response); + + $this->assertFalse($response->isSuccessful()); + $this->assertFalse($response->isRedirect()); + $this->assertNull($response->getTransactionReference()); + $this->assertSame('51', $response->getCode()); + $this->assertSame('Insufficient Funds', $response->getMessage()); + } +} diff --git a/tests/Mock/SecureXMLAuthorizeRequestFailure.txt b/tests/Mock/SecureXMLAuthorizeRequestFailure.txt deleted file mode 120000 index 01a7157..0000000 --- a/tests/Mock/SecureXMLAuthorizeRequestFailure.txt +++ /dev/null @@ -1 +0,0 @@ -SecureXMLPurchaseRequestSendFailure.txt \ No newline at end of file diff --git a/tests/Mock/SecureXMLAuthorizeRequestFailure.txt b/tests/Mock/SecureXMLAuthorizeRequestFailure.txt new file mode 100644 index 0000000..73886dd --- /dev/null +++ b/tests/Mock/SecureXMLAuthorizeRequestFailure.txt @@ -0,0 +1,9 @@ +HTTP/1.1 200 OK +Date: Fri, 17 Apr 2015 02:05:53 GMT +Server: Apache +Content-Type: text/xml;charset=ISO-8859-1 +Content-Length: 929 +Connection: close + + +8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PaymentABC0001510Unable To Connect To Server diff --git a/tests/Mock/SecureXMLAuthorizeRequestInsufficientFundsFailure.txt b/tests/Mock/SecureXMLAuthorizeRequestInsufficientFundsFailure.txt deleted file mode 120000 index 4793d98..0000000 --- a/tests/Mock/SecureXMLAuthorizeRequestInsufficientFundsFailure.txt +++ /dev/null @@ -1 +0,0 @@ -SecureXMLPurchaseRequestInsufficientFundsFailure.txt \ No newline at end of file diff --git a/tests/Mock/SecureXMLAuthorizeRequestInsufficientFundsFailure.txt b/tests/Mock/SecureXMLAuthorizeRequestInsufficientFundsFailure.txt new file mode 100644 index 0000000..4911f40 --- /dev/null +++ b/tests/Mock/SecureXMLAuthorizeRequestInsufficientFundsFailure.txt @@ -0,0 +1,9 @@ +HTTP/1.1 200 OK +Date: Fri, 17 Apr 2015 02:05:53 GMT +Server: Apache +Content-Type: text/xml;charset=ISO-8859-1 +Content-Length: 929 +Connection: close + + +8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PaymentABC000151Insufficient Funds diff --git a/tests/Mock/SecureXMLAuthorizeRequestSuccess.txt b/tests/Mock/SecureXMLAuthorizeRequestSuccess.txt index 6d72cc4..f632c7a 100644 --- a/tests/Mock/SecureXMLAuthorizeRequestSuccess.txt +++ b/tests/Mock/SecureXMLAuthorizeRequestSuccess.txt @@ -1,11 +1,8 @@ -HTTP/1.1 100 Continue -Server: Microsoft-IIS/5.0 -Date: Mon, 19 Apr 2004 06:19:48 GMT HTTP/1.1 200 OK -Server: Microsoft-IIS/5.0 -Date: Mon, 19 Apr 2004 06:20:01 GMT +Date: Fri, 17 Apr 2015 02:05:53 GMT +Server: Apache Content-Type: text/xml;charset=ISO-8859-1 Content-Length: 929 +Connection: close - -8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PaymentABC0001000Normal1023200AUDtestYes00Approved20040419009729424242...24207/066Visa +8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PaymentABC0001000Normal1023200AUDtestYes00Approved20040419009729424242...24207/066Visa diff --git a/tests/Mock/SecureXMLEchoTestRequestSuccess.txt b/tests/Mock/SecureXMLEchoTestRequestSuccess.txt index 07a99b7..ebf68df 100644 --- a/tests/Mock/SecureXMLEchoTestRequestSuccess.txt +++ b/tests/Mock/SecureXMLEchoTestRequestSuccess.txt @@ -1,22 +1,8 @@ -HTTP/1.1 100 Continue -Server: Microsoft-IIS/5.0 -Date: Mon, 19 Apr 2004 06:19:48 GMT HTTP/1.1 200 OK -Server: Microsoft-IIS/5.0 -Date: Mon, 19 Apr 2004 06:20:01 GMT +Date: Fri, 17 Apr 2015 02:05:53 GMT +Server: Apache Content-Type: text/xml;charset=ISO-8859-1 Content-Length: 929 +Connection: close - - - -8af793f9af34bea0cf40f5fb79f383 -20042403095956732000+660 -xml-4.2 - -Echo - -000 -Normal - - +8af793f9af34bea0cf40f5fb79f38320042403095956732000+660xml-4.2Echo000Normal diff --git a/tests/Mock/SecureXMLPurchaseRequestInsufficientFundsFailure.txt b/tests/Mock/SecureXMLPurchaseRequestInsufficientFundsFailure.txt index 8e6fbf1..88101e1 100644 --- a/tests/Mock/SecureXMLPurchaseRequestInsufficientFundsFailure.txt +++ b/tests/Mock/SecureXMLPurchaseRequestInsufficientFundsFailure.txt @@ -1,11 +1,8 @@ -HTTP/1.1 100 Continue -Server: Microsoft-IIS/5.0 -Date: Mon, 19 Apr 2004 06:19:48 GMT HTTP/1.1 200 OK -Server: Microsoft-IIS/5.0 -Date: Mon, 19 Apr 2004 06:20:01 GMT +Date: Fri, 17 Apr 2015 02:05:53 GMT +Server: Apache Content-Type: text/xml;charset=ISO-8859-1 Content-Length: 929 +Connection: close - -8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PaymentABC000151Insufficient Funds +8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PaymentABC000151Insufficient Funds diff --git a/tests/Mock/SecureXMLPurchaseRequestSendFailure.txt b/tests/Mock/SecureXMLPurchaseRequestSendFailure.txt index 81bedb0..33a7aee 100644 --- a/tests/Mock/SecureXMLPurchaseRequestSendFailure.txt +++ b/tests/Mock/SecureXMLPurchaseRequestSendFailure.txt @@ -1,11 +1,8 @@ -HTTP/1.1 100 Continue -Server: Microsoft-IIS/5.0 -Date: Mon, 19 Apr 2004 06:19:48 GMT HTTP/1.1 200 OK -Server: Microsoft-IIS/5.0 -Date: Mon, 19 Apr 2004 06:20:01 GMT +Date: Fri, 17 Apr 2015 02:05:53 GMT +Server: Apache Content-Type: text/xml;charset=ISO-8859-1 Content-Length: 929 +Connection: close - -8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PaymentABC0001510Unable To Connect To Server +8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PaymentABC0001510Unable To Connect To Server diff --git a/tests/Mock/SecureXMLPurchaseRequestSendSuccess.txt b/tests/Mock/SecureXMLPurchaseRequestSendSuccess.txt index 0d4b8ff..956aa74 100644 --- a/tests/Mock/SecureXMLPurchaseRequestSendSuccess.txt +++ b/tests/Mock/SecureXMLPurchaseRequestSendSuccess.txt @@ -1,11 +1,8 @@ -HTTP/1.1 100 Continue -Server: Microsoft-IIS/5.0 -Date: Mon, 19 Apr 2004 06:19:48 GMT HTTP/1.1 200 OK -Server: Microsoft-IIS/5.0 -Date: Mon, 19 Apr 2004 06:20:01 GMT +Date: Fri, 17 Apr 2015 02:05:53 GMT +Server: Apache Content-Type: text/xml;charset=ISO-8859-1 Content-Length: 929 +Connection: close - -8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PaymentABC0001000Normal023200AUDtestorder123Yes00Approved20040419009729424242...24207/066Visa +8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PaymentABC0001000Normal023200AUDtestorder123Yes00Approved20040419009729424242...24207/066Visa diff --git a/tests/Mock/SecureXMLStoredCardPurchaseRequestInsufficientFundsFailure.txt b/tests/Mock/SecureXMLStoredCardPurchaseRequestInsufficientFundsFailure.txt new file mode 100644 index 0000000..e69dc49 --- /dev/null +++ b/tests/Mock/SecureXMLStoredCardPurchaseRequestInsufficientFundsFailure.txt @@ -0,0 +1,8 @@ +HTTP/1.1 200 OK +Date: Fri, 17 Apr 2015 02:05:53 GMT +Server: Apache +Content-Type: text/xml;charset=ISO-8859-1 +Content-Length: 929 +Connection: close + +8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PeriodicABC000151Insufficient Funds diff --git a/tests/Mock/SecureXMLStoredCardPurchaseRequestSendFailure.txt b/tests/Mock/SecureXMLStoredCardPurchaseRequestSendFailure.txt new file mode 100644 index 0000000..51b3bc9 --- /dev/null +++ b/tests/Mock/SecureXMLStoredCardPurchaseRequestSendFailure.txt @@ -0,0 +1,8 @@ +HTTP/1.1 200 OK +Date: Fri, 17 Apr 2015 02:05:53 GMT +Server: Apache +Content-Type: text/xml;charset=ISO-8859-1 +Content-Length: 929 +Connection: close + +8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PeriodicABC0001510Unable To Connect To Server diff --git a/tests/Mock/SecureXMLStoredCardPurchaseRequestSendSuccess.txt b/tests/Mock/SecureXMLStoredCardPurchaseRequestSendSuccess.txt new file mode 100644 index 0000000..6b32aaf --- /dev/null +++ b/tests/Mock/SecureXMLStoredCardPurchaseRequestSendSuccess.txt @@ -0,0 +1,8 @@ +HTTP/1.1 200 OK +Date: Fri, 17 Apr 2015 02:05:53 GMT +Server: Apache +Content-Type: text/xml;charset=ISO-8859-1 +Content-Length: 929 +Connection: close + +8af793f9af34bea0cf40f5fc011e0c20041904161959849000+600xml-4.2PeriodicABC0001000NormaltriggerCRN0011001100Approvedyes3200YesAUD009729testorder12320040419424242...24207/06no6Visa diff --git a/tests/SecureXMLGatewayTest.php b/tests/SecureXMLGatewayTest.php index 2305605..809830a 100644 --- a/tests/SecureXMLGatewayTest.php +++ b/tests/SecureXMLGatewayTest.php @@ -30,6 +30,15 @@ public function testPurchase() $this->assertSame('10.00', $request->getAmount()); } + public function testPurchaseUsingStoredCard() + { + $request = $this->gateway->purchase(['amount' => '10.00', 'cardReference' => 'CRN00110011']); + + $this->assertInstanceOf('\Omnipay\SecurePay\Message\SecureXMLStoredCardPurchaseRequest', $request); + $this->assertSame('10.00', $request->getAmount()); + $this->assertSame('CRN00110011', $request->getCardReference()); + } + public function testRefund() { $request = $this->gateway->refund(['amount' => '10.00', 'transactionId' => 'order12345']); @@ -38,4 +47,29 @@ public function testRefund() $this->assertSame('10.00', $request->getAmount()); $this->assertSame('order12345', $request->getTransactionId()); } + + public function testCreateCard() + { + $request = $this->gateway->createCard(['amount' => '0.01', 'cardReference' => 'CRN00110011']); + + $this->assertInstanceOf('\Omnipay\SecurePay\Message\SecureXMLStoreCardRequest', $request); + $this->assertSame('0.01', $request->getAmount()); + $this->assertSame('CRN00110011', $request->getCardReference()); + } + + public function testUpdateCard() + { + $request = $this->gateway->updateCard(['cardReference' => 'CRN00110011']); + + $this->assertInstanceOf('\Omnipay\SecurePay\Message\SecureXMLEditCardRequest', $request); + $this->assertSame('CRN00110011', $request->getCardReference()); + } + + public function testDeleteCard() + { + $request = $this->gateway->deleteCard(['cardReference' => 'CRN00110011']); + + $this->assertInstanceOf('\Omnipay\SecurePay\Message\SecureXMLDeleteCardRequest', $request); + $this->assertSame('CRN00110011', $request->getCardReference()); + } }