diff --git a/app/PaymentDrivers/CheckoutCom/CreditCard.php b/app/PaymentDrivers/CheckoutCom/CreditCard.php index 5c4560abfaca..a1deb8422044 100644 --- a/app/PaymentDrivers/CheckoutCom/CreditCard.php +++ b/app/PaymentDrivers/CheckoutCom/CreditCard.php @@ -82,15 +82,6 @@ class CreditCard { $this->checkout->init(); - $cgt = ClientGatewayToken::query() - ->where('id', $this->decodePrimaryKey($request->input('token'))) - ->where('company_id', auth('contact')->user()->client->company->id) - ->first(); - - if (!$cgt) { - throw new PaymentFailed(ctrans('texts.payment_token_not_found'), 401); - } - $state = [ 'server_response' => json_decode($request->gateway_response), 'value' => $request->value, @@ -103,12 +94,11 @@ class CreditCard $state = array_merge($state, $request->all()); $state['store_card'] = boolval($state['store_card']); - $state['token'] = $cgt; $this->checkout->payment_hash->data = array_merge((array)$this->checkout->payment_hash->data, $state); $this->checkout->payment_hash->save(); - if ($request->has('token')) { + if ($request->has('token') && !is_null($request->token) && !empty($request->token)) { return $this->attemptPaymentUsingToken($request); } @@ -117,7 +107,16 @@ class CreditCard private function attemptPaymentUsingToken(PaymentResponseRequest $request) { - $method = new IdSource($this->checkout->payment_hash->data->token->token); + $cgt = ClientGatewayToken::query() + ->where('id', $this->decodePrimaryKey($request->input('token'))) + ->where('company_id', auth('contact')->user()->client->company->id) + ->first(); + + if (!$cgt) { + throw new PaymentFailed(ctrans('texts.payment_token_not_found'), 401); + } + + $method = new IdSource($cgt->token); return $this->completePayment($method, $request); } @@ -161,7 +160,7 @@ class CreditCard } if ($response->status == 'Pending') { - $this->checkout->confirmGatewayFee($request); + $this->checkout->confirmGatewayFee(); return $this->processPendingPayment($response); } @@ -171,7 +170,6 @@ class CreditCard PaymentFailureMailer::dispatch($this->checkout->client, $response->response_summary, $this->checkout->client->company, $this->checkout->payment_hash->data->value); - return $this->processUnsuccessfulPayment($response); } } catch (CheckoutHttpException $e) { diff --git a/app/PaymentDrivers/CheckoutCom/Utilities.php b/app/PaymentDrivers/CheckoutCom/Utilities.php index 62d5676a3a70..a4d222dbdb54 100644 --- a/app/PaymentDrivers/CheckoutCom/Utilities.php +++ b/app/PaymentDrivers/CheckoutCom/Utilities.php @@ -66,6 +66,7 @@ trait Utilities 'payment_type' => PaymentType::parseCardType(strtolower($_payment->source['scheme'])), 'amount' => $this->getParent()->payment_hash->data->raw_value, 'transaction_reference' => $_payment->id, + 'gateway_type_id' => GatewayType::CREDIT_CARD, ]; $payment = $this->getParent()->createPayment($data, \App\Models\Payment::STATUS_COMPLETED); diff --git a/app/PaymentDrivers/Stripe/CreditCard.php b/app/PaymentDrivers/Stripe/CreditCard.php index d9b007b02a08..eb5eac5b8116 100644 --- a/app/PaymentDrivers/Stripe/CreditCard.php +++ b/app/PaymentDrivers/Stripe/CreditCard.php @@ -86,6 +86,10 @@ class CreditCard $state = array_merge($state, $request->all()); $state['store_card'] = boolval($state['store_card']); + if ($request->has('token') && !is_null($request->token)) { + $state['store_card'] = false; + } + $state['payment_intent'] = PaymentIntent::retrieve($state['server_response']->id); $state['customer'] = $state['payment_intent']->customer; @@ -116,7 +120,6 @@ class CreditCard 'gateway_type_id' => GatewayType::CREDIT_CARD, ]; - $this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, ['amount' => $data['amount']]); $this->stripe->payment_hash->save(); diff --git a/cypress.json b/cypress.json index e959015b2688..f57c9ce64c18 100644 --- a/cypress.json +++ b/cypress.json @@ -1,5 +1,8 @@ { "video": false, - "baseUrl": "http://localhost:8000/", - "chromeWebSecurity": false + "baseUrl": "https://localhost:8000/", + "chromeWebSecurity": false, + "env": { + "runningEnvironment": "native" + } } diff --git a/cypress/integration/client_portal/checkout_credit_card.spec.js b/cypress/integration/client_portal/checkout_credit_card.spec.js new file mode 100644 index 000000000000..b2ce01811133 --- /dev/null +++ b/cypress/integration/client_portal/checkout_credit_card.spec.js @@ -0,0 +1,87 @@ +context('Checkout.com: Credit card testing', () => { + before(() => { + cy.artisan('migrate:fresh --seed'); + cy.artisan('ninja:create-single-account checkout'); + }); + + beforeEach(() => { + cy.viewport('macbook-13'); + cy.clientLogin(); + }); + + afterEach(() => { + cy.visit('/client/logout'); + }); + + it('should not be able to add payment method', function () { + cy.visit('/client/payment_methods'); + + cy.get('[data-cy=add-payment-method]').click(); + cy.get('[data-cy=add-credit-card-link]').click(); + + cy.get('[data-ref=gateway-container]') + .contains('Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.'); + }); + + it('should pay with new card', function () { + cy.visit('/client/invoices'); + + cy.get('[data-cy=pay-now]').first().click(); + cy.get('[data-cy=pay-now-dropdown]').click(); + cy.get('[data-cy=pay-with-0]').click(); + + cy.getWithinIframe('#checkout-frames-card-number').type('4658584090000001'); + cy.getWithinIframe('#checkout-frames-expiry-date').type('12/22'); + cy.getWithinIframe('#checkout-frames-cvv').type('257'); + + cy.get('#pay-button').click(); + + cy.url().should('contain', '/client/payments/VolejRejNm'); + }); + + it('should pay with new card & save credit card for future use', function () { + cy.visit('/client/invoices'); + + cy.get('[data-cy=pay-now]').first().click(); + cy.get('[data-cy=pay-now-dropdown]').click(); + cy.get('[data-cy=pay-with-0]').click(); + + cy.get('[name=token-billing-checkbox]').first().check(); + + cy.getWithinIframe('#checkout-frames-card-number').type('4543474002249996'); + cy.getWithinIframe('#checkout-frames-expiry-date').type('12/22'); + cy.getWithinIframe('#checkout-frames-cvv').type('956'); + + cy.get('#pay-button').click(); + + cy.url().should('contain', '/client/payments/Wpmbk5ezJn'); + }); + + it('should pay with saved card (token)', function () { + cy.visit('/client/invoices'); + + cy.get('[data-cy=pay-now]').first().click(); + cy.get('[data-cy=pay-now-dropdown]').click(); + cy.get('[data-cy=pay-with-0]').click(); + + cy.get('[name=payment-type]').first().check(); + + cy.get('#pay-now-with-token').click(); + + cy.url().should('contain', '/client/payments/Opnel5aKBz'); + }); + + it('should be able to remove payment method', function () { + cy.visit('/client/payment_methods'); + + cy.get('[data-cy=view-payment-method]').click(); + + cy.get('#open-delete-popup').click(); + + cy.get('[data-cy=confirm-payment-removal]').click(); + + cy.url().should('contain', '/client/payment_methods'); + + cy.get('body').contains('Payment method has been successfully removed.'); + }); +}); diff --git a/cypress/integration/client_portal/stripe_credit_card.spec.js b/cypress/integration/client_portal/stripe_credit_card.spec.js index f9d40b0f2e11..ce80b83becff 100644 --- a/cypress/integration/client_portal/stripe_credit_card.spec.js +++ b/cypress/integration/client_portal/stripe_credit_card.spec.js @@ -1,47 +1,98 @@ -describe('Stripe Credit Card Payments', () => { +describe('Stripe: Credit card testing', () => { + before(() => { + cy.artisan('migrate:fresh --seed'); + cy.artisan('ninja:create-single-account stripe'); + }); + beforeEach(() => { + cy.viewport('macbook-13'); cy.clientLogin(); }); - it('should be able to add credit card using Stripe', () => { + afterEach(() => { + cy.visit('/client/logout'); + }); + + it('should pay with new card', function () { + cy.visit('/client/invoices'); + + cy.get('[data-cy=pay-now]').first().click(); + cy.get('[data-cy=pay-now-dropdown]').click(); + cy.get('[data-cy=pay-with-0]').click(); + + cy.get('#cardholder-name').type('Invoice Ninja Rocks'); + cy.getWithinIframe('[name=cardnumber]').type('4242424242424242'); + cy.getWithinIframe('[name=exp-date]').type('04/24'); + cy.getWithinIframe('[name=cvc]').type('242'); + cy.getWithinIframe('[name=postal]').type('42424'); + + cy.get('#pay-now').click(); + + cy.url().should('contain', '/client/payments/VolejRejNm'); + }); + + it('should pay with new card & save credit card for future use', function () { + cy.visit('/client/invoices'); + + cy.get('[data-cy=pay-now]').first().click(); + cy.get('[data-cy=pay-now-dropdown]').click(); + cy.get('[data-cy=pay-with-0]').click(); + + cy.get('#cardholder-name').type('Invoice Ninja Rocks'); + cy.getWithinIframe('[name=cardnumber]').type('4242424242424242'); + cy.getWithinIframe('[name=exp-date]').type('04/24'); + cy.getWithinIframe('[name=cvc]').type('242'); + cy.getWithinIframe('[name=postal]').type('42424'); + + cy.get('[name=token-billing-checkbox]').first().check(); + + cy.get('#pay-now').click(); + + cy.url().should('contain', '/client/payments/Wpmbk5ezJn'); + }); + + it('should pay with saved card (token)', function () { + cy.visit('/client/invoices'); + + cy.get('[data-cy=pay-now]').first().click(); + cy.get('[data-cy=pay-now-dropdown]').click(); + cy.get('[data-cy=pay-with-0]').click(); + + cy.get('[name=payment-type]').first().check(); + + cy.get('#pay-now').click(); + + cy.url().should('contain', '/client/payments/Opnel5aKBz'); + }); + + it('should be able to remove payment method', function () { + cy.visit('/client/payment_methods'); + + cy.get('[data-cy=view-payment-method]').click(); + + cy.get('#open-delete-popup').click(); + + cy.get('[data-cy=confirm-payment-removal]').click(); + + cy.url().should('contain', '/client/payment_methods'); + + cy.get('body').contains('Payment method has been successfully removed.'); + }); + + it('should be able to add credit card (standalone)', function () { cy.visit('/client/payment_methods'); cy.get('[data-cy=add-payment-method]').click(); cy.get('[data-cy=add-credit-card-link]').click(); - cy.get('#cardholder-name').type('Invoice Ninja'); + cy.get('#cardholder-name').type('Invoice Ninja Rocks'); + cy.getWithinIframe('[name=cardnumber]').type('4242424242424242'); + cy.getWithinIframe('[name=exp-date]').type('04/24'); + cy.getWithinIframe('[name=cvc]').type('242'); + cy.getWithinIframe('[name=postal]').type('42424'); - cy.getWithinIframe('[name="cardnumber"]').type('4242424242424242'); - cy.getWithinIframe('[name="exp-date"]').type('1230'); - cy.getWithinIframe('[name="cvc"]').type('100'); - cy.getWithinIframe('[name="postal"]').type('12345'); + cy.get('#authorize-card').click(); - cy.get('#card-button').click(); - - cy.get('#errors').should('be.empty'); - - cy.location('pathname').should('eq', '/client/payment_methods'); - }); - - it('should be able to complete payment with added credit card', () => { - cy.visit('/client/invoices'); - - cy.get('#unpaid-checkbox').click(); - - cy.get('[data-cy=pay-now') - .first() - .click(); - - cy.location('pathname').should('eq', '/client/invoices/payment'); - - cy.get('[data-cy=payment-methods-dropdown').click(); - - cy.get('[data-cy=payment-method') - .first() - .click(); - - cy.get('#pay-now-with-token').click(); - - cy.url().should('contain', '/client/payments'); + cy.url().should('contain', '/client/payment_methods'); }); }); diff --git a/cypress/support/account.js b/cypress/support/account.js new file mode 100644 index 000000000000..4be5996c72ff --- /dev/null +++ b/cypress/support/account.js @@ -0,0 +1,35 @@ +import axios from 'axios'; + +const baseUrl = Cypress.config().baseUrl.endsWith('/') + ? Cypress.config().baseUrl.slice(0, -1) + : Cypress.config().baseUrl; + +Cypress.Commands.add('createAdminAccount', () => { + let body = { + first_name: "Cypress", + last_name: "Testing", + email: "cypress_testing@example.com", + password: "password", + terms_of_service: true, + privacy_policy: true, + report_errors: true, + }; + + let headers = { + "Content-Type": "application/json", + "X-Requested-With": "XMLHttpRequest" + }; + + return axios.post(`${baseUrl}/api/v1/signup?first_load=true`, body, headers) + .then(response => { + console.log('Data from the request', response.data.data[0]); + return response.data.data[0]; + }) + .catch(e => { + throw "Unable to create an account for admin."; + }); +}); + +Cypress.Commands.add('createClientAccount', () => { + // .. +}); diff --git a/cypress/support/artisan.js b/cypress/support/artisan.js new file mode 100644 index 000000000000..d183e20ddce0 --- /dev/null +++ b/cypress/support/artisan.js @@ -0,0 +1,6 @@ +Cypress.Commands.add('artisan', (cmd) => { + let environment = Cypress.env('runningEnvironment'); + let prefix = environment === 'docker' ? 'docker-compose run --rm artisan' : 'php artisan'; + + return cy.exec(`${prefix} ${cmd}`); +}); diff --git a/cypress/support/index.js b/cypress/support/index.js index d68db96df269..7874a4e0db1c 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -14,7 +14,9 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import './commands'; +import './artisan'; +import './account'; // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/public/js/clients/payments/stripe-credit-card.js b/public/js/clients/payments/stripe-credit-card.js index c5ced87f1c0d..d9426148f2de 100755 --- a/public/js/clients/payments/stripe-credit-card.js +++ b/public/js/clients/payments/stripe-credit-card.js @@ -1,2 +1,2 @@ /*! For license information please see stripe-credit-card.js.LICENSE.txt */ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=11)}({11:function(e,t,n){e.exports=n("uALE")},uALE:function(e,t){var n,r,o;function a(e,t){for(var n=0;ncompany_gateway->token_billing !== 'always') +@php + $token_billing = $gateway instanceof \App\Models\CompanyGateway + ? $gateway->token_billing !== 'always' + : $gateway->company_gateway->token_billing !== 'always'; +@endphp + +@if($token_billing)
{{ ctrans('texts.save_payment_method_details') }} diff --git a/resources/views/portal/ninja2020/invoices/payment.blade.php b/resources/views/portal/ninja2020/invoices/payment.blade.php index 55cc78958f05..eca418b4195f 100644 --- a/resources/views/portal/ninja2020/invoices/payment.blade.php +++ b/resources/views/portal/ninja2020/invoices/payment.blade.php @@ -24,7 +24,7 @@
-