View source
<?php
namespace Drupal\commerce_paypal\Plugin\Commerce\PaymentGateway;
use Drupal\commerce_payment\CreditCard;
use Drupal\commerce_payment\Entity\PaymentInterface;
use Drupal\commerce_payment\Entity\PaymentMethodInterface;
use Drupal\commerce_payment\Exception\HardDeclineException;
use Drupal\commerce_payment\Exception\InvalidRequestException;
use Drupal\commerce_payment\Exception\PaymentGatewayException;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OnsitePaymentGatewayBase;
use Drupal\commerce_price\Price;
use Drupal\Core\Form\FormStateInterface;
use GuzzleHttp\Exception\RequestException;
use InvalidArgumentException;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Payflow extends OnsitePaymentGatewayBase implements PayflowInterface {
const PAYPAL_API_TEST_URL = 'https://pilot-payflowpro.paypal.com';
const PAYPAL_API_URL = 'https://payflowpro.paypal.com';
protected $httpClient;
protected $rounder;
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;
}
public function defaultConfiguration() {
return [
'partner' => '',
'vendor' => '',
'user' => '',
'password' => '',
] + parent::defaultConfiguration();
}
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;
}
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'];
}
}
}
protected function getApiUrl() {
return $this
->getMode() == 'test' ? self::PAYPAL_API_TEST_URL : self::PAYPAL_API_URL;
}
protected function getPartner() {
return $this->configuration['partner'] ?: '';
}
protected function getVendor() {
return $this->configuration['vendor'] ?: '';
}
protected function getUser() {
return $this->configuration['user'] ?: '';
}
protected function getPassword() {
return $this->configuration['password'] ?: '';
}
protected function getExpirationDate(array $payment_details) {
$date = \DateTime::createFromFormat('Y', $payment_details['expiration']['year']);
return $payment_details['expiration']['month'] . $date
->format('y');
}
protected function getParameters(array $parameters = []) {
$defaultParameters = [
'tender' => 'C',
'partner' => $this
->getPartner(),
'vendor' => $this
->getVendor(),
'user' => $this
->getUser(),
'pwd' => $this
->getPassword(),
];
return $parameters + $defaultParameters;
}
protected function getTransactionNumber(PaymentInterface $payment) {
return explode('|', $payment
->getRemoteId())[0];
}
protected function getAuthorizationCode(PaymentInterface $payment) {
$remote_id = $payment
->getRemoteId();
return strpos($remote_id, '|') !== FALSE ? explode('|', $remote_id)[1] : $remote_id;
}
protected function prepareRemoteId(array $data) {
if (!array_key_exists('pnref', $data) || !array_key_exists('authcode', $data)) {
return FALSE;
}
return $data['pnref'] . '|' . $data['authcode'];
}
protected function prepareBody(array $parameters = []) {
$parameters = $this
->getParameters($parameters);
$values = [];
foreach ($parameters as $key => $value) {
$values[] = strtoupper($key) . '=' . $value;
}
return implode('&', $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;
}
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());
}
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;
}
}
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.');
}
}
public function capturePayment(PaymentInterface $payment, Price $amount = NULL) {
$this
->validatePayment($payment, 'authorization');
$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);
}
}
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.');
}
}
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.");
}
$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);
}
}
public function createPaymentMethod(PaymentMethodInterface $payment_method, array $payment_details) {
try {
$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'];
$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']);
$payment_method
->setRemoteId($data['pnref'])
->setExpiresTime($expires)
->save();
} catch (RequestException $e) {
throw new HardDeclineException("Unable to store the credit card");
}
}
public function deletePaymentMethod(PaymentMethodInterface $payment_method) {
$payment_method
->delete();
}
}