/**
 * 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);
         }
     }
 }