diff --git a/README.md b/README.md index 898cc948..e3b693c3 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The following gateways are provided by this package: * AuthorizeNet_SIM * AuthorizeNet_DPM -In addition, `Accept.JS` is supported by the AIM driver. More details are provided below. +In addition, `Accept.JS` is supported by the AIM driver and CIM (create card). More details are provided below. For general usage instructions, please see the main [Omnipay](https://github.com/thephpleague/omnipay) repository. @@ -45,15 +45,15 @@ The card is tokenized into two values returned in `opaqueData` object from Accep These two values must be POSTed back to the merchant application, usually as a part of the payment form. Make sure the raw credit card details are NOT posted back to your site. -How this is handled is beyond this short note, but examples are always welcome in the documentation. +How this is handled is beyond this short note, but examples are always welcomed in the documentation. -On the server, the tokenized detailt are passed into the `payment` or `authorize` request object. +On the server, the tokenized details are passed into the `payment` or `authorize` request object. You will still need to pass in the `CreditCard` object, as that contains details of the payee and recipient, but just leave the credit card details of that object blank. For example: ```php // $gateway is an instantiation of the AIM driver. -// $dataDescriptor and $dataValue come from the paymentr form at the front end. +// $dataDescriptor and $dataValue come from the payment form at the front end. $request = $gateway->purchase( [ @@ -66,6 +66,55 @@ $request = $gateway->purchase( ); ``` +CIM Create Card feature usage: +Accept.js must be implemented on your frontend payment form, once Accept.js 'tokenizes' the customer's +card, just send the two opaque fields and remove the Card's (Number, Expiration and CVV) from your post request. + +Accept.js goal is to remove the need of Card information from ever going into your server so be sure to remove that data +before posting to your server. + +The create card feature on CIM will automatically create a Customer Profile and a Payment Profile with the +'tokenized' card for each customer you request it for on your authorize.net account, you can use these Payment Profiles +later to request payments from your customers. + +In order to create a Customer & Payment Profile pass the opaque fields and the card array with the billing information +to the createCard method on the CIM driver: + +```php +// $gateway is an instantiation of the CIM driver. //Omnipay::create( 'AuthorizeNet_CIM' ) +// $dataDescriptor and $dataValue come from the payment form at the front end. + +$request = $gateway->createCard( + [ + 'opaqueDataDescriptor' => $dataDescriptor, + 'opaqueDataValue' => $dataValue, + 'name' => $name, + 'email' => $email, //Authorize.net will use the email to identify the CustomerProfile + 'customerType' => 'individual', + 'customerId' => $user_customer_id,//a customer ID generated by your system or send null + 'description' => 'MEMBER',//whichever description you wish to send + 'forceCardUpdate' => true + 'card' => [ + 'billingFirstName' => $name, + 'billingLastName' => $last_name, + 'billingAddress1' => $address, + 'billingCity' => $city, + 'billingState' => $state, + 'billingPostcode' => $zipcode, + 'billingPhone' => '', + //... may include shipping info but do not include card (number, cvv or expiration) + ], + ] +); +$response = $request->send(); +$data = $response->getData(); + +$data['paymentProfile']['customerProfileId']; +$data['paymentProfile']['customerPaymentProfileId']; +//Now you can use these 2 fields to reference this customer and this payment profile for later use with +//the rest of the CIM driver features as usual. +``` + ## DPM and SIM Signatures DPM and SIM used to sign their requests with the `transactionKey` using the mdh HMAC algorithm. diff --git a/src/CIMGateway.php b/src/CIMGateway.php index b515f5b2..ee824974 100644 --- a/src/CIMGateway.php +++ b/src/CIMGateway.php @@ -46,6 +46,11 @@ public function createCard(array $parameters = array()) return $this->createRequest('\Omnipay\AuthorizeNet\Message\CIMCreateCardRequest', $parameters); } + public function updateCard(array $parameters = array()) + { + return $this->createRequest('\Omnipay\AuthorizeNet\Message\CIMUpdatePaymentProfileRequest', $parameters); + } + public function getPaymentProfile(array $parameters = array()) { return $this->createRequest('\Omnipay\AuthorizeNet\Message\CIMGetPaymentProfileRequest', $parameters); diff --git a/src/Message/CIMCreateCardRequest.php b/src/Message/CIMCreateCardRequest.php index 99313b66..e87638a2 100644 --- a/src/Message/CIMCreateCardRequest.php +++ b/src/Message/CIMCreateCardRequest.php @@ -13,12 +13,9 @@ class CIMCreateCardRequest extends CIMAbstractRequest public function getData() { - $this->validate('card'); - - /** @var CreditCard $card */ - $card = $this->getCard(); - $card->validate(); + $this->validate('card'); + $this->cardValidate(); $data = $this->getBaseData(); $this->addProfileData($data); $this->addTransactionSettings($data); @@ -26,6 +23,23 @@ public function getData() return $data; } + /** + * Validate card or skip if opaque data is available + * + * @param \SimpleXMLElement $data + */ + protected function cardValidate() + { + + if ($this->getOpaqueDataDescriptor() && $this->getOpaqueDataValue()) { + return; + } + + /** @var CreditCard $card */ + $card = $this->getCard(); + $card->validate(); + } + /** * Add customer profile data to the specified xml element * @@ -97,12 +111,18 @@ protected function addBillingData(\SimpleXMLElement $data) } $req = $data->addChild('payment'); - $req->creditCard->cardNumber = $card->getNumber(); - $req->creditCard->expirationDate = $card->getExpiryDate('Y-m'); - if ($card->getCvv()) { - $req->creditCard->cardCode = $card->getCvv(); + if ($this->getOpaqueDataDescriptor() && $this->getOpaqueDataValue()) { + //Use opaqueData if available instead of card data + $req->opaqueData->dataDescriptor = $this->getOpaqueDataDescriptor(); + $req->opaqueData->dataValue = $this->getOpaqueDataValue(); } else { - $this->setValidationMode(self::VALIDATION_MODE_NONE); + $req->creditCard->cardNumber = $card->getNumber(); + $req->creditCard->expirationDate = $card->getExpiryDate('Y-m'); + if ($card->getCvv()) { + $req->creditCard->cardCode = $card->getCvv(); + } else { + $this->setValidationMode(self::VALIDATION_MODE_NONE); + } } } } @@ -194,8 +214,11 @@ public function createPaymentProfile(CIMCreateCardResponse $createCardResponse) $createPaymentProfileResponse = $this->makeCreatePaymentProfileRequest($parameters); if ($createPaymentProfileResponse->isSuccessful()) { $parameters['customerPaymentProfileId'] = $createPaymentProfileResponse->getCustomerPaymentProfileId(); - } elseif ($this->getForceCardUpdate() !== true) { + } elseif ($this->getForceCardUpdate() !== true || + ($this->getOpaqueDataDescriptor() && $this->getOpaqueDataValue()) + ) { // force card update flag turned off. No need to further process. + // also if opaque data is being used we are not able to update existing payment profiles return $createCardResponse; } diff --git a/src/Message/CIMCreatePaymentProfileRequest.php b/src/Message/CIMCreatePaymentProfileRequest.php index f2c4ed92..4c399146 100644 --- a/src/Message/CIMCreatePaymentProfileRequest.php +++ b/src/Message/CIMCreatePaymentProfileRequest.php @@ -14,11 +14,7 @@ class CIMCreatePaymentProfileRequest extends CIMCreateCardRequest public function getData() { $this->validate('card', 'customerProfileId'); - - /** @var CreditCard $card */ - $card = $this->getCard(); - $card->validate(); - + $this->cardValidate(); $data = $this->getBaseData(); $data->customerProfileId = $this->getCustomerProfileId(); $this->addPaymentProfileData($data); diff --git a/src/Message/CIMUpdatePaymentProfileRequest.php b/src/Message/CIMUpdatePaymentProfileRequest.php index 4677adfb..fe6e8973 100644 --- a/src/Message/CIMUpdatePaymentProfileRequest.php +++ b/src/Message/CIMUpdatePaymentProfileRequest.php @@ -15,9 +15,7 @@ public function getData() { $this->validate('card', 'customerProfileId', 'customerPaymentProfileId'); - /** @var CreditCard $card */ - $card = $this->getCard(); - $card->validate(); + $this->cardValidate(); $data = $this->getBaseData(); $data->customerProfileId = $this->getCustomerProfileId(); diff --git a/tests/CIMGatewayTest.php b/tests/CIMGatewayTest.php index 3d4a85dd..5c6e1a21 100644 --- a/tests/CIMGatewayTest.php +++ b/tests/CIMGatewayTest.php @@ -31,6 +31,18 @@ public function setUp() 'forceCardUpdate' => true ); + $validCard = $this->getValidCard(); + unset($validCard['number'],$validCard['expiryMonth'],$validCard['expiryYear'],$validCard['cvv']); + //remove the actual card data since we are setting opaque values + $this->createCardFromOpaqueDataOptions = array( + 'email' => "kaylee@serenity.com", + 'card' => $validCard, + 'opaqueDataDescriptor' => 'COMMON.ACCEPT.INAPP.PAYMENT', + 'opaqueDataValue' => 'jb2RlIjoiNTB', + 'testMode' => true, + 'forceCardUpdate' => true + ); + $this->authorizeOptions = array( 'cardReference' => '{"customerProfileId":"28972084","customerPaymentProfileId":"26317840","customerShippingAddressId":"27057149"}', 'amount' => 10.00, @@ -89,6 +101,20 @@ public function testCreateCardSuccess() $this->assertSame('Successful.', $response->getMessage()); } + public function testCreateCardFromOpaqueDataSuccess() + { + $this->setMockHttpResponse(array('CIMCreateCardSuccess.txt','CIMGetPaymentProfileSuccess.txt')); + + $response = $this->gateway->createCard($this->createCardFromOpaqueDataOptions)->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertSame( + '{"customerProfileId":"28972084","customerPaymentProfileId":"26485433"}', + $response->getCardReference() + ); + $this->assertSame('Successful.', $response->getMessage()); + } + public function testShouldCreateCardIfDuplicateCustomerProfileExists() { $this->setMockHttpResponse(array('CIMCreateCardFailureWithDuplicate.txt', 'CIMCreatePaymentProfileSuccess.txt', @@ -104,6 +130,21 @@ public function testShouldCreateCardIfDuplicateCustomerProfileExists() $this->assertSame('Successful.', $response->getMessage()); } + public function testShouldCreateCardFromOpaqueDataIfDuplicateCustomerProfileExists() + { + $this->setMockHttpResponse(array('CIMCreateCardFailureWithDuplicate.txt', 'CIMCreatePaymentProfileSuccess.txt', + 'CIMGetProfileSuccess.txt', 'CIMGetPaymentProfileSuccess.txt')); + + $response = $this->gateway->createCard($this->createCardFromOpaqueDataOptions)->send(); + + $this->assertTrue($response->isSuccessful()); + $this->assertSame( + '{"customerProfileId":"28775801","customerPaymentProfileId":"26485433"}', + $response->getCardReference() + ); + $this->assertSame('Successful.', $response->getMessage()); + } + public function testShouldUpdateExistingPaymentProfileIfDuplicateExistsAndForceCardUpdateIsSet() { // Duplicate **payment** profile @@ -120,6 +161,17 @@ public function testShouldUpdateExistingPaymentProfileIfDuplicateExistsAndForceC $this->assertSame('Successful.', $response->getMessage()); } + public function testDoesntUpdateExistingPaymentProfileFromOpaqueData() + { + // Duplicate **payment** profile + $this->setMockHttpResponse(array('CIMCreateCardFailureWithDuplicate.txt', 'CIMCreatePaymentProfileFailure.txt', + 'CIMGetProfileSuccess.txt', 'CIMUpdatePaymentProfileSuccess.txt', 'CIMGetPaymentProfileSuccess.txt')); + + $response = $this->gateway->createCard($this->createCardFromOpaqueDataOptions)->send(); + + $this->assertFalse($response->isSuccessful()); + } + public function testShouldUpdateExistingPaymentProfileIfDuplicateExistsAndMaxPaymentProfileLimitIsMet() { $this->setMockHttpResponse(array('CIMCreateCardFailureWithDuplicate.txt', diff --git a/tests/Message/CIMCreateCardRequestTest.php b/tests/Message/CIMCreateCardRequestTest.php index 92911b00..f97eee6b 100644 --- a/tests/Message/CIMCreateCardRequestTest.php +++ b/tests/Message/CIMCreateCardRequestTest.php @@ -59,4 +59,25 @@ public function testGetDataShouldSetValidationModeToNoneIfNoCvvProvided() $this->assertFalse(isset($data->profile->paymentProfiles->payment->creditCard->cardCode)); $this->assertEquals(CIMCreatePaymentProfileRequest::VALIDATION_MODE_NONE, $this->request->getValidationMode()); } + + public function testGetDataOpaqueData() + { + + $validCard = $this->getValidCard(); + unset($validCard['number'],$validCard['expiryMonth'],$validCard['expiryYear'],$validCard['cvv']); + //remove the actual card data since we are setting opaque values + $this->params = array( + 'email' => "kaylee@serenity.com", + 'card' => $validCard, + 'opaqueDataDescriptor' => 'COMMON.ACCEPT.INAPP.PAYMENT', + 'opaqueDataValue' => 'jb2RlIjoiNTB', + 'developerMode' => true + ); + $this->request->initialize($this->params); + + $data = $this->request->getData(); + + $this->assertEquals('COMMON.ACCEPT.INAPP.PAYMENT', $data->profile->paymentProfiles->payment->opaqueData->dataDescriptor); + $this->assertEquals('jb2RlIjoiNTB', $data->profile->paymentProfiles->payment->opaqueData->dataValue); + } } diff --git a/tests/Message/CIMCreatePaymentProfileRequestTest.php b/tests/Message/CIMCreatePaymentProfileRequestTest.php index cf407618..eaad61a7 100644 --- a/tests/Message/CIMCreatePaymentProfileRequestTest.php +++ b/tests/Message/CIMCreatePaymentProfileRequestTest.php @@ -31,4 +31,25 @@ public function testGetData() $this->assertEquals($card['number'], $data->paymentProfile->payment->creditCard->cardNumber); $this->assertEquals('testMode', $data->validationMode); } + + public function testGetDataOpaqueData() + { + $validCard = $this->getValidCard(); + unset($validCard['number'],$validCard['expiryMonth'],$validCard['expiryYear'],$validCard['cvv']); + //remove the actual card data since we are setting opaque values + $this->request->initialize( + array( + 'customerProfileId' => '28775801', + 'email' => "kaylee@serenity.com", + 'card' => $validCard, + 'opaqueDataDescriptor' => 'COMMON.ACCEPT.INAPP.PAYMENT', + 'opaqueDataValue' => 'jb2RlIjoiNTB', + 'developerMode' => true + ) + ); + + $data = $this->request->getData(); + $this->assertEquals('COMMON.ACCEPT.INAPP.PAYMENT', $data->paymentProfile->payment->opaqueData->dataDescriptor); + $this->assertEquals('jb2RlIjoiNTB', $data->paymentProfile->payment->opaqueData->dataValue); + } }