/
var
/
www
/
html
/
plugin-techloyce
/
Modules
/
Accountings
/
Services
/
Upload File
HOME
<?php namespace Modules\Accountings\Services; use Carbon\Carbon; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; use Modules\Accountings\Contracts\QuickBooksContract; use QuickBooksOnline\API\Data\IPPCustomer; use QuickBooksOnline\API\Data\IPPIntuitEntity; use QuickBooksOnline\API\Data\IPPInvoice; use QuickBooksOnline\API\DataService\DataService; use QuickBooksOnline\API\Exception\IdsException; use QuickBooksOnline\API\Exception\SdkException; use Modules\Accountings\Traits\Helpers; use QuickBooksOnline\API\Exception\ServiceException; use QuickBooksOnline\API\Facades\Customer; use QuickBooksOnline\API\Facades\Invoice; use QuickBooksOnline\API\Facades\Item; use QuickBooksOnline\API\Facades\Line; use QuickBooksOnline\API\Facades\Payment; /** * Class QuickBooksService * * @package Modules\Accountings\Services * @author Engineer Saud <saud.ahmad@techloyce.com> * @copyright 2020 Techloyce.com All rights reserved. * @since Jun 22, 2020 * @project Middleware */ class QuickBooksService implements QuickBooksContract { use Helpers; /** * Property request * * @var Request */ public $request; /** * Property response * * @var array */ public $response; /** * Property credentials * * @var array */ private $credentials; /** * QuickBooksService constructor. */ public function __construct() { $this->response = ['status' => false]; $this->credentials = config('accountings.service:QuickBooks'); } /** * Function OAuth2Config * * @param null $auth Setup company if auth available. * * @return DataService * @throws SdkException */ private function OAuth2Config($auth = null) { $config = [ 'auth_mode' => 'oauth2', 'ClientID' => $this->credentials['client_id'], 'ClientSecret' => $this->credentials['client_secret'], 'RedirectURI' => $this->credentials['redirect_uri'], 'scope' => $this->credentials['scope'], 'baseUrl' => "development" ]; if ($auth) { /* Setup realmId, refresh and access token if available. */ if ($auth->destination_access_token) { $config['accessTokenKey'] = $auth->destination_access_token; } if ($auth->destination_refresh_token) { $config['refreshTokenKey'] = $auth->destination_refresh_token; } if (!empty($auth->destination_extra_information['realmId'])) { $config['QBORealmID'] = $auth->destination_extra_information['realmId']; } } // Initialize dataService object return DataService::Configure($config); } /** * @inheritDoc */ public function authorize(Request $request) { // Request data from source $this->request = $request; // OAuth2 configuration $dataService = $this->OAuth2Config(); // Get OAuth2 login data $OAuth2LoginHelper = $dataService->getOAuth2LoginHelper(); // Destination url $authorizationCodeUrl = $OAuth2LoginHelper->getAuthorizationCodeURL(); // Get the state generated for you and store it to the session. $_SESSION['oauth2state'] = $OAuth2LoginHelper->getState(); return redirect()->away($authorizationCodeUrl); } /** * @inheritDoc */ public function authorize_callback(Request $request) { // Destination callback request data. $this->request = $request; // If request has code then proceed request if ($request->code) { // Initialize dataService object $dataService = $this->OAuth2Config(); $OAuth2LoginHelper = $dataService->getOAuth2LoginHelper(); // Fetch access token object $accessToken = $OAuth2LoginHelper ->exchangeAuthorizationCodeForToken( $request->code, $request->realmId ); $dataService->updateOAuth2Token($accessToken); $accessTokenValue = $accessToken->getAccessToken(); $refreshTokenValue = $accessToken->getRefreshToken(); $this->response['status'] = true; $this->response['access_token'] = $accessTokenValue; $this->response['refresh_token'] = $refreshTokenValue; $this->response['expires_in'] = $accessToken->getAccessTokenExpiresAt(); $this->response['extra_information'] = array( 'realmId' => $request->realmId ); } else { $this->response['status'] = false; } return $this; } /** * Function get_access_token * Get valid access toke * If expired then updated token returned * * @param $auth * * @return array * @throws SdkException */ private function get_refresh_data($auth) { $dataService = $this->OAuth2Config($auth); $OAuth2LoginHelper = $dataService->getOAuth2LoginHelper(); $data = array(); if ($auth->integration_source == "QuickBooks") { $expires_in = $auth->source_expires_in; if (Carbon::createFromTimestamp($expires_in)->subMinutes(5)->lte(Carbon::now())) { try { $refreshToken = $auth->source_refresh_token; $newAccessToken = $OAuth2LoginHelper ->refreshAccessTokenWithRefreshToken($refreshToken); $auth->source_access_token = $newAccessToken->getAccessToken(); $auth->source_refresh_token = $newAccessToken->getRefreshToken(); $auth->source_expires_in = $newAccessToken->getAccessTokenExpiresAt(); $auth->source_token_updated_at = Carbon::now()->format("Y-m-d H:i:s"); $auth->save(); $data['status'] = true; } catch (ServiceException $e) { $data['status'] = false; $data['message'] = "Authentication Failed!"; } } else { $data['status'] = true; } } elseif ($auth->integration_destination == "QuickBooks") { $expires_in = $auth->destination_expires_in; $data['realm_id'] = $auth->destination_extra_information['realmId']; if (Carbon::createFromTimestamp($expires_in)->subMinutes(5)->lte(Carbon::now())) { try { $refreshToken = $auth->destination_refresh_token; $newAccessToken = $OAuth2LoginHelper ->refreshAccessTokenWithRefreshToken($refreshToken); $auth->destination_access_token = $newAccessToken->getAccessToken(); $auth->destination_refresh_token = $newAccessToken->getRefreshToken(); $auth->destination_expires_in = $newAccessToken->getAccessTokenExpiresAt(); $auth->destination_token_updated_at = Carbon::now()->format("Y-m-d H:i:s"); $auth->save(); $data['status'] = true; } catch (Exception $e) { $data['status'] = false; $data['message'] = "Authentication Failed: " . $e->getMessage(); } } else { $data['status'] = true; } } $data['dataService'] = $dataService; return $data; } /** * Function create_update_customer * Create QBO customer * If already available then only update. * * @param $token * @param $customer * * @return mixed * @throws SdkException */ public function create_update_customer($token, $customer) { if (!Cache::has($customer['id'] . "-updated")) { $qbo_company_data = $this->get_refresh_data($token); /* @var DataService $dataService */ $dataService = $qbo_company_data['dataService']; if ($qbo_company_data['status'] == true) { try { // Add a customer $customerData = [ "BillAddr" => [ "Line1" => $customer['billing_address_1'], "Line2" => $customer['billing_address_2'], "City" => $customer['billing_city'], "Country" => $customer['billing_country'], "CountrySubDivisionCode" => $customer['billing_state'], "PostalCode" => $customer['billing_postal_code'] ], "ShipAddr" => [ "Line1" => $customer['shipping_address_1'], "Line2" => $customer['shipping_address_2'], "City" => $customer['shipping_city'], "Country" => $customer['shipping_country'], "CountrySubDivisionCode" => $customer['shipping_state'], "PostalCode" => $customer['shipping_postal_code'] ], "Notes" => $customer['notes'], "Title" => "Mr", "GivenName" => $customer['contact_first_name'], "MiddleName" => "1B", "FamilyName" => $customer['contact_last_name'], "Suffix" => "Jr", "FullyQualifiedName" => $customer['name'], "CompanyName" => $customer['name'], "DisplayName" => $customer['name'], "PrimaryPhone" => [ "FreeFormNumber" => $customer['phone_number'] ], "PrimaryEmailAddr" => [ "Address" => $customer['email'] ] ]; $need_to_update_resource = false; if (isset($customer['resource_id'])) { try { $customerObj = $dataService->FindById( new IPPCustomer([ 'Id' => $customer['resource_id'] ]), true ); // If record found then update record data. if ($customerObj) { //Prepare update object $customerUpdate = Customer::update($customerObj, $customerData); // Send update request $customerProcessed = $dataService->Add($customerUpdate); Cache::put($customer['id'] . "-updated", "true", Carbon::now()->addMinutes(1)); } } catch (Exception $e) { Log::channel("accountings_log")->info("----------Exception-----------"); Log::channel("accountings_log")->info($e->getMessage()); Log::channel("accountings_log")->info("----------Exception-----------"); $data['status'] = false; $data['message'] = "Error Occurred: " . $e->getMessage(); } } // If record not found to update then create new one. if (empty($customerProcessed)) { do { // Make Customer object for QBO $customerCreate = Customer::create($customerData); // Add customer in QBO $customerProcessed = $dataService->Add($customerCreate); // Get QBO error if occurred. $error = $dataService->getLastError(); if (!empty($error) && $error->getHttpStatusCode() === 400 ) { if (strstr($customerData['DisplayName'], $customer['customer_number'])) { $customerData['DisplayName'] .= random_int(0, 99); } else { $customerData['DisplayName'] .= ' (' . $customer['customer_number'] . ')'; } } else { break; } // Again create with new display name if customer not created. } while (!$customerProcessed); if ($customerProcessed) { Cache::put($customer['id'] . "-updated", "true", Carbon::now()->addMinutes(1)); $need_to_update_resource = true; } } if ($customerProcessed) { $data['status'] = true; $data['need_to_update'] = $need_to_update_resource; $data['data']['resource_id'] = $customerProcessed->Id; $data['data']['contact'] = $customerProcessed; } else { $data['status'] = false; $data['message'] = "Error Occurred: QuickBooks id or customer not found"; } } catch (Exception $e) { Log::channel("accountings_log")->info("----------Exception-----------"); Log::channel("accountings_log")->info($e->getMessage()); Log::channel("accountings_log")->info("----------Exception-----------"); $data['status'] = false; $data['message'] = "Error Occurred: " . $e->getMessage(); } } else { $data['status'] = false; $data['message'] = "Authentication Failed!"; } } else { $data['status'] = false; $data['message'] = "Recently Updated"; } return $data; } /** * Function create_update_invoice * Create QBO invoice * If already available then only update. * * @param $token * @param $invoice * * @return mixed */ public function create_update_invoice($token, $invoice) { try { if (!Cache::has($invoice['id'] . "-updated")) { // Refresh tokens $qbo_company_data = $this->get_refresh_data($token); $dataService = $qbo_company_data['dataService']; // refresh status true then proceed request. if ($qbo_company_data['status'] === true) { try { $qbo_line_item_arr = []; // Minimum one line item required to create QBO invoice. if (isset($invoice['line_items'])) { foreach ($invoice['line_items'] as $key => $line_item) { $item_quantity = $line_item['quantity']; $item_rate = $line_item['price']; $qbo_item = $this->create_update_item($dataService, $line_item); // Item description if (!empty($line_item['product_description'])) $item_description = $line_item['product_description']; elseif (!empty($line_item['product_name'])) $item_description = $line_item['product_name']; elseif (!empty($line_item['plan_description'])) $item_description = $line_item['plan_description']; elseif (!empty($line_item['plan_name'])) $item_description = $line_item['plan_name']; else $item_description = "Unamed"; // Make QBO line item $line_data = [ "Id" => $key, "LineNum" => $key, "Description" => $item_description, "Amount" => $item_rate, "DetailType" => "SalesItemLineDetail", "SalesItemLineDetail" => [ "UnitPrice" => $item_rate / $item_quantity, "Qty" => $item_quantity, "DiscountAmt" => $line_item['discount'], "TaxCodeRef" => [ "value" => "TAX" ] ] ]; if (!empty($qbo_item)) { $line_data['SalesItemLineDetail']['ItemRef'] = [ "value" => $qbo_item->Id ]; } $qbo_line_item_arr[] = Line::create($line_data); } } /* Referenced Customer */ $qbo_customer = $this->create_update_customer($token, $invoice['customer']); if (isset($qbo_customer['status']) && $qbo_customer['status'] === true ) { $sourceServiceObj = $this->resolveSourcesServiceDepedency( $token->integration_source, $token->integration_destination ); if (isset($qbo_customer['need_to_update']) && $qbo_customer['need_to_update'] == true ) { $sourceServiceObj->update_customer_resource_id( $invoice['customer']['id'], $token, $qbo_customer ); } } else { $data['status'] = $qbo_customer['status']; if (isset($qbo_customer['message'])) $data['message'] = $qbo_customer['message']; $data['extra-information'] = "Customer not found or could not be created!"; return $data; } $due_date = Carbon::parse($invoice['due_date']); $date = Carbon::parse($invoice['date']); //Add a new Invoice $invoice_data = [ 'sparse' => true, "Line" => $qbo_line_item_arr, "DocNumber" => $invoice['number'], "DueDate" => $due_date->format('Y-m-d'), "TxnDate" => $date->format('Y-m-d'), "MetaData" => [ "CreateTime" => $date->format('Y-m-d') ], "CustomerRef" => [ "value" => $qbo_customer['data']['resource_id'] ], "BillEmail" => [ "Address" => $qbo_customer['data']['contact']->PrimaryEmailAddr->Address ], "BillEmailCc" => [ "Address" => "" ], "BillEmailBcc" => [ "Address" => "" ], "ShipAddr" => [ "Line1" => "", ] ]; $need_to_update_resource = false; $is_processed = false; if (isset($invoice['resource_id'])) { $qbo_invoice = $dataService->FindById(new IPPInvoice([ 'Id' => $invoice['resource_id'] ]), true); if (!empty($qbo_invoice)) { // Add sync token in request object. $invoice_data['SyncToken'] = $qbo_invoice->SyncToken; // Request to update invoice. $invoice_processed = $dataService->Add( Invoice::update($qbo_invoice, $invoice_data) ); $qbo_id = $invoice_processed->Id; Cache::put( $invoice['id'] . "-updated", "true", Carbon::now()->addMinutes(1) ); // Invoice updated, no any need to process (create) $is_processed = true; } } // If invoice is not processed (update) then create on QBO side. if (!$is_processed) { // Add invoice in QBO $invoice_processed = $dataService->Add( Invoice::create($invoice_data) ); // Id of QBO newly created invoice $qbo_id = $invoice_processed->Id; Cache::put($invoice['id'] . "-updated", "true", Carbon::now()->addMinutes(1)); $need_to_update_resource = true; } $data['status'] = true; $data['need_to_update'] = $need_to_update_resource; $data['data']['resource_id'] = $qbo_id; $data['data']['invoice'] = $invoice_processed; return $data; } catch (Exception $e) { Log::channel("accountings_log")->info("----------Exception-----------"); Log::channel("accountings_log")->info($e->getMessage()); Log::channel("accountings_log")->info("----------Exception-----------"); $data['status'] = false; $data['message'] = $e->getMessage(); } } else { Log::channel("accountings_log")->info("----------Error-----------"); Log::channel("accountings_log")->info('Access token not refreshed!'); Log::channel("accountings_log")->info("----------Error-----------"); $data['status'] = false; $data['message'] = "Authentication Failed!"; } } else { $data['status'] = false; $data['message'] = "Recently Updated"; } } catch (Exception $e) { $data['status'] = false; $data['message'] = $e->getMessage(); Log::channel("accountings_log")->info("----------Exception-----------"); Log::channel("accountings_log")->info($e->getMessage()); Log::channel("accountings_log")->info("----------Exception-----------"); } return $data; } /** * Function create_update_item * * @param DataService $dataService * @param $item * * @return Exception|IPPIntuitEntity|string|null * @throws IdsException * @throws Exception */ private function create_update_item(DataService $dataService, $item) { $qbo_item = null; if ($item['account_code']) { $item_data = [ "Name" => $item['name'], "Description" => $item['description'], "Active" => true, "FullyQualifiedName" => $item['name'], "Taxable" => true, "UnitPrice" => $item['price'], "Type" => "Service", "IncomeAccountRef" => [ "value" => $item['account_code'] ] ]; // Query charge service $qbo_item = $dataService->Query("select * from Item where name = '" . $item['name'] ."'"); // Update item if already exist. if (!empty($qbo_item)) { $item_obj = Item::update(end($qbo_item), $item_data); } else { $item_obj = Item::create($item_data); } // Create / update in QuickBooks $qbo_item = $dataService->Add($item_obj); $error = $dataService->getLastError(); if (!empty($error)) { Log::channel("accountings_log")->info("ItemError: " . $error->getIntuitErrorDetail()); } } return $qbo_item; } /** * Function get_account_codes * Fetch all account and send back to resource service. * * @param $token * @param $request * * @return mixed * @throws SdkException */ public function get_account_codes($token, $request) { // Refresh tokens $qbo_company_data = $this->get_refresh_data($token); $dataService = $qbo_company_data['dataService']; if ($qbo_company_data['status'] === true) { try { // Generate the query. if ($request->Type) { // Prepare string to acceptable for the QBO query. $accountTypes = '\''.implode("','", explode(',', $request->Type)).'\''; $query = "select * from Account where AccountType in ($accountTypes)"; } else { $query = 'select * from Account'; } // Send query request. $account_codes = $dataService->Query($query); // Makeup the data. $accounts = array(); if ($account_codes) { foreach ($account_codes as $key => $account) { $accounts[$key]["id"] = $account->Id; $accounts[$key]["name"] = $account->Name; $accounts[$key]["code"] = $account->Id; $accounts[$key]["type"] = $account->Classification; $accounts[$key]["status"] = $account->status; } } $data['status'] = true; $data['data'] = $accounts; } catch (Exception $e) { $data['status'] = false; $data['message'] = "Error Occurred"; Log::channel("accountings_log") ->info('Error (in function "get_account_codes"): '.$e->getMessage()); } } else { $data['status'] = false; $data['message'] = "Authentication Failed!"; } return $data; } /** * Function save_invoice_payments * Make a payment in QBO against invoice. * * @param mixed $token Authentication user. * @param int $invoice_id QuickBooks invoice id. * @param object $invoice QuickBooks invoice response object. * @param array $source_payments Array of payments from source. * * @return mixed */ public function save_invoice_payments($token, $invoice_id, $invoice, $source_payments) { try { // Refresh tokens $qbo_company_data = $this->get_refresh_data($token); /* @var DataService $dataService */ $dataService = $qbo_company_data['dataService']; if ($qbo_company_data['status'] === true) { $data['status'] = true; // Minimum one source payment required. if (is_array($source_payments) && count($source_payments) > 0 ) { // Source configurations. $sourceServiceObj = $this->resolveSourcesServiceDepedency( $token->integration_source, $token->integration_destination ); // Send request against each payment. foreach ($source_payments as $payment) { if (!Cache::has($payment['id'] . "-updated")) { $payment_data = [ 'TotalAmt' => $payment['amount'], 'CustomerRef' => [ 'value' => $invoice->CustomerRef ], 'Line' => [ [ 'Amount' => $payment['amount'], 'LinkedTxn' => [ [ 'TxnId' => $invoice->Id, 'TxnType' => 'Invoice' ] ] ] ], 'TxnDate' => Carbon::now()->format("Y-m-d"), 'PaymentRefNum' => $payment['reference'], 'PrivateNote' => $payment['description'], ]; if (isset($payment['account_code'])) { $query = 'select * from Account where Id = "' . $payment['account_code'] . '"'; $accounts = $dataService->Query($query); if (!empty($accounts)) { $payment_data += [ 'DepositToAccountRef' => [ 'value' => end($accounts)->Id ] ]; } } // Create a new payment transaction if no one already created. if (!isset($payment['resource_id'])) { $payment_obj = Payment::create($payment_data); $qbo_payment = $dataService->Add($payment_obj); if ($qbo_payment) { $sourceServiceObj->update_transaction_resource_id( $payment['id'], $token, $qbo_payment->Id ); Cache::put( $payment['id'] . "-updated", "true", Carbon::now()->addMinutes(1) ); $data['status'] = true; } } } } } } else { $data['status'] = false; $data['message'] = "Authentication Failed!"; } } catch (Exception $e) { $data['status'] = false; $data['message'] = $e->getMessage(); Log::channel("accountings_log")->info("----------Exception-----------"); Log::channel("accountings_log")->info($e->getMessage()); Log::channel("accountings_log")->info("----------Exception-----------"); } return $data; } }