/**
  * Check whether a given subscription is using reference transactions and if so process the payment.
  *
  * @since 2.0
  */
 public static function process_subscription_payment($amount, $order)
 {
     // If the subscription is using reference transactions, we can process the payment ourselves
     $paypal_profile_id = wcs_get_paypal_id($order->id);
     if (wcs_is_paypal_profile_a($paypal_profile_id, 'billing_agreement')) {
         if (0 == $amount) {
             $order->payment_complete();
             return;
         }
         $response = self::get_api()->do_reference_transaction($paypal_profile_id, $order, array('amount' => $amount, 'invoice_number' => self::get_option('invoice_prefix') . wcs_str_to_ascii(ltrim($order->get_order_number(), _x('#', 'hash before the order number. Used as a character to remove from the actual order number', 'woocommerce-subscriptions')))));
         self::process_subscription_payment_response($order, $response);
     }
 }
 /**
  * Charge a payment against a reference token
  *
  * @link https://developer.paypal.com/docs/classic/express-checkout/integration-guide/ECReferenceTxns/#id094UM0DA0HS
  * @link https://developer.paypal.com/docs/classic/api/merchant/DoReferenceTransaction_API_Operation_NVP/
  *
  * @param string $reference_id the ID of a refrence object, e.g. billing agreement ID.
  * @param WC_Order $order order object
  * @param array $args {
  *     @type string 'payment_type'         (Optional) Specifies type of PayPal payment you require for the billing agreement. It is one of the following values. 'Any' or 'InstantOnly'. Echeck is not supported for DoReferenceTransaction requests.
  *     @type string 'payment_action'       How you want to obtain payment. It is one of the following values: 'Authorization' - this payment is a basic authorization subject to settlement with PayPal Authorization and Capture; or 'Sale' - This is a final sale for which you are requesting payment.
  *     @type string 'return_fraud_filters' (Optional) Flag to indicate whether you want the results returned by Fraud Management Filters. By default, you do not receive this information.
  * }
  * @since 2.0
  */
 public function do_reference_transaction($reference_id, $order, $args = array())
 {
     $defaults = array('amount' => $order->get_total(), 'payment_type' => 'Any', 'payment_action' => 'Sale', 'return_fraud_filters' => 1, 'notify_url' => WC()->api_request_url('WC_Gateway_Paypal'), 'invoice_number' => wcs_str_to_ascii(ltrim($order->get_order_number(), _x('#', 'hash before the order number', 'woocommerce-subscriptions'))), 'custom' => json_encode(array('order_id' => $order->id, 'order_key' => $order->order_key)));
     $args = wp_parse_args($args, $defaults);
     $this->set_method('DoReferenceTransaction');
     // set base params
     $this->add_parameters(array('REFERENCEID' => $reference_id, 'BUTTONSOURCE' => 'WooThemes_Cart', 'RETURNFMFDETAILS' => $args['return_fraud_filters'], 'AMT' => $order->get_total(), 'CURRENCYCODE' => $order->get_order_currency(), 'INVNUM' => $args['invoice_number'], 'PAYMENTACTION' => $args['payment_action'], 'NOTIFYURL' => $args['notify_url'], 'CUSTOM' => $args['custom']));
     $order_subtotal = $i = 0;
     $order_items = array();
     // add line items
     foreach ($order->get_items() as $item) {
         $order_items[] = array('NAME' => wcs_get_paypal_item_name($item['name']), 'AMT' => $order->get_item_subtotal($item), 'QTY' => !empty($item['qty']) ? absint($item['qty']) : 1);
         $order_subtotal += $item['line_total'];
     }
     // add fees
     foreach ($order->get_fees() as $fee) {
         $order_items[] = array('NAME' => wcs_get_paypal_item_name($fee['name']), 'AMT' => $fee['line_total'], 'QTY' => 1);
         $order_subtotal += $fee['line_total'];
     }
     // WC 2.3+, no after-tax discounts
     if ($order->get_total_discount() > 0) {
         $order_items[] = array('NAME' => __('Total Discount', 'woocommerce-subscriptions'), 'QTY' => 1, 'AMT' => -$order->get_total_discount());
     }
     if ($order->prices_include_tax) {
         $item_names = array();
         foreach ($order_items as $item) {
             $item_names[] = sprintf('%s x %s', $item['NAME'], $item['QTY']);
         }
         // add a single item for the entire order
         $this->add_line_item_parameters(array('NAME' => sprintf(__('%s - Order', 'woocommerce-subscriptions'), get_option('blogname')), 'DESC' => wcs_get_paypal_item_name(implode(', ', $item_names)), 'AMT' => $order_subtotal + $order->get_cart_tax(), 'QTY' => 1), 0);
         // add order-level parameters - do not send the TAXAMT due to rounding errors
         $this->add_payment_parameters(array('ITEMAMT' => $order_subtotal + $order->get_cart_tax(), 'SHIPPINGAMT' => $order->get_total_shipping() + $order->get_shipping_tax()));
     } else {
         // add individual order items
         foreach ($order_items as $item) {
             $this->add_line_item_parameters($item, $i++);
         }
         // add order-level parameters
         $this->add_payment_parameters(array('ITEMAMT' => $order_subtotal, 'SHIPPINGAMT' => $order->get_total_shipping(), 'TAXAMT' => $order->get_total_tax()));
     }
 }
 /**
  * Set up the payment details for a DoExpressCheckoutPayment or DoReferenceTransaction request
  *
  * @since 2.0.9
  * @param WC_Order $order order object
  * @param string $type the type of transaction for the payment
  * @param bool $use_deprecated_params whether to use deprecated PayPal NVP parameters (required for DoReferenceTransaction API calls)
  */
 protected function add_payment_details_parameters(WC_Order $order, $type, $use_deprecated_params = false)
 {
     $calculated_total = 0;
     $order_subtotal = 0;
     $item_count = 0;
     $order_items = array();
     // add line items
     foreach ($order->get_items() as $item) {
         $product = new WC_Product($item['product_id']);
         $order_items[] = array('NAME' => wcs_get_paypal_item_name($product->get_title()), 'DESC' => $this->get_item_description($item, $product), 'AMT' => $this->round($order->get_item_subtotal($item)), 'QTY' => !empty($item['qty']) ? absint($item['qty']) : 1, 'ITEMURL' => $product->get_permalink());
         $order_subtotal += $item['line_total'];
     }
     // add fees
     foreach ($order->get_fees() as $fee) {
         $order_items[] = array('NAME' => wcs_get_paypal_item_name($fee['name']), 'AMT' => $this->round($fee['line_total']), 'QTY' => 1);
         $order_subtotal += $fee['line_total'];
     }
     // add discounts
     if ($order->get_total_discount() > 0) {
         $order_items[] = array('NAME' => __('Total Discount', 'woocommerce-subscriptions'), 'QTY' => 1, 'AMT' => -$this->round($order->get_total_discount()));
     }
     if ($this->skip_line_items($order)) {
         $total_amount = $this->round($order->get_total());
         // calculate the total as PayPal would
         $calculated_total += $this->round($order_subtotal + $order->get_cart_tax()) + $this->round($order->get_total_shipping() + $order->get_shipping_tax());
         // offset the discrepency between the WooCommerce cart total and PayPal's calculated total by adjusting the order subtotal
         if ($total_amount !== $calculated_total) {
             $order_subtotal = $order_subtotal - ($calculated_total - $total_amount);
         }
         $item_names = array();
         foreach ($order_items as $item) {
             $item_names[] = sprintf('%1$s x %2$s', $item['NAME'], $item['QTY']);
         }
         // add a single item for the entire order
         $this->add_line_item_parameters(array('NAME' => sprintf(__('%s - Order', 'woocommerce-subscriptions'), get_option('blogname')), 'DESC' => wcs_get_paypal_item_name(implode(', ', $item_names)), 'AMT' => $this->round($order_subtotal + $order->get_cart_tax()), 'QTY' => 1), 0, $use_deprecated_params);
         // add order-level parameters
         //  - Do not sent the TAXAMT due to rounding errors
         if ($use_deprecated_params) {
             $this->add_parameters(array('AMT' => $total_amount, 'CURRENCYCODE' => $order->get_order_currency(), 'ITEMAMT' => $this->round($order_subtotal + $order->get_cart_tax()), 'SHIPPINGAMT' => $this->round($order->get_total_shipping() + $order->get_shipping_tax()), 'INVNUM' => WCS_PayPal::get_option('invoice_prefix') . wcs_str_to_ascii(ltrim($order->get_order_number(), _x('#', 'hash before the order number. Used as a character to remove from the actual order number', 'woocommerce-subscriptions'))), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->id, 'CUSTOM' => json_encode(array('order_id' => $order->id, 'order_key' => $order->order_key))));
         } else {
             $this->add_payment_parameters(array('AMT' => $total_amount, 'CURRENCYCODE' => $order->get_order_currency(), 'ITEMAMT' => $this->round($order_subtotal + $order->get_cart_tax()), 'SHIPPINGAMT' => $this->round($order->get_total_shipping() + $order->get_shipping_tax()), 'INVNUM' => WCS_PayPal::get_option('invoice_prefix') . wcs_str_to_ascii(ltrim($order->get_order_number(), _x('#', 'hash before the order number. Used as a character to remove from the actual order number', 'woocommerce-subscriptions'))), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->id, 'CUSTOM' => json_encode(array('order_id' => $order->id, 'order_key' => $order->order_key))));
         }
     } else {
         // add individual order items
         foreach ($order_items as $item) {
             $this->add_line_item_parameters($item, $item_count++, $use_deprecated_params);
             $calculated_total += $this->round($item['AMT'] * $item['QTY']);
         }
         // add shipping and tax to calculated total
         $calculated_total += $this->round($order->get_total_shipping()) + $this->round($order->get_total_tax());
         $total_amount = $this->round($order->get_total());
         // add order-level parameters
         if ($use_deprecated_params) {
             $this->add_parameters(array('AMT' => $total_amount, 'CURRENCYCODE' => $order->get_order_currency(), 'ITEMAMT' => $this->round($order_subtotal), 'SHIPPINGAMT' => $this->round($order->get_total_shipping()), 'TAXAMT' => $this->round($order->get_total_tax()), 'INVNUM' => WCS_PayPal::get_option('invoice_prefix') . wcs_str_to_ascii(ltrim($order->get_order_number(), _x('#', 'hash before the order number. Used as a character to remove from the actual order number', 'woocommerce-subscriptions'))), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->id, 'CUSTOM' => json_encode(array('order_id' => $order->id, 'order_key' => $order->order_key))));
         } else {
             $this->add_payment_parameters(array('AMT' => $total_amount, 'CURRENCYCODE' => $order->get_order_currency(), 'ITEMAMT' => $this->round($order_subtotal), 'SHIPPINGAMT' => $this->round($order->get_total_shipping()), 'TAXAMT' => $this->round($order->get_total_tax()), 'INVNUM' => WCS_PayPal::get_option('invoice_prefix') . wcs_str_to_ascii(ltrim($order->get_order_number(), _x('#', 'hash before the order number. Used as a character to remove from the actual order number', 'woocommerce-subscriptions'))), 'PAYMENTACTION' => $type, 'PAYMENTREQUESTID' => $order->id, 'CUSTOM' => json_encode(array('order_id' => $order->id, 'order_key' => $order->order_key))));
         }
         // offset the discrepency between the WooCommerce cart total and PayPal's calculated total by adjusting the cost of the first item
         if ($total_amount !== $calculated_total) {
             $this->parameters['L_PAYMENTREQUEST_0_AMT0'] = $this->parameters['L_PAYMENTREQUEST_0_AMT0'] - ($calculated_total - $total_amount);
         }
     }
 }
