woocommerce/includes/gateways/paypal/class-wc-gateway-paypal.php

1065 lines
36 KiB
PHP
Raw Normal View History

2012-05-26 16:25:07 +00:00
<?php
2013-02-20 17:14:46 +00:00
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
2012-05-26 16:25:07 +00:00
/**
* PayPal Standard Payment Gateway
*
2012-05-26 16:25:07 +00:00
* Provides a PayPal Standard Payment Gateway.
*
* @class WC_Paypal
* @extends WC_Gateway_Paypal
* @version 2.0.0
2012-08-15 18:15:06 +00:00
* @package WooCommerce/Classes/Payment
* @author WooThemes
2012-05-26 16:25:07 +00:00
*/
class WC_Gateway_Paypal extends WC_Payment_Gateway {
2012-11-27 16:22:47 +00:00
2012-09-12 12:36:34 +00:00
var $notify_url;
2012-11-27 16:22:47 +00:00
/**
* Constructor for the gateway.
*
* @access public
* @return void
*/
public function __construct() {
$this->id = 'paypal';
$this->icon = apply_filters( 'woocommerce_paypal_icon', WC()->plugin_url() . '/assets/images/icons/paypal.png' );
$this->has_fields = false;
$this->order_button_text = __( 'Proceed to PayPal', 'woocommerce' );
$this->liveurl = 'https://www.paypal.com/cgi-bin/webscr';
$this->testurl = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
$this->method_title = __( 'PayPal', 'woocommerce' );
$this->method_description = __( 'PayPal standard works by sending the user to PayPal to enter their payment information.', 'woocommerce' );
$this->notify_url = WC()->api_request_url( 'WC_Gateway_Paypal' );
$this->supports = array(
'products',
'refunds'
);
2012-05-26 16:25:07 +00:00
// Load the settings.
2013-01-02 13:38:33 +00:00
$this->init_form_fields();
2012-05-26 16:25:07 +00:00
$this->init_settings();
2012-05-26 16:25:07 +00:00
// Define user set variables
$this->title = $this->get_option( 'title' );
$this->description = $this->get_option( 'description' );
$this->email = $this->get_option( 'email' );
2013-03-11 10:36:51 +00:00
$this->receiver_email = $this->get_option( 'receiver_email', $this->email );
$this->testmode = $this->get_option( 'testmode' );
$this->send_shipping = $this->get_option( 'send_shipping' );
$this->address_override = $this->get_option( 'address_override' );
$this->debug = $this->get_option( 'debug' );
$this->page_style = $this->get_option( 'page_style' );
$this->invoice_prefix = $this->get_option( 'invoice_prefix', 'WC-' );
2013-07-23 09:18:58 +00:00
$this->paymentaction = $this->get_option( 'paymentaction', 'sale' );
2013-08-19 11:28:29 +00:00
$this->identity_token = $this->get_option( 'identity_token', '' );
$this->api_username = $this->get_option( 'api_username' );
$this->api_password = $this->get_option( 'api_password' );
$this->api_signature = $this->get_option( 'api_signature' );
2012-05-26 16:25:07 +00:00
// Logs
if ( 'yes' == $this->debug ) {
$this->log = new WC_Logger();
}
2012-05-26 16:25:07 +00:00
// Actions
add_action( 'valid-paypal-standard-ipn-request', array( $this, 'successful_request' ) );
add_action( 'woocommerce_receipt_paypal', array( $this, 'receipt_page' ) );
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
2013-08-19 11:28:29 +00:00
add_action( 'woocommerce_thankyou_paypal', array( $this, 'pdt_return_handler' ) );
2012-11-27 16:22:47 +00:00
2012-09-12 12:36:34 +00:00
// Payment listener/API hook
add_action( 'woocommerce_api_wc_gateway_paypal', array( $this, 'check_ipn_response' ) );
if ( ! $this->is_valid_for_use() ) {
$this->enabled = false;
}
}
/**
* Check if this gateway is enabled and available in the user's country
*
* @access public
* @return bool
*/
function is_valid_for_use() {
if ( ! in_array( get_woocommerce_currency(), apply_filters( 'woocommerce_paypal_supported_currencies', array( 'AUD', 'BRL', 'CAD', 'MXN', 'NZD', 'HKD', 'SGD', 'USD', 'EUR', 'JPY', 'TRY', 'NOK', 'CZK', 'DKK', 'HUF', 'ILS', 'MYR', 'PHP', 'PLN', 'SEK', 'CHF', 'TWD', 'THB', 'GBP', 'RMB', 'RUB' ) ) ) ) {
return false;
}
2012-05-26 16:25:07 +00:00
return true;
}
2012-05-26 16:25:07 +00:00
/**
* Admin Panel Options
2012-05-26 16:25:07 +00:00
* - Options for bits like 'title' and availability on a country-by-country basis
*
* @since 1.0.0
*/
public function admin_options() {
if ( $this->is_valid_for_use() ) {
parent::admin_options();
} else {
?>
<div class="inline error"><p><strong><?php _e( 'Gateway Disabled', 'woocommerce' ); ?></strong>: <?php _e( 'PayPal does not support your store currency.', 'woocommerce' ); ?></p></div>
<?php
}
}
/**
* Initialise Gateway Settings Form Fields
*
* @access public
* @return void
*/
public function init_form_fields() {
$this->form_fields = array(
2012-05-26 16:25:07 +00:00
'enabled' => array(
2013-08-19 11:28:29 +00:00
'title' => __( 'Enable/Disable', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable PayPal standard', 'woocommerce' ),
'default' => 'yes'
),
2012-05-26 16:25:07 +00:00
'title' => array(
2013-08-19 11:28:29 +00:00
'title' => __( 'Title', 'woocommerce' ),
'type' => 'text',
'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ),
'default' => __( 'PayPal', 'woocommerce' ),
'desc_tip' => true,
),
2012-05-26 16:25:07 +00:00
'description' => array(
2013-08-19 11:28:29 +00:00
'title' => __( 'Description', 'woocommerce' ),
'type' => 'text',
'desc_tip' => true,
2013-08-19 11:28:29 +00:00
'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce' ),
'default' => __( 'Pay via PayPal; you can pay with your credit card if you don\'t have a PayPal account', 'woocommerce' )
),
2012-05-26 16:25:07 +00:00
'email' => array(
2013-08-19 11:28:29 +00:00
'title' => __( 'PayPal Email', 'woocommerce' ),
'type' => 'email',
'description' => __( 'Please enter your PayPal email address; this is needed in order to take payment.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => 'you@youremail.com'
),
'testmode' => array(
'title' => __( 'PayPal sandbox', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable PayPal sandbox', 'woocommerce' ),
'default' => 'no',
'description' => sprintf( __( 'PayPal sandbox can be used to test payments. Sign up for a developer account <a href="%s">here</a>.', 'woocommerce' ), 'https://developer.paypal.com/' ),
),
'debug' => array(
'title' => __( 'Debug Log', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable logging', 'woocommerce' ),
'default' => 'no',
'description' => sprintf( __( 'Log PayPal events, such as IPN requests, inside <code>%s</code>', 'woocommerce' ), wc_get_log_file_path( 'paypal' ) )
),
'shipping' => array(
'title' => __( 'Shipping options', 'woocommerce' ),
'type' => 'title',
'description' => '',
),
'send_shipping' => array(
'title' => __( 'Shipping details', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Send shipping details to PayPal instead of billing.', 'woocommerce' ),
'description' => __( 'PayPal allows us to send 1 address. If you are using PayPal for shipping labels you may prefer to send the shipping address rather than billing.', 'woocommerce' ),
'default' => 'no'
),
'address_override' => array(
'title' => __( 'Address override', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable "address_override" to prevent address information from being changed.', 'woocommerce' ),
'description' => __( 'PayPal verifies addresses therefore this setting can cause errors (we recommend keeping it disabled).', 'woocommerce' ),
'default' => 'no'
),
'advanced' => array(
'title' => __( 'Advanced options', 'woocommerce' ),
'type' => 'title',
'description' => '',
),
2013-03-11 10:36:51 +00:00
'receiver_email' => array(
2013-08-19 11:28:29 +00:00
'title' => __( 'Receiver Email', 'woocommerce' ),
'type' => 'email',
'description' => __( 'If your main PayPal email differs from the PayPal email entered above, input your main receiver email for your PayPal account here. This is used to validate IPN requests.', 'woocommerce' ),
2013-08-19 11:28:29 +00:00
'default' => '',
'desc_tip' => true,
'placeholder' => 'you@youremail.com'
),
'invoice_prefix' => array(
2013-08-19 11:28:29 +00:00
'title' => __( 'Invoice Prefix', 'woocommerce' ),
'type' => 'text',
'description' => __( 'Please enter a prefix for your invoice numbers. If you use your PayPal account for multiple stores ensure this prefix is unique as PayPal will not allow orders with the same invoice number.', 'woocommerce' ),
'default' => 'WC-',
'desc_tip' => true,
),
2013-07-23 09:18:58 +00:00
'paymentaction' => array(
2013-08-19 11:28:29 +00:00
'title' => __( 'Payment Action', 'woocommerce' ),
'type' => 'select',
'description' => __( 'Choose whether you wish to capture funds immediately or authorize payment only.', 'woocommerce' ),
'default' => 'sale',
'desc_tip' => true,
'options' => array(
'sale' => __( 'Capture', 'woocommerce' ),
'authorization' => __( 'Authorize', 'woocommerce' )
)
),
'page_style' => array(
2013-08-19 11:28:29 +00:00
'title' => __( 'Page Style', 'woocommerce' ),
'type' => 'text',
'description' => __( 'Optionally enter the name of the page style you wish to use. These are defined within your PayPal account.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' )
),
'identity_token' => array(
'title' => __( 'PayPal Identity Token', 'woocommerce' ),
'type' => 'text',
'description' => __( 'Optionally enable "Payment Data Transfer" (Profile > Website Payment Preferences) and then copy your identity token here. This will allow payments to be verified without the need for PayPal IPN.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' )
),
'api_details' => array(
'title' => __( 'API Credentials', 'woocommerce' ),
2013-08-19 11:28:29 +00:00
'type' => 'title',
'description' => sprintf( __( 'Enter your PayPal API credentials to process refunds via PayPal. Learn how to access your PayPal API Credentials %shere%s.', 'woocommerce' ), '<a href="https://developer.paypal.com/webapps/developer/docs/classic/api/apiCredentials/#creating-classic-api-credentials">', '</a>' ),
2013-08-19 11:28:29 +00:00
),
'api_username' => array(
'title' => __( 'API Username', 'woocommerce' ),
'type' => 'text',
'description' => __( 'Get your API credentials from PayPal.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' )
2013-08-19 11:28:29 +00:00
),
'api_password' => array(
'title' => __( 'API Password', 'woocommerce' ),
'type' => 'text',
'description' => __( 'Get your API credentials from PayPal.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' )
2013-08-19 11:28:29 +00:00
),
'api_signature' => array(
'title' => __( 'API Signature', 'woocommerce' ),
'type' => 'text',
'description' => __( 'Get your API credentials from PayPal.', 'woocommerce' ),
'default' => '',
'desc_tip' => true,
'placeholder' => __( 'Optional', 'woocommerce' )
2013-08-19 11:28:29 +00:00
),
);
}
2014-02-24 14:49:01 +00:00
/**
* Limit the length of item names
* @param string $item_name
* @return string
*/
public function paypal_item_name( $item_name ) {
if ( strlen( $item_name ) > 127 ) {
$item_name = substr( $item_name, 0, 124 ) . '...';
}
return html_entity_decode( $item_name, ENT_NOQUOTES, 'UTF-8' );
}
2012-08-15 18:15:06 +00:00
/**
2012-05-26 16:25:07 +00:00
* Get PayPal Args for passing to PP
2012-08-15 18:15:06 +00:00
*
* @access public
* @param mixed $order
* @return array
*/
2012-05-26 16:25:07 +00:00
function get_paypal_args( $order ) {
2012-05-26 16:25:07 +00:00
$order_id = $order->id;
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'Generating payment form for order ' . $order->get_order_number() . '. Notify URL: ' . $this->notify_url );
}
2012-10-20 14:40:27 +00:00
if ( in_array( $order->billing_country, array( 'US','CA' ) ) ) {
$order->billing_phone = str_replace( array( '(', '-', ' ', ')', '.' ), '', $order->billing_phone );
2012-05-26 16:25:07 +00:00
$phone_args = array(
2012-10-20 14:40:27 +00:00
'night_phone_a' => substr( $order->billing_phone, 0, 3 ),
'night_phone_b' => substr( $order->billing_phone, 3, 3 ),
'night_phone_c' => substr( $order->billing_phone, 6, 4 ),
'day_phone_a' => substr( $order->billing_phone, 0, 3 ),
'day_phone_b' => substr( $order->billing_phone, 3, 3 ),
'day_phone_c' => substr( $order->billing_phone, 6, 4 )
2012-05-26 16:25:07 +00:00
);
2012-10-20 14:40:27 +00:00
} else {
2012-05-26 16:25:07 +00:00
$phone_args = array(
'night_phone_b' => $order->billing_phone,
'day_phone_b' => $order->billing_phone
);
2012-10-20 14:40:27 +00:00
}
2012-05-26 16:25:07 +00:00
// PayPal Args
$paypal_args = array_merge(
array(
'cmd' => '_cart',
'business' => $this->email,
'no_note' => 1,
'currency_code' => get_woocommerce_currency(),
'charset' => 'UTF-8',
'rm' => is_ssl() ? 2 : 1,
'upload' => 1,
'return' => urlencode( esc_url( add_query_arg( 'utm_nooverride', '1', $this->get_return_url( $order ) ) ) ),
'cancel_return' => urlencode( esc_url( $order->get_cancel_order_url() ) ),
'page_style' => $this->page_style,
'paymentaction' => $this->paymentaction,
'bn' => 'WooThemes_Cart',
// Order key + ID
'invoice' => $this->invoice_prefix . ltrim( $order->get_order_number(), '#' ),
'custom' => serialize( array( $order_id, $order->order_key ) ),
2012-05-26 16:25:07 +00:00
// IPN
'notify_url' => $this->notify_url,
2012-05-26 16:25:07 +00:00
// Billing Address info
'first_name' => $order->billing_first_name,
'last_name' => $order->billing_last_name,
'company' => $order->billing_company,
'address1' => $order->billing_address_1,
'address2' => $order->billing_address_2,
'city' => $order->billing_city,
'state' => $this->get_paypal_state( $order->billing_country, $order->billing_state ),
'zip' => $order->billing_postcode,
'country' => $order->billing_country,
'email' => $order->billing_email
),
2012-05-26 16:25:07 +00:00
$phone_args
);
2012-05-26 16:25:07 +00:00
// Shipping
if ( 'yes' == $this->send_shipping ) {
2012-05-26 16:25:07 +00:00
$paypal_args['address_override'] = ( $this->address_override == 'yes' ) ? 1 : 0;
2012-05-26 16:25:07 +00:00
$paypal_args['no_shipping'] = 0;
2012-05-26 16:25:07 +00:00
// If we are sending shipping, send shipping address instead of billing
$paypal_args['first_name'] = $order->shipping_first_name;
$paypal_args['last_name'] = $order->shipping_last_name;
$paypal_args['company'] = $order->shipping_company;
$paypal_args['address1'] = $order->shipping_address_1;
$paypal_args['address2'] = $order->shipping_address_2;
$paypal_args['city'] = $order->shipping_city;
$paypal_args['state'] = $this->get_paypal_state( $order->shipping_country, $order->shipping_state );
2012-05-26 16:25:07 +00:00
$paypal_args['country'] = $order->shipping_country;
$paypal_args['zip'] = $order->shipping_postcode;
} else {
$paypal_args['no_shipping'] = 1;
}
// Try to send line items, or default to sending the order as a whole
if ( $line_items = $this->get_line_items( $order ) ) {
$paypal_args = array_merge( $paypal_args, $line_items );
} else {
2012-05-26 16:25:07 +00:00
// Don't pass items - paypal borks tax due to prices including tax. PayPal has no option for tax inclusive pricing sadly. Pass 1 item for the order items overall
$item_names = array();
if ( sizeof( $order->get_items() ) > 0 ) {
foreach ( $order->get_items() as $item ) {
if ( $item['qty'] ) {
2012-10-20 14:40:27 +00:00
$item_names[] = $item['name'] . ' x ' . $item['qty'];
}
}
}
$paypal_args['item_name_1'] = $this->paypal_item_name( sprintf( __( 'Order %s' , 'woocommerce'), $order->get_order_number() ) . " - " . implode( ', ', $item_names ) );
$paypal_args['quantity_1'] = '1';
$paypal_args['amount_1'] = number_format( $order->get_total() - round( $order->get_total_shipping() + $order->get_shipping_tax(), 2 ) + $order->get_order_discount(), 2, '.', '' );
2012-05-26 16:25:07 +00:00
// Shipping Cost
// No longer using shipping_1 because
// a) paypal ignore it if *any* shipping rules are within paypal
2013-03-03 17:07:31 +00:00
// b) paypal ignore anything over 5 digits, so 999.99 is the max
if ( ( $order->get_total_shipping() + $order->get_shipping_tax() ) > 0 ) {
2014-02-24 14:49:01 +00:00
$paypal_args['item_name_2'] = $this->paypal_item_name( __( 'Shipping via', 'woocommerce' ) . ' ' . ucwords( $order->get_shipping_method() ) );
2012-05-26 16:25:07 +00:00
$paypal_args['quantity_2'] = '1';
$paypal_args['amount_2'] = number_format( $order->get_total_shipping() + $order->get_shipping_tax(), 2, '.', '' );
2012-10-20 14:40:27 +00:00
}
// Discount
if ( $order->get_order_discount() ) {
$paypal_args['discount_amount_cart'] = $order->get_order_discount();
}
}
$paypal_args = apply_filters( 'woocommerce_paypal_args', $paypal_args );
2012-05-26 16:25:07 +00:00
return $paypal_args;
}
2012-11-27 16:22:47 +00:00
/**
* Get line items to send to paypal
*
* @param WC_Order $order
* @return array on success, or false when it is not possible to send line items
*/
private function get_line_items( $order ) {
// Do not send lines for tax inclusive prices
if ( 'yes' === get_option( 'woocommerce_calc_taxes' ) && 'yes' === get_option( 'woocommerce_prices_include_tax' ) ) {
return false;
}
2012-11-27 16:22:47 +00:00
// Do not send lines when order discount is present, or too many line items in the order.
if ( $order->get_order_discount() > 0 || ( sizeof( $order->get_items() ) + sizeof( $order->get_fees() ) ) >= 9 ) {
return false;
}
2012-11-27 16:22:47 +00:00
$item_loop = 0;
$args = array();
$args['tax_cart'] = $order->get_total_tax();
// Products
if ( sizeof( $order->get_items() ) > 0 ) {
foreach ( $order->get_items() as $item ) {
if ( ! $item['qty'] ) {
continue;
}
$item_loop ++;
$product = $order->get_product_from_item( $item );
$item_name = $item['name'];
$item_meta = new WC_Order_Item_Meta( $item['item_meta'] );
if ( $meta = $item_meta->display( true, true ) ) {
$item_name .= ' ( ' . $meta . ' )';
}
2012-11-27 16:22:47 +00:00
$args[ 'item_name_' . $item_loop ] = $this->paypal_item_name( $item_name );
$args[ 'quantity_' . $item_loop ] = $item['qty'];
$args[ 'amount_' . $item_loop ] = $order->get_item_subtotal( $item, false );
2012-11-27 16:22:47 +00:00
if ( $args[ 'amount_' . $item_loop ] < 0 ) {
return false; // Abort - negative line
}
2012-11-27 16:22:47 +00:00
if ( $product->get_sku() ) {
$args[ 'item_number_' . $item_loop ] = $product->get_sku();
2012-10-20 14:40:27 +00:00
}
}
}
2012-11-27 16:22:47 +00:00
// Discount
if ( $order->get_cart_discount() > 0 ) {
$args['discount_amount_cart'] = round( $order->get_cart_discount(), 2 );
}
// Fees
if ( sizeof( $order->get_fees() ) > 0 ) {
foreach ( $order->get_fees() as $item ) {
$item_loop ++;
$args[ 'item_name_' . $item_loop ] = $this->paypal_item_name( $item['name'] );
$args[ 'quantity_' . $item_loop ] = 1;
$args[ 'amount_' . $item_loop ] = $item['line_total'];
2012-11-27 16:22:47 +00:00
if ( $args[ 'amount_' . $item_loop ] < 0 ) {
return false; // Abort - negative line
2012-11-12 17:15:54 +00:00
}
}
2012-10-20 14:40:27 +00:00
}
// Shipping Cost item - paypal only allows shipping per item, we want to send shipping for the order
if ( $order->get_total_shipping() > 0 ) {
$item_loop ++;
$args[ 'item_name_' . $item_loop ] = $this->paypal_item_name( sprintf( __( 'Shipping via %s', 'woocommerce' ), $order->get_shipping_method() ) );
$args[ 'quantity_' . $item_loop ] = '1';
$args[ 'amount_' . $item_loop ] = number_format( $order->get_total_shipping(), 2, '.', '' );
}
return $args;
2012-05-26 16:25:07 +00:00
}
/**
2012-05-26 16:25:07 +00:00
* Generate the paypal button link
*
* @access public
* @param mixed $order_id
* @return string
*/
public function generate_paypal_form( $order_id ) {
$order = wc_get_order( $order_id );
2012-05-26 16:25:07 +00:00
if ( 'yes' == $this->testmode ) {
$paypal_adr = $this->testurl . '?test_ipn=1&';
} else {
$paypal_adr = $this->liveurl . '?';
}
2012-05-26 16:25:07 +00:00
$paypal_args = $this->get_paypal_args( $order );
2012-05-26 16:25:07 +00:00
$paypal_args_array = array();
foreach ( $paypal_args as $key => $value ) {
$paypal_args_array[] = '<input type="hidden" name="'.esc_attr( $key ) . '" value="' . esc_attr( $value ) . '" />';
2012-05-26 16:25:07 +00:00
}
wc_enqueue_js( '
$.blockUI({
message: "' . esc_js( __( 'Thank you for your order. We are now redirecting you to PayPal to make payment.', 'woocommerce' ) ) . '",
baseZ: 99999,
overlayCSS:
{
background: "#fff",
opacity: 0.6
2012-05-26 16:25:07 +00:00
},
css: {
padding: "20px",
zindex: "9999999",
textAlign: "center",
color: "#555",
border: "3px solid #aaa",
backgroundColor:"#fff",
cursor: "wait",
lineHeight: "24px",
}
2012-05-26 16:25:07 +00:00
});
jQuery("#submit_paypal_payment_form").click();
2012-10-20 14:40:27 +00:00
' );
return '<form action="' . esc_url( $paypal_adr ) . '" method="post" id="paypal_payment_form" target="_top">
' . implode( '', $paypal_args_array ) . '
<!-- Button Fallback -->
<div class="payment_buttons">
<input type="submit" class="button alt" id="submit_paypal_payment_form" value="' . __( 'Pay via PayPal', 'woocommerce' ) . '" /> <a class="button cancel" href="' . esc_url( $order->get_cancel_order_url() ) . '">' . __( 'Cancel order &amp; restore cart', 'woocommerce' ) . '</a>
</div>
<script type="text/javascript">
jQuery(".payment_buttons").hide();
</script>
2012-05-26 16:25:07 +00:00
</form>';
}
/**
* Process the payment and return the result
*
* @access public
* @param int $order_id
* @return array
*/
public function process_payment( $order_id ) {
$order = wc_get_order( $order_id );
$paypal_args = $this->get_paypal_args( $order );
$paypal_args = http_build_query( $paypal_args, '', '&' );
if ( 'yes' == $this->testmode ) {
$paypal_adr = $this->testurl . '?test_ipn=1&';
} else {
$paypal_adr = $this->liveurl . '?';
}
return array(
'result' => 'success',
'redirect' => $paypal_adr . $paypal_args
);
}
/**
* Process a refund if supported
* @param int $order_id
* @param float $amount
* @param string $reason
* @return bool|wp_error True or false based on success, or a WP_Error object
*/
2014-08-11 13:07:09 +00:00
public function process_refund( $order_id, $amount = null, $reason = '' ) {
$order = wc_get_order( $order_id );
if ( ! $order || ! $order->get_transaction_id() || ! $this->api_username || ! $this->api_password || ! $this->api_signature ) {
return false;
}
$post_data = array(
'VERSION' => '84.0',
'SIGNATURE' => $this->api_signature,
'USER' => $this->api_username,
'PWD' => $this->api_password,
'METHOD' => 'RefundTransaction',
'TRANSACTIONID' => $order->get_transaction_id(),
'REFUNDTYPE' => is_null( $amount ) ? 'Full' : 'Partial'
);
if ( ! is_null( $amount ) ) {
$post_data['AMT'] = number_format( $amount, 2, '.', '' );
$post_data['CURRENCYCODE'] = $order->get_order_currency();
}
if ( $reason ) {
if ( 255 < strlen( $reason ) ) {
$reason = substr( $reason, 0, 252 ) . '...';
}
$post_data['NOTE'] = html_entity_decode( $reason, ENT_NOQUOTES, 'UTF-8' );
}
$response = wp_remote_post( 'yes' === $this->testmode ? 'https://api-3t.sandbox.paypal.com/nvp' : 'https://api-3t.paypal.com/nvp', array(
'method' => 'POST',
'body' => $post_data,
'timeout' => 70,
'sslverify' => false,
'user-agent' => 'WooCommerce',
'httpversion' => '1.1'
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
if ( empty( $response['body'] ) ) {
return new WP_Error( 'paypal-error', __( 'Empty Paypal response.', 'woocommerce' ) );
}
parse_str( $response['body'], $parsed_response );
switch ( strtolower( $parsed_response['ACK'] ) ) {
case 'success':
case 'successwithwarning':
$order->add_order_note( sprintf( __( 'Refunded %s - Refund ID: %s', 'woocommerce' ), $parsed_response['GROSSREFUNDAMT'], $parsed_response['REFUNDTRANSACTIONID'] ) );
return true;
break;
2012-05-26 16:25:07 +00:00
}
return false;
2012-05-26 16:25:07 +00:00
}
/**
* Output for the order received page.
*
* @access public
* @return void
*/
public function receipt_page( $order ) {
echo '<p>' . __( 'Thank you - your order is now pending payment. You should be automatically redirected to PayPal to make payment.', 'woocommerce' ) . '</p>';
2012-05-26 16:25:07 +00:00
echo $this->generate_paypal_form( $order );
}
2012-05-26 16:25:07 +00:00
/**
* Check PayPal IPN validity
**/
public function check_ipn_request_is_valid( $ipn_response ) {
2013-06-03 15:07:18 +00:00
// Get url
if ( 'yes' == $this->testmode ) {
2013-06-03 15:07:18 +00:00
$paypal_adr = $this->testurl;
} else {
2013-06-03 15:07:18 +00:00
$paypal_adr = $this->liveurl;
}
2013-06-03 15:07:18 +00:00
if ( 'yes' == $this->debug ) {
2013-06-03 15:07:18 +00:00
$this->log->add( 'paypal', 'Checking IPN response is valid via ' . $paypal_adr . '...' );
}
2014-04-15 15:56:35 +00:00
// Get received values from post data
2014-01-30 14:46:23 +00:00
$validate_ipn = array( 'cmd' => '_notify-validate' );
$validate_ipn += stripslashes_deep( $ipn_response );
2012-05-26 16:25:07 +00:00
// Send back post vars to paypal
$params = array(
2014-01-30 14:46:23 +00:00
'body' => $validate_ipn,
'sslverify' => false,
'timeout' => 60,
'httpversion' => '1.1',
2014-01-13 12:22:52 +00:00
'compress' => false,
'decompress' => false,
'user-agent' => 'WooCommerce/' . WC()->version
);
2012-05-26 16:25:07 +00:00
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'IPN Request: ' . print_r( $params, true ) );
}
2012-05-26 16:25:07 +00:00
// Post back to get a response
$response = wp_remote_post( $paypal_adr, $params );
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'IPN Response: ' . print_r( $response, true ) );
}
// check to see if the request was valid
2014-08-05 20:12:52 +00:00
if ( ! is_wp_error( $response ) && $response['response']['code'] >= 200 && $response['response']['code'] < 300 && strstr( $response['body'], 'VERIFIED' ) ) {
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'Received valid response from PayPal' );
}
2012-11-27 16:22:47 +00:00
return true;
}
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'Received invalid response from PayPal' );
if ( is_wp_error( $response ) ) {
$this->log->add( 'paypal', 'Error response: ' . $response->get_error_message() );
}
}
return false;
}
2012-05-26 16:25:07 +00:00
/**
* Check for PayPal IPN Response
2012-08-15 18:15:06 +00:00
*
* @access public
* @return void
*/
public function check_ipn_response() {
2012-09-12 12:36:34 +00:00
@ob_clean();
2014-01-30 14:46:23 +00:00
$ipn_response = ! empty( $_POST ) ? $_POST : false;
if ( $ipn_response && $this->check_ipn_request_is_valid( $ipn_response ) ) {
header( 'HTTP/1.1 200 OK' );
2014-01-30 14:46:23 +00:00
do_action( "valid-paypal-standard-ipn-request", $ipn_response );
2012-09-12 12:36:34 +00:00
} else {
2013-06-03 15:07:18 +00:00
wp_die( "PayPal IPN Request Failure", "PayPal IPN", array( 'response' => 200 ) );
}
2012-05-26 16:25:07 +00:00
}
2012-05-26 16:25:07 +00:00
/**
* Successful Payment!
2012-08-15 18:15:06 +00:00
*
* @access public
* @param array $posted
* @return void
*/
public function successful_request( $posted ) {
$posted = stripslashes_deep( $posted );
2012-05-26 16:25:07 +00:00
// Custom holds post ID
if ( ! empty( $posted['invoice'] ) && ! empty( $posted['custom'] ) ) {
2012-11-27 16:22:47 +00:00
$order = $this->get_paypal_order( $posted['custom'], $posted['invoice'] );
2012-11-27 16:22:47 +00:00
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'Found order #' . $order->id );
}
// Lowercase returned variables
$posted['payment_status'] = strtolower( $posted['payment_status'] );
$posted['txn_type'] = strtolower( $posted['txn_type'] );
2012-11-27 16:22:47 +00:00
// Sandbox fix
if ( 1 == $posted['test_ipn'] && 'pending' == $posted['payment_status'] ) {
$posted['payment_status'] = 'completed';
}
2013-03-27 22:39:55 +00:00
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'Payment status: ' . $posted['payment_status'] );
}
// We are here so lets check status and do actions
switch ( $posted['payment_status'] ) {
case 'completed' :
case 'pending' :
// Check order not already completed
2014-06-03 09:45:33 +00:00
if ( $order->has_status( 'completed' ) ) {
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'Aborting, Order #' . $order->id . ' is already complete.' );
}
exit;
}
// Check valid txn_type
$accepted_types = array( 'cart', 'instant', 'express_checkout', 'web_accept', 'masspay', 'send_money' );
2012-09-12 11:08:35 +00:00
if ( ! in_array( $posted['txn_type'], $accepted_types ) ) {
if ( 'yes' == $this->debug ) {
2012-09-12 11:08:35 +00:00
$this->log->add( 'paypal', 'Aborting, Invalid type:' . $posted['txn_type'] );
}
2012-05-26 16:25:07 +00:00
exit;
2012-09-12 11:08:35 +00:00
}
2012-11-27 16:22:47 +00:00
// Validate currency
if ( $order->get_order_currency() != $posted['mc_currency'] ) {
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'Payment error: Currencies do not match (sent "' . $order->get_order_currency() . '" | returned "' . $posted['mc_currency'] . '")' );
}
// Put this order on-hold for manual checking
$order->update_status( 'on-hold', sprintf( __( 'Validation error: PayPal currencies do not match (code %s).', 'woocommerce' ), $posted['mc_currency'] ) );
exit;
}
2012-11-27 16:22:47 +00:00
// Validate amount
if ( $order->get_total() != $posted['mc_gross'] ) {
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'Payment error: Amounts do not match (gross ' . $posted['mc_gross'] . ')' );
}
2012-11-27 16:22:47 +00:00
// Put this order on-hold for manual checking
$order->update_status( 'on-hold', sprintf( __( 'Validation error: PayPal amounts do not match (gross %s).', 'woocommerce' ), $posted['mc_gross'] ) );
exit;
}
// Validate Email Address
2013-03-11 10:36:51 +00:00
if ( strcasecmp( trim( $posted['receiver_email'] ), trim( $this->receiver_email ) ) != 0 ) {
if ( 'yes' == $this->debug ) {
2013-03-11 10:36:51 +00:00
$this->log->add( 'paypal', "IPN Response is for another one: {$posted['receiver_email']} our email is {$this->receiver_email}" );
}
// Put this order on-hold for manual checking
$order->update_status( 'on-hold', sprintf( __( 'Validation error: PayPal IPN response from a different email address (%s).', 'woocommerce' ), $posted['receiver_email'] ) );
exit;
}
2012-05-26 16:25:07 +00:00
// Store PP Details
if ( ! empty( $posted['payer_email'] ) ) {
2014-01-30 14:39:14 +00:00
update_post_meta( $order->id, 'Payer PayPal address', wc_clean( $posted['payer_email'] ) );
}
if ( ! empty( $posted['first_name'] ) ) {
2014-01-30 14:39:14 +00:00
update_post_meta( $order->id, 'Payer first name', wc_clean( $posted['first_name'] ) );
}
if ( ! empty( $posted['last_name'] ) ) {
2014-01-30 14:39:14 +00:00
update_post_meta( $order->id, 'Payer last name', wc_clean( $posted['last_name'] ) );
}
if ( ! empty( $posted['payment_type'] ) ) {
2014-01-30 14:39:14 +00:00
update_post_meta( $order->id, 'Payment type', wc_clean( $posted['payment_type'] ) );
}
if ( $posted['payment_status'] == 'completed' ) {
$order->add_order_note( __( 'IPN payment completed', 'woocommerce' ) );
$txn_id = ( ! empty( $posted['txn_id'] ) ) ? wc_clean( $posted['txn_id'] ) : '';
$order->payment_complete( $txn_id );
} else {
$order->update_status( 'on-hold', sprintf( __( 'Payment pending: %s', 'woocommerce' ), $posted['pending_reason'] ) );
}
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'Payment complete.' );
}
break;
case 'denied' :
case 'expired' :
case 'failed' :
case 'voided' :
// Order failed
$order->update_status( 'failed', sprintf( __( 'Payment %s via IPN.', 'woocommerce' ), strtolower( $posted['payment_status'] ) ) );
break;
case 'refunded' :
// Only handle full refunds, not partial
if ( $order->get_total() == ( $posted['mc_gross'] * -1 ) ) {
// Mark order as refunded
$order->update_status( 'refunded', sprintf( __( 'Payment %s via IPN.', 'woocommerce' ), strtolower( $posted['payment_status'] ) ) );
$this->send_ipn_email_notification(
sprintf( __( 'Payment for order %s refunded/reversed', 'woocommerce' ), $order->get_order_number() ),
sprintf( __( 'Order %s has been marked as refunded - PayPal reason code: %s', 'woocommerce' ), $order->get_order_number(), $posted['reason_code'] )
2012-05-26 16:25:07 +00:00
);
}
break;
case 'reversed' :
// Mark order as refunded
$order->update_status( 'on-hold', sprintf( __( 'Payment %s via IPN.', 'woocommerce' ), strtolower( $posted['payment_status'] ) ) );
$this->send_ipn_email_notification(
sprintf( __( 'Payment for order %s reversed', 'woocommerce' ), $order->get_order_number() ),
sprintf(__( 'Order %s has been marked on-hold due to a reversal - PayPal reason code: %s', 'woocommerce' ), $order->get_order_number(), $posted['reason_code'] )
2012-05-26 16:25:07 +00:00
);
break;
case 'canceled_reversal' :
$this->send_ipn_email_notification(
sprintf( __( 'Reversal cancelled for order %s', 'woocommerce' ), $order->get_order_number() ),
sprintf( __( 'Order %s has had a reversal cancelled. Please check the status of payment and update the order status accordingly.', 'woocommerce' ), $order->get_order_number() )
2013-08-15 10:19:26 +00:00
);
break;
default :
// No action
break;
}
2012-05-26 16:25:07 +00:00
exit;
}
}
/**
* Send a notification to the user handling orders.
* @param string $subject
* @param string $message
*/
public function send_ipn_email_notification( $subject, $message ) {
$new_order_settings = get_option( 'woocommerce_new_order_settings', array() );
$mailer = WC()->mailer();
$message = $mailer->wrap_message( $subject, $message );
$mailer->send( ! empty( $new_order_settings['recipient'] ) ? $new_order_settings['recipient'] : get_option( 'admin_email' ), $subject, $message );
2012-05-26 16:25:07 +00:00
}
2012-11-27 16:22:47 +00:00
/**
* Return handler
*
* Alternative to IPN
*/
public function pdt_return_handler() {
2013-08-19 11:28:29 +00:00
$posted = stripslashes_deep( $_REQUEST );
if ( ! empty( $this->identity_token ) && ! empty( $posted['cm'] ) ) {
2013-08-19 11:28:29 +00:00
$order = $this->get_paypal_order( $posted['cm'] );
2013-08-19 11:28:29 +00:00
2014-06-03 09:45:33 +00:00
if ( ! $order->has_status( 'pending' ) ) {
2013-12-03 14:03:25 +00:00
return false;
}
2013-08-19 11:28:29 +00:00
$posted['st'] = strtolower( $posted['st'] );
2013-08-19 11:28:29 +00:00
switch ( $posted['st'] ) {
case 'completed' :
2013-08-19 11:28:29 +00:00
// Validate transaction
if ( 'yes' == $this->testmode ) {
2013-08-19 11:28:29 +00:00
$paypal_adr = $this->testurl;
} else {
2013-08-19 11:28:29 +00:00
$paypal_adr = $this->liveurl;
}
2013-08-19 11:28:29 +00:00
$pdt = array(
'body' => array(
'cmd' => '_notify-synch',
'tx' => $posted['tx'],
'at' => $this->identity_token
),
'sslverify' => false,
'timeout' => 60,
'httpversion' => '1.1',
'user-agent' => 'WooCommerce/' . WC_VERSION
);
2013-08-19 11:28:29 +00:00
// Post back to get a response
$response = wp_remote_post( $paypal_adr, $pdt );
2013-08-19 11:28:29 +00:00
if ( is_wp_error( $response ) ) {
return false;
}
2013-08-19 11:28:29 +00:00
if ( ! strpos( $response['body'], "SUCCESS" ) === 0 ) {
return false;
}
2013-08-19 11:28:29 +00:00
// Validate Amount
if ( $order->get_total() != $posted['amt'] ) {
2013-08-19 11:28:29 +00:00
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'Payment error: Amounts do not match (amt ' . $posted['amt'] . ')' );
}
2013-08-19 11:28:29 +00:00
// Put this order on-hold for manual checking
$order->update_status( 'on-hold', sprintf( __( 'Validation error: PayPal amounts do not match (amt %s).', 'woocommerce' ), $posted['amt'] ) );
return true;
2013-08-19 11:28:29 +00:00
} else {
2013-08-19 11:28:29 +00:00
// Store PP Details
$order->add_order_note( __( 'PDT payment completed', 'woocommerce' ) );
$txn_id = ( ! empty( $posted['tx'] ) ) ? wc_clean( $posted['tx'] ) : '';
$order->payment_complete( $txn_id );
return true;
}
2013-08-19 11:28:29 +00:00
break;
}
}
2013-12-03 14:03:25 +00:00
return false;
2013-12-02 13:42:39 +00:00
}
2012-09-12 12:05:53 +00:00
2012-09-12 11:08:35 +00:00
/**
* get_paypal_order function.
2012-11-27 16:22:47 +00:00
*
2013-08-19 11:28:29 +00:00
* @param string $custom
* @param string $invoice
* @return WC_Order object
2012-09-12 11:08:35 +00:00
*/
2013-08-19 11:28:29 +00:00
private function get_paypal_order( $custom, $invoice = '' ) {
$custom = maybe_unserialize( $custom );
// Backwards comp for IPN requests
if ( is_numeric( $custom ) ) {
$order_id = (int) $custom;
$order_key = $invoice;
} elseif( is_string( $custom ) ) {
$order_id = (int) str_replace( $this->invoice_prefix, '', $custom );
$order_key = $custom;
} else {
list( $order_id, $order_key ) = $custom;
}
2012-09-12 11:08:35 +00:00
$order = wc_get_order( $order_id );
2012-09-12 11:08:35 +00:00
2012-11-27 16:22:47 +00:00
if ( ! isset( $order->id ) ) {
2012-09-12 11:08:35 +00:00
// We have an invalid $order_id, probably because invoice_prefix has changed
$order_id = wc_get_order_id_by_order_key( $order_key );
$order = wc_get_order( $order_id );
2012-09-12 11:08:35 +00:00
}
2012-11-27 16:22:47 +00:00
2012-09-12 11:08:35 +00:00
// Validate key
if ( $order->order_key !== $order_key ) {
if ( 'yes' == $this->debug ) {
$this->log->add( 'paypal', 'Error: Order Key does not match invoice.' );
}
exit;
}
2012-11-27 16:22:47 +00:00
return $order;
2012-09-12 11:08:35 +00:00
}
/**
* Get the state to send to paypal
* @param string $cc
* @param string $state
* @return string
*/
public function get_paypal_state( $cc, $state ) {
if ( 'US' === $cc ) {
return $state;
}
$states = WC()->countries->get_states( $cc );
if ( isset( $states[ $state ] ) ) {
return $states[ $state ];
}
return $state;
}
/**
* Get the transaction URL.
*
* @param WC_Order $order
*
* @return string
*/
public function get_transaction_url( $order ) {
if ( 'yes' == $this->testmode ) {
$this->view_transaction_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s';
} else {
$this->view_transaction_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s';
}
return parent::get_transaction_url( $order );
}
}