Skip to content

Commit 3e85eaa

Browse files
rszramamglaman
authored andcommitted
Issue #3153381 by lisastreeter, rszrama, jsacksick, mglaman, zaporylie, rhovland: Add an "AVS response code" base field to Payment entities
1 parent 38283c6 commit 3e85eaa

File tree

13 files changed

+265
-0
lines changed

13 files changed

+265
-0
lines changed

modules/payment/commerce_payment.install

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,22 @@ function commerce_payment_update_8205() {
126126
$storage_definition->setDefaultValueCallback($default_value_callback);
127127
$definition_update_manager->updateFieldStorageDefinition($storage_definition);
128128
}
129+
130+
/**
131+
* Add the avs_response_code and avs_response_code_label fields to payments.
132+
*/
133+
function commerce_payment_update_8206() {
134+
$entity_definition_update = \Drupal::entityDefinitionUpdateManager();
135+
136+
$storage_definition = BaseFieldDefinition::create('string')
137+
->setLabel(t('AVS response code'))
138+
->setDescription(t('The AVS response code.'))
139+
->setDisplayConfigurable('view', TRUE);
140+
$entity_definition_update->installFieldStorageDefinition('avs_response_code', 'commerce_payment', 'commerce_payment', $storage_definition);
141+
142+
$storage_definition = BaseFieldDefinition::create('string')
143+
->setLabel(t('AVS response code label'))
144+
->setDescription(t('The AVS response code label.'))
145+
->setDisplayConfigurable('view', TRUE);
146+
$entity_definition_update->installFieldStorageDefinition('avs_response_code_label', 'commerce_payment', 'commerce_payment', $storage_definition);
147+
}

modules/payment/src/CreditCard.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,4 +273,70 @@ public static function validateSecurityCode(string $security_code, CreditCardTyp
273273
return TRUE;
274274
}
275275

276+
/**
277+
* Gets all available AVS response code meanings.
278+
*
279+
* @return array
280+
* The AVS response code meanings.
281+
*
282+
* @see https://developer.paypal.com/docs/nvp-soap-api/AVSResponseCodes/#avs-error-response-codes
283+
*/
284+
public static function getAvsResponseCodeMeanings() : array {
285+
// AVS codes for Visa, MasterCard, Discover, and American Express.
286+
$standard_codes = [
287+
'A' => new TranslatableMarkup('Address'),
288+
'B' => new TranslatableMarkup('International "A"'),
289+
'C' => new TranslatableMarkup('International "N"'),
290+
'D' => new TranslatableMarkup('International "X"'),
291+
'E' => new TranslatableMarkup('Not allowed for MOTO (Internet/Phone) transactions'),
292+
'F' => new TranslatableMarkup('UK-specific "X"'),
293+
'G' => new TranslatableMarkup('Global Unavailable'),
294+
'I' => new TranslatableMarkup('International Unavailable'),
295+
'M' => new TranslatableMarkup('Address'),
296+
'N' => new TranslatableMarkup('No'),
297+
'P' => new TranslatableMarkup('Postal (International "Z")'),
298+
'R' => new TranslatableMarkup('Retry'),
299+
'S' => new TranslatableMarkup('AVS not Supported'),
300+
'U' => new TranslatableMarkup('Unavailable'),
301+
'W' => new TranslatableMarkup('Whole ZIP'),
302+
'X' => new TranslatableMarkup('Exact match'),
303+
'Y' => new TranslatableMarkup('Yes'),
304+
'Z' => new TranslatableMarkup('ZIP'),
305+
];
306+
307+
// Additional codes for American Express.
308+
$amex_codes = [
309+
'A' => new TranslatableMarkup('Card holder address only correct.'),
310+
'D' => new TranslatableMarkup('Card holder name incorrect, postal code matches.'),
311+
'E' => new TranslatableMarkup('Card holder name incorrect, address and postal code match.'),
312+
'F' => new TranslatableMarkup('Card holder name incorrect, address matches.'),
313+
'K' => new TranslatableMarkup('Card holder name matches.'),
314+
'L' => new TranslatableMarkup('Card holder name and postal code match.'),
315+
'M' => new TranslatableMarkup('Card holder name, address and postal code match.'),
316+
'N' => new TranslatableMarkup('No, card holder address and postal code are both incorrect.'),
317+
'O' => new TranslatableMarkup('Card holder name and address match.'),
318+
'R' => new TranslatableMarkup('System unavailable; retry.'),
319+
'S' => new TranslatableMarkup('AVS not supported.'),
320+
'U' => new TranslatableMarkup('Information unavailable.'),
321+
'W' => new TranslatableMarkup('No, card holder name, address and postal code are all incorrect.'),
322+
'Y' => new TranslatableMarkup('Yes, card holder address and postal code are both correct.'),
323+
'Z' => new TranslatableMarkup('Card holder postal code only correct.'),
324+
] + $standard_codes;
325+
326+
return [
327+
'visa' => $standard_codes,
328+
'mastercard' => $standard_codes,
329+
'amex' => $amex_codes,
330+
'discover' => $standard_codes,
331+
'maestro' => [
332+
'0' => new TranslatableMarkup('All the address information matched.'),
333+
'1' => new TranslatableMarkup('None of the address information matched.'),
334+
'2' => new TranslatableMarkup('Part of the address information matched.'),
335+
'3' => new TranslatableMarkup('The merchant did not provide AVS information. Not processed.'),
336+
'4' => new TranslatableMarkup('Address not checked, or acquirer had no response. Service not available.'),
337+
'U' => new TranslatableMarkup('Address not checked, or acquirer had no response. Service not available.'),
338+
],
339+
];
340+
}
341+
276342
}

