Last active
February 17, 2020 14:01
-
-
Save dfinnema/3895a327f92a5757070554d6b4a8b356 to your computer and use it in GitHub Desktop.
WooCommerce Xero (v1.7.3) + Stripe Fees (chart of account account manually set to 766)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
if ( ! defined( 'ABSPATH' ) ) { | |
exit; | |
} // Exit if accessed directly | |
class WC_XR_Line_Item_Manager { | |
/** | |
* @var WC_XR_Settings | |
*/ | |
private $settings; | |
private $eu_countries_code = array('BE', 'BG', 'CZ', 'DK', 'DE', 'EE', 'IE', 'EL', 'ES', 'FR', 'HR', 'IT', 'CY', 'LV', | |
'LT', 'LU', 'HU', 'MT', 'NL', 'AT', 'PL', 'PT', 'RO', 'SI', 'SK', 'FI', 'SE'); | |
private $eu_countries = array('Austria', 'Belgium', 'Bulgaria', 'Croatia', 'Cyprus', 'Czech Republic', 'Denmark', | |
'Estonia', 'Finland', 'France', 'Germany', 'Greece', 'Hungary', 'Ireland', 'Italy', | |
'Latvia', 'Lithuania', 'Luxembourg', 'Malta', 'Netherlands', 'Poland', 'Portugal', | |
'Romania', 'Slovakia', 'Slovenia', 'Spain', 'Sweden'); | |
/** | |
* WC_XR_Line_Item_Manager constructor. | |
* | |
* @param WC_XR_Settings $settings | |
*/ | |
public function __construct( WC_XR_Settings $settings ) { | |
$this->settings = $settings; | |
} | |
/** | |
* Build product line items | |
* | |
* @param WC_Order $order | |
* | |
* @return array<WC_XR_Line_Item> | |
*/ | |
public function build_products( $order ) { | |
$items = $order->get_items(); | |
$this->order = $order; | |
// The line items | |
$line_items = array(); | |
// Check if there are any order items | |
if ( count( $items ) > 0 ) { | |
// Get the sales account | |
if (in_array($order->billing_country, $this->eu_countries_code) || | |
in_array($order->billing_country, $this->eu_countries)) { | |
$sales_account = $this->settings->get_option( 'sales_account_EU' ); | |
} elseif (($order->billing_country === 'UK') || | |
($order->billing_country === 'GB') || | |
($order->billing_country === 'United Kingdom') || | |
($order->billing_country === 'United Kingdom (UK)')) { | |
$sales_account = $this->settings->get_option( 'sales_account_UK' ); | |
} else { | |
$sales_account = $this->settings->get_option( 'sales_account_world' ); | |
} | |
// Check we need to send sku's | |
$send_inventory = ( ( 'on' === $this->settings->get_option( 'send_inventory' ) ) ? true : false ); | |
// Add order items as line items | |
foreach ( $items as $item ) { | |
// Get the product | |
$product = $order->get_product_from_item( $item ); | |
// Create Line Item object | |
$line_item = new WC_XR_Line_Item( $this->settings ); | |
// Set description | |
$line_item->set_description( str_replace( array( '“', '”' ), '""', $item['name'] ) ); | |
// Set account code | |
$line_item->set_account_code( $sales_account ); | |
// Send SKU? | |
if ( $send_inventory ) { | |
$line_item->set_item_code( $product->sku ); | |
} | |
// if ( true === $send_inventory && $product->is_on_sale() ) {} // Set the unit price if we send inventory and the product is on sale | |
// Set the Unit Amount with 4DP | |
$line_item->set_unit_amount( ( floatval( $item['line_subtotal'] ) / intval( $item['qty'] ) ) ); | |
// Quantity | |
$line_item->set_quantity( $item['qty'] ); | |
// Line Amount | |
$line_item->set_line_amount( $item['line_subtotal'] ); | |
// Tax Amount | |
$line_item->set_tax_amount( $item['line_tax'] ); | |
// Tax Rate | |
$item_tax_status = $product ? $product->get_tax_status() : 'taxable'; | |
if ( 'taxable' === $item_tax_status ) { | |
add_filter( 'woocommerce_get_tax_location', array( $this, 'set_tax_location' ), 10, 2 ); | |
$rates = WC_Tax::get_rates( $product->get_tax_class() ); | |
remove_filter( 'woocommerce_get_tax_location', array( $this, 'set_tax_location' ) ); | |
reset( $rates ); | |
$line_item->set_tax_rate( $rates[ key( $rates ) ] ); | |
} | |
// Add Line Item to array | |
$line_items[] = $line_item; | |
} | |
} | |
return $line_items; | |
} | |
/** | |
* Sets the tax location (without needing a session) so we can calculate | |
* the correct rates for our items. | |
*/ | |
public function set_tax_location( $location, $tax_class ) { | |
if ( sizeof( $location ) === 4 ) { | |
return $location; | |
} | |
$shipping_methods = array(); | |
foreach ( $this->order->get_shipping_methods() as $method ) { | |
$shipping_methods[] = $method['method_id']; | |
} | |
$tax_based_on = get_option( 'woocommerce_tax_based_on' ); | |
if ( true == apply_filters( 'woocommerce_apply_base_tax_for_local_pickup', true ) && sizeof( array_intersect( $shipping_methods, apply_filters( 'woocommerce_local_pickup_methods', array( 'local_pickup' ) ) ) ) > 0 ) { | |
$tax_based_on = 'base'; | |
} | |
if ( 'base' === $tax_based_on ) { | |
$country = WC()->countries->get_base_country(); | |
$state = WC()->countries->get_base_state(); | |
$postcode = WC()->countries->get_base_postcode(); | |
$city = WC()->countries->get_base_city(); | |
} elseif ( 'billing' === $tax_based_on ) { | |
$country = $this->order->billing_country; | |
$state = $this->order->billing_state; | |
$postcode = $this->order->billing_postcode; | |
$city = $this->order->billing_city; | |
} else { | |
$country = $this->order->shipping_country; | |
$state = $this->order->shipping_state; | |
$postcode = $this->order->shipping_postcode; | |
$city = $this->order->shipping_city; | |
} | |
return array( $country, $state, $postcode, $city ); | |
} | |
/** | |
* Build shipping line item | |
* | |
* @param WC_Order $order | |
* | |
* @return WC_XR_Line_Item | |
*/ | |
public function build_shipping( $order ) { | |
if ( $order->order_shipping > 0 ) { | |
// Create Line Item object | |
$line_item = new WC_XR_Line_Item( $this->settings ); | |
// Shipping Description | |
$line_item->set_description( 'Shipping: ' . $order->get_shipping_method() . ' ' . | |
'SHIPPING TO:' . ' ' . | |
$order->shipping_first_name . ' ' . $order->shipping_last_name . ' ' . | |
$order->shipping_address_1 . ' ' . | |
$order->shipping_address_2 . ' ' . | |
$order->shipping_postcode . ' ' . $order->shipping_city . ' ' . | |
$order->shipping_state . ' ' . | |
$order->shipping_country); | |
// Shipping Quantity | |
$line_item->set_quantity( 1 ); | |
// Shipping account code | |
$line_item->set_account_code( $this->settings->get_option( 'shipping_account' ) ); | |
// Shipping cost | |
$line_item->set_unit_amount( $order->order_shipping ); | |
// Shipping tax | |
$line_item->set_tax_amount( $order->order_shipping_tax ); | |
$line_item->set_tax_rate( array( | |
'rate' => ( ( $order->order_shipping_tax / $order->order_shipping ) * 100 ), | |
'label' => 'Shipping', | |
'shipping' => true, | |
'compound' => false, | |
) ); | |
return $line_item; | |
} | |
} | |
/** | |
* Build discount line item | |
* | |
* @param WC_Order $order | |
* | |
* @return WC_XR_Line_Item | |
*/ | |
public function build_discount( $order ) { | |
if ( $order->get_total_discount() > 0 ) { | |
// Create Line Item object | |
$line_item = new WC_XR_Line_Item( $this->settings ); | |
// Shipping Description | |
$line_item->set_description( 'Order Discount' ); | |
// Shipping Quantity | |
$line_item->set_quantity( 1 ); | |
// Shipping account code | |
$line_item->set_account_code( $this->settings->get_option( 'discount_account' ) ); | |
// Shipping cost | |
$line_item->set_unit_amount( - $order->get_total_discount() ); | |
return $line_item; | |
} | |
} | |
/** | |
* Add Stripe Fees | |
* | |
* @param WC_Order $order | |
* | |
* @return WC_XR_Line_Item | |
*/ | |
public function build_stripefee( $order ) { | |
if ( $order->get_total() > 0 ) { | |
// Create Line Item object | |
$line_item = new WC_XR_Line_Item( $this->settings ); | |
// Fee Description | |
$line_item->set_description( 'Stripe Fee' ); | |
// Fee Quantity | |
$line_item->set_quantity( 1 ); | |
// Fee account code | |
$line_item->set_account_code( 405 ); | |
// Fee Cost | |
$fee = get_post_meta($order->id, 'Stripe Fee', 1); | |
if (!empty ($fee)) { | |
$line_item->set_unit_amount( - $fee ); | |
} else { | |
$line_item->set_unit_amount( 0 ); | |
} | |
// Tax Amount | |
//$line_item->set_tax_amount( 0 ); | |
//$line_item->set_tax_rate( '0.15' ); | |
return $line_item; | |
} | |
} | |
/** | |
* Build a correction line if needed | |
* | |
* @param WC_Order $order | |
* @param WC_XR_Line_Item[] $line_items | |
* | |
* @return WC_XR_Line_Item | |
*/ | |
public function build_correction( $order, $line_items ) { | |
// Line Item | |
$correction_line = null; | |
// The line item total in cents | |
$line_total = 0; | |
// Get a sum of the amount and tax of all line items | |
if ( count( $line_items ) > 0 ) { | |
foreach ( $line_items as $line_item ) { | |
$val = round( $line_item->get_unit_amount(), 2 ) * $line_item->get_quantity(); | |
$line_total += round( $val, 2 ) + round( $line_item->get_tax_amount(), 2 ); | |
} | |
} | |
// Order total in cents | |
$order_total = round( $order->get_total(), 2 ); | |
// Check if there's a difference | |
if ( $order_total !== $line_total ) { | |
// Calculate difference | |
$diff = $order_total - $line_total; | |
// Get rounding account code | |
$account_code = $this->settings->get_option( 'rounding_account' ); | |
// Check rounding account code | |
if ( '' !== $account_code ) { | |
// Create correction line item | |
$correction_line = new WC_XR_Line_Item( $this->settings ); | |
// Correction description | |
$correction_line->set_description( 'Rounding adjustment' ); | |
// Correction quantity | |
$correction_line->set_quantity( 1 ); | |
// Correction amount | |
$correction_line->set_unit_amount( $diff ); | |
$correction_line->set_account_code( $account_code ); | |
} else { | |
// There's a rounding difference but no rounding account | |
$logger = new WC_XR_Logger( $this->settings ); | |
$logger->write( "There's a rounding difference but no rounding account set in XERO settings." ); | |
} | |
} | |
return $correction_line; | |
} | |
/** | |
* Build line items | |
* | |
* @param WC_Order $order | |
* | |
* @return array<WC_XR_Line_Item> | |
*/ | |
public function build_line_items( $order ) { | |
// Fill line items array with products | |
$line_items = $this->build_products( $order ); | |
// Add shipping line item if there's shipping | |
if ( $order->order_shipping > 0 ) { | |
$line_items[] = $this->build_shipping( $order ); | |
} | |
// Add discount line item if there's discount | |
if ( $order->get_total_discount() > 0 ) { | |
$line_items[] = $this->build_discount( $order ); | |
} | |
// Build correction | |
$correction = $this->build_correction( $order, $line_items ); | |
if ( null !== $correction ) { | |
$line_items[] = $correction; | |
} | |
// Add stripe fees line item if there's stripe fees | |
$check_stripe_fee = get_post_meta( $order->id , 'Stripe Fee', true ); | |
if ( !empty($check_stripe_fee) ) { | |
$line_items[] = $this->build_stripefee( $order ); | |
} | |
// Return line items | |
return $line_items; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
if ( ! defined( 'ABSPATH' ) ) { | |
exit; | |
} // Exit if accessed directly | |
class WC_XR_Payment_Manager { | |
/** | |
* @var WC_XR_Settings | |
*/ | |
private $settings; | |
/** | |
* WC_XR_Payment_Manager constructor. | |
* | |
* @param WC_XR_Settings $settings | |
*/ | |
public function __construct( WC_XR_Settings $settings ){ | |
$this->settings = $settings; | |
} | |
public function setup_hooks() { | |
// Check if we need to send payments when they're completed automatically | |
if ( 'on' === $this->settings->get_option( 'send_payments' ) ) { | |
add_action( 'woocommerce_order_status_completed', array( $this, 'send_payment' ) ); | |
} | |
add_filter( 'woocommerce_xero_order_payment_date', array( $this, 'cod_payment_set_payment_date_as_current_date' ), 10, 2 ); | |
} | |
/** | |
* Send the payment to the XERO API | |
* | |
* @param int $order_id | |
* | |
* @return bool | |
*/ | |
public function send_payment( $order_id ) { | |
// Get the order | |
$order = wc_get_order( $order_id ); | |
if ( ! get_post_meta( $order->id, '_xero_invoice_id', true ) ) { | |
$order->add_order_note( __( 'Xero Payment not created: Invoice has not been sent.', 'wc-xero' ) ); | |
return false; | |
} | |
// Payment Request | |
$payment_request = new WC_XR_Request_Payment( $this->settings, $this->get_payment_by_order( $order ) ); | |
// Write exception message to log | |
$logger = new WC_XR_Logger( $this->settings ); | |
// Logging start | |
$logger->write( 'START XERO NEW PAYMENT. order_id=' . $order->id ); | |
// Try to do the request | |
try { | |
// Do the request | |
$payment_request->do_request(); | |
// Parse XML Response | |
$xml_response = $payment_request->get_response_body_xml(); | |
// Check response status | |
if ( 'OK' == $xml_response->Status ) { | |
// Add post meta | |
update_post_meta( $order->id, '_xero_payment_id', (string) $xml_response->Payments->Payment[0]->PaymentID ); | |
// Write logger | |
$logger->write( 'XERO RESPONSE:' . "\n" . $payment_request->get_response_body() ); | |
// Add order note | |
$order->add_order_note( __( 'Xero Payment created. ', 'wc-xero' ) . | |
' Payment ID: ' . (string) $xml_response->Payments->Payment[0]->PaymentID ); | |
} else { // XML reponse is not OK | |
// Logger write | |
$logger->write( 'XERO ERROR RESPONSE:' . "\n" . $payment_request->get_response_body() ); | |
// Error order note | |
$error_num = (string) $xml_response->ErrorNumber; | |
$error_msg = (string) $xml_response->Elements->DataContractBase->ValidationErrors->ValidationError->Message; | |
$order->add_order_note( __( 'ERROR creating Xero payment. ErrorNumber:' . $error_num . '| Error Message:' . $error_msg, 'wc-xero' ) ); | |
} | |
} catch ( Exception $e ) { | |
// Add Exception as order note | |
$order->add_order_note( $e->getMessage() ); | |
$logger->write( $e->getMessage() ); | |
return false; | |
} | |
// Logging end | |
$logger->write( 'END XERO NEW PAYMENT' ); | |
return true; | |
} | |
/** | |
* Get payment by order | |
* | |
* @param WC_Order $order | |
* | |
* @return WC_XR_Payment | |
*/ | |
public function get_payment_by_order( $order ) { | |
// Get the XERO invoice ID | |
$invoice_id = get_post_meta( $order->id, '_xero_invoice_id', true ); | |
// Get the XERO currency rate | |
$currency_rate = get_post_meta( $order->id, '_xero_currencyrate', true ); | |
// Date time object of order data | |
$order_dt = new DateTime( $order->order_date ); | |
// The Payment object | |
$payment = new WC_XR_Payment(); | |
$payment->set_order( $order ); | |
// Set the invoice ID | |
$payment->set_invoice_id( $invoice_id ); | |
// Set the Payment Account code | |
$payment->set_code( $this->settings->get_option( 'payment_account' ) ); | |
// Set the payment date | |
$payment->set_date( apply_filters( 'woocommerce_xero_order_payment_date', $order_dt->format( 'Y-m-d' ), $order ) ); | |
// Set the currency rate | |
$payment->set_currency_rate( $currency_rate ); | |
// Stripe Fee? | |
$fee = get_post_meta($order->id, 'Stripe Fee', 1); | |
if (!empty ($fee)) { | |
// Set the amount | |
$payment->set_amount( $order->order_total - $fee ); | |
} else { | |
// Set the amount | |
$payment->set_amount( $order->order_total ); | |
} | |
return $payment; | |
} | |
/** | |
* If the payment gateway is set to COD, set the payment date as the current date instead of the order date. | |
*/ | |
public function cod_payment_set_payment_date_as_current_date( $order_date, $order ) { | |
$payment_method = ! empty( $order->payment_method ) ? $order->payment_method : ''; | |
if ( 'cod' !== $payment_method ) { | |
return $order_date; | |
} | |
return date( 'Y-m-d', time() ); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@dfinnema I'm pretty sure you have to multiply by 100, instead of using
round( $order->get_total(), 2 );
in some scenarios you'll get a lower value than actually needed.