mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
- Rewrite of checkout-credit-card.js
- Fixed bug with detach() on driver class - Changed the way token checking works from frontend - Support for multiple payment methods on payment page - Changed save card label to "Save payment method details" - Updated relevant Javascript assets
This commit is contained in:
parent
8d9f9ccf17
commit
2fccfe2df1
@ -78,7 +78,7 @@ class PaymentController extends Controller
|
||||
public function process(Request $request)
|
||||
{
|
||||
$is_credit_payment = false;
|
||||
$token = false;
|
||||
$tokens = [];
|
||||
|
||||
if ($request->input('company_gateway_id') == CompanyGateway::GATEWAY_CREDIT) {
|
||||
$is_credit_payment = true;
|
||||
@ -229,7 +229,10 @@ class PaymentController extends Controller
|
||||
$fee_totals = $first_invoice->amount - $starting_invoice_amount;
|
||||
|
||||
if ($gateway) {
|
||||
$token = auth()->user()->client->gateway_token($gateway->id, $payment_method_id);
|
||||
$tokens = auth()->user()->client->gateway_tokens()
|
||||
->whereCompanyGatewayId($gateway->id)
|
||||
->whereGatewayTypeId($payment_method_id)
|
||||
->get();
|
||||
}
|
||||
|
||||
$payment_hash = new PaymentHash;
|
||||
@ -250,7 +253,7 @@ class PaymentController extends Controller
|
||||
'payment_hash' => $payment_hash->hash,
|
||||
'total' => $totals,
|
||||
'invoices' => $payable_invoices,
|
||||
'token' => $token,
|
||||
'tokens' => $tokens,
|
||||
'payment_method_id' => $payment_method_id,
|
||||
'amount_with_fee' => $invoice_totals + $fee_totals,
|
||||
];
|
||||
@ -285,24 +288,21 @@ class PaymentController extends Controller
|
||||
|
||||
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->payment_hash])->first();
|
||||
|
||||
try {
|
||||
return $gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod($request->input('payment_method_id'))
|
||||
->setPaymentHash($payment_hash)
|
||||
->checkRequirements()
|
||||
->processPaymentResponse($request);
|
||||
} catch(\Exception $e) {
|
||||
SystemLogger::dispatch(
|
||||
|
||||
|
||||
/*SystemLogger::dispatch(
|
||||
$e->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_FAILURE,
|
||||
auth('contact')->user()->client
|
||||
);
|
||||
|
||||
throw new PaymentFailed($e->getMessage());
|
||||
}
|
||||
);*/
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,7 +91,7 @@ class CreditCard
|
||||
$this->checkout->payment_hash->data = array_merge((array) $this->checkout->payment_hash->data, $state);
|
||||
$this->checkout->payment_hash->save();
|
||||
|
||||
if ($request->has('token') && !is_null($request->token) && $request->pay_with_token) {
|
||||
if ($request->has('token') && !is_null($request->token) && !empty($request->token)) {
|
||||
return $this->attemptPaymentUsingToken($request);
|
||||
}
|
||||
|
||||
|
@ -208,4 +208,9 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
return $this->processInternallyFailedPayment($this, $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function detach(ClientGatewayToken $clientGatewayToken)
|
||||
{
|
||||
// Gateway doesn't support this feature.
|
||||
}
|
||||
}
|
||||
|
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
2
public/js/clients/payments/checkout-credit-card.js
vendored
Normal file
2
public/js/clients/payments/checkout-credit-card.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! For license information please see checkout-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=8)}({8:function(e,t,n){e.exports=n("fQHp")},fQHp: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(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.tokens=[]}var t,r,o;return t=e,(r=[{key:"mountFrames",value:function(){console.log("Mount checkout frames..")}},{key:"handlePaymentUsingToken",value:function(e){document.getElementById("checkout--container").classList.add("hidden"),document.getElementById("pay-now-with-token--container").classList.remove("hidden"),document.querySelector("input[name=token]").value=e.target.dataset.token}},{key:"handlePaymentUsingCreditCard",value:function(e){var t;document.getElementById("checkout--container").classList.remove("hidden"),document.getElementById("pay-now-with-token--container").classList.add("hidden");var n=document.getElementById("pay-button"),r=null!==(t=document.querySelector('meta[name="public-key"]').content)&&void 0!==t?t:"",o=document.getElementById("payment-form");Frames.init(r),Frames.addEventHandler(Frames.Events.CARD_VALIDATION_CHANGED,(function(e){n.disabled=!Frames.isCardValid()})),Frames.addEventHandler(Frames.Events.CARD_TOKENIZED,(function(e){document.querySelector('input[name="gateway_response"]').value=JSON.stringify(e),document.querySelector('input[name="store_card"]').value=document.querySelector("input[name=token-billing-checkbox]:checked").value,document.getElementById("server-response").submit()})),o.addEventListener("submit",(function(e){e.preventDefault(),Frames.submitCard()}))}},{key:"completePaymentUsingToken",value:function(e){var t=document.getElementById("pay-now-with-token");t.disabled=!0,t.querySelector("svg").classList.remove("hidden"),t.querySelector("span").classList.add("hidden"),document.getElementById("server-response").submit()}},{key:"handle",value:function(){var e=this;this.handlePaymentUsingCreditCard(),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(t){return t.addEventListener("click",e.handlePaymentUsingToken)})),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",this.handlePaymentUsingCreditCard),document.getElementById("pay-now-with-token").addEventListener("click",this.completePaymentUsingToken)}}])&&n(t.prototype,r),o&&n(t,o),e}())).handle()}});
|
0
public/js/clients/payments/checkout.com.js.LICENSE.txt → public/js/clients/payments/checkout-credit-card.js.LICENSE.txt
Executable file → Normal file
0
public/js/clients/payments/checkout.com.js.LICENSE.txt → public/js/clients/payments/checkout-credit-card.js.LICENSE.txt
Executable file → Normal file
@ -1,12 +1,12 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=a33a5a58bfc6e2174841",
|
||||
"/css/app.css": "/css/app.css?id=0bc73459b671b24af838",
|
||||
"/css/app.css": "/css/app.css?id=599b11149976e86c83a3",
|
||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
|
||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
|
||||
"/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=cddcd46c630c71737bda",
|
||||
"/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=fe43d5a1ad3ec29387d4",
|
||||
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=5469146cd629ea1b5c20",
|
||||
"/js/clients/payments/checkout.com.js": "/js/clients/payments/checkout.com.js?id=ce184db42e52d403c21b",
|
||||
"/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=935645b176c73b7831f4",
|
||||
"/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=f4659d26a26d2f408397",
|
||||
|
90
resources/js/clients/payments/checkout-credit-card.js
vendored
Normal file
90
resources/js/clients/payments/checkout-credit-card.js
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
class CheckoutCreditCard {
|
||||
constructor() {
|
||||
this.tokens = [];
|
||||
}
|
||||
|
||||
mountFrames() {
|
||||
console.log('Mount checkout frames..');
|
||||
}
|
||||
|
||||
handlePaymentUsingToken(e) {
|
||||
document.getElementById('checkout--container').classList.add('hidden');
|
||||
document.getElementById('pay-now-with-token--container').classList.remove('hidden');
|
||||
|
||||
document
|
||||
.querySelector('input[name=token]')
|
||||
.value = e.target.dataset.token;
|
||||
}
|
||||
|
||||
handlePaymentUsingCreditCard(e) {
|
||||
document.getElementById('checkout--container').classList.remove('hidden');
|
||||
document.getElementById('pay-now-with-token--container').classList.add('hidden');
|
||||
|
||||
const payButton = document.getElementById('pay-button');
|
||||
const publicKey = document.querySelector('meta[name="public-key"]').content ?? '';
|
||||
const form = document.getElementById('payment-form');
|
||||
|
||||
Frames.init(publicKey);
|
||||
|
||||
Frames.addEventHandler(Frames.Events.CARD_VALIDATION_CHANGED, function (event) {
|
||||
payButton.disabled = !Frames.isCardValid();
|
||||
});
|
||||
|
||||
Frames.addEventHandler(Frames.Events.CARD_TOKENIZED, function (event) {
|
||||
document.querySelector(
|
||||
'input[name="gateway_response"]'
|
||||
).value = JSON.stringify(event);
|
||||
|
||||
document.querySelector(
|
||||
'input[name="store_card"]'
|
||||
).value = document.querySelector(
|
||||
'input[name=token-billing-checkbox]:checked'
|
||||
).value;
|
||||
|
||||
document.getElementById('server-response').submit();
|
||||
});
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
Frames.submitCard();
|
||||
});
|
||||
}
|
||||
|
||||
completePaymentUsingToken(e) {
|
||||
let btn = document.getElementById('pay-now-with-token');
|
||||
|
||||
btn.disabled = true;
|
||||
btn.querySelector('svg').classList.remove('hidden');
|
||||
btn.querySelector('span').classList.add('hidden');
|
||||
|
||||
document.getElementById('server-response').submit();
|
||||
}
|
||||
|
||||
handle() {
|
||||
this.handlePaymentUsingCreditCard();
|
||||
|
||||
Array
|
||||
.from(document.getElementsByClassName('toggle-payment-with-token'))
|
||||
.forEach((element) => element.addEventListener('click', this.handlePaymentUsingToken));
|
||||
|
||||
document
|
||||
.getElementById('toggle-payment-with-credit-card')
|
||||
.addEventListener('click', this.handlePaymentUsingCreditCard);
|
||||
|
||||
document
|
||||
.getElementById('pay-now-with-token')
|
||||
.addEventListener('click', this.completePaymentUsingToken);
|
||||
}
|
||||
}
|
||||
|
||||
new CheckoutCreditCard().handle();
|
@ -3374,4 +3374,6 @@ return [
|
||||
'required_client_info_save_label' => 'We will save this, so you don\'t have to enter it next time.',
|
||||
'notification_credit_bounced' => 'We were unable to deliver Credit :invoice to :contact.',
|
||||
'notification_credit_bounced_subject' => 'Unable to deliver Credit :invoice',
|
||||
|
||||
'save_payment_method_details' => 'Save payment method details',
|
||||
];
|
||||
|
@ -7,7 +7,109 @@
|
||||
<meta name="currency" content="{{ $currency }}">
|
||||
<meta name="reference" content="{{ $payment_hash }}">
|
||||
|
||||
<style>*,*::after,*::before{box-sizing:border-box}html{background-color:#FFF;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif}#payment-form{width:31.5rem;margin:0 auto}iframe{width:100%}.one-liner{display:flex;flex-direction:column}#pay-button{border:none;border-radius:3px;color:#FFF;font-weight:500;height:40px;width:100%;background-color:#13395E;box-shadow:0 1px 3px 0 rgba(19,57,94,0.4)}#pay-button:active{background-color:#0B2A49;box-shadow:0 1px 3px 0 rgba(19,57,94,0.4)}#pay-button:hover{background-color:#15406B;box-shadow:0 2px 5px 0 rgba(19,57,94,0.4)}#pay-button:disabled{background-color:#697887;box-shadow:none}#pay-button:not(:disabled){cursor:pointer}.card-frame{border:solid 1px #13395E;border-radius:3px;width:100%;margin-bottom:8px;height:40px;box-shadow:0 1px 3px 0 rgba(19,57,94,0.2)}.card-frame.frame--rendered{opacity:1}.card-frame.frame--rendered.frame--focus{border:solid 1px #13395E;box-shadow:0 2px 5px 0 rgba(19,57,94,0.15)}.card-frame.frame--rendered.frame--invalid{border:solid 1px #D96830;box-shadow:0 2px 5px 0 rgba(217,104,48,0.15)}.success-payment-message{color:#13395E;line-height:1.4}.token{color:#b35e14;font-size:0.9rem;font-family:monospace}@media screen and (min-width: 31rem){.one-liner{flex-direction:row}.card-frame{width:318px;margin-bottom:0}#pay-button{width:175px;margin-left:8px}}</style>
|
||||
<style>*, *::after, *::before {
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: #FFF;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif
|
||||
}
|
||||
|
||||
#payment-form {
|
||||
width: 31.5rem;
|
||||
margin: 0 auto
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.one-liner {
|
||||
display: flex;
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
#pay-button {
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
color: #FFF;
|
||||
font-weight: 500;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
background-color: #13395E;
|
||||
box-shadow: 0 1px 3px 0 rgba(19, 57, 94, 0.4)
|
||||
}
|
||||
|
||||
#pay-button:active {
|
||||
background-color: #0B2A49;
|
||||
box-shadow: 0 1px 3px 0 rgba(19, 57, 94, 0.4)
|
||||
}
|
||||
|
||||
#pay-button:hover {
|
||||
background-color: #15406B;
|
||||
box-shadow: 0 2px 5px 0 rgba(19, 57, 94, 0.4)
|
||||
}
|
||||
|
||||
#pay-button:disabled {
|
||||
background-color: #697887;
|
||||
box-shadow: none
|
||||
}
|
||||
|
||||
#pay-button:not(:disabled) {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.card-frame {
|
||||
border: solid 1px #13395E;
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
height: 40px;
|
||||
box-shadow: 0 1px 3px 0 rgba(19, 57, 94, 0.2)
|
||||
}
|
||||
|
||||
.card-frame.frame--rendered {
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
.card-frame.frame--rendered.frame--focus {
|
||||
border: solid 1px #13395E;
|
||||
box-shadow: 0 2px 5px 0 rgba(19, 57, 94, 0.15)
|
||||
}
|
||||
|
||||
.card-frame.frame--rendered.frame--invalid {
|
||||
border: solid 1px #D96830;
|
||||
box-shadow: 0 2px 5px 0 rgba(217, 104, 48, 0.15)
|
||||
}
|
||||
|
||||
.success-payment-message {
|
||||
color: #13395E;
|
||||
line-height: 1.4
|
||||
}
|
||||
|
||||
.token {
|
||||
color: #b35e14;
|
||||
font-size: 0.9rem;
|
||||
font-family: monospace
|
||||
}
|
||||
|
||||
@media screen and (min-width: 31rem) {
|
||||
.one-liner {
|
||||
flex-direction: row
|
||||
}
|
||||
|
||||
.card-frame {
|
||||
width: 318px;
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
#pay-button {
|
||||
width: 175px;
|
||||
margin-left: 8px
|
||||
}
|
||||
}</style>
|
||||
|
||||
<script src="https://cdn.checkout.com/js/framesv2.min.js"></script>
|
||||
@endsection
|
||||
|
||||
@ -24,10 +126,7 @@
|
||||
<input type="hidden" name="raw_value" value="{{ $raw_value }}">
|
||||
<input type="hidden" name="currency" value="{{ $currency }}">
|
||||
<input type="hidden" name="pay_with_token" value="false">
|
||||
|
||||
@isset($token)
|
||||
<input type="hidden" name="token" value="{{ $token->token }}">
|
||||
@endisset
|
||||
<input type="hidden" name="token" value="">
|
||||
</form>
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')])
|
||||
@ -37,22 +136,25 @@
|
||||
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
|
||||
@isset($token)
|
||||
<label class="mr-4">
|
||||
<input
|
||||
type="radio"
|
||||
id="toggle-payment-with-token"
|
||||
class="form-radio cursor-pointer" name="payment-type" />
|
||||
<span class="ml-1 cursor-pointer">**** {{ $token->meta->last4 }}</span>
|
||||
</label>
|
||||
@if(count($tokens) > 0)
|
||||
@foreach($tokens as $token)
|
||||
<label class="mr-4">
|
||||
<input
|
||||
type="radio"
|
||||
data-token="{{ $token->token }}"
|
||||
name="payment-type"
|
||||
class="form-radio cursor-pointer toggle-payment-with-token"/>
|
||||
<span class="ml-1 cursor-pointer">**** {{ optional($token->meta)->last4 }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
@endisset
|
||||
|
||||
|
||||
<label>
|
||||
<input
|
||||
<input
|
||||
type="radio"
|
||||
id="toggle-payment-with-credit-card"
|
||||
class="form-radio cursor-pointer"
|
||||
name="payment-type"
|
||||
name="payment-type"
|
||||
checked/>
|
||||
<span class="ml-1 cursor-pointer">{{ __('texts.credit_card') }}</span>
|
||||
</label>
|
||||
@ -78,12 +180,12 @@
|
||||
@endcomponent
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
<div class="hidden" id="pay-now-with-token--container">
|
||||
<div class="hidden" id="pay-now-with-token--container">
|
||||
@include('portal.ninja2020.gateways.includes.pay_now', ['id' => 'pay-now-with-token'])
|
||||
</div>
|
||||
</div>
|
||||
@endcomponent
|
||||
@endsection
|
||||
|
||||
@section('gateway_footer')
|
||||
<script src="{{ asset('js/clients/payments/checkout.com.js') }}"></script>
|
||||
@endsection
|
||||
<script src="{{ asset('js/clients/payments/checkout-credit-card.js') }}"></script>
|
||||
@endsection
|
||||
|
@ -1,7 +1,7 @@
|
||||
@unless(isset($show_save) && $show_save == false)
|
||||
<div class="{{ ($gateway->token_billing == 'optin' || $gateway->token_billing == 'optout') ? 'sm:grid' : 'hidden' }} 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.token_billing_checkbox') }}
|
||||
{{ ctrans('texts.save_payment_method_details') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
<label class="mr-4">
|
||||
|
8
webpack.mix.js
vendored
8
webpack.mix.js
vendored
@ -31,8 +31,8 @@ mix.js("resources/js/app.js", "public/js")
|
||||
"public/js/clients/payments/stripe-alipay.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/payments/checkout.com.js",
|
||||
"public/js/clients/payments/checkout.com.js"
|
||||
"resources/js/clients/payments/checkout-credit-card.js",
|
||||
"public/js/clients/payments/checkout-credit-card.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/quotes/action-selectors.js",
|
||||
@ -47,7 +47,7 @@ mix.js("resources/js/app.js", "public/js")
|
||||
"public/js/clients/payments/stripe-credit-card.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/setup/setup.js",
|
||||
"resources/js/setup/setup.js",
|
||||
"public/js/setup/setup.js"
|
||||
)
|
||||
.js(
|
||||
@ -71,4 +71,4 @@ mix.sass("resources/sass/app.scss", "public/css")
|
||||
postCss: [tailwindcss("./tailwind.config.js")]
|
||||
});
|
||||
mix.version();
|
||||
mix.disableNotifications();
|
||||
mix.disableNotifications();
|
||||
|
Loading…
x
Reference in New Issue
Block a user