diff --git a/VERSION.txt b/VERSION.txt index f17f38570499..48a48c66fbf3 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.5.24 \ No newline at end of file +5.5.25 \ No newline at end of file diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index 9a29fd955bf4..c6130cb178b1 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -109,6 +109,7 @@ class StorePaymentRequest extends Request 'credits.*.amount' => ['bail','required', new CreditsSumRule($this->all())], 'invoices' => new ValidPayableInvoicesRule(), 'number' => ['nullable', 'bail', Rule::unique('payments')->where('company_id', auth()->user()->company()->id)], + 'idempotency_key' => ['nullable', 'bail', 'string','max:64', Rule::unique('payments')->where('company_id', auth()->user()->company()->id)], ]; diff --git a/app/PaymentDrivers/Stripe/ACH.php b/app/PaymentDrivers/Stripe/ACH.php index 24c1a28190e4..d602d320f0c9 100644 --- a/app/PaymentDrivers/Stripe/ACH.php +++ b/app/PaymentDrivers/Stripe/ACH.php @@ -125,6 +125,20 @@ class ACH $bank_account = Customer::retrieveSource($request->customer, $request->source, [], $this->stripe->stripe_connect_auth); + /* Catch externally validated bank accounts and mark them as verified */ + if(property_exists($bank_account, 'status') && $bank_account->status == 'verified'){ + + $meta = $token->meta; + $meta->state = 'authorized'; + $token->meta = $meta; + $token->save(); + + return redirect() + ->route('client.payment_methods.show', $token->hashed_id) + ->with('message', __('texts.payment_method_verified')); + + } + try { $bank_account->verify(['amounts' => request()->transactions]); diff --git a/app/PaymentDrivers/WePay/CreditCard.php b/app/PaymentDrivers/WePay/CreditCard.php index 18f3632bb26d..f95d2a7855ab 100644 --- a/app/PaymentDrivers/WePay/CreditCard.php +++ b/app/PaymentDrivers/WePay/CreditCard.php @@ -37,6 +37,7 @@ class CreditCard public function authorizeView($data) { $data['gateway'] = $this->wepay_payment_driver; + $data['country_code'] = $this->wepay_payment_driver->client ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2; return render('gateways.wepay.authorize.authorize', $data); } @@ -101,6 +102,7 @@ class CreditCard { $data['gateway'] = $this->wepay_payment_driver; $data['description'] = ctrans('texts.invoices').': '.collect($data['invoices'])->pluck('invoice_number'); + $data['country_code'] = $this->wepay_payment_driver->client ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2; return render('gateways.wepay.credit_card.pay', $data); } diff --git a/config/ninja.php b/config/ninja.php index 2959269981c7..34373b8c92e2 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.5.24', - 'app_tag' => '5.5.24', + 'app_version' => '5.5.25', + 'app_tag' => '5.5.25', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), diff --git a/database/migrations/2022_09_30_235337_add_idempotency_key_to_payments.php b/database/migrations/2022_09_30_235337_add_idempotency_key_to_payments.php new file mode 100644 index 000000000000..7f95df32434d --- /dev/null +++ b/database/migrations/2022_09_30_235337_add_idempotency_key_to_payments.php @@ -0,0 +1,33 @@ +string('idempotency_key', 64)->nullable()->index(); + + $table->unique(['company_id', 'idempotency_key']); + + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + + } +}; diff --git a/public/js/clients/payments/wepay-credit-card.js b/public/js/clients/payments/wepay-credit-card.js index 47a03c20bb88..04a5b8f17032 100644 --- a/public/js/clients/payments/wepay-credit-card.js +++ b/public/js/clients/payments/wepay-credit-card.js @@ -1,2 +1,2 @@ /*! For license information please see wepay-credit-card.js.LICENSE.txt */ -(()=>{var e;function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){for(var r=0;r0&&void 0!==arguments[0]?arguments[0]:"payment";t(this,e),this.action=r,this.errors=document.getElementById("errors")}var n,d,a;return n=e,(d=[{key:"initializeWePay",value:function(){var e,t=null===(e=document.querySelector('meta[name="wepay-environment"]'))||void 0===e?void 0:e.content;return WePay.set_endpoint("staging"===t?"stage":"production"),this}},{key:"validateCreditCardFields",value:function(){return this.myCard=$("#my-card"),""===document.getElementById("cardholder_name")?(document.getElementById("cardholder_name").focus(),this.errors.textContent="Cardholder name required.",void(this.errors.hidden=!1)):""===this.myCard.CardJs("cardNumber").replace(/[^\d]/g,"")?(document.getElementById("card_number").focus(),this.errors.textContent="Card number required.",void(this.errors.hidden=!1)):""===this.myCard.CardJs("cvc").replace(/[^\d]/g,"")?(document.getElementById("cvv").focus(),this.errors.textContent="CVV number required.",void(this.errors.hidden=!1)):""===this.myCard.CardJs("expiryMonth").replace(/[^\d]/g,"")?(this.errors.textContent="Expiry Month number required.",void(this.errors.hidden=!1)):""!==this.myCard.CardJs("expiryYear").replace(/[^\d]/g,"")||(this.errors.textContent="Expiry Year number required.",void(this.errors.hidden=!1))}},{key:"handleAuthorization",value:function(){var e=this;if(this.validateCreditCardFields()){var t=document.getElementById("card_button");t.disabled=!0,t.querySelector("svg").classList.remove("hidden"),t.querySelector("span").classList.add("hidden"),WePay.credit_card.create({client_id:document.querySelector("meta[name=wepay-client-id]").content,user_name:document.getElementById("cardholder_name").value,email:document.querySelector("meta[name=contact-email]").content,cc_number:this.myCard.CardJs("cardNumber").replace(/[^\d]/g,""),cvv:this.myCard.CardJs("cvc").replace(/[^\d]/g,""),expiration_month:this.myCard.CardJs("expiryMonth").replace(/[^\d]/g,""),expiration_year:this.myCard.CardJs("expiryYear").replace(/[^\d]/g,""),address:{postal_code:document.querySelector(["meta[name=client-postal-code"]).content}},(function(r){r.error?((t=document.getElementById("card_button")).disabled=!1,t.querySelector("svg").classList.add("hidden"),t.querySelector("span").classList.remove("hidden"),e.errors.textContent="",e.errors.textContent=r.error_description,e.errors.hidden=!1):(document.querySelector('input[name="credit_card_id"]').value=r.credit_card_id,document.getElementById("server_response").submit())}))}}},{key:"completePaymentUsingToken",value:function(e){document.querySelector('input[name="credit_card_id"]').value=null,document.querySelector('input[name="token"]').value=e,document.getElementById("server-response").submit()}},{key:"completePaymentWithoutToken",value:function(){var e=this;if(!this.validateCreditCardFields())return this.payNowButton=document.getElementById("pay-now"),this.payNowButton.disabled=!1,this.payNowButton.querySelector("svg").classList.add("hidden"),void this.payNowButton.querySelector("span").classList.remove("hidden");WePay.credit_card.create({client_id:document.querySelector("meta[name=wepay-client-id]").content,user_name:document.getElementById("cardholder_name").value,email:document.querySelector("meta[name=contact-email]").content,cc_number:this.myCard.CardJs("cardNumber").replace(/[^\d]/g,""),cvv:this.myCard.CardJs("cvc").replace(/[^\d]/g,""),expiration_month:this.myCard.CardJs("expiryMonth").replace(/[^\d]/g,""),expiration_year:this.myCard.CardJs("expiryYear").replace(/[^\d]/g,""),address:{postal_code:document.querySelector(["meta[name=client-postal-code"]).content}},(function(t){t.error?(e.payNowButton.disabled=!1,e.payNowButton.querySelector("svg").classList.add("hidden"),e.payNowButton.querySelector("span").classList.remove("hidden"),e.errors.textContent="",e.errors.textContent=t.error_description,e.errors.hidden=!1):(document.querySelector('input[name="credit_card_id"]').value=t.credit_card_id,document.querySelector('input[name="token"]').value=null,document.getElementById("server-response").submit())}))}},{key:"handle",value:function(){var e=this;this.initializeWePay(),"authorize"===this.action?document.getElementById("card_button").addEventListener("click",(function(){return e.handleAuthorization()})):"payment"===this.action&&(Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(e){return e.addEventListener("click",(function(e){document.getElementById("save-card--container").style.display="none",document.getElementById("wepay--credit-card-container").style.display="none",document.getElementById("token").value=e.target.dataset.token}))})),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",(function(e){document.getElementById("save-card--container").style.display="grid",document.getElementById("wepay--credit-card-container").style.display="flex",document.getElementById("token").value=null})),document.getElementById("pay-now").addEventListener("click",(function(){e.payNowButton=document.getElementById("pay-now"),e.payNowButton.disabled=!0,e.payNowButton.querySelector("svg").classList.remove("hidden"),e.payNowButton.querySelector("span").classList.add("hidden");var t=document.querySelector("input[name=token]"),r=document.querySelector("input[name=token-billing-checkbox]:checked");return r&&(document.getElementById("store_card").value=r.value),t.value?e.completePaymentUsingToken(t.value):e.completePaymentWithoutToken()})))}}])&&r(n.prototype,d),a&&r(n,a),Object.defineProperty(n,"prototype",{writable:!1}),e}();new d(n).handle()})(); \ No newline at end of file +(()=>{var e;function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function n(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:"payment";t(this,e),this.action=n,this.errors=document.getElementById("errors")}var r,d,a;return r=e,(d=[{key:"initializeWePay",value:function(){var e,t=null===(e=document.querySelector('meta[name="wepay-environment"]'))||void 0===e?void 0:e.content;return WePay.set_endpoint("staging"===t?"stage":"production"),this}},{key:"validateCreditCardFields",value:function(){return this.myCard=$("#my-card"),""===document.getElementById("cardholder_name")?(document.getElementById("cardholder_name").focus(),this.errors.textContent="Cardholder name required.",void(this.errors.hidden=!1)):""===this.myCard.CardJs("cardNumber").replace(/[^\d]/g,"")?(document.getElementById("card_number").focus(),this.errors.textContent="Card number required.",void(this.errors.hidden=!1)):""===this.myCard.CardJs("cvc").replace(/[^\d]/g,"")?(document.getElementById("cvv").focus(),this.errors.textContent="CVV number required.",void(this.errors.hidden=!1)):""===this.myCard.CardJs("expiryMonth").replace(/[^\d]/g,"")?(this.errors.textContent="Expiry Month number required.",void(this.errors.hidden=!1)):""!==this.myCard.CardJs("expiryYear").replace(/[^\d]/g,"")||(this.errors.textContent="Expiry Year number required.",void(this.errors.hidden=!1))}},{key:"handleAuthorization",value:function(){var e=this;if(this.validateCreditCardFields()){var t=document.getElementById("card_button");t.disabled=!0,t.querySelector("svg").classList.remove("hidden"),t.querySelector("span").classList.add("hidden"),WePay.credit_card.create({client_id:document.querySelector("meta[name=wepay-client-id]").content,user_name:document.getElementById("cardholder_name").value,email:document.querySelector("meta[name=contact-email]").content,cc_number:this.myCard.CardJs("cardNumber").replace(/[^\d]/g,""),cvv:this.myCard.CardJs("cvc").replace(/[^\d]/g,""),expiration_month:this.myCard.CardJs("expiryMonth").replace(/[^\d]/g,""),expiration_year:this.myCard.CardJs("expiryYear").replace(/[^\d]/g,""),address:{country:document.querySelector(["meta[name=country_code"]).content,postal_code:document.querySelector(["meta[name=client-postal-code"]).content}},(function(n){n.error?((t=document.getElementById("card_button")).disabled=!1,t.querySelector("svg").classList.add("hidden"),t.querySelector("span").classList.remove("hidden"),e.errors.textContent="",e.errors.textContent=n.error_description,e.errors.hidden=!1):(document.querySelector('input[name="credit_card_id"]').value=n.credit_card_id,document.getElementById("server_response").submit())}))}}},{key:"completePaymentUsingToken",value:function(e){document.querySelector('input[name="credit_card_id"]').value=null,document.querySelector('input[name="token"]').value=e,document.getElementById("server-response").submit()}},{key:"completePaymentWithoutToken",value:function(){var e=this;if(!this.validateCreditCardFields())return this.payNowButton=document.getElementById("pay-now"),this.payNowButton.disabled=!1,this.payNowButton.querySelector("svg").classList.add("hidden"),void this.payNowButton.querySelector("span").classList.remove("hidden");WePay.credit_card.create({client_id:document.querySelector("meta[name=wepay-client-id]").content,user_name:document.getElementById("cardholder_name").value,email:document.querySelector("meta[name=contact-email]").content,cc_number:this.myCard.CardJs("cardNumber").replace(/[^\d]/g,""),cvv:this.myCard.CardJs("cvc").replace(/[^\d]/g,""),expiration_month:this.myCard.CardJs("expiryMonth").replace(/[^\d]/g,""),expiration_year:this.myCard.CardJs("expiryYear").replace(/[^\d]/g,""),address:{country:document.querySelector(["meta[name=country_code"]).content,postal_code:document.querySelector(["meta[name=client-postal-code"]).content}},(function(t){t.error?(e.payNowButton.disabled=!1,e.payNowButton.querySelector("svg").classList.add("hidden"),e.payNowButton.querySelector("span").classList.remove("hidden"),e.errors.textContent="",e.errors.textContent=t.error_description,e.errors.hidden=!1):(document.querySelector('input[name="credit_card_id"]').value=t.credit_card_id,document.querySelector('input[name="token"]').value=null,document.getElementById("server-response").submit())}))}},{key:"handle",value:function(){var e=this;this.initializeWePay(),"authorize"===this.action?document.getElementById("card_button").addEventListener("click",(function(){return e.handleAuthorization()})):"payment"===this.action&&(Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(e){return e.addEventListener("click",(function(e){document.getElementById("save-card--container").style.display="none",document.getElementById("wepay--credit-card-container").style.display="none",document.getElementById("token").value=e.target.dataset.token}))})),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",(function(e){document.getElementById("save-card--container").style.display="grid",document.getElementById("wepay--credit-card-container").style.display="flex",document.getElementById("token").value=null})),document.getElementById("pay-now").addEventListener("click",(function(){e.payNowButton=document.getElementById("pay-now"),e.payNowButton.disabled=!0,e.payNowButton.querySelector("svg").classList.remove("hidden"),e.payNowButton.querySelector("span").classList.add("hidden");var t=document.querySelector("input[name=token]"),n=document.querySelector("input[name=token-billing-checkbox]:checked");return n&&(document.getElementById("store_card").value=n.value),t.value?e.completePaymentUsingToken(t.value):e.completePaymentWithoutToken()})))}}])&&n(r.prototype,d),a&&n(r,a),Object.defineProperty(r,"prototype",{writable:!1}),e}();new d(r).handle()})(); \ No newline at end of file diff --git a/public/mix-manifest.json b/public/mix-manifest.json index a51a8482ad72..ecda845180d2 100755 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -22,7 +22,7 @@ "/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=2b2fe55f926789abc52f19111006e1ec", "/js/clients/payments/braintree-credit-card.js": "/js/clients/payments/braintree-credit-card.js?id=cf25867ef09115b7f5a209819ba79bbf", "/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js?id=5764a8d406c1eda848d073f10d178626", - "/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js?id=bec9106c8ba5a973acee4cfc47301324", + "/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js?id=dbba20d70fbebb326ddbc46115af9771", "/js/clients/payment_methods/wepay-bank-account.js": "/js/clients/payment_methods/wepay-bank-account.js?id=b8706d7de6127f184ad19b2a810880be", "/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=e0b1231a7bf6252672836222285c0f52", "/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=bbab588ed009a93345bec520cbe66869", diff --git a/resources/js/clients/payments/wepay-credit-card.js b/resources/js/clients/payments/wepay-credit-card.js index 02f4d4fabb0b..0561f9f9b14d 100644 --- a/resources/js/clients/payments/wepay-credit-card.js +++ b/resources/js/clients/payments/wepay-credit-card.js @@ -82,6 +82,7 @@ class WePayCreditCard { expiration_month: this.myCard.CardJs('expiryMonth').replace(/[^\d]/g, ''), expiration_year: this.myCard.CardJs('expiryYear').replace(/[^\d]/g, ''), address: { + country: document.querySelector(['meta[name=country_code']).content, postal_code: document.querySelector(['meta[name=client-postal-code']).content, } }, (data) => { @@ -127,6 +128,7 @@ class WePayCreditCard { expiration_month: this.myCard.CardJs('expiryMonth').replace(/[^\d]/g, ''), expiration_year: this.myCard.CardJs('expiryYear').replace(/[^\d]/g, ''), address: { + country: document.querySelector(['meta[name=country_code']).content, postal_code: document.querySelector(['meta[name=client-postal-code']).content, } }, (data) => { diff --git a/resources/views/portal/ninja2020/gateways/wepay/authorize/authorize.blade.php b/resources/views/portal/ninja2020/gateways/wepay/authorize/authorize.blade.php index eb8e17245aec..d6f8faadac7a 100644 --- a/resources/views/portal/ninja2020/gateways/wepay/authorize/authorize.blade.php +++ b/resources/views/portal/ninja2020/gateways/wepay/authorize/authorize.blade.php @@ -7,6 +7,7 @@ + diff --git a/resources/views/portal/ninja2020/gateways/wepay/authorize/bank_transfer.blade.php b/resources/views/portal/ninja2020/gateways/wepay/authorize/bank_transfer.blade.php index 5001be704bc7..ba8888fb861b 100644 --- a/resources/views/portal/ninja2020/gateways/wepay/authorize/bank_transfer.blade.php +++ b/resources/views/portal/ninja2020/gateways/wepay/authorize/bank_transfer.blade.php @@ -4,6 +4,7 @@ + @endsection diff --git a/resources/views/portal/ninja2020/gateways/wepay/credit_card/pay.blade.php b/resources/views/portal/ninja2020/gateways/wepay/credit_card/pay.blade.php index 88e91f1cb17e..87a3d3a10195 100644 --- a/resources/views/portal/ninja2020/gateways/wepay/credit_card/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/wepay/credit_card/pay.blade.php @@ -7,7 +7,7 @@ - + diff --git a/tests/Feature/PaymentTest.php b/tests/Feature/PaymentTest.php index f12e892cea6e..6d79cdcdbf5b 100644 --- a/tests/Feature/PaymentTest.php +++ b/tests/Feature/PaymentTest.php @@ -62,6 +62,55 @@ class PaymentTest extends TestCase ); } + public function testStorePaymentIdempotencyKeyIllegalLength() + { + $client = ClientFactory::create($this->company->id, $this->user->id); + $client->save(); + + $this->invoice = InvoiceFactory::create($this->company->id, $this->user->id); //stub the company and user_id + $this->invoice->client_id = $client->id; + + $this->invoice->line_items = $this->buildLineItems(); + $this->invoice->uses_inclusive_Taxes = false; + + $this->invoice->save(); + + $this->invoice_calc = new InvoiceSum($this->invoice); + $this->invoice_calc->build(); + + $this->invoice = $this->invoice_calc->getInvoice(); + + $data = [ + 'amount' => $this->invoice->amount, + 'client_id' => $client->hashed_id, + 'invoices' => [ + [ + 'invoice_id' => $this->invoice->hashed_id, + 'amount' => $this->invoice->amount, + ], + ], + 'date' => '2020/12/11', + 'idempotency_key' => 'dsjafhajklsfhlaksjdhlkajsdjdfjdfljasdfhkjlsafhljfkfhsjlfhiuwayerfiuwaskjgbzmvnjzxnjcbgfkjhdgfoiwwrasdfasdfkashjdfkaskfjdasfda' + + ]; + + $response = false; + try { + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/payments/', $data); + } catch (ValidationException $e) { + // $message = json_decode($e->validator->getMessageBag(), 1); + + + } + + $this->assertFalse($response); + + } + + public function testPaymentList() { Client::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id])->each(function ($c) {