modules/payment/src/Entity/Payment.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,36 @@ public function setRemoteState($remote_state) {
158158
return $this;
159159
}
160160

161+
/**
162+
* {@inheritdoc}
163+
*/
164+
public function getAvsResponseCode() {
165+
return $this->get('avs_response_code')->value;
166+
}
167+
168+
/**
169+
* {@inheritdoc}
170+
*/
171+
public function setAvsResponseCode($avs_response_code) {
172+
$this->set('avs_response_code', $avs_response_code);
173+
return $this;
174+
}
175+
176+
/**
177+
* {@inheritdoc}
178+
*/
179+
public function getAvsResponseCodeLabel() {
180+
return $this->get('avs_response_code_label')->value;
181+
}
182+
183+
/**
184+
* {@inheritdoc}
185+
*/
186+
public function setAvsResponseCodeLabel($avs_response_code_label) {
187+
$this->set('avs_response_code_label', $avs_response_code_label);
188+
return $this;
189+
}
190+
161191
/**
162192
* {@inheritdoc}
163193
*/
@@ -423,6 +453,16 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
423453
->setDescription(t('The time when the payment was completed.'))
424454
->setDisplayConfigurable('view', TRUE);
425455

456+
$fields['avs_response_code'] = BaseFieldDefinition::create('string')
457+
->setLabel(t('AVS response code'))
458+
->setDescription(t('The AVS response code.'))
459+
->setDisplayConfigurable('view', TRUE);
460+
461+
$fields['avs_response_code_label'] = BaseFieldDefinition::create('string')
462+
->setLabel(t('AVS response code label'))
463+
->setDescription(t('The AVS response code label.'))
464+
->setDisplayConfigurable('view', TRUE);
465+
426466
// These fields have been replaced by payment_gateway_mode and completed.
427467
// They have been temporarily kept for commerce_payment_post_update_2().
428468
// They are no longer used and will be removed in Commerce 2.0.

modules/payment/src/Entity/PaymentInterface.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,42 @@ public function getRemoteState();
9797
*/
9898
public function setRemoteState($remote_state);
9999

