Skip to content

Commit

Permalink
Add support for adding payment method without attaching to customer
Browse files Browse the repository at this point in the history
Includes better success code handling for different possible
codes that show up for adding payment methods.
  • Loading branch information
nickyr committed Dec 7, 2016
1 parent c33fd50 commit 0ac8731
Show file tree
Hide file tree
Showing 23 changed files with 532 additions and 131 deletions.
14 changes: 1 addition & 13 deletions src/Message/AbstractHOARequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

abstract class AbstractHOARequest extends AbstractRequest
{
public static $REGULAR_REQUEST_CLASS = 'Omnipay\Vindicia\Message\AbstractRequest';
protected static $REGULAR_REQUEST_CLASS = '\Omnipay\Vindicia\Message\AbstractRequest';

/**
* The corresponding regular (non-HOA) request. This is used to fake
Expand Down Expand Up @@ -203,18 +203,6 @@ protected function buildPrivateFormValues($keySoFar, $member)
);
}
return $values;




// $values = array();
// foreach ($member as $key => $value) {
// $values = array_merge(
// $this->buildPrivateFormValues($keySoFar . '_' . $key, $value),
// $values
// );
// }
// return $values;
} elseif (isset($member)) {
return array(new NameValue($keySoFar, $member));
} else {
Expand Down
14 changes: 8 additions & 6 deletions src/Message/AbstractRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ abstract class AbstractRequest extends \Omnipay\Common\Message\AbstractRequest
*/
const DEFAULT_MIN_CHARGEBACK_PROBABILITY = 100;

/**
* The class to use for the response.
*
* @var string
*/
protected static $RESPONSE_CLASS = '\Omnipay\Vindicia\Message\Response';

/**
* Create a new Request
*
Expand Down Expand Up @@ -637,7 +644,7 @@ public function sendData($data)
ini_set('soap.wsdl_cache_ttl', $originalWsdlCacheTtl);
ini_set('default_socket_timeout', $originalSocketTimeout);

$this->response = $this->buildResponse($response);
$this->response = new static::$RESPONSE_CLASS($this, $response);
return $this->response;
}

Expand All @@ -653,11 +660,6 @@ public function send()
return parent::send();
}

protected function buildResponse($response)
{
return new Response($this, $response);
}

protected function isUpdate()
{
return $this->isUpdate;
Expand Down
18 changes: 7 additions & 11 deletions src/Message/CaptureRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@
*/
class CaptureRequest extends AbstractRequest
{
/**
* The class to use for the response.
*
* @var string
*/
protected static $RESPONSE_CLASS = '\Omnipay\Vindicia\Message\CaptureResponse';

/**
* The name of the function to be called in Vindicia's API
*
Expand Down Expand Up @@ -130,15 +137,4 @@ public function send()
*/
return parent::send();
}

/**
* Use a special response object for Capture requests.
*
* @param object $response
* @return CaptureResponse
*/
protected function buildResponse($response)
{
return new CaptureResponse($this, $response);
}
}
2 changes: 1 addition & 1 deletion src/Message/CaptureResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function isSuccessful()
// Check all the other response codes that come back and make sure they match up
if ($success
&& (!isset($this->data->return)
|| intval($this->data->return->returnCode) !== self::SUCCESS_CODE
|| !in_array(intval($this->data->return->returnCode), static::getSuccessCodes(), true)
|| intval($this->data->qtySuccess) !== 1
|| intval($this->data->qtyFail) !== 0
)
Expand Down
12 changes: 7 additions & 5 deletions src/Message/CompleteHOARequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
*/
class CompleteHOARequest extends AbstractRequest
{
/**
* The class to use for the response.
*
* @var string
*/
protected static $RESPONSE_CLASS = '\Omnipay\Vindicia\Message\CompleteHOAResponse';

/**
* The name of the function to be called in Vindicia's API
*
Expand Down Expand Up @@ -80,9 +87,4 @@ public function send()
*/
return parent::send();
}

protected function buildResponse($response)
{
return new CompleteHOAResponse($this, $response);
}
}
51 changes: 47 additions & 4 deletions src/Message/CompleteHOAResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* they include a response for the method that was actually
* called by HOA. If the request itself fails, we return its
* error message and code. Otherwise, we return the status
* for the method called by HOA. getFailureType or
* for the method called by HOA.
* isRequestFailure and isMethodFailure can be used to
* determine if it was a request failure or method failure.
*/
Expand All @@ -13,6 +13,7 @@
use Omnipay\Common\Exception\InvalidResponseException;
use Omnipay\Common\Message\RequestInterface;
use Omnipay\Vindicia\Attribute;
use ReflectionClass;

