mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
649966c6bd
2
.github/workflows/phpunit.yml
vendored
2
.github/workflows/phpunit.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
operating-system: ['ubuntu-18.04', 'ubuntu-20.04']
|
||||
php-versions: ['7.3','7.4','8.0']
|
||||
php-versions: ['7.4','8.0']
|
||||
phpunit-versions: ['latest']
|
||||
|
||||
env:
|
||||
|
@ -1 +1 @@
|
||||
5.2.19
|
||||
5.3.0
|
@ -81,8 +81,6 @@ class InvoiceItemSum
|
||||
|
||||
private function push()
|
||||
{
|
||||
nlog($this->sub_total . " + ". $this->getLineTotal());
|
||||
|
||||
$this->sub_total += $this->getLineTotal();
|
||||
|
||||
$this->line_items[] = $this->item;
|
||||
@ -125,9 +123,6 @@ class InvoiceItemSum
|
||||
{
|
||||
$item_tax = 0;
|
||||
|
||||
// nlog(print_r($this->item,1));
|
||||
// nlog(print_r($this->invoice,1));
|
||||
|
||||
$amount = $this->item->line_total - ($this->item->line_total * ($this->invoice->discount / 100));
|
||||
$item_tax_rate1_total = $this->calcAmountLineTax($this->item->tax_rate1, $amount);
|
||||
|
||||
|
@ -112,6 +112,7 @@ class BaseController extends Controller
|
||||
'company.groups',
|
||||
'company.payment_terms',
|
||||
'company.designs.company',
|
||||
'company.expense_categories',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
@ -202,6 +203,10 @@ class BaseController extends Controller
|
||||
$transformer = new $this->entity_transformer($this->serializer);
|
||||
$updated_at = request()->has('updated_at') ? request()->input('updated_at') : 0;
|
||||
|
||||
if ($user->getCompany()->is_large && $updated_at == 0){
|
||||
$updated_at = time();
|
||||
}
|
||||
|
||||
$updated_at = date('Y-m-d H:i:s', $updated_at);
|
||||
|
||||
$query->with(
|
||||
@ -735,6 +740,9 @@ class BaseController extends Controller
|
||||
|
||||
//pass referral code to front end
|
||||
$data['rc'] = request()->has('rc') ? request()->input('rc') : '';
|
||||
$data['build'] = request()->has('build') ? request()->input('build') : '';
|
||||
|
||||
$data['path'] = $this->setBuild();
|
||||
|
||||
$this->buildCache();
|
||||
|
||||
@ -744,6 +752,29 @@ class BaseController extends Controller
|
||||
return redirect('/setup');
|
||||
}
|
||||
|
||||
private function setBuild()
|
||||
{
|
||||
$build = '';
|
||||
|
||||
if(request()->has('build')) {
|
||||
$build = request()->input('build');
|
||||
}
|
||||
|
||||
switch ($build) {
|
||||
case 'wasm':
|
||||
return 'main.wasm.dart.js';
|
||||
case 'foss':
|
||||
return 'main.foss.dart.js';
|
||||
case 'last':
|
||||
return 'main.last.dart.js';
|
||||
case 'next':
|
||||
return 'main.next.dart.js';
|
||||
default:
|
||||
return 'main.dart.js';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function checkFeature($feature)
|
||||
{
|
||||
|
||||
|
@ -28,6 +28,7 @@ class SubdomainController extends BaseController
|
||||
'custom_domain',
|
||||
'preview',
|
||||
'invoiceninja',
|
||||
'cname',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
|
@ -107,6 +107,7 @@ class StoreRecurringInvoiceRequest extends Request
|
||||
} else {
|
||||
if ($client = Client::find($input['client_id'])) {
|
||||
$input['auto_bill'] = $client->getSetting('auto_bill');
|
||||
$input['auto_bill_enabled'] = $this->setAutoBillFlag($input['auto_bill']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,14 +116,12 @@ class StoreRecurringInvoiceRequest extends Request
|
||||
|
||||
private function setAutoBillFlag($auto_bill)
|
||||
{
|
||||
if ($auto_bill == 'always') {
|
||||
if ($auto_bill == 'always' || $auto_bill == 'optout') {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//if ($auto_bill == 'off' || $auto_bill == 'optin') {
|
||||
return false;
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
public function messages()
|
||||
|
@ -403,11 +403,18 @@ class Import implements ShouldQueue
|
||||
$company_repository->save($data, $this->company);
|
||||
|
||||
if (isset($data['settings']->company_logo) && strlen($data['settings']->company_logo) > 0) {
|
||||
|
||||
try {
|
||||
$tempImage = tempnam(sys_get_temp_dir(), basename($data['settings']->company_logo));
|
||||
copy($data['settings']->company_logo, $tempImage);
|
||||
$this->uploadLogo($tempImage, $this->company, $this->company);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
$settings = $this->company->settings;
|
||||
$settings->company_logo = '';
|
||||
$this->company->settings = $settings;
|
||||
$this->company->save();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -705,6 +712,7 @@ class Import implements ShouldQueue
|
||||
$modified = $resource;
|
||||
$modified['company_id'] = $this->company->id;
|
||||
$modified['user_id'] = $this->processUserId($resource);
|
||||
$modified['number'] = $this->checkUniqueConstraint(Vendor::class, 'number', $modified['number']);
|
||||
|
||||
unset($modified['id']);
|
||||
unset($modified['contacts']);
|
||||
|
@ -25,6 +25,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class ReminderJob implements ShouldQueue
|
||||
{
|
||||
@ -148,6 +149,10 @@ class ReminderJob implements ShouldQueue
|
||||
*/
|
||||
private function setLateFee($invoice, $amount, $percent) :Invoice
|
||||
{
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
$t->replace(Ninja::transformTranslations($invoice->client->getMergedSettings()));
|
||||
|
||||
$temp_invoice_balance = $invoice->balance;
|
||||
|
||||
if ($amount <= 0 && $percent <= 0) {
|
||||
@ -179,8 +184,12 @@ class ReminderJob implements ShouldQueue
|
||||
$invoice->fresh();
|
||||
$invoice->service()->deletePdf();
|
||||
|
||||
/* Refresh the client here to ensure the balance is fresh */
|
||||
$client = $invoice->client;
|
||||
$client = $client->fresh();
|
||||
|
||||
nlog("adjusting client balance and invoice balance by ". ($invoice->balance - $temp_invoice_balance));
|
||||
$invoice->client->service()->updateBalance($invoice->balance - $temp_invoice_balance)->save();
|
||||
$client->service()->updateBalance($invoice->balance - $temp_invoice_balance)->save();
|
||||
$invoice->ledger()->updateInvoiceBalance($invoice->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$invoice->number}");
|
||||
|
||||
return $invoice;
|
||||
|
@ -45,7 +45,7 @@ class CreditArchivedActivity implements ShouldQueue
|
||||
|
||||
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->credit->user_id;
|
||||
|
||||
$fields->payment_id = $event->credit->id;
|
||||
$fields->credit_id = $event->credit->id;
|
||||
$fields->client_id = $event->credit->client_id;
|
||||
$fields->user_id = $user_id;
|
||||
$fields->company_id = $event->credit->company_id;
|
||||
|
@ -80,7 +80,7 @@ class PaymentNotification implements ShouldQueue
|
||||
private function trackRevenue($event)
|
||||
{
|
||||
$payment = $event->payment;
|
||||
$invoice = $payment->invoice;
|
||||
$invoice = $payment->invoices()->exists() ? $payment->invoices->first() : false;
|
||||
$company = $payment->company;
|
||||
|
||||
$analytics_id = $company->google_analytics_key;
|
||||
@ -100,7 +100,7 @@ class PaymentNotification implements ShouldQueue
|
||||
$currency_code = $client->getCurrencyCode();
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
$item .= ' [R]';
|
||||
$item .= ' [R5]';
|
||||
}
|
||||
|
||||
$base = "v=1&tid={$analytics_id}&cid={$client->id}&cu={$currency_code}&ti={$entity_number}";
|
||||
|
@ -56,7 +56,7 @@ class SupportMessageSent extends Mailable
|
||||
$plan = $account->plan ?: 'customer support';
|
||||
$plan = ucfirst($plan);
|
||||
|
||||
if(strlen($plan) >1)
|
||||
if(strlen($account->plan) > 1)
|
||||
$priority = '[PRIORITY] ';
|
||||
|
||||
$company = auth()->user()->company();
|
||||
|
@ -94,6 +94,7 @@ class Company extends BaseModel
|
||||
'invoice_task_datelog',
|
||||
'default_password_timeout',
|
||||
'show_task_end_date',
|
||||
'use_comma_as_decimal_place',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@ -226,7 +227,7 @@ class Company extends BaseModel
|
||||
|
||||
public function activities()
|
||||
{
|
||||
return $this->hasMany(Activity::class)->orderBy('id', 'DESC')->take(300);
|
||||
return $this->hasMany(Activity::class)->orderBy('id', 'DESC')->take(50);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,9 +79,12 @@ class Gateway extends StaticModel
|
||||
{
|
||||
switch ($this->id) {
|
||||
case 1:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]];//Authorize.net
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Authorize.net
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true]];//eWay
|
||||
break;
|
||||
case 11:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => false]];//Payfast
|
||||
break;
|
||||
case 15:
|
||||
|
@ -71,6 +71,7 @@ class CompanyPresenter extends EntityPresenter
|
||||
else
|
||||
return "data:image/png;base64, ". base64_encode(file_get_contents(asset('images/new_logo.png'), false, stream_context_create($context_options)));
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function address($settings = null)
|
||||
|
@ -70,6 +70,7 @@ class SystemLog extends Model
|
||||
const TYPE_PAYFAST = 310;
|
||||
const TYPE_PAYTRACE = 311;
|
||||
const TYPE_MOLLIE = 312;
|
||||
const TYPE_EWAY = 313;
|
||||
|
||||
const TYPE_QUOTA_EXCEEDED = 400;
|
||||
const TYPE_UPSTREAM_FAILURE = 401;
|
||||
|
@ -182,7 +182,6 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return $company_token->company;
|
||||
}
|
||||
|
||||
|
||||
// return false;
|
||||
throw new \Exception('No Company Found');
|
||||
//return Company::find(config('ninja.company_id'));
|
||||
|
@ -103,14 +103,14 @@ class EntitySentNotification extends Notification
|
||||
"texts.notification_{$this->entity_name}_sent_subject",
|
||||
[
|
||||
'amount' => $amount,
|
||||
'client' => $this->contact->present()->name(),
|
||||
'client' => $this->contact->client->present()->name(),
|
||||
'invoice' => $this->entity->number,
|
||||
]
|
||||
))
|
||||
->attachment(function ($attachment) use ($amount) {
|
||||
$attachment->title(ctrans('texts.invoice_number_placeholder', ['invoice' => $this->entity->number]), $this->invitation->getAdminLink())
|
||||
->fields([
|
||||
ctrans('texts.client') => $this->contact->present()->name(),
|
||||
ctrans('texts.client') => $this->contact->client->present()->name(),
|
||||
ctrans('texts.amount') => $amount,
|
||||
]);
|
||||
});
|
||||
|
250
app/PaymentDrivers/Eway/CreditCard.php
Normal file
250
app/PaymentDrivers/Eway/CreditCard.php
Normal file
@ -0,0 +1,250 @@
|
||||
<?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 App\PaymentDrivers\Eway;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Jobs\Mail\PaymentFailureMailer;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\EwayPaymentDriver;
|
||||
use App\PaymentDrivers\Eway\ErrorCode;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreditCard
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $eway_driver;
|
||||
|
||||
public function __construct(EwayPaymentDriver $eway_driver)
|
||||
{
|
||||
$this->eway_driver = $eway_driver;
|
||||
}
|
||||
|
||||
public function authorizeView($data)
|
||||
{
|
||||
|
||||
$data['gateway'] = $this->eway_driver;
|
||||
$data['api_key'] = $this->eway_driver->company_gateway->getConfigField('apiKey');
|
||||
$data['public_api_key'] = $this->eway_driver->company_gateway->getConfigField('publicApiKey');
|
||||
|
||||
return render('gateways.eway.authorize', $data);
|
||||
|
||||
}
|
||||
|
||||
public function authorizeResponse($request)
|
||||
{
|
||||
|
||||
$token = $this->createEwayToken($request->input('securefieldcode'));
|
||||
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
|
||||
}
|
||||
|
||||
private function createEwayToken($securefieldcode)
|
||||
{
|
||||
$transaction = [
|
||||
'Reference' => $this->eway_driver->client->number,
|
||||
'Title' => '',
|
||||
'FirstName' => $this->eway_driver->client->contacts()->first()->present()->last_name(),
|
||||
'LastName' => $this->eway_driver->client->contacts()->first()->present()->first_name(),
|
||||
'CompanyName' => $this->eway_driver->client->name,
|
||||
'Street1' => $this->eway_driver->client->address1,
|
||||
'Street2' => $this->eway_driver->client->address2,
|
||||
'City' => $this->eway_driver->client->city,
|
||||
'State' => $this->eway_driver->client->state,
|
||||
'PostalCode' => $this->eway_driver->client->postal_code,
|
||||
'Country' => $this->eway_driver->client->country->iso_3166_2,
|
||||
'Phone' => $this->eway_driver->client->phone,
|
||||
'Email' => $this->eway_driver->client->contacts()->first()->email,
|
||||
"Url" => $this->eway_driver->client->website,
|
||||
'Method' => \Eway\Rapid\Enum\PaymentMethod::CREATE_TOKEN_CUSTOMER,
|
||||
'SecuredCardData' => $securefieldcode,
|
||||
];
|
||||
|
||||
$response = $this->eway_driver->init()->eway->createCustomer(\Eway\Rapid\Enum\ApiMethod::DIRECT, $transaction);
|
||||
|
||||
$response_status = ErrorCode::getStatus($response->ResponseMessage);
|
||||
|
||||
if(!$response_status['success'])
|
||||
throw new PaymentFailed($response_status['message'], 400);
|
||||
|
||||
//success
|
||||
$cgt = [];
|
||||
$cgt['token'] = $response->Customer->TokenCustomerID;
|
||||
$cgt['payment_method_id'] = GatewayType::CREDIT_CARD;
|
||||
|
||||
$payment_meta = new \stdClass;
|
||||
$payment_meta->exp_month = $response->Customer->CardDetails->ExpiryMonth;
|
||||
$payment_meta->exp_year = $response->Customer->CardDetails->ExpiryYear;
|
||||
$payment_meta->brand = 'CC';
|
||||
$payment_meta->last4 = substr($response->Customer->CardDetails->Number, -4);;
|
||||
$payment_meta->type = GatewayType::CREDIT_CARD;
|
||||
|
||||
$cgt['payment_meta'] = $payment_meta;
|
||||
|
||||
$token = $this->eway_driver->storeGatewayToken($cgt, []);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function paymentView($data)
|
||||
{
|
||||
|
||||
$data['gateway'] = $this->eway_driver;
|
||||
$data['public_api_key'] = $this->eway_driver->company_gateway->getConfigField('publicApiKey');
|
||||
|
||||
return render('gateways.eway.pay', $data);
|
||||
|
||||
}
|
||||
|
||||
public function paymentResponse($request)
|
||||
{
|
||||
|
||||
$state = [
|
||||
'server_response' => $request->all(),
|
||||
];
|
||||
|
||||
$this->eway_driver->payment_hash->data = array_merge((array) $this->eway_driver->payment_hash->data, $state);
|
||||
$this->eway_driver->payment_hash->save();
|
||||
|
||||
if(boolval($request->input('store_card')))
|
||||
{
|
||||
$token = $this->createEwayToken($request->input('securefieldcode'));
|
||||
$payment = $this->tokenBilling($token->token, $this->eway_driver->payment_hash);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||
|
||||
}
|
||||
|
||||
if($request->token){
|
||||
|
||||
$payment = $this->tokenBilling($request->token, $this->eway_driver->payment_hash);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||
|
||||
}
|
||||
|
||||
$transaction = [
|
||||
'Payment' => [
|
||||
'TotalAmount' => $this->convertAmountForEway(),
|
||||
],
|
||||
'TransactionType' => \Eway\Rapid\Enum\TransactionType::PURCHASE,
|
||||
'SecuredCardData' => $request->input('securefieldcode'),
|
||||
];
|
||||
|
||||
$response = $this->eway_driver->init()->eway->createTransaction(\Eway\Rapid\Enum\ApiMethod::DIRECT, $transaction);
|
||||
|
||||
$response_status = ErrorCode::getStatus($response->ResponseMessage);
|
||||
|
||||
if(!$response_status['success']){
|
||||
|
||||
$this->logResponse($response, false);
|
||||
|
||||
throw new PaymentFailed($response_status['message'], 400);
|
||||
}
|
||||
|
||||
$this->logResponse($response, true);
|
||||
|
||||
$payment = $this->storePayment($response);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||
|
||||
}
|
||||
|
||||
private function storePayment($response)
|
||||
{
|
||||
$amount = array_sum(array_column($this->eway_driver->payment_hash->invoices(), 'amount')) + $this->eway_driver->payment_hash->fee_total;
|
||||
|
||||
$payment_record = [];
|
||||
$payment_record['amount'] = $amount;
|
||||
$payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER;
|
||||
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
|
||||
$payment_record['transaction_reference'] = $response->TransactionID;
|
||||
|
||||
$payment = $this->eway_driver->createPayment($payment_record);
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
private function convertAmountForEway($amount = false)
|
||||
{
|
||||
|
||||
if(!$amount)
|
||||
$amount = array_sum(array_column($this->eway_driver->payment_hash->invoices(), 'amount')) + $this->eway_driver->payment_hash->fee_total;
|
||||
|
||||
if(in_array($this->eway_driver->client->currency()->code, ['VND', 'JPY', 'KRW', 'GNF', 'IDR', 'PYG', 'RWF', 'UGX', 'VUV', 'XAF', 'XPF']))
|
||||
return $amount;
|
||||
|
||||
return $amount * 100;
|
||||
}
|
||||
|
||||
private function logResponse($response, $success = true)
|
||||
{
|
||||
|
||||
$logger_message = [
|
||||
'server_response' => $response,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$logger_message,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
$success ? SystemLog::EVENT_GATEWAY_SUCCESS : SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_EWAY,
|
||||
$this->eway_driver->client,
|
||||
$this->eway_driver->client->company,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function tokenBilling($token, $payment_hash)
|
||||
{
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
|
||||
$transaction = [
|
||||
'Customer' => [
|
||||
'TokenCustomerID' => $token,
|
||||
],
|
||||
'Payment' => [
|
||||
'TotalAmount' => $this->convertAmountForEway($amount),
|
||||
],
|
||||
'TransactionType' => \Eway\Rapid\Enum\TransactionType::RECURRING,
|
||||
];
|
||||
|
||||
$response = $this->eway_driver->init()->eway->createTransaction(\Eway\Rapid\Enum\ApiMethod::DIRECT, $transaction);
|
||||
|
||||
$response_status = ErrorCode::getStatus($response->ResponseMessage);
|
||||
|
||||
if(!$response_status['success']){
|
||||
|
||||
$this->logResponse($response, false);
|
||||
|
||||
throw new PaymentFailed($response_status['message'], 400);
|
||||
}
|
||||
|
||||
$this->logResponse($response, true);
|
||||
|
||||
$payment = $this->storePayment($response);
|
||||
|
||||
return $payment;
|
||||
}
|
||||
}
|
105
app/PaymentDrivers/Eway/ErrorCode.php
Normal file
105
app/PaymentDrivers/Eway/ErrorCode.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?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 App\PaymentDrivers\Eway;
|
||||
|
||||
class ErrorCode
|
||||
{
|
||||
|
||||
private static $success = [
|
||||
"A2000" => "Transaction Approved",
|
||||
"A2008" => "Honour With Identification",
|
||||
"A2010" => "Approved For Partial Amount",
|
||||
"A2011" => "Approved, VIP",
|
||||
"A2016" => "Approved, Update Track 3",
|
||||
];
|
||||
|
||||
private static $failure = [
|
||||
"D4401" => "Refer to Issuer",
|
||||
"D4402" => "Refer to Issuer, special",
|
||||
"D4403" => "No Merchant",
|
||||
"D4404" => "Pick Up Card",
|
||||
"D4405" => "Do Not Honour",
|
||||
"D4406" => "Error",
|
||||
"D4407" => "Pick Up Card, Special",
|
||||
"D4409" => "Request In Progress",
|
||||
"D4412" => "Invalid Transaction",
|
||||
"D4413" => "Invalid Amount",
|
||||
"D4414" => "Invalid Card Number",
|
||||
"D4415" => "No Issuer",
|
||||
"D4417" => "3D Secure Error",
|
||||
"D4419" => "Re-enter Last Transaction",
|
||||
"D4421" => "No Action Taken",
|
||||
"D4422" => "Suspected Malfunction",
|
||||
"D4423" => "Unacceptable Transaction Fee",
|
||||
"D4425" => "Unable to Locate Record On File",
|
||||
"D4430" => "Format Error",
|
||||
"D4431" => "Bank Not Supported By Switch",
|
||||
"D4433" => "Expired Card, Capture",
|
||||
"D4434" => "Suspected Fraud, Retain Card",
|
||||
"D4435" => "Card Acceptor, Contact Acquirer, Retain Card",
|
||||
"D4436" => "Restricted Card, Retain Card",
|
||||
"D4437" => "Contact Acquirer Security Department, Retain Card",
|
||||
"D4438" => "PIN Tries Exceeded, Capture",
|
||||
"D4439" => "No Credit Account",
|
||||
"D4440" => "Function Not Supported",
|
||||
"D4441" => "Lost Card",
|
||||
"D4442" => "No Universal Account",
|
||||
"D4443" => "Stolen Card",
|
||||
"D4444" => "No Investment Account",
|
||||
"D4450" => "Click-to-Pay (Visa Checkout) Transaction",
|
||||
"D4451" => "Insufficient Funds",
|
||||
"D4452" => "No Cheque Account",
|
||||
"D4453" => "No Savings Account",
|
||||
"D4454" => "Expired Card",
|
||||
"D4455" => "Incorrect PIN",
|
||||
"D4456" => "No Card Record",
|
||||
"D4457" => "Function Not Permitted to Cardholder",
|
||||
"D4458" => "Function Not Permitted to Terminal",
|
||||
"D4459" => "Suspected Fraud",
|
||||
"D4460" => "Acceptor Contact Acquirer",
|
||||
"D4461" => "Exceeds Withdrawal Limit",
|
||||
"D4462" => "Restricted Card",
|
||||
"D4463" => "Security Violation",
|
||||
"D4464" => "Original Amount Incorrect",
|
||||
"D4466" => "Acceptor Contact Acquirer, Security",
|
||||
"D4467" => "Capture Card",
|
||||
"D4475" => "PIN Tries Exceeded",
|
||||
"D4476" => "Invalidate Txn Reference",
|
||||
"D4481" => "Accumulated Transaction Counter (Amount) Exceeded",
|
||||
"D4482" => "CVV Validation Error",
|
||||
"D4483" => "Acquirer Is Not Accepting Transactions From You At This Time",
|
||||
"D4484" => "Acquirer Is Not Accepting This Transaction",
|
||||
"D4490" => "Cut off In Progress",
|
||||
"D4491" => "Card Issuer Unavailable",
|
||||
"D4492" => "Unable To Route Transaction",
|
||||
"D4493" => "Cannot Complete, Violation Of The Law",
|
||||
"D4494" => "Duplicate Transaction",
|
||||
"D4495" => "Amex Declined",
|
||||
"D4496" => "System Error",
|
||||
"D4497" => "MasterPass Error",
|
||||
"D4498" => "PayPal Create Transaction Error",
|
||||
"D4499" => "Invalid Transaction for Auth/Void",
|
||||
];
|
||||
|
||||
|
||||
public static function getStatus($code)
|
||||
{
|
||||
if(array_key_exists($code, self::$success))
|
||||
return ['success' => true, 'message' => self::$success[$code]];
|
||||
|
||||
if(array_key_exists($code, self::$failure))
|
||||
return ['success' => false, 'message' => self::$failure[$code]];
|
||||
|
||||
return ['success' => false, 'message' => "Unknown error message code - {$code}"];
|
||||
}
|
||||
}
|
111
app/PaymentDrivers/Eway/Token.php
Normal file
111
app/PaymentDrivers/Eway/Token.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?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 App\PaymentDrivers\Eway;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Jobs\Mail\PaymentFailureMailer;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\EwayPaymentDriver;
|
||||
use App\PaymentDrivers\Eway\ErrorCode;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Token
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $eway_driver;
|
||||
|
||||
public function __construct(EwayPaymentDriver $eway_driver)
|
||||
{
|
||||
$this->eway_driver = $eway_driver;
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
|
||||
$transaction = [
|
||||
'Customer' => [
|
||||
'TokenCustomerID' => $cgt->token,
|
||||
],
|
||||
'Payment' => [
|
||||
'TotalAmount' => $this->eway_driver->convertAmount($amount),
|
||||
],
|
||||
'TransactionType' => \Eway\Rapid\Enum\TransactionType::RECURRING,
|
||||
];
|
||||
|
||||
$response = $this->eway_driver->init()->eway->createTransaction(\Eway\Rapid\Enum\ApiMethod::DIRECT, $transaction);
|
||||
|
||||
$response_status = ErrorCode::getStatus($response->ResponseMessage);
|
||||
|
||||
if(!$response_status['success'])
|
||||
return $this->processUnsuccessfulPayment($response);
|
||||
|
||||
$payment = $this->processSuccessfulPayment($response);
|
||||
|
||||
return $payment;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private function processSuccessfulPayment($response)
|
||||
{
|
||||
$amount = array_sum(array_column($this->eway_driver->payment_hash->invoices(), 'amount')) + $this->eway_driver->payment_hash->fee_total;
|
||||
|
||||
$data = [
|
||||
'gateway_type_id' => $cgt->gateway_type_id,
|
||||
'payment_type' => GatewayType::CREDIT_CARD_OTHER,
|
||||
'transaction_reference' => $response->Customer->Reference,
|
||||
'amount' => $amount,
|
||||
];
|
||||
|
||||
$payment = $this->eway_driver->createPayment($data);
|
||||
$payment->meta = $cgt->meta;
|
||||
$payment->save();
|
||||
|
||||
$payment_hash->payment_id = $payment->id;
|
||||
$payment_hash->save();
|
||||
|
||||
return $payment;
|
||||
|
||||
}
|
||||
|
||||
private function processUnsuccessfulPayment($response)
|
||||
{
|
||||
|
||||
$response_status = ErrorCode::getStatus($response->ResponseMessage);
|
||||
|
||||
$error = $response_status['message']
|
||||
$error_code = $response->ResponseMessage;
|
||||
|
||||
$data = [
|
||||
'response' => $response,
|
||||
'error' => $error,
|
||||
'error_code' => $error_code,
|
||||
];
|
||||
|
||||
return $this->driver_class->processUnsuccessfulTransaction($data);
|
||||
|
||||
}
|
||||
|
||||
}
|
200
app/PaymentDrivers/EwayPaymentDriver.php
Normal file
200
app/PaymentDrivers/EwayPaymentDriver.php
Normal file
@ -0,0 +1,200 @@
|
||||
<?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 App\PaymentDrivers;
|
||||
|
||||
use App\Http\Requests\Payments\PaymentWebhookRequest;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Eway\CreditCard;
|
||||
use App\PaymentDrivers\Eway\Token;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class EwayPaymentDriver extends BaseDriver
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $refundable = true; //does this gateway support refunds?
|
||||
|
||||
public $token_billing = true; //does this gateway support token billing?
|
||||
|
||||
public $can_authorise_credit_card = true; //does this gateway support authorizations?
|
||||
|
||||
public $eway; //initialized gateway
|
||||
|
||||
public $payment_method; //initialized payment method
|
||||
|
||||
public static $methods = [
|
||||
GatewayType::CREDIT_CARD => CreditCard::class, //maps GatewayType => Implementation class
|
||||
];
|
||||
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_EWAY; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model
|
||||
|
||||
public function init()
|
||||
{
|
||||
$apiKey = $this->company_gateway->getConfigField('apiKey');
|
||||
$apiPassword = $this->company_gateway->getConfigField('password');
|
||||
$apiEndpoint = $this->company_gateway->getConfigField('testMode') ? \Eway\Rapid\Client::MODE_SANDBOX : \Eway\Rapid\Client::MODE_PRODUCTION;
|
||||
$this->eway = \Eway\Rapid::createClient($apiKey, $apiPassword, $apiEndpoint);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/* Returns an array of gateway types for the payment gateway */
|
||||
public function gatewayTypes(): array
|
||||
{
|
||||
$types = [];
|
||||
|
||||
$types[] = GatewayType::CREDIT_CARD;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/* Sets the payment method initialized */
|
||||
public function setPaymentMethod($payment_method_id)
|
||||
{
|
||||
$class = self::$methods[$payment_method_id];
|
||||
$this->payment_method = new $class($this);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function authorizeView(array $data)
|
||||
{
|
||||
return $this->payment_method->authorizeView($data);
|
||||
}
|
||||
|
||||
public function authorizeResponse($request)
|
||||
{
|
||||
return $this->payment_method->authorizeResponse($request);
|
||||
}
|
||||
|
||||
public function processPaymentView(array $data)
|
||||
{
|
||||
return $this->payment_method->paymentView($data); //this is your custom implementation from here
|
||||
}
|
||||
|
||||
public function processPaymentResponse($request)
|
||||
{
|
||||
return $this->payment_method->paymentResponse($request); //this is your custom implementation from here
|
||||
}
|
||||
|
||||
/* We need PCI compliance prior to enabling this */
|
||||
public function refund(Payment $payment, $amount, $return_client_response = false)
|
||||
{
|
||||
|
||||
$refund = [
|
||||
'Refund' => [
|
||||
'TransactionID' => $payment->transaction_reference,
|
||||
'TotalAmount' => $this->convertAmount($amount)
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->init()->eway->refund($refund);
|
||||
|
||||
$transaction_reference = '';
|
||||
$refund_status = true;
|
||||
$refund_message = '';
|
||||
|
||||
if ($response->TransactionStatus) {
|
||||
$transaction_reference = $response->TransactionID;
|
||||
} else {
|
||||
if ($response->getErrors()) {
|
||||
foreach ($response->getErrors() as $error) {
|
||||
$refund_status = false;
|
||||
$refund_message = \Eway\Rapid::getMessage($error);
|
||||
}
|
||||
} else {
|
||||
$refund_status = false;
|
||||
$refund_message = 'Sorry, your refund failed';
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'transaction_reference' => $response->TransactionID,
|
||||
'transaction_response' => json_encode($response),
|
||||
'success' => $refund_status,
|
||||
'description' => $refund_message,
|
||||
'code' => '',
|
||||
];
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
return (new Token($this))->tokenBilling($cgt, $payment_hash);
|
||||
}
|
||||
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function convertAmount($amount)
|
||||
{
|
||||
$precision = $this->client->currency()->precision;
|
||||
|
||||
if($precision == 0)
|
||||
return $amount;
|
||||
|
||||
if($precision == 1)
|
||||
return $amount*10;
|
||||
|
||||
if($precision == 2)
|
||||
return $amount*100;
|
||||
|
||||
|
||||
return $amount;
|
||||
}
|
||||
|
||||
public function getClientRequiredFields(): array
|
||||
{
|
||||
$fields = [];
|
||||
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required,email:rfc'];
|
||||
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
|
||||
|
||||
if ($this->company_gateway->require_client_name) {
|
||||
$fields[] = ['name' => 'client_name', 'label' => ctrans('texts.client_name'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
// if ($this->company_gateway->require_contact_name) {
|
||||
// }
|
||||
|
||||
// if ($this->company_gateway->require_contact_email) {
|
||||
// }
|
||||
|
||||
if ($this->company_gateway->require_client_phone) {
|
||||
$fields[] = ['name' => 'client_phone', 'label' => ctrans('texts.client_phone'), 'type' => 'tel', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_billing_address) {
|
||||
$fields[] = ['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_city', 'label' => ctrans('texts.city'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_state', 'label' => ctrans('texts.state'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
if($this->company_gateway->require_postal_code)
|
||||
$fields[] = ['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'];
|
||||
|
||||
if ($this->company_gateway->require_shipping_address) {
|
||||
$fields[] = ['name' => 'client_shipping_address_line_1', 'label' => ctrans('texts.shipping_address1'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_shipping_city', 'label' => ctrans('texts.shipping_city'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_shipping_state', 'label' => ctrans('texts.shipping_state'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_shipping_postal_code', 'label' => ctrans('texts.shipping_postal_code'), 'type' => 'text', 'validation' => 'required'];
|
||||
$fields[] = ['name' => 'client_shipping_country_id', 'label' => ctrans('texts.shipping_country'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
|
||||
return $fields;
|
||||
}
|
||||
}
|
113
app/PaymentDrivers/Sample/CreditCard.php
Normal file
113
app/PaymentDrivers/Sample/CreditCard.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?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 App\PaymentDrivers\Sample;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Jobs\Mail\PaymentFailureMailer;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreditCard
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $driver_class;
|
||||
|
||||
public function __construct(PaymentDriver $driver_class)
|
||||
{
|
||||
$this->driver_class = $driver_class;
|
||||
}
|
||||
|
||||
public function authorizeView($data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function authorizeRequest($request)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function paymentView($data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function processPaymentResponse($request)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/* This method is stubbed ready to go - you just need to harvest the equivalent 'transaction_reference' */
|
||||
private function processSuccessfulPayment($response)
|
||||
{
|
||||
$amount = array_sum(array_column($this->driver_class->payment_hash->invoices(), 'amount')) + $this->driver_class->payment_hash->fee_total;
|
||||
|
||||
$payment_record = [];
|
||||
$payment_record['amount'] = $amount;
|
||||
$payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER;
|
||||
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
|
||||
// $payment_record['transaction_reference'] = $response->transaction_id;
|
||||
|
||||
$payment = $this->driver_class->createPayment($payment_record, Payment::STATUS_COMPLETED);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||
|
||||
}
|
||||
|
||||
private function processUnsuccessfulPayment($response)
|
||||
{
|
||||
/*Harvest your own errors here*/
|
||||
// $error = $response->status_message;
|
||||
|
||||
// if(property_exists($response, 'approval_message') && $response->approval_message)
|
||||
// $error .= " - {$response->approval_message}";
|
||||
|
||||
// $error_code = property_exists($response, 'approval_message') ? $response->approval_message : 'Undefined code';
|
||||
|
||||
$data = [
|
||||
'response' => $response,
|
||||
'error' => $error,
|
||||
'error_code' => $error_code,
|
||||
];
|
||||
|
||||
return $this->driver_class->processUnsuccessfulTransaction($data);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Helpers */
|
||||
|
||||
/*
|
||||
You will need some helpers to handle successful and unsuccessful responses
|
||||
|
||||
Some considerations after a succesful transaction include:
|
||||
|
||||
Logging of events: success +/- failure
|
||||
Recording a payment
|
||||
Notifications
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\PaymentDrivers;
|
||||
namespace App\PaymentDrivers\Sample;
|
||||
|
||||
use App\Http\Requests\Payments\PaymentWebhookRequest;
|
||||
use App\Models\ClientGatewayToken;
|
||||
@ -17,10 +17,9 @@ use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Stripe\CreditCard;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class DriverTemplate extends BaseDriver
|
||||
class PaymentDriver extends BaseDriver
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
@ -85,12 +84,12 @@ class DriverTemplate extends BaseDriver
|
||||
|
||||
public function refund(Payment $payment, $amount, $return_client_response = false)
|
||||
{
|
||||
return $this->payment_method->yourRefundImplementationHere(); //this is your custom implementation from here
|
||||
//this is your custom implementation from here
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
return $this->payment_method->yourTokenBillingImplmentation(); //this is your custom implementation from here
|
||||
//this is your custom implementation from here
|
||||
}
|
||||
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
|
34
app/PaymentDrivers/Sample/resources/authorize.blade.php
Normal file
34
app/PaymentDrivers/Sample/resources/authorize.blade.php
Normal file
@ -0,0 +1,34 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')])
|
||||
|
||||
@section('gateway_head')
|
||||
@endsection
|
||||
|
||||
@section('gateway_content')
|
||||
<form action="{{ $payment_endpoint_url }}" method="post" id="server_response">
|
||||
|
||||
@if(!Request::isSecure())
|
||||
<p class="alert alert-failure">{{ ctrans('texts.https_required') }}</p>
|
||||
@endif
|
||||
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
|
||||
<!-- This is a generic credit card component utilizing CardJS -->
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.method')])
|
||||
{{ ctrans('texts.credit_card') }}
|
||||
@endcomponent
|
||||
|
||||
<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>
|
||||
@endsection
|
||||
|
||||
@section('gateway_footer')
|
||||
<!-- Your JS includes go here -->
|
||||
@endsection
|
64
app/PaymentDrivers/Sample/resources/pay.blade.php
Normal file
64
app/PaymentDrivers/Sample/resources/pay.blade.php
Normal file
@ -0,0 +1,64 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')])
|
||||
|
||||
@section('gateway_head')
|
||||
@endsection
|
||||
|
||||
@section('gateway_content')
|
||||
<form action="{{ $payment_endpoint_url }}" method="post" id="server_response">
|
||||
|
||||
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.method')])
|
||||
{{ ctrans('texts.credit_card') }}
|
||||
@endcomponent
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||
|
||||
<-- If there are existing tokens available these are displayed here for you -->
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
|
||||
@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
|
||||
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
|
||||
|
||||
<!-- This include gives the options to save the payment method -->
|
||||
@include('portal.ninja2020.gateways.includes.save_card')
|
||||
|
||||
<!-- This include pops up a credit card form -->
|
||||
@include('portal.ninja2020.gateways.wepay.includes.credit_card')
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.pay_now')
|
||||
|
||||
</form>
|
||||
@endsection
|
||||
|
||||
@section('gateway_footer')
|
||||
<script>
|
||||
|
||||
document.getElementById('pay-now').addEventListener('click', function() {
|
||||
document.getElementById('server_response').submit();
|
||||
});
|
||||
|
||||
</script>
|
||||
@endsection
|
||||
|
@ -61,7 +61,7 @@ class ACH
|
||||
|
||||
try {
|
||||
$source = Customer::createSource($customer->id, ['source' => $stripe_response->token->id], $this->stripe->stripe_connect_auth);
|
||||
// $source = $this->stripe->stripe->customers->createSource($customer->id, ['source' => $stripe_response->token->id]);
|
||||
|
||||
} catch (InvalidRequestException $e) {
|
||||
throw new PaymentFailed($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
@ -141,4 +141,68 @@ class ImportCustomers
|
||||
|
||||
$this->update_payment_methods->updateMethods($customer, $client);
|
||||
}
|
||||
|
||||
public function importCustomer($customer_id)
|
||||
{
|
||||
|
||||
$this->stripe->init();
|
||||
|
||||
$this->update_payment_methods = new UpdatePaymentMethods($this->stripe);
|
||||
|
||||
if(strlen($this->stripe->company_gateway->getConfigField('account_id')) < 1)
|
||||
throw new StripeConnectFailure('Stripe Connect has not been configured');
|
||||
|
||||
$customer = Customer::retrieve($customer_id, $this->stripe_connect_auth);
|
||||
|
||||
if(!$customer)
|
||||
return;
|
||||
|
||||
foreach($this->stripe->company_gateway->company->clients as $client)
|
||||
{
|
||||
if($client->present()->email() == $customer->email) {
|
||||
|
||||
$this->update_payment_methods->updateMethods($customer, $client);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function match()
|
||||
{
|
||||
$this->stripe->init();
|
||||
|
||||
$this->update_payment_methods = new UpdatePaymentMethods($this->stripe);
|
||||
|
||||
if(strlen($this->stripe->company_gateway->getConfigField('account_id')) < 1)
|
||||
throw new StripeConnectFailure('Stripe Connect has not been configured');
|
||||
|
||||
foreach($this->stripe->company_gateway->company->clients as $client)
|
||||
{
|
||||
|
||||
$searchResults = \Stripe\Customer::all([
|
||||
"email" => $client->present()->email(),
|
||||
"limit" => 2,
|
||||
"starting_after" => null
|
||||
],$this->stripe->stripe_connect_auth);
|
||||
|
||||
// nlog(count($searchResults));
|
||||
|
||||
if(count($searchResults) == 1)
|
||||
{
|
||||
|
||||
$cgt = ClientGatewayToken::where('gateway_customer_reference', $searchResults->data[0]->id)->where('company_id', $this->stripe->company_gateway->company->id)->exists();
|
||||
|
||||
if(!$cgt)
|
||||
{
|
||||
nlog("customer ".$searchResults->data[0]->id. " does not exist.");
|
||||
|
||||
$this->update_payment_methods->updateMethods($searchResults->data[0], $client);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,12 @@ class UpdatePaymentMethods
|
||||
$this->addOrUpdateCard($method, $customer->id, $client, GatewayType::SOFORT);
|
||||
}
|
||||
|
||||
//$this->importBankAccounts($customer, $client);
|
||||
}
|
||||
|
||||
private function importBankAccounts($customer, $client)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// private function addOrUpdateBankAccount($bank_account, $customer_reference, Client $client)
|
||||
|
@ -83,7 +83,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
* Initializes the Stripe API.
|
||||
* @return void
|
||||
*/
|
||||
public function init(): void
|
||||
public function init()
|
||||
{
|
||||
if($this->stripe_connect)
|
||||
{
|
||||
@ -103,6 +103,8 @@ class StripePaymentDriver extends BaseDriver
|
||||
Stripe::setApiKey($this->company_gateway->getConfigField('apiKey'));
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPaymentMethod($payment_method_id)
|
||||
@ -543,6 +545,16 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
}
|
||||
|
||||
public function importMatchedClients()
|
||||
{
|
||||
return (new ImportCustomers($this))->match();
|
||||
}
|
||||
|
||||
public function importCustomer($customer_id)
|
||||
{
|
||||
return (new ImportCustomers($this))->importCustomer($customer_id);
|
||||
}
|
||||
|
||||
public function verifyConnect()
|
||||
{
|
||||
return (new Verify($this))->run();
|
||||
|
@ -160,6 +160,7 @@ class CompanyTransformer extends EntityTransformer
|
||||
'invoice_task_datelog' => (bool) $company->invoice_task_datelog,
|
||||
'show_task_end_date' => (bool) $company->show_task_end_date,
|
||||
'markdown_enabled' => (bool) $company->markdown_enabled,
|
||||
'use_comma_as_decimal_place' => (bool) $company->use_comma_as_decimal_place,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -126,6 +126,7 @@ class HtmlEngine
|
||||
if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') {
|
||||
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.invoice')];
|
||||
$data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
||||
$data['$number_short'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number_short')];
|
||||
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.invoice_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')];
|
||||
@ -142,6 +143,7 @@ class HtmlEngine
|
||||
if ($this->entity_string == 'quote') {
|
||||
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.quote')];
|
||||
$data['$number'] = ['value' => $this->entity->number ?: '', 'label' => ctrans('texts.quote_number')];
|
||||
$data['$number_short'] = ['value' => $this->entity->number ?: '', 'label' => ctrans('texts.quote_number_short')];
|
||||
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.quote_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.view_quote')];
|
||||
@ -153,6 +155,7 @@ class HtmlEngine
|
||||
if ($this->entity_string == 'credit') {
|
||||
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.credit')];
|
||||
$data['$number'] = ['value' => $this->entity->number ?: '', 'label' => ctrans('texts.credit_number')];
|
||||
$data['$number_short'] = ['value' => $this->entity->number ?: '', 'label' => ctrans('texts.credit_number_short')];
|
||||
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.credit_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_credit').'</a>', 'label' => ctrans('texts.view_credit')];
|
||||
|
@ -38,7 +38,7 @@ class SystemHealth
|
||||
//'intl', //todo double check whether we need this for email dns validation
|
||||
];
|
||||
|
||||
private static $php_version = 7.3;
|
||||
private static $php_version = 7.4;
|
||||
|
||||
/**
|
||||
* Check loaded extensions / PHP version / DB Connections.
|
||||
|
@ -40,5 +40,6 @@ trait Uploadable
|
||||
$entity->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
],
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": "^7.3|^7.4|^8.0",
|
||||
"php": "^7.4|^8.0",
|
||||
"ext-dom": "*",
|
||||
"ext-json": "*",
|
||||
"ext-libxml": "*",
|
||||
@ -41,6 +41,7 @@
|
||||
"codedge/laravel-selfupdater": "^3.2",
|
||||
"composer/composer": "^2",
|
||||
"doctrine/dbal": "^2.10",
|
||||
"eway/eway-rapid-php": "^1.3",
|
||||
"fakerphp/faker": "^1.14",
|
||||
"fideloper/proxy": "^4.2",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
|
1448
composer.lock
generated
1448
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.2.19',
|
||||
'app_tag' => '5.2.19',
|
||||
'app_version' => '5.3.0',
|
||||
'app_tag' => '5.3.0',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Gateway;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ActivateEwayPaymentDriver extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if($eway = Gateway::find(3))
|
||||
{
|
||||
$eway->visible = true;
|
||||
$eway->provider = 'Eway';
|
||||
|
||||
$fields = json_decode($eway->fields);
|
||||
$fields->publicApiKey = '';
|
||||
$eway->fields = json_encode($fields);
|
||||
|
||||
$eway->save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UseCommaAsDecimalPlaceCompaniesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->boolean('use_comma_as_decimal_place')->default(0);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
'],
|
||||
['id' => 2, 'name' => 'CardSave', 'provider' => 'CardSave', 'key' => '46c5c1fed2c43acf4f379bae9c8b9f76', 'fields' => '{"merchantId":"","password":""}
|
||||
'],
|
||||
['id' => 3, 'name' => 'Eway Rapid', 'provider' => 'Eway_RapidShared', 'is_offsite' => true, 'key' => '944c20175bbe6b9972c05bcfe294c2c7', 'fields' => '{"apiKey":"","password":"","testMode":false}'],
|
||||
['id' => 3, 'name' => 'Eway Rapid', 'provider' => 'Eway', 'is_offsite' => true, 'key' => '944c20175bbe6b9972c05bcfe294c2c7', 'fields' => '{"apiKey":"","password":"","publicApiKey":"","testMode":false}'],
|
||||
['id' => 4, 'name' => 'FirstData Connect', 'provider' => 'FirstData_Connect', 'key' => '4e0ed0d34552e6cb433506d1ac03a418', 'fields' => '{"storeId":"","sharedSecret":"","testMode":false}'],
|
||||
['id' => 5, 'name' => 'Migs ThreeParty', 'provider' => 'Migs_ThreeParty', 'key' => '513cdc81444c87c4b07258bc2858d3fa', 'fields' => '{"merchantId":"","merchantAccessCode":"","secureHash":""}'],
|
||||
['id' => 6, 'name' => 'Migs TwoParty', 'provider' => 'Migs_TwoParty', 'key' => '99c2a271b5088951334d1302e038c01a', 'fields' => '{"merchantId":"","merchantAccessCode":"","secureHash":""}'],
|
||||
@ -96,7 +96,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
|
||||
Gateway::query()->update(['visible' => 0]);
|
||||
|
||||
Gateway::whereIn('id', [1,7,15,20,39,46,55,50])->update(['visible' => 1]);
|
||||
Gateway::whereIn('id', [1,3,7,15,20,39,46,55,50])->update(['visible' => 1]);
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
Gateway::whereIn('id', [20])->update(['visible' => 0]);
|
||||
|
1112
package-lock.json
generated
1112
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,7 @@
|
||||
"linkify-urls": "^3.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"resolve-url-loader": "^3.1.4",
|
||||
"sass": "^1.35.2",
|
||||
"sass": "^1.38.0",
|
||||
"sass-loader": "^8.0.0",
|
||||
"tailwindcss": "^1.9.6"
|
||||
}
|
||||
|
6
public/flutter_service_worker.js
vendored
6
public/flutter_service_worker.js
vendored
@ -30,11 +30,11 @@ const RESOURCES = {
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"manifest.json": "17bae385e59f59be709280b542203f8e",
|
||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
||||
"version.json": "46d4015fc9abcefe5371cafcf2084173",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"main.dart.js": "870ee0de972245c17e9db71991aecfc1",
|
||||
"/": "24783ba7c9b4ad6d85740d03acd4bf25"
|
||||
"main.dart.js": "88f74d039d64ab36ff800c42ba1d83c4",
|
||||
"/": "bafb7d5b8d7dac602c955954c6dd27ae"
|
||||
};
|
||||
|
||||
// The application shell files that are downloaded before a service worker can
|
||||
|
2
public/js/clients/payments/eway-credit-card.js
vendored
Normal file
2
public/js/clients/payments/eway-credit-card.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -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
|
||||
*/
|
146809
public/main.dart.js
vendored
146809
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
147561
public/main.foss.dart.js
vendored
147561
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
228236
public/main.foss.wasm.dart.js
vendored
Normal file
228236
public/main.foss.wasm.dart.js
vendored
Normal file
File diff suppressed because one or more lines are too long
232362
public/main.last.dart.js
vendored
Normal file
232362
public/main.last.dart.js
vendored
Normal file
File diff suppressed because one or more lines are too long
231108
public/main.next.dart.js
vendored
Normal file
231108
public/main.next.dart.js
vendored
Normal file
File diff suppressed because one or more lines are too long
341918
public/main.wasm.dart.js
vendored
341918
public/main.wasm.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -19,7 +19,7 @@
|
||||
],
|
||||
"icons": [
|
||||
{
|
||||
"src": "images/icon.png",
|
||||
"src": "images/logo.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
|
@ -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/eway-credit-card.js": "/js/clients/payments/eway-credit-card.js?id=08ea84e9451abd434cff",
|
||||
"/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=73b66e88e2daabcd6549",
|
||||
"/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",
|
||||
|
510
resources/js/clients/payments/eway-credit-card.js
vendored
Normal file
510
resources/js/clients/payments/eway-credit-card.js
vendored
Normal file
@ -0,0 +1,510 @@
|
||||
/**
|
||||
* 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 EwayRapid {
|
||||
constructor() {
|
||||
this.cardStyles =
|
||||
'padding: 2px; border: 1px solid #AAA; border-radius: 3px; height: 34px; width: 100%;';
|
||||
|
||||
this.errorCodes = new Map();
|
||||
|
||||
this.errorCodes.set('V6000', 'Validation error');
|
||||
this.errorCodes.set('V6001', 'Invalid CustomerIP');
|
||||
this.errorCodes.set('V6002', 'Invalid DeviceID');
|
||||
this.errorCodes.set('V6003', 'Invalid Request PartnerID');
|
||||
this.errorCodes.set('V6004', 'Invalid Request Method');
|
||||
this.errorCodes.set(
|
||||
'V6010',
|
||||
'Invalid TransactionType, account not certified for eCome only MOTO or Recurring available'
|
||||
);
|
||||
this.errorCodes.set('V6011', 'Invalid Payment TotalAmount');
|
||||
this.errorCodes.set('V6012', 'Invalid Payment InvoiceDescription');
|
||||
this.errorCodes.set('V6013', 'Invalid Payment InvoiceNumber');
|
||||
this.errorCodes.set('V6014', 'Invalid Payment InvoiceReference');
|
||||
this.errorCodes.set('V6015', 'Invalid Payment CurrencyCode');
|
||||
this.errorCodes.set('V6016', 'Payment Required');
|
||||
this.errorCodes.set('V6017', 'Payment CurrencyCode Required');
|
||||
this.errorCodes.set('V6018', 'Unknown Payment CurrencyCode');
|
||||
this.errorCodes.set(
|
||||
'V6019',
|
||||
'Cardholder identity authentication required'
|
||||
);
|
||||
this.errorCodes.set('V6020', 'Cardholder Input Required');
|
||||
this.errorCodes.set('V6021', 'EWAY_CARDHOLDERNAME Required');
|
||||
this.errorCodes.set('V6022', 'EWAY_CARDNUMBER Required');
|
||||
this.errorCodes.set('V6023', 'EWAY_CARDCVN Required');
|
||||
this.errorCodes.set(
|
||||
'V6024',
|
||||
'Cardholder Identity Authentication One Time Password Not Active Yet'
|
||||
);
|
||||
this.errorCodes.set('V6025', 'PIN Required');
|
||||
this.errorCodes.set('V6033', 'Invalid Expiry Date');
|
||||
this.errorCodes.set('V6034', 'Invalid Issue Number');
|
||||
this.errorCodes.set('V6035', 'Invalid Valid From Date');
|
||||
this.errorCodes.set('V6039', 'Invalid Network Token Status');
|
||||
this.errorCodes.set('V6040', 'Invalid TokenCustomerID');
|
||||
this.errorCodes.set('V6041', 'Customer Required');
|
||||
this.errorCodes.set('V6042', 'Customer FirstName Required');
|
||||
this.errorCodes.set('V6043', 'Customer LastName Required');
|
||||
this.errorCodes.set('V6044', 'Customer CountryCode Required');
|
||||
this.errorCodes.set('V6045', 'Customer Title Required');
|
||||
this.errorCodes.set('V6046', 'TokenCustomerID Required');
|
||||
this.errorCodes.set('V6047', 'RedirectURL Required');
|
||||
this.errorCodes.set(
|
||||
'V6048',
|
||||
'CheckoutURL Required when CheckoutPayment specified'
|
||||
);
|
||||
this.errorCodes.set('V6049', 'nvalid Checkout URL');
|
||||
this.errorCodes.set('V6051', 'Invalid Customer FirstName');
|
||||
this.errorCodes.set('V6052', 'Invalid Customer LastName');
|
||||
this.errorCodes.set('V6053', 'Invalid Customer CountryCode');
|
||||
this.errorCodes.set('V6058', 'Invalid Customer Title');
|
||||
this.errorCodes.set('V6059', 'Invalid RedirectURL');
|
||||
this.errorCodes.set('V6060', 'Invalid TokenCustomerID');
|
||||
this.errorCodes.set('V6061', 'Invalid Customer Reference');
|
||||
this.errorCodes.set('V6062', 'Invalid Customer CompanyName');
|
||||
this.errorCodes.set('V6063', 'Invalid Customer JobDescription');
|
||||
this.errorCodes.set('V6064', 'Invalid Customer Street1');
|
||||
this.errorCodes.set('V6065', 'Invalid Customer Street2');
|
||||
this.errorCodes.set('V6066', 'Invalid Customer City');
|
||||
this.errorCodes.set('V6067', 'Invalid Customer State');
|
||||
this.errorCodes.set('V6068', 'Invalid Customer PostalCode');
|
||||
this.errorCodes.set('V6069', 'Invalid Customer Email');
|
||||
this.errorCodes.set('V6070', 'Invalid Customer Phone');
|
||||
this.errorCodes.set('V6071', 'Invalid Customer Mobile');
|
||||
this.errorCodes.set('V6072', 'Invalid Customer Comments');
|
||||
this.errorCodes.set('V6073', 'Invalid Customer Fax');
|
||||
this.errorCodes.set('V6074', 'Invalid Customer URL');
|
||||
this.errorCodes.set('V6075', 'Invalid ShippingAddress FirstName');
|
||||
this.errorCodes.set('V6076', 'Invalid ShippingAddress LastName');
|
||||
this.errorCodes.set('V6077', 'Invalid ShippingAddress Street1');
|
||||
this.errorCodes.set('V6078', 'Invalid ShippingAddress Street2');
|
||||
this.errorCodes.set('V6079', 'Invalid ShippingAddress City');
|
||||
this.errorCodes.set('V6080', 'Invalid ShippingAddress State');
|
||||
this.errorCodes.set('V6081', 'Invalid ShippingAddress PostalCode');
|
||||
this.errorCodes.set('V6082', 'Invalid ShippingAddress Email');
|
||||
this.errorCodes.set('V6083', 'Invalid ShippingAddress Phone');
|
||||
this.errorCodes.set('V6084', 'Invalid ShippingAddress Country');
|
||||
this.errorCodes.set('V6085', 'Invalid ShippingAddress ShippingMethod');
|
||||
this.errorCodes.set('V6086', 'Invalid ShippingAddress Fax');
|
||||
this.errorCodes.set('V6091', 'Unknown Customer CountryCode');
|
||||
this.errorCodes.set('V6092', 'Unknown ShippingAddress CountryCode');
|
||||
this.errorCodes.set('V6093', 'Insufficient Address Information');
|
||||
this.errorCodes.set('V6100', 'Invalid EWAY_CARDNAME');
|
||||
this.errorCodes.set('V6101', 'Invalid EWAY_CARDEXPIRYMONTH');
|
||||
this.errorCodes.set('V6102', 'Invalid EWAY_CARDEXPIRYYEAR');
|
||||
this.errorCodes.set('V6103', 'Invalid EWAY_CARDSTARTMONTH');
|
||||
this.errorCodes.set('V6104', 'Invalid EWAY_CARDSTARTYEAR');
|
||||
this.errorCodes.set('V6105', 'Invalid EWAY_CARDISSUENUMBER');
|
||||
this.errorCodes.set('V6106', 'Invalid EWAY_CARDCVN');
|
||||
this.errorCodes.set('V6107', 'Invalid EWAY_ACCESSCODE');
|
||||
this.errorCodes.set('V6108', 'Invalid CustomerHostAddress');
|
||||
this.errorCodes.set('V6109', 'Invalid UserAgent');
|
||||
this.errorCodes.set('V6110', 'Invalid EWAY_CARDNUMBER');
|
||||
this.errorCodes.set(
|
||||
'V6111',
|
||||
'Unauthorised API Access, Account Not PCI Certified'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6112',
|
||||
'Redundant card details other than expiry year and month'
|
||||
);
|
||||
this.errorCodes.set('V6113', 'Invalid transaction for refund');
|
||||
this.errorCodes.set('V6114', 'Gateway validation error');
|
||||
this.errorCodes.set(
|
||||
'V6115',
|
||||
'Invalid DirectRefundRequest, Transaction ID'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6116',
|
||||
'Invalid card data on original TransactionID'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6117',
|
||||
'Invalid CreateAccessCodeSharedRequest, FooterText'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6118',
|
||||
'Invalid CreateAccessCodeSharedRequest, HeaderText'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6119',
|
||||
'Invalid CreateAccessCodeSharedRequest, Language'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6120',
|
||||
'Invalid CreateAccessCodeSharedRequest, LogoUrl'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6121',
|
||||
'Invalid TransactionSearch, Filter Match Type'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6122',
|
||||
'Invalid TransactionSearch, Non numeric Transaction ID'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6123',
|
||||
'Invalid TransactionSearch,no TransactionID or AccessCode specified'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6124',
|
||||
'Invalid Line Items. The line items have been provided however the totals do not match the TotalAmount field'
|
||||
);
|
||||
this.errorCodes.set('V6125', 'Selected Payment Type not enabled');
|
||||
this.errorCodes.set(
|
||||
'V6126',
|
||||
'Invalid encrypted card number, decryption failed'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6127',
|
||||
'Invalid encrypted cvn, decryption failed'
|
||||
);
|
||||
this.errorCodes.set('V6128', 'Invalid Method for Payment Type');
|
||||
this.errorCodes.set(
|
||||
'V6129',
|
||||
'Transaction has not been authorised for Capture/Cancellation'
|
||||
);
|
||||
this.errorCodes.set('V6130', 'Generic customer information error');
|
||||
this.errorCodes.set('V6131', 'Generic shipping information error');
|
||||
this.errorCodes.set(
|
||||
'V6132',
|
||||
'Transaction has already been completed or voided, operation not permitted'
|
||||
);
|
||||
this.errorCodes.set('V6133', 'Checkout not available for Payment Type');
|
||||
this.errorCodes.set(
|
||||
'V6134',
|
||||
'Invalid Auth Transaction ID for Capture/Void'
|
||||
);
|
||||
this.errorCodes.set('V6135', 'PayPal Error Processing Refund');
|
||||
this.errorCodes.set(
|
||||
'V6136',
|
||||
'Original transaction does not exist or state is incorrect'
|
||||
);
|
||||
this.errorCodes.set('V6140', 'Merchant account is suspended');
|
||||
this.errorCodes.set(
|
||||
'V6141',
|
||||
'Invalid PayPal account details or API signature'
|
||||
);
|
||||
this.errorCodes.set('V6142', 'Authorise not available for Bank/Branch');
|
||||
this.errorCodes.set('V6143', 'Invalid Public Key');
|
||||
this.errorCodes.set(
|
||||
'V6144',
|
||||
'Method not available with Public API Key Authentication'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6145',
|
||||
'Credit Card not allow if Token Customer ID is provided with Public API Key Authentication'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6146',
|
||||
'Client Side Encryption Key Missing or Invalid'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6147',
|
||||
'Unable to Create One Time Code for Secure Field'
|
||||
);
|
||||
this.errorCodes.set('V6148', 'Secure Field has Expired');
|
||||
this.errorCodes.set('V6149', 'Invalid Secure Field One Time Code');
|
||||
this.errorCodes.set('V6150', 'Invalid Refund Amount');
|
||||
this.errorCodes.set(
|
||||
'V6151',
|
||||
'Refund amount greater than original transaction'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6152',
|
||||
'Original transaction already refunded for total amount'
|
||||
);
|
||||
this.errorCodes.set('V6153', 'Card type not support by merchant');
|
||||
this.errorCodes.set('V6154', 'Insufficent Funds Available For Refund');
|
||||
this.errorCodes.set('V6155', 'Missing one or more fields in request');
|
||||
this.errorCodes.set('V6160', 'Encryption Method Not Supported');
|
||||
this.errorCodes.set(
|
||||
'V6161',
|
||||
'Encryption failed, missing or invalid key'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6165',
|
||||
'Invalid Click-to-Pay (Visa Checkout) data or decryption failed'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6170',
|
||||
'Invalid TransactionSearch, Invoice Number is not unique'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6171',
|
||||
'Invalid TransactionSearch, Invoice Number not found'
|
||||
);
|
||||
this.errorCodes.set('V6220', 'Three domain secure XID invalid');
|
||||
this.errorCodes.set('V6221', 'Three domain secure ECI invalid');
|
||||
this.errorCodes.set('V6222', 'Three domain secure AVV invalid');
|
||||
this.errorCodes.set('V6223', 'Three domain secure XID is required');
|
||||
this.errorCodes.set('V6224', 'Three Domain Secure ECI is required');
|
||||
this.errorCodes.set('V6225', 'Three Domain Secure AVV is required');
|
||||
this.errorCodes.set(
|
||||
'V6226',
|
||||
'Three Domain Secure AuthStatus is required'
|
||||
);
|
||||
this.errorCodes.set('V6227', 'Three Domain Secure AuthStatus invalid');
|
||||
this.errorCodes.set('V6228', 'Three domain secure Version is required');
|
||||
this.errorCodes.set(
|
||||
'V6230',
|
||||
'Three domain secure Directory Server Txn ID invalid'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6231',
|
||||
'Three domain secure Directory Server Txn ID is required'
|
||||
);
|
||||
this.errorCodes.set('V6232', 'Three domain secure Version is invalid');
|
||||
this.errorCodes.set('V6501', 'Invalid Amex InstallementPlan');
|
||||
this.errorCodes.set(
|
||||
'V6502',
|
||||
'Invalid Number Of Installements for Amex. Valid values are from 0 to 99 inclusive'
|
||||
);
|
||||
this.errorCodes.set('V6503', 'Merchant Amex ID required');
|
||||
this.errorCodes.set('V6504', 'Invalid Merchant Amex ID');
|
||||
this.errorCodes.set('V6505', 'Merchant Terminal ID required');
|
||||
this.errorCodes.set('V6506', 'Merchant category code required');
|
||||
this.errorCodes.set('V6507', 'Invalid merchant category code');
|
||||
this.errorCodes.set('V6508', 'Amex 3D ECI required');
|
||||
this.errorCodes.set('V6509', 'Invalid Amex 3D ECI');
|
||||
this.errorCodes.set('V6510', 'Invalid Amex 3D verification value');
|
||||
this.errorCodes.set('V6511', 'Invalid merchant location data');
|
||||
this.errorCodes.set('V6512', 'Invalid merchant street address');
|
||||
this.errorCodes.set('V6513', 'Invalid merchant city');
|
||||
this.errorCodes.set('V6514', 'Invalid merchant country');
|
||||
this.errorCodes.set('V6515', 'Invalid merchant phone');
|
||||
this.errorCodes.set('V6516', 'Invalid merchant postcode');
|
||||
this.errorCodes.set('V6517', 'Amex connection error');
|
||||
this.errorCodes.set(
|
||||
'V6518',
|
||||
'Amex EC Card Details API returned invalid data'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6520',
|
||||
'Invalid or missing Amex Point Of Sale Data'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6521',
|
||||
'Invalid or missing Amex transaction date time'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6522',
|
||||
'Invalid or missing Amex Original transaction date time'
|
||||
);
|
||||
this.errorCodes.set(
|
||||
'V6530',
|
||||
'Credit Card Number in non Credit Card Field'
|
||||
);
|
||||
}
|
||||
|
||||
get groupFieldConfig() {
|
||||
return {
|
||||
publicApiKey: document.querySelector('meta[name=public-api-key]')
|
||||
?.content,
|
||||
fieldDivId: 'eway-secure-panel',
|
||||
fieldType: 'group',
|
||||
styles: '',
|
||||
layout: {
|
||||
fonts: ['Lobster'],
|
||||
rows: [
|
||||
{
|
||||
styles: '',
|
||||
cells: [
|
||||
{
|
||||
colSpan: 12,
|
||||
styles: 'margin-top: 15px;',
|
||||
label: {
|
||||
fieldColSpan: 4,
|
||||
text: document.querySelector(
|
||||
'meta[name=translation-card-name]'
|
||||
)?.content,
|
||||
styles: '',
|
||||
},
|
||||
field: {
|
||||
fieldColSpan: 8,
|
||||
fieldType: 'name',
|
||||
styles: this.cardStyles,
|
||||
divStyles: 'padding-left: 10px;',
|
||||
},
|
||||
},
|
||||
{
|
||||
colSpan: 12,
|
||||
styles: 'margin-top: 15px;',
|
||||
label: {
|
||||
fieldColSpan: 4,
|
||||
text: document.querySelector(
|
||||
'meta[name=translation-expiry_date]'
|
||||
)?.content,
|
||||
styles: '',
|
||||
},
|
||||
field: {
|
||||
fieldColSpan: 8,
|
||||
fieldType: 'expirytext',
|
||||
styles: this.cardStyles,
|
||||
divStyles: 'padding-left: 10px;',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
styles: '',
|
||||
cells: [
|
||||
{
|
||||
colSpan: 12,
|
||||
styles: 'margin-top: 15px;',
|
||||
label: {
|
||||
fieldColSpan: 4,
|
||||
text: document.querySelector(
|
||||
'meta[name=translation-card_number]'
|
||||
)?.content,
|
||||
styles: '',
|
||||
},
|
||||
field: {
|
||||
fieldColSpan: 8,
|
||||
fieldType: 'card',
|
||||
styles: this.cardStyles,
|
||||
},
|
||||
},
|
||||
{
|
||||
colSpan: 12,
|
||||
styles: 'margin-top: 15px;',
|
||||
label: {
|
||||
fieldColSpan: 4,
|
||||
text: document.querySelector(
|
||||
'meta[name=translation-cvv]'
|
||||
)?.content,
|
||||
styles: '',
|
||||
},
|
||||
field: {
|
||||
fieldColSpan: 8,
|
||||
fieldType: 'cvn',
|
||||
styles: this.cardStyles,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
securePanelCallback(event) {
|
||||
document.getElementById('errors').hidden = true;
|
||||
|
||||
if (event.errors) {
|
||||
return this.handleErrors(event.errors);
|
||||
}
|
||||
|
||||
if (document.getElementById('authorize-card')) {
|
||||
document.getElementById('authorize-card').disabled = false;
|
||||
}
|
||||
|
||||
if (document.getElementById('pay-now')) {
|
||||
document.getElementById('pay-now').disabled = false;
|
||||
}
|
||||
|
||||
document.querySelector('input[name=securefieldcode]').value =
|
||||
event.secureFieldCode;
|
||||
}
|
||||
|
||||
handleErrors(errors) {
|
||||
let _errors = errors.split(' ');
|
||||
let shouldShowGenericError = false;
|
||||
let message = '';
|
||||
|
||||
_errors.forEach((error) => {
|
||||
message = message.concat(this.errorCodes.get(error) + '<br>');
|
||||
});
|
||||
|
||||
document.getElementById('errors').innerHTML = message;
|
||||
document.getElementById('errors').hidden = false;
|
||||
}
|
||||
|
||||
completeAuthorization(event) {
|
||||
event.target.parentElement.disabled = true;
|
||||
|
||||
document.getElementById('server-response').submit();
|
||||
}
|
||||
|
||||
completePaymentUsingToken(event) {
|
||||
event.target.parentElement.disabled = true;
|
||||
|
||||
document.getElementById('server-response').submit();
|
||||
}
|
||||
|
||||
completePaymentWithoutToken(event) {
|
||||
event.target.parentElement.disabled = true;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.eWAY = eWAY.setupSecureField(this.groupFieldConfig, (event) =>
|
||||
this.securePanelCallback(event)
|
||||
);
|
||||
}
|
||||
|
||||
handle() {
|
||||
this.initialize();
|
||||
|
||||
document
|
||||
.getElementById('authorize-card')
|
||||
?.addEventListener('click', (e) => this.completeAuthorization(e));
|
||||
|
||||
Array.from(
|
||||
document.getElementsByClassName('toggle-payment-with-token')
|
||||
).forEach((element) =>
|
||||
element.addEventListener('click', (element) => {
|
||||
document
|
||||
.getElementById('eway-secure-panel')
|
||||
.classList.add('hidden');
|
||||
document.getElementById('save-card--container').style.display =
|
||||
'none';
|
||||
document.querySelector('input[name=token]').value =
|
||||
element.target.dataset.token;
|
||||
document.getElementById('pay-now').disabled = false;
|
||||
})
|
||||
);
|
||||
|
||||
document
|
||||
.getElementById('toggle-payment-with-credit-card')
|
||||
.addEventListener('click', (element) => {
|
||||
document
|
||||
.getElementById('eway-secure-panel')
|
||||
.classList.remove('hidden');
|
||||
document.getElementById('save-card--container').style.display =
|
||||
'grid';
|
||||
document.querySelector('input[name=token]').value = '';
|
||||
document.getElementById('pay-now').disabled = true;
|
||||
});
|
||||
|
||||
document.getElementById('pay-now')?.addEventListener('click', (e) => {
|
||||
let tokenInput = document.querySelector('input[name=token]');
|
||||
|
||||
if (tokenInput.value) {
|
||||
return this.completePaymentUsingToken(e);
|
||||
}
|
||||
|
||||
return this.completePaymentWithoutToken(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
new EwayRapid().handle();
|
@ -4296,6 +4296,8 @@ $LANG = array(
|
||||
'lang_Persian' => 'Persian',
|
||||
'lang_Latvian' => 'Latvian',
|
||||
'expiry_date' => 'Expiry date',
|
||||
'cardholder_name' => 'Card holder name',
|
||||
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -1,26 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-report-errors="{{ $report_errors }}" data-rc="{{ $rc }}">
|
||||
<html data-report-errors="{{ $report_errors }}" data-rc="{{ $rc }}" data-build="{{ $build }}">
|
||||
<head>
|
||||
<!-- Source: https://github.com/invoiceninja/invoiceninja -->
|
||||
<!-- Version: {{ config('ninja.app_version') }} -->
|
||||
<base href="{{ $_SERVER['REQUEST_URI'] }}">
|
||||
<meta charset="UTF-8">
|
||||
<title>Invoice Ninja</title>
|
||||
<meta name="google-signin-client_id" content="{{ config('services.google.client_id') }}">
|
||||
<link rel="manifest" href="manifest.json?v={{ config('ninja.app_version') }}">
|
||||
<script src="{{ asset('js/pdf.min.js') }}"></script>
|
||||
<script type="text/javascript">
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = "{{ asset('js/pdf.worker.min.js') }}";
|
||||
</script>
|
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||
<meta name="description" content="Invoice Clients, Track Work-Time, Get Paid Online.">
|
||||
<!-- iOS meta tags & icons -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="invoiceninja_client">
|
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||
<title>Invoice Ninja</title>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body style="background-color:#888888;">
|
||||
|
||||
<style>
|
||||
|
||||
/* fix for blurry fonts
|
||||
flt-glass-pane {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
*/
|
||||
|
||||
/* https://projects.lukehaas.me/css-loaders/ */
|
||||
.loader,
|
||||
.loader:before,
|
||||
@ -34,7 +35,7 @@
|
||||
animation: load7 1.8s infinite ease-in-out;
|
||||
}
|
||||
.loader {
|
||||
color: #FFFFFF;
|
||||
color: #ffffff;
|
||||
font-size: 10px;
|
||||
margin: 80px auto;
|
||||
position: relative;
|
||||
@ -79,85 +80,82 @@
|
||||
box-shadow: 0 2.5em 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color:#888888;">
|
||||
<!-- This script installs service_worker.js to provide PWA functionality to
|
||||
application. For more information, see:
|
||||
https://developers.google.com/web/fundamentals/primers/service-workers -->
|
||||
|
||||
<script>
|
||||
@if (request()->clear_local)
|
||||
window.onload = function() {
|
||||
window.localStorage.clear();
|
||||
}
|
||||
@endif
|
||||
var serviceWorkerVersion = null;
|
||||
var scriptLoaded = false;
|
||||
function loadMainDartJs() {
|
||||
if (scriptLoaded) {
|
||||
return;
|
||||
}
|
||||
scriptLoaded = true;
|
||||
var scriptTag = document.createElement('script');
|
||||
@if(config('ninja.flutter_renderer') == 'hosted')
|
||||
scriptTag.src = 'main.dart.js';
|
||||
@else
|
||||
scriptTag.src = 'main.foss.dart.js';
|
||||
@endif
|
||||
scriptTag.type = 'application/javascript';
|
||||
document.body.append(scriptTag);
|
||||
}
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
// Service workers are supported. Use them.
|
||||
window.addEventListener('load', function () {
|
||||
// Wait for registration to finish before dropping the <script> tag.
|
||||
// Otherwise, the browser will load the script multiple times,
|
||||
// potentially different versions.
|
||||
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
|
||||
navigator.serviceWorker.register(serviceWorkerUrl)
|
||||
.then((reg) => {
|
||||
function waitForActivation(serviceWorker) {
|
||||
serviceWorker.addEventListener('statechange', () => {
|
||||
if (serviceWorker.state == 'activated') {
|
||||
console.log('Installed new service worker.');
|
||||
loadMainDartJs();
|
||||
}
|
||||
navigator.serviceWorker.register('flutter_service_worker.js?v={{ config('ninja.app_version') }}');
|
||||
});
|
||||
}
|
||||
if (!reg.active && (reg.installing || reg.waiting)) {
|
||||
// No active web worker and we have installed or are installing
|
||||
// one for the first time. Simply wait for it to activate.
|
||||
waitForActivation(reg.installing ?? reg.waiting);
|
||||
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
|
||||
// When the app updates the serviceWorkerVersion changes, so we
|
||||
// need to ask the service worker to update.
|
||||
console.log('New service worker available.');
|
||||
reg.update();
|
||||
waitForActivation(reg.installing);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function(event) {
|
||||
document.getElementById('loader').style.display = 'none';
|
||||
});
|
||||
|
||||
|
||||
function invokeServiceWorkerUpdateFlow() {
|
||||
// you have a better UI here, reloading is not a great user experince here.
|
||||
const confirmed = alert('New version of the app is available. Refresh now');
|
||||
if (confirmed == true) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
async function handleServiceWorker() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
// get the ServiceWorkerRegistration instance
|
||||
const registration = await navigator.serviceWorker.getRegistration();
|
||||
// (it is also returned from navigator.serviceWorker.register() function)
|
||||
|
||||
if (registration) {
|
||||
// detect Service Worker update available and wait for it to become installed
|
||||
registration.addEventListener('updatefound', () => {
|
||||
if (registration.installing) {
|
||||
// wait until the new Service worker is actually installed (ready to take over)
|
||||
registration.installing.addEventListener('statechange', () => {
|
||||
if (registration.waiting) {
|
||||
// if there's an existing controller (previous Service Worker), show the prompt
|
||||
if (navigator.serviceWorker.controller) {
|
||||
invokeServiceWorkerUpdateFlow(registration);
|
||||
} else {
|
||||
// Existing service worker is still good.
|
||||
console.log('Loading app from service worker.');
|
||||
loadMainDartJs();
|
||||
// otherwise it's the first install, nothing to do
|
||||
console.log('Service Worker initialized for the first time');
|
||||
}
|
||||
}
|
||||
});
|
||||
// If service worker doesn't succeed in a reasonable amount of time,
|
||||
// fallback to plaint <script> tag.
|
||||
setTimeout(() => {
|
||||
if (!scriptLoaded) {
|
||||
console.warn(
|
||||
'Failed to load app from service worker. Falling back to plain <script> tag.',
|
||||
);
|
||||
loadMainDartJs();
|
||||
}
|
||||
}, 4000);
|
||||
});
|
||||
} else {
|
||||
// Service workers not supported. Just drop the <script> tag.
|
||||
loadMainDartJs();
|
||||
|
||||
let refreshing = false;
|
||||
|
||||
// detect controller change and refresh the page
|
||||
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
||||
if (!refreshing) {
|
||||
window.location.reload();
|
||||
refreshing = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleServiceWorker();
|
||||
|
||||
</script>
|
||||
|
||||
<script defer src="{{ $path }}?v={{ config('ninja.app_version') }}" type="application/javascript"></script>
|
||||
|
||||
|
||||
<center style="padding-top: 150px" id="loader">
|
||||
<div class="loader"></div>
|
||||
</center>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -2,6 +2,7 @@
|
||||
:root {
|
||||
--primary-color: $primary_color;
|
||||
--secondary-color: $secondary_color;
|
||||
--line-height: 1.6;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -31,10 +32,11 @@
|
||||
padding: 3rem;
|
||||
color: white;
|
||||
min-width: 100%;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
max-width: 55%;
|
||||
}
|
||||
|
||||
#company-details,
|
||||
@ -47,6 +49,7 @@
|
||||
margin: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
#client-details > :first-child {
|
||||
@ -251,6 +254,10 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
[data-ref="product_table-product.description-th"] {
|
||||
width: 23%;
|
||||
}
|
||||
|
||||
/** Useful snippets, uncomment to enable. **/
|
||||
|
||||
/** Hide company logo **/
|
||||
|
@ -2,6 +2,7 @@
|
||||
:root {
|
||||
--primary-color: $primary_color;
|
||||
--secondary-color: $secondary_color;
|
||||
--line-height: 1.6;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -40,16 +41,14 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #AAA9A9;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
#company-address {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #b1b1b1;
|
||||
}
|
||||
|
||||
#company-address > * {
|
||||
margin-bottom: 0.8rem;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
.entity-issued-to {
|
||||
@ -58,8 +57,8 @@
|
||||
}
|
||||
|
||||
.client-and-entity-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1.5fr;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@ -67,6 +66,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 1rem;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
#client-details > p:nth-child(1) {
|
||||
@ -82,6 +82,8 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#entity-details p { margin-right: 20px; }
|
||||
|
||||
#entity-details th {
|
||||
font-weight: normal;
|
||||
padding-bottom: .5rem;
|
||||
@ -195,6 +197,7 @@
|
||||
#table-totals>.totals-table-right-side>*> :nth-child(1) {
|
||||
text-align: "$dir_text_align";
|
||||
margin-top: .75rem;
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
#table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) {
|
||||
@ -205,6 +208,7 @@
|
||||
|
||||
#table-totals>.totals-table-right-side>*> :nth-child(2) {
|
||||
text-align: right;
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
#table-totals
|
||||
@ -244,6 +248,15 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
[data-ref="totals_table-outstanding-label"],
|
||||
[data-ref="totals_table-outstanding"] {
|
||||
background-color: var(--secondary-color);
|
||||
color: white;
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
/** Useful snippets, uncomment to enable. **/
|
||||
|
||||
/** Hide company logo **/
|
||||
|
@ -2,6 +2,7 @@
|
||||
:root {
|
||||
--primary-color: $primary_color;
|
||||
--secondary-color: $secondary_color;
|
||||
--line-height: 1.6;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -35,6 +36,7 @@
|
||||
#company-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
#company-details > p:first-child {
|
||||
@ -44,10 +46,11 @@
|
||||
#company-address {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
.entity-label {
|
||||
margin-top: 1rem;
|
||||
margin-top: 2.5rem;
|
||||
text-transform: uppercase;
|
||||
padding-left: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
@ -56,8 +59,7 @@
|
||||
}
|
||||
|
||||
.client-and-entity-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: .75fr 1fr;
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
border-top: 1px solid #d8d8d8;
|
||||
border-bottom: 1px solid #d8d8d8;
|
||||
@ -65,16 +67,21 @@
|
||||
|
||||
#entity-details {
|
||||
text-align: left;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#entity-details > tr,
|
||||
#entity-details th {
|
||||
font-weight: normal;
|
||||
padding-right: 15px;
|
||||
padding-top: 2.5px;
|
||||
padding-bottom: 2.5px;
|
||||
}
|
||||
|
||||
#client-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
#client-details > :first-child {
|
||||
@ -202,6 +209,12 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.company-logo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/** Useful snippets, uncomment to enable. **/
|
||||
|
||||
/** Hide company logo **/
|
||||
@ -236,11 +249,9 @@
|
||||
|
||||
<div id="body">
|
||||
<div class="header-container">
|
||||
<img
|
||||
class="company-logo"
|
||||
src="$company.logo"
|
||||
alt="$company.name logo"
|
||||
/>
|
||||
<div class="company-logo-container">
|
||||
<img class="company-logo" src="$company.logo" alt="$company.name logo">
|
||||
</div>
|
||||
|
||||
<div id="company-details"></div>
|
||||
<div id="company-address"></div>
|
||||
@ -248,7 +259,9 @@
|
||||
|
||||
<p class="entity-label">$entity_label</p>
|
||||
<div class="client-and-entity-wrapper">
|
||||
<div>
|
||||
<table id="entity-details" cellspacing="0" dir="$dir"></table>
|
||||
</div>
|
||||
|
||||
<div id="client-details"></div>
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@
|
||||
:root {
|
||||
--primary-color: $primary_color;
|
||||
--secondary-color: $secondary_color;
|
||||
--line-height: 1.6;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -26,12 +27,15 @@
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 20px;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
}
|
||||
|
||||
#entity-details p { margin-top: 5px; }
|
||||
|
||||
.header-wrapper #client-details,
|
||||
.header-wrapper #company-details,
|
||||
.header-wrapper #company-address {
|
||||
@ -234,6 +238,7 @@
|
||||
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
|
||||
</style>
|
||||
|
||||
|
||||
<div id="header"></div>
|
||||
|
||||
<div id="body">
|
||||
|
@ -2,6 +2,7 @@
|
||||
:root {
|
||||
--primary-color: $primary_color;
|
||||
--secondary-color: $secondary_color;
|
||||
--line-height: 1.6;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -23,14 +24,14 @@
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
max-width: 55%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.company-logo-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-bottom: 2.5rem;
|
||||
padding-bottom: 60px;
|
||||
border-bottom: 4px solid;
|
||||
}
|
||||
|
||||
@ -39,12 +40,15 @@
|
||||
}
|
||||
|
||||
.client-entity-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1.8fr 1.2fr;
|
||||
display: flex;
|
||||
margin-top: 3rem;
|
||||
gap: 10px;
|
||||
gap: 20px;
|
||||
margin-left: 10px;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
#entity-details p { margin-right: 20px; margin-top: 5px; }
|
||||
|
||||
.client-entity-wrapper .wrapper-info-text {
|
||||
display: block;
|
||||
font-size: 1.5rem;
|
||||
@ -56,6 +60,8 @@
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.text-with-client { margin-right: 15px; }
|
||||
|
||||
.client-entity-wrapper .wrapper-left-side #client-details,
|
||||
.client-entity-wrapper .wrapper-left-side #company-details,
|
||||
.client-entity-wrapper .wrapper-left-side #company-address {
|
||||
@ -121,11 +127,18 @@
|
||||
#product-table > tbody > tr > td,
|
||||
#delivery-note-table > tbody > tr > td,
|
||||
#task-table > tbody > tr > td {
|
||||
border-bottom: 1px solid;
|
||||
border-top: 1px solid;
|
||||
border-bottom: 1pt solid;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
#product-table > tbody > tr:first-child > td,
|
||||
#delivery-note-table > tbody > tr:first-child > td,
|
||||
#task-table > tbody > tr:first-child > td {
|
||||
border-top: 1pt solid !important;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
|
||||
#product-table > tbody > tr > td:last-child,
|
||||
#delivery-note-table > tbody > tr > td:last-child,
|
||||
#task-table > tbody > tr > td:last-child {
|
||||
@ -236,24 +249,21 @@
|
||||
|
||||
<div id="body">
|
||||
<div class="company-logo-wrapper">
|
||||
<img
|
||||
class="company-logo"
|
||||
src="$company.logo"
|
||||
alt="$company.name logo"
|
||||
/>
|
||||
<img class="company-logo" src="$company.logo" alt="$company.name logo">
|
||||
</div>
|
||||
|
||||
<hr class="double-border"/>
|
||||
<hr class="double-border">
|
||||
|
||||
<div class="client-entity-wrapper">
|
||||
<div class="wrapper-left-side">
|
||||
<div class="text-with-client">
|
||||
<h2 class="wrapper-info-text">$balance_due_label</h2>
|
||||
<h2 class="wrapper-info-text">$to_label</h2>
|
||||
|
||||
<div id="client-details"></div>
|
||||
</div>
|
||||
|
||||
<div class="company-info">
|
||||
<h2 class="wrapper-info-text">$from_label</h2>
|
||||
<div id="company-details"></div>
|
||||
<div id="company-address"></div>
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@
|
||||
:root {
|
||||
--primary-color: $primary_color;
|
||||
--secondary-color: $secondary_color;
|
||||
--line-height: 1.6;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -26,6 +27,7 @@
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 1.8fr;
|
||||
gap: 20px;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
.header-wrapper .header-text-label {
|
||||
@ -223,6 +225,10 @@
|
||||
direction: $dir;
|
||||
}
|
||||
|
||||
[data-ref="product_table-product.unit_cost-td"] { text-align: right; }
|
||||
|
||||
[data-ref="totals_table-outstanding"] { color: var(--primary-color); }
|
||||
|
||||
/** Useful snippets, uncomment to enable. **/
|
||||
|
||||
/** Hide company logo **/
|
||||
@ -252,6 +258,7 @@
|
||||
/** For more info, please check our docs: https://invoiceninja.github.io **/
|
||||
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
|
||||
</style>
|
||||
|
||||
<div id="header"></div>
|
||||
|
||||
<div id="body">
|
||||
|
@ -1,14 +1,14 @@
|
||||
<style id="style">
|
||||
:root {
|
||||
--primary-color: $primary_color;
|
||||
--secondary-color: $secondary_color;
|
||||
--primary-color: #298aab;
|
||||
--secondary-color: #7081e0;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: "$font_size";
|
||||
font-size: "7px";
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
zoom: 80%;
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
@page {
|
||||
margin: -0.22cm;
|
||||
size: $page_size $page_layout;
|
||||
size: A4 portrait;
|
||||
}
|
||||
|
||||
p {
|
||||
@ -61,7 +61,7 @@
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
max-width: 55%;
|
||||
}
|
||||
|
||||
#client-details {
|
||||
@ -149,12 +149,19 @@
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: grid;
|
||||
gap: 15px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
/* grid-template-columns: 1fr 1fr 1fr; */
|
||||
color: #fff4e9;
|
||||
max-height: 140px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.footer-company-details-address-wrapper {
|
||||
display: flex;
|
||||
gap: 25px;
|
||||
margin-right: 150px;
|
||||
}
|
||||
|
||||
#company-address,
|
||||
@ -177,38 +184,38 @@
|
||||
#table-totals {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
padding-top: .5rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-left: 3rem;
|
||||
padding-right: 3rem;
|
||||
gap: 80px;
|
||||
}
|
||||
|
||||
#table-totals .totals-table-right-side>* {
|
||||
#table-totals .totals-table-right-side > * {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
#table-totals>.totals-table-right-side>*> :nth-child(1) {
|
||||
text-align: "$dir_text_align";
|
||||
margin-top: .75rem;
|
||||
#table-totals > .totals-table-right-side > * > :nth-child(1) {
|
||||
text-align: "left";
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
#table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) {
|
||||
#table-totals
|
||||
> .totals-table-right-side
|
||||
> *
|
||||
> :not([hidden])
|
||||
~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(.75rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(.75rem * var(--tw-space-y-reverse));
|
||||
margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
#table-totals>.totals-table-right-side>*> :nth-child(2) {
|
||||
#table-totals > .totals-table-right-side > * > :nth-child(2) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#table-totals
|
||||
> *
|
||||
[data-element='product-table-balance-due-label'],
|
||||
#table-totals
|
||||
> *
|
||||
[data-element='product-table-balance-due'] {
|
||||
#table-totals > * [data-element="product-table-balance-due-label"],
|
||||
#table-totals > * [data-element="product-table-balance-due"] {
|
||||
font-weight: bold;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
@ -240,11 +247,12 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.print-content {}
|
||||
table.print-content {
|
||||
}
|
||||
|
||||
table.print-content th,
|
||||
table.print-content td {
|
||||
padding: .2rem .4rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #dee2e6;
|
||||
@ -258,7 +266,7 @@
|
||||
}
|
||||
|
||||
.no-print {
|
||||
display: none
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,6 +278,10 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
[data-ref="product_table-product.description-th"] {
|
||||
width: 23%;
|
||||
}
|
||||
|
||||
/** Useful snippets, uncomment to enable. **/
|
||||
|
||||
/** Hide company logo **/
|
||||
@ -367,8 +379,10 @@
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<div class="footer-company-details-address-wrapper">
|
||||
<div id="company-details"></div>
|
||||
<div id="company-address"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End Footer -->
|
||||
|
@ -2,6 +2,7 @@
|
||||
:root {
|
||||
--primary-color: $primary_color;
|
||||
--secondary-color: $secondary_color;
|
||||
--line-height: 1.6;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -25,6 +26,7 @@
|
||||
.header-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
@ -34,6 +36,7 @@
|
||||
.header-wrapper #company-address {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
.header-wrapper #entity-details {
|
||||
@ -62,6 +65,7 @@
|
||||
#client-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: var(--line-height);
|
||||
}
|
||||
|
||||
#product-table,
|
||||
@ -134,6 +138,7 @@
|
||||
#table-totals>.totals-table-right-side>*> :nth-child(1) {
|
||||
text-align: "$dir_text_align";
|
||||
margin-top: .75rem;
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
#table-totals>.totals-table-right-side> * > :not([hidden]) ~ :not([hidden]) {
|
||||
@ -144,6 +149,7 @@
|
||||
|
||||
#table-totals>.totals-table-right-side>*> :nth-child(2) {
|
||||
text-align: right;
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
#table-totals
|
||||
@ -180,6 +186,15 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
[data-ref="totals_table-outstanding-label"],
|
||||
[data-ref="totals_table-outstanding"] {
|
||||
background-color: #e6e6e6;
|
||||
color: black;
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
/** Useful snippets, uncomment to enable. **/
|
||||
|
||||
/** Hide company logo **/
|
||||
@ -209,6 +224,7 @@
|
||||
/** For more info, please check our docs: https://invoiceninja.github.io **/
|
||||
/** To find out selectors on your own: https://invoiceninja.github.io/docs/custom-fields/#snippets **/
|
||||
</style>
|
||||
|
||||
<div id="header"></div>
|
||||
|
||||
<div id="body">
|
||||
|
@ -2,6 +2,7 @@
|
||||
:root {
|
||||
--primary-color: $primary_color;
|
||||
--secondary-color: $secondary_color;
|
||||
--line-height: 1.6;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -24,8 +25,9 @@
|
||||
|
||||
.header-wrapper {
|
||||
margin-top: 2rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 0.5fr 2fr;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
padding: 2rem 3rem;
|
||||
}
|
||||
|
||||
@ -35,6 +37,8 @@
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#entity-details p { margin-right: 20px; }
|
||||
|
||||
.header-wrapper #entity-details {
|
||||
width: 100%;
|
||||
color: white;
|
||||
@ -249,6 +253,19 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#footer-colors {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
min-width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
#footer-colors > * {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/** Useful snippets, uncomment to enable. **/
|
||||
|
||||
/** Hide company logo **/
|
||||
@ -292,13 +309,9 @@
|
||||
|
||||
<div id="body">
|
||||
<div class="header-wrapper">
|
||||
<img
|
||||
class="company-logo"
|
||||
src="$company.logo"
|
||||
alt="$company.name logo"
|
||||
/>
|
||||
|
||||
<div></div>
|
||||
<div>
|
||||
<img class="company-logo" src="$company.logo" alt="$company.name logo">
|
||||
</div>
|
||||
|
||||
<div class="entity-details-wrapper">
|
||||
<table id="entity-details" cellspacing="0" dir="$dir"></table>
|
||||
@ -332,6 +345,17 @@
|
||||
<div id="footer">
|
||||
<p data-ref="total_table-footer">$entity_footer</p>
|
||||
|
||||
<div id="footer-colors">
|
||||
<div style="background-color: #00968B"><!-- 1 --></div>
|
||||
<div style="background-color: #1D756E"><!-- 2 --></div>
|
||||
<div style="background-color: #FCB600"><!-- 3 --></div>
|
||||
<div style="background-color: #BA932F"><!-- 4 --></div>
|
||||
<div style="background-color: #A72A4E"><!-- 5 --></div>
|
||||
<div style="background-color: #E20041"><!-- 6 --></div>
|
||||
<div style="background-color: #F8B300"><!-- 7 --></div>
|
||||
<div style="background-color: #009B8F"><!-- 8 --></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Clear up space a bit, if [product-table, tasks-table, delivery-note-table] isn't present.
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
@ -51,10 +51,11 @@
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-width: 65%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.header-invoice-number {
|
||||
|
@ -88,9 +88,11 @@
|
||||
{{ ctrans('texts.you_might_be_interested_in_following') }}:
|
||||
</p>
|
||||
|
||||
<div class="mt-4 space-x-2">
|
||||
<div class="mt-4">
|
||||
@foreach($subscription->service()->getPlans() as $_subscription)
|
||||
<button class="mt-8 mr-2">
|
||||
<a class="border mt-4 bg-white rounded py-2 px-4 hover:bg-gray-100 text-sm" target="_blank" href="{{ route('client.subscription.purchase', $_subscription->hashed_id) }}">{{ $_subscription->name }}</a>
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,40 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' =>
|
||||
ctrans('texts.credit_card')])
|
||||
|
||||
@section('gateway_head')
|
||||
<meta name="public-api-key" content="{{ $public_api_key }}">
|
||||
<meta name="translation-card-name" content="{{ ctrans('texts.cardholder_name') }}">
|
||||
<meta name="translation-expiry_date" content="{{ ctrans('texts.date') }}">
|
||||
<meta name="translation-card_number" content="{{ ctrans('texts.card_number') }}">
|
||||
<meta name="translation-cvv" content="{{ ctrans('texts.cvv') }}">
|
||||
@endsection
|
||||
|
||||
@section('gateway_content')
|
||||
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}"
|
||||
method="post" id="server-response">
|
||||
@csrf
|
||||
|
||||
<input type="hidden" id="securefieldcode" name="securefieldcode">
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->company_gateway->id }}">
|
||||
<input type="hidden" name="payment_method_id" value="1">
|
||||
</form>
|
||||
|
||||
@if (!Request::isSecure())
|
||||
<p class="alert alert-failure">{{ ctrans('texts.https_required') }}</p>
|
||||
@endif
|
||||
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
<div id="eway-secure-panel"></div>
|
||||
@endcomponent
|
||||
|
||||
@component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card', 'disabled' => true])
|
||||
{{ ctrans('texts.add_payment_method') }}
|
||||
@endcomponent
|
||||
@endsection
|
||||
|
||||
@section('gateway_footer')
|
||||
<script src="https://secure.ewaypayments.com/scripts/eWAY.min.js" data-init="false"></script>
|
||||
<script src="{{ asset('js/clients/payments/eway-credit-card.js') }}"></script>
|
||||
@endsection
|
@ -0,0 +1,292 @@
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
var labelStyles = "padding-right: 20px; float: right;";
|
||||
var publicApiKey = "{{ $public_api_key }}";
|
||||
var cardStyles = "padding: 2px; border: 1px solid #AAA; border-radius: 3px; height: 34px; width: 100%;";
|
||||
var rowStyles = "";
|
||||
var groupStyles = "";
|
||||
|
||||
var groupFieldConfig = {
|
||||
publicApiKey: publicApiKey,
|
||||
fieldDivId: "eway-secure-panel",
|
||||
fieldType: "group",
|
||||
styles: groupStyles,
|
||||
layout : {
|
||||
fonts: [
|
||||
"Lobster"
|
||||
],
|
||||
rows : [
|
||||
{
|
||||
styles: rowStyles,
|
||||
cells: [
|
||||
{
|
||||
colSpan: 12,
|
||||
styles: "margin-top: 15px;",
|
||||
label: {
|
||||
fieldColSpan: 4,
|
||||
text: "Card Name:",
|
||||
styles: "",
|
||||
},
|
||||
field: {
|
||||
fieldColSpan: 8,
|
||||
fieldType: "name",
|
||||
styles: cardStyles,
|
||||
divStyles: "padding-left: 10px;"
|
||||
}
|
||||
},
|
||||
{
|
||||
colSpan: 12,
|
||||
styles: "margin-top: 15px;",
|
||||
label: {
|
||||
fieldColSpan: 4,
|
||||
text: "Expiry:",
|
||||
styles: "",
|
||||
},
|
||||
field: {
|
||||
fieldColSpan: 8,
|
||||
fieldType: "expirytext",
|
||||
styles: cardStyles,
|
||||
divStyles: "padding-left: 10px;"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
styles: rowStyles,
|
||||
cells: [
|
||||
{
|
||||
colSpan: 12,
|
||||
styles: "margin-top: 15px;",
|
||||
label: {
|
||||
fieldColSpan: 4,
|
||||
text: "Card Number:",
|
||||
styles: "",
|
||||
},
|
||||
field: {
|
||||
fieldColSpan: 8,
|
||||
fieldType: "card",
|
||||
styles: cardStyles,
|
||||
}
|
||||
},
|
||||
{
|
||||
colSpan: 12,
|
||||
styles: "margin-top: 15px;",
|
||||
label: {
|
||||
fieldColSpan: 4,
|
||||
text: "CVV Number:",
|
||||
styles: "",
|
||||
},
|
||||
field: {
|
||||
fieldColSpan: 8,
|
||||
fieldType: "cvn",
|
||||
styles: cardStyles,
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
function securePanelCallback(event) {
|
||||
if (!event.fieldValid) {
|
||||
alert(getError(event.errors));
|
||||
} else {
|
||||
var s = document.querySelector("input[name=securefieldcode]");
|
||||
s.value = event.secureFieldCode
|
||||
console.log(s.value);
|
||||
}
|
||||
}
|
||||
|
||||
function doneCallback() {
|
||||
console.log("done call bak");
|
||||
var form = document.getElementById("server-response");
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function saveAndSubmit() {
|
||||
console.log("save and sub");
|
||||
eWAY.saveAllFields(doneCallback, 2000);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function getError(k){
|
||||
myArr = k.split(" ");
|
||||
|
||||
var str = "";
|
||||
|
||||
for(error in myArr){
|
||||
str = str.concat(map.get(myArr[error])) + '\n';
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
const map = new Map();
|
||||
map.set('V6000', 'Validation error');
|
||||
map.set('V6001', 'Invalid CustomerIP');
|
||||
map.set('V6002', 'Invalid DeviceID');
|
||||
map.set('V6003', 'Invalid Request PartnerID');
|
||||
map.set('V6004', 'Invalid Request Method');
|
||||
map.set('V6010', 'Invalid TransactionType, account not certified for eCome only MOTO or Recurring available');
|
||||
map.set('V6011', 'Invalid Payment TotalAmount');
|
||||
map.set('V6012', 'Invalid Payment InvoiceDescription');
|
||||
map.set('V6013', 'Invalid Payment InvoiceNumber');
|
||||
map.set('V6014', 'Invalid Payment InvoiceReference');
|
||||
map.set('V6015', 'Invalid Payment CurrencyCode');
|
||||
map.set('V6016', 'Payment Required');
|
||||
map.set('V6017', 'Payment CurrencyCode Required');
|
||||
map.set('V6018', 'Unknown Payment CurrencyCode');
|
||||
map.set('V6019', 'Cardholder identity authentication required');
|
||||
map.set('V6020', 'Cardholder Input Required');
|
||||
map.set('V6021', 'EWAY_CARDHOLDERNAME Required');
|
||||
map.set('V6022', 'EWAY_CARDNUMBER Required');
|
||||
map.set('V6023', 'EWAY_CARDCVN Required');
|
||||
map.set('V6024', 'Cardholder Identity Authentication One Time Password Not Active Yet');
|
||||
map.set('V6025', 'PIN Required');
|
||||
map.set('V6033', 'Invalid Expiry Date');
|
||||
map.set('V6034', 'Invalid Issue Number');
|
||||
map.set('V6035', 'Invalid Valid From Date');
|
||||
map.set('V6039', 'Invalid Network Token Status');
|
||||
map.set('V6040', 'Invalid TokenCustomerID');
|
||||
map.set('V6041', 'Customer Required');
|
||||
map.set('V6042', 'Customer FirstName Required');
|
||||
map.set('V6043', 'Customer LastName Required');
|
||||
map.set('V6044', 'Customer CountryCode Required');
|
||||
map.set('V6045', 'Customer Title Required');
|
||||
map.set('V6046', 'TokenCustomerID Required');
|
||||
map.set('V6047', 'RedirectURL Required');
|
||||
map.set('V6048', 'CheckoutURL Required when CheckoutPayment specified');
|
||||
map.set('V6049', 'nvalid Checkout URL');
|
||||
map.set('V6051', 'Invalid Customer FirstName');
|
||||
map.set('V6052', 'Invalid Customer LastName');
|
||||
map.set('V6053', 'Invalid Customer CountryCode');
|
||||
map.set('V6058', 'Invalid Customer Title');
|
||||
map.set('V6059', 'Invalid RedirectURL');
|
||||
map.set('V6060', 'Invalid TokenCustomerID');
|
||||
map.set('V6061', 'Invalid Customer Reference');
|
||||
map.set('V6062', 'Invalid Customer CompanyName');
|
||||
map.set('V6063', 'Invalid Customer JobDescription');
|
||||
map.set('V6064', 'Invalid Customer Street1');
|
||||
map.set('V6065', 'Invalid Customer Street2');
|
||||
map.set('V6066', 'Invalid Customer City');
|
||||
map.set('V6067', 'Invalid Customer State');
|
||||
map.set('V6068', 'Invalid Customer PostalCode');
|
||||
map.set('V6069', 'Invalid Customer Email');
|
||||
map.set('V6070', 'Invalid Customer Phone');
|
||||
map.set('V6071', 'Invalid Customer Mobile');
|
||||
map.set('V6072', 'Invalid Customer Comments');
|
||||
map.set('V6073', 'Invalid Customer Fax');
|
||||
map.set('V6074', 'Invalid Customer URL');
|
||||
map.set('V6075', 'Invalid ShippingAddress FirstName');
|
||||
map.set('V6076', 'Invalid ShippingAddress LastName');
|
||||
map.set('V6077', 'Invalid ShippingAddress Street1');
|
||||
map.set('V6078', 'Invalid ShippingAddress Street2');
|
||||
map.set('V6079', 'Invalid ShippingAddress City');
|
||||
map.set('V6080', 'Invalid ShippingAddress State');
|
||||
map.set('V6081', 'Invalid ShippingAddress PostalCode');
|
||||
map.set('V6082', 'Invalid ShippingAddress Email');
|
||||
map.set('V6083', 'Invalid ShippingAddress Phone');
|
||||
map.set('V6084', 'Invalid ShippingAddress Country');
|
||||
map.set('V6085', 'Invalid ShippingAddress ShippingMethod');
|
||||
map.set('V6086', 'Invalid ShippingAddress Fax');
|
||||
map.set('V6091', 'Unknown Customer CountryCode');
|
||||
map.set('V6092', 'Unknown ShippingAddress CountryCode');
|
||||
map.set('V6093', 'Insufficient Address Information');
|
||||
map.set('V6100', 'Invalid EWAY_CARDNAME');
|
||||
map.set('V6101', 'Invalid EWAY_CARDEXPIRYMONTH');
|
||||
map.set('V6102', 'Invalid EWAY_CARDEXPIRYYEAR');
|
||||
map.set('V6103', 'Invalid EWAY_CARDSTARTMONTH');
|
||||
map.set('V6104', 'Invalid EWAY_CARDSTARTYEAR');
|
||||
map.set('V6105', 'Invalid EWAY_CARDISSUENUMBER');
|
||||
map.set('V6106', 'Invalid EWAY_CARDCVN');
|
||||
map.set('V6107', 'Invalid EWAY_ACCESSCODE');
|
||||
map.set('V6108', 'Invalid CustomerHostAddress');
|
||||
map.set('V6109', 'Invalid UserAgent');
|
||||
map.set('V6110', 'Invalid EWAY_CARDNUMBER');
|
||||
map.set('V6111', 'Unauthorised API Access, Account Not PCI Certified');
|
||||
map.set('V6112', 'Redundant card details other than expiry year and month');
|
||||
map.set('V6113', 'Invalid transaction for refund');
|
||||
map.set('V6114', 'Gateway validation error');
|
||||
map.set('V6115', 'Invalid DirectRefundRequest, Transaction ID');
|
||||
map.set('V6116', 'Invalid card data on original TransactionID');
|
||||
map.set('V6117', 'Invalid CreateAccessCodeSharedRequest, FooterText');
|
||||
map.set('V6118', 'Invalid CreateAccessCodeSharedRequest, HeaderText');
|
||||
map.set('V6119', 'Invalid CreateAccessCodeSharedRequest, Language');
|
||||
map.set('V6120', 'Invalid CreateAccessCodeSharedRequest, LogoUrl');
|
||||
map.set('V6121', 'Invalid TransactionSearch, Filter Match Type');
|
||||
map.set('V6122', 'Invalid TransactionSearch, Non numeric Transaction ID');
|
||||
map.set('V6123', 'Invalid TransactionSearch,no TransactionID or AccessCode specified');
|
||||
map.set('V6124', 'Invalid Line Items. The line items have been provided however the totals do not match the TotalAmount field');
|
||||
map.set('V6125', 'Selected Payment Type not enabled');
|
||||
map.set('V6126', 'Invalid encrypted card number, decryption failed');
|
||||
map.set('V6127', 'Invalid encrypted cvn, decryption failed');
|
||||
map.set('V6128', 'Invalid Method for Payment Type');
|
||||
map.set('V6129', 'Transaction has not been authorised for Capture/Cancellation');
|
||||
map.set('V6130', 'Generic customer information error');
|
||||
map.set('V6131', 'Generic shipping information error');
|
||||
map.set('V6132', 'Transaction has already been completed or voided, operation not permitted');
|
||||
map.set('V6133', 'Checkout not available for Payment Type');
|
||||
map.set('V6134', 'Invalid Auth Transaction ID for Capture/Void');
|
||||
map.set('V6135', 'PayPal Error Processing Refund');
|
||||
map.set('V6136', 'Original transaction does not exist or state is incorrect');
|
||||
map.set('V6140', 'Merchant account is suspended');
|
||||
map.set('V6141', 'Invalid PayPal account details or API signature');
|
||||
map.set('V6142', 'Authorise not available for Bank/Branch');
|
||||
map.set('V6143', 'Invalid Public Key');
|
||||
map.set('V6144', 'Method not available with Public API Key Authentication');
|
||||
map.set('V6145', 'Credit Card not allow if Token Customer ID is provided with Public API Key Authentication');
|
||||
map.set('V6146', 'Client Side Encryption Key Missing or Invalid');
|
||||
map.set('V6147', 'Unable to Create One Time Code for Secure Field');
|
||||
map.set('V6148', 'Secure Field has Expired');
|
||||
map.set('V6149', 'Invalid Secure Field One Time Code');
|
||||
map.set('V6150', 'Invalid Refund Amount');
|
||||
map.set('V6151', 'Refund amount greater than original transaction');
|
||||
map.set('V6152', 'Original transaction already refunded for total amount');
|
||||
map.set('V6153', 'Card type not support by merchant');
|
||||
map.set('V6154', 'Insufficent Funds Available For Refund');
|
||||
map.set('V6155', 'Missing one or more fields in request');
|
||||
map.set('V6160', 'Encryption Method Not Supported');
|
||||
map.set('V6161', 'Encryption failed, missing or invalid key');
|
||||
map.set('V6165', 'Invalid Click-to-Pay (Visa Checkout) data or decryption failed');
|
||||
map.set('V6170', 'Invalid TransactionSearch, Invoice Number is not unique');
|
||||
map.set('V6171', 'Invalid TransactionSearch, Invoice Number not found');
|
||||
map.set('V6220', 'Three domain secure XID invalid');
|
||||
map.set('V6221', 'Three domain secure ECI invalid');
|
||||
map.set('V6222', 'Three domain secure AVV invalid');
|
||||
map.set('V6223', 'Three domain secure XID is required');
|
||||
map.set('V6224', 'Three Domain Secure ECI is required');
|
||||
map.set('V6225', 'Three Domain Secure AVV is required');
|
||||
map.set('V6226', 'Three Domain Secure AuthStatus is required');
|
||||
map.set('V6227', 'Three Domain Secure AuthStatus invalid');
|
||||
map.set('V6228', 'Three domain secure Version is required');
|
||||
map.set('V6230', 'Three domain secure Directory Server Txn ID invalid');
|
||||
map.set('V6231', 'Three domain secure Directory Server Txn ID is required');
|
||||
map.set('V6232', 'Three domain secure Version is invalid');
|
||||
map.set('V6501', 'Invalid Amex InstallementPlan');
|
||||
map.set('V6502', 'Invalid Number Of Installements for Amex. Valid values are from 0 to 99 inclusive');
|
||||
map.set('V6503', 'Merchant Amex ID required');
|
||||
map.set('V6504', 'Invalid Merchant Amex ID');
|
||||
map.set('V6505', 'Merchant Terminal ID required');
|
||||
map.set('V6506', 'Merchant category code required');
|
||||
map.set('V6507', 'Invalid merchant category code');
|
||||
map.set('V6508', 'Amex 3D ECI required');
|
||||
map.set('V6509', 'Invalid Amex 3D ECI');
|
||||
map.set('V6510', 'Invalid Amex 3D verification value');
|
||||
map.set('V6511', 'Invalid merchant location data');
|
||||
map.set('V6512', 'Invalid merchant street address');
|
||||
map.set('V6513', 'Invalid merchant city');
|
||||
map.set('V6514', 'Invalid merchant country');
|
||||
map.set('V6515', 'Invalid merchant phone');
|
||||
map.set('V6516', 'Invalid merchant postcode');
|
||||
map.set('V6517', 'Amex connection error');
|
||||
map.set('V6518', 'Amex EC Card Details API returned invalid data');
|
||||
map.set('V6520', 'Invalid or missing Amex Point Of Sale Data');
|
||||
map.set('V6521', 'Invalid or missing Amex transaction date time');
|
||||
map.set('V6522', 'Invalid or missing Amex Original transaction date time');
|
||||
map.set('V6530', 'Credit Card Number in non Credit Card Field');
|
||||
|
||||
|
||||
</script>
|
62
resources/views/portal/ninja2020/gateways/eway/pay.blade.php
Normal file
62
resources/views/portal/ninja2020/gateways/eway/pay.blade.php
Normal file
@ -0,0 +1,62 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' =>
|
||||
ctrans('texts.credit_card')])
|
||||
|
||||
@section('gateway_head')
|
||||
<meta name="public-api-key" content="{{ $public_api_key }}">
|
||||
<meta name="translation-card-name" content="{{ ctrans('texts.cardholder_name') }}">
|
||||
<meta name="translation-expiry_date" content="{{ ctrans('texts.date') }}">
|
||||
<meta name="translation-card_number" content="{{ ctrans('texts.card_number') }}">
|
||||
<meta name="translation-cvv" content="{{ ctrans('texts.cvv') }}">
|
||||
@endsection
|
||||
|
||||
@section('gateway_content')
|
||||
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
|
||||
@csrf
|
||||
<input type="hidden" name="gateway_response">
|
||||
<input type="hidden" name="store_card" id="store_card">
|
||||
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
|
||||
<input type="hidden" name="payment_method_id" value="1">
|
||||
<input type="hidden" name="token" id="token" value="">
|
||||
<input type="hidden" name="securefieldcode" value="">
|
||||
</form>
|
||||
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')])
|
||||
{{ ctrans('texts.credit_card') }}
|
||||
@endcomponent
|
||||
|
||||
@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)
|
||||
<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 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
|
||||
|
||||
@component('portal.ninja2020.components.general.card-element-single')
|
||||
<div id="eway-secure-panel"></div>
|
||||
@endcomponent
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.save_card')
|
||||
|
||||
@include('portal.ninja2020.gateways.includes.pay_now', ['disabled' => true])
|
||||
@endsection
|
||||
|
||||
@section('gateway_footer')
|
||||
<script src="https://secure.ewaypayments.com/scripts/eWAY.min.js" data-init="false"></script>
|
||||
<script src="{{ asset('js/clients/payments/eway-credit-card.js') }}"></script>
|
||||
@endsection
|
@ -4,7 +4,8 @@
|
||||
type="{{ $type ?? 'button' }}"
|
||||
id="{{ $id ?? 'pay-now' }}"
|
||||
@isset($data) @foreach($data as $prop => $value) data-{{ $prop }}="{{ $value }}" @endforeach @endisset
|
||||
class="button button-primary bg-primary {{ $class ?? '' }}">
|
||||
class="button button-primary bg-primary {{ $class ?? '' }}"
|
||||
{{ isset($disabled) && $disabled === true ? 'disabled' : '' }}>
|
||||
<svg class="animate-spin h-5 w-5 text-white hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<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>
|
||||
|
125
tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php
Normal file
125
tests/Browser/ClientPortal/Gateways/Eway/CreditCardTest.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?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\Eway;
|
||||
|
||||
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->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visit(new Login())
|
||||
->auth();
|
||||
});
|
||||
}
|
||||
|
||||
public function testPaymentWithNewCard()
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->click('@pay-now-dropdown')
|
||||
->clickLink('Credit Card')
|
||||
->withinFrame('iframe', function (Browser $browser) {
|
||||
$browser
|
||||
->type('EWAY_CARDNAME', 'Invoice Ninja')
|
||||
->type('EWAY_CARDNUMBER', '4111 1111 1111 1111')
|
||||
->type('EWAY_CARDEXPIRY', '04/22')
|
||||
->type('EWAY_CARDCVN', '100');
|
||||
})
|
||||
->click('#pay-now')
|
||||
->waitForText('Details of the payment', 60);
|
||||
});
|
||||
}
|
||||
|
||||
public function testPayWithNewCardAndSaveForFutureUse()
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->click('@pay-now-dropdown')
|
||||
->clickLink('Credit Card')
|
||||
->withinFrame('iframe', function (Browser $browser) {
|
||||
$browser
|
||||
->type('EWAY_CARDNAME', 'Invoice Ninja')
|
||||
->type('EWAY_CARDNUMBER', '4111 1111 1111 1111')
|
||||
->type('EWAY_CARDEXPIRY', '04/22')
|
||||
->type('EWAY_CARDCVN', '100');
|
||||
})
|
||||
->radio('#proxy_is_default', true)
|
||||
->click('#pay-now')
|
||||
->waitForText('Details of the payment', 60)
|
||||
->visitRoute('client.payment_methods.index')
|
||||
->clickLink('View')
|
||||
->assertSee('1111');
|
||||
});
|
||||
}
|
||||
|
||||
public function testPayWithSavedCreditCard()
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.invoices.index')
|
||||
->click('@pay-now')
|
||||
->click('@pay-now-dropdown')
|
||||
->clickLink('Credit Card')
|
||||
->click('.toggle-payment-with-token')
|
||||
->click('#pay-now')
|
||||
->waitForText('Details of the payment', 60);
|
||||
});
|
||||
}
|
||||
|
||||
public function testRemoveCreditCard()
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.payment_methods.index')
|
||||
->clickLink('View')
|
||||
->press('Remove Payment Method')
|
||||
->waitForText('Confirmation')
|
||||
->click('@confirm-payment-removal')
|
||||
->assertSee('Payment method has been successfully removed.');
|
||||
});
|
||||
}
|
||||
|
||||
public function testAddingCreditCardStandalone()
|
||||
{
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser
|
||||
->visitRoute('client.payment_methods.index')
|
||||
->press('Add Payment Method')
|
||||
->clickLink('Credit Card')
|
||||
->withinFrame('iframe', function (Browser $browser) {
|
||||
$browser
|
||||
->type('EWAY_CARDNAME', 'Invoice Ninja')
|
||||
->type('EWAY_CARDNUMBER', '4111 1111 1111 1111')
|
||||
->type('EWAY_CARDEXPIRY', '04/22')
|
||||
->type('EWAY_CARDCVN', '100');
|
||||
})
|
||||
->press('Add Payment Method')
|
||||
->waitForText('**** 1111');
|
||||
});
|
||||
}
|
||||
}
|
@ -156,7 +156,6 @@ trait MockAccountData
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$this->account = Account::factory()->create();
|
||||
$this->company = Company::factory()->create([
|
||||
'account_id' => $this->account->id,
|
||||
|
@ -11,8 +11,10 @@
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Factory\InvoiceInvitationFactory;
|
||||
use App\Models\CompanyToken;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
@ -30,6 +32,9 @@ class InvitationTest extends TestCase
|
||||
$this->withoutMiddleware(
|
||||
ThrottleRequests::class
|
||||
);
|
||||
|
||||
$this->withoutExceptionHandling();
|
||||
|
||||
}
|
||||
|
||||
public function testInvitationSanity()
|
||||
@ -54,9 +59,10 @@ class InvitationTest extends TestCase
|
||||
try {
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->put('/api/v1/invoices/'.$this->encodePrimaryKey($this->invoice->id), $this->invoice->toArray());
|
||||
} catch (\Exception $e) {
|
||||
} catch (ValidationException $e) {
|
||||
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
|
4
webpack.mix.js
vendored
4
webpack.mix.js
vendored
@ -89,6 +89,10 @@ mix.js("resources/js/app.js", "public/js")
|
||||
.js(
|
||||
"resources/js/clients/payments/mollie-credit-card.js",
|
||||
"public/js/clients/payments/mollie-credit-card.js"
|
||||
)
|
||||
.js(
|
||||
"resources/js/clients/payments/eway-credit-card.js",
|
||||
"public/js/clients/payments/eway-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