Skip to content

Commit bade6ad

Browse files
committed
Process response code message on capture
1 parent a96febb commit bade6ad

6 files changed

Lines changed: 160 additions & 11 deletions

File tree

modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,9 +345,18 @@ public function capture( Order $order ): Order {
345345

346346
$order = $this->order_factory->from_paypal_response( $json );
347347

348-
$capture_status = $order->purchase_units()[0]->payments()->captures()[0]->status() ?? null;
348+
$first_capture = $order->purchase_units()[0]->payments()->captures()[0] ?? null;
349+
$capture_status = $first_capture ? $first_capture->status() : null;
349350
if ( $capture_status && $capture_status->is( CaptureStatus::DECLINED ) ) {
350-
throw new RuntimeException( __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' ) );
351+
$fraud = $first_capture->fraud_processor_response();
352+
$decline_message = ( $fraud && $fraud->response_code() )
353+
? sprintf(
354+
/* translators: %s - processor response code and description */
355+
__( 'Payment declined by card processor: %s. Please use a different payment method or contact your bank.', 'woocommerce-paypal-payments' ),
356+
$fraud->get_response_code_message()
357+
)
358+
: __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' );
359+
throw new RuntimeException( $decline_message );
351360
}
352361

353362
return $order;

modules/ppcp-api-client/src/Entity/FraudProcessorResponse.php

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,24 @@ class FraudProcessorResponse {
2828
*/
2929
protected string $cvv2_code;
3030

31+
/**
32+
* The processor response code (e.g. 9500, 9100).
33+
*
34+
* @var string
35+
*/
36+
protected string $response_code;
37+
3138
/**
3239
* FraudProcessorResponse constructor.
3340
*
3441
* @param string|null $avs_code The AVS response code.
3542
* @param string|null $cvv2_code The CVV response code.
43+
* @param string|null $response_code The processor response code.
3644
*/
37-
public function __construct( ?string $avs_code, ?string $cvv2_code ) {
38-
$this->avs_code = (string) $avs_code;
39-
$this->cvv2_code = (string) $cvv2_code;
45+
public function __construct( ?string $avs_code, ?string $cvv2_code, ?string $response_code = null ) {
46+
$this->avs_code = (string) $avs_code;
47+
$this->cvv2_code = (string) $cvv2_code;
48+
$this->response_code = (string) $response_code;
4049
}
4150

4251
/**
@@ -57,15 +66,25 @@ public function cvv_code(): string {
5766
return $this->cvv2_code;
5867
}
5968

69+
/**
70+
* Returns the processor response code.
71+
*
72+
* @return string
73+
*/
74+
public function response_code(): string {
75+
return $this->response_code;
76+
}
77+
6078
/**
6179
* Returns the object as array.
6280
*
6381
* @return array
6482
*/
6583
public function to_array(): array {
6684
return array(
67-
'avs_code' => $this->avs_code(),
68-
'cvv2_code' => $this->cvv_code(),
85+
'avs_code' => $this->avs_code(),
86+
'cvv2_code' => $this->cvv_code(),
87+
'response_code' => $this->response_code(),
6988
);
7089
}
7190

@@ -155,4 +174,70 @@ public function get_cvv2_code_message(): string {
155174
*/
156175
return $messages[ $this->cvv_code() ] ?? sprintf( '%s: Error', $this->cvv_code() );
157176
}
177+
178+
/**
179+
* Returns the human-readable description for the processor response code.
180+
*
181+
* @return string
182+
*/
183+
public function get_response_code_message(): string {
184+
if ( ! $this->response_code() ) {
185+
return '';
186+
}
187+
$messages = array(
188+
'0000' => '0000: Approved',
189+
'00N7' => '00N7: Decline CVV2 Failure',
190+
'0100' => '0100: Refer to card issuer',
191+
'0390' => '0390: No credit account',
192+
'0500' => '0500: Do not honor',
193+
'0580' => '0580: Transaction not permitted to cardholder',
194+
'0800' => '0800: Bad response reversal amount',
195+
'0880' => '0880: Cryptographic failure',
196+
'0890' => '0890: Unavailable',
197+
'0960' => '0960: System malfunction',
198+
'1000' => '1000: Partial Approval',
199+
'10BR' => '10BR: 3DS Authentication Failure',
200+
'1300' => '1300: Invalid data format',
201+
'1310' => '1310: Invalid amount',
202+
'1312' => '1312: Invalid transaction card issuer acquirer',
203+
'1317' => '1317: Invalid capture date',
204+
'1320' => '1320: Invalid currency code',
205+
'1330' => '1330: Invalid account',
206+
'1335' => '1335: Invalid account type',
207+
'1340' => '1340: Invalid terminal id',
208+
'1350' => '1350: Invalid merchant/terminal city',
209+
'1360' => '1360: Bad or malformed request',
210+
'1370' => '1370: Issuer unavailable',
211+
'1380' => '1380: Updates not allowed',
212+
'1382' => '1382: Bad CVV2',
213+
'1384' => '1384: Similar transaction recently submitted',
214+
'1390' => '1390: Trace number error',
215+
'1393' => '1393: Transaction amount range error',
216+
'5100' => '5100: Generic Decline',
217+
'5110' => '5110: CVV2 Failure',
218+
'5120' => '5120: Insufficient funds',
219+
'5130' => '5130: Invalid PIN',
220+
'5140' => '5140: Card closed',
221+
'5150' => '5150: Pick up card (fraud)',
222+
'5160' => '5160: Unauthorized user',
223+
'5170' => '5170: Card blocked',
224+
'5180' => '5180: Declined by the issuer',
225+
'5200' => '5200: Account closed',
226+
'5400' => '5400: Expired card',
227+
'5910' => '5910: Issuer not available, return to issuer',
228+
'5920' => '5920: Issuer not available, return to issuer',
229+
'5930' => '5930: Card not activated',
230+
'6300' => '6300: Account blocked',
231+
'9100' => '9100: Declined, Please Retry',
232+
'9500' => '9500: Suspected Fraud',
233+
'9510' => '9510: Security Violation',
234+
'9520' => '9520: Lost or Stolen Card',
235+
'9530' => '9530: Hold - Call issuer',
236+
'9540' => '9540: Refused Card',
237+
'9600' => '9600: Unacceptable PIN - Transaction Declined - Retry',
238+
'PCNF' => 'PCNF: Purchase Confirmation Not Received',
239+
'PCOM' => 'PCOM: Purchase Confirmation Received',
240+
);
241+
return $messages[ $this->response_code() ] ?? sprintf( '%s: Unknown response code', $this->response_code() );
242+
}
158243
}

modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ class FraudProcessorResponseFactory {
2525
* @return FraudProcessorResponse
2626
*/
2727
public function from_paypal_response( stdClass $data ): FraudProcessorResponse {
28-
$avs_code = ( $data->avs_code ?? null ) ?: null;
29-
$cvv_code = ( $data->cvv_code ?? null ) ?: null;
28+
$avs_code = ( $data->avs_code ?? null ) ?: null;
29+
$cvv_code = ( $data->cvv_code ?? null ) ?: null;
30+
$response_code = ( $data->response_code ?? null ) ?: null;
3031

31-
return new FraudProcessorResponse( $avs_code, $cvv_code );
32+
return new FraudProcessorResponse( $avs_code, $cvv_code, $response_code );
3233
}
3334
}

modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,18 @@ protected function handle_fraud( FraudProcessorResponse $fraud, Order $order, WC
106106
<li>%1$s</li>
107107
<li>%2$s</li>
108108
<li>%3$s</li>
109+
%4$s
109110
</ul>';
111+
$response_code_item = $fraud->response_code()
112+
? sprintf(
113+
'<li>%s</li>',
114+
sprintf(
115+
/* translators: %s is processor response code and description */
116+
esc_html__( 'Response Code: %s', 'woocommerce-paypal-payments' ),
117+
esc_html( $fraud->get_response_code_message() )
118+
)
119+
)
120+
: '';
110121
$response_order_note_result = sprintf(
111122
$response_order_note_result_format,
112123
/* translators: %1$s is card brand and %2$s card last 4 digits */
@@ -115,6 +126,7 @@ protected function handle_fraud( FraudProcessorResponse $fraud, Order $order, WC
115126
sprintf( __( 'AVS: %s', 'woocommerce-paypal-payments' ), $fraud->get_avs_code_message() ),
116127
/* translators: %s is fraud CVV message */
117128
sprintf( __( 'CVV: %s', 'woocommerce-paypal-payments' ), $fraud->get_cvv2_code_message() ),
129+
$response_code_item,
118130
);
119131
$response_order_note = sprintf(
120132
$response_order_note_format,

modules/ppcp-wc-gateway/src/Processor/PaymentsStatusHandlingTrait.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,28 @@ protected function handle_capture_status(
8787
// It is checked in the capture endpoint already, but there are other ways to capture,
8888
// such as when paid via saved card.
8989
case CaptureStatus::DECLINED:
90+
$fraud = $capture->fraud_processor_response();
91+
if ( $fraud && $fraud->response_code() ) {
92+
$wc_order->add_order_note(
93+
sprintf(
94+
/* translators: %s - processor response code and description */
95+
__( 'PayPal payment declined. Processor response: %s', 'woocommerce-paypal-payments' ),
96+
$fraud->get_response_code_message()
97+
)
98+
);
99+
}
90100
$wc_order->update_status(
91101
'failed',
92102
__( 'Could not capture the payment.', 'woocommerce-paypal-payments' )
93103
);
94-
throw new RuntimeException( __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' ) );
104+
$decline_message = ( $fraud && $fraud->response_code() )
105+
? sprintf(
106+
/* translators: %s - processor response code and description */
107+
__( 'Payment declined by card processor: %s. Please use a different payment method or contact your bank.', 'woocommerce-paypal-payments' ),
108+
$fraud->get_response_code_message()
109+
)
110+
: __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' );
111+
throw new RuntimeException( $decline_message );
95112
case CaptureStatus::PENDING:
96113
case CaptureStatus::FAILED:
97114
$wc_order->update_status(

modules/ppcp-wc-gateway/src/WCGatewayModule.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
1818
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
1919
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
20+
use WooCommerce\PayPalCommerce\ApiClient\Entity\FraudProcessorResponse;
2021
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
2122
use WooCommerce\PayPalCommerce\ApiClient\Helper\ReferenceTransactionStatus;
2223
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
@@ -166,6 +167,30 @@ function ( int $order_id ) use ( $fees_renderer ) {
166167
}
167168
);
168169

170+
add_action(
171+
'woocommerce_admin_order_totals_after_total',
172+
function ( int $order_id ) {
173+
$wc_order = wc_get_order( $order_id );
174+
if ( ! $wc_order instanceof WC_Order ) {
175+
return;
176+
}
177+
$fraud_result = $wc_order->get_meta( PayPalGateway::FRAUD_RESULT_META_KEY );
178+
if ( empty( $fraud_result['response_code'] ) ) {
179+
return;
180+
}
181+
$fraud = new FraudProcessorResponse(
182+
$fraud_result['avs_code'] ?? null,
183+
$fraud_result['cvv2_code'] ?? null,
184+
$fraud_result['response_code']
185+
);
186+
printf(
187+
'<tr><td class="label">%s:</td><td width="1%%"></td><td class="total">%s</td></tr>',
188+
esc_html__( 'Processor Response', 'woocommerce-paypal-payments' ),
189+
esc_html( $fraud->get_response_code_message() )
190+
);
191+
}
192+
);
193+
169194
add_action(
170195
'admin_enqueue_scripts',
171196
function () use ( $c ) {

0 commit comments

Comments
 (0)