Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,14 @@ public function capture( Order $order ): Order {

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

$capture_status = $order->purchase_units()[0]->payments()->captures()[0]->status() ?? null;
$first_capture = $order->purchase_units()[0]->payments()->captures()[0] ?? null;
$capture_status = $first_capture ? $first_capture->status() : null;
if ( $capture_status && $capture_status->is( CaptureStatus::DECLINED ) ) {
throw new RuntimeException( __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' ) );
$fraud = $first_capture->fraud_processor_response();
$decline_message = $fraud
? $fraud->get_customer_decline_message()
: __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' );
throw new RuntimeException( $decline_message );
}

return $order;
Expand Down
112 changes: 107 additions & 5 deletions modules/ppcp-api-client/src/Entity/FraudProcessorResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,24 @@ class FraudProcessorResponse {
*/
protected string $cvv2_code;

/**
* The processor response code (e.g. 9500, 9100).
*
* @var string
*/
protected string $response_code;

/**
* FraudProcessorResponse constructor.
*
* @param string|null $avs_code The AVS response code.
* @param string|null $cvv2_code The CVV response code.
* @param string|null $response_code The processor response code.
*/
public function __construct( ?string $avs_code, ?string $cvv2_code ) {
$this->avs_code = (string) $avs_code;
$this->cvv2_code = (string) $cvv2_code;
public function __construct( ?string $avs_code, ?string $cvv2_code, ?string $response_code = null ) {
$this->avs_code = (string) $avs_code;
$this->cvv2_code = (string) $cvv2_code;
$this->response_code = (string) $response_code;
}

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

/**
* Returns the processor response code.
*
* @return string
*/
public function response_code(): string {
return $this->response_code;
}

/**
* Returns the object as array.
*
* @return array
*/
public function to_array(): array {
return array(
'avs_code' => $this->avs_code(),
'cvv2_code' => $this->cvv_code(),
'avs_code' => $this->avs_code(),
'cvv2_code' => $this->cvv_code(),
'response_code' => $this->response_code(),
);
}

Expand Down Expand Up @@ -155,4 +174,87 @@ public function get_cvv2_code_message(): string {
*/
return $messages[ $this->cvv_code() ] ?? sprintf( '%s: Error', $this->cvv_code() );
}

/**
* Returns the customer-facing decline message, including the processor response code when available.
*
* @return string
*/
public function get_customer_decline_message(): string {
if ( $this->response_code() ) {
return sprintf(
/* translators: %s - processor response code and description */
__( 'Payment declined by card processor: %s. Please use a different payment method or contact your bank.', 'woocommerce-paypal-payments' ),
$this->get_response_code_message()
);
}

return __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' );
}

/**
* Returns the human-readable description for the processor response code.
*
* @return string
*/
public function get_response_code_message(): string {
if ( ! $this->response_code() ) {
return '';
}
$messages = array(
'0000' => '0000: Approved',
'00N7' => '00N7: Decline CVV2 Failure',
'0100' => '0100: Refer to card issuer',
'0390' => '0390: No credit account',
'0500' => '0500: Do not honor',
'0580' => '0580: Transaction not permitted to cardholder',
'0800' => '0800: Bad response reversal amount',
'0880' => '0880: Cryptographic failure',
'0890' => '0890: Unavailable',
'0960' => '0960: System malfunction',
'1000' => '1000: Partial Approval',
'10BR' => '10BR: 3DS Authentication Failure',
'1300' => '1300: Invalid data format',
'1310' => '1310: Invalid amount',
'1312' => '1312: Invalid transaction card issuer acquirer',
'1317' => '1317: Invalid capture date',
'1320' => '1320: Invalid currency code',
'1330' => '1330: Invalid account',
'1335' => '1335: Invalid account type',
'1340' => '1340: Invalid terminal id',
'1350' => '1350: Invalid merchant/terminal city',
'1360' => '1360: Bad or malformed request',
'1370' => '1370: Issuer unavailable',
'1380' => '1380: Updates not allowed',
'1382' => '1382: Bad CVV2',
'1384' => '1384: Similar transaction recently submitted',
'1390' => '1390: Trace number error',
'1393' => '1393: Transaction amount range error',
'5100' => '5100: Generic Decline',
'5110' => '5110: CVV2 Failure',
'5120' => '5120: Insufficient funds',
'5130' => '5130: Invalid PIN',
'5140' => '5140: Card closed',
'5150' => '5150: Pick up card (fraud)',
'5160' => '5160: Unauthorized user',
'5170' => '5170: Card blocked',
'5180' => '5180: Declined by the issuer',
'5200' => '5200: Account closed',
'5400' => '5400: Expired card',
'5910' => '5910: Issuer not available, return to issuer',
'5920' => '5920: Issuer not available, return to issuer',
'5930' => '5930: Card not activated',
'6300' => '6300: Account blocked',
'9100' => '9100: Declined, Please Retry',
'9500' => '9500: Suspected Fraud',
'9510' => '9510: Security Violation',
'9520' => '9520: Lost or Stolen Card',
'9530' => '9530: Hold - Call issuer',
'9540' => '9540: Refused Card',
'9600' => '9600: Unacceptable PIN - Transaction Declined - Retry',
'PCNF' => 'PCNF: Purchase Confirmation Not Received',
'PCOM' => 'PCOM: Purchase Confirmation Received',
);
return $messages[ $this->response_code() ] ?? sprintf( '%s: Unknown response code', $this->response_code() );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ class FraudProcessorResponseFactory {
* @return FraudProcessorResponse
*/
public function from_paypal_response( stdClass $data ): FraudProcessorResponse {
$avs_code = ( $data->avs_code ?? null ) ?: null;
$cvv_code = ( $data->cvv_code ?? null ) ?: null;
$avs_code = ( $data->avs_code ?? null ) ?: null;
$cvv_code = ( $data->cvv_code ?? null ) ?: null;
$response_code = ( $data->response_code ?? null ) ?: null;

return new FraudProcessorResponse( $avs_code, $cvv_code );
return new FraudProcessorResponse( $avs_code, $cvv_code, $response_code );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,18 @@ protected function handle_fraud( FraudProcessorResponse $fraud, Order $order, WC
<li>%1$s</li>
<li>%2$s</li>
<li>%3$s</li>
%4$s
</ul>';
$response_code_item = $fraud->response_code()
? sprintf(
'<li>%s</li>',
sprintf(
/* translators: %s is processor response code and description */
esc_html__( 'Response Code: %s', 'woocommerce-paypal-payments' ),
esc_html( $fraud->get_response_code_message() )
)
)
: '';
$response_order_note_result = sprintf(
$response_order_note_result_format,
/* translators: %1$s is card brand and %2$s card last 4 digits */
Expand All @@ -115,6 +126,7 @@ protected function handle_fraud( FraudProcessorResponse $fraud, Order $order, WC
sprintf( __( 'AVS: %s', 'woocommerce-paypal-payments' ), $fraud->get_avs_code_message() ),
/* translators: %s is fraud CVV message */
sprintf( __( 'CVV: %s', 'woocommerce-paypal-payments' ), $fraud->get_cvv2_code_message() ),
$response_code_item,
);
$response_order_note = sprintf(
$response_order_note_format,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,19 @@ protected function handle_capture_status(
// It is checked in the capture endpoint already, but there are other ways to capture,
// such as when paid via saved card.
case CaptureStatus::DECLINED:
$wc_order->update_status(
'failed',
__( 'Could not capture the payment.', 'woocommerce-paypal-payments' )
);
throw new RuntimeException( __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' ) );
$fraud = $capture->fraud_processor_response();
$status_note = ( $fraud && $fraud->response_code() )
? sprintf(
/* translators: %s - processor response code and description */
__( 'Could not capture the payment. Processor response: %s', 'woocommerce-paypal-payments' ),
$fraud->get_response_code_message()
)
: __( 'Could not capture the payment.', 'woocommerce-paypal-payments' );
$wc_order->update_status( 'failed', $status_note );
$decline_message = $fraud
? $fraud->get_customer_decline_message()
: __( 'Payment provider declined the payment, please use a different payment method.', 'woocommerce-paypal-payments' );
throw new RuntimeException( $decline_message );
case CaptureStatus::PENDING:
case CaptureStatus::FAILED:
$wc_order->update_status(
Expand Down
21 changes: 21 additions & 0 deletions modules/ppcp-wc-gateway/src/WCGatewayModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,27 @@ function ( int $order_id ) use ( $fees_renderer ) {
}
);

add_action(
'woocommerce_admin_order_totals_after_total',
function ( int $order_id ) use ( $c ) {
$wc_order = wc_get_order( $order_id );
if ( ! $wc_order instanceof WC_Order ) {
return;
}
$fraud_result = $wc_order->get_meta( PayPalGateway::FRAUD_RESULT_META_KEY );
if ( empty( $fraud_result['response_code'] ) ) {
return;
}
$fraud = $c->get( 'api.factory.fraud-processor-response' )
->from_paypal_response( (object) $fraud_result );
printf(
'<tr><td class="label">%s:</td><td width="1%%"></td><td class="total">%s</td></tr>',
esc_html__( 'Processor Response', 'woocommerce-paypal-payments' ),
esc_html( $fraud->get_response_code_message() )
);
}
);

add_action(
'admin_enqueue_scripts',
function () use ( $c ) {
Expand Down
Loading
Loading