From 5beaa8078683f54a3aa8790d60bf05c3ced70b9d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Sep 2024 12:54:14 +1000 Subject: [PATCH 01/11] Fixes for product sales export --- app/Export/CSV/ProductSalesExport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Export/CSV/ProductSalesExport.php b/app/Export/CSV/ProductSalesExport.php index a8e2082d183b..77f33108e2c6 100644 --- a/app/Export/CSV/ProductSalesExport.php +++ b/app/Export/CSV/ProductSalesExport.php @@ -23,7 +23,7 @@ use League\Csv\Writer; class ProductSalesExport extends BaseExport { - public string $date_key = 'created_at'; + public string $date_key = 'date'; /** @var Collection<\App\Models\Product> $products*/ protected Collection $products; From 1e5faf2ecd5b06d57e27f2b130390abf54ff190a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Sep 2024 13:19:01 +1000 Subject: [PATCH 02/11] Fixes for duplicate mollie requests --- app/Import/Transformer/Csv/InvoiceTransformer.php | 1 + app/PaymentDrivers/Mollie/IDEAL.php | 13 +++++++++++++ app/PaymentDrivers/MolliePaymentDriver.php | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/Import/Transformer/Csv/InvoiceTransformer.php b/app/Import/Transformer/Csv/InvoiceTransformer.php index 50a85a0922ca..bd7d2543077e 100644 --- a/app/Import/Transformer/Csv/InvoiceTransformer.php +++ b/app/Import/Transformer/Csv/InvoiceTransformer.php @@ -36,6 +36,7 @@ class InvoiceTransformer extends BaseTransformer $invoiceStatusMap = [ 'sent' => Invoice::STATUS_SENT, 'draft' => Invoice::STATUS_DRAFT, + 'paid' => Invoice::STATUS_PAID, ]; $transformed = [ diff --git a/app/PaymentDrivers/Mollie/IDEAL.php b/app/PaymentDrivers/Mollie/IDEAL.php index 134853a6fe04..5645a23e786f 100644 --- a/app/PaymentDrivers/Mollie/IDEAL.php +++ b/app/PaymentDrivers/Mollie/IDEAL.php @@ -176,6 +176,19 @@ class IDEAL implements MethodInterface, LivewireMethodInterface */ public function processSuccessfulPayment(\Mollie\Api\Resources\Payment $payment, string $status = 'paid'): RedirectResponse { + $p = \App\Models\Payment::query() + ->withTrashed() + ->where('company_id', $this->mollie->client->company_id) + ->where('transaction_reference', $payment->id) + ->first(); + + if($p) { + $p->status_id = Payment::STATUS_COMPLETED; + $p->save(); + + return redirect()->route('client.payments.show', ['payment' => $p->hashed_id]); + } + $data = [ 'gateway_type_id' => GatewayType::IDEAL, 'amount' => array_sum(array_column($this->mollie->payment_hash->invoices(), 'amount')) + $this->mollie->payment_hash->fee_total, diff --git a/app/PaymentDrivers/MolliePaymentDriver.php b/app/PaymentDrivers/MolliePaymentDriver.php index 8f98a2faebd1..33e7fbb83c33 100644 --- a/app/PaymentDrivers/MolliePaymentDriver.php +++ b/app/PaymentDrivers/MolliePaymentDriver.php @@ -287,7 +287,7 @@ class MolliePaymentDriver extends BaseDriver { // Allow app to catch up with webhook request. // sleep(4); - usleep(rand(2800000, 4000000)); + usleep(rand(1500000, 4000000)); $validator = Validator::make($request->all(), [ 'id' => ['required', 'starts_with:tr'], From 6d0c008b6947a29aa22fd6e1e973f043a85b085c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Sep 2024 13:48:28 +1000 Subject: [PATCH 03/11] Updates for CBA Powerboard --- .../Controllers/CompanyGatewayController.php | 6 +++++ .../CBAPowerBoard/CreditCard.php | 24 +++++++++++++++++-- .../2024_09_06_042040_cba_powerboard.php | 10 ++++++++ database/seeders/PaymentLibrariesSeeder.php | 2 +- .../assets/powerboard-credit-card-f4852d3b.js | 13 ++++++++++ .../assets/powerboard-credit-card-f5f23291.js | 13 ---------- public/build/manifest.json | 2 +- .../payments/powerboard-credit-card.js | 10 ++++++++ .../powerboard/credit_card/pay.blade.php | 1 + .../credit_card/pay_livewire.blade.php | 1 + 10 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 public/build/assets/powerboard-credit-card-f4852d3b.js delete mode 100644 public/build/assets/powerboard-credit-card-f5f23291.js diff --git a/app/Http/Controllers/CompanyGatewayController.php b/app/Http/Controllers/CompanyGatewayController.php index 5d25533decc2..4d1ce5f8e301 100644 --- a/app/Http/Controllers/CompanyGatewayController.php +++ b/app/Http/Controllers/CompanyGatewayController.php @@ -251,6 +251,12 @@ class CompanyGatewayController extends BaseController $company_gateway->driver()->init()->settings()->updateSettings(); })->afterResponse(); + $config = $company_gateway->getConfig(); + $config->visa = true; + $config->mastercard = true; + $company_gateway->setConfig($config); + $company_gateway->save(); + break; default: diff --git a/app/PaymentDrivers/CBAPowerBoard/CreditCard.php b/app/PaymentDrivers/CBAPowerBoard/CreditCard.php index 512e7cb2f6c6..fa7141f08dee 100644 --- a/app/PaymentDrivers/CBAPowerBoard/CreditCard.php +++ b/app/PaymentDrivers/CBAPowerBoard/CreditCard.php @@ -218,8 +218,27 @@ class CreditCard implements LivewireMethodInterface { $this->powerboard->init(); - // if(!isset($this->cba_gateway->verification_status) || $this->cba_gateway->verification_status != "completed") - // throw new PaymentFailed("This payment method is not configured as yet. Reference Powerboard portal for further information", 400); + $available_cards = [ + "amex", + "ausbc", + "discover", + "japcb", + "laser", + "mastercard", + "solo", + "visa", + "visa_white", + ]; + + $supported_cards = $this->powerboard->company_gateway->getConfig(); + + $supported_cards_array = []; + + foreach($available_cards as $card){ + if($supported_cards->{$card}){ + $supported_cards_array[] = $card; + } + } $merge = [ 'public_key' => $this->powerboard->company_gateway->getConfigField('publicKey'), @@ -227,6 +246,7 @@ class CreditCard implements LivewireMethodInterface 'gateway' => $this->powerboard, 'environment' => $this->powerboard->environment, 'gateway_id' => $this->cba_gateway->_id ?? false, + 'supported_cards' => $supported_cards_array, ]; return array_merge($data, $merge); diff --git a/database/migrations/2024_09_06_042040_cba_powerboard.php b/database/migrations/2024_09_06_042040_cba_powerboard.php index 255438b06481..fe39ea8cef77 100644 --- a/database/migrations/2024_09_06_042040_cba_powerboard.php +++ b/database/migrations/2024_09_06_042040_cba_powerboard.php @@ -22,6 +22,16 @@ return new class extends Migration $fields->secretKey = ''; $fields->testMode = false; $fields->gatewayId = ''; + $fields->amex = false; + $fields->ausbc = false; + $fields->discover = false; + $fields->japcb = false; + $fields->laser = false; + $fields->mastercard = true; + $fields->solo = false; + $fields->visa = true; + $fields->visa_white = false; + if($gateway = Gateway::find(64)){ $gateway->fields = json_encode($fields); diff --git a/database/seeders/PaymentLibrariesSeeder.php b/database/seeders/PaymentLibrariesSeeder.php index d4cf01d6966d..edd1d050eff4 100644 --- a/database/seeders/PaymentLibrariesSeeder.php +++ b/database/seeders/PaymentLibrariesSeeder.php @@ -89,7 +89,7 @@ class PaymentLibrariesSeeder extends Seeder ['id' => 61, 'name' => 'PayPal Platform', 'provider' => 'PayPal_PPCP', 'key' => '80af24a6a691230bbec33e930ab40666', 'fields' => '{"testMode":false}'], ['id' => 62, 'name' => 'BTCPay', 'provider' => 'BTCPay', 'key' => 'vpyfbmdrkqcicpkjqdusgjfluebftuva', 'fields' => '{"btcpayUrl":"", "apiKey":"", "storeId":"", "webhookSecret":""}'], ['id' => 63, 'name' => 'Rotessa', 'is_offsite' => false, 'sort_order' => 22, 'provider' => 'Rotessa', 'key' => '91be24c7b792230bced33e930ac61676', 'fields' => '{"apiKey":"", "testMode":false}'], - ['id' => 64, 'name' => 'CBA PowerBoard', 'is_offsite' => false, 'sort_order' => 26, 'provider' => 'CBAPowerBoard', 'key' => 'b67581d804dbad1743b61c57285142ad', 'fields' => '{"publicKey":"", "secretKey":"", "testMode":false, "gatewayId":""}'], + ['id' => 64, 'name' => 'CBA PowerBoard', 'is_offsite' => false, 'sort_order' => 26, 'provider' => 'CBAPowerBoard', 'key' => 'b67581d804dbad1743b61c57285142ad', 'fields' => '{"publicKey":"", "secretKey":"", "testMode":false, "gatewayId":"", "amex":false, "ausbc":false, "discover":false, "japcb":false, "laser":false, "mastercard":true, "solo":false, "visa":true, "visa_white":false}'], ['id' => 65, 'name' => 'Blockonomics', 'is_offsite' => false, 'sort_order' => 27, 'provider' => 'Blockonomics', 'key' => 'wbhf02us6owgo7p4nfjd0ymssdshks4d', 'fields' => '{"apiKey":"", "callbackSecret":""}'], ]; diff --git a/public/build/assets/powerboard-credit-card-f4852d3b.js b/public/build/assets/powerboard-credit-card-f4852d3b.js new file mode 100644 index 000000000000..da41a5aff94c --- /dev/null +++ b/public/build/assets/powerboard-credit-card-f4852d3b.js @@ -0,0 +1,13 @@ +import{i as l,w as m}from"./wait-8f4ae121.js";/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */let u=!1;function y(){const t=document.querySelector("meta[name=public_key]"),e=document.querySelector("meta[name=gateway_id]"),r=document.querySelector("meta[name=environment]"),n=document.querySelector("meta[name=supported_cards]"),o=new cba.HtmlWidget("#widget",t==null?void 0:t.content,e==null?void 0:e.content);if(o.setEnv(r==null?void 0:r.content),o.useAutoResize(),n!=null&&n.content)try{const a=JSON.parse(n.content);o.setSupportedCardIcons(a,!0)}catch{}o.interceptSubmitForm("#stepone"),o.onFinishInsert('#server-response input[name="gateway_response"]',"payment_source"),o.setFormFields(["card_name*"]),o.reload();let d=document.getElementById("pay-now");return d.disabled=!1,d.querySelector("svg").classList.add("hidden"),d.querySelector("span").classList.remove("hidden"),document.querySelector('#server-response input[name="gateway_response"]').value="",o}function g(){var t,e,r;(t=document.querySelector("#widget"))==null||t.replaceChildren(),(e=document.querySelector("#widget"))==null||e.classList.remove("hidden"),(r=document.querySelector("#widget-3dsecure"))==null||r.replaceChildren()}function s(){var o,d;if(g(),!((o=document.querySelector("meta[name=gateway_id]"))==null?void 0:o.content)){let a=document.getElementById("pay-now");a.disabled=!0,a.querySelector("svg").classList.remove("hidden"),a.querySelector("span").classList.add("hidden"),document.getElementById("errors").textContent="Gateway not found or verified",document.getElementById("errors").hidden=!1}const e=y();e.on("finish",()=>{document.getElementById("errors").hidden=!0,p()}),e.on("submit",function(a){document.getElementById("errors").hidden=!0});let r=document.getElementById("pay-now");r.addEventListener("click",()=>{const a=document.getElementById("widget");if(e.getValidationState(),!e.isValidForm()&&a.offsetParent!==null){r.disabled=!1,r.querySelector("svg").classList.add("hidden"),r.querySelector("span").classList.remove("hidden");return}r.disabled=!0,r.querySelector("svg").classList.remove("hidden"),r.querySelector("span").classList.add("hidden");let c=document.querySelector("input[name=token-billing-checkbox]:checked");c&&(document.getElementById("store_card").value=c.value),a.offsetParent!==null?document.getElementById("stepone_submit").click():document.getElementById("server-response").submit()}),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",a=>{var i;document.getElementById("widget").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value="",(i=document.querySelector("#powerboard-payment-container"))==null||i.classList.remove("hidden")}),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach(a=>a.addEventListener("click",c=>{var i;document.getElementById("widget").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=c.target.dataset.token,(i=document.querySelector("#powerboard-payment-container"))==null||i.classList.add("hidden")}));const n=document.querySelector('input[name="payment-type"]');n&&n.click(),u&&((d=document.getElementById("toggle-payment-with-credit-card"))==null||d.click())}async function p(){try{const t=await h();if(!t||!t.status||t.status==="not_authenticated"||t==="not_authenticated")throw u=!0,s(),new Error("There was an issue authenticating this payment method.");if(t.status==="authentication_not_supported"){document.querySelector('input[name="browser_details"]').value=null,document.querySelector('input[name="charge"]').value=JSON.stringify(t);let n=document.querySelector("input[name=token-billing-checkbox]:checked");return n&&(document.getElementById("store_card").value=n.value),document.getElementById("server-response").submit()}const e=new cba.Canvas3ds("#widget-3dsecure",t._3ds.token);e.load(),document.getElementById("widget").classList.add("hidden"),e.on("chargeAuthSuccess",function(n){document.querySelector('input[name="browser_details"]').value=null,document.querySelector('input[name="charge"]').value=JSON.stringify(n);let o=document.querySelector("input[name=token-billing-checkbox]:checked");o&&(document.getElementById("store_card").value=o.value),document.getElementById("server-response").submit()}),e.on("chargeAuthReject",function(n){document.getElementById("errors").textContent="Sorry, your transaction could not be processed...",document.getElementById("errors").hidden=!1,u=!0,s()}),e.load()}catch(t){const e=t.message??"Unknown error.";document.getElementById("errors").textContent=`Sorry, your transaction could not be processed... + +${e}`,document.getElementById("errors").hidden=!1,u=!0,s()}}async function h(){const t={name:navigator.userAgent.substring(0,100),java_enabled:navigator.javaEnabled()?"true":"false",language:navigator.language||navigator.userLanguage,screen_height:window.screen.height.toString(),screen_width:window.screen.width.toString(),time_zone:(new Date().getTimezoneOffset()*-1).toString(),color_depth:window.screen.colorDepth.toString()};document.querySelector('input[name="browser_details"]').value=JSON.stringify(t);const e=JSON.stringify(Object.fromEntries(new FormData(document.getElementById("server-response")))),r=document.querySelector("meta[name=payments_route]");try{const n=await fetch(r.content,{method:"POST",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest",Accept:"application/json","X-CSRF-Token":document.querySelector('meta[name="csrf-token"]').content},body:e});return n.ok?await n.json():await n.json().then(o=>{throw new Error(o.message??"Unknown error.")})}catch(n){document.getElementById("errors").textContent=`Sorry, your transaction could not be processed... + +${n.message}`,document.getElementById("errors").hidden=!1,u=!0,s()}}l()?s():m("#powerboard-credit-card-payment").then(()=>s()); diff --git a/public/build/assets/powerboard-credit-card-f5f23291.js b/public/build/assets/powerboard-credit-card-f5f23291.js deleted file mode 100644 index 4bfbe65ceab5..000000000000 --- a/public/build/assets/powerboard-credit-card-f5f23291.js +++ /dev/null @@ -1,13 +0,0 @@ -import{i as l,w as m}from"./wait-8f4ae121.js";/** - * Invoice Ninja (https://invoiceninja.com). - * - * @link https://github.com/invoiceninja/invoiceninja source repository - * - * @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com) - * - * @license https://www.elastic.co/licensing/elastic-license - */let i=!1;function y(){const n=document.querySelector("meta[name=public_key]"),t=document.querySelector("meta[name=gateway_id]"),r=document.querySelector("meta[name=environment]"),e=new cba.HtmlWidget("#widget",n==null?void 0:n.content,t==null?void 0:t.content);e.setEnv(r==null?void 0:r.content),e.useAutoResize(),e.interceptSubmitForm("#stepone"),e.onFinishInsert('#server-response input[name="gateway_response"]',"payment_source"),e.setFormFields(["card_name*"]),e.reload();let o=document.getElementById("pay-now");return o.disabled=!1,o.querySelector("svg").classList.add("hidden"),o.querySelector("span").classList.remove("hidden"),document.querySelector('#server-response input[name="gateway_response"]').value="",e}function g(){var n,t,r;(n=document.querySelector("#widget"))==null||n.replaceChildren(),(t=document.querySelector("#widget"))==null||t.classList.remove("hidden"),(r=document.querySelector("#widget-3dsecure"))==null||r.replaceChildren()}function a(){var o,u;if(g(),!((o=document.querySelector("meta[name=gateway_id]"))==null?void 0:o.content)){let d=document.getElementById("pay-now");d.disabled=!0,d.querySelector("svg").classList.remove("hidden"),d.querySelector("span").classList.add("hidden"),document.getElementById("errors").textContent="Gateway not found or verified",document.getElementById("errors").hidden=!1}const t=y();t.on("finish",()=>{document.getElementById("errors").hidden=!0,p()}),t.on("submit",function(d){document.getElementById("errors").hidden=!0});let r=document.getElementById("pay-now");r.addEventListener("click",()=>{const d=document.getElementById("widget");if(t.getValidationState(),!t.isValidForm()&&d.offsetParent!==null){r.disabled=!1,r.querySelector("svg").classList.add("hidden"),r.querySelector("span").classList.remove("hidden");return}r.disabled=!0,r.querySelector("svg").classList.remove("hidden"),r.querySelector("span").classList.add("hidden");let s=document.querySelector("input[name=token-billing-checkbox]:checked");s&&(document.getElementById("store_card").value=s.value),d.offsetParent!==null?document.getElementById("stepone_submit").click():document.getElementById("server-response").submit()}),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",d=>{var c;document.getElementById("widget").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value="",(c=document.querySelector("#powerboard-payment-container"))==null||c.classList.remove("hidden")}),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach(d=>d.addEventListener("click",s=>{var c;document.getElementById("widget").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=s.target.dataset.token,(c=document.querySelector("#powerboard-payment-container"))==null||c.classList.add("hidden")}));const e=document.querySelector('input[name="payment-type"]');e&&e.click(),i&&((u=document.getElementById("toggle-payment-with-credit-card"))==null||u.click())}async function p(){try{const n=await h();if(!n||!n.status||n.status==="not_authenticated"||n==="not_authenticated")throw i=!0,a(),new Error("There was an issue authenticating this payment method.");if(n.status==="authentication_not_supported"){document.querySelector('input[name="browser_details"]').value=null,document.querySelector('input[name="charge"]').value=JSON.stringify(n);let e=document.querySelector("input[name=token-billing-checkbox]:checked");return e&&(document.getElementById("store_card").value=e.value),document.getElementById("server-response").submit()}const t=new cba.Canvas3ds("#widget-3dsecure",n._3ds.token);t.load(),document.getElementById("widget").classList.add("hidden"),t.on("chargeAuthSuccess",function(e){document.querySelector('input[name="browser_details"]').value=null,document.querySelector('input[name="charge"]').value=JSON.stringify(e);let o=document.querySelector("input[name=token-billing-checkbox]:checked");o&&(document.getElementById("store_card").value=o.value),document.getElementById("server-response").submit()}),t.on("chargeAuthReject",function(e){document.getElementById("errors").textContent="Sorry, your transaction could not be processed...",document.getElementById("errors").hidden=!1,i=!0,a()}),t.load()}catch(n){const t=n.message??"Unknown error.";document.getElementById("errors").textContent=`Sorry, your transaction could not be processed... - -${t}`,document.getElementById("errors").hidden=!1,i=!0,a()}}async function h(){const n={name:navigator.userAgent.substring(0,100),java_enabled:navigator.javaEnabled()?"true":"false",language:navigator.language||navigator.userLanguage,screen_height:window.screen.height.toString(),screen_width:window.screen.width.toString(),time_zone:(new Date().getTimezoneOffset()*-1).toString(),color_depth:window.screen.colorDepth.toString()};document.querySelector('input[name="browser_details"]').value=JSON.stringify(n);const t=JSON.stringify(Object.fromEntries(new FormData(document.getElementById("server-response")))),r=document.querySelector("meta[name=payments_route]");try{const e=await fetch(r.content,{method:"POST",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest",Accept:"application/json","X-CSRF-Token":document.querySelector('meta[name="csrf-token"]').content},body:t});return e.ok?await e.json():await e.json().then(o=>{throw new Error(o.message??"Unknown error.")})}catch(e){document.getElementById("errors").textContent=`Sorry, your transaction could not be processed... - -${e.message}`,document.getElementById("errors").hidden=!1,i=!0,a()}}l()?a():m("#powerboard-credit-card-payment").then(()=>a()); diff --git a/public/build/manifest.json b/public/build/manifest.json index 971803657672..46e006a2c368 100644 --- a/public/build/manifest.json +++ b/public/build/manifest.json @@ -158,7 +158,7 @@ "src": "resources/js/clients/payments/paytrace-credit-card.js" }, "resources/js/clients/payments/powerboard-credit-card.js": { - "file": "assets/powerboard-credit-card-f5f23291.js", + "file": "assets/powerboard-credit-card-f4852d3b.js", "imports": [ "_wait-8f4ae121.js" ], diff --git a/resources/js/clients/payments/powerboard-credit-card.js b/resources/js/clients/payments/powerboard-credit-card.js index f18bd63b8813..cadd48372334 100644 --- a/resources/js/clients/payments/powerboard-credit-card.js +++ b/resources/js/clients/payments/powerboard-credit-card.js @@ -16,6 +16,7 @@ function setup() { const publicKey = document.querySelector('meta[name=public_key]'); const gatewayId = document.querySelector('meta[name=gateway_id]'); const env = document.querySelector('meta[name=environment]'); + const supportedCards = document.querySelector('meta[name=supported_cards]'); const widget = new cba.HtmlWidget( '#widget', @@ -25,6 +26,15 @@ function setup() { widget.setEnv(env?.content); widget.useAutoResize(); + + if (supportedCards?.content) { + try { + const supportedCardsArray = JSON.parse(supportedCards.content); + widget.setSupportedCardIcons(supportedCardsArray, true); + } catch (error) { + } + } + widget.interceptSubmitForm('#stepone'); widget.onFinishInsert( '#server-response input[name="gateway_response"]', diff --git a/resources/views/portal/ninja2020/gateways/powerboard/credit_card/pay.blade.php b/resources/views/portal/ninja2020/gateways/powerboard/credit_card/pay.blade.php index 8f0f1996f7f2..132fdd199180 100644 --- a/resources/views/portal/ninja2020/gateways/powerboard/credit_card/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/powerboard/credit_card/pay.blade.php @@ -6,6 +6,7 @@ + @endsection @section('gateway_content') diff --git a/resources/views/portal/ninja2020/gateways/powerboard/credit_card/pay_livewire.blade.php b/resources/views/portal/ninja2020/gateways/powerboard/credit_card/pay_livewire.blade.php index 5a4e7e440f40..d0215dab2a31 100644 --- a/resources/views/portal/ninja2020/gateways/powerboard/credit_card/pay_livewire.blade.php +++ b/resources/views/portal/ninja2020/gateways/powerboard/credit_card/pay_livewire.blade.php @@ -4,6 +4,7 @@ +
From 5bb049df3430c3f4f960c3e226a20d273134efa7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Sep 2024 13:54:06 +1000 Subject: [PATCH 04/11] Fixes for 429 --- app/Http/Requests/Invoice/StoreInvoiceRequest.php | 6 ++++-- app/Http/Requests/Payment/StorePaymentRequest.php | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Http/Requests/Invoice/StoreInvoiceRequest.php b/app/Http/Requests/Invoice/StoreInvoiceRequest.php index 4617dd77340a..fecc11b1bbe9 100644 --- a/app/Http/Requests/Invoice/StoreInvoiceRequest.php +++ b/app/Http/Requests/Invoice/StoreInvoiceRequest.php @@ -93,11 +93,13 @@ class StoreInvoiceRequest extends Request /** @var \App\Models\User $user */ $user = auth()->user(); - if(\Illuminate\Support\Facades\Cache::has($this->ip()."|INVOICE|".$this->input('client_id', '')."|".$user->company()->company_key)) { + $client_id = is_string($this->input('client_id', '')) ? $this->input('client_id') : ''; + + if(\Illuminate\Support\Facades\Cache::has($this->ip()."|INVOICE|".$client_id."|".$user->company()->company_key)) { usleep(200000); } - \Illuminate\Support\Facades\Cache::put($this->ip()."|INVOICE|".$this->input('client_id', '')."|".$user->company()->company_key,1); + \Illuminate\Support\Facades\Cache::put($this->ip()."|INVOICE|".$client_id."|".$user->company()->company_key,1); $input = $this->all(); diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index b74c5c09445f..0387396baab6 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -80,11 +80,13 @@ class StorePaymentRequest extends Request /** @var \App\Models\User $user */ $user = auth()->user(); - if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key)) { + $client_id = is_string($this->input('client_id', '')) ? $this->input('client_id') : ''; + + if(\Illuminate\Support\Facades\Cache::has($this->ip()."|".$this->input('amount', 0)."|".$client_id."|".$user->company()->company_key)) { throw new DuplicatePaymentException('Duplicate request.', 429); } - \Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('amount', 0)."|".$this->input('client_id', '')."|".$user->company()->company_key), true, 1); + \Illuminate\Support\Facades\Cache::put(($this->ip()."|".$this->input('amount', 0)."|".$client_id."|".$user->company()->company_key), true, 1); $input = $this->all(); From 2ceaf880b8fc84ee496a52ad7687b848eb391c51 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Sep 2024 14:35:33 +1000 Subject: [PATCH 05/11] Clean up for handling postmark email failures --- app/Jobs/Mail/NinjaMailerJob.php | 58 ++++++++++++++++++------------ app/Services/Email/Email.php | 61 ++++++++++++++------------------ 2 files changed, 63 insertions(+), 56 deletions(-) diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php index 855751670a4d..86dbe7e07ea8 100644 --- a/app/Jobs/Mail/NinjaMailerJob.php +++ b/app/Jobs/Mail/NinjaMailerJob.php @@ -11,31 +11,32 @@ namespace App\Jobs\Mail; -use App\DataMapper\Analytics\EmailFailure; -use App\DataMapper\Analytics\EmailSuccess; -use App\Events\Invoice\InvoiceWasEmailedAndFailed; -use App\Events\Payment\PaymentWasEmailedAndFailed; -use App\Jobs\Util\SystemLogger; -use App\Libraries\Google\Google; -use App\Libraries\MultiDB; -use App\Models\ClientContact; +use App\Models\User; +use App\Utils\Ninja; use App\Models\Company; use App\Models\Invoice; use App\Models\Payment; use App\Models\SystemLog; -use App\Models\User; -use App\Utils\Ninja; -use App\Utils\Traits\MakesHash; -use GuzzleHttp\Exception\ClientException; +use App\Libraries\MultiDB; +use App\Models\ClientContact; use Illuminate\Bus\Queueable; +use App\Jobs\Util\SystemLogger; +use App\Utils\Traits\MakesHash; +use App\Libraries\Google\Google; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Cache; +use Illuminate\Queue\SerializesModels; +use Postmark\Models\PostmarkException; +use Turbo124\Beacon\Facades\LightLogs; +use Illuminate\Queue\InteractsWithQueue; +use GuzzleHttp\Exception\ClientException; +use App\DataMapper\Analytics\EmailFailure; +use App\DataMapper\Analytics\EmailSuccess; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Mail; -use Turbo124\Beacon\Facades\LightLogs; +use App\Events\Invoice\InvoiceWasEmailedAndFailed; +use App\Events\Payment\PaymentWasEmailedAndFailed; /*Multi Mailer implemented*/ @@ -242,6 +243,21 @@ class NinjaMailerJob implements ShouldQueue } + /** + * Post mark buries the proper message in a guzzle response + * this merges a text string with a json object + * need to harvest the ->Message property using the following + */ + + if ($e instanceof PostmarkException) { //postmark specific failure + + $this->fail(); + $this->entityEmailFailed($e->getMessage()); + $this->cleanUpMailers(); + + return; + } + //only report once, not on all tries if ($this->attempts() == $this->tries) { /* If there is an entity attached to the message send a failure mailer */ @@ -249,10 +265,8 @@ class NinjaMailerJob implements ShouldQueue $this->entityEmailFailed($message); } - /* Don't send postmark failures to Sentry */ - if (Ninja::isHosted() && (!$e instanceof ClientException)) { - app('sentry')->captureException($e); - } + app('sentry')->captureException($e); + } /* Releasing immediately does not add in the backoff */ diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index 76103ddd96e5..5b477e510f56 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -11,38 +11,39 @@ namespace App\Services\Email; -use App\DataMapper\Analytics\EmailFailure; -use App\DataMapper\Analytics\EmailSuccess; -use App\Events\Invoice\InvoiceWasEmailedAndFailed; -use App\Events\Payment\PaymentWasEmailedAndFailed; -use App\Jobs\Util\SystemLogger; -use App\Libraries\Google\Google; -use App\Libraries\MultiDB; -use App\Mail\Engine\PaymentEmailEngine; +use Log; +use App\Models\User; +use App\Utils\Ninja; use App\Models\Client; -use App\Models\ClientContact; +use App\Models\Vendor; use App\Models\Company; use App\Models\Invoice; use App\Models\Payment; use App\Models\SystemLog; -use App\Models\User; -use App\Models\Vendor; -use App\Models\VendorContact; use App\Utils\HtmlEngine; -use App\Utils\Ninja; +use App\Libraries\MultiDB; +use App\Models\ClientContact; +use App\Models\VendorContact; +use Illuminate\Bus\Queueable; +use Illuminate\Mail\Mailable; +use App\Jobs\Util\SystemLogger; use App\Utils\Traits\MakesHash; use App\Utils\VendorHtmlEngine; +use App\Libraries\Google\Google; +use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Cache; +use Illuminate\Queue\SerializesModels; +use Postmark\Models\PostmarkException; +use Turbo124\Beacon\Facades\LightLogs; +use App\Mail\Engine\PaymentEmailEngine; +use Illuminate\Queue\InteractsWithQueue; use GuzzleHttp\Exception\ClientException; -use Illuminate\Bus\Queueable; +use App\DataMapper\Analytics\EmailFailure; +use App\DataMapper\Analytics\EmailSuccess; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Mail\Mailable; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\Mail; -use Log; -use Turbo124\Beacon\Facades\LightLogs; +use App\Events\Invoice\InvoiceWasEmailedAndFailed; +use App\Events\Payment\PaymentWasEmailedAndFailed; class Email implements ShouldQueue { @@ -375,16 +376,10 @@ class Email implements ShouldQueue * this merges a text string with a json object * need to harvest the ->Message property using the following */ - if ($e instanceof ClientException) { //postmark specific failure - $response = $e->getResponse(); - $message_body = json_decode($response->getBody()->getContents()); - - if ($message_body && property_exists($message_body, 'Message')) { - $message = $message_body->Message; - } + if ($e instanceof PostmarkException) { //postmark specific failure $this->fail(); - $this->entityEmailFailed($message); + $this->entityEmailFailed($e->getMessage()); $this->cleanUpMailers(); return; @@ -395,10 +390,8 @@ class Email implements ShouldQueue /* If the is an entity attached to the message send a failure mailer */ $this->entityEmailFailed($message); - /* Don't send postmark failures to Sentry */ - if (Ninja::isHosted() && (!$e instanceof ClientException)) { - app('sentry')->captureException($e); - } + app('sentry')->captureException($e); + } $this->tearDown(); @@ -985,7 +978,7 @@ class Email implements ShouldQueue * @param string $message * @return void */ - private function entityEmailFailed($message): void + private function entityEmailFailed(string $message = ''): void { $class = get_class($this->email_object->entity); From 69834c93622c6e583028110c85f4101bd6990cd7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Sep 2024 14:40:02 +1000 Subject: [PATCH 06/11] Fixes for invalid locale --- app/Models/ClientContact.php | 2 +- app/Models/VendorContact.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/ClientContact.php b/app/Models/ClientContact.php index 939dc7140d09..1df334bdf288 100644 --- a/app/Models/ClientContact.php +++ b/app/Models/ClientContact.php @@ -297,7 +297,7 @@ class ClientContact extends Authenticatable implements HasLocalePreference return $languages->first(function ($item) { return $item->id == $this->client->getSetting('language_id'); - })->locale; + })->locale ?? 'en'; } public function routeNotificationForMail($notification) diff --git a/app/Models/VendorContact.php b/app/Models/VendorContact.php index 197ebddd33b3..6b3fce06a75f 100644 --- a/app/Models/VendorContact.php +++ b/app/Models/VendorContact.php @@ -189,7 +189,7 @@ class VendorContact extends Authenticatable implements HasLocalePreference return $languages->first(function ($item) { return $item->id == $this->company->getSetting('language_id'); - })->locale; + })->locale ?? 'en'; } /** From 6afcb90ba4f4c02eec97aef1119438644b3e8f25 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Sep 2024 15:02:19 +1000 Subject: [PATCH 07/11] Prevent queries from unrelated tables --- app/Filters/QueryFilters.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Filters/QueryFilters.php b/app/Filters/QueryFilters.php index c4ace3e0c6b6..a37faf0c63bb 100644 --- a/app/Filters/QueryFilters.php +++ b/app/Filters/QueryFilters.php @@ -255,7 +255,7 @@ abstract class QueryFilters public function client_id(string $client_id = ''): Builder { - if (strlen($client_id) == 0) { + if (strlen($client_id) == 0 || !in_array('vendor_id', \Illuminate\Support\Facades\Schema::getColumnListing($this->builder->getModel()->getTable()))) { return $this->builder; } @@ -264,7 +264,7 @@ abstract class QueryFilters public function vendor_id(string $vendor_id = ''): Builder { - if (strlen($vendor_id) == 0) { + if (strlen($vendor_id) == 0 || !in_array('vendor_id', \Illuminate\Support\Facades\Schema::getColumnListing($this->builder->getModel()->getTable()))) { return $this->builder; } From 5732794681a6726617d32c40e128b83a88b28e31 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 26 Sep 2024 16:06:16 +1000 Subject: [PATCH 08/11] Fixes for inbound validation of payments --- .../Requests/Payment/StorePaymentRequest.php | 7 ++-- .../Import/Quickbooks/QuickbooksTest.php | 14 ++++---- tests/Feature/PaymentTest.php | 32 +++++++++++++++++++ 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index 0387396baab6..23eb235ffca2 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -107,7 +107,7 @@ class StorePaymentRequest extends Request $input['invoices'][$key]['invoice_id'] = $this->decodePrimaryKey($value['invoice_id']); } - if (array_key_exists('amount', $value)) { + if (array_key_exists('amount', $value) && is_numeric($value['amount'])) { $invoices_total += $value['amount']; } } @@ -121,7 +121,10 @@ class StorePaymentRequest extends Request foreach ($input['credits'] as $key => $value) { if (isset($value['credit_id']) && is_string($value['credit_id'])) { $input['credits'][$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']); - $credits_total += $value['amount']; + + if (array_key_exists('amount', $value) && is_numeric($value['amount'])) { + $credits_total += $value['amount']; + } } } } diff --git a/tests/Feature/Import/Quickbooks/QuickbooksTest.php b/tests/Feature/Import/Quickbooks/QuickbooksTest.php index 039aed0f401b..4846179ca46a 100644 --- a/tests/Feature/Import/Quickbooks/QuickbooksTest.php +++ b/tests/Feature/Import/Quickbooks/QuickbooksTest.php @@ -52,13 +52,15 @@ class QuickbooksTest extends TestCase $this->faker = \Faker\Factory::create(); } + + public function createQbProduct() { $service_product = Product::factory()->create([ 'company_id' => $this->company->id, 'user_id' => $this->company->owner()->id, 'notes' => $this->faker->sentence(), - 'product_key' => $this->faker->word(63), + 'product_key' => \Illuminate\Support\Str::random(64), 'tax_id' => 2, ]); @@ -67,7 +69,7 @@ class QuickbooksTest extends TestCase 'company_id' => $this->company->id, 'user_id' => $this->company->owner()->id, 'notes' => $this->faker->sentence(), - 'product_key' => $this->faker->word(63), + 'product_key' => \Illuminate\Support\Str::random(64), 'tax_id' => 1, ]); @@ -347,14 +349,14 @@ class QuickbooksTest extends TestCase "DueDate" => $i->due_date, "TotalAmt" => $i->amount, "DocNumber" => $i->number, - "ApplyTaxAfterDiscount" => false, + // "ApplyTaxAfterDiscount" => false, "GlobalTaxCalculation" => "TaxExcluded", // This tells QuickBooks to calculate taxes "TxnTaxDetail" => [ "UseAutomatedSalesTax" => true, - // "TxnTaxCodeRef" => [ - // "value" => "TAX" // Use the appropriate tax code for your QuickBooks account + "TxnTaxCodeRef" => [ + "value" => "SALES_TAX_STUB" // Use the appropriate tax code for your QuickBooks account // "DefaultTaxRateRef" => [ - // ] + ], ] // "Note" => $this->invoice->public_notes, ]; diff --git a/tests/Feature/PaymentTest.php b/tests/Feature/PaymentTest.php index fa028184bd07..0b44556b6aee 100644 --- a/tests/Feature/PaymentTest.php +++ b/tests/Feature/PaymentTest.php @@ -62,6 +62,38 @@ class PaymentTest extends TestCase ); } + public function testNullPaymentAmounts() + { + + $data = [ + 'amount' => "null", + 'client_id' => "null", + 'invoices' => [ + [ + 'invoice_id' => $this->invoice->hashed_id, + 'amount' => "null", + ], + ], + 'credits' => [ + [ + 'credit_id' => $this->invoice->hashed_id, + 'amount' => "null", + ], + ], + 'date' => '2020/12/11', + 'idempotency_key' => 'xx', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/payments/', $data); + + $response->assertStatus(422); + + } + + public function testIdempotencyTrigger() { From 320022155e8db9c759011f916ec7493a31ab3fce Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 29 Sep 2024 14:11:33 +1000 Subject: [PATCH 09/11] Updates for postmark webhhoks --- app/Jobs/PostMark/ProcessPostmarkWebhook.php | 2 ++ app/Jobs/RecurringInvoice/SendRecurring.php | 2 -- app/Models/Invoice.php | 2 +- composer.lock | 38 ++++++++++---------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/app/Jobs/PostMark/ProcessPostmarkWebhook.php b/app/Jobs/PostMark/ProcessPostmarkWebhook.php index 9e489bb6b6d2..8e7a3da3aeda 100644 --- a/app/Jobs/PostMark/ProcessPostmarkWebhook.php +++ b/app/Jobs/PostMark/ProcessPostmarkWebhook.php @@ -68,6 +68,8 @@ class ProcessPostmarkWebhook implements ShouldQueue return SystemLog::query() ->where('company_id', $this->invitation->company_id) ->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE) + ->where('category_id', SystemLog::CATEGORY_MAIL) + // ->where('client_id', $this->invitation->contact->client_id) ->whereJsonContains('log', ['MessageID' => $message_id]) ->orderBy('id', 'desc') ->first(); diff --git a/app/Jobs/RecurringInvoice/SendRecurring.php b/app/Jobs/RecurringInvoice/SendRecurring.php index 6874e2e980c0..f8c4531e41e3 100644 --- a/app/Jobs/RecurringInvoice/SendRecurring.php +++ b/app/Jobs/RecurringInvoice/SendRecurring.php @@ -50,8 +50,6 @@ class SendRecurring implements ShouldQueue */ public function __construct(public RecurringInvoice $recurring_invoice, public string $db = 'db-ninja-01') { - $this->recurring_invoice = $recurring_invoice; - $this->db = $db; } /** diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 1cfd1df3404e..56797bfe2ec0 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -368,7 +368,7 @@ class Invoice extends BaseModel public function activities() { - return $this->hasMany(Activity::class)->orderBy('id', 'DESC')->take(50); + return $this->hasMany(Activity::class)->where('company_id', $this->company_id)->where('client_id', $this->client_id)->orderBy('id', 'DESC')->take(50); } /** diff --git a/composer.lock b/composer.lock index 737ab9f6630d..ce5e1acd83b0 100644 --- a/composer.lock +++ b/composer.lock @@ -2854,16 +2854,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.373.0", + "version": "v0.374.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "88ee17077a2048ba0b2c637754b599be0523fe36" + "reference": "5f6060df419f4f72dcc970197f9a9b1613cecc62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/88ee17077a2048ba0b2c637754b599be0523fe36", - "reference": "88ee17077a2048ba0b2c637754b599be0523fe36", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/5f6060df419f4f72dcc970197f9a9b1613cecc62", + "reference": "5f6060df419f4f72dcc970197f9a9b1613cecc62", "shasum": "" }, "require": { @@ -2892,9 +2892,9 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.373.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.374.0" }, - "time": "2024-09-16T00:56:51+00:00" + "time": "2024-09-23T01:02:23+00:00" }, { "name": "google/auth", @@ -7874,20 +7874,22 @@ }, { "name": "nwidart/laravel-modules", - "version": "v11.1.2", + "version": "v11.1.4", "source": { "type": "git", "url": "https://github.com/nWidart/laravel-modules.git", - "reference": "d275a5b9f7c329c505480750d354a7eef69fc42a" + "reference": "fb1f6bd7b168baaa6212dee678c18fc983d47ed4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/d275a5b9f7c329c505480750d354a7eef69fc42a", - "reference": "d275a5b9f7c329c505480750d354a7eef69fc42a", + "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/fb1f6bd7b168baaa6212dee678c18fc983d47ed4", + "reference": "fb1f6bd7b168baaa6212dee678c18fc983d47ed4", "shasum": "" }, "require": { + "ext-dom": "*", "ext-json": "*", + "ext-simplexml": "*", "php": ">=8.2", "wikimedia/composer-merge-plugin": "^2.1" }, @@ -7945,7 +7947,7 @@ ], "support": { "issues": "https://github.com/nWidart/laravel-modules/issues", - "source": "https://github.com/nWidart/laravel-modules/tree/v11.1.2" + "source": "https://github.com/nWidart/laravel-modules/tree/v11.1.4" }, "funding": [ { @@ -7957,7 +7959,7 @@ "type": "github" } ], - "time": "2024-09-20T08:45:18+00:00" + "time": "2024-09-22T20:04:49+00:00" }, { "name": "nyholm/psr7", @@ -9362,16 +9364,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.30.1", + "version": "1.31.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "51b95ec8670af41009e2b2b56873bad96682413e" + "reference": "249f15fb843bf240cf058372dad29e100cee6c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e", - "reference": "51b95ec8670af41009e2b2b56873bad96682413e", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/249f15fb843bf240cf058372dad29e100cee6c17", + "reference": "249f15fb843bf240cf058372dad29e100cee6c17", "shasum": "" }, "require": { @@ -9403,9 +9405,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.31.0" }, - "time": "2024-09-07T20:13:05+00:00" + "time": "2024-09-22T11:32:18+00:00" }, { "name": "pragmarx/google2fa", From 498fb51faa2c91bf4b8971a6181374ede6c30ee0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 29 Sep 2024 14:12:54 +1000 Subject: [PATCH 10/11] Minor updates --- .../Requests/Payment/UpdatePaymentRequest.php | 2 +- app/Jobs/Util/ReminderJob.php | 4 +- app/Services/Quickbooks/QuickbooksService.php | 5 + .../Import/Quickbooks/QuickbooksTest.php | 111 +++++++++++++++--- 4 files changed, 105 insertions(+), 17 deletions(-) diff --git a/app/Http/Requests/Payment/UpdatePaymentRequest.php b/app/Http/Requests/Payment/UpdatePaymentRequest.php index eba4d15c6cf9..d48fdc8177d6 100644 --- a/app/Http/Requests/Payment/UpdatePaymentRequest.php +++ b/app/Http/Requests/Payment/UpdatePaymentRequest.php @@ -100,7 +100,7 @@ class UpdatePaymentRequest extends Request public function messages() { return [ - 'distinct' => 'Attemping duplicate payment on the same invoice Invoice', + 'distinct' => 'Attemping duplicate payment on the same Invoice', ]; } } diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index 5de7e4835b3b..9eee0d1e6758 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -71,7 +71,7 @@ class ReminderJob implements ShouldQueue ->whereHas('company', function ($query) { $query->where('is_disabled', 0); }) - ->with('invitations')->chunk(200, function ($invoices) { + ->with('invitations')->chunk(800, function ($invoices) { foreach ($invoices as $invoice) { $this->sendReminderForInvoice($invoice); } @@ -99,7 +99,7 @@ class ReminderJob implements ShouldQueue ->whereHas('company', function ($query) { $query->where('is_disabled', 0); }) - ->with('invitations')->chunk(200, function ($invoices) { + ->with('invitations')->chunk(800, function ($invoices) { foreach ($invoices as $invoice) { $this->sendReminderForInvoice($invoice); diff --git a/app/Services/Quickbooks/QuickbooksService.php b/app/Services/Quickbooks/QuickbooksService.php index fc222126a455..9b3c897e23b8 100644 --- a/app/Services/Quickbooks/QuickbooksService.php +++ b/app/Services/Quickbooks/QuickbooksService.php @@ -172,6 +172,11 @@ class QuickbooksService { return $this->sdk->FindById($entity, $id); } + + public function query(string $query) + { + return $this->sdk->Query($query); + } /** * Flag to determine if a sync is allowed in either direction diff --git a/tests/Feature/Import/Quickbooks/QuickbooksTest.php b/tests/Feature/Import/Quickbooks/QuickbooksTest.php index 4846179ca46a..40f0e73ae01e 100644 --- a/tests/Feature/Import/Quickbooks/QuickbooksTest.php +++ b/tests/Feature/Import/Quickbooks/QuickbooksTest.php @@ -137,11 +137,17 @@ class QuickbooksTest extends TestCase $client = Client::factory()->create([ 'company_id' => $this->company->id, 'user_id' => $this->company->owner()->id, - 'address1' => $this->faker->address(), - 'city' => $this->faker->city(), - 'state' => $this->faker->state(), - 'postal_code' => $this->faker->postcode(), + 'address1' => "10369 Ashton Avenue", + 'address2' => '', + 'city' => "Beverley Hills", + 'state' => "California", + 'postal_code' => "90210", 'country_id' => 840, + 'shipping_address1' => "10369 Ashton Avenue", + 'address2' => '', + 'shipping_city' => "Beverley Hills", + 'shipping_state' => "California", + 'shipping_postal_code' => "90210", 'shipping_country_id' => 840, ]); @@ -215,9 +221,16 @@ class QuickbooksTest extends TestCase //create ninja invoice $qb_invoice = $this->createQbInvoice($customer); - $this->assertNotNull($qb_invoice); - nlog($qb_invoice); +$this->assertNotNull($qb_invoice); + +// sleep(5); + +// $updatedInvoice = $this->qb->sdk->FindById('invoice', $qb_invoice->Id->value); + + + + nlog($updatedInvoice); } public function testCreateCustomerInQb() @@ -264,6 +277,8 @@ class QuickbooksTest extends TestCase $item_product->quantity = 1; $item_product->cost = $non_inventory_product->price; $item_product->line_total = $non_inventory_product->price; + $item_product->tax_name1 = 'CA Sales Tax'; + $item_product->tax_rate1 = 8; $item_product->type_id = '1'; $item_service = new InvoiceItem(); @@ -305,6 +320,12 @@ class QuickbooksTest extends TestCase // $this->assertEquals(30, $i->balance); $line_items = []; + + // $taxDetail = [ + // "TotalTax" => 0, + // "TaxLine" => [] + // ]; + $line_num = 1; foreach($i->line_items as $line_item) @@ -329,10 +350,68 @@ class QuickbooksTest extends TestCase ], ], 'Description' => $line_item->notes, - 'Amount' => $line_item->line_total, ]; + // if($line_item->tax_rate1 > 0) + // { + // $tax_line_detail = [ + // "Amount" => $line_item->tax_amount, + // "DetailType" => "TaxLineDetail", + // "TaxLineDetail" => [ + // "TaxRateRef" => [ + // "value" => $line_item->tax_rate1, + // "name" => $line_item->tax_name1, + // ], + // "PercentBased" => true, + // "TaxPercent" => $line_item->tax_rate1, + // "NetAmountTaxable" => $line_item->line_total, + // ] + // ]; + + // $taxDetail['TaxLine'][] = $tax_line_detail; + // $taxDetail['TotalTax'] += $line_item->tax_amount; + // } + + // if ($line_item->tax_rate2 > 0) { + // $tax_line_detail = [ + // "Amount" => $line_item->tax_amount, + // "DetailType" => "TaxLineDetail", + // "TaxLineDetail" => [ + // "TaxRateRef" => [ + // "value" => $line_item->tax_rate2, + // "name" => $line_items->tax_name2, + // ], + // "PercentBased" => true, + // "TaxPercent" => $line_item->tax_rate2, + // "NetAmountTaxable" => $line_item->line_total, + // ] + // ]; + + // $taxDetail['TaxLine'][] = $tax_line_detail; + // $taxDetail['TotalTax'] += $line_item->tax_amount; + // } + + // if ($line_item->tax_rate3 > 0) { + // $tax_line_detail = [ + // "Amount" => $line_item->tax_amount, + // "DetailType" => "TaxLineDetail", + // "TaxLineDetail" => [ + // "TaxRateRef" => [ + // "value" => $line_item->tax_name3 + // ], + // "PercentBased" => true, + // "TaxPercent" => $line_item->tax_rate3, + // "NetAmountTaxable" => $line_item->line_total, + // ] + // ]; + + // $taxDetail['TaxLine'][] = $tax_line_detail; + // $taxDetail['TotalTax'] += $line_item->tax_amount; + // } + + + $line_num++; } @@ -349,15 +428,19 @@ class QuickbooksTest extends TestCase "DueDate" => $i->due_date, "TotalAmt" => $i->amount, "DocNumber" => $i->number, + "ApplyTaxAfterDiscount" => true, + "PrintStatus" => "NeedToPrint", + "EmailStatus" => "NotSet", + "GlobalTaxCalculation" => "TaxExcluded", // "ApplyTaxAfterDiscount" => false, - "GlobalTaxCalculation" => "TaxExcluded", // This tells QuickBooks to calculate taxes - "TxnTaxDetail" => [ - "UseAutomatedSalesTax" => true, - "TxnTaxCodeRef" => [ - "value" => "SALES_TAX_STUB" // Use the appropriate tax code for your QuickBooks account + // "TxnTaxDetail" => $taxDetail, + // "TxnTaxDetail" => [ + // "UseAutomatedSalesTax" => true, + // "TxnTaxCodeRef" => [ + // "value" => "SALES_TAX_STUB" // Use the appropriate tax code for your QuickBooks account // "DefaultTaxRateRef" => [ - ], - ] + // ], + // ] // "Note" => $this->invoice->public_notes, ]; From 256d30ee66df668fcd0cbe3f7e9ebc69f51c31d9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 29 Sep 2024 14:33:22 +1000 Subject: [PATCH 11/11] Minor updates --- app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php | 4 ++-- app/Jobs/Ninja/TaskScheduler.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php b/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php index 313b1926837f..263847b32171 100644 --- a/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php +++ b/app/Http/Requests/TaskScheduler/StoreSchedulerRequest.php @@ -62,8 +62,8 @@ class StoreSchedulerRequest extends Request 'parameters' => 'bail|array', 'parameters.clients' => ['bail','sometimes', 'array', new ValidClientIds()], 'parameters.date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom,all', - 'parameters.start_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'], - 'parameters.end_date' => ['bail', 'sometimes', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'], + 'parameters.start_date' => ['bail', 'date:Y-m-d', 'required_if:parameters.date_rate,custom'], + 'parameters.end_date' => ['bail', 'date:Y-m-d', 'required_if:parameters.date_rate,custom', 'after_or_equal:parameters.start_date'], 'parameters.entity' => ['bail', 'sometimes', 'string', 'in:invoice,credit,quote,purchase_order'], 'parameters.entity_id' => ['bail', 'sometimes', 'string'], 'parameters.report_name' => ['bail','sometimes', 'string', 'required_if:template,email_report','in:vendor,purchase_order_item,purchase_order,ar_detailed,ar_summary,client_balance,tax_summary,profitloss,client_sales,user_sales,product_sales,activity,activities,client,clients,client_contact,client_contacts,credit,credits,document,documents,expense,expenses,invoice,invoices,invoice_item,invoice_items,quote,quotes,quote_item,quote_items,recurring_invoice,recurring_invoices,payment,payments,product,products,task,tasks'], diff --git a/app/Jobs/Ninja/TaskScheduler.php b/app/Jobs/Ninja/TaskScheduler.php index 2f717a7fb9cd..c30af040148b 100644 --- a/app/Jobs/Ninja/TaskScheduler.php +++ b/app/Jobs/Ninja/TaskScheduler.php @@ -57,13 +57,13 @@ class TaskScheduler implements ShouldQueue ->cursor() ->each(function ($scheduler) { - nlog("Doing job {$scheduler->name}"); + nlog("Doing job ::{$scheduler->id}:: {$scheduler->name}"); try { //@var \App\Models\Schedule $scheduler $scheduler->service()->runTask(); } catch(\Exception $e) { - nlog("Exception:: TaskScheduler:: Doing job {$scheduler->name}" . $e->getMessage()); + nlog("Exception:: TaskScheduler:: Doing job :: {$scheduler->id} :: {$scheduler->name}" . $e->getMessage()); } }); @@ -83,13 +83,13 @@ class TaskScheduler implements ShouldQueue ->cursor() ->each(function ($scheduler) { - nlog("Doing job {$scheduler->name}"); + nlog("Doing job ::{$scheduler->id}:: {$scheduler->name}"); try { /** @var \App\Models\Scheduler $scheduler */ $scheduler->service()->runTask(); } catch(\Exception $e) { - nlog("Exception:: TaskScheduler::" . $e->getMessage()); + nlog("Exception:: TaskScheduler:: #{$scheduler->id}::" . $e->getMessage()); nlog($e->getMessage()); }