/** * If the passed address has not been validated already, run it through * TaxCloud's VerifyAddress API * * @since 1.0 * @param (array) $address Associative array representing address * @param (int) $order_id -1 if the function is called during checkout. Otherwise, curent order ID. * @return (array) modified address array */ function maybe_validate_address($address, $order_id = -1) { $hash = md5(json_encode($address)); // Determine if validation is necessary $needs_validate = address_needs_validation($hash, $order_id); if ($order_id == -1) { $validated_addresses = isset(WC()->session->validated_addresses) ? WC()->session->validated_addresses : array(); } else { $validated_addresses = get_post_meta($order_id, '_wootax_validated_addresses', true); $validated_addresses = !is_array($validated_addresses) ? array() : $validated_addresses; } if (!$needs_validate) { return $validated_addresses[$hash]; } else { $final_address = $address; // All array values must be lowercase for validation to work properly $address = array_map('strtolower', $address); $usps_id = WC_WooTax::get_option('usps_id'); if ($usps_id) { $address['uspsUserID'] = $usps_id; // USPS Web Tools ID is required for calls to VerifyAddress $res = TaxCloud()->send_request('VerifyAddress', $address); // Check for errors if ($res !== false) { unset($res->ErrNumber); unset($res->ErrDescription); if (!isset($res->Country)) { $res->Country = $address['Country']; } if (!isset($res->Address2)) { $res->Address2 = ''; } $final_address = (array) $res; } } // Update address in $validated_addresses array $validated_addresses[$hash] = $final_address; // Store validated addresses in session or order meta depending on context if ($order_id == -1) { WC()->session->validated_addresses = $validated_addresses; } else { update_post_meta($order_id, '_wootax_validated_addresses', $validated_addresses); } } return $final_address; }
/** * Constructor: Starts Lookup and hooks into WooCommerce * * @since 4.2 */ public function __construct() { if (WT_SUBS_ACTIVE && WC_Subscriptions_Cart::cart_contains_subscription()) { $this->is_subscription = true; // Restore shipping taxes array for orders containing subscriptions add_filter('woocommerce_calculated_total', array($this, 'store_shipping_taxes'), 10, 2); add_action('woocommerce_cart_updated', array($this, 'restore_shipping_taxes')); // Hook for 2.3: woocommerce_after_calculate_totals // Set is_renewal flag if subscriptions is calculating the recurring order total if (WC_Subscriptions_Cart::get_calculation_type() == 'recurring_total') { $this->is_renewal = true; } else { $this->is_renewal = false; } } $this->cart = WC()->cart; $this->taxcloud = TaxCloud(); $this->addresses = fetch_business_addresses(); $this->init(); }
/** * Get all exemption certificates for a user given their username * * @since 4.3 * @return an array of exemption certificates */ function get_user_exemption_certs($user_login) { if (empty($user_login)) { return array(); } // Send GetExemptCertificates request $response = TaxCloud()->send_request('GetExemptCertificates', array('customerID' => $user_login)); if ($response !== false) { $certificate_result = is_object($response->ExemptCertificates) && isset($response->ExemptCertificates->ExemptionCertificate) ? $response->ExemptCertificates->ExemptionCertificate : NULL; $final_certificates = array(); if ($certificate_result != NULL) { // Convert response to array if only a single certificate is returned if (!is_array($certificate_result)) { $certificate_result = array($certificate_result); } // Dump certificates into object to be returned to client $certificates = $duplicates = array(); if (is_array($certificate_result)) { foreach ($certificate_result as $certificate) { // Add this certificate to the cert_list array $certificates[] = $certificate; // Add single purchase certificates to duplicate array if ($certificate->Detail->SinglePurchase == 1) { $order_number = $certificate->Detail->SinglePurchaseOrderNumber; if (!isset($duplicates[$order_number]) || !is_array($duplicates[$order_number])) { $duplicates[$order_number] = array(); } $duplicates[$order_number][] = $certificate->CertificateID; } } } // Isolate single certificates that should be kept if (count($duplicates) > 0) { foreach ($duplicates as &$dupes) { if (count($dupes) > 1) { $x = count($dupes); while (count($dupes) > 1) { unset($dupes[$x]); $x--; } } } } // Loop through cert_list and construct filtered cert_list array (duplicate single certificates removed) foreach ($certificates as $cert) { if (!is_object($cert)) { continue; } $keep = false; if ($cert->Detail->SinglePurchase == true && is_array($duplicates[$cert->Detail->SinglePurchaseOrderNumber]) && in_array($cert->CertificateID, $duplicates[$cert->Detail->SinglePurchaseOrderNumber])) { $keep = true; } elseif ($cert->Detail->SinglePurchase == true && !is_array($duplicates[$cert->Detail->SinglePurchaseOrderNumber]) || $cert->Detail->SinglePurchase == false) { $keep = true; } if ($keep) { $final_certificates[] = $cert; } } } return $final_certificates; } else { return array(); } }
/** * Determines if an order is ready for a lookup request * For an order to be "ready," three criteria must be met: * - At least one origin address is added to the site * - The customer's full address is available * - The order has not already been captured * * @since 4.2 * @return boolean true if the order is ready for a tax lookup; otherwise, false */ private function ready_for_lookup() { // Check for orders that are already captured if (WT_Orders::get_meta($this->order_id, 'captured')) { return false; } // Verify that one origin address (at least) is available for use if (!is_array(WT_Orders::$addresses) || count(WT_Orders::$addresses) == 0) { return false; } // Check for a valid destinaton address if (!TaxCloud()->is_valid_address($this->destination_address, true)) { return false; } return true; }
/** * Send a Returned request to TaxCloud when a full or partial refund is processed * - Full refund initiated when an order's status is set to "refunded" * - Partial refund initiated when WooCommerce manual refund mechanism is used * * @since 4.4 * @param (int) $order_id ID of WooCommerce order * @param (bool) $cron is this method being called from a WooTax cronjob? * @param (array) $items array of items to refund for partial refunds */ public static function refund_order($order_id, $cron = false, $items = array()) { $order = self::get_order($order_id); // Different rules need to be applied for full refunds $full_refund = count($items) == 0; $destination_address = !$full_refund ? $order->destination_address : array(); // Exit if the order has already been refunded, has not been captured, or was placed by international customer if ($full_refund && true === self::get_meta($order_id, 'refunded')) { return; } else { if (!self::get_meta($order_id, 'captured')) { if (!$cron && $full_refund) { wootax_add_message('<strong>WARNING:</strong> This order was not refunded in TaxCloud because it has not been captured yet. Please set the order\'s status to completed before refunding it.', 'update-nag'); } else { if (!$cron) { return "You must set this order's status to 'completed' before refunding any items."; } } return; } else { if (isset($destination_address['Country']) && !in_array($destination_address['Country'], array('United States', 'US'))) { return true; } } } // Set up item mapping array if this is a partial refund if (!$full_refund) { // Construct mapping array $mapping_array = self::get_meta($order_id, 'mapping_array'); if (count($mapping_array) == 0) { foreach ($items as $location => $items) { $mapping_array[$location] = array(); foreach ($items as $item) { $mapping_array[$location][$item['ItemID']] = $order->get_item_index($item['ItemID']); } } } } // Loop through sub-orders and send Returned request for each $taxcloud_ids = self::get_meta($order_id, 'taxcloud_ids'); foreach ($taxcloud_ids as $address_key => $order_ids) { $refund_items = NULL; // Get cart items to refund in correct format if appropriate if (!$full_refund && isset($items[$address_key])) { $refund_items = array(); // Get items in appropriate format foreach ($items[$address_key] as $item) { $item['Index'] = $mapping_array[$address_key][$item['ItemID']]; $refund_items[] = $item; } } // Send Returned request $date = new DateTime('NOW'); $req = array('cartItems' => $refund_items, 'returnedDate' => $date->format(DateTime::ATOM), 'orderID' => $order_ids['order_id']); $res = TaxCloud()->send_request('Returned', $req); // Check for errors if ($res == false) { if (!$cron && $full_refund) { wootax_add_message('There was an error while refunding the order. ' . TaxCloud()->get_error_message()); break; } else { return TaxCloud()->get_error_message(); } } } // For full refunds, remove order tax completely if ($full_refund) { $order->remove_tax(); } self::update_meta($order_id, 'refunded', true); return true; }
/** * Validates the user's TaxCloud API ID/API Key by sending a Ping request to the TaxCloud API * * @since 1.0 * @return (boolean) true or an error message on failure */ public static function verify_taxcloud_settings() { $taxcloud_id = $_POST['wootax_tc_id']; $taxcloud_key = $_POST['wootax_tc_key']; if (empty($taxcloud_id) || empty($taxcloud_key)) { die(false); } else { $taxcloud = TaxCloud($taxcloud_id, $taxcloud_key); // Send ping request and check for errors $response = $taxcloud->send_request('Ping'); if ($response == false) { die($taxcloud->get_error_message()); } else { die(true); } } }