├── .gitignore
├── docs
└── authorizenet-default-payment-form.png
├── src
├── Message
│ ├── PurchaseRequest.php
│ ├── HostedPage
│ │ ├── PurchaseRequest.php
│ │ ├── Response.php
│ │ └── AuthorizeRequest.php
│ ├── VoidRequest.php
│ ├── Response.php
│ ├── FetchTransactionRequest.php
│ ├── FetchTransactionResponse.php
│ ├── RefundRequest.php
│ ├── CaptureRequest.php
│ ├── AuthorizeResponse.php
│ ├── AbstractResponse.php
│ ├── AbstractRequest.php
│ ├── AcceptNotification.php
│ └── AuthorizeRequest.php
├── Traits
│ ├── HasHostedPageGatewayParams.php
│ └── HasGatewayParams.php
├── AbstractGateway.php
├── ApiGateway.php
└── HostedPageGateway.php
├── tests
├── ApiGatewayTest.php
├── HostedPageGatewayTest.php
├── Mock
│ └── AcceptNotificationSuccess.txt
└── Message
│ ├── AcceptNotificationTest.php
│ └── AuthorizeRequestTest.php
├── phpunit.xml.dist
├── .travis.yml
├── LICENSE
├── composer.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.lock
3 | composer.phar
4 | phpunit.xml
5 | .idea
6 |
--------------------------------------------------------------------------------
/docs/authorizenet-default-payment-form.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/academe/omnipay-authorizenetapi/HEAD/docs/authorizenet-default-payment-form.png
--------------------------------------------------------------------------------
/src/Message/PurchaseRequest.php:
--------------------------------------------------------------------------------
1 | gateway = new ApiGateway(
14 | $this->getHttpClient(),
15 | $this->getHttpRequest()
16 | );
17 |
18 | $this->gateway->setAuthName('authName');
19 | $this->gateway->setTransactionKey('transactionKey');
20 | $this->gateway->setRefId('refId');
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Message/HostedPage/PurchaseRequest.php:
--------------------------------------------------------------------------------
1 | gateway = new ApiGateway(
14 | $this->getHttpClient(),
15 | $this->getHttpRequest()
16 | );
17 |
18 | $this->gateway->setAuthName('authName');
19 | $this->gateway->setTransactionKey('transactionKey');
20 | $this->gateway->setRefId('refId');
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Traits/HasHostedPageGatewayParams.php:
--------------------------------------------------------------------------------
1 | setParameter('cancelUrl', $value);
17 | }
18 |
19 | /**
20 | * Used only by the hosted payment page at this time.
21 | */
22 | public function setReturnUrl($value)
23 | {
24 | $this->setParameter('returnUrl', $value);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 | ./tests/
17 |
18 |
19 |
20 |
21 |
22 | src
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.0
5 | - 7.1
6 | - 7.2
7 | - 7.3
8 |
9 | # This triggers builds to run on the new TravisCI infrastructure.
10 | # See: http://docs.travis-ci.com/user/workers/container-based-infrastructure/
11 | sudo: false
12 |
13 | ## Cache composer
14 | cache:
15 | directories:
16 | - $HOME/.composer/cache
17 |
18 | env:
19 | global:
20 | - setup=basic
21 |
22 | matrix:
23 | include:
24 |
25 | install:
26 | - if [[ $setup = 'basic' ]]; then travis_retry composer install --prefer-dist --no-interaction; fi
27 | - if [[ $setup = 'lowest' ]]; then travis_retry composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable; fi
28 |
29 | script: vendor/bin/phpcs --standard=PSR2 src && vendor/bin/phpunit --coverage-text
30 |
--------------------------------------------------------------------------------
/tests/Mock/AcceptNotificationSuccess.txt:
--------------------------------------------------------------------------------
1 | HTTP/1.1 200 OK
2 | Date: Sat, 16 Feb 2013 06:39:41 GMT
3 | X-Opnet-Transaction-Trace: 2b1726b9-2b7a-49f7-a5c6-24cd2622000b-8580-2210
4 | Content-Type: application/json
5 | X-Anet-Signature: sha512=13151697A33C77CB102AAA060CD58FB2394696BAEA981047A31830A8A1853A4C8D454E696871A38A68B6940B8746FC096111334D584579F6792F568A622A0373
6 | Content-Length: 340
7 | Connection: close
8 | X-Accel-Internal: /internal-nginx-static-location
9 | X-Forwarded-For: 198.241.207.38
10 | X-Real-Ip: 198.241.207.38
11 | Host: omnipay.example.co.uk
12 |
13 | {"notificationId":"701bf27d-d46f-4c3b-82f2-066448e2901e","eventType":"net.authorize.payment.authorization.created","eventDate":"2019-01-31T14:38:42.6937313Z","webhookId":"e6b3764d-5677-4fb1-a929-2e25a02f3073","payload":{"responseCode":1,"authCode":"P1XHLC","avsResponse":"Y","authAmount":7.67,"entityName":"transaction","id":"60116007277"}}
14 |
--------------------------------------------------------------------------------
/src/Message/VoidRequest.php:
--------------------------------------------------------------------------------
1 | getTransactionReference();
20 |
21 | $transaction = new VoidTransaction($refTransId);
22 |
23 | return $transaction;
24 | }
25 |
26 | /**
27 | * Accept a transaction and sends it as a request.
28 | *
29 | * @param $data TransactionRequestInterface
30 | * @returns CaptureResponse
31 | */
32 | public function sendData($data)
33 | {
34 | $response_data = $this->sendTransaction($data);
35 |
36 | return new Response($this, $response_data);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Jason Judge
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "academe/omnipay-authorizenetapi",
3 | "type": "library",
4 | "description": "Authorize.Net payment gateway driver for the Omnipay 3.x payment processing library",
5 | "keywords": [
6 | "authorize.net",
7 | "gateway",
8 | "merchant",
9 | "omnipay",
10 | "pay",
11 | "payment"
12 | ],
13 | "homepage": "https://github.com/academe/omnipay-authorizenetapi",
14 | "license": "MIT",
15 | "authors": [
16 | {
17 | "name": "Jason Judge",
18 | "email": "jason@academe.co.uk"
19 | }
20 | ],
21 | "autoload": {
22 | "psr-4": {
23 | "Omnipay\\AuthorizeNetApi\\": "src/"
24 | }
25 | },
26 | "require": {
27 | "omnipay/common": "^3",
28 | "academe/authorizenet-objects": "~0.7",
29 | "symfony/property-access": "^3.2 || ^4.0"
30 | },
31 | "require-dev": {
32 | "omnipay/tests": "^3",
33 | "squizlabs/php_codesniffer": "^3"
34 | },
35 | "extra": {
36 | "branch-alias": {
37 | "dev-master": "3.0.x-dev"
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Message/Response.php:
--------------------------------------------------------------------------------
1 | getTransactionCode() ?: parent::getCode();
29 | }
30 |
31 | /**
32 | * Get the transaction message text if available, falling back
33 | * to the response envelope.
34 | */
35 | public function getMessage()
36 | {
37 | return $this->getTransactionMessage() ?: parent::getMessage();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Message/FetchTransactionRequest.php:
--------------------------------------------------------------------------------
1 | getAuth(),
20 | $this->getTransactionReference()
21 | );
22 |
23 | if ($this->getTransactionId()) {
24 | $request = $request->withRefId($this->getTransactionId());
25 | }
26 |
27 | return $request;
28 | }
29 |
30 | /**
31 | * Accept a transaction and sends it as a request.
32 | *
33 | * @param $data TransactionRequestInterface
34 | * @returns TransactionResponse
35 | */
36 | public function sendData($data)
37 | {
38 | // Send the request to the gateway.
39 | $response_data = $this->sendMessage($data);
40 |
41 | // We should be getting a transactino back.
42 | return new FetchTransactionResponse($this, $response_data);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Message/FetchTransactionResponse.php:
--------------------------------------------------------------------------------
1 | ).
30 | // So I don't trust the data type we get back, and we will play loose and
31 | // fast with implicit conversions here.
32 |
33 | return $this->getResponseCode() == TransactionResponseModel::RESPONSE_CODE_APPROVED;
34 | }
35 |
36 | /**
37 | * Get the transaction response code.
38 | * Expected values are one of TransactionResponseModel::RESPONSE_CODE_*
39 | */
40 | public function getResponseCode()
41 | {
42 | return $this->getValue($this->transactionIndex . '.responseCode');
43 | }
44 |
45 | /**
46 | * Tells us whether the transaction is pending or not.
47 | */
48 | public function isPending()
49 | {
50 | return $this->getResponseCode() == TransactionResponseModel::RESPONSE_CODE_PENDING;
51 | }
52 |
53 | public function getTransactionType()
54 | {
55 | return $this->getValue($this->transactionIndex . '.transactionType');
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Message/RefundRequest.php:
--------------------------------------------------------------------------------
1 | getCard();
19 |
20 | if ($card) {
21 | // A credit card has been supplied.
22 |
23 | if ($card->getNumber()) {
24 | $creditCard = new CreditCard(
25 | $card->getNumber(),
26 | // Either MMYY or MMYYYY will work.
27 | // (This will be overwritten with 'XXXX' for now)
28 | $card->getExpiryMonth() . $card->getExpiryYear()
29 | );
30 |
31 | $transaction = $transaction->withPayment($creditCard);
32 | }
33 | }
34 |
35 | // Instead of supplying the full credit card dtails, just
36 | // provide the lasy four digits of the card number.
37 |
38 | if ($this->getNumberLastFour()) {
39 | $creditCard = new CreditCard(
40 | $this->getNumberLastFour(),
41 | 'XXXX'
42 | );
43 |
44 | $transaction = $transaction->withPayment($creditCard);
45 | }
46 |
47 | return $transaction;
48 | }
49 |
50 | protected function createTransaction(AmountInterface $amount, $refTransId)
51 | {
52 | return new Refund($amount, $refTransId);
53 | }
54 |
55 | /**
56 | * The last four digits of the origonal credit card.
57 | */
58 | public function getNumberLastFour()
59 | {
60 | return $this->getParameter('numberLastFour');
61 | }
62 |
63 | public function setNumberLastFour($value)
64 | {
65 | return $this->setParameter('numberLastFour', $value);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/AbstractGateway.php:
--------------------------------------------------------------------------------
1 | null,
26 | // Required.
27 | // The access token assigned to this application.
28 | 'transactionKey' => null,
29 | // Optional.
30 | // Either mobileDeviceId or refId can be provided.
31 | 'mobileDeviceId' => null,
32 | 'refId' => null,
33 | // True to run against the sandbox.
34 | 'testMode' => false,
35 | // The shared key used to sign notifications.
36 | 'signatureKey' => null,
37 | // Set to disable the webhook signature assertions.
38 | 'disableWebhookSignature' => false,
39 | );
40 | }
41 |
42 | /**
43 | * The capture transaction.
44 | */
45 | public function capture(array $parameters = [])
46 | {
47 | return $this->createRequest(
48 | \Omnipay\AuthorizeNetApi\Message\CaptureRequest::class,
49 | $parameters
50 | );
51 | }
52 |
53 | /**
54 | * Fetch a transaction.
55 | */
56 | public function fetchTransaction(array $parameters = [])
57 | {
58 | return $this->createRequest(
59 | \Omnipay\AuthorizeNetApi\Message\FetchTransactionRequest::class,
60 | $parameters
61 | );
62 | }
63 |
64 | /**
65 | * Handle notifcation server requests (webhooks).
66 | */
67 | public function acceptNotification(array $parameters = [])
68 | {
69 | return $this->createRequest(
70 | \Omnipay\AuthorizeNetApi\Message\AcceptNotification::class,
71 | $parameters
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/ApiGateway.php:
--------------------------------------------------------------------------------
1 | createRequest(
34 | AuthorizeRequest::class,
35 | $parameters
36 | );
37 | }
38 |
39 | /**
40 | * The purchase transaction.
41 | */
42 | public function purchase(array $parameters = [])
43 | {
44 | return $this->createRequest(
45 | PurchaseRequest::class,
46 | $parameters
47 | );
48 | }
49 |
50 | /**
51 | * Void an authorized transaction.
52 | */
53 | public function void(array $parameters = [])
54 | {
55 | return $this->createRequest(
56 | VoidRequest::class,
57 | $parameters
58 | );
59 | }
60 |
61 | /**
62 | * Refund a captured transaction (before it is cleared).
63 | */
64 | public function refund(array $parameters = [])
65 | {
66 | return $this->createRequest(
67 | RefundRequest::class,
68 | $parameters
69 | );
70 | }
71 |
72 | /**
73 | * Fetch an existing transaction details.
74 | */
75 | public function fetchTransaction(array $parameters = [])
76 | {
77 | return $this->createRequest(
78 | FetchTransactionRequest::class,
79 | $parameters
80 | );
81 | }
82 |
83 | /**
84 | * Accept a notification.
85 | */
86 | public function acceptNotification(array $parameters = [])
87 | {
88 | return $this->createRequest(
89 | AcceptNotification::class,
90 | $parameters
91 | );
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Message/CaptureRequest.php:
--------------------------------------------------------------------------------
1 | getCurrency(),
21 | $this->getAmountInteger()
22 | );
23 |
24 | // Identify the original transaction being authorised.
25 | $refTransId = $this->getTransactionReference();
26 |
27 | $transaction = $this->createTransaction($amount, $refTransId);
28 |
29 | // The description and invoice number go into an Order object.
30 | if ($this->getInvoiceNumber() || $this->getDescription()) {
31 | $order = new Order(
32 | $this->getInvoiceNumber(),
33 | $this->getDescription()
34 | );
35 |
36 | $transaction = $transaction->withOrder($order);
37 | }
38 |
39 | $transaction = $transaction->with([
40 | 'terminalNumber' => $this->getTerminalNumber(),
41 | ]);
42 |
43 | return $transaction;
44 | }
45 |
46 | /**
47 | * Create a new instance of the transaction object.
48 | *
49 | * - PriorAuthCapture is used for transactions authorised through
50 | * the API, e.g. a credit card authorisation.
51 | * - CaptureOnly is used to capture amounts authorized through
52 | * other channels, such as a telephone order (MOTO).
53 | *
54 | * Only the first is supported at this time. Which gets used will
55 | * depend on what data is passed in.
56 | */
57 | protected function createTransaction(AmountInterface $amount, $refTransId)
58 | {
59 | return new PriorAuthCapture($amount, $refTransId);
60 | }
61 |
62 | /**
63 | * Accept a transaction and sends it as a request.
64 | *
65 | * @param $data TransactionRequestInterface
66 | * @returns CaptureResponse
67 | */
68 | public function sendData($data)
69 | {
70 | $response_data = $this->sendTransaction($data);
71 |
72 | return new Response($this, $response_data);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/HostedPageGateway.php:
--------------------------------------------------------------------------------
1 | createRequest(
37 | AuthorizeRequest::class,
38 | $parameters
39 | );
40 | }
41 |
42 | /**
43 | * The purchase transaction, through a hosted page.
44 | */
45 | public function purchase(array $parameters = array())
46 | {
47 | return $this->createRequest(
48 | PurchaseRequest::class,
49 | $parameters
50 | );
51 | }
52 |
53 | /**
54 | * Void an authorized transaction.
55 | */
56 | public function void(array $parameters = [])
57 | {
58 | return $this->createRequest(
59 | VoidRequest::class,
60 | $parameters
61 | );
62 | }
63 |
64 | /**
65 | * Refund a captured transaction (before it is cleared).
66 | */
67 | public function refund(array $parameters = [])
68 | {
69 | return $this->createRequest(
70 | RefundRequest::class,
71 | $parameters
72 | );
73 | }
74 |
75 | /**
76 | * Fetch an existing transaction details.
77 | */
78 | public function fetchTransaction(array $parameters = [])
79 | {
80 | return $this->createRequest(
81 | FetchTransactionRequest::class,
82 | $parameters
83 | );
84 | }
85 |
86 | /**
87 | * Accept a notification.
88 | */
89 | public function acceptNotification(array $parameters = [])
90 | {
91 | return $this->createRequest(
92 | AcceptNotification::class,
93 | $parameters
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Message/HostedPage/Response.php:
--------------------------------------------------------------------------------
1 | getTestMode()) {
34 | return $this->endpointHostedPageSandbox;
35 | } else {
36 | return $this->endpointHostedPageLive;
37 | }
38 | }
39 |
40 | /**
41 | * Not yet complete, as the hosted payment form still needs to be processed.
42 | */
43 | public function isSuccessful()
44 | {
45 | return false;
46 | }
47 |
48 | /**
49 | * @returns string The token used to invoke the remote form.
50 | */
51 | public function getToken()
52 | {
53 | return $this->getValue('token');
54 | }
55 |
56 | /**
57 | * @returns bool Is a redirect if the request is successful.
58 | */
59 | public function isRedirect()
60 | {
61 | return $this->responseIsSuccessful();
62 | }
63 |
64 | /**
65 | * @returns string The redirect method is a POST.
66 | */
67 | public function getRedirectMethod()
68 | {
69 | return 'POST';
70 | }
71 |
72 | /**
73 | * @returns array
74 | */
75 | public function getRedirectData()
76 | {
77 | return [
78 | 'token' => $this->getToken()
79 | ];
80 | }
81 |
82 | /**
83 | * @returns array
84 | */
85 | public function getRedirectUrl()
86 | {
87 | return $this->getEndpoint();
88 | }
89 |
90 | /**
91 | * Gets the test mode of the original request.
92 | *
93 | * @return boolean
94 | */
95 | public function getTestMode()
96 | {
97 | return $this->testMode;
98 | }
99 |
100 | /**
101 | * Sets the test mode of the response.
102 | *
103 | * @param boolean $value True for test mode on.
104 | * @return AbstractRequest
105 | */
106 | public function setTestMode($value)
107 | {
108 | return $this->testMode = $value;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Traits/HasGatewayParams.php:
--------------------------------------------------------------------------------
1 | setParameter('authName', $value);
23 | }
24 |
25 | public function getAuthName()
26 | {
27 | return $this->getParameter('authName');
28 | }
29 |
30 | /**
31 | * The mobile device ID.
32 | */
33 | public function setMobileDeviceId($value)
34 | {
35 | if ($value !== null && ! is_string($value)) {
36 | throw new InvalidRequestException('Mobile device ID must be a string.');
37 | }
38 |
39 | return $this->setParameter('mobileDeviceId', $value);
40 | }
41 |
42 | public function getMobileDeviceId()
43 | {
44 | return $this->getParameter('mobileDeviceId');
45 | }
46 |
47 | /**
48 | * The ref ID.
49 | */
50 | public function setRefId($value)
51 | {
52 | if ($value !== null && ! is_string($value)) {
53 | throw new InvalidRequestException('Ref ID must be a string.');
54 | }
55 |
56 | return $this->setParameter('refId', $value);
57 | }
58 |
59 | public function getRefId()
60 | {
61 | return $this->getParameter('refId');
62 | }
63 |
64 | /**
65 | * The application auth transaction key.
66 | */
67 | public function setTransactionKey($value)
68 | {
69 | if (! is_string($value)) {
70 | throw new InvalidRequestException('Transaction Key must be a string.');
71 | }
72 |
73 | return $this->setParameter('transactionKey', $value);
74 | }
75 |
76 | public function getTransactionKey()
77 | {
78 | return $this->getParameter('transactionKey');
79 | }
80 |
81 | /**
82 | * The shared signature key.used to sign notifications sent by the
83 | * webhooks in the X-Anet-Signature HTTP header.
84 | * Only needed when receiving a notification.
85 | * Optional; the signature hash will only be checked if the signature
86 | * is supplied.
87 | */
88 | public function setSignatureKey($value)
89 | {
90 | if ($value !== null && ! is_string($value)) {
91 | throw new InvalidRequestException('Signature Key must be a string.');
92 | }
93 |
94 | return $this->setParameter('signatureKey', $value);
95 | }
96 |
97 | public function getSignatureKey()
98 | {
99 | return $this->getParameter('signatureKey');
100 | }
101 |
102 | /**
103 | * @param mixed $value cast to boolean when referenced
104 | */
105 | public function setDisableWebhookSignature($value)
106 | {
107 | return $this->setParameter('disableWebhookSignature', $value);
108 | }
109 |
110 | public function getDisableWebhookSignature()
111 | {
112 | return $this->getParameter('disableWebhookSignature');
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/tests/Message/AcceptNotificationTest.php:
--------------------------------------------------------------------------------
1 | getHttpRequest();
22 |
23 | // Omnipay needs a way to quickly set these up.
24 | // 'AcceptNotificationSuccess.txt'
25 |
26 | $payload = '{"notificationId":"701bf27d-d46f-4c3b-82f2-066448e2901e","eventType":"net.authorize.payment.authorization.created","eventDate":"2019-01-31T14:38:42.6937313Z","webhookId":"e6b3764d-5677-4fb1-a929-2e25a02f3073","payload":{"responseCode":1,"authCode":"P1XHLC","avsResponse":"Y","authAmount":7.67,"entityName":"transaction","id":"60116007277"}}';
27 |
28 | // Should actually be a POST, but can't work out how to do that.
29 | // Send headers through server parameter.
30 |
31 | $httpRequest->initialize(
32 | [],
33 | [],
34 | [],
35 | [],
36 | [],
37 | [
38 | 'HTTP_X-Anet-Signature' => 'sha512=13151697A33C77CB102AAA060CD58FB2394696BAEA981047A31830A8A1853A4C8D454E696871A38A68B6940B8746FC096111334D584579F6792F568A622A0373',
39 | 'HTTP_Content-Type' => 'application/json',
40 | ],
41 | $payload
42 | );
43 | //var_dump((string)$httpRequest);
44 |
45 | $this->request = new AcceptNotification(
46 | $this->getHttpClient(),
47 | $httpRequest
48 | );
49 | //var_dump($this->request);
50 |
51 | $this->request->initialize([
52 | 'signatureKey' => '339E42F5D962293A925C244313E8C3546FD765AD202BFF3805F4C8459193E2AA1C106F476D6907C7A85714CCD69BB7BF184D17ECCDD546CED7EF69DB4C4AD723',
53 | ]);
54 | }
55 |
56 | public function testSuccess()
57 | {
58 | $this->assertSame('60116007277', $this->request->getTransactionReference());
59 |
60 | $this->assertSame('completed', $this->request->getTransactionStatus());
61 | $this->assertSame('', $this->request->getMessage());
62 |
63 | $this->assertSame('payment', $this->request->getEventTarget());
64 | $this->assertSame('authorization', $this->request->getEventSubTarget());
65 | $this->assertSame('created', $this->request->getEventAction());
66 |
67 | $this->assertTrue($this->request->isSignatureValid());
68 | $this->assertNull($this->request->assertSignature());
69 | }
70 |
71 | /**
72 | * @expectedException \Omnipay\Common\Exception\InvalidRequestException
73 | * @expectedExceptionMessage Invalid or missing signature
74 | */
75 | public function testInvalidSignature()
76 | {
77 | // Mix up the key.
78 | $this->request->setSignatureKey(str_shuffle($this->request->getSignatureKey()));
79 |
80 | $this->assertFalse($this->request->isSignatureValid());
81 | $this->request->assertSignature();
82 | }
83 |
84 | /**
85 | * Signature assertion can be suppressed - it still shows as
86 | * invalid if the application checks, but does not thrown an exception.
87 | */
88 | public function testInvalidSignatureSuppressed()
89 | {
90 | // Mix up the key.
91 | $this->request->setSignatureKey(str_shuffle($this->request->getSignatureKey()));
92 | $this->request->setDisableWebhookSignature(true);
93 |
94 | $this->assertFalse($this->request->isSignatureValid());
95 | $this->assertNull($this->request->assertSignature());
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/tests/Message/AuthorizeRequestTest.php:
--------------------------------------------------------------------------------
1 | gateway = new ApiGateway(
17 | $this->getHttpClient(),
18 | $this->getHttpRequest()
19 | );
20 |
21 | $this->gateway->setAuthName('authName');
22 | $this->gateway->setTransactionKey('transactionKey');
23 | $this->gateway->setRefId('refId');
24 | }
25 |
26 | public function testOpaqueData()
27 | {
28 | $opaqueDescriptor = 'COMMON.ACCEPT.INAPP.PAYMENT';
29 | $opaqueValue = str_shuffle(str_repeat('1234567890ABCDEFGHIJ', 10));
30 |
31 | $cardToken = $opaqueDescriptor . ':' . $opaqueValue;
32 |
33 | $request = $this->gateway->authorize([
34 | 'opaqueDataDescriptor' => $opaqueDescriptor,
35 | 'opaqueDataValue' => $opaqueValue,
36 | ]);
37 |
38 | $this->assertSame($cardToken, $request->getToken());
39 |
40 | $this->assertSame($opaqueDescriptor, $request->getOpaqueDataDescriptor());
41 | $this->assertSame($opaqueValue, $request->getOpaqueDataValue());
42 | }
43 |
44 | public function testCardToken()
45 | {
46 | $opaqueDescriptor = 'COMMON.ACCEPT.INAPP.PAYMENT';
47 | $opaqueValue = str_shuffle(str_repeat('1234567890ABCDEFGHIJ', 10));
48 |
49 | $cardToken = $opaqueDescriptor . ':' . $opaqueValue;
50 |
51 | $request = $this->gateway->authorize([
52 | 'token' => $cardToken,
53 | ]);
54 |
55 | $this->assertSame($cardToken, $request->getToken());
56 |
57 | $this->assertSame($opaqueDescriptor, $request->getOpaqueDataDescriptor());
58 | $this->assertSame($opaqueValue, $request->getOpaqueDataValue());
59 | }
60 |
61 | public function testCustomerData()
62 | {
63 | $request = $this->gateway->authorize([
64 | 'amount' => 1.23,
65 | 'customerId' => 'customerId',
66 | 'customerType' => 'individual',
67 | 'customerTaxId' => 'customerTaxId',
68 | 'customerDriversLicense' => 'customerDriversLicense',
69 | 'card' => new CreditCard([
70 | 'email' => 'email@example.com',
71 | ]),
72 | ]);
73 |
74 | // The request data will have a customer object with this data in.
75 |
76 | $this->assertArraySubset(
77 | [
78 | 'id' => 'customerId',
79 | 'type' => 'individual',
80 | 'email' => 'email@example.com',
81 | 'driversLicense' => 'customerDriversLicense',
82 | 'taxId' => 'customerTaxId',
83 | ],
84 | $request->getData()->getCustomer()->jsonSerialize()
85 | );
86 | }
87 |
88 | /**
89 | * If there is no address information, then don't send empty
90 | * address objects to the gateway; just suppress them.
91 | */
92 | public function testAddressSetNotSet()
93 | {
94 | $request = $this->gateway->authorize([
95 | 'amount' => 1.23,
96 | 'card' => new CreditCard([
97 | ]),
98 | ]);
99 |
100 | $this->assertNull($request->getData()->getBillTo());
101 | $this->assertNull($request->getData()->getShipTo());
102 |
103 | $request = $this->gateway->authorize([
104 | 'amount' => 1.23,
105 | 'card' => new CreditCard([
106 | 'billingAddress1' => 'Street Number',
107 | 'shippingCity' => 'City',
108 | ]),
109 | ]);
110 |
111 | $this->assertInstanceOf(
112 | NameAddress::class,
113 | $request->getData()->getBillTo()
114 | );
115 |
116 | $this->assertInstanceOf(
117 | NameAddress::class,
118 | $request->getData()->getShipTo()
119 | );
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/Message/AuthorizeResponse.php:
--------------------------------------------------------------------------------
1 | ).
34 | // So I don't trust the data type we get back, and we will play loose and
35 | // fast with implicit conversions here.
36 |
37 | return parent::isSuccessful()
38 | && $this->getResponseCode() == TransactionResponseModel::RESPONSE_CODE_APPROVED;
39 | }
40 |
41 | /**
42 | * Tells us whether the transaction is pending or not.
43 | */
44 | public function isPending()
45 | {
46 | return $this->responseIsSuccessful()
47 | && $this->getResponseCode() == TransactionResponseModel::RESPONSE_CODE_PENDING;
48 | }
49 |
50 | /**
51 | * Get the transaction response code.
52 | * Expected values are one of TransactionResponseModel::RESPONSE_CODE_*
53 | */
54 | public function getResponseCode()
55 | {
56 | return $this->getValue($this->transactionIndex . '.responseCode');
57 | }
58 |
59 | /**
60 | * Collection of transaction message objects, or null if there are none.
61 | *
62 | * @returns TransactionMessages
63 | */
64 | public function getTransactionMessages()
65 | {
66 | return $this->getValue($this->transactionIndex . '.transactionMessages')
67 | ?: new TransactionMessages();
68 | }
69 |
70 | /**
71 | * Collection of transaction errors, or null if none.
72 | *
73 | * @returns Errors
74 | */
75 | public function getTransactionErrors()
76 | {
77 | return $this->getValue($this->transactionIndex . '.errors')
78 | ?: new Errors();
79 | }
80 |
81 | /**
82 | * Return the message code from the transaction if available,
83 | * or the response envelope.
84 | */
85 | public function getCode()
86 | {
87 | return $this->getTransactionCode() ?: parent::getCode();
88 | }
89 |
90 | /**
91 | * Get the transaction message text if available, falling back
92 | * to the response envelope.
93 | */
94 | public function getMessage()
95 | {
96 | return $this->getTransactionMessage() ?: parent::getMessage();
97 | }
98 |
99 | /**
100 | * @return string Six characters.
101 | */
102 | public function getAuthCode()
103 | {
104 | return $this->getValue($this->transactionIndex . '.authCode');
105 | }
106 |
107 | /**
108 | * @returns string Single letter; one of TransactionResponse::AVS_RESULT_CODE_*
109 | */
110 | public function getAvsResultCode()
111 | {
112 | return $this->getValue($this->transactionIndex . '.avsResultCode');
113 | }
114 |
115 | /**
116 | * @returns string Single letter; one of TransactionResponse::CVV_RESULT_CODE_*
117 | */
118 | public function getCvvResultCode()
119 | {
120 | return $this->getValue($this->transactionIndex . '.cvvResultCode');
121 | }
122 |
123 | /**
124 | * @returns int One of TransactionResponse::CAVV_RESULT_CODE_*
125 | */
126 | public function getCavvResultCode()
127 | {
128 | return $this->getValue($this->transactionIndex . '.cavvResultCode');
129 | }
130 |
131 | /**
132 | * Related transactionReference
133 | * @returns string Reference to previous transaction
134 | */
135 | public function getRefTransID()
136 | {
137 | return $this->getValue($this->transactionIndex . '.refTransID');
138 | }
139 |
140 | /**
141 | * @returns string Transaction hash, upper case MD5.
142 | */
143 | public function getTransHash()
144 | {
145 | return $this->getValue($this->transactionIndex . '.transHash');
146 | }
147 |
148 | /**
149 | * @returns string The last four digits of either the card number
150 | * or bank account number used for the transaction in the format XXXX1234.
151 | */
152 | public function getAccountNumber()
153 | {
154 | return $this->getValue($this->transactionIndex . '.accountNumber');
155 | }
156 |
157 | /**
158 | * @returns string Either the credit card type or in the case of
159 | * eCheck, the value is eCheck.
160 | */
161 | public function getAccountType()
162 | {
163 | return $this->getValue($this->transactionIndex . '.accountType');
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/Message/AbstractResponse.php:
--------------------------------------------------------------------------------
1 | setParsedData(new Response($data));
42 | }
43 |
44 | /**
45 | * Get a value from the parsed data, based on a path.
46 | * e.g. 'object.arrayProperty[0].stringProperty'.
47 | * Returns null if the dependency path is broken at any point.
48 | * See http://symfony.com/doc/current/components/property_access.html
49 | */
50 | public function getValue($path)
51 | {
52 | $accessor = $this->getAccessor();
53 |
54 | // If the accessor has not already been set, then create the default
55 | // accessor now.
56 | if (empty($accessor)) {
57 | $accessor = PropertyAccess::createPropertyAccessorBuilder()
58 | ->enableMagicCall()
59 | ->disableExceptionOnInvalidIndex()
60 | ->getPropertyAccessor();
61 |
62 | $this->setAccessor($accessor);
63 | }
64 |
65 | try {
66 | // Get the property using its path.
67 | // If the path breaks at any point, an exception will be
68 | // thrown, but we just want to return a null.
69 |
70 | return $accessor->getValue($this->getParsedData(), $path);
71 | } catch (ExceptionInterface $e) {
72 | return null;
73 | }
74 | }
75 |
76 | /**
77 | * Set the property accessor helper.
78 | */
79 | public function setAccessor(PropertyAccessor $value)
80 | {
81 | $this->accessor = $value;
82 | }
83 |
84 | /**
85 | * Get the property accessor helper.
86 | */
87 | public function getAccessor()
88 | {
89 | return $this->accessor;
90 | }
91 |
92 | /**
93 | * Set the data parsed into a nested value object.
94 | */
95 | public function setParsedData(Response $value)
96 | {
97 | $this->parsedData = $value;
98 | }
99 |
100 | /**
101 | * Get the data parsed into a nested value object.
102 | */
103 | public function getParsedData()
104 | {
105 | return $this->parsedData;
106 | }
107 |
108 | /**
109 | * The merchant supplied ID.
110 | * Up to 20 characters.
111 | * aka transactionId
112 | */
113 | public function getRefId()
114 | {
115 | return $this->getValue('refId');
116 | }
117 |
118 | /**
119 | * The transactionId is returned only if sent in the request.
120 | */
121 | public function getTransactionId()
122 | {
123 | return $this->getRefId();
124 | }
125 |
126 | /**
127 | * Get the first top-level result code.
128 | * Note this will be unsuitable for most transactions, as the response can
129 | * be successful ("Ok") even while the transaction response is not.
130 | * This is the result code of the envelope that the transaction response
131 | * is returned in.
132 | * e.g. "Ok"
133 | */
134 | public function getResultCode()
135 | {
136 | return $this->getValue('resultCode');
137 | }
138 |
139 | /**
140 | * Get the first top-level message text.
141 | * e.g. "Successful."
142 | * e.g. "The transaction was unsuccessful."
143 | */
144 | public function getResponseMessage()
145 | {
146 | return $this->getValue('messages.first.text');
147 | }
148 |
149 | /**
150 | * Get the transaction message text from the response envelope.
151 | * Inheriting responses will normally refine this to look deeper into
152 | * the response body.
153 | */
154 | public function getMessage()
155 | {
156 | return $this->getResponseMessage();
157 | }
158 |
159 | /**
160 | * Get the first top-level (i.e. message wrapper) message code.
161 | * e.g. "I00001"
162 | * e.g. "E00027"
163 | */
164 | public function getResponseCode()
165 | {
166 | return $this->getValue('messages.first.code');
167 | }
168 |
169 | /**
170 | * Return the message code from the response envelope.
171 | * Inheriting responses will normally refine this to look deeper into
172 | * the response body.
173 | */
174 | public function getCode()
175 | {
176 | return $this->getResponseCode();
177 | }
178 |
179 | /**
180 | * Get all top-level (envelope) response message collection.
181 | */
182 | public function getResponseMessages()
183 | {
184 | return $this->getValue('messages');
185 | }
186 |
187 | /**
188 | * Tell us whether the response was successful overall.
189 | * This is just about the response as a whole; the response may
190 | * still represent a failed transaction.
191 | */
192 | public function responseIsSuccessful()
193 | {
194 | return $this->getResultCode() === Response::RESULT_CODE_OK;
195 | }
196 |
197 | public function isSuccessful()
198 | {
199 | return $this->getResultCode() === Response::RESULT_CODE_OK;
200 | }
201 |
202 | /**
203 | * Return the last four digits of the crddit card used, if availale.
204 | * @return string
205 | */
206 | public function getNumberLastFour()
207 | {
208 | return substr($this->getValue($this->transactionIndex . '.accountNumber'), -4, 4) ?: null;
209 | }
210 |
211 | /**
212 | * Return the text of the first error or message in the transaction response.
213 | */
214 | public function getTransactionMessage()
215 | {
216 | return $this->getValue($this->transactionIndex . '.errors.first.text')
217 | ?: $this->getValue($this->transactionIndex . '.transactionMessages.first.text');
218 | }
219 |
220 | /**
221 | * Return the code of the first error or message in the transaction response.
222 | */
223 | public function getTransactionCode()
224 | {
225 | return $this->getValue($this->transactionIndex . '.errors.first.code')
226 | ?: $this->getValue($this->transactionIndex . '.transactionMessages.first.code');
227 | }
228 |
229 | /**
230 | * ID created for the transaction by the remote gateway.
231 | */
232 | public function getTransactionReference()
233 | {
234 | return $this->getValue($this->transactionIndex . '.transId');
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/src/Message/AbstractRequest.php:
--------------------------------------------------------------------------------
1 | getAuthName(), $this->getTransactionKey());
34 | }
35 |
36 | /**
37 | * Return the relevant endpoint.
38 | */
39 | public function getEndpoint()
40 | {
41 | if ($this->getTestMode()) {
42 | return $this->endpointSandbox;
43 | } else {
44 | return $this->endpointLive;
45 | }
46 | }
47 |
48 | /**
49 | * Send a HTTP request to the gateway.
50 | *
51 | * @param array|\JsonSerializable $data The body data to send to the gateway
52 | * @return GuzzleHttp\Psr7\Response
53 | */
54 | protected function sendRequest($data, $method = 'POST')
55 | {
56 | $response = $this->httpClient->request(
57 | $method,
58 | $this->getEndpoint(),
59 | array(
60 | 'Content-Type' => 'application/json',
61 | ),
62 | json_encode($data)
63 | );
64 |
65 | return $response;
66 | }
67 |
68 | /**
69 | * Strip a Byte Order Mark (BOM) from the start of a string.
70 | *
71 | * @param string $string A string with a potential BOM prefix.
72 | * @return string The string with the BOM removed.
73 | */
74 | public function removeBOM($string)
75 | {
76 | return preg_replace('/^[\x00-\x1F\x80-\xFF]{1,3}/', '', $string);
77 | }
78 |
79 | /**
80 | * Send a transaction and return the decoded data.
81 | * Any movement of funds is normnally done by creating a transaction
82 | * to perform the action. Requests that involve profiles, fetching
83 | * information, won't involve transactions.
84 | *
85 | * @param TransactionRequestInterface $transaction The transaction object
86 | * @return array The decoded data returned by the gateway.
87 | */
88 | public function sendTransaction(TransactionRequestInterface $transaction)
89 | {
90 | // Wrap the transaction detail into a request.
91 | $request = $this->wrapTransaction($this->getAuth(), $transaction);
92 |
93 | // The merchant site ID.
94 | $request = $request->withRefId($this->getTransactionId());
95 |
96 | return $this->sendMessage($request);
97 | }
98 |
99 | /**
100 | * Send a messgae and return the resulting decoded response data.
101 | *
102 | * TODO: handle unexpected results and HTTP return codes.
103 | *
104 | * @param ApiAbstractRequest $message The hydrated request message
105 | * (from the academe/authorizenet-objects package)
106 | */
107 | protected function sendMessage(ApiAbstractRequest $message)
108 | {
109 | // Send the request to the gateway.
110 | $response = $this->sendRequest($message);
111 |
112 | // The caller will know what object to put this data into.
113 | $body = (string)($response->getBody());
114 |
115 | // The body will be JSON, but *may* have a Byte Order Mark (BOM) prefix.
116 | // Remove the BOM.
117 | $body = $this->removeBOM($body);
118 |
119 | // Now decode the JSON body.
120 | $data = json_decode($body, true);
121 |
122 | // Return a data response.
123 | return $data;
124 | }
125 |
126 | /**
127 | * Wrap the transaction detail into a full request for an action on
128 | * the transaction.
129 | */
130 | protected function wrapTransaction($auth, $transaction)
131 | {
132 | return new CreateTransaction($auth, $transaction);
133 | }
134 |
135 | /**
136 | * @param string Merchant-defined invoice number associated with the order.
137 | * @return $this
138 | */
139 | public function setInvoiceNumber($value)
140 | {
141 | return $this->setParameter('invoiceNumber', $value);
142 | }
143 |
144 | /**
145 | * @return string
146 | */
147 | public function getInvoiceNumber()
148 | {
149 | return $this->getParameter('invoiceNumber');
150 | }
151 |
152 | /**
153 | * @param string Merchant-defined invoice number associated with the order.
154 | * @return $this
155 | */
156 | public function setTerminalNumber($value)
157 | {
158 | return $this->setParameter('terminalNumber', $value);
159 | }
160 |
161 | /**
162 | * @return string
163 | */
164 | public function getTerminalNumber()
165 | {
166 | return $this->getParameter('terminalNumber');
167 | }
168 |
169 | /**
170 | * authenticationIndicator and authenticationValue are used as a pair.
171 | * @param string 3D Secure indicator.
172 | * @return $this
173 | */
174 | public function setAuthenticationIndicator($value)
175 | {
176 | return $this->setParameter('authenticationIndicator', $value);
177 | }
178 |
179 | /**
180 | * @return string
181 | */
182 | public function getAuthenticationIndicator()
183 | {
184 | return $this->getParameter('authenticationIndicator');
185 | }
186 |
187 | /**
188 | * authenticationIndicator and authenticationValue are used as a pair.
189 | * @param string 3D Secure value.
190 | * @return $this
191 | */
192 | public function setAuthenticationValue($value)
193 | {
194 | return $this->setParameter('authenticationValue', $value);
195 | }
196 |
197 | /**
198 | * @return string
199 | */
200 | public function getAuthenticationValue()
201 | {
202 | return $this->getParameter('authenticationValue');
203 | }
204 |
205 | /**
206 | * @param string customer ID.
207 | * @return $this
208 | */
209 | public function setCustomerId($value)
210 | {
211 | return $this->setParameter('customerId', $value);
212 | }
213 |
214 | /**
215 | * @return string
216 | */
217 | public function getCustomerId()
218 | {
219 | return $this->getParameter('customerId');
220 | }
221 |
222 | /**
223 | * Valid values are one of
224 | * \Academe\AuthorizeNet\Request\Model\Customer::CUSTOMER_TYPE_*
225 | * @param string customer type
226 | * @return $this
227 | */
228 | public function setCustomerType($value)
229 | {
230 | return $this->setParameter('customerType', $value);
231 | }
232 |
233 | /**
234 | * @return string
235 | */
236 | public function getCustomerType()
237 | {
238 | return $this->getParameter('customerType');
239 | }
240 |
241 | /**
242 | * @param string Customer Drivers License.
243 | * @return $this
244 | */
245 | public function setCustomerDriversLicense($value)
246 | {
247 | return $this->setParameter('customerDriversLicense', $value);
248 | }
249 |
250 | /**
251 | * @return string
252 | */
253 | public function getCustomerDriversLicense()
254 | {
255 | return $this->getParameter('customerDriversLicense');
256 | }
257 |
258 | /**
259 | * @param string Customer Tax ID.
260 | * @return $this
261 | */
262 | public function setCustomerTaxId($value)
263 | {
264 | return $this->setParameter('customerTaxId', $value);
265 | }
266 |
267 | /**
268 | * @return string
269 | */
270 | public function getCustomerTaxId()
271 | {
272 | return $this->getParameter('customerTaxId');
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/src/Message/AcceptNotification.php:
--------------------------------------------------------------------------------
1 | getContentType() === 'json') {
53 | $this->payload = (string)$httpRequest->getContent();
54 | }
55 |
56 | $this->data = json_decode($this->payload, true);
57 |
58 | $this->setParsedData(new Notification($this->data));
59 |
60 | // Save the signature for validating later.
61 | // It cannot be validated until this object is initialised with parameters.
62 |
63 | $this->signature = $httpRequest->headers->get(
64 | static::SIGNATURE_HEADER_NAME
65 | );
66 | }
67 |
68 | /**
69 | * Set the data parsed into a nested value object.
70 | */
71 | public function setParsedData(Notification $value)
72 | {
73 | $this->parsedData = $value;
74 | }
75 |
76 | /**
77 | * Get the data parsed into a nested value object.
78 | */
79 | public function getParsedData()
80 | {
81 | return $this->parsedData;
82 | }
83 |
84 | /**
85 | * Get the raw data array for this message.
86 | * The raw data is from the JSON payload.
87 | *
88 | * @return mixed
89 | */
90 | public function getData()
91 | {
92 | return $this->data;
93 | }
94 |
95 | /**
96 | * Gateway Reference
97 | *
98 | * @throws InvalidRequestException
99 | * @return string The gateway key for this transaction
100 | */
101 | public function getTransactionReference()
102 | {
103 | $this->assertSignature();
104 |
105 | if ($this->getEventTarget() === $this->getParsedData()::EVENT_TARGET_PAYMENT) {
106 | return $this->getPayload()->getTransId();
107 | }
108 | }
109 |
110 | /**
111 | * Was the transaction successful?
112 | *
113 | * @throws InvalidRequestException
114 | * @return string Transaction status, one of {@see STATUS_COMPLETED}, {@see #STATUS_PENDING},
115 | * or {@see #STATUS_FAILED}.
116 | */
117 | public function getTransactionStatus()
118 | {
119 | $this->assertSignature();
120 |
121 | $responseCode = $this->getResponseCode();
122 |
123 | if ($responseCode === TransactionResponse::RESPONSE_CODE_APPROVED) {
124 | return static::STATUS_COMPLETED;
125 | } elseif ($responseCode === TransactionResponse::RESPONSE_CODE_PENDING) {
126 | return static::STATUS_PENDIND;
127 | } elseif ($responseCode !== null) {
128 | return static::STATUS_FAILED;
129 | }
130 | }
131 |
132 | /**
133 | * Response Message
134 | *
135 | * @throws InvalidRequestException
136 | * @return string A response message from the payment gateway
137 | */
138 | public function getMessage()
139 | {
140 | $this->assertSignature();
141 |
142 | // There are actually no messages in the notifications.
143 |
144 | return '';
145 | }
146 |
147 | /**
148 | * There is nothing to send in order to response to this webhook.
149 | * The merchant site just needs to return a HTTP 200.
150 | *
151 | * @param mixed $data The data to send
152 | * @return ResponseInterface
153 | */
154 | public function sendData($data)
155 | {
156 | return $this;
157 | }
158 |
159 | /**
160 | * The main target of the notificaiton: payment or customer.
161 | */
162 | public function getEventTarget()
163 | {
164 | return $this->getParsedData()->getEventTarget();
165 | }
166 |
167 | /**
168 | * The sub-target of the notificaiton.
169 | */
170 | public function getEventSubtarget()
171 | {
172 | return $this->getParsedData()->getEventSubtarget();
173 | }
174 |
175 | /**
176 | * The action against the target of the notificaito.
177 | */
178 | public function getEventAction()
179 | {
180 | return $this->getParsedData()->getEventAction();
181 | }
182 |
183 | /**
184 | * The UUID identifying this specific notification.
185 | */
186 | public function getNotificationId()
187 | {
188 | return $this->getParsedData()->getNotificationId();
189 | }
190 |
191 | /**
192 | * The UUID identifying the webhook being fired.
193 | */
194 | public function getWebhookId()
195 | {
196 | return $this->getParsedData()->getWebhookId();
197 | }
198 |
199 | /**
200 | * Optional notification payload.
201 | */
202 | public function getPayload()
203 | {
204 | return $this->getParsedData()->getPayload();
205 | }
206 |
207 | /**
208 | * @return int Raw response code
209 | */
210 | public function getResponseCode()
211 | {
212 | if ($this->getEventTarget() === $this->getParsedData()::EVENT_TARGET_PAYMENT) {
213 | return $this->getPayload()->getResponseCode();
214 | }
215 | }
216 |
217 | /**
218 | * @return string Raw response code
219 | */
220 | public function getAuthCode()
221 | {
222 | if ($this->getEventTarget() === $this->getParsedData()::EVENT_TARGET_PAYMENT) {
223 | return $this->getPayload()->getAuthCode();
224 | }
225 | }
226 |
227 | /**
228 | * @return string Raw AVS response code
229 | */
230 | public function getAvsResponse()
231 | {
232 | if ($this->getEventTarget() === $this->getParsedData()::EVENT_TARGET_PAYMENT) {
233 | return $this->getPayload()->getAvsResponse();
234 | }
235 | }
236 |
237 | /**
238 | * @return float authAmount, no currency, no stated units
239 | */
240 | public function getAuthAmount()
241 | {
242 | if ($this->getEventTarget() === $this->getParsedData()::EVENT_TARGET_PAYMENT) {
243 | return $this->getPayload()->getAuthAmount();
244 | }
245 | }
246 |
247 | /**
248 | * Assert that the signature of the webhook is valid.
249 | * Will honour the flag to skip this check.
250 | *
251 | * @throws InvalidRequestException
252 | */
253 | public function assertSignature()
254 | {
255 | // Signature checking can be explicitly disabled.
256 |
257 | if ((bool)$this->getDisableWebhookSignature()) {
258 | return;
259 | }
260 |
261 | if (! $this->isSignatureValid()) {
262 | throw new InvalidRequestException('Invalid or missing signature');
263 | }
264 | }
265 |
266 | /**
267 | * Check whether the signature is valid.
268 | *
269 | * @return bool true = valid; false = invalid.
270 | */
271 | public function isSignatureValid()
272 | {
273 | // A missing or malformed signature is invalid.
274 |
275 | if ($this->signature === null || strpos($this->signature, 'sha512=') !== 0) {
276 | return false;
277 | }
278 |
279 | // A missing signature key is also invalid.
280 |
281 | if (($signatureKey = $this->getSignatureKey()) === null) {
282 | return false;
283 | }
284 |
285 | // Check the signature.
286 |
287 | list ($algorithm, $signatureString) = explode('=', $this->signature, 2);
288 |
289 | $hashedPayload = strtoupper(
290 | hash_hmac($algorithm, $this->payload, $signatureKey)
291 | );
292 |
293 | return $hashedPayload === $signatureString;
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/src/Message/HostedPage/AuthorizeRequest.php:
--------------------------------------------------------------------------------
1 | getCancelUrl()) {
38 | $this->setReturnOptionsCancelUrl($cancelUrl);
39 | }
40 |
41 | if ($returnUrl = $this->getReturnUrl()) {
42 | $this->setReturnOptionsUrl($returnUrl);
43 | }
44 |
45 | // Then add the settings to the outer requuest object.
46 |
47 | if ($settings = $this->getHostedPaymentSettings()) {
48 | $request = $request->withHostedPaymentSettings($settings);
49 | }
50 |
51 | return $request;
52 | }
53 |
54 | public function getData()
55 | {
56 | $transaction = parent::getData();
57 |
58 | return $transaction;
59 | }
60 |
61 | /**
62 | * Accept a transaction and sends it as a request.
63 | *
64 | * @param $data TransactionRequestInterface
65 | * @returns TransactionResponse
66 | */
67 | public function sendData($data)
68 | {
69 | $response_data = $this->sendTransaction($data);
70 |
71 | $response = new Response($this, $response_data);
72 |
73 | // The response needs to know whether we are in test mode or not,
74 | // so that it chooses the correct hosted page URL to redirect to.
75 |
76 | $response->setTestMode($this->getTestMode());
77 |
78 | return $response;
79 | }
80 |
81 | /**
82 | * Add all the hosted payment settings all at once.
83 | * @param array $value Name/value pairs for each setting.
84 | */
85 | public function setHostedPaymentSettings(array $value)
86 | {
87 | foreach ($value as $name => $value) {
88 | $this->setHostedPaymentSetting($name, $value);
89 | }
90 | }
91 |
92 | /**
93 | * @returns HostedPaymentSettings|null
94 | */
95 | public function getHostedPaymentSettings()
96 | {
97 | return $this->hostedPaymentSettings;
98 | }
99 |
100 | /**
101 | * @param mixed $name The Name of the setting,
102 | * one of \Academe\AuthorizeNet\Request\Model\HostedPaymentSetting::SETTING_NAME_*
103 | * @param string|array $value The value of the setting.
104 | */
105 | public function setHostedPaymentSetting($name, $value)
106 | {
107 | // Initialise the collection if not intialised.
108 | if (empty($this->hostedPaymentSettings)) {
109 | $this->hostedPaymentSettings = new HostedPaymentSettings();
110 | }
111 |
112 | $this->hostedPaymentSettings->push(
113 | new HostedPaymentSetting($name, $value)
114 | );
115 | }
116 |
117 | /**
118 | * Set a named parameter on a named payment page setting.
119 | */
120 | public function setHostedPaymentSettingParameter($settingName, $parameterName, $parameterValue)
121 | {
122 | if ($settings = $this->getHostedPaymentSettings()) {
123 | // The settings collection already exists, so add to it.
124 | $settings->setSettingParameter($settingName, $parameterName, $parameterValue);
125 | } else {
126 | // No settings at all so far, so add this one to start.
127 | $this->setHostedPaymentSetting($settingName, [$parameterName => $parameterValue]);
128 | }
129 | }
130 |
131 | // The following parameters follow a consistent pattern that could be implemented
132 | // as a magic __call method, but aren't at this time (in case they need to go into
133 | // the gateway settings).
134 | // They exist to allow these settings to provided as scalar values, even though
135 | // they end up quite deep in the request strucure.
136 | // There are no equivalent getters for these setters. Do we need getters?
137 |
138 | /**
139 | * @param bool $value
140 | */
141 | public function setReturnOptionsShowReceipt($value)
142 | {
143 | return $this->setHostedPaymentSettingParameter(
144 | HostedPaymentSetting::SETTING_NAME_RETURN_OPTIONS,
145 | 'showReceipt',
146 | (bool)$value
147 | );
148 | }
149 |
150 | /**
151 | * @param string $value
152 | */
153 | public function setReturnOptionsUrl($value)
154 | {
155 | return $this->setHostedPaymentSettingParameter(
156 | HostedPaymentSetting::SETTING_NAME_RETURN_OPTIONS,
157 | 'url',
158 | $value
159 | );
160 | }
161 |
162 | /**
163 | * @param string $value
164 | */
165 | public function setReturnOptionsUrlText($value)
166 | {
167 | return $this->setHostedPaymentSettingParameter(
168 | HostedPaymentSetting::SETTING_NAME_RETURN_OPTIONS,
169 | 'urlText',
170 | $value
171 | );
172 | }
173 |
174 | /**
175 | * @param string $value
176 | */
177 | public function setReturnOptionsCancelUrl($value)
178 | {
179 | return $this->setHostedPaymentSettingParameter(
180 | HostedPaymentSetting::SETTING_NAME_RETURN_OPTIONS,
181 | 'cancelUrl',
182 | $value
183 | );
184 | }
185 |
186 | /**
187 | * @param string $value
188 | */
189 | public function setReturnOptionsCancelUrlText($value)
190 | {
191 | return $this->setHostedPaymentSettingParameter(
192 | HostedPaymentSetting::SETTING_NAME_RETURN_OPTIONS,
193 | 'cancelUrlText',
194 | $value
195 | );
196 | }
197 |
198 | /**
199 | * @param string $value
200 | */
201 | public function setButtonOptionsText($value)
202 | {
203 | return $this->setHostedPaymentSettingParameter(
204 | HostedPaymentSetting::SETTING_NAME_BUTTON_OPTIONS,
205 | 'text',
206 | $value
207 | );
208 | }
209 |
210 | /**
211 | * @param string $value
212 | */
213 | public function setStyleOptionsBgColor($value)
214 | {
215 | return $this->setHostedPaymentSettingParameter(
216 | HostedPaymentSetting::SETTING_NAME_STYLE_OPTIONS,
217 | 'bgColor',
218 | $value
219 | );
220 | }
221 |
222 | /**
223 | * @param bool $value
224 | */
225 | public function setPaymentOptionsCardCodeRequired($value)
226 | {
227 | return $this->setHostedPaymentSettingParameter(
228 | HostedPaymentSetting::SETTING_NAME_PAYMENT_OPTIONS,
229 | 'cardCodeRequired',
230 | (bool)$value
231 | );
232 | }
233 |
234 | /**
235 | * @param bool $value
236 | */
237 | public function setPaymentOptionsShowCreditCard($value)
238 | {
239 | return $this->setHostedPaymentSettingParameter(
240 | HostedPaymentSetting::SETTING_NAME_PAYMENT_OPTIONS,
241 | 'showCreditCard',
242 | (bool)$value
243 | );
244 | }
245 |
246 | /**
247 | * @param bool $value
248 | */
249 | public function setPaymentOptionsShowBankAccount($value)
250 | {
251 | return $this->setHostedPaymentSettingParameter(
252 | HostedPaymentSetting::SETTING_NAME_PAYMENT_OPTIONS,
253 | 'showBankAccount',
254 | (bool)$value
255 | );
256 | }
257 |
258 | /**
259 | * @param bool $value
260 | */
261 | public function setSecurityOptionsCaptcha($value)
262 | {
263 | return $this->setHostedPaymentSettingParameter(
264 | HostedPaymentSetting::SETTING_NAME_SECURITY_OPTIONS,
265 | 'captcha',
266 | (bool)$value
267 | );
268 | }
269 |
270 | /**
271 | * @param bool $value
272 | */
273 | public function setShippingAddressOptionsShow($value)
274 | {
275 | return $this->setHostedPaymentSettingParameter(
276 | HostedPaymentSetting::SETTING_NAME_SHIPPING_ADDRESS_OPTIONS,
277 | 'show',
278 | (bool)$value
279 | );
280 | }
281 |
282 | /**
283 | * @param bool $value
284 | */
285 | public function setShippingAddressOptionsRequired($value)
286 | {
287 | return $this->setHostedPaymentSettingParameter(
288 | HostedPaymentSetting::SETTING_NAME_SHIPPING_ADDRESS_OPTIONS,
289 | 'required',
290 | (bool)$value
291 | );
292 | }
293 |
294 | /**
295 | * @param bool $value
296 | */
297 | public function setBillingAddressOptionsShow($value)
298 | {
299 | return $this->setHostedPaymentSettingParameter(
300 | HostedPaymentSetting::SETTING_NAME_BILLING_ADDRESS_OPTIONS,
301 | 'show',
302 | (bool)$value
303 | );
304 | }
305 |
306 | /**
307 | * @param bool $value
308 | */
309 | public function setBillingAddressOptionsRequired($value)
310 | {
311 | return $this->setHostedPaymentSettingParameter(
312 | HostedPaymentSetting::SETTING_NAME_BILLING_ADDRESS_OPTIONS,
313 | 'required',
314 | (bool)$value
315 | );
316 | }
317 |
318 | /**
319 | * @param bool $value
320 | */
321 | public function setCustomerOptionsShowEmail($value)
322 | {
323 | return $this->setHostedPaymentSettingParameter(
324 | HostedPaymentSetting::SETTING_NAME_CUSTOMER_OPTIONS,
325 | 'showEmail',
326 | (bool)$value
327 | );
328 | }
329 |
330 | /**
331 | * @param bool $value
332 | */
333 | public function setCustomerOptionsRequiredEmail($value)
334 | {
335 | return $this->setHostedPaymentSettingParameter(
336 | HostedPaymentSetting::SETTING_NAME_CUSTOMER_OPTIONS,
337 | 'requiredEmail',
338 | (bool)$value
339 | );
340 | }
341 |
342 | /**
343 | * @param bool $value
344 | */
345 | public function setOrderOptionsShow($value)
346 | {
347 | return $this->setHostedPaymentSettingParameter(
348 | HostedPaymentSetting::SETTING_NAME_ORDER_OPTIONS,
349 | 'show',
350 | (bool)$value
351 | );
352 | }
353 |
354 | /**
355 | * @param string $value
356 | */
357 | public function setOrderOptionsMerchantName($value)
358 | {
359 | return $this->setHostedPaymentSettingParameter(
360 | HostedPaymentSetting::SETTING_NAME_ORDER_OPTIONS,
361 | 'merchantName',
362 | $value
363 | );
364 | }
365 |
366 | /**
367 | * Name is no typo - it just follows the same patterns as above.
368 | * @param string $value
369 | */
370 | public function setIFrameCommunicatorUrlUrl($value)
371 | {
372 | return $this->setHostedPaymentSettingParameter(
373 | HostedPaymentSetting::SETTING_NAME_FRAME_COMMUNICATOR_URL,
374 | 'url',
375 | $value
376 | );
377 | }
378 | }
379 |
--------------------------------------------------------------------------------
/src/Message/AuthorizeRequest.php:
--------------------------------------------------------------------------------
1 | getCurrency(), $this->getAmountInteger());
49 |
50 | $transaction = $this->createTransaction($amount);
51 |
52 | // Build the customer, and add the customer to the transaction
53 | // if it has any attributes set.
54 |
55 | $customer = new Customer();
56 |
57 | $customer = $customer
58 | ->withId($this->getCustomerId())
59 | ->withCustomerType($this->getCustomerType())
60 | ->withDriversLicense($this->getCustomerDriversLicense())
61 | ->withTaxId($this->getCustomerTaxId());
62 |
63 | if ($card = $this->getCard()) {
64 | $billingAddress = trim(
65 | $card->getBillingAddress1() . ' ' . $card->getBillingAddress2()
66 | );
67 |
68 | if ($billingAddress === '') {
69 | $billingAddress = null;
70 | }
71 |
72 | $billTo = new NameAddress(
73 | $card->getBillingFirstName(),
74 | $card->getBillingLastName(),
75 | $card->getBillingCompany(),
76 | $billingAddress,
77 | $card->getBillingCity(),
78 | $card->getBillingState(),
79 | $card->getBillingPostcode(),
80 | $card->getBillingCountry()
81 | );
82 |
83 | // The billTo may have phone and fax number, but the shipTo does not.
84 | $billTo = $billTo->withPhoneNumber($card->getBillingPhone());
85 | $billTo = $billTo->withFaxNumber($card->getBillingFax());
86 |
87 | if ($billTo->hasAny()) {
88 | $transaction = $transaction->withBillTo($billTo);
89 | }
90 |
91 | $shippingAddress = trim(
92 | $card->getShippingAddress1() . ' ' . $card->getShippingAddress2()
93 | );
94 |
95 | if ($shippingAddress === '') {
96 | $shippingAddress = null;
97 | }
98 |
99 | $shipTo = new NameAddress(
100 | $card->getShippingFirstName(),
101 | $card->getShippingLastName(),
102 | $card->getShippingCompany(),
103 | $shippingAddress,
104 | $card->getShippingCity(),
105 | $card->getShippingState(),
106 | $card->getShippingPostcode(),
107 | $card->getShippingCountry()
108 | );
109 |
110 | if ($shipTo->hasAny()) {
111 | $transaction = $transaction->withShipTo($shipTo);
112 | }
113 |
114 | if ($card->getEmail()) {
115 | $customer = $customer->withEmail($card->getEmail());
116 | }
117 |
118 | // Credit card, track 1 and track 2 are mutually exclusive.
119 |
120 | if ($card->getNumber()) {
121 | // A credit card has been supplied.
122 |
123 | $card->validate();
124 |
125 | $creditCard = new CreditCard(
126 | $card->getNumber(),
127 | // Either MMYY or MMYYYY will work.
128 | $card->getExpiryMonth() . $card->getExpiryYear()
129 | );
130 |
131 | if ($card->getCvv()) {
132 | $creditCard = $creditCard->withCardCode($card->getCvv());
133 | }
134 |
135 | $transaction = $transaction->withPayment($creditCard);
136 | } elseif ($card->getTrack1()) {
137 | // A card magnetic track has been supplied (aka card present).
138 |
139 | $transaction = $transaction->withPayment(
140 | new Track1($card->getTrack1())
141 | );
142 | } elseif ($card->getTrack2()) {
143 | $transaction = $transaction->withPayment(
144 | new Track2($card->getTrack2())
145 | );
146 | }
147 | } // credit card
148 |
149 | if ($customer->hasAny()) {
150 | $transaction = $transaction->withCustomer($customer);
151 | }
152 |
153 | // Allow "Accept JS" nonce (in two parts) instead of card (aka OpaqueData).
154 |
155 | $descriptor = $this->getOpaqueDataDescriptor();
156 | $value = $this->getOpaqueDataValue();
157 |
158 | if ($descriptor && $value) {
159 | $transaction = $transaction->withPayment(
160 | new OpaqueData($descriptor, $value)
161 | );
162 | }
163 |
164 | if ($this->getClientIp()) {
165 | $transaction = $transaction->withCustomerIp($this->getClientIp());
166 | }
167 |
168 | // The MarketType and DeviceType is mandatory if tracks are supplied.
169 |
170 | if ($this->getDeviceType()
171 | || $this->getMarketType()
172 | || (isset($card) && $card->getTracks())
173 | ) {
174 | // TODO: accept optional customerSignature
175 |
176 | $retail = new Retail(
177 | $this->getMarketType() ?: Retail::MARKET_TYPE_RETAIL,
178 | $this->getDeviceType() ?: Retail::DEVICE_TYPE_UNKNOWN
179 | );
180 |
181 | $transaction = $transaction->withRetail($retail);
182 | }
183 |
184 | // The description and invoice number go into an Order object.
185 |
186 | if ($this->getInvoiceNumber() || $this->getDescription()) {
187 | $order = new Order(
188 | $this->getInvoiceNumber(),
189 | $this->getDescription()
190 | );
191 |
192 | $transaction = $transaction->withOrder($order);
193 | }
194 |
195 | // 3D Secure is handled by a thirds party provider.
196 | // These two fields submit the authentication values provided.
197 | // It is not really clear if both these fields must be always provided together,
198 | // or whether just one is permitted.
199 |
200 | if ($this->getAuthenticationIndicator() || $this->getAuthenticationValue()) {
201 | $cardholderAuthentication = new CardholderAuthentication(
202 | $this->getAuthenticationIndicator(),
203 | $this->getAuthenticationValue()
204 | );
205 |
206 | $transaction = $transaction->withCardholderAuthentication($cardholderAuthentication);
207 | }
208 |
209 | // Is a basket of items to go into the request?
210 |
211 | if ($this->getItems()) {
212 | $lineItems = new LineItems();
213 |
214 | $currencies = new ISOCurrencies();
215 | $moneyParser = new DecimalMoneyParser($currencies);
216 |
217 | foreach ($this->getItems() as $itemId => $item) {
218 | // Parse to a Money object.
219 |
220 | $itemMoney = $moneyParser->parse((string)$item->getPrice(), $this->getCurrency());
221 |
222 | // Omnipay provides the line price, but the LineItem wants the unit price.
223 |
224 | $itemQuantity = $item->getQuantity();
225 |
226 | if (! empty($itemQuantity)) {
227 | // Divide the line price by the quantity to get the item price.
228 |
229 | $itemMoney = $itemMoney->divide($itemQuantity);
230 | }
231 |
232 | // Wrap in a MoneyPhp object for the AmountInterface.
233 |
234 | $amount = new MoneyPhp($itemMoney);
235 |
236 | $lineItem = new LineItem(
237 | $itemId,
238 | $item->getName(),
239 | $item->getDescription(),
240 | $itemQuantity,
241 | $amount, // AmountInterface (unit price)
242 | null // $taxable
243 | );
244 |
245 | $lineItems->push($lineItem);
246 | }
247 |
248 | if ($lineItems->count()) {
249 | $transaction = $transaction->withLineItems($lineItems);
250 | }
251 | }
252 |
253 | $transaction = $transaction->with([
254 | 'terminalNumber' => $this->getTerminalNumber(),
255 | ]);
256 |
257 | if ($sourceUserFields = $this->getUserFields()) {
258 | // Can be provided as key/value array, array of name/value pairs
259 | // or a readymade collection of models.
260 |
261 | if ($sourceUserFields instanceof UserFields) {
262 | // Already a collection; just use it.
263 |
264 | $userFields = $sourceUserFields;
265 | } else {
266 | $userFields = new UserFields();
267 |
268 | if (is_array($sourceUserFields)) {
269 | foreach ($sourceUserFields as $key => $value) {
270 | if (is_string($key) && is_string($value)) {
271 | // key/value pairs: 'key' => 'value'
272 |
273 | $userFields->push(new UserField($key, $value));
274 | }
275 |
276 | if (is_array($value) && count($value) === 2) {
277 | // name/value pairs: ['name' => 'the name', 'value' => 'the value']
278 |
279 | $userFields->push(new UserField($value['name'], $value['value']));
280 | }
281 |
282 | if ($value instanceof UserField) {
283 | // An array of UserField objects was supplied.
284 |
285 | $userFields->push($value);
286 | }
287 | }
288 | }
289 | }
290 |
291 | if ($userFields->count()) {
292 | $transaction = $transaction->withUserFields($userFields);
293 | }
294 | }
295 |
296 | return $transaction;
297 | }
298 |
299 | /**
300 | * Create a new instance of the transaction object.
301 | */
302 | protected function createTransaction(AmountInterface $amount)
303 | {
304 | return new AuthOnly($amount);
305 | }
306 |
307 | /**
308 | * Accept a transaction and sends it as a request.
309 | *
310 | * @param $data TransactionRequestInterface
311 | * @returns TransactionResponse
312 | */
313 | public function sendData($data)
314 | {
315 | $responseData = $this->sendTransaction($data);
316 |
317 | return new AuthorizeResponse($this, $responseData);
318 | }
319 |
320 | /**
321 | * Value must be one of Retail::DEVICE_TYPE_*
322 | * @param int $value The retail device type.
323 | * @return $this
324 | */
325 | public function setDeviceType($value)
326 | {
327 | return $this->setParameter('deviceType', $value);
328 | }
329 |
330 | /**
331 | * @return int
332 | */
333 | public function getDeviceType()
334 | {
335 | return $this->getParameter('deviceType');
336 | }
337 |
338 | /**
339 | * Value must be one of Retail::MARKET_TYPE_*
340 | * @param int $value The retail market type.
341 | * @return $this
342 | */
343 | public function setMarketType($value)
344 | {
345 | return $this->setParameter('marketType', $value);
346 | }
347 |
348 | /**
349 | * @return int
350 | */
351 | public function getMarketType()
352 | {
353 | return $this->getParameter('marketType');
354 | }
355 |
356 | /**
357 | * @param string $value Example: 'COMMON.ACCEPT.INAPP.PAYMENT'.
358 | * @return $this
359 | */
360 | public function setOpaqueDataDescriptor($value)
361 | {
362 | return $this->setParameter('opaqueDataDescriptor', $value);
363 | }
364 |
365 | /**
366 | * @return string
367 | */
368 | public function getOpaqueDataDescriptor()
369 | {
370 | return $this->getParameter('opaqueDataDescriptor');
371 | }
372 |
373 | /**
374 | * @param string $value Long text token usually 216 bytes long.
375 | * @return $this
376 | */
377 | public function setOpaqueDataValue($value)
378 | {
379 | return $this->setParameter('opaqueDataValue', $value);
380 | }
381 |
382 | /**
383 | * @return string
384 | */
385 | public function getOpaqueDataValue()
386 | {
387 | return $this->getParameter('opaqueDataValue');
388 | }
389 |
390 | /**
391 | * @param string $descriptor
392 | * @param string $value
393 | * @return $this
394 | */
395 | public function setOpaqueData($descriptor, $value)
396 | {
397 | $this->setOpaqueDataDataDescriptor($descriptor);
398 | $this->setOpaqueDataValue($value);
399 |
400 | return $this;
401 | }
402 |
403 | /**
404 | * The opaque data comes in two parts, but Omnipay uses just
405 | * one parameter for a card token.
406 | * Join the descriptor and the value with a colon.
407 | */
408 | public function setToken($value)
409 | {
410 | list($opaqueDataDescriptor, $opaqueDataValue) = explode(static::CARD_TOKEN_SEPARATOR, $value, 2);
411 |
412 | $this->setOpaqueDataDescriptor($opaqueDataDescriptor);
413 | $this->setOpaqueDataValue($opaqueDataValue);
414 |
415 | return $this;
416 | }
417 |
418 | public function getToken()
419 | {
420 | $opaqueDataDescriptor = $this->getOpaqueDataDescriptor();
421 | $opaqueDataValue = $this->getOpaqueDataValue();
422 |
423 | if ($opaqueDataDescriptor && $opaqueDataValue) {
424 | return $opaqueDataDescriptor
425 | . static::CARD_TOKEN_SEPARATOR
426 | . $opaqueDataValue;
427 | }
428 | }
429 |
430 | public function setUserFields($value)
431 | {
432 | return $this->setParameter('userFields', $value);
433 | }
434 |
435 | public function getUserFields()
436 | {
437 | return $this->getParameter('userFields');
438 | }
439 | }
440 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [](https://travis-ci.org/academe/omnipay-authorizenetapi)
3 | [](https://packagist.org/packages/academe/omnipay-authorizenetapi)
4 | [](https://packagist.org/packages/academe/omnipay-authorizenetapi)
5 | [](https://packagist.org/packages/academe/omnipay-authorizenetapi)
6 | [](https://packagist.org/packages/academe/omnipay-authorizenetapi)
7 |
8 | Table of Contents
9 | =================
10 |
11 | * [Table of Contents](#table-of-contents)
12 | * [Omnipay-AuthorizeNetApi](#omnipay-authorizenetapi)
13 | * [Installation](#installation)
14 | * [Authorize.Net API](#authorizenet-api)
15 | * [API Authorize/Purchase (Credit Card)](#api-authorizepurchase-credit-card)
16 | * [API Capture](#api-capture)
17 | * [API Authorize/Purchase (Opaque Data)](#api-authorizepurchase-opaque-data)
18 | * [API Void](#api-void)
19 | * [API Refund](#api-refund)
20 | * [API Fetch Transaction](#api-fetch-transaction)
21 | * [Hosted Payment Page](#hosted-payment-page)
22 | * [Hosted Payment Page Authorize/Purchase](#hosted-payment-page-authorizepurchase)
23 | * [Webhook Notifications](#webhook-notifications)
24 |
25 | # Omnipay-AuthorizeNetApi
26 |
27 | Omnipay 3.x implementation of Authorize.Net API
28 |
29 | # Installation
30 |
31 | composer require "academe/omnipay-authorizenetapi: ~3.0"
32 |
33 | # Authorize.Net API
34 |
35 | The *Authorize.Net API* driver handles server-to-server requests.
36 | It is used both for direct card payment (though check PCI requirements)
37 | and for creating transactions using a card token generated client-side.
38 |
39 | ## API Authorize/Purchase (Credit Card)
40 |
41 | The following example is a simple authorize with supplied card details.
42 | *You would normally avoid allowing card details near your merchant site
43 | back end for PCI compliance reasons,
44 | supplying a tokenised card reference instead (see later section for this).*
45 |
46 | ```php
47 | setAuthName('XXXXXxxxxxx');
54 | $gateway->setTransactionKey('XXXXX99999xxxxx');
55 | $gateway->setTestMode(true);
56 |
57 | $creditCard = new Omnipay\Common\CreditCard([
58 | // Swiped tracks can be provided instead, if the card is present.
59 | 'number' => '4000123412341234',
60 | 'expiryMonth' => '12',
61 | 'expiryYear' => '2020',
62 | 'cvv' => '123',
63 | // Billing and shipping details can be added here.
64 | ]);
65 |
66 | // Generate a unique merchant site transaction ID.
67 | $transactionId = rand(100000000, 999999999);
68 |
69 | $response = $gateway->authorize([
70 | 'amount' => '7.99',
71 | 'currency' => 'USD',
72 | 'transactionId' => $transactionId,
73 | 'card' => $creditCard,
74 | // Additional optional attributes:
75 | 'customerId' => '123456',
76 | 'customerType' => \Academe\AuthorizeNet\Request\Model\Customer::CUSTOMER_TYPE_INDIVIDUAL,
77 | 'customerDriversLicense' => [
78 | 'number' => '123456',
79 | 'state' => 'NY',
80 | 'dateOfBirth' => '1967-01-01',
81 | ],
82 | 'customerTaxId' => 'TAX456',
83 | ])->send();
84 |
85 | // Or use $gateway->purchase() to immediately capture.
86 |
87 | var_dump($response->isSuccessful());
88 | // bool(true)
89 |
90 | var_dump($response->getCode());
91 | // string(1) "1"
92 |
93 | var_dump($response->getMessage());
94 | // string(35) "This transaction has been approved."
95 |
96 | var_dump($response->getTransactionReference());
97 | // string(11) "60103474871"
98 | ```
99 |
100 | ## API Capture
101 |
102 | Once authorized, the amount can be captured:
103 |
104 | ```php
105 | // Captured from the authorization response.
106 | $transactionReference = $response->getTransactionReference();
107 |
108 | $response = $gateway->capture([
109 | 'amount' => '7.99',
110 | 'currency' => 'USD',
111 | 'transactionReference' => $transactionReference,
112 | ])->send();
113 | ```
114 |
115 | ## API Authorize/Purchase (Opaque Data)
116 |
117 | The "Opaque Data" here is a tokenised credit or debit card.
118 | Authorize.Net can tokenise cards in a number of ways, one of which
119 | is through the `accept.js` package on the front end.
120 | It works like this:
121 |
122 | You build a payment form in your page.
123 | As well as hard-coding it as shown below, the gateway provides a method
124 | you can use to generate it dynamically.
125 |
126 | ```html
127 |
138 | ```
139 |
140 | Note the card detail elements do not have names, so will not be submitted
141 | to your site. This is important for PCI reasons.
142 | Two hidden fields are defined to carry the opaque data to your site.
143 | You can include as many other fields as you like in the same form,
144 | which may include a name and an address.
145 |
146 | After the payment form, you will need the `accept.js` JavaScript:
147 |
148 | ```javascript
149 |
153 | ```
154 |
155 | Use the `https://js.authorize.net/v1/Accept.js` URL for production.
156 |
157 | You need to catch the "Pay Now" submission and send it to a function to
158 | process the card details. Either an `onclick` attribute or a jQuery event
159 | will work. For example:
160 |
161 |
162 |
163 | The `sendPaymentDataToAnet` function handles the tokenisation.
164 |
165 | ```javascript
166 |
192 | ```
193 |
194 | The response handler is able to provide errors that may have been
195 | generated while trying to tokenise the card.
196 | But if all is well, it updates the payment form with the opaque data
197 | (another function `paymentFormUpdate`):
198 |
199 | ```javascript
200 | function responseHandler(response) {
201 | if (response.messages.resultCode === "Error") {
202 | var i = 0;
203 | while (i < response.messages.message.length) {
204 | console.log(
205 | response.messages.message[i].code + ": " +
206 | response.messages.message[i].text
207 | );
208 | i = i + 1;
209 | }
210 | } else {
211 | paymentFormUpdate(response.opaqueData);
212 | }
213 | }
214 | ```
215 |
216 | Populate the opaque data hidden form items, then finally submit the form:
217 |
218 | ```javascript
219 | function paymentFormUpdate(opaqueData) {
220 | document.getElementById("opaqueDataDescriptor").value = opaqueData.dataDescriptor;
221 | document.getElementById("opaqueDataValue").value = opaqueData.dataValue;
222 | document.getElementById("paymentForm").submit();
223 | }
224 | ```
225 |
226 | Back at the server, you will have two opaque data fields to capture:
227 |
228 | * opaqueDataDescriptor
229 | * opaqueDataValue
230 |
231 | Initiate an `authorize()` or `purchase()` at the backend, as described in
232 | the previous section. In the `creditCard` object, leave the card details
233 | blank, not set. Instead, send the opaque data:
234 |
235 | ```php
236 | $request = $gateway->authorize([
237 | ...
238 | 'opaqueDataDescriptor' => $opaqueDataDescriptor,
239 | 'opaqueDataValue' => $opaqueDataValue,
240 | ]);
241 | ```
242 |
243 | or
244 |
245 | ```php
246 | $request->setOpaqueData($opaqueDataDescriptor, $opaqueDataValue);
247 | ```
248 |
249 | or join with a colon (:) to handle as a card token:
250 |
251 | ```php
252 | $request->setToken($opaqueDataDescriptor . ':' . $opaqueDataValue);
253 | ```
254 |
255 | The authorize or purchase should then go ahead as though the card
256 | details were provided directly. In the result, the last four digits
257 | of the card will be made available in case a refund needs to be performed.
258 |
259 | Further details can be
260 | [found in the official documentation](https://developer.authorize.net/api/reference/features/acceptjs.html).
261 |
262 | Note also that the opaque data is used for other payment sources, such as
263 | bank accounts and PayPal.
264 |
265 | ## API Void
266 |
267 | An authorized transaction can be voided:
268 |
269 | ```php
270 | // Captured from the authorization response.
271 | $transactionReference = $response->getTransactionReference();
272 |
273 | $response = $gateway->void([
274 | 'transactionReference' => $transactionReference,
275 | ])->send();
276 | ```
277 |
278 | ## API Refund
279 |
280 | A cleared credit card payment can be refunded, given the original
281 | transaction reference, the original amount, and the last four digits
282 | of the credit card:
283 |
284 | ```php
285 | $response = $gateway->refund([
286 | 'amount' => '7.99',
287 | 'currency' => 'USD',
288 | 'transactionReference' => $transactionReference,
289 | 'numberLastFour' => '1234',
290 | ])->send();
291 | ```
292 |
293 | ## API Fetch Transaction
294 |
295 | An existing transaction can be fetched from the gateway given
296 | its `transactionReference`:
297 |
298 | ```php
299 | $response = $gateway->fetchTransaction([
300 | 'transactionReference' => $transactionReference,
301 | ])->send();
302 | ```
303 |
304 | The Hosted Payment Page will host the payment form on the gateway.
305 | The form can be presented to the user as a full page redirect or in an iframe.
306 |
307 | # Hosted Payment Page
308 |
309 | The Hosted Payment Page is a different gateway:
310 |
311 | ```php
312 | $gateway = Omnipay\Omnipay::create('AuthorizeNetApi_HostedPage');
313 | ```
314 |
315 | The gateway is configured the same way as the direct API gateway,
316 | and the authorize/purchase
317 | requests are created in the same way, except for the addition of
318 | `return` and `cancel` URLs:
319 |
320 | ## Hosted Payment Page Authorize/Purchase
321 |
322 | ```php
323 | $request = $gateway->authorize([
324 | 'amount' => $amount,
325 | // etc.
326 | 'returnUrl' => 'return URL after the transaction is approved or rejected',
327 | 'cancelUrl' => 'URL to use if the user cancels the transaction',
328 | ]);
329 | ```
330 |
331 | The response will be a redirect, with the following details used to
332 | construct the redirect in the merchant site:
333 |
334 | ```php
335 | $response = $request->send();
336 |
337 | $response->getRedirectMethod();
338 | // Usually "POST"
339 |
340 | $response->getRedirectUrl();
341 | // The redirect URL or POST form action.
342 |
343 | $response->getRedirectData()
344 | // Array of name/value elements used to construct hidden fields
345 | // in the POST form.
346 | ```
347 |
348 | A naive POST "pay now" button may look like the following form.
349 |
350 | ```php
351 | $method = $response->getRedirectMethod();
352 | $action = $response->getRedirectUrl();
353 |
354 | echo "";
363 | ```
364 |
365 | This will take the user to the gateway payment page, looking something
366 | like this by default:
367 |
368 | ------
369 | 
370 | ------
371 |
372 | The billing details will be prefilled with the card details supplied
373 | in the `$gateway->authorize()`.
374 | What the user can change and/or see, can be changed using options or
375 | confiration in the account.
376 |
377 | Taking the `hostedPaymentPaymentOptions` as an example,
378 | this is how the options are set:
379 |
380 | The [documentation](https://developer.authorize.net/api/reference/features/accept_hosted.html)
381 | lists `hostedPaymentPaymentOptions` as supporting these options:
382 | `{"cardCodeRequired": false, "showCreditCard": true, "showBankAccount": true}`
383 |
384 | To set any of the options, drop the `hostedPayment` prefix from the options
385 | name, then append with the specific option you want to set, and use the
386 | result as the parameter, keeping the name in *camelCase*.
387 | So the above set of options are supported by the following parameters:
388 |
389 | * paymentOptionsCardCodeRequired
390 | * paymentOptionsShowCreditCard
391 | * paymentOptionsShowBankAccount
392 |
393 | You can set these in the `authorize()` stage:
394 |
395 | ```php
396 | $request = $gateway->authorize([
397 | ...
398 | // Hide the bank account form but show the credit card form.
399 | 'paymentOptionsShowCreditCard' => true,
400 | 'paymentOptionsShowBankAccount' => false,
401 | // Change the "Pay" buton text.
402 | 'buttonOptionsText' => 'Pay now',
403 | ]);
404 | ```
405 |
406 | or use the `set*()` form to do the same thing:
407 |
408 | $request->setPaymentOptionsShowBankAccount(false);
409 |
410 | # Webhook Notifications
411 |
412 | The Authorize.Net gateway provides a rich set of webhooks to notify the
413 | merchant site (and/or other backend systems) about events related to
414 | customers or payments.
415 | The [current documentation can be found here](https://developer.authorize.net/api/reference/features/webhooks.html).
416 |
417 | For some API methods, such as the Hosted Payment Page, the webhooks
418 | are necessary for operation. For other API methods they provide additional
419 | information.
420 |
421 | The webhooks can be configured in the Authorize.Net account settings page.
422 | They can also be fully managed through a REST API, so that a merchant
423 | site can register for all the webhooks that it needs.
424 | *Note that the webhook management RESTful API has not yet been implemented here.*
425 |
426 | Your notification handler is set up like this at your webhook endpoint:
427 |
428 | ```php
429 | $gateway = Omnipay::create('AuthorizeNetApi_Api');
430 |
431 | $gateway->setAuthName($authName);
432 | $gateway->setTransactionKey($authKey);
433 | $gateway->setSignatureKey($signatureKey); // HMAC-256
434 | $gateway->setTestMode(true); // for false
435 |
436 | $notification = $gateway->acceptNotification();
437 | ```
438 |
439 | This will read and parse the webhook `POST` data.
440 | The raw nested array data can be found at:
441 |
442 | $notification->getData();
443 |
444 | The parsed `Notification` value object can be found at:
445 |
446 | $notification->getParsedData();
447 |
448 | Some details that describe the nature of the notification are:
449 |
450 | ```php
451 | // The main target: payment or customer
452 | $notification->getEventTarget();
453 |
454 | // The event subtarget. e.g. capture, fraud, void, subscription
455 | $notification->getEventSubtarget();
456 |
457 | // The event action. e.g. created, updated, deleted, held, approved, declined
458 | $notification->getEventMethod();
459 | ```
460 |
461 | See here for a full list of the target, subtarget and actions:
462 | https://github.com/academe/authorizenet-objects/blob/master/src/ServerRequest/Notification.php#L24
463 |
464 | For those notifications that contain the `transactionReference`, this can be
465 | obtained:
466 |
467 | $notification->getTransactionReference();
468 |
469 | For any notifications that do not involve a transaction, this will be `null`.
470 | Note that the webhook does not include the merchant `transactionId`,
471 | so there is nothing to tie the payment webhook back to a Hosted Page request.
472 | In this case, you can supply the `transactionId` as a query parameter
473 | on the `notifyUrl` when creating the Hosted Payment Page data.
474 | However, do be aware this ID will be visible to end users monitoring
475 | browser traffic, and the ID (being in the URL) will not be included
476 | in the notification signing, so could be faked. It is unlikely, but just
477 | be aware that it is a potential attack vector, so maybe self-sign the URL
478 | too.
479 |
480 | Notifications can be signed by the gateway using a `signatureKey`.
481 | By default, this notification handler will verify the `signature`
482 | and throw an exception if it failes to validate against the key
483 | you provide when fetching the result of the transaction.
484 |
485 | A manual check of the `signature` can be made using:
486 |
487 | $notification->isSignatureValid()
488 |
489 | This will return `true` if the signature is valid, or `false` if any
490 | part of the verification process fails.
491 |
492 | Validation of the `signature` can be disabled if needed:
493 |
494 | $gateway->setDisableWebhookSignature(true);
495 |
496 | For consistency with other Omipay Drivers, this driver *may* make an
497 | opinionated decision on how the `transactionId` is passed into the
498 | notification handler, but only after researchign how other people are
499 | handling it.
500 | There is a front-end way to do it through an iframe, but it seems
501 | vulnerable to user manipulation to me.
502 |
503 |
--------------------------------------------------------------------------------