mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
PayTrace improvements (#43)
* Refactor credit card payment JavaScript * CreditCard.php: Rename $paytrace_driver to $paytrace * Credit card: Support for token billing (frontend) * Fixes for "save card" label * Credit card: Authorize * Allow PayTrace to be seeded from CreateSingleAccount * Add PayTrace decrypted config to ninja.testvars * Extract to separate JavaScript * Scaffold Dusk test * CreditCard -> CreditCardTest * CreditCard -> CreditCardTest * Scaffold pay with new card test * Fixes for gateway_key in CreateSingleAccount * Production builds
This commit is contained in:
parent
5ea07be358
commit
b7c248eec5
@ -742,6 +742,27 @@ class CreateSingleAccount extends Command
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->save();
|
||||
}
|
||||
|
||||
if (config('ninja.testvars.paytrace.decrypted') && ($this->gateway == 'all' || $this->gateway == 'paytrace')) {
|
||||
$cg = new CompanyGateway;
|
||||
$cg->company_id = $company->id;
|
||||
$cg->user_id = $user->id;
|
||||
$cg->gateway_key = 'bbd736b3254b0aabed6ad7fda1298c88';
|
||||
$cg->require_cvv = true;
|
||||
$cg->require_billing_address = true;
|
||||
$cg->require_shipping_address = true;
|
||||
$cg->update_details = true;
|
||||
$cg->config = encrypt(config('ninja.testvars.paytrace.decrypted'));
|
||||
$cg->save();
|
||||
|
||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||
|
||||
$fees_and_limits = new stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function createRecurringInvoice($client)
|
||||
|
@ -33,18 +33,18 @@ class CreditCard
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $paytrace_driver;
|
||||
public $paytrace;
|
||||
|
||||
public function __construct(PaytracePaymentDriver $paytrace_driver)
|
||||
public function __construct(PaytracePaymentDriver $paytrace)
|
||||
{
|
||||
$this->paytrace_driver = $paytrace_driver;
|
||||
$this->paytrace = $paytrace;
|
||||
}
|
||||
|
||||
public function authorizeView($data)
|
||||
{
|
||||
|
||||
$data['client_key'] = $this->paytrace_driver->getAuthToken();
|
||||
$data['gateway'] = $this->paytrace_driver;
|
||||
$data['client_key'] = $this->paytrace->getAuthToken();
|
||||
$data['gateway'] = $this->paytrace;
|
||||
|
||||
return render('gateways.paytrace.authorize', $data);
|
||||
}
|
||||
@ -94,11 +94,11 @@ class CreditCard
|
||||
'customer_id' => Str::random(32),
|
||||
'hpf_token' => $data['HPF_Token'],
|
||||
'enc_key' => $data['enc_key'],
|
||||
'integrator_id' => $this->company_gateway->getConfigField('integratorId'),
|
||||
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
|
||||
'billing_address' => $this->buildBillingAddress(),
|
||||
];
|
||||
|
||||
$response = $this->paytrace_driver->gatewayRequest('/v1/customer/pt_protect_create', $post_data);
|
||||
$response = $this->paytrace->gatewayRequest('/v1/customer/pt_protect_create', $post_data);
|
||||
|
||||
$cgt = [];
|
||||
$cgt['token'] = $response->customer_id;
|
||||
@ -115,15 +115,15 @@ class CreditCard
|
||||
|
||||
$cgt['payment_meta'] = $payment_meta;
|
||||
|
||||
$token = $this->paytrace_driver->storeGatewayToken($cgt, []);
|
||||
$token = $this->paytrace->storeGatewayToken($cgt, []);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function getCustomerProfile($customer_id)
|
||||
{
|
||||
$profile = $this->paytrace_driver->gatewayRequest('/v1/customer/export', [
|
||||
'integrator_id' => $this->company_gateway->getConfigField('integratorId'),
|
||||
$profile = $this->paytrace->gatewayRequest('/v1/customer/export', [
|
||||
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
|
||||
'customer_id' => $customer_id,
|
||||
// 'include_bin' => true,
|
||||
]);
|
||||
@ -135,19 +135,19 @@ class CreditCard
|
||||
private function buildBillingAddress()
|
||||
{
|
||||
return [
|
||||
'name' => $this->paytrace_driver->client->present()->name(),
|
||||
'street_address' => $this->paytrace_driver->client->address1,
|
||||
'city' => $this->paytrace_driver->client->city,
|
||||
'state' => $this->paytrace_driver->client->state,
|
||||
'zip' => $this->paytrace_driver->client->postal_code
|
||||
'name' => $this->paytrace->client->present()->name(),
|
||||
'street_address' => $this->paytrace->client->address1,
|
||||
'city' => $this->paytrace->client->city,
|
||||
'state' => $this->paytrace->client->state,
|
||||
'zip' => $this->paytrace->client->postal_code
|
||||
];
|
||||
}
|
||||
|
||||
public function paymentView($data)
|
||||
{
|
||||
|
||||
$data['client_key'] = $this->paytrace_driver->getAuthToken();
|
||||
$data['gateway'] = $this->paytrace_driver;
|
||||
$data['client_key'] = $this->paytrace->getAuthToken();
|
||||
$data['gateway'] = $this->paytrace;
|
||||
|
||||
return render('gateways.paytrace.pay', $data);
|
||||
|
||||
@ -171,13 +171,13 @@ class CreditCard
|
||||
$data = [
|
||||
'hpf_token' => $response_array['HPF_Token'],
|
||||
'enc_key' => $response_array['enc_key'],
|
||||
'integrator_id' => $this->company_gateway->getConfigField('integratorId'),
|
||||
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
|
||||
'billing_address' => $this->buildBillingAddress(),
|
||||
'amount' => $request->input('amount_with_fee'),
|
||||
'invoice_id' => $this->harvestInvoiceId(),
|
||||
];
|
||||
|
||||
$response = $this->paytrace_driver->gatewayRequest('/v1/transactions/sale/pt_protect', $data);
|
||||
$response = $this->paytrace->gatewayRequest('/v1/transactions/sale/pt_protect', $data);
|
||||
|
||||
if($response->success)
|
||||
return $this->processSuccessfulPayment($response);
|
||||
@ -195,10 +195,10 @@ class CreditCard
|
||||
'amount' => $request->input('amount_with_fee'),
|
||||
];
|
||||
|
||||
$response = $this->paytrace_driver->gatewayRequest('/v1/transactions/sale/by_customer', $data);
|
||||
$response = $this->paytrace->gatewayRequest('/v1/transactions/sale/by_customer', $data);
|
||||
|
||||
if($response->success){
|
||||
$this->paytrace_driver->logSuccessfulGatewayResponse(['response' => $response, 'data' => $this->paytrace_driver->payment_hash], SystemLog::TYPE_PAYTRACE);
|
||||
$this->paytrace->logSuccessfulGatewayResponse(['response' => $response, 'data' => $this->paytrace->payment_hash], SystemLog::TYPE_PAYTRACE);
|
||||
|
||||
return $this->processSuccessfulPayment($response);
|
||||
}
|
||||
@ -208,7 +208,7 @@ class CreditCard
|
||||
|
||||
private function harvestInvoiceId()
|
||||
{
|
||||
$_invoice = collect($this->paytrace_driver->payment_hash->data->invoices)->first();
|
||||
$_invoice = collect($this->paytrace->payment_hash->data->invoices)->first();
|
||||
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
|
||||
|
||||
if($invoice)
|
||||
@ -219,7 +219,7 @@ class CreditCard
|
||||
|
||||
private function processSuccessfulPayment($response)
|
||||
{
|
||||
$amount = array_sum(array_column($this->paytrace_driver->payment_hash->invoices(), 'amount')) + $this->paytrace_driver->payment_hash->fee_total;
|
||||
$amount = array_sum(array_column($this->paytrace->payment_hash->invoices(), 'amount')) + $this->paytrace->payment_hash->fee_total;
|
||||
|
||||
$payment_record = [];
|
||||
$payment_record['amount'] = $amount;
|
||||
@ -227,7 +227,7 @@ class CreditCard
|
||||
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
|
||||
$payment_record['transaction_reference'] = $response->transaction_id;
|
||||
|
||||
$payment = $this->paytrace_driver->createPayment($payment_record, Payment::STATUS_COMPLETED);
|
||||
$payment = $this->paytrace->createPayment($payment_record, Payment::STATUS_COMPLETED);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||
|
||||
@ -249,7 +249,7 @@ class CreditCard
|
||||
'error_code' => $error_code,
|
||||
];
|
||||
|
||||
return $this->paytrace_driver->processUnsuccessfulTransaction($data);
|
||||
return $this->paytrace->processUnsuccessfulTransaction($data);
|
||||
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,8 @@ return [
|
||||
'braintree' => env('BRAINTREE_KEYS', ''),
|
||||
'paytrace' => [
|
||||
'username' => env('PAYTRACE_U', ''),
|
||||
'password' => env('PAYTRACE_P','')
|
||||
'password' => env('PAYTRACE_P',''),
|
||||
'decrypted' => env('PAYTRACE_KEYS', ''),
|
||||
],
|
||||
],
|
||||
'contact' => [
|
||||
|
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/paytrace-credit-card.js
vendored
Normal file
2
public/js/clients/payments/paytrace-credit-card.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! For license information please see paytrace-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=21)}({"0Swb":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(){var t;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.clientKey=null===(t=document.querySelector("meta[name=paytrace-client-key]"))||void 0===t?void 0:t.content}var t,r,o;return t=e,(r=[{key:"creditCardStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"100%"}}},{key:"codeStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"300px"}}},{key:"expStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"85px",type:"dropdown"}}},{key:"updatePayTraceLabels",value:function(){window.PTPayment.getControl("securityCode").label.text(document.querySelector("meta[name=ctrans-cvv]").content),window.PTPayment.getControl("creditCard").label.text(document.querySelector("meta[name=ctrans-card_number]").content),window.PTPayment.getControl("expiration").label.text(document.querySelector("meta[name=ctrans-expires]").content)}},{key:"setupPayTrace",value:function(){return window.PTPayment.setup({styles:{code:this.codeStyles,cc:this.creditCardStyles,exp:this.expStyles},authorization:{clientKey:this.clientKey}})}},{key:"handlePaymentWithCreditCard",value:function(e){var t=this;e.target.parentElement.disabled=!0,document.getElementById("errors").hidden=!0,window.PTPayment.validate((function(n){if(n.length>=1){var r=document.getElementById("errors");return r.textContent=n[0].description,r.hidden=!1,e.target.parentElement.disabled=!1}t.ptInstance.process().then((function(e){document.getElementById("HPF_Token").value=e.message.hpf_token,document.getElementById("enc_key").value=e.message.enc_key;var t=document.querySelector('input[name="token-billing-checkbox"]:checked');t&&(document.querySelector('input[name="store_card"]').value=t.value),document.getElementById("server_response").submit()})).catch((function(e){document.getElementById("errors").textContent=JSON.stringify(e),document.getElementById("errors").hidden=!1,console.log(e)}))}))}},{key:"handlePaymentWithToken",value:function(e){e.target.parentElement.disabled=!0,document.getElementById("server_response").submit()}},{key:"handle",value:function(){var e=this;this.setupPayTrace().then((function(t){var n;e.ptInstance=t,e.updatePayTraceLabels(),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(e){return e.addEventListener("click",(function(e){document.getElementById("paytrace--credit-card-container").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=e.target.dataset.token}))})),null===(n=document.getElementById("toggle-payment-with-credit-card"))||void 0===n||n.addEventListener("click",(function(e){document.getElementById("paytrace--credit-card-container").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value=""})),document.getElementById("pay-now").addEventListener("click",(function(t){return""===document.querySelector("input[name=token]").value?e.handlePaymentWithCreditCard(t):e.handlePaymentWithToken(t)}))}))}}])&&n(t.prototype,r),o&&n(t,o),e}())).handle()},21:function(e,t,n){e.exports=n("0Swb")}});
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 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://www.elastic.co/licensing/elastic-license
|
||||
*/
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
|
||||
"/css/app.css": "/css/app.css?id=f4c07fdabcbe50c9f4be",
|
||||
"/css/app.css": "/css/app.css?id=5d7e42fa72eef8af62f5",
|
||||
"/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/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
|
||||
@ -11,6 +11,7 @@
|
||||
"/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js?id=c35db3cbb65806ab6a8a",
|
||||
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=5469146cd629ea1b5c20",
|
||||
"/js/clients/payments/checkout-credit-card.js": "/js/clients/payments/checkout-credit-card.js?id=065e5450233cc5b47020",
|
||||
"/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=c8d3808a4c02d1392e96",
|
||||
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7",
|
||||
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17",
|
||||
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=f1719b79a2bb274d3f64",
|
||||
|
186
resources/js/clients/payments/paytrace-credit-card.js
vendored
Normal file
186
resources/js/clients/payments/paytrace-credit-card.js
vendored
Normal file
@ -0,0 +1,186 @@
|
||||
/**
|
||||
* 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://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
class PayTraceCreditCard {
|
||||
constructor() {
|
||||
this.clientKey = document.querySelector(
|
||||
'meta[name=paytrace-client-key]'
|
||||
)?.content;
|
||||
}
|
||||
|
||||
get creditCardStyles() {
|
||||
return {
|
||||
font_color: '#111827',
|
||||
border_color: 'rgba(210,214,220,1)',
|
||||
label_color: '#111827',
|
||||
label_size: '12pt',
|
||||
background_color: 'white',
|
||||
border_style: 'solid',
|
||||
font_size: '15pt',
|
||||
height: '30px',
|
||||
width: '100%',
|
||||
};
|
||||
}
|
||||
|
||||
get codeStyles() {
|
||||
return {
|
||||
font_color: '#111827',
|
||||
border_color: 'rgba(210,214,220,1)',
|
||||
label_color: '#111827',
|
||||
label_size: '12pt',
|
||||
background_color: 'white',
|
||||
border_style: 'solid',
|
||||
font_size: '15pt',
|
||||
height: '30px',
|
||||
width: '300px',
|
||||
};
|
||||
}
|
||||
|
||||
get expStyles() {
|
||||
return {
|
||||
font_color: '#111827',
|
||||
border_color: 'rgba(210,214,220,1)',
|
||||
label_color: '#111827',
|
||||
label_size: '12pt',
|
||||
background_color: 'white',
|
||||
border_style: 'solid',
|
||||
font_size: '15pt',
|
||||
height: '30px',
|
||||
width: '85px',
|
||||
type: 'dropdown',
|
||||
};
|
||||
}
|
||||
|
||||
updatePayTraceLabels() {
|
||||
window.PTPayment.getControl('securityCode').label.text(
|
||||
document.querySelector('meta[name=ctrans-cvv]').content
|
||||
);
|
||||
|
||||
window.PTPayment.getControl('creditCard').label.text(
|
||||
document.querySelector('meta[name=ctrans-card_number]').content
|
||||
);
|
||||
|
||||
window.PTPayment.getControl('expiration').label.text(
|
||||
document.querySelector('meta[name=ctrans-expires]').content
|
||||
);
|
||||
}
|
||||
|
||||
setupPayTrace() {
|
||||
return window.PTPayment.setup({
|
||||
styles: {
|
||||
code: this.codeStyles,
|
||||
cc: this.creditCardStyles,
|
||||
exp: this.expStyles,
|
||||
},
|
||||
authorization: {
|
||||
clientKey: this.clientKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
handlePaymentWithCreditCard(event) {
|
||||
event.target.parentElement.disabled = true;
|
||||
document.getElementById('errors').hidden = true;
|
||||
|
||||
window.PTPayment.validate((errors) => {
|
||||
if (errors.length >= 1) {
|
||||
let errorsContainer = document.getElementById('errors');
|
||||
|
||||
errorsContainer.textContent = errors[0].description;
|
||||
errorsContainer.hidden = false;
|
||||
|
||||
return (event.target.parentElement.disabled = false);
|
||||
}
|
||||
|
||||
this.ptInstance
|
||||
.process()
|
||||
.then((response) => {
|
||||
document.getElementById('HPF_Token').value =
|
||||
response.message.hpf_token;
|
||||
document.getElementById('enc_key').value =
|
||||
response.message.enc_key;
|
||||
|
||||
let tokenBillingCheckbox = document.querySelector(
|
||||
'input[name="token-billing-checkbox"]:checked'
|
||||
);
|
||||
|
||||
if (tokenBillingCheckbox) {
|
||||
document.querySelector(
|
||||
'input[name="store_card"]'
|
||||
).value = tokenBillingCheckbox.value;
|
||||
}
|
||||
|
||||
document.getElementById('server_response').submit();
|
||||
})
|
||||
.catch((error) => {
|
||||
document.getElementById(
|
||||
'errors'
|
||||
).textContent = JSON.stringify(error);
|
||||
document.getElementById('errors').hidden = false;
|
||||
|
||||
console.log(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handlePaymentWithToken(event) {
|
||||
event.target.parentElement.disabled = true;
|
||||
|
||||
document.getElementById('server_response').submit();
|
||||
}
|
||||
|
||||
handle() {
|
||||
this.setupPayTrace().then((instance) => {
|
||||
this.ptInstance = instance;
|
||||
this.updatePayTraceLabels();
|
||||
|
||||
Array.from(
|
||||
document.getElementsByClassName('toggle-payment-with-token')
|
||||
).forEach((element) =>
|
||||
element.addEventListener('click', (element) => {
|
||||
document
|
||||
.getElementById('paytrace--credit-card-container')
|
||||
.classList.add('hidden');
|
||||
document.getElementById(
|
||||
'save-card--container'
|
||||
).style.display = 'none';
|
||||
document.querySelector('input[name=token]').value =
|
||||
element.target.dataset.token;
|
||||
})
|
||||
);
|
||||
|
||||
document
|
||||
.getElementById('toggle-payment-with-credit-card')
|
||||
?.addEventListener('click', (element) => {
|
||||
document
|
||||
.getElementById('paytrace--credit-card-container')
|
||||
.classList.remove('hidden');
|
||||
document.getElementById(
|
||||
'save-card--container'
|
||||
).style.display = 'grid';
|
||||
document.querySelector('input[name=token]').value = '';
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById('pay-now')
|
||||
.addEventListener('click', (e) => {
|
||||
if (
|
||||
document.querySelector('input[name=token]').value === ''
|
||||
) {
|
||||
return this.handlePaymentWithCreditCard(e);
|
||||
}
|
||||
|
||||
return this.handlePaymentWithToken(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
new PayTraceCreditCard().handle();
|
@ -16,12 +16,12 @@
|
||||
value="true"/>
|
||||
<span class="ml-1 cursor-pointer">{{ ctrans('texts.yes') }}</span>
|
||||
</label>
|
||||
<labecoml>
|
||||
<label>
|
||||
<input type="radio" class="form-radio cursor-pointer" name="token-billing-checkbox"
|
||||
id="proxy_is_default"
|
||||
value="false" checked />
|
||||
<span class="ml-1 cursor-pointer">{{ ctrans('texts.no') }}</span>
|
||||
</labecoml>
|
||||
</label>
|
||||
</dd>
|
||||
</div>
|
||||
@else
|
||||
|
@ -1,145 +1,37 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')])
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title'
|
||||
=> ctrans('texts.payment_type_credit_card')])
|
||||
|
||||
@section('gateway_head')
|
||||
<meta name="paytrace-client-key" content="{{ $client_key }}">
|
||||
<meta name="ctrans-cvv" content="{{ ctrans('texts.cvv') }}">
|
||||
<meta name="ctrans-card_number" content="{{ ctrans('texts.card_number') }}">
|
||||
<meta name="ctrans-expires" content="{{ ctrans('texts.expires') }}">
|
||||
@endsection
|
||||
|
||||
@section('gateway_content')
|
||||
|
||||
@if(!Request::isSecure())
|
||||
<p class="alert alert-failure">{{ ctrans('texts.https_required') }}</p>
|
||||
@endif
|
||||
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}"
|
||||
method="post" id="server_response">
|
||||
@csrf
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->company_gateway->id }}">
|
||||
<input type="txt" id=HPF_Token name= HPF_Token hidden>
|
||||
<input type="txt" id=enc_key name= enc_key hidden>
|
||||
<input type="text" name="token" hidden>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" method="post" id="server_response">
|
||||
@csrf
|
||||
|
||||
<div class="w-screen items-center">
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
<div class="w-screen items-center" id="paytrace--credit-card-container">
|
||||
<div id="pt_hpf_form"></div>
|
||||
</div>
|
||||
@endcomponent
|
||||
|
||||
<div id='pt_hpf_form'><!--iframe sensitive data payment fields inserted here--></div>
|
||||
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->company_gateway->id }}">
|
||||
<input type="txt" id=HPF_Token name= HPF_Token hidden>
|
||||
<input type="txt" id=enc_key name= enc_key hidden>
|
||||
|
||||
<div class="bg-white px-4 py-5 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
id="{{ $id ?? 'pay-now' }}"
|
||||
class="button button-primary bg-primary {{ $class ?? '' }}">
|
||||
<span>{{ ctrans('texts.add_payment_method') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@component('portal.ninja2020.gateways.includes.pay_now')
|
||||
{{ ctrans('texts.add_payment_method') }}
|
||||
@endcomponent
|
||||
@endsection
|
||||
|
||||
@section('gateway_footer')
|
||||
|
||||
<script src='https://protect.paytrace.com/js/protect.min.js'></script>
|
||||
|
||||
<script>
|
||||
|
||||
// Minimal Protect.js setup call
|
||||
PTPayment.setup({
|
||||
styles:
|
||||
{
|
||||
'code': {
|
||||
'font_color':'#5D99CA',
|
||||
'border_color':'#EF9F6D',
|
||||
'label_color':'#EF9F6D',
|
||||
'label_size':'20px',
|
||||
'background_color':'white',
|
||||
'border_style':'dotted',
|
||||
'font_size':'15pt',
|
||||
'height':'30px',
|
||||
'width':'100px'
|
||||
},
|
||||
'cc': {
|
||||
'font_color':'#5D99CA',
|
||||
'border_color':'#EF9F6D',
|
||||
'label_color':'#EF9F6D',
|
||||
'label_size':'20px',
|
||||
'background_color':'white',
|
||||
'border_style':'solid',
|
||||
'font_size':'15pt',
|
||||
'height':'30px',
|
||||
'width':'300px'
|
||||
},
|
||||
'exp': {
|
||||
'font_color':'#5D99CA',
|
||||
'border_color':'#EF9F6D',
|
||||
'label_color':'#EF9F6D',
|
||||
'label_size':'20px',
|
||||
'background_color':'white',
|
||||
'border_style':'dashed',
|
||||
'font_size':'15pt',
|
||||
'height':'30px',
|
||||
'width':'85px',
|
||||
'type':'dropdown'
|
||||
}
|
||||
},
|
||||
authorization: { 'clientKey': "{!! $client_key !!}" }
|
||||
}).then(function(instance){
|
||||
|
||||
|
||||
PTPayment.getControl("securityCode").label.text("{!! ctrans('texts.cvv')!!}");
|
||||
PTPayment.getControl("creditCard").label.text("{!! ctrans('texts.card_number')!!}");
|
||||
PTPayment.getControl("expiration").label.text("{!! ctrans('texts.expires')!!}");
|
||||
//PTPayment.style({'cc': {'label_color': 'red'}});
|
||||
//PTPayment.style({'code': {'label_color': 'red'}});
|
||||
//PTPayment.style({'exp': {'label_color': 'red'}});
|
||||
//PTPayment.style({'exp':{'type':'dropdown'}});
|
||||
|
||||
//PTPayment.theme('horizontal');
|
||||
// this can be any event we chose. We will use the submit event and stop any default event handling and prevent event handling bubbling.
|
||||
document.getElementById("server_response").addEventListener("submit",function(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// To trigger the validation of sensitive data payment fields within the iframe before calling the tokenization process:
|
||||
PTPayment.validate(function(validationErrors) {
|
||||
|
||||
if (validationErrors.length >= 1) {
|
||||
|
||||
let errors = document.getElementById('errors');
|
||||
|
||||
errors.textContent = '';
|
||||
errors.textContent = validationErrors[0].description;
|
||||
errors.hidden = false;
|
||||
|
||||
} else {
|
||||
// no error so tokenize
|
||||
instance.process()
|
||||
.then( (r) => {
|
||||
submitPayment(r);
|
||||
}, (err) => {
|
||||
handleError(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
function handleError(err){
|
||||
document.write(JSON.stringify(err));
|
||||
}
|
||||
|
||||
function submitPayment(r){
|
||||
|
||||
var hpf_token = document.getElementById("HPF_Token");
|
||||
var enc_key = document.getElementById("enc_key");
|
||||
hpf_token.value = r.message.hpf_token;
|
||||
enc_key.value = r.message.enc_key;
|
||||
|
||||
document.getElementById("server_response").submit();
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script src="https://protect.paytrace.com/js/protect.min.js"></script>
|
||||
<script src="{{ asset('js/clients/payments/paytrace-credit-card.js') }}"></script>
|
||||
@endsection
|
||||
|
@ -1,7 +1,11 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title' => ctrans('texts.payment_type_credit_card')])
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title'
|
||||
=> ctrans('texts.payment_type_credit_card')])
|
||||
|
||||
@section('gateway_head')
|
||||
|
||||
<meta name="paytrace-client-key" content="{{ $client_key }}">
|
||||
<meta name="ctrans-cvv" content="{{ ctrans('texts.cvv') }}">
|
||||
<meta name="ctrans-card_number" content="{{ ctrans('texts.card_number') }}">
|
||||
<meta name="ctrans-expires" content="{{ ctrans('texts.expires') }}">
|
||||
@endsection
|
||||
|
||||
@section('gateway_content')
|
||||
@ -10,12 +14,12 @@
|
||||
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->company_gateway->id }}">
|
||||
<input type="hidden" name="payment_method_id" value="1">
|
||||
<input type="hidden" name="token" id="token"/>
|
||||
<input type="hidden" name="store_card" id="store_card"/>
|
||||
<input type="hidden" name="amount_with_fee" id="amount_with_fee" value="{{ $total['amount_with_fee'] }}"/>
|
||||
<input type="txt" id=HPF_Token name= HPF_Token hidden>
|
||||
<input type="txt" id=enc_key name= enc_key hidden>
|
||||
|
||||
<input type="hidden" name="token" id="token" />
|
||||
<input type="hidden" name="store_card" id="store_card" />
|
||||
<input type="hidden" name="amount_with_fee" id="amount_with_fee" value="{{ $total['amount_with_fee'] }}" />
|
||||
<input type="txt" id="HPF_Token" name="HPF_Token" hidden>
|
||||
<input type="txt" id="enc_key" name="enc_key" hidden>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
|
||||
@ -26,182 +30,35 @@
|
||||
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
|
||||
@if(count($tokens) > 0)
|
||||
@foreach($tokens as $token)
|
||||
@if (count($tokens) > 0)
|
||||
@foreach ($tokens as $token)
|
||||
<label class="mr-4">
|
||||
<input
|
||||
type="radio"
|
||||
data-token="{{ $token->hashed_id }}"
|
||||
name="payment-type"
|
||||
class="form-radio cursor-pointer toggle-payment-with-token"/>
|
||||
<input type="radio" data-token="{{ $token->hashed_id }}" 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
|
||||
type="radio"
|
||||
id="toggle-payment-with-credit-card"
|
||||
class="form-radio cursor-pointer"
|
||||
name="payment-type"
|
||||
checked/>
|
||||
<input type="radio" id="toggle-payment-with-credit-card" class="form-radio cursor-pointer" name="payment-type"
|
||||
checked />
|
||||
<span class="ml-1 cursor-pointer">{{ __('texts.new_card') }}</span>
|
||||
</label>
|
||||
@endcomponent
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.save_card')
|
||||
|
||||
<div class="w-screen items-center" id="paytrace--credit-card-container">
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
<div class="w-screen items-center" id="paytrace--credit-card-container">
|
||||
<div id="pt_hpf_form"></div>
|
||||
</div>
|
||||
@endcomponent
|
||||
|
||||
<div id='pt_hpf_form'><!--iframe sensitive data payment fields inserted here--></div>
|
||||
|
||||
</div>
|
||||
@include('portal.ninja2020.gateways.includes.pay_now', ['type' => 'submit'])
|
||||
</form>
|
||||
@include('portal.ninja2020.gateways.includes.pay_now')
|
||||
@endsection
|
||||
|
||||
@section('gateway_footer')
|
||||
|
||||
<script src='https://protect.paytrace.com/js/protect.min.js'></script>
|
||||
|
||||
<script>
|
||||
|
||||
let token_payment = true;
|
||||
|
||||
Array
|
||||
.from(document.getElementsByClassName('toggle-payment-with-token'))
|
||||
.forEach((element) => element.addEventListener('click', (e) => {
|
||||
document
|
||||
.getElementById('save-card--container').style.display = 'none';
|
||||
document
|
||||
.getElementById('paytrace--credit-card-container').style.display = 'none';
|
||||
|
||||
document
|
||||
.getElementById('token').value = e.target.dataset.token;
|
||||
}));
|
||||
|
||||
let payWithCreditCardToggle = document.getElementById('toggle-payment-with-credit-card');
|
||||
|
||||
if (payWithCreditCardToggle) {
|
||||
payWithCreditCardToggle
|
||||
.addEventListener('click', () => {
|
||||
document
|
||||
.getElementById('save-card--container').style.display = 'grid';
|
||||
document
|
||||
.getElementById('paytrace--credit-card-container').style.display = 'grid';
|
||||
|
||||
document
|
||||
.getElementById('token').value = null;
|
||||
token_payment = false;
|
||||
});
|
||||
}
|
||||
|
||||
var tokens = document.getElementsByClassName('toggle-payment-with-token');
|
||||
tokens[0].click();
|
||||
|
||||
// Minimal Protect.js setup call
|
||||
PTPayment.setup({
|
||||
styles:
|
||||
{
|
||||
'code': {
|
||||
'font_color':'#5D99CA',
|
||||
'border_color':'#EF9F6D',
|
||||
'label_color':'#EF9F6D',
|
||||
'label_size':'20px',
|
||||
'background_color':'white',
|
||||
'border_style':'dotted',
|
||||
'font_size':'15pt',
|
||||
'height':'30px',
|
||||
'width':'100px'
|
||||
},
|
||||
'cc': {
|
||||
'font_color':'#5D99CA',
|
||||
'border_color':'#EF9F6D',
|
||||
'label_color':'#EF9F6D',
|
||||
'label_size':'20px',
|
||||
'background_color':'white',
|
||||
'border_style':'solid',
|
||||
'font_size':'15pt',
|
||||
'height':'30px',
|
||||
'width':'300px'
|
||||
},
|
||||
'exp': {
|
||||
'font_color':'#5D99CA',
|
||||
'border_color':'#EF9F6D',
|
||||
'label_color':'#EF9F6D',
|
||||
'label_size':'20px',
|
||||
'background_color':'white',
|
||||
'border_style':'dashed',
|
||||
'font_size':'15pt',
|
||||
'height':'30px',
|
||||
'width':'85px',
|
||||
'type':'dropdown'
|
||||
}
|
||||
},
|
||||
authorization: { 'clientKey': "{!! $client_key !!}" }
|
||||
}).then(function(instance){
|
||||
|
||||
|
||||
PTPayment.getControl("securityCode").label.text("{!! ctrans('texts.cvv')!!}");
|
||||
PTPayment.getControl("creditCard").label.text("{!! ctrans('texts.card_number')!!}");
|
||||
PTPayment.getControl("expiration").label.text("{!! ctrans('texts.expires')!!}");
|
||||
|
||||
document.getElementById("server_response").addEventListener("submit",function(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
PTPayment.validate(function(validationErrors) {
|
||||
|
||||
if (validationErrors.length >= 1 && !token_payment) {
|
||||
let errors = document.getElementById('errors');
|
||||
|
||||
errors.textContent = '';
|
||||
errors.textContent = validationErrors[0].description;
|
||||
errors.hidden = false;
|
||||
|
||||
} else {
|
||||
// no error so tokenize
|
||||
if(token_payment){
|
||||
|
||||
tokenPayment();
|
||||
}
|
||||
|
||||
instance.process()
|
||||
.then( (r) => {
|
||||
submitPayment(r);
|
||||
}, (err) => {
|
||||
handleError(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
function handleError(err){
|
||||
console.log(err);
|
||||
document.write(JSON.stringify(err));
|
||||
}
|
||||
|
||||
function submitPayment(r){
|
||||
|
||||
var hpf_token = document.getElementById("HPF_Token");
|
||||
var enc_key = document.getElementById("enc_key");
|
||||
hpf_token.value = r.message.hpf_token;
|
||||
enc_key.value = r.message.enc_key;
|
||||
|
||||
document.getElementById("server_response").submit();
|
||||
|
||||
}
|
||||
|
||||
|
||||
function tokenPayment(){
|
||||
|
||||
document.getElementById("server_response").submit();
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
<script src='https://protect.paytrace.com/js/protect.min.js'></script>
|
||||
<script src="{{ asset('js/clients/payments/paytrace-credit-card.js') }}"></script>
|
||||
@endsection
|
||||
|
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace Tests\Browser\ClientPortal\Gateways\PayTrace;
|
||||
|
||||
use App\Models\CompanyGateway;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Tests\Browser\Pages\ClientPortal\Login;
|
||||
use Tests\DuskTestCase;
|
||||
|
||||
class CreditCardTest extends DuskTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
foreach (static::$browsers as $browser) {
|
||||
$browser->driver->manage()->deleteAllCookies();
|
||||
}
|
||||
|
||||
$this->disableCompanyGateways();
|
||||
|
||||
CompanyGateway::where('gateway_key', 'bbd736b3254b0aabed6ad7fda1298c88')->restore();
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visit(new Login())
|
||||
->auth();
|
||||
});
|
||||
}
|
||||
|
||||
public function testPayingWithNewCreditCard()
|
||||
{
|
||||
$this->markTestSkipped('Credit card not supported.');
|
||||
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->press('Pay Now')
|
||||
->clickLink('Credit Card')
|
||||
->withinFrame('iframe', function (Browser $browser) {
|
||||
$browser
|
||||
->type('CC', '4012000098765439')
|
||||
->select('EXP_MM', '12')
|
||||
->select('EXP_YY', '30')
|
||||
->type('SEC', '999');
|
||||
})
|
||||
->press('Pay Now')
|
||||
->waitForText('Details of the payment', 60);
|
||||
});
|
||||
}
|
||||
}
|
4
webpack.mix.js
vendored
4
webpack.mix.js
vendored
@ -81,6 +81,10 @@ mix.js("resources/js/app.js", "public/js")
|
||||
.js(
|
||||
"resources/js/clients/payment_methods/wepay-bank-account.js",
|
||||
"public/js/clients/payment_methods/wepay-bank-account.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/payments/paytrace-credit-card.js",
|
||||
"public/js/clients/payments/paytrace-credit-card.js"
|
||||
);
|
||||
|
||||
mix.copyDirectory('node_modules/card-js/card-js.min.css', 'public/css/card-js.min.css');
|
||||
|
Loading…
x
Reference in New Issue
Block a user