class CompleteHOAResponse extends Response
{
Expand All @@ -32,6 +33,10 @@ class CompleteHOAResponse extends Response
* @var string|null
*/
protected $failureType;
/**
* @var bool
*/
protected $isSuccessful;

// Cached objects:
protected $formValues;
Expand Down Expand Up @@ -60,16 +65,45 @@ public function __construct(RequestInterface $request, $data)
if (isset($this->data->session->apiReturn)) {
$this->code = $this->data->session->apiReturn->returnCode;
$this->message = $this->data->session->apiReturn->returnString;
if (!$this->isSuccessful()) {

if ($this->wasAuthorize()) {
$requestClass = '\Omnipay\Vindicia\Message\HOAAuthorizeRequest';
} elseif ($this->wasPurchase()) {
$requestClass = '\Omnipay\Vindicia\Message\HOAPurchaseRequest';
} elseif ($this->wasCreatePaymentMethod()) {
$requestClass = '\Omnipay\Vindicia\Message\HOACreatePaymentMethodRequest';
} elseif ($this->wasCreateSubscription()) {
$requestClass = '\Omnipay\Vindicia\Message\HOACreateSubscriptionRequest';
} else {
// sometimes Vindicia doesn't return any method info on a failure
$this->isSuccessful = false;
$this->failureType = self::METHOD_FAILURE;
return;
}

$requestReflection = new ReflectionClass($requestClass);
$regularRequestClassProperty = $requestReflection->getProperty('REGULAR_REQUEST_CLASS');
$regularRequestClassProperty->setAccessible(true);
$regularRequestClass = $regularRequestClassProperty->getValue();
$regularRequestClassReflection = new ReflectionClass($regularRequestClass);
$regularRequestResponseClassProperty = $regularRequestClassReflection->getProperty('RESPONSE_CLASS');
$regularRequestResponseClassProperty->setAccessible(true);
$regularRequestResponseClass = $regularRequestResponseClassProperty->getValue();
$regularRequestResponseSuccessCodes = $regularRequestResponseClass::getSuccessCodes();

$this->isSuccessful = in_array(intval($this->getCode()), $regularRequestResponseSuccessCodes, true);
if (!$this->isSuccessful) {
$this->failureType = self::METHOD_FAILURE;
}

return;
}

// otherwise, we want the response from the request
$this->code = parent::getCode();
$this->message = parent::getMessage();
if (!$this->isSuccessful()) {
$this->isSuccessful = parent::isSuccessful();
if (!$this->isSuccessful) {
$this->failureType = self::REQUEST_FAILURE;
}
}
Expand Down Expand Up @@ -104,6 +138,11 @@ public function getCode()
throw new InvalidResponseException('Response has no code.');
}

public function isSuccessful()
{
return $this->isSuccessful;
}

public function getTransaction()
{
if (isset($this->transaction)) {
Expand Down Expand Up @@ -154,6 +193,7 @@ public function getSubscription()
* method called by HOA failed. Returns null if the request was successful.
*
* @return string|null
* @deprecated
*/
public function getFailureType()
{
Expand Down Expand Up @@ -248,7 +288,10 @@ public function wasPurchase()
*/
public function wasCreatePaymentMethod()
{
return $this->getMethod() === 'accountUpdatePaymentMethod';
return in_array($this->getMethod(), array(
'accountUpdatePaymentMethod',
'paymentMethodUpdate' // @todo double check this
), true);
}

/**
Expand Down
18 changes: 7 additions & 11 deletions src/Message/CreatePayPalSubscriptionRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@
*/
class CreatePayPalSubscriptionRequest extends CreateSubscriptionRequest
{
/**
* The class to use for the response.
*
* @var string
*/
protected static $RESPONSE_CLASS = '\Omnipay\Vindicia\Message\CreatePayPalSubscriptionResponse';

public function getData($paymentMethodType = self::PAYMENT_METHOD_CREDIT_CARD)
{
$this->validate('returnUrl', 'cancelUrl');
Expand All @@ -129,15 +136,4 @@ public function send()
*/
return parent::send();
}

/**
* Use a special response object for PayPal subscription requests.
*
* @param object $response
* @return CreatePayPalSubscriptionResponse
*/
protected function buildResponse($response)
{
return new CreatePayPalSubscriptionResponse($this, $response);
}
}
67 changes: 42 additions & 25 deletions src/Message/CreatePaymentMethodRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@
use Omnipay\Common\Exception\InvalidRequestException;

/**
* Create a new payment method and attach it to a customer. Or, update an existing
* payment method.
* Create a new payment method. Or, update an existing payment method. If a customer id
* or reference is provided, the payment method will be attached to that customer.
*
* Note: You can also create a payment method in the same request as creating a customer.
* See Message\CreateCustomerRequest.
*
* Parameters:
* - customerId: Your identifier for the customer to whom this payment method will belong.
* Either customerId or customerReference is required.
* - customerReference: The gateway's identifier for the customer to whom this payment method
* will belong. Either customerId or customerReference is required.
* - card: The card details you're adding. Required.
* - paymentMethodId: Your identifier for the payment method. Required.
* - customerId: Your identifier for the customer to whom this payment method will belong.
* If provided, the customer must already exist.
* - customerReference: The gateway's identifier for the customer to whom this payment method
* will belong. If provided, the customer must already exist.
* - validate: If set to true, Vindicia will validate the card before adding it (generally
* by a 99 cent authorization). Validation may include CVV and AVS validation as well, if set
* up with Vindicia. Default is false.
Expand Down Expand Up @@ -97,6 +97,7 @@
* } else {
* // error handling
* }
*
* </code>
*/
class CreatePaymentMethodRequest extends AbstractRequest
Expand All @@ -115,6 +116,13 @@ class CreatePaymentMethodRequest extends AbstractRequest
const VALIDATE_CARD = 'Validate';
const SKIP_CARD_VALIDATION = 'Update';

/**
* The class to use for the response.
*
* @var string
*/
protected static $RESPONSE_CLASS = '\Omnipay\Vindicia\Message\CreatePaymentMethodResponse';

public function initialize(array $parameters = array())
{
$this->cardRequired = true;
Expand Down Expand Up @@ -144,12 +152,12 @@ public function initialize(array $parameters = array())
*/
protected function getFunction()
{
return 'updatePaymentMethod';
return $this->hasCustomer() ? 'updatePaymentMethod' : 'update';
}

protected function getObject()
{
return self::$CUSTOMER_OBJECT;
return $this->hasCustomer() ? self::$CUSTOMER_OBJECT : self::$PAYMENT_METHOD_OBJECT;
}

/**
Expand Down Expand Up @@ -284,29 +292,38 @@ public function getData($paymentMethodType = self::PAYMENT_METHOD_CREDIT_CARD)
);
}

$customerId = $this->getCustomerId();
$customerReference = $this->getCustomerReference();
if (!$customerId && !$customerReference) {
throw new InvalidRequestException('Either the customerId or customerReference parameter is required.');
}

if ($this->getCardRequired()) {
$this->validate('card');
}

$account = new stdClass();
$account->merchantAccountId = $customerId;
$account->VID = $customerReference;
$data = array(
'action' => $this->getFunction(),
'ignoreAvsPolicy' => $this->getSkipAvsValidation(),
'ignoreCvnPolicy' => $this->getSkipCvvValidation(),
'paymentMethod' => $this->buildPaymentMethod($paymentMethodType, true)
);

if ($this->hasCustomer()) {
$account = new stdClass();
$account->merchantAccountId = $this->getCustomerId();
$account->VID = $this->getCustomerReference();

$data = array();
$data['account'] = $account;
$data['paymentMethod'] = $this->buildPaymentMethod($paymentMethodType, true);
$data['action'] = $this->getFunction();
$data['replaceOnAllAutoBills'] = $this->getUpdateSubscriptions();
$data['updateBehavior'] = $this->getValidate() ? self::VALIDATE_CARD : self::SKIP_CARD_VALIDATION;
$data['ignoreAvsPolicy'] = $this->getSkipAvsValidation();
$data['ignoreCvnPolicy'] = $this->getSkipCvvValidation();
$data['account'] = $account;
$data['replaceOnAllAutoBills'] = $this->getUpdateSubscriptions();
$data['updateBehavior'] = $this->getValidate() ? self::VALIDATE_CARD : self::SKIP_CARD_VALIDATION;
} else {
$data['validate'] = $this->getValidate();
$data['minChargebackProbability'] = $this->getMinChargebackProbability();
$data['replaceOnAllAutoBills'] = $this->getUpdateSubscriptions();
$data['replaceOnAllChildAutoBills'] = $this->getUpdateSubscriptions();
$data['sourceIp'] = $this->getIp();
}

return $data;
}

protected function hasCustomer()
{
return ($this->getCustomerId() !== null || $this->getCustomerReference() !== null);
}
}
12 changes: 12 additions & 0 deletions src/Message/CreatePaymentMethodResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Omnipay\Vindicia\Message;

class CreatePaymentMethodResponse extends Response
{
protected static $SUCCESS_CODES = array(
200,
228, // Payment method saved but missing associated account - unable to replace on autobills
261 // All active AutoBills were updated. AutoBills which are both expired and Suspended cannot be updated.
);
}
Loading

0 comments on commit 0ac8731

Please sign in to comment.