Example #4
0
 /**
  * Check whether a given subscription is using reference transactions and if so process the payment.
  *
  * @since 2.0
  */
 public static function process_subscription_payment($amount, $order)
 {
     // If the subscription is using reference transactions, we can process the payment ourselves
     $paypal_profile_id = wcs_get_paypal_id($order->id);
     if (wcs_is_paypal_profile_a($paypal_profile_id, 'billing_agreement')) {
         if (0 == $amount) {
             $order->payment_complete();
             return;
         }
         $response = self::get_api()->do_reference_transaction($paypal_profile_id, $order, array('amount' => $amount, 'invoice_number' => self::get_option('invoice_prefix') . wcs_str_to_ascii(ltrim($order->get_order_number(), _x('#', 'hash before the order number', 'woocommerce-subscriptions')))));
         if ($response->has_api_error()) {
             $error_message = $response->get_api_error_message();
             // Some PayPal error messages end with a fullstop, others do not, we prefer our punctuation consistent, so add one if we don't already have one.
             if ('.' !== substr($error_message, -1)) {
                 $error_message .= '.';
             }
             // translators: placeholders are PayPal API error code and PayPal API error message
             $order->update_status('failed', sprintf(__('PayPal API error: (%d) %s', 'woocommerce-subscriptions'), $response->get_api_error_code(), $error_message));
         } elseif ($response->transaction_held()) {
             // translators: placeholder is PayPal transaction status message
             $order_note = sprintf(__('PayPal Transaction Held: %s', 'woocommerce-subscriptions'), $response->get_status_message());
             $order_status = apply_filters('wcs_paypal_held_payment_order_status', 'on-hold', $order, $response);
             // mark order as held
             if (!$order->has_status($order_status)) {
                 $order->update_status($order_status, $order_note);
             } else {
                 $order->add_order_note($order_note);
             }
         } elseif (!$response->transaction_approved()) {
             // translators: placeholder is PayPal transaction status message
             $order->update_status('failed', sprintf(__('PayPal payment declined: %s', 'woocommerce-subscriptions'), $response->get_status_message()));
         } elseif ($response->transaction_approved()) {
             $order->add_order_note(sprintf(__('PayPal payment approved (ID: %s)', 'woocommerce-subscriptions'), $response->get_transaction_id()));
             $order->payment_complete($response->get_transaction_id());
         }
     }
 }