class Payflow in Commerce PayPal 8
Provides the PayPal Payflow payment gateway.
Plugin annotation
@CommercePaymentGateway(
id = "paypal_payflow",
label = "PayPal - Payflow",
display_label = "Credit Card",
payment_method_types = {"credit_card"},
credit_card_types = {
"amex", "dinersclub", "discover", "jcb", "maestro", "mastercard", "visa", "unionpay"
},
)
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_paypal\Plugin\Commerce\PaymentGateway\Payflow implements PayflowInterface
- 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 Payflow
File
- src/
Plugin/ Commerce/ PaymentGateway/ Payflow.php, line 31
Namespace
Drupal\commerce_paypal\Plugin\Commerce\PaymentGatewayView source
class Payflow extends OnsitePaymentGatewayBase implements PayflowInterface {
/**
* Payflow test API URL.
*/
const PAYPAL_API_TEST_URL = 'https://pilot-payflowpro.paypal.com';
/**
* Payflow production API URL.
*/
const PAYPAL_API_URL = 'https://payflowpro.paypal.com';
/**
* The HTTP client.
*
* @var \GuzzleHttp\Client
*/
protected $httpClient;
/**
* The rounder.
*
* @var \Drupal\commerce_price\RounderInterface
*/
protected $rounder;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->httpClient = $container
->get('http_client');
$instance->rounder = $container
->get('commerce_price.rounder');
return $instance;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'partner' => '',
'vendor' => '',
'user' => '',
'password' => '',
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['partner'] = [
'#type' => 'textfield',
'#title' => $this
->t('Partner'),
'#default_value' => $this->configuration['partner'],
'#required' => TRUE,
];
$form['vendor'] = [
'#type' => 'textfield',
'#title' => $this
->t('Vendor'),
'#default_value' => $this->configuration['vendor'],
'#required' => TRUE,
];
$form['user'] = [
'#type' => 'textfield',
'#title' => $this
->t('User'),
'#default_value' => $this->configuration['user'],
'#required' => TRUE,
];
$form['password'] = [
'#type' => 'password',
'#title' => $this
->t('Password'),
'#description' => $this
->t('Only needed if you wish to change the stored value.'),
'#default_value' => $this->configuration['password'],
'#required' => empty($this->configuration['password']),
];
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['partner'] = $values['partner'];
$this->configuration['vendor'] = $values['vendor'];
$this->configuration['user'] = $values['user'];
if (!empty($values['password'])) {
$this->configuration['password'] = $values['password'];
}
}
}
/**
* Returns the Api URL.
*/
protected function getApiUrl() {
return $this
->getMode() == 'test' ? self::PAYPAL_API_TEST_URL : self::PAYPAL_API_URL;
}
/**
* Returns the partner.
*/
protected function getPartner() {
return $this->configuration['partner'] ?: '';
}
/**
* Returns the vendor.
*/
protected function getVendor() {
return $this->configuration['vendor'] ?: '';
}
/**
* Returns the user.
*/
protected function getUser() {
return $this->configuration['user'] ?: '';
}
/**
* Returns the password.
*/
protected function getPassword() {
return $this->configuration['password'] ?: '';
}
/**
* Formats the expiration date from the provided payment details.
*
* PayPal requires the expiration date to be in MMYY format.
* Using a four-digit year (MMYYYY) will cause some banks to decline
* transactions because the expiration date is considered invalid.
* For example, 072018 will be interpreted as 0720 instead of 0718.
*
* @param array $payment_details
* The payment details.
*
* @return string
* The expiration date, in the MMYY format.
*/
protected function getExpirationDate(array $payment_details) {
$date = \DateTime::createFromFormat('Y', $payment_details['expiration']['year']);
return $payment_details['expiration']['month'] . $date
->format('y');
}
/**
* Merge default Payflow parameters in with the provided ones.
*
* @param array $parameters
* The parameters for the transaction.
*
* @return array
* The new parameters.
*/
protected function getParameters(array $parameters = []) {
$defaultParameters = [
'tender' => 'C',
'partner' => $this
->getPartner(),
'vendor' => $this
->getVendor(),
'user' => $this
->getUser(),
'pwd' => $this
->getPassword(),
];
return $parameters + $defaultParameters;
}
/**
* Get the remote transaction number ('pnref') of a payment.
*
* @param \Drupal\commerce_payment\Entity\PaymentInterface $payment
* The payment.
*
* @return string
* The Payflow transaction number.
*/
protected function getTransactionNumber(PaymentInterface $payment) {
return explode('|', $payment
->getRemoteId())[0];
}
/**
* Get the remote authorization code ('authcode') of a payment.
*
* @param \Drupal\commerce_payment\Entity\PaymentInterface $payment
* The payment.
*
* @return string
* The Payflow authorization code.
*/
protected function getAuthorizationCode(PaymentInterface $payment) {
$remote_id = $payment
->getRemoteId();
return strpos($remote_id, '|') !== FALSE ? explode('|', $remote_id)[1] : $remote_id;
}
/**
* Gets a composite Remote ID from two Payflow payment transaction fields.
*
* @param array $data
* A data array including 'pnref' and 'authcode' keys.
*
* @return bool|string
* FALSE if required keys are missing, or a string "<pnref>|<authcode>".
*/
protected function prepareRemoteId(array $data) {
if (!array_key_exists('pnref', $data) || !array_key_exists('authcode', $data)) {
return FALSE;
}
return $data['pnref'] . '|' . $data['authcode'];
}
/**
* Prepares the request body to name/value pairs.
*
* @param array $parameters
* The request parameters.
*
* @return string
* The request body.
*/
protected function prepareBody(array $parameters = []) {
$parameters = $this
->getParameters($parameters);
$values = [];
foreach ($parameters as $key => $value) {
$values[] = strtoupper($key) . '=' . $value;
}
return implode('&', $values);
}
/**
* Prepares the result of a request.
*
* @param string $body
* The result.
*
* @return array
* An array of the result values.
*/
protected function prepareResult($body) {
$responseParts = explode('&', $body);
$result = [];
foreach ($responseParts as $bodyPart) {
list($key, $value) = explode('=', $bodyPart, 2);
$result[strtolower($key)] = $value;
}
return $result;
}
/**
* Post a transaction to the Payflow server and return the response.
*
* @param array $parameters
* The parameters to send (will have base parameters added).
*
* @return array
* The response body data in array format.
*/
protected function executeTransaction(array $parameters) {
$body = $this
->prepareBody($parameters);
$response = $this->httpClient
->post($this
->getApiUrl(), [
'headers' => [
'Content-Type' => 'text/namevalue',
'Content-Length' => strlen($body),
],
'body' => $body,
'timeout' => 0,
]);
return $this
->prepareResult($response
->getBody()
->getContents());
}
/**
* Attempt to validate payment information according to a payment state.
*
* @param \Drupal\commerce_payment\Entity\PaymentInterface $payment
* The payment to validate.
* @param string|null $payment_state
* The payment state to validate the payment for.
*/
protected function validatePayment(PaymentInterface $payment, $payment_state = 'new') {
$this
->assertPaymentState($payment, [
$payment_state,
]);
$payment_method = $payment
->getPaymentMethod();
if (empty($payment_method)) {
throw new InvalidArgumentException('The provided payment has no payment method referenced.');
}
switch ($payment_state) {
case 'new':
if ($payment_method
->isExpired()) {
throw new HardDeclineException('The provided payment method has expired.');
}
break;
case 'authorization':
if ($payment
->isExpired()) {
throw new \InvalidArgumentException('Authorizations are guaranteed for up to 29 days.');
}
if (empty($payment
->getRemoteId())) {
throw new \InvalidArgumentException('Could not retrieve the transaction ID.');
}
break;
}
}
/**
* {@inheritdoc}
*/
public function createPayment(PaymentInterface $payment, $capture = TRUE) {
$this
->validatePayment($payment, 'new');
try {
$data = $this
->executeTransaction([
'trxtype' => $capture ? 'S' : 'A',
'amt' => $this->rounder
->round($payment
->getAmount())
->getNumber(),
'currencycode' => $payment
->getAmount()
->getCurrencyCode(),
'origid' => $payment
->getPaymentMethod()
->getRemoteId(),
'verbosity' => 'HIGH',
]);
if ($data['result'] !== '0') {
throw new HardDeclineException('Could not charge the payment method. Response: ' . $data['respmsg'], $data['result']);
}
$next_state = $capture ? 'completed' : 'authorization';
$payment
->setState($next_state);
if (!$capture) {
$payment
->setExpiresTime($this->time
->getRequestTime() + 86400 * 29);
}
$payment
->setRemoteId($this
->prepareRemoteId($data))
->setRemoteState('3')
->save();
} catch (RequestException $e) {
throw new HardDeclineException('Could not charge the payment method.');
}
}
/**
* {@inheritdoc}
*/
public function capturePayment(PaymentInterface $payment, Price $amount = NULL) {
$this
->validatePayment($payment, 'authorization');
// If not specified, capture the entire amount.
$amount = $amount ?: $payment
->getAmount();
$amount = $this->rounder
->round($amount);
try {
$data = $this
->executeTransaction([
'trxtype' => 'D',
'amt' => $this->rounder
->round($amount)
->getNumber(),
'currency' => $amount
->getCurrencyCode(),
'origid' => $this
->getTransactionNumber($payment),
]);
if ($data['result'] !== '0') {
throw new PaymentGatewayException('Count not capture payment. Message: ' . $data['respmsg'], $data['result']);
}
$payment
->setState('completed');
$payment
->setAmount($amount);
$payment
->save();
} catch (RequestException $e) {
throw new PaymentGatewayException('Count not capture payment. Message: ' . $e
->getMessage(), $e
->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function voidPayment(PaymentInterface $payment) {
$this
->validatePayment($payment, 'authorization');
$remoteId = $this
->getTransactionNumber($payment);
if (empty($remoteId)) {
throw new PaymentGatewayException('Remote authorization ID could not be determined.');
}
try {
$data = $this
->executeTransaction([
'trxtype' => 'V',
'origid' => $this
->getTransactionNumber($payment),
'verbosity' => 'HIGH',
]);
if ($data['result'] !== '0') {
throw new PaymentGatewayException('Payment could not be voided. Message: ' . $data['respmsg'], $data['result']);
}
$payment
->setState('authorization_voided');
$payment
->save();
} catch (RequestException $e) {
throw new InvalidArgumentException('Only payments in the "authorization" state can be voided.');
}
}
/**
* {@inheritdoc}
*
* TODO: Find a way to store the capture ID.
*/
public function refundPayment(PaymentInterface $payment, Price $amount = NULL) {
$this
->assertPaymentState($payment, [
'completed',
'partially_refunded',
]);
if ($payment
->getCompletedTime() < strtotime('-180 days')) {
throw new InvalidRequestException("Unable to refund a payment captured more than 180 days ago.");
}
// If not specified, refund the entire amount.
$amount = $amount ?: $payment
->getAmount();
$amount = $this->rounder
->round($amount);
$this
->assertRefundAmount($payment, $amount);
$transaction_number = $this
->getTransactionNumber($payment);
if (empty($transaction_number)) {
throw new InvalidRequestException('Could not determine the remote payment details.');
}
try {
$new_refunded_amount = $payment
->getRefundedAmount()
->add($amount);
$data = $this
->executeTransaction([
'trxtype' => 'C',
'origid' => $transaction_number,
'amt' => $amount
->getNumber(),
]);
if ($data['result'] !== '0') {
throw new PaymentGatewayException('Credit could not be completed. Message: ' . $data['respmsg'], $data['result']);
}
if ($new_refunded_amount
->lessThan($payment
->getAmount())) {
$payment
->setState('partially_refunded');
}
else {
$payment
->setState('refunded');
}
$payment
->setRefundedAmount($new_refunded_amount);
$payment
->save();
} catch (RequestException $e) {
throw new InvalidRequestException("Could not refund the payment.", $e
->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function createPaymentMethod(PaymentMethodInterface $payment_method, array $payment_details) {
try {
/** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */
$address = $payment_method
->getBillingProfile()
->get('address')
->first();
$data = $this
->executeTransaction([
'trxtype' => 'A',
'amt' => 0,
'verbosity' => 'HIGH',
'acct' => $payment_details['number'],
'expdate' => $this
->getExpirationDate($payment_details),
'cvv2' => $payment_details['security_code'],
'billtoemail' => $payment_method
->getOwner()
->getEmail(),
'billtofirstname' => $address
->getGivenName(),
'billtolastname' => $address
->getFamilyName(),
'billtostreet' => $address
->getAddressLine1(),
'billtocity' => $address
->getLocality(),
'billtostate' => $address
->getAdministrativeArea(),
'billtozip' => $address
->getPostalCode(),
'billtocountry' => $address
->getCountryCode(),
]);
if ($data['result'] !== '0') {
throw new HardDeclineException("Unable to verify the credit card: " . $data['respmsg'], $data['result']);
}
$payment_method->card_type = $payment_details['type'];
// Only the last 4 numbers are safe to store.
$payment_method->card_number = substr($payment_details['number'], -4);
$payment_method->card_exp_month = $payment_details['expiration']['month'];
$payment_method->card_exp_year = $payment_details['expiration']['year'];
$expires = CreditCard::calculateExpirationTimestamp($payment_details['expiration']['month'], $payment_details['expiration']['year']);
// Store the remote ID returned by the request.
$payment_method
->setRemoteId($data['pnref'])
->setExpiresTime($expires)
->save();
} catch (RequestException $e) {
throw new HardDeclineException("Unable to store the credit card");
}
}
/**
* {@inheritdoc}
*/
public function deletePaymentMethod(PaymentMethodInterface $payment_method) {
$payment_method
->delete();
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
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:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
OnsitePaymentGatewayBase:: |
protected | function |
Gets the default payment gateway forms. Overrides PaymentGatewayBase:: |
|
Payflow:: |
protected | property | The HTTP client. | |
Payflow:: |
protected | property | The rounder. | |
Payflow:: |
public | function |
Form constructor. Overrides OnsitePaymentGatewayBase:: |
|
Payflow:: |
public | function |
Captures the given authorized payment. Overrides SupportsAuthorizationsInterface:: |
|
Payflow:: |
public static | function |
Creates an instance of the plugin. Overrides PaymentGatewayBase:: |
|
Payflow:: |
public | function |
Creates a payment. Overrides SupportsStoredPaymentMethodsInterface:: |
|
Payflow:: |
public | function |
Creates a payment method with the given payment details. Overrides SupportsCreatingPaymentMethodsInterface:: |
|
Payflow:: |
public | function |
Gets default configuration for this plugin. Overrides PaymentGatewayBase:: |
|
Payflow:: |
public | function |
Deletes the given payment method. Overrides SupportsStoredPaymentMethodsInterface:: |
|
Payflow:: |
protected | function | Post a transaction to the Payflow server and return the response. | |
Payflow:: |
protected | function | Returns the Api URL. | |
Payflow:: |
protected | function | Get the remote authorization code ('authcode') of a payment. | |
Payflow:: |
protected | function | Formats the expiration date from the provided payment details. | |
Payflow:: |
protected | function | Merge default Payflow parameters in with the provided ones. | |
Payflow:: |
protected | function | Returns the partner. | |
Payflow:: |
protected | function | Returns the password. | |
Payflow:: |
protected | function | Get the remote transaction number ('pnref') of a payment. | |
Payflow:: |
protected | function | Returns the user. | |
Payflow:: |
protected | function | Returns the vendor. | |
Payflow:: |
constant | Payflow test API URL. | ||
Payflow:: |
constant | Payflow production API URL. | ||
Payflow:: |
protected | function | Prepares the request body to name/value pairs. | |
Payflow:: |
protected | function | Gets a composite Remote ID from two Payflow payment transaction fields. | |
Payflow:: |
protected | function | Prepares the result of a request. | |
Payflow:: |
public | function |
TODO: Find a way to store the capture ID. Overrides SupportsRefundsInterface:: |
|
Payflow:: |
public | function |
Form submission handler. Overrides PaymentGatewayBase:: |
|
Payflow:: |
protected | function | Attempt to validate payment information according to a payment state. | |
Payflow:: |
public | function |
Voids the given payment. Overrides SupportsVoidsInterface:: |
|
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 |
Builds the available operations for the given payment. Overrides PaymentGatewayInterface:: |
1 |
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 JS library ID. 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 |
Form validation handler. Overrides PluginFormInterface:: |
|
PaymentGatewayBase:: |
public | function |
Constructs a new PaymentGatewayBase object. Overrides PluginBase:: |
3 |
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. |