100+
/**
101+
* Gets the payment AVS response code.
102+
*
103+
* @return string
104+
* The payment AVS response code.
105+
*/
106+
public function getAvsResponseCode();
107+
108+
/**
109+
* Sets the payment AVS response code.
110+
*
111+
* @param string $avs_response_code
112+
* The payment AVS response code.
113+
*
114+
* @return $this
115+
*/
116+
public function setAvsResponseCode($avs_response_code);
117+
118+
/**
119+
* Gets the payment AVS response code label.
120+
*
121+
* @return string|null
122+
* The payment AVS response code label, or NULL if it does not exist.
123+
*/
124+
public function getAvsResponseCodeLabel();
125+
126+
/**
127+
* Sets the payment AVS response code label.
128+
*
129+
* @param string $avs_response_code_label
130+
* The payment AVS response code label.
131+
*
132+
* @return $this
133+
*/
134+
public function setAvsResponseCodeLabel($avs_response_code_label);
135+
100136
/**
101137
* Gets the payment balance.
102138
*

modules/payment/src/PaymentListBuilder.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public function buildHeader() {
125125
$header['label'] = $this->t('Payment');
126126
$header['state'] = $this->t('State');
127127
$header['payment_gateway'] = $this->t('Payment gateway');
128+
$header['avs_response'] = '';
128129
$header['remote_id'] = $this->t('Remote ID');
129130
return $header + parent::buildHeader();
130131
}
@@ -146,6 +147,15 @@ public function buildRow(EntityInterface $entity) {
146147
$row['label'] = $formatted_amount;
147148
$row['state'] = $entity->getState()->getLabel();
148149
$row['payment_gateway'] = $payment_gateway ? $payment_gateway->label() : $this->t('N/A');
150+
151+
// Add the AVS response code label beneath the gateway name if it exists.
152+
if ($avs_response_code = $entity->getAvsResponseCode()) {
153+
$row['avs_response'] = $this->t('AVS response: [@code] @label', ['@code' => $avs_response_code, '@label' => $entity->getAvsResponseCodeLabel()]);
154+
}
155+
else {
156+
$row['avs_response'] = '';
157+
}
158+
149159
$row['remote_id'] = $entity->getRemoteId() ?: $this->t('N/A');
150160

151161
return $row + parent::buildRow($entity);

modules/payment/src/Plugin/Commerce/PaymentGateway/PaymentGatewayBase.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,17 @@ public function canVoidPayment(PaymentInterface $payment) {
437437
return $payment->getState()->getId() === 'authorization';
438438
}
439439

440+
/**
441+
* {@inheritdoc}
442+
*/
443+
public function buildAvsResponseCodeLabel($avs_response_code, $card_type) {
444+
$avs_code_meanings = CreditCard::getAvsResponseCodeMeanings();
445+
if (!isset($avs_code_meanings[$card_type][$avs_response_code])) {
446+
return NULL;
447+
}
448+
return $avs_code_meanings[$card_type][$avs_response_code];
449+
}
450+
440451
/**
441452
* {@inheritdoc}
442453
*/

modules/payment/src/Plugin/Commerce/PaymentGateway/PaymentGatewayInterface.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@ public function collectsBillingInformation();
123123
*/
124124
public function buildPaymentOperations(PaymentInterface $payment);
125125

