mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Testing Stripe:
- Changed baseUrl to more generic localhost:8000 - Testing adding Stripe card & paying with it - Added iframe helper functions - Fix issue with processingOverlay when adding stripe credit card - Added few selectors to pages for easier testing
This commit is contained in:
parent
1bbe674902
commit
89b950cbfd
@ -1,5 +1,5 @@
|
||||
{
|
||||
"video": false,
|
||||
"baseUrl": "http://ninja.test:8000/",
|
||||
"baseUrl": "http://localhost:8000/",
|
||||
"chromeWebSecurity": false
|
||||
}
|
||||
|
45
cypress/integration/client_portal/stripe_credit_card_spec.js
vendored
Normal file
45
cypress/integration/client_portal/stripe_credit_card_spec.js
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
describe('Stripe Credit Card Payments', () => {
|
||||
beforeEach(() => cy.clientLogin());
|
||||
|
||||
it('should be able to add credit card using Stripe', () => {
|
||||
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.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('#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');
|
||||
});
|
||||
});
|
46
cypress/support/commands.js
vendored
46
cypress/support/commands.js
vendored
@ -28,10 +28,10 @@ Cypress.Commands.add('clientLogin', () => {
|
||||
cy.visit('/client/login');
|
||||
cy.get('#test_email')
|
||||
.invoke('val')
|
||||
.then(emailValue => {
|
||||
.then((emailValue) => {
|
||||
cy.get('#test_password')
|
||||
.invoke('val')
|
||||
.then(passwordValue => {
|
||||
.then((passwordValue) => {
|
||||
cy.get('#email')
|
||||
.type(emailValue)
|
||||
.should('have.value', emailValue);
|
||||
@ -45,32 +45,32 @@ Cypress.Commands.add('clientLogin', () => {
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add(
|
||||
'iframeLoaded',
|
||||
{prevSubject: 'element'},
|
||||
($iframe) => {
|
||||
const contentWindow = $iframe.prop('contentWindow');
|
||||
return new Promise(resolve => {
|
||||
if (
|
||||
contentWindow
|
||||
) {
|
||||
resolve(contentWindow)
|
||||
} else {
|
||||
$iframe.on('load', () => {
|
||||
resolve(contentWindow)
|
||||
})
|
||||
}
|
||||
})
|
||||
Cypress.Commands.add('iframeLoaded', { prevSubject: 'element' }, ($iframe) => {
|
||||
const contentWindow = $iframe.prop('contentWindow');
|
||||
return new Promise((resolve) => {
|
||||
if (contentWindow) {
|
||||
resolve(contentWindow);
|
||||
} else {
|
||||
$iframe.on('load', () => {
|
||||
resolve(contentWindow);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Cypress.Commands.add(
|
||||
'getInDocument',
|
||||
{prevSubject: 'Permission denied to access property "document" on cross-origin object'},
|
||||
{
|
||||
prevSubject:
|
||||
'Permission denied to access property "document" on cross-origin object',
|
||||
},
|
||||
(document, selector) => Cypress.$(selector, document)
|
||||
);
|
||||
|
||||
Cypress.Commands.add(
|
||||
'getWithinIframe',
|
||||
(targetElement) => cy.get('iframe').iframeLoaded().its('document').getInDocument(targetElement)
|
||||
Cypress.Commands.add('getWithinIframe', (targetElement) =>
|
||||
cy
|
||||
.get('iframe')
|
||||
.iframeLoaded()
|
||||
.its('document')
|
||||
.getInDocument(targetElement)
|
||||
);
|
||||
|
@ -1,2 +1,2 @@
|
||||
/*! For license information please see authorize-stripe-card.js.LICENSE.txt */
|
||||
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.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 a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));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=1)}({1:function(e,t,n){e.exports=n("jzun")},jzun:function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}new(function(){function e(t){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.key=t,this.cardHolderName=document.getElementById("cardholder-name"),this.cardButton=document.getElementById("card-button"),this.clientSecret=this.cardButton.dataset.secret}var t,r,a;return t=e,(r=[{key:"setupStripe",value:function(){return this.stripe=Stripe(this.key),this.elements=this.stripe.elements(),this}},{key:"createElement",value:function(){return this.cardElement=this.elements.create("card"),this}},{key:"mountCardElement",value:function(){return this.cardElement.mount("#card-element"),this}},{key:"handleStripe",value:function(e,t){var n=this;this.cardButton.disabled=!0,this.cardButton.querySelector("span").classList.add("hidden"),this.cardButton.querySelector("svg").classList.remove("hidden"),e.handleCardSetup(this.clientSecret,this.cardElement,{payment_method_data:{billing_details:{name:t.value}}}).then((function(e){return e.error?n.handleFailure(e):n.handleSuccess(e)}))}},{key:"handleFailure",value:function(e){this.cardButton.disabled=!1,this.cardButton.querySelector("span").classList.remove("hidden"),this.cardButton.querySelector("svg").classList.add("hidden");var t=document.getElementById("errors");t.textContent="",t.textContent=e.error.message,t.hidden=!1}},{key:"handleSuccess",value:function(e){document.getElementById("gateway_response").value=JSON.stringify(e.setupIntent),document.getElementById("is_default").value=document.getElementById("proxy_is_default").checked,processingOverlay(!1),document.getElementById("server_response").submit()}},{key:"handle",value:function(){var e=this;return this.setupStripe().createElement().mountCardElement(),this.cardButton.addEventListener("click",(function(){e.handleStripe(e.stripe,e.cardHolderName)})),this}}])&&n(t.prototype,r),a&&n(t,a),e}())(document.querySelector('meta[name="stripe-publishable-key"]').content).handle()}});
|
||||
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var a=t[r]={i:r,l:!1,exports:{}};return e[r].call(a.exports,a,a.exports,n),a.l=!0,a.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 a in e)n.d(r,a,function(t){return e[t]}.bind(null,a));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=1)}({1:function(e,t,n){e.exports=n("jzun")},jzun:function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}new(function(){function e(t){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.key=t,this.cardHolderName=document.getElementById("cardholder-name"),this.cardButton=document.getElementById("card-button"),this.clientSecret=this.cardButton.dataset.secret}var t,r,a;return t=e,(r=[{key:"setupStripe",value:function(){return this.stripe=Stripe(this.key),this.elements=this.stripe.elements(),this}},{key:"createElement",value:function(){return this.cardElement=this.elements.create("card"),this}},{key:"mountCardElement",value:function(){return this.cardElement.mount("#card-element"),this}},{key:"handleStripe",value:function(e,t){var n=this;this.cardButton.disabled=!0,this.cardButton.querySelector("span").classList.add("hidden"),this.cardButton.querySelector("svg").classList.remove("hidden"),e.handleCardSetup(this.clientSecret,this.cardElement,{payment_method_data:{billing_details:{name:t.value}}}).then((function(e){return e.error?n.handleFailure(e):n.handleSuccess(e)}))}},{key:"handleFailure",value:function(e){this.cardButton.disabled=!1,this.cardButton.querySelector("span").classList.remove("hidden"),this.cardButton.querySelector("svg").classList.add("hidden");var t=document.getElementById("errors");t.textContent="",t.textContent=e.error.message,t.hidden=!1}},{key:"handleSuccess",value:function(e){document.getElementById("gateway_response").value=JSON.stringify(e.setupIntent),document.getElementById("is_default").value=document.getElementById("proxy_is_default").checked,document.getElementById("server_response").submit()}},{key:"handle",value:function(){var e=this;return this.setupStripe().createElement().mountCardElement(),this.cardButton.addEventListener("click",(function(){e.handleStripe(e.stripe,e.cardHolderName)})),this}}])&&n(t.prototype,r),a&&n(t,a),e}())(document.querySelector('meta[name="stripe-publishable-key"]').content).handle()}});
|
@ -5,7 +5,7 @@
|
||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=d7e708d66a9c769b4c6e",
|
||||
"/js/clients/payment_methods/authorize-ach.js": "/js/clients/payment_methods/authorize-ach.js?id=c73d32c192c36fe44123",
|
||||
"/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=044dc1eabd2ee074cb2b",
|
||||
"/js/clients/payment_methods/authorize-stripe-card.js": "/js/clients/payment_methods/authorize-stripe-card.js?id=7f17c8e95d622ae12253",
|
||||
"/js/clients/payment_methods/authorize-stripe-card.js": "/js/clients/payment_methods/authorize-stripe-card.js?id=b1389b07a0fdb263308f",
|
||||
"/js/clients/payments/alipay.js": "/js/clients/payments/alipay.js?id=6be66f65ec4537b3b05e",
|
||||
"/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=80605d1869d3b97c5090",
|
||||
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=7200ac43b87bddf1bedc",
|
||||
|
@ -75,8 +75,6 @@ class AuthorizeStripeCard {
|
||||
'proxy_is_default'
|
||||
).checked;
|
||||
|
||||
processingOverlay(false);
|
||||
|
||||
document.getElementById('server_response').submit();
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@
|
||||
@csrf
|
||||
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
|
||||
<input type="hidden" name="action" value="payment">
|
||||
<button class="px-2 py-1 mr-3 text-xs uppercase button button-primary bg-primary">
|
||||
<button class="px-2 py-1 mr-3 text-xs uppercase button button-primary bg-primary" data-cy="pay-now">
|
||||
@lang('texts.pay_now')
|
||||
</button>
|
||||
</form>
|
||||
|
@ -12,16 +12,16 @@
|
||||
<div class="relative" x-data="{ open: false }" x-on:click.away="open = false">
|
||||
<!-- Add payment method button -->
|
||||
@if($client->getCreditCardGateway() || $client->getBankTransferGateway())
|
||||
<button x-on:click="open = !open" class="button button-primary bg-primary">{{ ctrans('texts.add_payment_method') }}</button>
|
||||
<button x-on:click="open = !open" class="button button-primary bg-primary" data-cy="add-payment-method">{{ ctrans('texts.add_payment_method') }}</button>
|
||||
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg">
|
||||
<div class="py-1 rounded-md bg-white shadow-xs">
|
||||
@if($client->getCreditCardGateway())
|
||||
<a href="{{ route('client.payment_methods.create', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
||||
<a data-cy="add-credit-card-link" href="{{ route('client.payment_methods.create', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
||||
{{ ctrans('texts.credit_card') }}
|
||||
</a>
|
||||
@endif
|
||||
@if($client->getBankTransferGateway())
|
||||
<a href="{{ route('client.payment_methods.create', ['method' => $client->getBankTransferMethodType()]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
||||
<a data-cy="add-bank-account-link" href="{{ route('client.payment_methods.create', ['method' => $client->getBankTransferMethodType()]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
||||
{{ ctrans('texts.bank_account') }}
|
||||
</a>
|
||||
@endif
|
||||
@ -91,7 +91,7 @@
|
||||
</svg>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium">
|
||||
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium" data-cy="view-payment-method">
|
||||
<a href="{{ route('client.payment_methods.show', $payment_method->hashed_id) }}"
|
||||
class="text-blue-600 hover:text-indigo-900 focus:outline-none focus:underline">
|
||||
@lang('texts.view')
|
||||
|
@ -73,7 +73,7 @@
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span>{{ __('texts.save') }}</span>
|
||||
<span>{{ __('texts.pay_now') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
|
@ -21,7 +21,7 @@
|
||||
<div class="flex justify-end mb-2">
|
||||
<!-- Pay now button -->
|
||||
@if(count($payment_methods) > 0)
|
||||
<div x-data="{ open: false }" @keydown.window.escape="open = false" @click.away="open = false" class="relative inline-block text-left">
|
||||
<div x-data="{ open: false }" @keydown.window.escape="open = false" @click.away="open = false" class="relative inline-block text-left" data-cy="payment-methods-dropdown">
|
||||
<div>
|
||||
<div class="rounded-md shadow-sm">
|
||||
<button @click="open = !open" type="button" class="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800">
|
||||
@ -36,7 +36,7 @@
|
||||
<div class="bg-white rounded-md shadow-xs">
|
||||
<div class="py-1">
|
||||
@foreach($payment_methods as $payment_method)
|
||||
<a href="#" @click="{ open = false }" data-company-gateway-id="{{ $payment_method['company_gateway_id'] }}" data-gateway-type-id="{{ $payment_method['gateway_type_id'] }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">
|
||||
<a href="#" @click="{ open = false }" data-company-gateway-id="{{ $payment_method['company_gateway_id'] }}" data-gateway-type-id="{{ $payment_method['gateway_type_id'] }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" data-cy="payment-method">
|
||||
{{ $payment_method['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
|
@ -36,7 +36,7 @@
|
||||
<form action="{{ route('client.payment_methods.destroy', [$payment_method->hashed_id, 'method' => $payment_method->gateway_type->id]) }}" method="post">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="button button-danger button-block">
|
||||
<button type="submit" class="button button-danger button-block" data-cy="confirm-payment-removal">
|
||||
{{ ctrans('texts.remove') }}
|
||||
</button>
|
||||
</form>
|
||||
|
@ -98,7 +98,7 @@
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
||||
<div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }">
|
||||
<button class="button button-danger" translate @click="open = true">
|
||||
<button class="button button-danger" @click="open = true" id="open-delete-popup">
|
||||
{{ ctrans('texts.remove_payment_method') }}
|
||||
</button>
|
||||
@include('portal.ninja2020.payment_methods.includes.modals.removal')
|
||||
|
Loading…
x
Reference in New Issue
Block a user