class AcceptJs in Commerce Authorize.Net 8
Same name in this branch
- 8 src/Plugin/Commerce/PaymentType/AcceptJs.php \Drupal\commerce_authnet\Plugin\Commerce\PaymentType\AcceptJs
- 8 src/Plugin/Commerce/PaymentGateway/AcceptJs.php \Drupal\commerce_authnet\Plugin\Commerce\PaymentGateway\AcceptJs
Provides the Accept.js payment gateway.
Plugin annotation
@CommercePaymentGateway(
id = "authorizenet_acceptjs",
label = "Authorize.net (Accept.js)",
display_label = "Authorize.net",
forms = {
"add-payment-method" = "Drupal\commerce_authnet\PluginForm\AcceptJs\PaymentMethodAddForm",
"approve-payment" = "Drupal\commerce_authnet\PluginForm\AcceptJs\PaymentApproveForm",
"decline-payment" = "Drupal\commerce_authnet\PluginForm\AcceptJs\PaymentDeclineForm",
},
payment_type = "acceptjs",
payment_method_types = {"credit_card"},
credit_card_types = {
"amex", "dinersclub", "discover", "jcb", "mastercard", "visa", "unionpay"
},
requires_billing_information = FALSE,
)
Hierarchy
- class \Drupal\Component\Plugin\PluginBase implements DerivativeInspectionInterface, PluginInspectionInterface
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
- class \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\PaymentGatewayBase implements PaymentGatewayInterface, ContainerFactoryPluginInterface uses PluginWithFormsTrait
- class \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OnsitePaymentGatewayBase implements OnsitePaymentGatewayInterface
- class \Drupal\commerce_authnet\Plugin\Commerce\PaymentGateway\OnsiteBase implements OnsitePaymentGatewayInterface, SupportsAuthorizationsInterface
- class \Drupal\commerce_authnet\Plugin\Commerce\PaymentGateway\AcceptJs implements AcceptJsInterface
- class \Drupal\commerce_authnet\Plugin\Commerce\PaymentGateway\OnsiteBase implements OnsitePaymentGatewayInterface, SupportsAuthorizationsInterface
- class \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OnsitePaymentGatewayBase implements OnsitePaymentGatewayInterface
- class \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\PaymentGatewayBase implements PaymentGatewayInterface, ContainerFactoryPluginInterface uses PluginWithFormsTrait
- class \Drupal\Core\Plugin\PluginBase uses DependencySerializationTrait, MessengerTrait, StringTranslationTrait
Expanded class hierarchy of AcceptJs
File
- src/
Plugin/ Commerce/ PaymentGateway/ AcceptJs.php, line 54
Namespace
Drupal\commerce_authnet\Plugin\Commerce\PaymentGatewayView source
class AcceptJs extends OnsiteBase implements AcceptJsInterface {
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'cca_status' => FALSE,
'cca_api_id' => '',
'cca_org_unit_id' => '',
'cca_api_key' => '',
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['cca_status'] = [
'#type' => 'checkbox',
'#title' => $this
->t('Enable Cardinal Cruise Authentication'),
'#default_value' => $this->configuration['cca_status'],
];
$form['cca'] = [
'#type' => 'fieldset',
'#title' => $this
->t('Cardinal Cruise Authentication'),
'#states' => [
'visible' => [
'input[name="configuration[authorizenet_acceptjs][cca_status]"]' => [
'checked' => TRUE,
],
],
],
'#description' => $this
->t('In test mode the credentials provided here are not used (but the fields are still required).'),
];
$form['cca']['cca_api_id'] = [
'#type' => 'textfield',
'#title' => $this
->t('API Identifier'),
'#default_value' => $this->configuration['cca_api_id'],
'#states' => [
'required' => [
'input[name="configuration[authorizenet_acceptjs][cca_status]"]' => [
'checked' => TRUE,
],
],
],
];
$form['cca']['cca_org_unit_id'] = [
'#type' => 'textfield',
'#title' => $this
->t('Org Unit ID'),
'#default_value' => $this->configuration['cca_org_unit_id'],
'#states' => [
'required' => [
'input[name="configuration[authorizenet_acceptjs][cca_status]"]' => [
'checked' => TRUE,
],
],
],
];
$form['cca']['cca_api_key'] = [
'#type' => 'textfield',
'#title' => $this
->t('API key'),
'#default_value' => $this->configuration['cca_api_key'],
'#states' => [
'required' => [
'input[name="configuration[authorizenet_acceptjs][cca_status]"]' => [
'checked' => TRUE,
],
],
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
if (!$form_state
->getErrors()) {
$values = $form_state
->getValue($form['#parents']);
$this->configuration['cca_status'] = $values['cca_status'];
$this->configuration['cca_api_id'] = $values['cca']['cca_api_id'];
$this->configuration['cca_org_unit_id'] = $values['cca']['cca_org_unit_id'];
$this->configuration['cca_api_key'] = $values['cca']['cca_api_key'];
}
}
/**
* Get the CCA API Identifier.
*
* @return string
* The CCA API Identifier.
*/
public function getCcaApiId() {
if ($this->configuration['cca_status']) {
// Test API Id.
// @see https://developer.cardinalcommerce.com/try-it-now.shtml
if ($this->configuration['mode'] == 'test') {
return '582e0a2033fadd1260f990f6';
}
else {
return $this->configuration['cca_api_id'];
}
}
}
/**
* Get the CCA API Identifier.
*
* @return string
* The CCA API Identifier.
*/
public function getCcaOrgUnitId() {
if ($this->configuration['cca_status']) {
// Test Org Unit ID.
// @see https://developer.cardinalcommerce.com/try-it-now.shtml
if ($this->configuration['mode'] == 'test') {
return '582be9deda52932a946c45c4';
}
else {
return $this->configuration['cca_org_unit_id'];
}
}
}
/**
* Get the CCA API Key.
*
* @return string
* The CCA API Key.
*/
public function getCcaApiKey() {
if ($this->configuration['cca_status']) {
// Test API Key.
// @see https://developer.cardinalcommerce.com/try-it-now.shtml
if ($this->configuration['mode'] == 'test') {
return '754be3dc-10b7-471f-af31-f20ce12b9ec1';
}
else {
return $this->configuration['cca_api_key'];
}
}
}
/**
* {@inheritDoc}
*/
public function getJsLibrary() {
if ($this->configuration['cca_status']) {
if ($this
->getMode() === 'test') {
return 'commerce_authnet/cardinalcruise-dev';
}
return 'commerce_authnet/cardinalcruise';
}
return 'commerce_authnet/form-accept';
}
/**
* {@inheritdoc}
*/
public function createPayment(PaymentInterface $payment, $capture = TRUE) {
$this
->assertPaymentState($payment, [
'new',
]);
$payment_method = $payment
->getPaymentMethod();
$this
->assertPaymentMethod($payment_method);
$order = $payment
->getOrder();
$owner = $payment_method
->getOwner();
// Transaction request.
$transaction_request = new TransactionRequest([
'transactionType' => $capture ? TransactionRequest::AUTH_CAPTURE : TransactionRequest::AUTH_ONLY,
'amount' => $payment
->getAmount()
->getNumber(),
]);
$tempstore_3ds = $this->privateTempStore
->get('commerce_authnet')
->get($payment_method
->id());
if (!empty($tempstore_3ds)) {
// Do not send ECI and CAVV values when reusing a payment method.
$payment_storage = $this->entityTypeManager
->getStorage('commerce_payment');
$payment_method_has_been_used = $payment_storage
->getQuery()
->condition('payment_method', $payment_method
->id())
->range(0, 1)
->execute();
if (!$payment_method_has_been_used) {
$cardholder_authentication = new CardholderAuthentication();
$cardholder_authentication_empty = TRUE;
if (!empty($tempstore_3ds['eci']) && $tempstore_3ds['eci'] != '07') {
$cardholder_authentication->authenticationIndicator = $tempstore_3ds['eci'];
$cardholder_authentication_empty = FALSE;
}
if (!empty($tempstore_3ds['cavv'])) {
// This is quite undocumented, but seems that cavv needs to be
// urlencoded.
// @see https://community.developer.authorize.net/t5/Integration-and-Testing/Cardholder-Authentication-extraOptions-invalid-error/td-p/57955
$cardholder_authentication->cardholderAuthenticationValue = urlencode($tempstore_3ds['cavv']);
$cardholder_authentication_empty = FALSE;
}
if (!$cardholder_authentication_empty) {
$transaction_request
->addDataType($cardholder_authentication);
}
}
else {
$this->privateTempStore
->get('commerce_authnet')
->delete($payment_method
->id());
}
}
// @todo update SDK to support data type like this.
// Initializing the profile to charge and adding it to the transaction.
$customer_profile_id = $this
->getRemoteCustomerId($owner);
if (empty($customer_profile_id)) {
$customer_profile_id = $this
->getPaymentMethodCustomerId($payment_method);
}
$payment_profile_id = $this
->getRemoteProfileId($payment_method);
$profile_to_charge = new Profile([
'customerProfileId' => $customer_profile_id,
]);
$profile_to_charge
->addData('paymentProfile', [
'paymentProfileId' => $payment_profile_id,
]);
$transaction_request
->addData('profile', $profile_to_charge
->toArray());
$profiles = $order
->collectProfiles();
if (isset($profiles['shipping']) && !$profiles['shipping']
->get('address')
->isEmpty()) {
/** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $shipping_address */
$shipping_address = $profiles['shipping']
->get('address')
->first();
$ship_data = [
// @todo how to allow customizing this.
'firstName' => $shipping_address
->getGivenName(),
'lastName' => $shipping_address
->getFamilyName(),
'address' => substr($shipping_address
->getAddressLine1() . ' ' . $shipping_address
->getAddressLine2(), 0, 60),
'country' => $shipping_address
->getCountryCode(),
'company' => $shipping_address
->getOrganization(),
'city' => $shipping_address
->getLocality(),
'state' => $shipping_address
->getAdministrativeArea(),
'zip' => $shipping_address
->getPostalCode(),
];
$transaction_request
->addDataType(new ShipTo(array_filter($ship_data)));
}
// Adding order information to the transaction.
$transaction_request
->addOrder(new OrderDataType([
'invoiceNumber' => $order
->getOrderNumber() ?: $order
->id(),
]));
$transaction_request
->addData('customerIP', $order
->getIpAddress());
// Adding line items.
$line_items = $this
->getLineItems($order);
foreach ($line_items as $line_item) {
$transaction_request
->addLineItem($line_item);
}
// Adding tax information to the transaction.
$transaction_request
->addData('tax', $this
->getTax($order)
->toArray());
$transaction_request
->addData('shipping', $this
->getShipping($order)
->toArray());
$request = new CreateTransactionRequest($this->authnetConfiguration, $this->httpClient);
$request
->setTransactionRequest($transaction_request);
$response = $request
->execute();
if ($response
->getResultCode() != 'Ok') {
$this
->logResponse($response);
$message = $response
->getMessages()[0];
switch ($message
->getCode()) {
case 'E00040':
$payment_method
->delete();
throw new PaymentGatewayException('The provided payment method is no longer valid');
case 'E00042':
$payment_method
->delete();
throw new PaymentGatewayException('You cannot add more than 10 payment methods.');
default:
throw new PaymentGatewayException($message
->getText());
}
}
if (!empty($response
->getErrors())) {
$message = $response
->getErrors()[0];
throw new HardDeclineException($message
->getText());
}
// Select the next state based on fraud detection results.
$code = $response
->getMessageCode();
$expires = 0;
$next_state = 'authorization';
if ($code == 1 && $capture) {
$next_state = 'completed';
}
elseif ($code == 252) {
$next_state = 'unauthorized_review';
$expires = strtotime('+5 days');
}
elseif ($code == 253) {
$next_state = 'authorization_review';
$expires = strtotime('+5 days');
}
$payment
->setExpiresTime($expires);
$payment
->setState($next_state);
$payment
->setRemoteId($response->transactionResponse->transId);
$payment
->setAvsResponseCode($response->transactionResponse->avsResultCode);
// @todo Find out how long an authorization is valid, set its expiration.
$payment
->save();
}
/**
* {@inheritdoc}
*/
public function approvePayment(PaymentInterface $payment) {
/** @var \Drupal\commerce_payment\Entity\PaymentMethod $payment_method */
$this
->assertPaymentState($payment, [
'unauthorized_review',
'authorization_review',
]);
if ($payment
->isExpired()) {
throw new HardDeclineException('This payment has expired.');
}
$request = new UpdateHeldTransactionRequest($this->authnetConfiguration, $this->httpClient);
$request
->setAction(UpdateHeldTransactionRequest::APPROVE);
$request
->setRefTransId($payment
->getRemoteId());
$response = $request
->execute();
if ($response
->getResultCode() != 'Ok') {
$this
->logResponse($response);
$message = $response
->getMessages()[0];
throw new PaymentGatewayException($message
->getText());
}
$new_state = $payment
->getState()
->getId() == 'unauthorized_review' ? 'authorization' : 'completed';
$payment
->setState($new_state);
$payment
->save();
}
/**
* {@inheritdoc}
*/
public function declinePayment(PaymentInterface $payment) {
/** @var \Drupal\commerce_payment\Entity\PaymentMethod $payment_method */
$this
->assertPaymentState($payment, [
'unauthorized_review',
'authorization_review',
]);
if ($payment
->isExpired()) {
throw new HardDeclineException('This payment has expired.');
}
$request = new UpdateHeldTransactionRequest($this->authnetConfiguration, $this->httpClient);
$request
->setAction(UpdateHeldTransactionRequest::DECLINE);
$request
->setRefTransId($payment
->getRemoteId());
$response = $request
->execute();
if ($response
->getResultCode() != 'Ok') {
$this
->logResponse($response);
$message = $response
->getMessages()[0];
throw new PaymentGatewayException($message
->getText());
}
$new_state = $payment
->getState()
->getId() == 'unauthorized_review' ? 'unauthorized_declined' : 'authorization_declined';
$payment
->setState($new_state);
$payment
->save();
}
/**
* {@inheritdoc}
*/
public function refundPayment(PaymentInterface $payment, Price $amount = NULL) {
/** @var \Drupal\commerce_payment\Entity\PaymentMethod $payment_method */
$payment_method = $payment
->getPaymentMethod();
$this
->assertPaymentState($payment, [
'completed',
'partially_refunded',
]);
// If not specified, refund the entire amount.
$amount = $amount ?: $payment
->getAmount();
$this
->assertRefundAmount($payment, $amount);
$request = new CreateTransactionRequest($this->authnetConfiguration, $this->httpClient);
$transaction_request = new TransactionRequest([
'transactionType' => TransactionRequest::REFUND,
'amount' => $amount
->getNumber(),
'refTransId' => $payment
->getRemoteId(),
]);
// Add billing information when available, to satisfy AVS.
if ($billing_profile = $payment_method
->getBillingProfile()) {
/** @var \Drupal\address\AddressInterface $address */
$address = $billing_profile
->get('address')
->first();
$bill_to = array_filter([
// @todo how to allow customizing this.
'firstName' => $address
->getGivenName(),
'lastName' => $address
->getFamilyName(),
'company' => $address
->getOrganization(),
'address' => substr($address
->getAddressLine1() . ' ' . $address
->getAddressLine2(), 0, 60),
'country' => $address
->getCountryCode(),
'city' => $address
->getLocality(),
'state' => $address
->getAdministrativeArea(),
'zip' => $address
->getPostalCode(),
]);
$transaction_request
->addDataType(new BillTo($bill_to));
}
// Adding order information to the transaction.
$order = $payment
->getOrder();
$transaction_request
->addOrder(new OrderDataType([
'invoiceNumber' => $order
->getOrderNumber() ?: $order
->id(),
]));
$transaction_request
->addPayment(new CreditCardDataType([
'cardNumber' => $payment_method->card_number->value,
'expirationDate' => str_pad($payment_method->card_exp_month->value, 2, '0', STR_PAD_LEFT) . str_pad($payment_method->card_exp_year->value, 2, '0', STR_PAD_LEFT),
]));
$request
->setTransactionRequest($transaction_request);
$response = $request
->execute();
if ($response
->getResultCode() != 'Ok') {
$this
->logResponse($response);
$message = $response
->getMessages()[0];
throw new PaymentGatewayException($message
->getText());
}
$old_refunded_amount = $payment
->getRefundedAmount();
$new_refunded_amount = $old_refunded_amount
->add($amount);
if ($new_refunded_amount
->lessThan($payment
->getAmount())) {
$payment
->setState('partially_refunded');
}
else {
$payment
->setState('refunded');
}
$payment
->setRefundedAmount($new_refunded_amount);
$payment
->save();
}
/**
* {@inheritdoc}
*
* @todo Needs kernel test
*/
public function createPaymentMethod(PaymentMethodInterface $payment_method, array $payment_details) {
// We don't want 3DS on the user payment method form.
if (!empty($this
->getConfiguration()['cca_status']) && !empty($payment_details['cca_jwt_token'])) {
if (empty($payment_details['cca_jwt_response_token'])) {
throw new PaymentGatewayException('Cannot continue when CCA is enabled but not used.');
}
/** @var \Lcobucci\JWT\Token $token */
$token = (new Parser())
->parse($payment_details['cca_jwt_response_token']);
$signer = new Sha256();
if (!$token
->verify($signer, $this
->getCcaApiKey())) {
throw new PaymentGatewayException('Response CCA JWT is not valid.');
}
$claims = $token
->getClaims();
/** @var \Lcobucci\JWT\Claim $payload */
$payload = $claims['Payload'];
if (isset($payload
->getValue()->Payment->ExtendedData->SignatureVerification) && $payload
->getValue()->Payment->ExtendedData->SignatureVerification === 'N') {
throw new PaymentGatewayException('Unsuccessful signature verification.');
}
}
$required_keys = [
'data_descriptor',
'data_value',
];
foreach ($required_keys as $required_key) {
if (empty($payment_details[$required_key])) {
throw new \InvalidArgumentException(sprintf('$payment_details must contain the %s key.', $required_key));
}
}
$remote_payment_method = $this
->doCreatePaymentMethod($payment_method, $payment_details);
$payment_method->card_type = $this
->mapCreditCardType($remote_payment_method['card_type']);
$payment_method->card_number = $remote_payment_method['last4'];
$payment_method->card_exp_month = $remote_payment_method['expiration_month'];
$payment_method->card_exp_year = $remote_payment_method['expiration_year'];
$payment_method
->setRemoteId($remote_payment_method['remote_id']);
$expires = CreditCard::calculateExpirationTimestamp($remote_payment_method['expiration_month'], $remote_payment_method['expiration_year']);
$payment_method
->setExpiresTime($expires);
$payment_method
->save();
if (!empty($this
->getConfiguration()['cca_status']) && !empty($payment_details['cca_jwt_token'])) {
$value = [];
if (isset($payload
->getValue()->Payment->ExtendedData->CAVV)) {
$value['cavv'] = $payload
->getValue()->Payment->ExtendedData->CAVV;
$this->privateTempStore
->get('commerce_authnet')
->set($payment_method
->id(), $value);
}
if (isset($payload
->getValue()->Payment->ExtendedData->ECIFlag)) {
$value['eci'] = $payload
->getValue()->Payment->ExtendedData->ECIFlag;
$this->privateTempStore
->get('commerce_authnet')
->set($payment_method
->id(), $value);
}
}
}
/**
* {@inheritdoc}
*/
public function updatePaymentMethod(PaymentMethodInterface $payment_method) {
$request = new UpdateCustomerPaymentProfileRequest($this->authnetConfiguration, $this->httpClient);
$customer_profile_id = $this
->getRemoteCustomerId($payment_method
->getOwner());
if (empty($customer_profile_id)) {
$customer_profile_id = $this
->getPaymentMethodCustomerId($payment_method);
}
$request
->setCustomerProfileId($customer_profile_id);
$payment_profile = new PaymentProfile([
'customerType' => 'individual',
]);
$payment_profile
->addCustomerPaymentProfileId($this
->getRemoteProfileId($payment_method));
if ($billing_profile = $payment_method
->getBillingProfile()) {
/** @var \Drupal\address\AddressInterface $address */
$address = $billing_profile
->get('address')
->first();
$bill_to = array_filter([
'firstName' => $address
->getGivenName(),
'lastName' => $address
->getFamilyName(),
'company' => $address
->getOrganization(),
'address' => substr($address
->getAddressLine1() . ' ' . $address
->getAddressLine2(), 0, 60),
'country' => $address
->getCountryCode(),
'city' => $address
->getLocality(),
'state' => $address
->getAdministrativeArea(),
'zip' => $address
->getPostalCode(),
]);
$payment_profile
->addBillTo(new BillTo($bill_to));
}
$request
->setPaymentProfile($payment_profile);
$payment_profile
->addPayment(new AuthnetCreditCard([
'cardNumber' => 'XXXX' . $payment_method
->get('card_number')->value,
'expirationDate' => sprintf('%s-%s', $payment_method
->get('card_exp_month')->value, $payment_method
->get('card_exp_year')->value),
]));
$response = $request
->execute();
if ($response
->getResultCode() != 'Ok') {
$this
->logResponse($response);
$error = $response
->getMessages()[0];
throw new DeclineException('Unable to update the payment method.');
}
}
/**
* Creates the payment method on the gateway.
*
* This handles customer and payment profile creation, along with logic to
* handle Authorize.net's duplicate profile detection.
*
* @link https://developer.authorize.net/api/reference/features/customer_profiles.html#Duplicate_Profile_Verification
*
* @param \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method
* The payment method.
* @param array $payment_details
* The gateway-specific payment details.
*
* @return array
* The payment method information returned by the gateway. Notable keys:
* - token: The remote ID.
* Credit card specific keys:
* - card_type: The card type.
* - last4: The last 4 digits of the credit card number.
* - expiration_month: The expiration month.
* - expiration_year: The expiration year.
*
* @todo Rename to customer profile
* @todo Make a method for just creating payment profile on existing profile.
*/
protected function doCreatePaymentMethod(PaymentMethodInterface $payment_method, array $payment_details) {
$owner = $payment_method
->getOwner();
$customer_profile_id = NULL;
$customer_data = [];
if ($owner && !$owner
->isAnonymous()) {
$customer_profile_id = $this
->getRemoteCustomerId($owner);
if (empty($customer_profile_id)) {
$customer_profile_id = $this
->getPaymentMethodCustomerId($payment_method);
}
$customer_data['email'] = $owner
->getEmail();
}
if ($customer_profile_id) {
$payment_profile = $this
->buildCustomerPaymentProfile($payment_method, $payment_details, $customer_profile_id);
$request = new CreateCustomerPaymentProfileRequest($this->authnetConfiguration, $this->httpClient);
$request
->setCustomerProfileId($customer_profile_id);
$request
->setPaymentProfile($payment_profile);
$response = $request
->execute();
if ($response
->getResultCode() != 'Ok') {
$this
->logResponse($response);
$error = $response
->getMessages()[0];
switch ($error
->getCode()) {
case 'E00039':
if (!isset($response->customerPaymentProfileId)) {
throw new InvalidResponseException('Duplicate payment profile ID, however could not get existing ID.');
}
break;
case 'E00040':
// The customer record ID is invalid, remove it.
// @note this should only happen in development scenarios.
$this
->setRemoteCustomerId($owner, NULL);
$owner
->save();
throw new InvalidResponseException('The customer record could not be found');
default:
throw new InvalidResponseException($error
->getText());
}
}
$payment_profile_id = $response->customerPaymentProfileId;
$validation_direct_response = explode(',', $response->validationDirectResponse);
}
else {
$request = new CreateCustomerProfileRequest($this->authnetConfiguration, $this->httpClient);
if ($owner
->isAuthenticated()) {
$profile = new Profile([
// @todo how to allow altering.
'merchantCustomerId' => $owner
->id(),
'email' => $owner
->getEmail(),
]);
}
else {
$profile = new Profile([
// @todo how to allow altering.
'merchantCustomerId' => $owner
->id() . '_' . $this->time
->getRequestTime(),
'email' => $payment_details['customer_email'],
]);
}
$request
->setProfile($profile);
// Due to their being a possible duplicate record for the customer
// profile, we cannot attach the payment profile in the initial request.
// If we did, it would invalidate the token generated by Accept.js and
// not allow us to reconcile duplicate payment methods.
//
// So we do not attach a payment profile and run two requests, and make
// sure no validation mode is executed.
$request
->setValidationMode(NULL);
$response = $request
->execute();
if ($response
->getResultCode() == 'Ok') {
$customer_profile_id = $response->customerProfileId;
}
else {
// Handle duplicate.
if ($response
->getMessages()[0]
->getCode() == 'E00039') {
$result = array_filter(explode(' ', $response
->getMessages()[0]
->getText()), 'is_numeric');
$customer_profile_id = reset($result);
}
else {
$this
->logResponse($response);
throw new InvalidResponseException("Unable to create customer profile.");
}
}
$payment_profile = $this
->buildCustomerPaymentProfile($payment_method, $payment_details, $customer_profile_id);
$request = new CreateCustomerPaymentProfileRequest($this->authnetConfiguration, $this->httpClient);
$request
->setCustomerProfileId($customer_profile_id);
$request
->setPaymentProfile($payment_profile);
$response = $request
->execute();
if ($response
->getResultCode() != 'Ok') {
// If the error is not due to duplicates, log and error.
if ($response
->getMessages()[0]
->getCode() != 'E00039') {
$this
->logResponse($response);
throw new InvalidResponseException("Unable to create payment profile for existing customer");
}
}
$payment_profile_id = $response->customerPaymentProfileId;
$validation_direct_response = explode(',', $response->validationDirectResponse);
if ($owner
->isAuthenticated()) {
$this
->setRemoteCustomerId($owner, $customer_profile_id);
$owner
->save();
}
}
// The result in validationDirectResponse does not properly escape any
// delimiter characters in the response data. So if the address, name, or
// email have "," the key for the card type is offset.
//
// We know the card type is after the XXXX#### masked card number.
$card_type_key = 51;
foreach ($validation_direct_response as $key => $value) {
if (!empty($value) && strpos($value, 'XXXX') !== FALSE) {
$card_type_key = $key + 1;
}
}
$remote_id = $owner
->isAuthenticated() ? $payment_profile_id : $customer_profile_id . '|' . $payment_profile_id;
return [
'remote_id' => $remote_id,
'card_type' => $validation_direct_response[$card_type_key],
'last4' => $payment_details['last4'],
'expiration_month' => $payment_details['expiration_month'],
'expiration_year' => $payment_details['expiration_year'],
];
}
/**
* Creates a new customer payment profile in Authorize.net CIM.
*
* @param \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method
* The payment method.
* @param array $payment_details
* The gateway-specific payment details.
* @param string $customer_id
* The remote customer ID, if available.
*
* @return \CommerceGuys\AuthNet\DataTypes\PaymentProfile
* The payment profile data type.
*/
protected function buildCustomerPaymentProfile(PaymentMethodInterface $payment_method, array $payment_details, $customer_id = NULL) {
$payment = new OpaqueData([
'dataDescriptor' => $payment_details['data_descriptor'],
'dataValue' => $payment_details['data_value'],
]);
$payment_profile = new PaymentProfile([
// @todo how to allow customizing this.
'customerType' => 'individual',
]);
if ($billing_profile = $payment_method
->getBillingProfile()) {
/** @var \Drupal\address\AddressInterface $address */
$address = $billing_profile
->get('address')
->first();
$bill_to = array_filter([
// @todo how to allow customizing this.
'firstName' => $address
->getGivenName(),
'lastName' => $address
->getFamilyName(),
'company' => $address
->getOrganization(),
'address' => substr($address
->getAddressLine1() . ' ' . $address
->getAddressLine2(), 0, 60),
'country' => $address
->getCountryCode(),
'city' => $address
->getLocality(),
'state' => $address
->getAdministrativeArea(),
'zip' => $address
->getPostalCode(),
]);
$payment_profile
->addBillTo(new BillTo($bill_to));
}
$payment_profile
->addPayment($payment);
return $payment_profile;
}
/**
* Maps the Authorize.Net credit card type to a Commerce credit card type.
*
* @param string $card_type
* The Authorize.Net credit card type.
*
* @return string
* The Commerce credit card type.
*/
protected function mapCreditCardType($card_type) {
$map = [
'American Express' => 'amex',
'Diners Club' => 'dinersclub',
'Discover' => 'discover',
'JCB' => 'jcb',
'MasterCard' => 'mastercard',
'Visa' => 'visa',
'China UnionPay' => 'unionpay',
];
if (!isset($map[$card_type])) {
throw new HardDeclineException(sprintf('Unsupported credit card type "%s".', $card_type));
}
return $map[$card_type];
}
/**
* Gets the line items from order.
*
* @param \Drupal\commerce_order\Entity\OrderInterface $order
* The order.
*
* @return \CommerceGuys\AuthNet\DataTypes\LineItem[]
* An array of line items.
*/
protected function getLineItems(OrderInterface $order) {
$line_items = [];
foreach ($order
->getItems() as $order_item) {
$name = $order_item
->label();
$name = strlen($name) > 31 ? substr($name, 0, 28) . '...' : $name;
$line_items[] = new LineItem([
'itemId' => $order_item
->id(),
'name' => $name,
'quantity' => $order_item
->getQuantity(),
'unitPrice' => $order_item
->getUnitPrice()
->getNumber(),
]);
}
return $line_items;
}
/**
* {@inheritdoc}
*/
public function buildPaymentOperations(PaymentInterface $payment) {
$payment_state = $payment
->getState()
->getId();
$operations = parent::buildPaymentOperations($payment);
$operations['approve'] = [
'title' => $this
->t('Approve'),
'page_title' => $this
->t('Approve payment'),
'plugin_form' => 'approve-payment',
'access' => in_array($payment_state, [
'unauthorized_review',
'authorization_review',
]),
];
$operations['decline'] = [
'title' => $this
->t('Decline'),
'page_title' => $this
->t('Decline payment'),
'plugin_form' => 'decline-payment',
'access' => in_array($payment_state, [
'unauthorized_review',
'authorization_review',
]),
];
return $operations;
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
AcceptJs:: |
public | function |
Approves the given payment. Overrides AcceptJsInterface:: |
|
AcceptJs:: |
public | function |
Form constructor. Overrides OnsiteBase:: |
|
AcceptJs:: |
protected | function | Creates a new customer payment profile in Authorize.net CIM. | |
AcceptJs:: |
public | function |
Builds the available operations for the given payment. Overrides PaymentGatewayBase:: |
|
AcceptJs:: |
public | function |
Creates a payment. Overrides SupportsStoredPaymentMethodsInterface:: |
|
AcceptJs:: |
public | function |
@todo Needs kernel test Overrides SupportsCreatingPaymentMethodsInterface:: |
|
AcceptJs:: |
public | function |
Declines the given payment under review. Overrides AcceptJsInterface:: |
|
AcceptJs:: |
public | function |
Gets default configuration for this plugin. Overrides OnsiteBase:: |
|
AcceptJs:: |
protected | function | Creates the payment method on the gateway. | |
AcceptJs:: |
public | function | Get the CCA API Identifier. | |
AcceptJs:: |
public | function | Get the CCA API Key. | |
AcceptJs:: |
public | function | Get the CCA API Identifier. | |
AcceptJs:: |
public | function |
Gets the JS library ID. Overrides PaymentGatewayBase:: |
|
AcceptJs:: |
protected | function |
Gets the line items from order. Overrides OnsiteBase:: |
|
AcceptJs:: |
protected | function | Maps the Authorize.Net credit card type to a Commerce credit card type. | |
AcceptJs:: |
public | function |
Refunds the given payment. Overrides SupportsRefundsInterface:: |
|
AcceptJs:: |
public | function |
Form submission handler. Overrides OnsiteBase:: |
|
AcceptJs:: |
public | function |
Updates the given payment method. Overrides SupportsUpdatingStoredPaymentMethodsInterface:: |
|
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
OnsiteBase:: |
protected | property | The adjustment transformer. | |
OnsiteBase:: |
protected | property | The Authorize.net API configuration. | |
OnsiteBase:: |
protected | property | The HTTP client. | |
OnsiteBase:: |
protected | property | The logger. | |
OnsiteBase:: |
protected | property |
The messenger service. Overrides MessengerTrait:: |
|
OnsiteBase:: |
protected | property | The private temp store factory. | |
OnsiteBase:: |
public | function |
Captures the given authorized payment. Overrides SupportsAuthorizationsInterface:: |
1 |
OnsiteBase:: |
public static | function |
Creates an instance of the plugin. Overrides PaymentGatewayBase:: |
|
OnsiteBase:: |
public | function |
@todo Needs kernel test Overrides SupportsStoredPaymentMethodsInterface:: |
|
OnsiteBase:: |
protected | function | Formats an API response as a string. | |
OnsiteBase:: |
public | function | Returns the customer identifier from a payment method's remote id. | |
OnsiteBase:: |
public | function | Returns the payment method remote identifier ensuring customer identifier is removed. | |
OnsiteBase:: |
protected | function | Gets the shipping from order. | |
OnsiteBase:: |
protected | function | Gets the tax from order. | |
OnsiteBase:: |
protected | function | Writes an API response to the log for debugging. | |
OnsiteBase:: |
public | function |
Form validation handler. Overrides PaymentGatewayBase:: |
|
OnsiteBase:: |
public | function |
Voids the given payment. Overrides SupportsVoidsInterface:: |
1 |
OnsiteBase:: |
public | function |
Constructs a new PaymentGatewayBase object. Overrides PaymentGatewayBase:: |
|
OnsitePaymentGatewayBase:: |
protected | function |
Gets the default payment gateway forms. Overrides PaymentGatewayBase:: |
|
PaymentGatewayBase:: |
protected | property | The ID of the parent config entity. | |
PaymentGatewayBase:: |
protected | property | The entity type manager. | |
PaymentGatewayBase:: |
protected | property | The minor units converter. | |
PaymentGatewayBase:: |
protected | property | The parent config entity. | |
PaymentGatewayBase:: |
protected | property | The payment method types handled by the gateway. | |
PaymentGatewayBase:: |
protected | property | The payment type used by the gateway. | |
PaymentGatewayBase:: |
protected | property | The time. | |
PaymentGatewayBase:: |
protected | function | Asserts that the payment method is neither empty nor expired. | |
PaymentGatewayBase:: |
protected | function | Asserts that the payment state matches one of the allowed states. | |
PaymentGatewayBase:: |
protected | function | Asserts that the refund amount is valid. | |
PaymentGatewayBase:: |
public | function |
Builds a label for the given AVS response code and card type. Overrides PaymentGatewayInterface:: |
2 |
PaymentGatewayBase:: |
public | function |
Calculates dependencies for the configured plugin. Overrides DependentPluginInterface:: |
|
PaymentGatewayBase:: |
public | function | ||
PaymentGatewayBase:: |
public | function | ||
PaymentGatewayBase:: |
public | function | ||
PaymentGatewayBase:: |
public | function |
Gets whether the payment gateway collects billing information. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets this plugin's configuration. Overrides ConfigurableInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the credit card types handled by the gateway. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the default payment method type. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the payment gateway display label. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the payment gateway label. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the mode in which the payment gateway is operating. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the payment method types handled by the payment gateway. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Gets the payment type used by the payment gateway. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
protected | function | Gets the remote customer ID for the given user. | |
PaymentGatewayBase:: |
public | function |
Gets the supported modes. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Sets the configuration for this plugin instance. Overrides ConfigurableInterface:: |
|
PaymentGatewayBase:: |
protected | function | Sets the remote customer ID for the given user. | |
PaymentGatewayBase:: |
public | function |
Converts the given amount to its minor units. Overrides PaymentGatewayInterface:: |
|
PaymentGatewayBase:: |
public | function |
Overrides DependencySerializationTrait:: |
|
PaymentGatewayBase:: |
public | function |
Overrides DependencySerializationTrait:: |
|
PluginBase:: |
protected | property | Configuration information passed into the plugin. | 1 |
PluginBase:: |
protected | property | The plugin implementation definition. | 1 |
PluginBase:: |
protected | property | The plugin_id. | |
PluginBase:: |
constant | A string which is used to separate base plugin IDs from the derivative ID. | ||
PluginBase:: |
public | function |
Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface:: |
|
PluginBase:: |
public | function |
Gets the definition of the plugin implementation. Overrides PluginInspectionInterface:: |
3 |
PluginBase:: |
public | function |
Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface:: |
|
PluginBase:: |
public | function | Determines if the plugin is configurable. | |
PluginWithFormsTrait:: |
public | function | ||
PluginWithFormsTrait:: |
public | function | ||
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |