1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 21:22:58 +01:00

Merge pull request #4828 from beganovich/v5-0302-cypress-checkout

(v5) Fixes & tests for gateways
This commit is contained in:
Benjamin Beganović 2021-02-03 17:02:09 +01:00 committed by GitHub
commit aec701040a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 252 additions and 60 deletions

View File

@ -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) {

View File

@ -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);

View File

@ -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();

View File

@ -1,5 +1,8 @@
{
"video": false,
"baseUrl": "http://localhost:8000/",
"chromeWebSecurity": false
"baseUrl": "https://localhost:8000/",
"chromeWebSecurity": false,
"env": {
"runningEnvironment": "native"
}
}

View File

@ -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.');
});
});

View File

@ -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');
});
});

35
cypress/support/account.js vendored Normal file
View File

@ -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', () => {
// ..
});

6
cypress/support/artisan.js vendored Normal file
View File

@ -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}`);
});

View File

@ -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')

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@
"/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=98e406fa8e4db0e93427",
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=c4012ad90f17d60432ad",
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=6dbe9316b98deea55421",
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=9418a9c5c137994c4bd8",
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=c37c3892d35c50d82521",
"/js/clients/payments/stripe-sofort.js": "/js/clients/payments/stripe-sofort.js?id=9b9fd56d655ad238f149",
"/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=1b8f9325aa6e8595e7fa",
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=85bcae0a646882e56b12",

View File

@ -95,7 +95,7 @@ class StripeCreditCard {
if (tokenBillingCheckbox) {
document.querySelector('input[name="store_card"]').value =
tokenBillingCheckbox.checked;
tokenBillingCheckbox.value;
}
document.getElementById('server-response').submit();

View File

@ -1,4 +1,10 @@
@if($gateway->company_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)
<div class="sm:grid px-4 py-5 sm:grid-cols-3 sm:gap-4 sm:px-6" id="save-card--container">
<dt class="text-sm leading-5 font-medium text-gray-500">
{{ ctrans('texts.save_payment_method_details') }}

View File

@ -24,7 +24,7 @@
<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">
<button data-cy="pay-now-dropdown" @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">
{{ ctrans('texts.pay_now') }}
<svg class="w-5 h-5 ml-2 -mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
@ -35,13 +35,13 @@
<div x-show="open" class="absolute right-0 w-56 mt-2 origin-top-right rounded-md shadow-lg">
<div class="bg-white rounded-md shadow-xs">
<div class="py-1">
@foreach($payment_methods as $payment_method)
@foreach($payment_methods as $index => $payment_method)
@if($payment_method['label'] == 'Custom')
<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">
<a href="#" @click="{ open = false }" data-cy="pay-with-custom" 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">
{{ \App\Models\CompanyGateway::find($payment_method['company_gateway_id'])->firstOrFail()->getConfigField('name') }}
</a>
@elseif($total > 0)
<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">
<a href="#" @click="{ open = false }" data-cy="pay-with-{{ $index }}" 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>
@endif