126+
/**
127+
* Builds a label for the given AVS response code and card type.
128+
*
129+
* @param string $avs_response_code
130+
* The AVS response code.
131+
* @param string $card_type
132+
* The card type.
133+
*
134+
* @return string|null
135+
* The label, or NULL if not available.
136+
*/
137+
public function buildAvsResponseCodeLabel($avs_response_code, $card_type);
138+
126139
/**
127140
* Converts the given amount to its minor units.
128141
*

modules/payment/tests/src/Functional/DefaultPaymentAdminTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ public function testPaymentCreation() {
177177
$this->assertEquals($this->order->id(), $payment->getOrderId());
178178
$this->assertEquals('100.00', $payment->getAmount()->getNumber());
179179
$this->assertNotEmpty($payment->getCompletedTime());
180+
$this->assertEquals('A', $payment->getAvsResponseCode());
181+
$this->assertEquals('Address', $payment->getAvsResponseCodeLabel());
180182
}
181183

182184
/**

modules/payment/tests/src/FunctionalJavascript/PaymentCheckoutTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,8 @@ public function testCheckoutWithNewPaymentMethod() {
361361
$this->assertNotNull($payment);
362362
$this->assertEquals($payment->getAmount(), $order->getTotalPrice());
363363
$this->assertEquals('authorization', $payment->getState()->getId());
364+
$this->assertEquals('A', $payment->getAvsResponseCode());
365+
$this->assertEquals('Address', $payment->getAvsResponseCodeLabel());
364366
}
365367

366368
/**
@@ -410,6 +412,8 @@ public function testCheckoutWithExistingPaymentMethod() {
410412
$this->assertEquals(new Price('19.99', 'USD'), $payment->getAmount());
411413
$this->assertEquals(new Price('39.99', 'USD'), $order->getTotalPaid());
412414
$this->assertEquals(new Price('0', 'USD'), $order->getBalance());
415+
$this->assertEquals('A', $payment->getAvsResponseCode());
416+
$this->assertEquals('Address', $payment->getAvsResponseCodeLabel());
413417

414418
/** @var \Drupal\profile\Entity\ProfileInterface $order_billing_profile */
415419
$order_billing_profile = $order->getBillingProfile();
@@ -753,6 +757,8 @@ public function testCheckoutWithStoredOffsiteRedirectPost() {
753757
$payment = Payment::load(1);
754758
$this->assertNotNull($payment);
755759
$this->assertEquals($payment->getAmount(), $order->getTotalPrice());
760+
$this->assertEquals('Z', $payment->getAvsResponseCode());
761+
$this->assertEquals('ZIP', $payment->getAvsResponseCodeLabel());
756762
$this->assertEquals(3, $payment->get('payment_method')->target_id);
757763
}
758764

modules/payment/tests/src/Kernel/Entity/PaymentTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,16 @@ public function testPayment() {
118118
'amount' => new Price('30', 'USD'),
119119
'refunded_amount' => new Price('10', 'USD'),
120120
'state' => 'refunded',
121+
'avs_response_code' => 'X',
122+
'avs_response_code_label' => 'Meaning of X',
121123
]);
122124
$payment->save();
123125

124126
$this->assertInstanceOf(PaymentDefault::class, $payment->getType());
125127
$this->assertEquals('example', $payment->getPaymentGatewayId());
126128
$this->assertEquals('test', $payment->getPaymentGatewayMode());
129+
$this->assertEquals('X', $payment->getAvsResponseCode());
130+
$this->assertEquals('Meaning of X', $payment->getAvsResponseCodeLabel());
127131

128132
$this->assertEquals($this->order, $payment->getOrder());
129133
$this->assertEquals($this->order->id(), $payment->getOrderId());
@@ -134,6 +138,11 @@ public function testPayment() {
134138
$payment->setRemoteState('pending');
135139
$this->assertEquals('pending', $payment->getRemoteState());
136140

141+
$payment->setAvsResponseCode('Z');
142+
$this->assertEquals('Z', $payment->getAvsResponseCode());
143+
$payment->setAvsResponseCodeLabel('Meaning of Z');
144+
$this->assertEquals('Meaning of Z', $payment->getAvsResponseCodeLabel());
145+
137146
$this->assertEquals(new Price('30', 'USD'), $payment->getAmount());
138147
$this->assertEquals(new Price('10', 'USD'), $payment->getRefundedAmount());
139148
$this->assertEquals(new Price('20', 'USD'), $payment->getBalance());

0 commit comments

Comments
 (0)