View source
<?php
namespace Drupal\uc_credit;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\uc_order\OrderInterface;
use Drupal\uc_payment\PaymentMethodPluginBase;
abstract class CreditCardPaymentMethodBase extends PaymentMethodPluginBase {
public function getEnabledFields() {
return [
'cvv' => TRUE,
'owner' => FALSE,
'start' => FALSE,
'issue' => FALSE,
'bank' => FALSE,
'type' => FALSE,
];
}
public function getEnabledTypes() {
return [
'visa' => $this
->t('Visa'),
'mastercard' => $this
->t('MasterCard'),
'discover' => $this
->t('Discover'),
'amex' => $this
->t('American Express'),
];
}
public function getTransactionTypes() {
return [
UC_CREDIT_AUTH_CAPTURE,
UC_CREDIT_AUTH_ONLY,
];
}
public function getDisplayLabel($label) {
$build['#attached']['library'][] = 'uc_credit/uc_credit.styles';
$build['label'] = [
'#plain_text' => $label,
];
$cc_types = $this
->getEnabledTypes();
foreach ($cc_types as $type => $description) {
$build['image'][$type] = [
'#theme' => 'image',
'#uri' => drupal_get_path('module', 'uc_credit') . '/images/' . $type . '.gif',
'#alt' => $description,
'#attributes' => [
'class' => [
'uc-credit-cctype',
'uc-credit-cctype-' . $type,
],
],
];
}
return $build;
}
public function defaultConfiguration() {
return [
'txn_type' => UC_CREDIT_AUTH_CAPTURE,
];
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form['txn_type'] = [
'#type' => 'radios',
'#title' => $this
->t('Transaction type'),
'#default_value' => $this->configuration['txn_type'],
'#options' => [
UC_CREDIT_AUTH_CAPTURE => $this
->t('Authorize and capture immediately'),
UC_CREDIT_AUTH_ONLY => $this
->t('Authorization only'),
],
];
return $form;
}
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->configuration['txn_type'] = $form_state
->getValue('txn_type');
}
public function cartDetails(OrderInterface $order, array $form, FormStateInterface $form_state) {
$build = [
'#type' => 'container',
'#attributes' => [
'class' => 'uc-credit-form',
],
];
$build['#attached']['library'][] = 'uc_credit/uc_credit.styles';
$build['cc_policy'] = [
'#prefix' => '<p>',
'#markup' => $this
->t('Your billing information must match the billing address for the credit card entered below or we will be unable to process your payment.'),
'#suffix' => '</p>',
];
$order->payment_details = [];
$session = \Drupal::service('session');
if ($session
->has('sescrd')) {
$order->payment_details = uc_credit_cache($session
->get('sescrd'));
$build['payment_details_data'] = [
'#type' => 'hidden',
'#value' => base64_encode($session
->get('sescrd')),
];
$session
->remove('sescrd');
}
elseif (isset($_POST['panes']['payment']['details']['payment_details_data'])) {
$build['payment_details_data'] = [
'#type' => 'hidden',
'#value' => $_POST['panes']['payment']['details']['payment_details_data'],
];
}
$fields = $this
->getEnabledFields();
if (!empty($fields['type'])) {
$build['cc_type'] = [
'#type' => 'select',
'#title' => $this
->t('Card type'),
'#options' => $this
->getEnabledTypes(),
'#default_value' => isset($order->payment_details['cc_type']) ? $order->payment_details['cc_type'] : NULL,
];
}
if (!empty($fields['owner'])) {
$build['cc_owner'] = [
'#type' => 'textfield',
'#title' => $this
->t('Card owner'),
'#default_value' => isset($order->payment_details['cc_owner']) ? $order->payment_details['cc_owner'] : '',
'#attributes' => [
'autocomplete' => 'off',
],
'#size' => 32,
'#maxlength' => 64,
];
}
if (!isset($order->payment_details['cc_number'])) {
$default_num = NULL;
}
elseif (!$this
->validateCardNumber($order->payment_details['cc_number'])) {
$default_num = $order->payment_details['cc_number'];
}
else {
$default_num = $this
->t('(Last 4) @digits', [
'@digits' => substr($order->payment_details['cc_number'], -4),
]);
}
$build['cc_number'] = [
'#type' => 'textfield',
'#title' => $this
->t('Card number'),
'#default_value' => $default_num,
'#attributes' => [
'autocomplete' => 'off',
],
'#size' => 20,
'#maxlength' => 19,
];
if (!empty($fields['start'])) {
$month = isset($order->payment_details['cc_start_month']) ? $order->payment_details['cc_start_month'] : NULL;
$year = isset($order->payment_details['cc_start_year']) ? $order->payment_details['cc_start_year'] : NULL;
$year_range = range(date('Y') - 10, date('Y'));
$build['cc_start_month'] = [
'#type' => 'number',
'#title' => $this
->t('Start date'),
'#options' => [
1 => $this
->t('01 - January'),
2 => $this
->t('02 - February'),
3 => $this
->t('03 - March'),
4 => $this
->t('04 - April'),
5 => $this
->t('05 - May'),
6 => $this
->t('06 - June'),
7 => $this
->t('07 - July'),
8 => $this
->t('08 - August'),
9 => $this
->t('09 - September'),
10 => $this
->t('10 - October'),
11 => $this
->t('11 - November'),
12 => $this
->t('12 - December'),
],
'#default_value' => $month,
'#required' => TRUE,
];
$build['cc_start_year'] = [
'#type' => 'select',
'#title' => $this
->t('Start year'),
'#title_display' => 'invisible',
'#options' => array_combine($year_range, $year_range),
'#default_value' => $year,
'#field_suffix' => $this
->t('(if present)'),
'#required' => TRUE,
];
}
$month = isset($order->payment_details['cc_exp_month']) ? $order->payment_details['cc_exp_month'] : 1;
$year = isset($order->payment_details['cc_exp_year']) ? $order->payment_details['cc_exp_year'] : date('Y');
$year_range = range(date('Y'), date('Y') + 20);
$build['cc_exp_month'] = [
'#type' => 'select',
'#title' => $this
->t('Expiration date'),
'#options' => [
1 => $this
->t('01 - January'),
2 => $this
->t('02 - February'),
3 => $this
->t('03 - March'),
4 => $this
->t('04 - April'),
5 => $this
->t('05 - May'),
6 => $this
->t('06 - June'),
7 => $this
->t('07 - July'),
8 => $this
->t('08 - August'),
9 => $this
->t('09 - September'),
10 => $this
->t('10 - October'),
11 => $this
->t('11 - November'),
12 => $this
->t('12 - December'),
],
'#default_value' => $month,
'#required' => TRUE,
];
$build['cc_exp_year'] = [
'#type' => 'select',
'#title' => $this
->t('Expiration year'),
'#title_display' => 'invisible',
'#options' => array_combine($year_range, $year_range),
'#default_value' => $year,
'#field_suffix' => $this
->t('(if present)'),
'#required' => TRUE,
];
if (!empty($fields['issue'])) {
if (empty($order->payment_details['cc_issue'])) {
$default_card_issue = NULL;
}
elseif (!$this
->validateIssueNumber($order->payment_details['cc_issue'])) {
$default_card_issue = $order->payment_details['cc_issue'];
}
else {
$default_card_issue = str_repeat('-', strlen($order->payment_details['cc_issue']));
}
$build['cc_issue'] = [
'#type' => 'textfield',
'#title' => $this
->t('Issue number'),
'#default_value' => $default_card_issue,
'#attributes' => [
'autocomplete' => 'off',
],
'#size' => 2,
'#maxlength' => 2,
'#field_suffix' => $this
->t('(if present)'),
];
}
if (!empty($fields['cvv'])) {
if (empty($order->payment_details['cc_cvv'])) {
$default_cvv = NULL;
}
elseif (!$this
->validateCvv($order->payment_details['cc_cvv'])) {
$default_cvv = $order->payment_details['cc_cvv'];
}
else {
$default_cvv = str_repeat('-', strlen($order->payment_details['cc_cvv']));
}
$build['cc_cvv'] = [
'#type' => 'textfield',
'#title' => $this
->t('CVV'),
'#default_value' => $default_cvv,
'#attributes' => [
'autocomplete' => 'off',
],
'#size' => 4,
'#maxlength' => 4,
'#field_suffix' => [
'#theme' => 'uc_credit_cvv_help',
'#method' => $order
->getPaymentMethodId(),
],
];
}
if (!empty($fields['bank'])) {
$build['cc_bank'] = [
'#type' => 'textfield',
'#title' => $this
->t('Issuing bank'),
'#default_value' => isset($order->payment_details['cc_bank']) ? $order->payment_details['cc_bank'] : '',
'#attributes' => [
'autocomplete' => 'off',
],
'#size' => 32,
'#maxlength' => 64,
];
}
return $build;
}
public function cartReviewTitle() {
return $this
->t('Credit card');
}
public function cartReview(OrderInterface $order) {
$fields = $this
->getEnabledFields();
if (!empty($fields['type'])) {
$review[] = [
'title' => $this
->t('Card type'),
'data' => $order->payment_details['cc_type'],
];
}
if (!empty($fields['owner'])) {
$review[] = [
'title' => $this
->t('Card owner'),
'data' => $order->payment_details['cc_owner'],
];
}
$review[] = [
'title' => $this
->t('Card number'),
'data' => $this
->displayCardNumber($order->payment_details['cc_number']),
];
if (!empty($fields['start'])) {
$start = $order->payment_details['cc_start_month'] . '/' . $order->payment_details['cc_start_year'];
$review[] = [
'title' => $this
->t('Start date'),
'data' => strlen($start) > 1 ? $start : '',
];
}
$review[] = [
'title' => $this
->t('Expiration'),
'data' => $order->payment_details['cc_exp_month'] . '/' . $order->payment_details['cc_exp_year'],
];
if (!empty($fields['issue'])) {
$review[] = [
'title' => $this
->t('Issue number'),
'data' => $order->payment_details['cc_issue'],
];
}
if (!empty($fields['bank'])) {
$review[] = [
'title' => $this
->t('Issuing bank'),
'data' => $order->payment_details['cc_bank'],
];
}
return $review;
}
public function orderView(OrderInterface $order) {
$build = [];
$account = \Drupal::currentUser();
if ($account
->hasPermission('view cc details')) {
$rows = [];
if (!empty($order->payment_details['cc_type'])) {
$rows[] = $this
->t('Card type: @type', [
'@type' => $order->payment_details['cc_type'],
]);
}
if (!empty($order->payment_details['cc_owner'])) {
$rows[] = $this
->t('Card owner: @owner', [
'@owner' => $order->payment_details['cc_owner'],
]);
}
if (!empty($order->payment_details['cc_number'])) {
$rows[] = $this
->t('Card number: @number', [
'@number' => $this
->displayCardNumber($order->payment_details['cc_number']),
]);
}
if (!empty($order->payment_details['cc_start_month']) && !empty($order->payment_details['cc_start_year'])) {
$rows[] = $this
->t('Start date: @date', [
'@date' => $order->payment_details['cc_start_month'] . '/' . $order->payment_details['cc_start_year'],
]);
}
if (!empty($order->payment_details['cc_exp_month']) && !empty($order->payment_details['cc_exp_year'])) {
$rows[] = $this
->t('Expiration: @expiration', [
'@expiration' => $order->payment_details['cc_exp_month'] . '/' . $order->payment_details['cc_exp_year'],
]);
}
if (!empty($order->payment_details['cc_issue'])) {
$rows[] = $this
->t('Issue number: @number', [
'@number' => $order->payment_details['cc_issue'],
]);
}
if (!empty($order->payment_details['cc_bank'])) {
$rows[] = $this
->t('Issuing bank: @bank', [
'@bank' => $order->payment_details['cc_bank'],
]);
}
$build['cc_info'] = [
'#markup' => implode('<br />', $rows) . '<br />',
];
}
if ($account
->hasPermission('process credit cards')) {
$build['terminal'] = [
'#type' => 'link',
'#title' => $this
->t('Process card'),
'#url' => Url::fromRoute('uc_credit.terminal', [
'uc_order' => $order
->id(),
'uc_payment_method' => $order
->getPaymentMethodId(),
]),
];
}
return $build;
}
public function customerView(OrderInterface $order) {
$build = [];
if (!empty($order->payment_details['cc_number'])) {
$build['#markup'] = $this
->t('Card number') . ':<br />' . $this
->displayCardNumber($order->payment_details['cc_number']);
}
return $build;
}
public function orderEditDetails(OrderInterface $order) {
return $this
->t('Use the terminal available through the<br />%button button on the View tab to<br />process credit card payments.', [
'%button' => $this
->t('Process card'),
]);
}
public function cartProcess(OrderInterface $order, array $form, FormStateInterface $form_state) {
if (!$form_state
->hasValue([
'panes',
'payment',
'details',
'cc_number',
])) {
return;
}
$fields = $this
->getEnabledFields();
$cc_data = $form_state
->getValue([
'panes',
'payment',
'details',
]);
$cc_data['cc_number'] = str_replace(' ', '', $cc_data['cc_number']);
if (isset($cc_data['payment_details_data'])) {
$cache = uc_credit_cache(base64_decode($cc_data['payment_details_data']));
unset($cc_data['payment_details_data']);
}
if (substr($cc_data['cc_number'], 0, strlen(t('(Last4)'))) == $this
->t('(Last4)')) {
if (isset($cache['cc_number'])) {
$cc_data['cc_number'] = $cache['cc_number'];
}
else {
$cc_data['cc_number'] = '';
}
}
if (!empty($cc_data['cc_cvv']) && $cc_data['cc_cvv'] == str_repeat('-', strlen($cc_data['cc_cvv']))) {
if (isset($cache['cc_cvv'])) {
$cc_data['cc_cvv'] = $cache['cc_cvv'];
}
else {
$cc_data['cc_cvv'] = '';
}
}
$order->payment_details = $cc_data;
$return = TRUE;
if (!empty($fields['owner']) && empty($cc_data['cc_owner'])) {
$form_state
->setErrorByName('panes][payment][details][cc_owner', $this
->t('Enter the owner name as it appears on the card.'));
$return = FALSE;
}
if (!$this
->validateCardNumber($cc_data['cc_number'])) {
$form_state
->setErrorByName('panes][payment][details][cc_number', $this
->t('You have entered an invalid credit card number.'));
$return = FALSE;
}
if (!empty($fields['start']) && !$this
->validateStartDate($cc_data['cc_start_month'], $cc_data['cc_start_year'])) {
$form_state
->setErrorByName('panes][payment][details][cc_start_month', $this
->t('The start date you entered is invalid.'));
$form_state
->setErrorByName('panes][payment][details][cc_start_year');
$return = FALSE;
}
if (!$this
->validateExpirationDate($cc_data['cc_exp_month'], $cc_data['cc_exp_year'])) {
$form_state
->setErrorByName('panes][payment][details][cc_exp_month', $this
->t('The credit card you entered has expired.'));
$form_state
->setErrorByName('panes][payment][details][cc_exp_year');
$return = FALSE;
}
if (!empty($fields['issue']) && !$this
->validateIssueNumber($cc_data['cc_issue'])) {
$form_state
->setErrorByName('panes][payment][details][cc_issue', $this
->t('The issue number you entered is invalid.'));
$return = FALSE;
}
if (!empty($fields['cvv']) && !$this
->validateCvv($cc_data['cc_cvv'])) {
$form_state
->setErrorByName('panes][payment][details][cc_cvv', $this
->t('You have entered an invalid CVV number.'));
$return = FALSE;
}
if (!empty($fields['bank']) && empty($cc_data['cc_bank'])) {
$form_state
->setErrorByName('panes][payment][details][cc_bank', $this
->t('You must enter the issuing bank for that card.'));
$return = FALSE;
}
$key = uc_credit_encryption_key();
$crypt = \Drupal::service('uc_store.encryption');
$session = \Drupal::service('session');
$session
->set('sescrd', $crypt
->encrypt($key, base64_encode(serialize($order->payment_details))));
uc_store_encryption_errors($crypt, 'uc_credit');
return $return;
}
public function orderLoad(OrderInterface $order) {
$order->payment_details = uc_credit_cache();
if (empty($order->payment_details) && isset($order->data->cc_data)) {
$order->payment_details = uc_credit_cache($order->data->cc_data);
}
}
public function orderSave(OrderInterface $order) {
$cc_data = $order->payment_details;
$cc_data['cc_number'] = substr($cc_data['cc_number'], -4);
unset($cc_data['cc_cvv']);
$crypt = \Drupal::service('uc_store.encryption');
$order->data->cc_data = $crypt
->encrypt(uc_credit_encryption_key(), base64_encode(serialize($cc_data)));
uc_store_encryption_errors($crypt, 'uc_credit');
}
public function orderSubmit(OrderInterface $order) {
if (!$this
->processPayment($order, $order
->getTotal(), $this->configuration['txn_type'])) {
return $this
->t('We were unable to process your credit card payment. Please verify your details and try again.');
}
}
public function processPayment(OrderInterface $order, $amount, $txn_type, $reference = NULL) {
$this
->orderLoad($order);
$result = $this
->chargeCard($order, $amount, $txn_type, $reference);
if ($result['success'] === TRUE) {
if (!isset($result['log_payment']) || $result['log_payment'] !== FALSE) {
uc_payment_enter($order
->id(), $this
->getPluginId(), $amount, empty($result['uid']) ? 0 : $result['uid'], empty($result['data']) ? NULL : $result['data'], empty($result['comment']) ? '' : $result['comment']);
}
}
else {
\Drupal::logger('uc_payment')
->warning('Payment failed for order @order_id: @message', [
'@order_id' => $order
->id(),
'@message' => $result['message'],
'link' => $order
->toLink($this
->t('view order'))
->toString(),
]);
}
return $result['success'];
}
protected abstract function chargeCard(OrderInterface $order, $amount, $txn_type, $reference = NULL);
protected function displayCardNumber($number) {
if (strlen($number) == 4) {
return $this
->t('(Last 4) @digits', [
'@digits' => $number,
]);
}
return str_repeat('-', 12) . substr($number, -4);
}
protected function validateCardNumber($number) {
$id = substr($number, 0, 1);
$types = $this
->getEnabledTypes();
if ($id == 3 && empty($types['amex']) || $id == 4 && empty($types['visa']) || $id == 5 && empty($types['mastercard']) || $id == 6 && empty($types['discover']) || !ctype_digit($number)) {
return FALSE;
}
$total = 0;
for ($i = 0; $i < strlen($number); $i++) {
$digit = substr($number, $i, 1);
if ((strlen($number) - $i - 1) % 2) {
$digit *= 2;
if ($digit > 9) {
$digit -= 9;
}
}
$total += $digit;
}
if ($total % 10 != 0) {
return FALSE;
}
return TRUE;
}
protected function validateCvv($cvv) {
$digits = [];
$types = $this
->getEnabledTypes();
if (!empty($types['visa']) || !empty($types['mastercard']) || !empty($types['discover'])) {
$digits[] = 3;
}
if (!empty($types['amex'])) {
$digits[] = 4;
}
if (!is_numeric($cvv) || count($digits) > 0 && !in_array(strlen($cvv), $digits)) {
return FALSE;
}
return TRUE;
}
protected function validateStartDate($month, $year) {
if (empty($month) && empty($year)) {
return TRUE;
}
if (empty($month) || empty($year)) {
return FALSE;
}
if ($year > date('Y')) {
return FALSE;
}
elseif ($year == date('Y')) {
if ($month > date('n')) {
return FALSE;
}
}
return TRUE;
}
protected function validateExpirationDate($month, $year) {
if ($year < date('Y')) {
return FALSE;
}
elseif ($year == date('Y')) {
if ($month < date('n')) {
return FALSE;
}
}
return TRUE;
}
protected function validateIssueNumber($issue) {
if (empty($issue) || is_numeric($issue) && $issue > 0) {
return TRUE;
}
return FALSE;
}
}