/** * Completes a pending order and marks it's status as whatever it should be next. * This function will process any payments, capture amounts from gateways, increase * # sold for each product in the order, etc. * * @param string The pending order token. * @param int The status to set the completed order to. * @return boolean True if successful, false on failure. */ function CompletePendingOrder($pendingOrderToken, $status, $sendInvoice=true) { $orderData = LoadPendingOrdersByToken($pendingOrderToken, true); if($orderData === false) { return false; } $processedStoreCredit = false; $processedGiftCertificates = false; $orderStoreCredit = 0; $orderTotalAmount = 0; // Flag used to create the customer record but only if atleast one order was successful $createCustomer = false; // Sum up our total amount and store credit foreach ($orderData['orders'] as $order) { if ($order['ordstatus'] != 0) { continue; } $orderStoreCredit += $order['ordstorecreditamount']; $orderTotalAmount += $order['total_inc_tax']; } // flag to indicate if we should send notifications? only if the order was previously incomplete and the new status isn't declined/cancelled/refunded $sendNotifications = false; foreach($orderData['orders'] as $order) { $newStatus = $status; // Wait, was the order already complete? Then we don't do anything if($order['ordstatus'] != ORDER_STATUS_INCOMPLETE) { continue; } // If this order is digital, and the status is awaiting fulfillment, there's nothing // to actually fulfill, so set it to completed. if($order['ordisdigital'] && $newStatus == ORDER_STATUS_AWAITING_FULFILLMENT) { $newStatus = ORDER_STATUS_COMPLETED; } $extraInfo = @unserialize($order['extrainfo']); if(!is_array($extraInfo)) { $extraInfo = array(); } // only email and update order data (coupons, certificates, store credit etc) if it's not a declined, cancelled or refunded order if($newStatus != ORDER_STATUS_DECLINED && $newStatus != ORDER_STATUS_CANCELLED && $newStatus != ORDER_STATUS_REFUNDED) { $createCustomer = true; $sendNotifications = true; if($sendInvoice && !EmailInvoiceToCustomer($order['orderid'], $newStatus)) { $GLOBALS['HideError'] = ""; $GLOBALS['ErrorMessage'] = GetLang('ErroSendingInvoiceEmail'); $GLOBALS['HideSuccess'] = "none"; } // Are we updating the inventory levels when an order has been placed? if(GetConfig('UpdateInventoryLevels') == 1) { DecreaseInventoryFromOrder($order['orderid']); } // If this order now complete, we need to activate any gift certificates if(OrderIsComplete($newStatus)) { $GLOBALS['ISC_CLASS_GIFTCERTIFICATES'] = GetClass('ISC_GIFTCERTIFICATES'); $GLOBALS['ISC_CLASS_GIFTCERTIFICATES']->ActivateGiftCertificates($order['orderid']); } // If we've had one or more coupons been applied to this order, we now need to increment the number of uses $couponIds = array(); $query = " SELECT * FROM [|PREFIX|]order_coupons WHERE ordcouporderid='".(int)$order['orderid']."' "; $result = $GLOBALS['ISC_CLASS_DB']->Query($query); while($coupon = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) { $couponIds[] = $coupon['ordcouponid']; } if(!empty($couponIds)) { $couponsUsed = array_unique($couponIds); $couponList = implode(",", array_map("intval", $couponsUsed)); $query = " UPDATE [|PREFIX|]coupons SET couponnumuses=couponnumuses+1 WHERE couponid IN (".$couponList.") "; $GLOBALS['ISC_CLASS_DB']->Query($query); foreach ($couponIds as $cid) { getclass('ISC_COUPON')->updatePerCustomerUsage($cid); } } // If we used store credit on this order, we now need to subtract it from the users account. if($order['ordstorecreditamount'] > 0 && $processedStoreCredit == false) { $GLOBALS['ISC_CLASS_CUSTOMER'] = GetClass('ISC_CUSTOMER'); $currentCredit = $GLOBALS['ISC_CLASS_CUSTOMER']->GetCustomerStoreCredit($order['ordcustid']); $newCredit = $currentCredit - $orderStoreCredit; if($newCredit < 0) { $newCredit = 0; } $updatedCustomer = array( 'custstorecredit' => $newCredit, ); $GLOBALS['ISC_CLASS_DB']->UpdateQuery('customers', $updatedCustomer, "customerid='".(int)$order['ordcustid']."'"); $processedStoreCredit = true; } // If one or more gift certificates were used we need to apply them to this order and subtract the total if($order['ordgiftcertificateamount'] > 0 && isset($extraInfo['giftcertificates']) && !empty($extraInfo['giftcertificates']) && $processedGiftCertificates == false) { $usedCertificates = array(); $GLOBALS['ISC_CLASS_GIFT_CERTIFICATES'] = GetClass('ISC_GIFTCERTIFICATES'); $GLOBALS['ISC_CLASS_GIFT_CERTIFICATES']->ApplyGiftCertificatesToOrder($order['orderid'], $orderTotalAmount + $order['ordgiftcertificateamount'], $extraInfo['giftcertificates'], $usedCertificates); unset($extraInfo['giftcertificates']); $processedGiftCertificates = true; } // If there are one or more digital products in this order then we need to create a record in the order_downloads table // for each of them and set the expiry dates $query = " SELECT ordprodid, ordprodqty FROM [|PREFIX|]order_products WHERE orderorderid='".$order['orderid']."' AND ordprodtype='digital' "; $result = $GLOBALS['ISC_CLASS_DB']->Query($query); $digitalProductIds = array(); while($digitalProduct = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) { $digitalProductIds[$digitalProduct['ordprodid']] = $digitalProduct; } if(!empty($digitalProductIds)) { $query = " SELECT downloadid, productid, downexpiresafter, downmaxdownloads FROM [|PREFIX|]product_downloads WHERE productid IN (".implode(',', array_keys($digitalProductIds)).") "; $result = $GLOBALS['ISC_CLASS_DB']->Query($query); while($digitalDownload = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) { $expiryDate = 0; // If this download has an expiry date, set it to now + expiry time if($digitalDownload['downexpiresafter'] > 0) { $expiryDate = time() + $digitalDownload['downexpiresafter']; } // If they've purchased more than one, we need to give them max downloads X quantity downloads $quantity = $digitalProductIds[$digitalDownload['productid']]['ordprodqty']; $newDownload = array( 'orderid' => $order['orderid'], 'downloadid' => $digitalDownload['downloadid'], 'numdownloads' => 0, 'downloadexpires' => $expiryDate, 'maxdownloads' => $digitalDownload['downmaxdownloads'] * $quantity ); $GLOBALS['ISC_CLASS_DB']->InsertQuery('order_downloads', $newDownload); } } } // Does a customer account need to be created? if(!empty($extraInfo['createAccount'])) { createOrderCustomerAccount($order, $extraInfo['createAccount']); unset($extraInfo['createAccount']); } // Now update the order and set the status $updatedOrder = array( "ordstatus" => $newStatus, "extrainfo" => serialize($extraInfo) ); $GLOBALS['ISC_CLASS_DB']->UpdateQuery("orders", $updatedOrder, "orderid='".$order['orderid']."'"); } if($sendNotifications) { // Trigger all active new order notification methods SendOrderNotifications($pendingOrderToken); // Do we need to add them to a Interspire Email Marketer mailing list? SubscribeCustomerToLists($pendingOrderToken); // Update the current uses of each rule $quote = getCustomerQuote(); $appliedRules = array_keys(getCustomerQuote()->getAppliedDiscountRules()); if(!empty($appliedRules)) { require_once ISC_BASE_PATH.'/lib/rule.php'; updateRuleUses($appliedRules); } } // Empty the users cart and kill the checkout process EmptyCartAndKillCheckout(); return true; }
/** * Ebay: Sent to a seller when a buyer completes the checkout process for an item. Not sent when an auction ends without bids. * * My notes: Seems to be triggered when the buyer's payment process for an AUCTION item has completed, is not fired for fixed price items which fire 'FixedPrice...' notifications instead * * @param array $body */ protected function _handleAuctionCheckoutComplete($body) { // The data fields in the notification are the same as those returned by the GetItemTransactions call with the default detail level. if (!empty ($body['Item']['ItemID']) && ISC_ADMIN_EBAY::validEbayItemId($body['Item']['ItemID'])) { // variables init $order = array(); $orderId = 1; $order['ShippingInsuranceCost'] = 0; $completedPaymentHoldStatus = array('None', 'Released'); $orderStatus = ORDER_STATUS_AWAITING_PAYMENT; $existingOrderId = 0; // Determine if the buyer purchase multiple items from the same seller if (!empty($body['TransactionArray']['Transaction']['ContainingOrder'])) { // Call the operation to get the order transaction. $orderId = $body['TransactionArray']['Transaction']['ContainingOrder']['OrderID']; // if the record already exist, check if we need to update existing orders, that the payment hasn't been cleared previously. $existingOrder = GetOrderByEbayOrderId($orderId); $orderTransaction = ISC_ADMIN_EBAY_OPERATIONS::getOrderTransactions($orderId); $transactions = $orderTransaction->OrderArray->Order->TransactionArray->Transaction; $order['SubTotal'] = (string) $orderTransaction->OrderArray->Order->Subtotal; $order['ShippingCost'] = (string) $orderTransaction->OrderArray->Order->ShippingServiceSelected->ShippingServiceCost; $order['ShippingInsuranceCost'] = 0; $order['GrandTotal'] = (string) $orderTransaction->OrderArray->Order->Total; $order['TotalQuantityPurchased'] = 0; foreach ($transactions as $transaction) { $convertedTransaction = (array) $transaction; $variationOptionsString = ''; if (isset($convertedTransaction['Variation']->VariationSpecifics)) { $variationNameValueList = (array) $convertedTransaction['Variation']->VariationSpecifics->NameValueList; $variationOptions = array(); $variationSpecifics = (array) $convertedTransaction['Variation']->VariationSpecifics; if (is_array($variationSpecifics['NameValueList'])) { foreach ($variationSpecifics['NameValueList'] as $option) { $variationOptions[(string) $option->Name] = (string) $option->Value; } } else { $variationOptions[(string) $variationSpecifics['NameValueList']->Name] = (string) $variationSpecifics['NameValueList']->Value; } $variationOptionsString = serialize($variationOptions); } $quantityPurchased = $convertedTransaction['QuantityPurchased']; $transactionPrice = $convertedTransaction['TransactionPrice']; $itemId = (string) $convertedTransaction['Item']->ItemID; $transactionId = (string) $convertedTransaction['TransactionID']; $totalTransactionPrice = $transactionPrice * $quantityPurchased; $order['Transaction'][] = array( 'QuantityPurchased' => $quantityPurchased, 'TransactionPrice' => $transactionPrice, 'ItemId' => $itemId, 'TotalTransactionPrice' => $totalTransactionPrice, 'VariationOptionsString' => $variationOptionsString, 'TransactionId' => $transactionId, ); $order['TotalQuantityPurchased'] += $quantityPurchased; $order['Currency'] = GetCurrencyByCode($body['TransactionArray']['Transaction']['AmountPaid']['!currencyID']); $buyerInfoShippingAddress = $body['TransactionArray']['Transaction']['Buyer']['BuyerInfo']['ShippingAddress']; $buyerEmailAddress = $body['TransactionArray']['Transaction']['Buyer']['Email']; } if ($existingOrder) { $existingOrderId = $existingOrder['orderid']; } } else { $transactions = $body['TransactionArray']; foreach ($transactions as $transaction) { $itemId = $body['Item']['ItemID']; $transactionId = $transaction['TransactionID']; $query = " SELECT * FROM [|PREFIX|]order_products WHERE ebay_item_id = '".$GLOBALS["ISC_CLASS_DB"]->Quote($itemId)."' AND ebay_transaction_id = '".$GLOBALS["ISC_CLASS_DB"]->Quote($transactionId)."' LIMIT 1 "; $res = $GLOBALS['ISC_CLASS_DB']->Query($query); $row = $GLOBALS['ISC_CLASS_DB']->Fetch($res); $eachItemPriceExTax = $transaction['TransactionPrice']['!']; $quantityPurchased = $transaction['QuantityPurchased']; $totalTransactionPrice = $quantityPurchased * $eachItemPriceExTax; $variationOptionsString = ''; // do we have a variation for this product? if (isset($transaction['Variation']['VariationSpecifics'])) { $variationNameValueList = $transaction['Variation']['VariationSpecifics']['NameValueList']; $variationOptions = array(); foreach ($variationNameValueList as $option) { $variationOptions[$option['Name']] = $option['Value']; } $variationOptionsString = serialize($variationOptions); } $order['TotalQuantityPurchased'] = $quantityPurchased; $order['SubTotal'] = $eachItemPriceExTax * $order['TotalQuantityPurchased']; $order['ShippingCost'] = $transaction['ShippingServiceSelected']['ShippingServiceCost']['!']; if (isset ($transaction['ShippingServiceSelected']['ShippingInsuranceCost']['!'])) { $order['ShippingInsuranceCost'] = $transaction['ShippingServiceSelected']['ShippingInsuranceCost']['!']; } $order['GrandTotal'] = $transaction['AmountPaid']['!']; $order['Transaction'][] = array( 'QuantityPurchased' => $quantityPurchased, 'TransactionPrice' => $eachItemPriceExTax, 'ItemId' => $itemId, 'TotalTransactionPrice' => $totalTransactionPrice, 'VariationOptionsString' => $variationOptionsString, 'TransactionId' => $transactionId, ); $order['Currency'] = GetCurrencyByCode($transaction['AmountPaid']['!currencyID']); $buyerInfoShippingAddress = $transaction['Buyer']['BuyerInfo']['ShippingAddress']; $buyerEmailAddress = $transaction['Buyer']['Email']; if (!$row) { // only process the new transaction break; } else { $existingOrderId = $row['orderorderid']; } } } $paymentHoldStatus = $body['TransactionArray']['Transaction']['Status']['PaymentHoldStatus']; if (in_array(trim($paymentHoldStatus), $completedPaymentHoldStatus)) { $orderStatus = ORDER_STATUS_AWAITING_FULFILLMENT; } if ($existingOrderId != 0) { if (!isset ($existingOrder)) { $existingOrder = GetOrder($existingOrderId, false, true, true); } // check if there're any existing order need to be updated. // in the case, paypal release the hold payment of buyer if ($existingOrder['ordstatus'] == ORDER_STATUS_AWAITING_PAYMENT && $orderStatus == ORDER_STATUS_AWAITING_FULFILLMENT) { // update the quantity for each transaction $GLOBALS["ISC_CLASS_DB"]->StartTransaction(); foreach ($order['Transaction'] as $eachTransaction) { // Get product Id try { $itemObj = new ISC_ADMIN_EBAY_ITEMS($eachTransaction['ItemId']); $productId = $itemObj->getProductId(); } catch (Exception $e) { $this->log->LogSystemDebug('ebay', $e->getMessage()); return false; } // update the item quantity in store $updatedData['quantity_remaining'] = $itemObj->getQuantityRemaining() - $eachTransaction['QuantityPurchased']; if (!$GLOBALS['ISC_CLASS_DB']->UpdateQuery('ebay_items', $updatedData, "ebay_item_id='" . $eachTransaction['ItemId'] . "'")) { $this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error()); $GLOBALS["ISC_CLASS_DB"]->RollbackTransaction(); return false; } if (!UpdateOrderStatus($existingOrderId, $orderStatus, true, true)) { $GLOBALS["ISC_CLASS_DB"]->RollbackTransaction(); return false; } } $GLOBALS["ISC_CLASS_DB"]->CommitTransaction(); // update the store inventory if necessary if (GetConfig('UpdateInventoryLevels') == 1) { DecreaseInventoryFromOrder($existingOrderId); } $this->log->LogSystemDebug('ebay', 'The status of the store order ('. $existingOrderId .') has been updated to: Awaiting Fulfillment'); } return true; } $order['ShippingTotalCost'] = $order['ShippingInsuranceCost'] + $order['ShippingCost']; // Buyer's address information $addressMap = array( 'Name', 'CompanyName', 'Street1', 'Street2', 'CityName', 'PostalCode', 'Country', 'CountryName', 'Phone', 'StateOrProvince', ); // Initialize the value, make sure it's not empty foreach ($addressMap as $key) { if (!isset($buyerInfoShippingAddress[$key])) { $buyerInfoShippingAddress[$key] = ''; } } $buyerCountryId = GetCountryIdByISO2($buyerInfoShippingAddress['Country']); $buyerStateId = GetStateByName($buyerInfoShippingAddress['StateOrProvince'], $buyerCountryId); $buyerStateName = $buyerInfoShippingAddress['StateOrProvince']; if (!$buyerStateId) { $buyerStateId = GetStateByAbbrev($buyerInfoShippingAddress['StateOrProvince'], $buyerCountryId); $stateInfo = GetStateInfoById($buyerStateId); $buyerStateName = $stateInfo['statename']; } // Tokenize buyer's first and last name $nameTokens = explode(' ', $buyerInfoShippingAddress['Name']); $buyerFirstName = $nameTokens[0]; $buyerLastName = ''; if (!empty($nameTokens[1])) { $buyerLastName = $nameTokens[1]; } $orderToken = generateOrderToken(); // Preparing data to be inserted to orders table $newOrder = array( 'ordtoken' => $orderToken, 'orderpaymentmodule' => '', 'orderpaymentmethod' => '', 'orderpaymentmodule' => '', 'extraInfo' => serialize(array()), 'orddefaultcurrencyid' => $order['Currency']['currencyid'], 'orddate' => time(), 'ordlastmodified' => time(), 'ordcurrencyid' => $order['Currency']['currencyid'], 'ordcurrencyexchangerate' => 1, 'ordipaddress' => GetIP(), 'ordcustmessage' => '', 'ordstatus' => $orderStatus, 'base_shipping_cost' => $order['ShippingTotalCost'], 'base_handling_cost' => 0, 'ordbillemail' => $buyerEmailAddress, 'ordbillfirstname' => $buyerFirstName, 'ordbilllastname' => $buyerLastName, 'ordbillcompany' => $buyerInfoShippingAddress['CompanyName'], 'ordbillstreet1' => $buyerInfoShippingAddress['Street1'], 'ordbillstreet2' => $buyerInfoShippingAddress['Street2'], 'ordbillsuburb' => $buyerInfoShippingAddress['CityName'], 'ordbillzip' => $buyerInfoShippingAddress['PostalCode'], 'ordbillcountrycode' => $buyerInfoShippingAddress['Country'], 'ordbillphone' => $buyerInfoShippingAddress['Phone'], 'ordbillstateid' => (int) $buyerStateId, 'ordbillstate' => $buyerStateName, 'ordbillcountry' => $buyerInfoShippingAddress['CountryName'], 'ordbillcountryid' => (int) $buyerCountryId, 'total_ex_tax' => $order['GrandTotal'], 'total_inc_tax' => $order['GrandTotal'], 'shipping_cost_ex_tax' => $order['ShippingTotalCost'], 'shipping_cost_inc_tax' => $order['ShippingTotalCost'], 'subtotal_inc_tax' => $order['SubTotal'], 'subtotal_ex_tax' => $order['SubTotal'], 'ebay_order_id' => $orderId, ); ResetStartingOrderNumber(); // Start the transaction $GLOBALS["ISC_CLASS_DB"]->StartTransaction(); // Inserting order data $newOrderId = $GLOBALS["ISC_CLASS_DB"]->InsertQuery('orders', $newOrder); if (!$newOrderId) { $this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error()); $GLOBALS["ISC_CLASS_DB"]->RollbackTransaction(); return false; } $orderAddress = array( 'first_name' => $buyerFirstName, 'last_name' => $buyerLastName, 'company' => $buyerInfoShippingAddress['CompanyName'], 'address_1' => $buyerInfoShippingAddress['Street1'], 'address_2' => $buyerInfoShippingAddress['Street2'], 'city' => $buyerInfoShippingAddress['CityName'], 'zip' => $buyerInfoShippingAddress['PostalCode'], 'country_iso2' => $buyerInfoShippingAddress['Country'], 'phone' => $buyerInfoShippingAddress['Phone'], 'total_items' => $order['TotalQuantityPurchased'], 'email' => $buyerEmailAddress, 'country_id' => (int) $buyerCountryId, 'country' => $buyerInfoShippingAddress['CountryName'], 'state_id' => (int) $buyerStateId, 'state' => $buyerStateName, 'order_id' => $newOrderId, ); $addressId = $GLOBALS['ISC_CLASS_DB']->insertQuery('order_addresses', $orderAddress); if (!$addressId) { $this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error()); $GLOBALS["ISC_CLASS_DB"]->RollbackTransaction(); return false; } // Inserting order shipping $orderShipping = array( 'order_address_id' => $addressId, 'order_id' => $newOrderId, 'base_cost' => $order['ShippingTotalCost'], 'cost_inc_tax' => $order['ShippingTotalCost'], 'cost_ex_tax' => $order['ShippingTotalCost'], 'method' => 'Available on eBay', ); if (!$GLOBALS['ISC_CLASS_DB']->insertQuery('order_shipping', $orderShipping)) { $this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error()); $GLOBALS["ISC_CLASS_DB"]->RollbackTransaction(); return false; } // Go thru each sold item in the order foreach ($order['Transaction'] as $eachTransaction) { // Get product Id try { $itemObj = new ISC_ADMIN_EBAY_ITEMS($eachTransaction['ItemId']); $productId = $itemObj->getProductId(); } catch (Exception $e) { $this->log->LogSystemDebug('ebay', $e->getMessage()); return false; } // Inserting order product $productObj = new ISC_PRODUCT($productId); $newProduct = array( 'orderorderid' => $newOrderId, 'ordprodid' => $productId, 'ordprodsku' => $productObj->GetSKU(), 'ordprodname' => $productObj->GetProductName(), 'ordprodtype' => $productObj->GetProductType(), 'ordprodqty' => $eachTransaction['QuantityPurchased'], 'base_price' => $eachTransaction['TransactionPrice'], 'price_ex_tax' => $eachTransaction['TransactionPrice'], 'price_inc_tax' => $eachTransaction['TransactionPrice'], 'price_tax' => 0, 'base_total' => $eachTransaction['TotalTransactionPrice'], 'total_ex_tax' => $eachTransaction['TotalTransactionPrice'], 'total_inc_tax' => $eachTransaction['TotalTransactionPrice'], 'total_tax' => 0, 'base_cost_price' => 0, 'cost_price_inc_tax' => 0, 'cost_price_inc_tax' => 0, 'cost_price_tax' => 0, 'ordprodweight' => $productObj->GetWeight(false), 'ordprodoptions' => $eachTransaction['VariationOptionsString'], 'ordprodvariationid' => $productObj->_prodvariationid, 'ordprodwrapid' => 0, 'ordprodwrapname' => '', 'base_wrapping_cost' => 0, 'wrapping_cost_ex_tax' => 0, 'wrapping_cost_inc_tax' => 0, 'wrapping_cost_tax' => 0, 'ordprodwrapmessage' => '', 'ordprodeventname' => '', 'ordprodeventdate' => 0, 'ordprodfixedshippingcost' => $productObj->GetFixedShippingCost(), 'order_address_id' => $addressId, 'ebay_item_id' => $eachTransaction['ItemId'], 'ebay_transaction_id' => $eachTransaction['TransactionId'], ); $orderProductId = $GLOBALS['ISC_CLASS_DB']->insertQuery('order_products', $newProduct); if (!$orderProductId) { $this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error()); $GLOBALS["ISC_CLASS_DB"]->RollbackTransaction(); return false; } if ($orderStatus == ORDER_STATUS_AWAITING_FULFILLMENT) { // update the item quantity in store $updatedData['quantity_remaining'] = $itemObj->getQuantityRemaining() - $eachTransaction['QuantityPurchased']; if (!$GLOBALS['ISC_CLASS_DB']->UpdateQuery('ebay_items', $updatedData, "ebay_item_id='" . $eachTransaction['ItemId'] . "'")) { $this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error()); $GLOBALS["ISC_CLASS_DB"]->RollbackTransaction(); return false; } } } $GLOBALS["ISC_CLASS_DB"]->CommitTransaction(); // update the store inventory if necessary if (GetConfig('UpdateInventoryLevels') == 1) { DecreaseInventoryFromOrder($newOrderId); } // Trigger new order notifications SendOrderNotifications($orderToken); $this->log->LogSystemDebug('ebay', 'An Item ('. $body['Item']['ItemID'] .') has been paid by the buyer and added to the store order (' . $newOrderId. ').'); return true; } return false; }
/** * Completes a pending order and marks it's status as whatever it should be next. * This function will process any payments, capture amounts from gateways, increase * # sold for each product in the order, etc. * * @param string The pending order token. * @param int The status to set the completed order to. * @return boolean True if successful, false on failure. */ function CompletePendingOrder($pendingOrderToken, $status, $sendInvoice = true) { $orderData = LoadPendingOrdersByToken($pendingOrderToken, true); if ($orderData === false) { return false; } $processedStoreCredit = false; $processedGiftCertificates = false; $orderStoreCredit = 0; $orderTotalAmount = 0; // Sum up our total amount and store credit foreach ($orderData['orders'] as $order) { if ($order['ordstatus'] != 0) { continue; } $orderStoreCredit += $order['ordstorecreditamount']; $orderTotalAmount += $order['ordtotalamount']; } foreach ($orderData['orders'] as $order) { // Wait, was the order already complete? Then we don't do anything if ($order['ordstatus'] != 0) { continue; } // If this order is digital, and the status is awaiting fulfillment, there's nothing // to actually fulfill, so set it to completed. if ($order['ordisdigital'] && $status == ORDER_STATUS_AWAITING_FULFILLMENT) { $status = ORDER_STATUS_COMPLETED; } // Don't email the customer if this order was declined if ($status != ORDER_STATUS_DECLINED) { if ($sendInvoice && !EmailInvoiceToCustomer($order['orderid'], $status)) { $GLOBALS['HideError'] = ""; $GLOBALS['ErrorMessage'] = GetLang('ErroSendingInvoiceEmail'); $GLOBALS['HideSuccess'] = "none"; } // Are we updating the inventory levels when an order has been placed? if (GetConfig('UpdateInventoryLevels') == 1) { DecreaseInventoryFromOrder($order['orderid']); } } // If this order now complete, we need to activate any gift certificates if (OrderIsComplete($status)) { $GLOBALS['ISC_CLASS_GIFTCERTIFICATES'] = GetClass('ISC_GIFTCERTIFICATES'); $GLOBALS['ISC_CLASS_GIFTCERTIFICATES']->ActivateGiftCertificates($order['orderid']); } // If we've had one or more coupons been applied to this order, we now need to increment the number of uses $couponIds = array(); $query = "\n\t\t\tSELECT *\n\t\t\tFROM [|PREFIX|]order_coupons\n\t\t\tWHERE ordcouporderid='" . (int) $order['orderid'] . "'\n\t\t"; $result = $GLOBALS['ISC_CLASS_DB']->Query($query); while ($coupon = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) { $couponIds[] = $coupon['ordcouponid']; } if (!empty($couponIds)) { $couponsUsed = array_unique($couponIds); $couponList = implode(",", array_map("intval", $couponsUsed)); $query = "\n\t\t\t\tUPDATE [|PREFIX|]coupons\n\t\t\t\tSET couponnumuses=couponnumuses+1\n\t\t\t\tWHERE couponid IN (" . $couponList . ")\n\t\t\t"; $GLOBALS['ISC_CLASS_DB']->Query($query); } // If we used store credit on this order, we now need to subtract it from the users account. if ($order['ordstorecreditamount'] > 0 && $processedStoreCredit == false) { $GLOBALS['ISC_CLASS_CUSTOMER'] = GetClass('ISC_CUSTOMER'); $currentCredit = $GLOBALS['ISC_CLASS_CUSTOMER']->GetCustomerStoreCredit($order['ordcustid']); $newCredit = $currentCredit - $orderStoreCredit; if ($newCredit < 0) { $newCredit = 0; } $updatedCustomer = array('custstorecredit' => $newCredit); $GLOBALS['ISC_CLASS_DB']->UpdateQuery('customers', $updatedCustomer, "customerid='" . (int) $order['ordcustid'] . "'"); $processedStoreCredit = true; } $extraInfo = @unserialize($order['extrainfo']); if (!is_array($extraInfo)) { $extraInfo = array(); } // If one or more gift certificates were used we need to apply them to this order and subtract the total if ($order['ordgiftcertificateamount'] > 0 && isset($extraInfo['giftcertificates']) && !empty($extraInfo['giftcertificates']) && $processedGiftCertificates == false) { $usedCertificates = array(); $GLOBALS['ISC_CLASS_GIFT_CERTIFICATES'] = GetClass('ISC_GIFTCERTIFICATES'); $GLOBALS['ISC_CLASS_GIFT_CERTIFICATES']->ApplyGiftCertificatesToOrder($order['orderid'], $orderTotalAmount, $extraInfo['giftcertificates'], $usedCertificates); unset($extraInfo['giftcertificates']); $processedGiftCertificates = true; } // If there are one or more digital products in this order then we need to create a record in the order_downloads table // for each of them and set the expiry dates $query = "\n\t\t\tSELECT ordprodid, ordprodqty\n\t\t\tFROM [|PREFIX|]order_products\n\t\t\tWHERE orderorderid='" . $order['orderid'] . "' AND ordprodtype='digital'\n\t\t"; $result = $GLOBALS['ISC_CLASS_DB']->Query($query); $digitalProductIds = array(); while ($digitalProduct = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) { $digitalProductIds[$digitalProduct['ordprodid']] = $digitalProduct; } if (!empty($digitalProductIds)) { $query = "\n\t\t\t\tSELECT downloadid, productid, downexpiresafter, downmaxdownloads\n\t\t\t\tFROM [|PREFIX|]product_downloads\n\t\t\t\tWHERE productid IN (" . implode(',', array_keys($digitalProductIds)) . ")\n\t\t\t"; $result = $GLOBALS['ISC_CLASS_DB']->Query($query); while ($digitalDownload = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) { $expiryDate = 0; // If this download has an expiry date, set it to now + expiry time if ($digitalDownload['downexpiresafter'] > 0) { $expiryDate = time() + $digitalDownload['downexpiresafter']; } // If they've purchased more than one, we need to give them max downloads X quantity downloads $quantity = $digitalProductIds[$digitalDownload['productid']]['ordprodqty']; $newDownload = array('orderid' => $order['orderid'], 'downloadid' => $digitalDownload['downloadid'], 'numdownloads' => 0, 'downloadexpires' => $expiryDate, 'maxdownloads' => $digitalDownload['downmaxdownloads'] * $quantity); $GLOBALS['ISC_CLASS_DB']->InsertQuery('order_downloads', $newDownload); } } // Now update the order and set the status $updatedOrder = array("ordstatus" => $status, "extrainfo" => serialize($extraInfo)); $GLOBALS['ISC_CLASS_DB']->UpdateQuery("orders", $updatedOrder, "orderid='" . $order['orderid'] . "'"); } return true; }
/** * Insert/Update the node with the response record * * Method will insert/update the node with the response record * * @access protected * @param array $responseData The reponse data from QB * @param array $nodeData The optional node data array. If set then update, else insert * @return int The new or updtaed node ID on success, FALSE on error */ protected function syncResponseRecord2Store($responseData, $nodeData=false) { if (!is_array($responseData)) { $xargs = func_get_args(); throw new QBException("Invalid arguments when syncing order record from QB", $xargs); } /** * Get our stored customer ID if we have one */ $customerId = $this->getCustomer4Order($responseData["TxnID"]); /** * Get the customer ListID and find the matching customer ID if we can. We need to have a customer ListID */ if (is_null($customerId)) { if (!isset($responseData["CustomerRef"]["ListID"])) { throw new QBException("Unable to find customer ListID when syncing orders", $responseData); } $customerRef = $this->accounting->getReference("customer", '', $responseData["CustomerRef"]["ListID"], '', false); /** * If we don't have a reference then use the existing customer ID if we have one */ if (!is_array($customerRef)) { if (is_array($nodeData) && array_key_exists("ordcustid", $nodeData) && isId($nodeData["ordcustid"])) { $customerId = $nodeData["ordcustid"]; /** * Else if we do have a nodeData but no customer ID then its a guest checkout */ } else if (is_array($nodeData) && (!array_key_exists("ordcustid", $nodeData) || !isId($nodeData["ordcustid"]))) { $customerId = ''; /** * Else it is a new customer which we do not have a record for */ } else { $lastKid = end($this->spool["children"]); if ($lastKid["nodeType"] == "customer" && $lastKid["service"] == "query") { /** * If we couldn't find the record then error out */ if ($lastKid["errNo"] > 0) { throw new QBException("Unable to find customer when syncing orders from QB", $responseData["CustomerRef"]["ListID"]); } /** * Check to see if this is an anonymous customer (guest checkout). If so then don't create a customer record */ if ($this->accounting->isCustomerGuestCheckout($lastKid["response"])) { $customerId = ''; /** * Else it is a real customer so create it */ } else { $customerId = $this->customerSyncAPI->syncResponseRecord2Store($lastKid["response"]); if (!isId($customerId)) { throw new QBException("Unable to create customer record when syncing orders from QB", $lastKid["response"]); } $referenceDataSetup = $this->customerSyncAPI->referenceDataSetup; $referenceDataExternalKey = $this->customerSyncAPI->referenceDataExternalKey; $referenceReturn = $this->setReferenceDataStatically("customer", $customerId, $lastKid["response"], '', $referenceDataSetup, $referenceDataExternalKey); if (isId($referenceReturn)) { $customerRef = $this->accounting->getReference("customer", $referenceReturn, '', '', false); } } } else { $childRequestData = array( "ListID" => $responseData["CustomerRef"]["ListID"] ); return $this->execChildService("customer", "query", $childRequestData); } } /** * Else we have the customer but not the order yet */ } else if (is_array($customerRef)) { $customerId = $customerRef["accountingrefnodeid"]; /** * Else we got no customer */ } else { $customerId = ''; } /** * Save the customer ID for this order */ $this->setCustomer2Order($responseData["TxnID"], $customerId); } /** * If we have a custom ID then get the customer record as we'll need it later on */ $customerNodeData = ''; if (isId($customerId)) { $customerNodeData = $this->customerAPI->get($customerId); } if ($this->accounting->getValue("orderoption") == "order") { $salesLineRetTag = "SalesOrderLineRet"; } else { $salesLineRetTag = "SalesReceiptLineRet"; } /** * OK, we got the customer, now we need to get all the products */ if (!isset($responseData[$salesLineRetTag]) || !is_array($responseData[$salesLineRetTag]) || empty($responseData[$salesLineRetTag])) { throw new QBException("Missing/Invalid product array when syncing orders", array("tag" => $salesLineRetTag, "response" => $responseData)); } /** * Set aside some vars for shipping costs and the tax component */ $productSubTotal = 0; $shippingCost = 0; $taxCost = 0; /** * Sanatize it */ if (!isset($responseData[$salesLineRetTag][0])) { $responseData[$salesLineRetTag] = array($responseData[$salesLineRetTag]); } foreach ($responseData[$salesLineRetTag] as $product) { /** * Check to see if we have already recorded this product */ if ($this->checkProductListId4Order($responseData["TxnID"], $product["ItemRef"]["ListID"])) { continue; } /** * OK, we haven't done this one yet so lets do it. If we have any kids then deal with them first */ $lastKid = end($this->spool["children"]); if ($lastKid["service"] == "query" && ($lastKid["nodeType"] == "product" || $lastKid["nodeType"] == "productvariation")) { /** * If we couldn't find the record then error out */ if ($lastKid["errNo"] > 0) { throw new QBException("Unable to find product when syncing orders from QB", $product["ItemRef"]["ListID"]); } /** * Else try to add in this product/variation */ if ($lastKid["nodeType"] == "productvariation") { $productFatcory =& $this->productVariationSyncAPI; } else { $productFatcory =& $this->productSyncAPI; } $productData = $productFatcory->searchNodeByDB($lastKid["response"]); $productId = $productFatcory->syncResponseRecord2Store($lastKid["response"], $productData); /** * Dam! We can't add it. Error out of here as we really needed that product for the order */ if (!isId($productId)) { throw new QBException("Unable to create product/variation record when syncing orders from QB", $lastKid["response"]); } /** * Set the reference for this product */ $referenceDataSetup = $productFatcory->referenceDataSetup; $referenceDataExternalKey = $productFatcory->referenceDataExternalKey; $this->setReferenceDataStatically($lastKid["nodeType"], $productId, $lastKid["response"], '', $referenceDataSetup, $referenceDataExternalKey); } /** * There aren't any query kids so try and find the reference for this product/variation/other product */ $checkTypes = array("product", "productvariation", "prerequisite"); $productRef = ""; $productType = ""; foreach ($checkTypes as $checkType) { $productRef = $this->accounting->getReference($checkType, '', $product["ItemRef"]["ListID"], '', false); if (is_array($productRef)) { $productType = $checkType; break; } } /** * Check to see if this is a prerequisite (shipping & tax costs) */ if ($productType == "prerequisite") { switch (isc_strtolower(trim($productRef["accountingrefvalue"]["Type"]))) { case "shipping": $cost = ($product["Quantity"] * $product["Rate"]); break; case "tax": $cost = ($product["Quantity"] * $product["Rate"]); break; } $productNodeData = array( "Type" => isc_strtolower(trim($productRef["accountingrefvalue"]["Type"])), "Cost" => $cost ); $this->setProductListId2Order($responseData["TxnID"], $product["ItemRef"]["ListID"], $productType, $productNodeData, $product); /** * We don't want to insert this in the order_products table */ continue; } /** * OK, prerequisites are done, now for the rest. If no reference then send out a query child */ if (!is_array($productRef)) { if ($this->accounting->isProductVariationShortName($product["ItemRef"]["FullName"])) { $productType = "productvariation"; } else { $productType = "product"; } $childRequestData = array( "ListID" => $product["ItemRef"]["ListID"] ); return $this->execChildService($productType, "query", $childRequestData); } /** * Must have a reference by now */ if (!is_array($productRef)) { throw new QBException("Unable to find product reference when syncing order ID: " . $this->spool["nodeId"], $responseData); } $prodNodeData = ''; if ($productType == "productvariation") { $prodNodeData = $this->productVariationAPI->get($productRef["accountingrefnodeid"]); } else { $prodNodeData = $this->productAPI->get($productRef["accountingrefnodeid"]); } /** * If no prodNodeData then no go */ if (!is_array($prodNodeData)) { throw new QBException("Unable to find " . $productType . " node data when syncing order ID: " . $this->spool["nodeId"], array("order" => $responseData, "prodNodeId" => $productRef["accountingrefnodeid"])); } /** * Lastly, save this product to our tmp cache */ $this->setProductListId2Order($responseData["TxnID"], $product["ItemRef"]["ListID"], $productType, $prodNodeData, $product); } /** * OK, now retrieve all our product from our tmp cache to build the products for this order */ $products = array(); $taxCost = $shippingCost = 0; $cacheProducts = $this->getProductListIds4Order($responseData["TxnID"]); if (!is_array($cacheProducts) || empty($cacheProducts)) { throw new QBException("Empty product cache array when syncing order ID: " . $this->spool["nodeId"], $responseData); } foreach ($cacheProducts as $productListId => $product) { /** * Add up our stored shipping and tax costs if we have any */ if ($product["productType"] == "prerequisite") { switch (isc_strtolower(trim($product["productNodeData"]["Type"]))) { case "shipping": $shippingCost = $product["productNodeData"]["Cost"]; break; case "tax": $taxCost = $product["productNodeData"]["Cost"]; break; } continue; } $prodCode = ''; $prodVariationId = 0; $prodOptions = array(); if ($product["productType"] == "productvariation") { $prodCode = $product["productNodeData"]["vcsku"]; $prodVariationId = $product["productNodeData"]["combinationid"]; $prodOptions = $product["productNodeData"]["prodvariationarray"]; } if (trim($prodCode) == '') { $prodCode = $product["productNodeData"]["prodcode"]; } $products[] = array( "product_id" => $product["productNodeData"]["productid"], "product_name" => $product["productNodeData"]["prodname"], "product_code" => $prodCode, "quantity" => max(1, $product["productResponse"]["Quantity"]), "product_price" => $product["productResponse"]["Rate"], "original_price" => $product["productResponse"]["Rate"], "variation_id" => $prodVariationId, "options" => $prodOptions ); /** * Check to see if this is an existing product in an already existing order */ if (is_array($nodeData) && isset($nodeData["products"]) && is_array($nodeData["products"])) { foreach ($nodeData["products"] as $existingProduct) { if ($existingProduct["productid"] == $product["productNodeData"]["productid"] && isset($existingProduct["prodorderid"])) { $products[count($products)-1]["existing_order_product"] = $existingProduct["prodorderid"]; } } } /** * Add up our sub total */ $productSubTotal += $product["productResponse"]["Amount"]; } /** * OK, we have all the products and the customer details. Now for the actual order details */ $savedata = array( "ordcustid" => $customerId, "subtotal_ex_tax" => $productSubTotal, "total_tax" => $taxCost, "shipping_cost_ex_tax" => $shippingCost, "total_inc_tax" => ($productSubTotal + $taxCost + $shippingCost), "products" => $products ); if (isset($responseData["Memo"])) { $savedata["ordnotes"] = $responseData["Memo"]; } /** * Add in the addresses */ $addressMap = array( "shipaddress1" => "Addr1", "shipaddress2" => "Addr2", "shipcity" => "City", "shipstate" => "State", "shipzip" => "PostalCode", "shipcountry" => "Country" ); foreach (array("BillAddress", "ShipAddress") as $addressType) { if (!array_key_exists($addressType, $responseData) || !is_array($responseData[$addressType])) { $responseData[$addressType] = array(); } if ($addressType == "BillAddress") { $addressKey = "billingaddress"; } else { $addressKey = "shippingaddress"; } $savedata[$addressKey] = array(); foreach ($addressMap as $columnName => $refKey) { if (!isset($responseData[$addressType][$refKey]) && !is_array($nodeData)) { $responseData[$addressType][$refKey] = ''; } if (isset($responseData[$addressType][$refKey])) { $savedata[$addressKey][$columnName] = $responseData[$addressType][$refKey]; } } /** * Find the country and state IDs */ $countryId = $this->getCountryId(@$savedata[$addressKey]["shipcountry"], $properCountryName); $stateId = ''; if (isId($countryId) && trim(@$savedata[$addressKey]["shipstate"]) !== '') { $savedata[$addressKey]["shipcountry"] = $properCountryName; $stateId = $this->getStateId($savedata[$addressKey]["shipstate"], $countryId, $properStateName); if (!isId($stateId)) { $stateId = ''; } else if (trim($properStateName) !== '') { $savedata[$addressKey]["shipstate"] = $properStateName; } } else { $countryId = ''; } if (is_array($nodeData) || !isId($stateId)) { $savedata[$addressKey]["shipstateid"] = $stateId; } if (is_array($nodeData) || !isId($countryId)) { $savedata[$addressKey]["shipcountryid"] = $countryId; } /** * Fill in the name. Use whatever QB gave us regardless */ $customerName = @$responseData["CustomerRef"]["FullName"]; if ($this->accounting->isCustomerShortName($customerName)) { $tmpName = $this->accounting->qbCustomerShortName2CustomerNameId($customerName); if (is_array($tmpName) && array_key_exists("customername", $tmpName)) { $customerName = $tmpName["customername"]; } } else if ($this->accounting->isCustomerGuestShortName($customerName)) { $tmpName = $this->accounting->qbCustomerGuestShortName2CustomerGuestNameId($customerName); if (is_array($tmpName) && array_key_exists("customerguestname", $tmpName)) { $customerName = $tmpName["customerguestname"]; } } $nameParts = explode(" ", $customerName); if (count($nameParts) > 2) { $firstName = implode(" ", array_slice($nameParts, 0, count($nameParts)-1)); $lastName = $nameParts[count($nameParts)-1]; } else if (count($nameParts) == 1) { $firstName = $nameParts[0]; $lastName = ""; } else { $firstName = $nameParts[0]; $lastName = $nameParts[1]; } $savedata[$addressKey]["shipfirstname"] = $firstName; $savedata[$addressKey]["shiplastname"] = $lastName; /** * Set something to each field if it is NULL as the database can't handle NULL values for this schema */ foreach ($savedata[$addressKey] as $addKey => $addVal) { if (is_null($addVal)) { $savedata[$addressKey][$addKey] = ''; } } } /** * If we don't have a $nodeData then we can still fill in some blanks */ if (!is_array($nodeData)) { $savedata["ordtoken"] = GenerateOrderToken(); $savedata["ordstatus"] = ORDER_STATUS_COMPLETED; $savedata["orderpaymentmodule"] = "manual"; $savedata["orderpaymentmethod"] = GetLang("QuickBooksDefaultPaymentName"); $savedata["total_inc_tax"] = $savedata["totalcost"]; $savedata["handling_cost_ex_tax"] = 0; $savedata["handling_cost_inc_tax"] = 0; if (isset($savedata["billingaddress"]["shipcountry"])) { $savedata["ordgeoipcountry"] = $savedata["billingaddress"]["shipcountry"]; $savedata["ordgeoipcountrycode"] = GetCountryISO2ByName($savedata["billingaddress"]["shipcountry"]); } if (is_array($customerNodeData)) { $savedata["ordbillemail"] = $customerNodeData["custconemail"]; $savedata["ordbillphone"] = $customerNodeData["custconphone"]; $savedata["ordshipemail"] = $customerNodeData["custconemail"]; $savedata["ordshipphone"] = $customerNodeData["custconphone"]; } } else { $savedata["orderid"] = $nodeData["orderid"]; } /** * Alright, we have EVERYTHING, now create/update EVERYTHING */ $orderId = false; if (is_array($nodeData)) { /** * Reset the inventory levels before we update it */ if ($this->accounting->getValue("invlevels") !== ACCOUNTING_QUICKBOOKS_TYPE_QUICKBOOKS) { UpdateInventoryOnReturn($savedata["orderid"]); /* /lib/orders.php */ } if ($this->entityAPI->edit($savedata) !== false) { $orderId = $savedata["orderid"]; } /** * Now sync back the inventory levels */ if ($this->accounting->getValue("invlevels") !== ACCOUNTING_QUICKBOOKS_TYPE_QUICKBOOKS) { DecreaseInventoryFromOrder($orderId); } } else { $orderId = $this->entityAPI->add($savedata); /** * Sync up the inventory levels as each order is marked as completed */ if ($this->accounting->getValue("invlevels") !== ACCOUNTING_QUICKBOOKS_TYPE_QUICKBOOKS) { DecreaseInventoryFromOrder($orderId); } } if (!isId($orderId)) { $this->accounting->logError("ORDER DATA", array("SaveData" => $savedata, "NodeData" => $nodeData, "DB" => $GLOBALS["ISC_CLASS_DB"]->GetError())); throw new QBException("Cannot save order record with data from QB", array("SaveData" => $savedata, "NodeData" => $nodeData, "DB" => $GLOBALS["ISC_CLASS_DB"]->GetError())); } return $orderId; }