Capture and refund support

This commit is contained in:
Mike Jolley 2016-07-27 16:55:42 +01:00
parent a816e8a92d
commit d45be39758
4 changed files with 155 additions and 110 deletions

View File

@ -267,6 +267,7 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
WC_Gateway_Paypal_API_Handler::$api_username = $this->get_option( 'api_username' ); WC_Gateway_Paypal_API_Handler::$api_username = $this->get_option( 'api_username' );
WC_Gateway_Paypal_API_Handler::$api_password = $this->get_option( 'api_password' ); WC_Gateway_Paypal_API_Handler::$api_password = $this->get_option( 'api_password' );
WC_Gateway_Paypal_API_Handler::$api_signature = $this->get_option( 'api_signature' ); WC_Gateway_Paypal_API_Handler::$api_signature = $this->get_option( 'api_signature' );
WC_Gateway_Paypal_API_Handler::$sandbox = $this->testmode;
} }
/** /**
@ -286,7 +287,7 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
$this->init_api(); $this->init_api();
$result = WC_Gateway_Paypal_API_Handler::refund_order( $order, $amount, $reason, $this->testmode ); $result = WC_Gateway_Paypal_API_Handler::refund_transaction( $order, $amount, $reason );
if ( is_wp_error( $result ) ) { if ( is_wp_error( $result ) ) {
$this->log( 'Refund Failed: ' . $result->get_error_message() ); $this->log( 'Refund Failed: ' . $result->get_error_message() );
@ -295,15 +296,15 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
$this->log( 'Refund Result: ' . print_r( $result, true ) ); $this->log( 'Refund Result: ' . print_r( $result, true ) );
switch ( strtolower( $result['ACK'] ) ) { switch ( strtolower( $result->ACK ) ) {
case 'success': case 'success':
case 'successwithwarning': case 'successwithwarning':
$order->add_order_note( sprintf( __( 'Refunded %s - Refund ID: %s', 'woocommerce' ), $result['GROSSREFUNDAMT'], $result['REFUNDTRANSACTIONID'] ) ); $order->add_order_note( sprintf( __( 'Refunded %s - Refund ID: %s', 'woocommerce' ), $result->GROSSREFUNDAMT, $result->REFUNDTRANSACTIONID ) );
return true; return true;
break; break;
} }
return isset( $result['L_LONGMESSAGE0'] ) ? new WP_Error( 'error', $result['L_LONGMESSAGE0'] ) : false; return isset( $result->L_LONGMESSAGE0 ) ? new WP_Error( 'error', $result->L_LONGMESSAGE0 ) : false;
} }
/** /**
@ -314,9 +315,30 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
public function capture_payment( $order_id ) { public function capture_payment( $order_id ) {
$order = wc_get_order( $order_id ); $order = wc_get_order( $order_id );
if ( 'paypal' === $order->payment_method ) { if ( 'paypal' === $order->payment_method && 'pending' === get_post_meta( $order->id, '_paypal_status', true ) && $order->get_transaction_id() ) {
$this->init_api(); $this->init_api();
WC_Gateway_Paypal_API_Handler::do_capture( $order, $amount ); $result = WC_Gateway_Paypal_API_Handler::do_capture( $order );
if ( is_wp_error( $result ) ) {
$this->log( 'Capture Failed: ' . $result->get_error_message() );
$order->add_order_note( sprintf( __( 'Payment could not captured: %s', 'woocommerce' ), $result->get_error_message() ) );
return;
}
$this->log( 'Capture Result: ' . print_r( $result, true ) );
if ( ! empty( $result->PAYMENTSTATUS ) ) {
switch ( $result->PAYMENTSTATUS ) {
case 'Completed' :
$order->add_order_note( sprintf( __( 'Payment of %s was captured - Auth ID: %s, Transaction ID: %s', 'woocommerce' ), $result->AMT, $result->AUTHORIZATIONID, $result->TRANSACTIONID ) );
update_post_meta( $order->id, '_paypal_status', $result->PAYMENTSTATUS );
update_post_meta( $order->id, '_transaction_id', $result->TRANSACTIONID );
break;
default :
$order->add_order_note( sprintf( __( 'Payment could not captured - Auth ID: %s, Status: %s', 'woocommerce' ), $result->AUTHORIZATIONID, $result->PAYMENTSTATUS ) );
break;
}
}
} }
} }
} }

View File

@ -19,6 +19,9 @@ class WC_Gateway_Paypal_API_Handler {
/** @var string API Signature */ /** @var string API Signature */
public static $api_signature; public static $api_signature;
/** @var string API Signature */
public static $sandbox = false;
/** /**
* Get capture request args. * Get capture request args.
* See https://developer.paypal.com/docs/classic/api/merchant/DoCapture_API_Operation_NVP/. * See https://developer.paypal.com/docs/classic/api/merchant/DoCapture_API_Operation_NVP/.
@ -35,86 +38,13 @@ class WC_Gateway_Paypal_API_Handler {
'PWD' => self::$api_password, 'PWD' => self::$api_password,
'METHOD' => 'DoCapture', 'METHOD' => 'DoCapture',
'AUTHORIZATIONID' => $order->get_transaction_id(), 'AUTHORIZATIONID' => $order->get_transaction_id(),
'AMT' => number_format( $amount, 2, '.', '' ), 'AMT' => number_format( is_null( $amount ) ? $order->get_total() : $amount, 2, '.', '' ),
'CURRENCYCODE' => $order->get_order_currency(), 'CURRENCYCODE' => $order->get_order_currency(),
'COMPLETETYPE' => 'Complete', 'COMPLETETYPE' => 'Complete',
); );
return apply_filters( 'woocommerce_paypal_capture_request', $request, $order, $amount ); return apply_filters( 'woocommerce_paypal_capture_request', $request, $order, $amount );
} }
/**
* Capture an authorization.
* @param WC_Order $order
* @param float $amount
* @return object Either an object of name value pairs for a success, or a WP_ERROR object.
*/
public static function do_capture( $order, $amount ) {
$response = wp_safe_remote_post(
$sandbox ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp',
array(
'method' => 'POST',
'body' => self::get_capture_request( $order, $amount ),
'timeout' => 70,
'user-agent' => 'WooCommerce',
'httpversion' => '1.1'
)
);
WC_Gateway_Paypal::log( 'DoCapture Response: ' . print_r( $response, true ) );
if ( empty( $response['body'] ) ) {
return new WP_Error( 'paypal-api', 'Empty Response' );
} elseif ( is_wp_error( $response ) ) {
return $response;
}
parse_str( $response['body'], $response_array );
return (object) $response_array;
}
/**
* Refund an order via PayPal.
* @param WC_Order $order
* @param float $amount
* @param string $reason
* @param bool $sandbox
* @return array|wp_error The parsed response from paypal, or a WP_Error object
*/
public static function refund_order( $order, $amount = null, $reason = '', $sandbox = false ) {
$response = wp_safe_remote_post(
$sandbox ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp',
array(
'method' => 'POST',
'body' => self::get_request( $order, $amount, $reason ),
'timeout' => 70,
'user-agent' => 'WooCommerce',
'httpversion' => '1.1'
)
);
WC_Gateway_Paypal::log( 'Refund Response: ' . print_r( $response, true ) );
if ( is_wp_error( $response ) ) {
return $response;
}
if ( empty( $response['body'] ) ) {
return new WP_Error( 'paypal-refunds', 'Empty Response' );
}
parse_str( $response['body'], $response_array );
return $response_array;
}
}
/**
* Here for backwards compatibility.
* @since 2.7.0
*/
class WC_Gateway_Paypal_Refund extends WC_Gateway_Paypal_API_Handler {
/** /**
* Get refund request args. * Get refund request args.
* @param WC_Order $order * @param WC_Order $order
@ -122,7 +52,7 @@ class WC_Gateway_Paypal_Refund extends WC_Gateway_Paypal_API_Handler {
* @param string $reason * @param string $reason
* @return array * @return array
*/ */
public static function get_request( $order, $amount = null, $reason = '' ) { public static function get_refund_request( $order, $amount = null, $reason = '' ) {
$request = array( $request = array(
'VERSION' => '84.0', 'VERSION' => '84.0',
'SIGNATURE' => self::$api_signature, 'SIGNATURE' => self::$api_signature,
@ -142,16 +72,86 @@ class WC_Gateway_Paypal_Refund extends WC_Gateway_Paypal_API_Handler {
} }
/** /**
* Handle response from PayPal API. * Capture an authorization.
* @param WC_Order $order
* @param float $amount
* @return object Either an object of name value pairs for a success, or a WP_ERROR object.
*/ */
public static function handle_response( $response, $order ) { public static function do_capture( $order, $amount = null ) {
$raw_response = wp_safe_remote_post(
self::$sandbox ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp',
array(
'method' => 'POST',
'body' => self::get_capture_request( $order, $amount ),
'timeout' => 70,
'user-agent' => 'WooCommerce',
'httpversion' => '1.1'
)
);
switch ( strtolower( $response['ACK'] ) ) { WC_Gateway_Paypal::log( 'DoCapture Response: ' . print_r( $raw_response, true ) );
case 'success':
case 'successwithwarning': if ( empty( $raw_response['body'] ) ) {
$order->add_order_note( sprintf( __( 'Refunded %s - Refund ID: %s', 'woocommerce' ), $response['GROSSREFUNDAMT'], $response['REFUNDTRANSACTIONID'] ) ); return new WP_Error( 'paypal-api', 'Empty Response' );
return true; } elseif ( is_wp_error( $raw_response ) ) {
break; return $raw_response;
}
parse_str( $raw_response['body'], $response );
return (object) $response;
}
/**
* Refund an order via PayPal.
* @param WC_Order $order
* @param float $amount
* @param string $reason
* @return object Either an object of name value pairs for a success, or a WP_ERROR object.
*/
public static function refund_transaction( $order, $amount = null, $reason = '' ) {
$raw_response = wp_safe_remote_post(
self::$sandbox ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp',
array(
'method' => 'POST',
'body' => self::get_refund_request( $order, $amount, $reason ),
'timeout' => 70,
'user-agent' => 'WooCommerce',
'httpversion' => '1.1'
)
);
WC_Gateway_Paypal::log( 'Refund Response: ' . print_r( $raw_response, true ) );
if ( empty( $raw_response['body'] ) ) {
return new WP_Error( 'paypal-api', 'Empty Response' );
} elseif ( is_wp_error( $raw_response ) ) {
return $raw_response;
}
parse_str( $raw_response['body'], $response );
return (object) $response;
}
}
/**
* Here for backwards compatibility.
* @since 2.7.0
*/
class WC_Gateway_Paypal_Refund extends WC_Gateway_Paypal_API_Handler {
public static function get_request( $order, $amount = null, $reason = '' ) {
return self::get_refund_request( $order, $amount, $reason );
}
public static function refund_order( $order, $amount = null, $reason = '', $sandbox = false ) {
if ( $sandbox ) {
self::$sandbox = $sandbox;
}
$result = self::refund_transaction( $order, $amount, $reason );
if ( is_wp_error( $result ) ) {
return $result;
} else {
return (array) $result;
} }
} }
} }

View File

@ -192,7 +192,11 @@ class WC_Gateway_Paypal_IPN_Handler extends WC_Gateway_Paypal_Response {
} }
} else { } else {
$this->payment_on_hold( $order, sprintf( __( 'Payment pending: %s', 'woocommerce' ), $posted['pending_reason'] ) ); if ( 'authorization' === $posted['pending_reason'] ) {
$this->payment_on_hold( $order, __( 'Payment authorized. Change payment status to processing or complete to capture funds.', 'woocommerce' ) );
} else {
$this->payment_on_hold( $order, sprintf( __( 'Payment pending (%s).', 'woocommerce' ), $posted['pending_reason'] ) );
}
} }
} }
@ -304,6 +308,12 @@ class WC_Gateway_Paypal_IPN_Handler extends WC_Gateway_Paypal_Response {
if ( ! empty( $posted['payment_type'] ) ) { if ( ! empty( $posted['payment_type'] ) ) {
update_post_meta( $order->id, 'Payment type', wc_clean( $posted['payment_type'] ) ); update_post_meta( $order->id, 'Payment type', wc_clean( $posted['payment_type'] ) );
} }
if ( ! empty( $posted['txn_id'] ) ) {
update_post_meta( $order->id, '_transaction_id', wc_clean( $posted['txn_id'] ) );
}
if ( ! empty( $posted['payment_status'] ) ) {
update_post_meta( $order->id, '_paypal_status', wc_clean( $posted['payment_status'] ) );
}
} }
/** /**

View File

@ -88,7 +88,13 @@ class WC_Gateway_Paypal_PDT_Handler extends WC_Gateway_Paypal_Response {
$transaction_result = $this->validate_transaction( $transaction ); $transaction_result = $this->validate_transaction( $transaction );
if ( $transaction_result && 'completed' === $status ) { WC_Gateway_Paypal::log( 'PDT Transaction Result: ' . print_r( $transaction_result, true ) );
update_post_meta( $order->id, '_paypal_status', $status );
update_post_meta( $order->id, '_transaction_id', $transaction );
if ( $transaction_result ) {
if ( 'completed' === $status ) {
if ( $order->get_total() != $amount ) { if ( $order->get_total() != $amount ) {
WC_Gateway_Paypal::log( 'Payment error: Amounts do not match (amt ' . $amount . ')' ); WC_Gateway_Paypal::log( 'Payment error: Amounts do not match (amt ' . $amount . ')' );
$this->payment_on_hold( $order, sprintf( __( 'Validation error: PayPal amounts do not match (amt %s).', 'woocommerce' ), $amount ) ); $this->payment_on_hold( $order, sprintf( __( 'Validation error: PayPal amounts do not match (amt %s).', 'woocommerce' ), $amount ) );
@ -112,6 +118,13 @@ class WC_Gateway_Paypal_PDT_Handler extends WC_Gateway_Paypal_Response {
update_post_meta( $order->id, 'Payment type', $transaction_result['payment_type'] ); update_post_meta( $order->id, 'Payment type', $transaction_result['payment_type'] );
} }
} }
} else {
if ( 'authorization' === $transaction_result['pending_reason'] ) {
$this->payment_on_hold( $order, __( 'Payment authorized. Change payment status to processing or complete to capture funds.', 'woocommerce' ) );
} else {
$this->payment_on_hold( $order, sprintf( __( 'Payment pending (%s).', 'woocommerce' ), $transaction_result['pending_reason'] ) );
}
}
} }
} }
} }