mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-09-21 08:51:34 +02:00
commit
89fb681a22
6
.github/workflows/phpunit.yml
vendored
6
.github/workflows/phpunit.yml
vendored
@ -1,10 +1,10 @@
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- v2
|
- v5-develop
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- v2
|
- v5-develop
|
||||||
|
|
||||||
name: phpunit
|
name: phpunit
|
||||||
jobs:
|
jobs:
|
||||||
@ -62,7 +62,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
with:
|
with:
|
||||||
ref: v2
|
ref: v5-develop
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Copy .env
|
- name: Copy .env
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v1
|
||||||
with:
|
with:
|
||||||
ref: v2
|
ref: v5-stable
|
||||||
|
|
||||||
- name: Copy .env file
|
- name: Copy .env file
|
||||||
run: |
|
run: |
|
||||||
|
@ -1 +1 @@
|
|||||||
5.0.17
|
5.0.18
|
@ -101,7 +101,7 @@ class CreateSingleAccount extends Command
|
|||||||
$this->warmCache();
|
$this->warmCache();
|
||||||
|
|
||||||
$this->createSmallAccount();
|
$this->createSmallAccount();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createSmallAccount()
|
private function createSmallAccount()
|
||||||
@ -176,8 +176,8 @@ class CreateSingleAccount extends Command
|
|||||||
|
|
||||||
$client = $company->clients->random();
|
$client = $company->clients->random();
|
||||||
|
|
||||||
$this->info('creating credit for client #'.$client->id);
|
// $this->info('creating credit for client #'.$client->id);
|
||||||
$this->createCredit($client);
|
// $this->createCredit($client); /** Prevents Stripe from running payments. */
|
||||||
|
|
||||||
$client = $company->clients->random();
|
$client = $company->clients->random();
|
||||||
|
|
||||||
@ -497,7 +497,7 @@ class CreateSingleAccount extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function createGateways($company, $user)
|
private function createGateways($company, $user)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (config('ninja.testvars.stripe') && ($this->gateway == 'all' || $this->gateway == 'stripe')) {
|
if (config('ninja.testvars.stripe') && ($this->gateway == 'all' || $this->gateway == 'stripe')) {
|
||||||
$cg = new CompanyGateway;
|
$cg = new CompanyGateway;
|
||||||
|
@ -154,10 +154,12 @@ class CompanySettings extends BaseSettings
|
|||||||
public $email_style_custom = ''; //the template itself
|
public $email_style_custom = ''; //the template itself
|
||||||
public $email_subject_invoice = '';
|
public $email_subject_invoice = '';
|
||||||
public $email_subject_quote = '';
|
public $email_subject_quote = '';
|
||||||
|
public $email_subject_credit = '';
|
||||||
public $email_subject_payment = '';
|
public $email_subject_payment = '';
|
||||||
public $email_subject_payment_partial = '';
|
public $email_subject_payment_partial = '';
|
||||||
public $email_subject_statement = '';
|
public $email_subject_statement = '';
|
||||||
public $email_template_invoice = '';
|
public $email_template_invoice = '';
|
||||||
|
public $email_template_credit = '';
|
||||||
public $email_template_quote = '';
|
public $email_template_quote = '';
|
||||||
public $email_template_payment = '';
|
public $email_template_payment = '';
|
||||||
public $email_template_payment_partial = '';
|
public $email_template_payment_partial = '';
|
||||||
@ -350,10 +352,12 @@ class CompanySettings extends BaseSettings
|
|||||||
'email_signature' => 'string',
|
'email_signature' => 'string',
|
||||||
'email_subject_invoice' => 'string',
|
'email_subject_invoice' => 'string',
|
||||||
'email_subject_quote' => 'string',
|
'email_subject_quote' => 'string',
|
||||||
|
'email_subject_credit' => 'string',
|
||||||
'email_subject_payment' => 'string',
|
'email_subject_payment' => 'string',
|
||||||
'email_subject_payment_partial' => 'string',
|
'email_subject_payment_partial' => 'string',
|
||||||
'email_template_invoice' => 'string',
|
'email_template_invoice' => 'string',
|
||||||
'email_template_quote' => 'string',
|
'email_template_quote' => 'string',
|
||||||
|
'email_template_credit' => 'string',
|
||||||
'email_template_payment' => 'string',
|
'email_template_payment' => 'string',
|
||||||
'email_template_payment_partial' => 'string',
|
'email_template_payment_partial' => 'string',
|
||||||
'email_subject_reminder1' => 'string',
|
'email_subject_reminder1' => 'string',
|
||||||
|
@ -30,6 +30,9 @@ class EmailTemplateDefaults
|
|||||||
case 'email_template_quote':
|
case 'email_template_quote':
|
||||||
return self::emailQuoteTemplate();
|
return self::emailQuoteTemplate();
|
||||||
break;
|
break;
|
||||||
|
case 'email_template_credit':
|
||||||
|
return self::emailCreditTemplate();
|
||||||
|
break;
|
||||||
case 'email_template_payment':
|
case 'email_template_payment':
|
||||||
return self::emailPaymentTemplate();
|
return self::emailPaymentTemplate();
|
||||||
break;
|
break;
|
||||||
@ -69,6 +72,9 @@ class EmailTemplateDefaults
|
|||||||
case 'email_subject_quote':
|
case 'email_subject_quote':
|
||||||
return self::emailQuoteSubject();
|
return self::emailQuoteSubject();
|
||||||
break;
|
break;
|
||||||
|
case 'email_subject_credit':
|
||||||
|
return self::emailCreditSubject();
|
||||||
|
break;
|
||||||
case 'email_subject_payment':
|
case 'email_subject_payment':
|
||||||
return self::emailPaymentSubject();
|
return self::emailPaymentSubject();
|
||||||
break;
|
break;
|
||||||
@ -109,7 +115,11 @@ class EmailTemplateDefaults
|
|||||||
public static function emailInvoiceSubject()
|
public static function emailInvoiceSubject()
|
||||||
{
|
{
|
||||||
return ctrans('texts.invoice_subject', ['number'=>'$number', 'account'=>'$company.name']);
|
return ctrans('texts.invoice_subject', ['number'=>'$number', 'account'=>'$company.name']);
|
||||||
//return Parsedown::instance()->line(self::transformText('invoice_subject'));
|
}
|
||||||
|
|
||||||
|
public static function emailCreditSubject()
|
||||||
|
{
|
||||||
|
return ctrans('texts.credit_subject', ['number'=>'$number', 'account'=>'$company.name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function emailInvoiceTemplate()
|
public static function emailInvoiceTemplate()
|
||||||
@ -122,14 +132,11 @@ class EmailTemplateDefaults
|
|||||||
$invoice_message = '<p>'.self::transformText('invoice_message').'</p><br><br><p>$view_link</p>';
|
$invoice_message = '<p>'.self::transformText('invoice_message').'</p><br><br><p>$view_link</p>';
|
||||||
|
|
||||||
return $invoice_message;
|
return $invoice_message;
|
||||||
//return $converter->convertToHtml($invoice_message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function emailQuoteSubject()
|
public static function emailQuoteSubject()
|
||||||
{
|
{
|
||||||
return ctrans('texts.quote_subject', ['number'=>'$number', 'account'=>'$company.name']);
|
return ctrans('texts.quote_subject', ['number'=>'$number', 'account'=>'$company.name']);
|
||||||
|
|
||||||
//return Parsedown::instance()->line(self::transformText('quote_subject'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function emailQuoteTemplate()
|
public static function emailQuoteTemplate()
|
||||||
@ -158,6 +165,17 @@ class EmailTemplateDefaults
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function emailCreditTemplate()
|
||||||
|
{
|
||||||
|
$converter = new CommonMarkConverter([
|
||||||
|
'html_input' => 'strip',
|
||||||
|
'allow_unsafe_links' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $converter->convertToHtml(self::transformText('credit_message'));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static function emailPaymentPartialTemplate()
|
public static function emailPaymentPartialTemplate()
|
||||||
{
|
{
|
||||||
$converter = new CommonMarkConverter([
|
$converter = new CommonMarkConverter([
|
||||||
|
@ -94,15 +94,15 @@ class InvoiceItemFactory
|
|||||||
$item->cost = $faker->randomFloat(2, -1, -1000);
|
$item->cost = $faker->randomFloat(2, -1, -1000);
|
||||||
$item->line_total = $item->quantity * $item->cost;
|
$item->line_total = $item->quantity * $item->cost;
|
||||||
$item->is_amount_discount = true;
|
$item->is_amount_discount = true;
|
||||||
$item->discount = $faker->numberBetween(1, 10);
|
$item->discount = 0;
|
||||||
$item->notes = $faker->realText(20);
|
$item->notes = $faker->realText(20);
|
||||||
$item->product_key = $faker->word();
|
$item->product_key = $faker->word();
|
||||||
$item->custom_value1 = $faker->realText(10);
|
$item->custom_value1 = $faker->realText(10);
|
||||||
$item->custom_value2 = $faker->realText(10);
|
$item->custom_value2 = $faker->realText(10);
|
||||||
$item->custom_value3 = $faker->realText(10);
|
$item->custom_value3 = $faker->realText(10);
|
||||||
$item->custom_value4 = $faker->realText(10);
|
$item->custom_value4 = $faker->realText(10);
|
||||||
$item->tax_name1 = 'GST';
|
$item->tax_name1 = '';
|
||||||
$item->tax_rate1 = 10.00;
|
$item->tax_rate1 = 0;
|
||||||
$item->type_id = "1";
|
$item->type_id = "1";
|
||||||
|
|
||||||
$data[] = $item;
|
$data[] = $item;
|
||||||
|
@ -288,5 +288,39 @@ class InvoiceSum
|
|||||||
return $this->getTotalTaxes();
|
return $this->getTotalTaxes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function purgeTaxes()
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->tax_rate1 = 0;
|
||||||
|
$this->tax_name1 = '';
|
||||||
|
|
||||||
|
$this->tax_rate2 = 0;
|
||||||
|
$this->tax_name2 = '';
|
||||||
|
|
||||||
|
$this->tax_rate3 = 0;
|
||||||
|
$this->tax_name3 = '';
|
||||||
|
|
||||||
|
$this->discount = 0;
|
||||||
|
|
||||||
|
$line_items = collect($this->invoice->line_items);
|
||||||
|
|
||||||
|
$items = $line_items->map(function ($item){
|
||||||
|
$item->tax_rate1 = 0;
|
||||||
|
$item->tax_rate2 = 0;
|
||||||
|
$item->tax_rate3 = 0;
|
||||||
|
$item->tax_name1 = '';
|
||||||
|
$item->tax_name2 = '';
|
||||||
|
$item->tax_name3 = '';
|
||||||
|
$item->discount = 0;
|
||||||
|
|
||||||
|
return $item;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->invoice->line_items = $items->toArray();
|
||||||
|
|
||||||
|
$this->build();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -302,4 +302,9 @@ class InvoiceSumInclusive
|
|||||||
return $this->getTotalTaxes();
|
return $this->getTotalTaxes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function purgeTaxes()
|
||||||
|
{
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ use App\Events\Misc\InvitationWasViewed;
|
|||||||
use App\Events\Quote\QuoteWasViewed;
|
use App\Events\Quote\QuoteWasViewed;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\InvoiceInvitation;
|
use App\Models\InvoiceInvitation;
|
||||||
|
use App\Models\RecurringInvoiceInvitation;
|
||||||
use App\Utils\Ninja;
|
use App\Utils\Ninja;
|
||||||
use App\Utils\Traits\MakesDates;
|
use App\Utils\Traits\MakesDates;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
|
@ -13,6 +13,7 @@ namespace App\Http\Requests\Invoice;
|
|||||||
|
|
||||||
use App\Http\Requests\Request;
|
use App\Http\Requests\Request;
|
||||||
use App\Http\ValidationRules\Invoice\UniqueInvoiceNumberRule;
|
use App\Http\ValidationRules\Invoice\UniqueInvoiceNumberRule;
|
||||||
|
use App\Http\ValidationRules\Project\ValidProjectForClient;
|
||||||
use App\Models\ClientContact;
|
use App\Models\ClientContact;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Utils\Traits\CleanLineItems;
|
use App\Utils\Traits\CleanLineItems;
|
||||||
@ -47,12 +48,14 @@ class StoreInvoiceRequest extends Request
|
|||||||
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
||||||
}
|
}
|
||||||
|
|
||||||
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;
|
$rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id;
|
||||||
|
|
||||||
$rules['invitations.*.client_contact_id'] = 'distinct';
|
$rules['invitations.*.client_contact_id'] = 'distinct';
|
||||||
|
|
||||||
$rules['number'] = new UniqueInvoiceNumberRule($this->all());
|
$rules['number'] = new UniqueInvoiceNumberRule($this->all());
|
||||||
|
|
||||||
|
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +71,10 @@ class StoreInvoiceRequest extends Request
|
|||||||
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
|
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('project_id', $input) && is_string($input['project_id'])) {
|
||||||
|
$input['project_id'] = $this->decodePrimaryKey($input['project_id']);
|
||||||
|
}
|
||||||
|
|
||||||
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
|
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
|
||||||
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
|
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,8 @@ class StoreProjectRequest extends Request
|
|||||||
{
|
{
|
||||||
$rules = [];
|
$rules = [];
|
||||||
|
|
||||||
$rules['name'] ='required|unique:projects,name,null,null,company_id,'.auth()->user()->companyId();
|
//$rules['name'] ='required|unique:projects,name,null,null,company_id,'.auth()->user()->companyId();
|
||||||
|
$rules['name'] = 'required';
|
||||||
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;
|
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace App\Http\Requests\RecurringInvoice;
|
namespace App\Http\Requests\RecurringInvoice;
|
||||||
|
|
||||||
use App\Http\Requests\Request;
|
use App\Http\Requests\Request;
|
||||||
|
use App\Http\ValidationRules\Recurring\UniqueRecurringInvoiceNumberRule;
|
||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Models\RecurringInvoice;
|
use App\Models\RecurringInvoice;
|
||||||
use App\Utils\Traits\CleanLineItems;
|
use App\Utils\Traits\CleanLineItems;
|
||||||
@ -52,6 +53,8 @@ class StoreRecurringInvoiceRequest extends Request
|
|||||||
|
|
||||||
$rules['frequency_id'] = 'required|integer';
|
$rules['frequency_id'] = 'required|integer';
|
||||||
|
|
||||||
|
$rules['number'] = new UniqueRecurringInvoiceNumberRule($this->all());
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,10 @@ class UpdateRecurringInvoiceRequest extends Request
|
|||||||
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
$rules['documents'] = 'file|mimes:png,ai,svg,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx|max:20000';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->input('number')) {
|
||||||
|
$rules['number'] = 'unique:recurring_invoices,number,'.$this->id.',id,company_id,'.$this->recurring_invoice->company_id;
|
||||||
|
}
|
||||||
|
|
||||||
return $rules;
|
return $rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,10 +48,13 @@ class StoreVendorRequest extends Request
|
|||||||
|
|
||||||
protected function prepareForValidation()
|
protected function prepareForValidation()
|
||||||
{
|
{
|
||||||
// $input = $this->all();
|
$input = $this->all();
|
||||||
|
|
||||||
|
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
|
||||||
// $this->replace($input);
|
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function messages()
|
public function messages()
|
||||||
|
@ -69,6 +69,10 @@ class UpdateVendorRequest extends Request
|
|||||||
{
|
{
|
||||||
$input = $this->all();
|
$input = $this->all();
|
||||||
|
|
||||||
|
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
|
||||||
|
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
|
||||||
|
}
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
55
app/Http/ValidationRules/Project/ValidProjectForClient.php
Normal file
55
app/Http/ValidationRules/Project/ValidProjectForClient.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\ValidationRules\Project;
|
||||||
|
|
||||||
|
use App\Models\Project;
|
||||||
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use Illuminate\Contracts\Validation\Rule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ValidProjectForClient.
|
||||||
|
*/
|
||||||
|
class ValidProjectForClient implements Rule
|
||||||
|
{
|
||||||
|
use MakesHash;
|
||||||
|
|
||||||
|
public $input;
|
||||||
|
|
||||||
|
public function __construct($input)
|
||||||
|
{
|
||||||
|
$this->input = $input;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param string $attribute
|
||||||
|
* @param mixed $value
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function passes($attribute, $value)
|
||||||
|
{
|
||||||
|
if(is_string($this->input['project_id']))
|
||||||
|
$this->input['project_id'] = $this->decodePrimaryKey($this->input['project_id']);
|
||||||
|
|
||||||
|
$project = Project::findOrFail($this->input['project_id']);
|
||||||
|
|
||||||
|
return $project->client_id == $this->input['client_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function message()
|
||||||
|
{
|
||||||
|
return "Project client does not match entity client";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Invoice Ninja (https://invoiceninja.com).
|
||||||
|
*
|
||||||
|
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||||
|
*
|
||||||
|
* @license https://opensource.org/licenses/AAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\ValidationRules\Recurring;
|
||||||
|
|
||||||
|
use App\Libraries\MultiDB;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\RecurringInvoice;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Contracts\Validation\Rule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class UniqueRecurringInvoiceNumberRule.
|
||||||
|
*/
|
||||||
|
class UniqueRecurringInvoiceNumberRule implements Rule
|
||||||
|
{
|
||||||
|
public $input;
|
||||||
|
|
||||||
|
public function __construct($input)
|
||||||
|
{
|
||||||
|
$this->input = $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $attribute
|
||||||
|
* @param mixed $value
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function passes($attribute, $value)
|
||||||
|
{
|
||||||
|
return $this->checkIfInvoiceNumberUnique(); //if it exists, return false!
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function message()
|
||||||
|
{
|
||||||
|
return "Recurring Invoice number {$this->input['number']} already taken";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $email
|
||||||
|
*
|
||||||
|
* //off,when_sent,when_paid
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function checkIfInvoiceNumberUnique() : bool
|
||||||
|
{
|
||||||
|
if(empty($this->input['number']))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
$invoice = RecurringInvoice::where('client_id', $this->input['client_id'])
|
||||||
|
->where('number', $this->input['number'])
|
||||||
|
->withTrashed()
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($invoice) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -318,7 +318,7 @@ class Company extends BaseModel
|
|||||||
return isset($this->settings->language_id) && $this->language() ? $this->language()->locale : config('ninja.i18n.locale');
|
return isset($this->settings->language_id) && $this->language() ? $this->language()->locale : config('ninja.i18n.locale');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLogo()
|
public function getLogo() :?string
|
||||||
{
|
{
|
||||||
return $this->settings->company_logo ?: null;
|
return $this->settings->company_logo ?: null;
|
||||||
}
|
}
|
||||||
@ -394,7 +394,6 @@ class Company extends BaseModel
|
|||||||
|
|
||||||
public function company_users()
|
public function company_users()
|
||||||
{
|
{
|
||||||
//return $this->hasMany(CompanyUser::class)->withTimestamps();
|
|
||||||
return $this->hasMany(CompanyUser::class);
|
return $this->hasMany(CompanyUser::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class RecurringInvoiceInvitation extends BaseModel
|
|||||||
*/
|
*/
|
||||||
public function contact()
|
public function contact()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(ClientContact::class)->withTrashed();
|
return $this->belongsTo(ClientContact::class, 'client_contact_id', 'id')->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,7 +33,10 @@ class RecurringInvoiceRepository extends BaseRepository
|
|||||||
|
|
||||||
$invoice_calc = new InvoiceSum($invoice, $invoice->settings);
|
$invoice_calc = new InvoiceSum($invoice, $invoice->settings);
|
||||||
|
|
||||||
$invoice->service()->applyNumber()->save();
|
$invoice->service()
|
||||||
|
->applyNumber()
|
||||||
|
->createInvitations()
|
||||||
|
->save();
|
||||||
|
|
||||||
$invoice = $invoice_calc->build()->getInvoice();
|
$invoice = $invoice_calc->build()->getInvoice();
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ class VendorRepository extends BaseRepository
|
|||||||
*/
|
*/
|
||||||
public function save(array $data, Vendor $vendor) : ?Vendor
|
public function save(array $data, Vendor $vendor) : ?Vendor
|
||||||
{
|
{
|
||||||
|
|
||||||
$vendor->fill($data);
|
$vendor->fill($data);
|
||||||
|
|
||||||
$vendor->save();
|
$vendor->save();
|
||||||
|
@ -91,7 +91,7 @@ class HandleReversal extends AbstractService
|
|||||||
$credit_calc = new InvoiceSum($credit);
|
$credit_calc = new InvoiceSum($credit);
|
||||||
$credit_calc->build();
|
$credit_calc->build();
|
||||||
|
|
||||||
$credit = $credit_calc->getCredit();
|
$credit = $credit_calc->purgeTaxes()->getCredit();
|
||||||
|
|
||||||
$credit->service()->markSent()->save();
|
$credit->service()->markSent()->save();
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class CompanyLedgerTransformer extends EntityTransformer
|
|||||||
*/
|
*/
|
||||||
public function transform(CompanyLedger $company_ledger)
|
public function transform(CompanyLedger $company_ledger)
|
||||||
{
|
{
|
||||||
$entity_name = lcfirst(class_basename($company_ledger->company_ledgerable_type)).'_id';
|
$entity_name = lcfirst(rtrim(class_basename($company_ledger->company_ledgerable_type), 's')).'_id';
|
||||||
|
|
||||||
return [
|
return [
|
||||||
$entity_name => (string) $this->encodePrimaryKey($company_ledger->company_ledgerable_id),
|
$entity_name => (string) $this->encodePrimaryKey($company_ledger->company_ledgerable_id),
|
||||||
|
@ -104,6 +104,10 @@ trait AppSetup
|
|||||||
'subject' => EmailTemplateDefaults::emailStatementSubject(),
|
'subject' => EmailTemplateDefaults::emailStatementSubject(),
|
||||||
'body' => EmailTemplateDefaults::emailStatementTemplate(),
|
'body' => EmailTemplateDefaults::emailStatementTemplate(),
|
||||||
],
|
],
|
||||||
|
'credit' => [
|
||||||
|
'subject' => EmailTemplateDefaults::emailCreditSubject(),
|
||||||
|
'body' => EmailTemplateDefaults::emailCreditTemplate(),
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
Cache::forever($name, $data);
|
Cache::forever($name, $data);
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace App\Utils\Traits;
|
namespace App\Utils\Traits;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class Inviteable.
|
* Class Inviteable.
|
||||||
*/
|
*/
|
||||||
@ -42,7 +44,9 @@ trait Inviteable
|
|||||||
|
|
||||||
public function getLink() :string
|
public function getLink() :string
|
||||||
{
|
{
|
||||||
$entity_type = strtolower(class_basename($this->entityType()));
|
//$entity_type = strtolower(class_basename($this->entityType()));
|
||||||
|
|
||||||
|
$entity_type = Str::snake(class_basename($this->entityType()));
|
||||||
|
|
||||||
//$this->with('company','contact',$this->entity_type);
|
//$this->with('company','contact',$this->entity_type);
|
||||||
//$this->with('company');
|
//$this->with('company');
|
||||||
|
@ -6,13 +6,13 @@ return [
|
|||||||
'license_url' => 'https://app.invoiceninja.com',
|
'license_url' => 'https://app.invoiceninja.com',
|
||||||
'production' => env('NINJA_PROD', false),
|
'production' => env('NINJA_PROD', false),
|
||||||
'license' => env('NINJA_LICENSE', ''),
|
'license' => env('NINJA_LICENSE', ''),
|
||||||
'version_url' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v2/VERSION.txt',
|
'version_url' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-stable/VERSION.txt',
|
||||||
'app_name' => env('APP_NAME'),
|
'app_name' => env('APP_NAME'),
|
||||||
'app_env' => env('APP_ENV', 'selfhosted'),
|
'app_env' => env('APP_ENV', 'selfhosted'),
|
||||||
'require_https' => env('REQUIRE_HTTPS', true),
|
'require_https' => env('REQUIRE_HTTPS', true),
|
||||||
'app_url' => rtrim(env('APP_URL', ''), '/').'/',
|
'app_url' => rtrim(env('APP_URL', ''), '/').'/',
|
||||||
'app_domain' => env('APP_DOMAIN', ''),
|
'app_domain' => env('APP_DOMAIN', ''),
|
||||||
'app_version' => '5.0.17',
|
'app_version' => '5.0.18',
|
||||||
'minimum_client_version' => '5.0.16',
|
'minimum_client_version' => '5.0.16',
|
||||||
'terms_version' => '1.0.1',
|
'terms_version' => '1.0.1',
|
||||||
'api_secret' => env('API_SECRET', ''),
|
'api_secret' => env('API_SECRET', ''),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"video": false,
|
"video": false,
|
||||||
"baseUrl": "http://ninja.test:8000/",
|
"baseUrl": "http://localhost:8000/",
|
||||||
"chromeWebSecurity": false
|
"chromeWebSecurity": false
|
||||||
}
|
}
|
||||||
|
48
cypress/excluded/checkout_credit_card.spec.js
vendored
Normal file
48
cypress/excluded/checkout_credit_card.spec.js
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { second } from '../fixtures/example.json';
|
||||||
|
|
||||||
|
describe('Checkout Credit Card Payments', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// cy.useGateway(second);
|
||||||
|
cy.clientLogin();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to complete payment using checkout credit card', () => {
|
||||||
|
cy.visit('/client/invoices');
|
||||||
|
|
||||||
|
cy.get('#unpaid-checkbox').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=pay-now')
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.location('pathname').should('eq', '/client/invoices/payment');
|
||||||
|
|
||||||
|
cy.get('[data-cy=payment-methods-dropdown').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=payment-method')
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.wait(8000);
|
||||||
|
|
||||||
|
cy.get('.cko-pay-now.show')
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.wait(3000);
|
||||||
|
|
||||||
|
cy.getWithinIframe('[data-checkout="card-number"]').type(
|
||||||
|
'4242424242424242'
|
||||||
|
);
|
||||||
|
cy.getWithinIframe('[data-checkout="expiry-month"]').type('12');
|
||||||
|
cy.getWithinIframe('[data-checkout="expiry-year"]').type('30');
|
||||||
|
cy.getWithinIframe('[data-checkout="cvv"]').type('100');
|
||||||
|
|
||||||
|
cy.getWithinIframe('.form-submit')
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.wait(5000);
|
||||||
|
cy.url().should('contain', '/client/payments');
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "Using fixtures to represent data",
|
"name": "Using fixtures to represent data",
|
||||||
"email": "hello@cypress.io",
|
"email": "hello@cypress.io",
|
||||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
"body": "Fixtures are a great way to mock data for responses to routes",
|
||||||
}
|
|
||||||
|
"first": "VolejRejNm",
|
||||||
|
"second": "Wpmbk5ezJn",
|
||||||
|
|
||||||
|
"url": "http://localhost:8000"
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@ describe('Credits', () => {
|
|||||||
.should('contain.text', 'Credits');
|
.should('contain.text', 'Credits');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have required table elements', () => {
|
/* it('should have required table elements', () => {
|
||||||
cy.visit('/client/credits');
|
cy.visit('/client/credits');
|
||||||
|
|
||||||
cy.get('body')
|
cy.get('body')
|
||||||
@ -33,5 +33,5 @@ describe('Credits', () => {
|
|||||||
.should(location => {
|
.should(location => {
|
||||||
expect(location.pathname).to.eq('/client/credits/VolejRejNm');
|
expect(location.pathname).to.eq('/client/credits/VolejRejNm');
|
||||||
});
|
});
|
||||||
});
|
});*/
|
||||||
});
|
});
|
||||||
|
@ -19,35 +19,6 @@ context('Payment methods', () => {
|
|||||||
.should('contain.text', 'Payment Method');
|
.should('contain.text', 'Payment Method');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add stripe credit card', () => {
|
|
||||||
cy.visit('/client/payment_methods');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('#add-payment-method')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'Add Payment Method')
|
|
||||||
.click()
|
|
||||||
|
|
||||||
cy.location().should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/payment_methods/create');
|
|
||||||
});
|
|
||||||
|
|
||||||
cy.wait(3000);
|
|
||||||
|
|
||||||
cy.get('#cardholder-name').type('Invoice Ninja');
|
|
||||||
|
|
||||||
cy.getWithinIframe('[name="cardnumber"]').type('4242424242424242');
|
|
||||||
cy.getWithinIframe('[name="exp-date"]').type('2442');
|
|
||||||
cy.getWithinIframe('[name="cvc"]').type('242');
|
|
||||||
cy.getWithinIframe('[name="postal"]').type('12345');
|
|
||||||
|
|
||||||
cy.get('#card-button').click();
|
|
||||||
|
|
||||||
cy.location().should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/payment_methods');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have per page options dropdown', () => {
|
it('should have per page options dropdown', () => {
|
||||||
cy.visit('/client/payment_methods');
|
cy.visit('/client/payment_methods');
|
||||||
|
|
||||||
|
@ -27,20 +27,4 @@ context('Payments', () => {
|
|||||||
.first()
|
.first()
|
||||||
.should('have.value', '10');
|
.should('have.value', '10');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
it('should have required table elements', () => {
|
|
||||||
cy.visit('/client/payments');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('table.payments-table > tbody > tr')
|
|
||||||
.first()
|
|
||||||
.find('a')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'View')
|
|
||||||
.click()
|
|
||||||
.location()
|
|
||||||
.should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/payments/VolejRejNm');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
@ -3,8 +3,6 @@ context('Recurring invoices', () => {
|
|||||||
cy.clientLogin();
|
cy.clientLogin();
|
||||||
});
|
});
|
||||||
|
|
||||||
// test url
|
|
||||||
|
|
||||||
it('should show recurring invoices page', () => {
|
it('should show recurring invoices page', () => {
|
||||||
cy.visit('/client/recurring_invoices');
|
cy.visit('/client/recurring_invoices');
|
||||||
|
|
||||||
@ -29,20 +27,4 @@ context('Recurring invoices', () => {
|
|||||||
.first()
|
.first()
|
||||||
.should('have.value', '10');
|
.should('have.value', '10');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have required table elements', () => {
|
|
||||||
cy.visit('/client/recurring_invoices');
|
|
||||||
|
|
||||||
cy.get('body')
|
|
||||||
.find('table.recurring-invoices-table > tbody > tr')
|
|
||||||
.first()
|
|
||||||
.find('a')
|
|
||||||
.first()
|
|
||||||
.should('contain.text', 'View')
|
|
||||||
.click()
|
|
||||||
.location()
|
|
||||||
.should(location => {
|
|
||||||
expect(location.pathname).to.eq('/client/recurring_invoices/VolejRejNm');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
47
cypress/integration/client_portal/stripe_credit_card.spec.js
vendored
Normal file
47
cypress/integration/client_portal/stripe_credit_card.spec.js
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
describe('Stripe Credit Card Payments', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.clientLogin();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to add credit card using Stripe', () => {
|
||||||
|
cy.visit('/client/payment_methods');
|
||||||
|
|
||||||
|
cy.get('[data-cy=add-payment-method]').click();
|
||||||
|
cy.get('[data-cy=add-credit-card-link]').click();
|
||||||
|
|
||||||
|
cy.get('#cardholder-name').type('Invoice Ninja');
|
||||||
|
|
||||||
|
cy.getWithinIframe('[name="cardnumber"]').type('4242424242424242');
|
||||||
|
cy.getWithinIframe('[name="exp-date"]').type('1230');
|
||||||
|
cy.getWithinIframe('[name="cvc"]').type('100');
|
||||||
|
cy.getWithinIframe('[name="postal"]').type('12345');
|
||||||
|
|
||||||
|
cy.get('#card-button').click();
|
||||||
|
|
||||||
|
cy.get('#errors').should('be.empty');
|
||||||
|
|
||||||
|
cy.location('pathname').should('eq', '/client/payment_methods');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to complete payment with added credit card', () => {
|
||||||
|
cy.visit('/client/invoices');
|
||||||
|
|
||||||
|
cy.get('#unpaid-checkbox').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=pay-now')
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.location('pathname').should('eq', '/client/invoices/payment');
|
||||||
|
|
||||||
|
cy.get('[data-cy=payment-methods-dropdown').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=payment-method')
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('#pay-now-with-token').click();
|
||||||
|
|
||||||
|
cy.url().should('contain', '/client/payments');
|
||||||
|
});
|
||||||
|
});
|
78
cypress/support/commands.js
vendored
78
cypress/support/commands.js
vendored
@ -23,15 +23,17 @@
|
|||||||
//
|
//
|
||||||
// -- This will overwrite an existing command --
|
// -- This will overwrite an existing command --
|
||||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||||
|
const axios = require('axios');
|
||||||
|
const fixture = require('../fixtures/example.json');
|
||||||
|
|
||||||
Cypress.Commands.add('clientLogin', () => {
|
Cypress.Commands.add('clientLogin', () => {
|
||||||
cy.visit('/client/login');
|
cy.visit('/client/login');
|
||||||
cy.get('#test_email')
|
cy.get('#test_email')
|
||||||
.invoke('val')
|
.invoke('val')
|
||||||
.then(emailValue => {
|
.then((emailValue) => {
|
||||||
cy.get('#test_password')
|
cy.get('#test_password')
|
||||||
.invoke('val')
|
.invoke('val')
|
||||||
.then(passwordValue => {
|
.then((passwordValue) => {
|
||||||
cy.get('#email')
|
cy.get('#email')
|
||||||
.type(emailValue)
|
.type(emailValue)
|
||||||
.should('have.value', emailValue);
|
.should('have.value', emailValue);
|
||||||
@ -45,32 +47,62 @@ Cypress.Commands.add('clientLogin', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add(
|
Cypress.Commands.add('iframeLoaded', { prevSubject: 'element' }, ($iframe) => {
|
||||||
'iframeLoaded',
|
const contentWindow = $iframe.prop('contentWindow');
|
||||||
{prevSubject: 'element'},
|
return new Promise((resolve) => {
|
||||||
($iframe) => {
|
if (contentWindow) {
|
||||||
const contentWindow = $iframe.prop('contentWindow');
|
resolve(contentWindow);
|
||||||
return new Promise(resolve => {
|
} else {
|
||||||
if (
|
$iframe.on('load', () => {
|
||||||
contentWindow
|
resolve(contentWindow);
|
||||||
) {
|
});
|
||||||
resolve(contentWindow)
|
}
|
||||||
} else {
|
|
||||||
$iframe.on('load', () => {
|
|
||||||
resolve(contentWindow)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
Cypress.Commands.add(
|
Cypress.Commands.add(
|
||||||
'getInDocument',
|
'getInDocument',
|
||||||
{prevSubject: 'Permission denied to access property "document" on cross-origin object'},
|
{
|
||||||
|
prevSubject:
|
||||||
|
'Permission denied to access property "document" on cross-origin object',
|
||||||
|
},
|
||||||
(document, selector) => Cypress.$(selector, document)
|
(document, selector) => Cypress.$(selector, document)
|
||||||
);
|
);
|
||||||
|
|
||||||
Cypress.Commands.add(
|
Cypress.Commands.add('getWithinIframe', (targetElement) =>
|
||||||
'getWithinIframe',
|
cy
|
||||||
(targetElement) => cy.get('iframe').iframeLoaded().its('document').getInDocument(targetElement)
|
.get('iframe')
|
||||||
|
.iframeLoaded()
|
||||||
|
.its('document')
|
||||||
|
.getInDocument(targetElement)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Cypress.Commands.add('useGateway', (gateway) => {
|
||||||
|
let body = {
|
||||||
|
settings: {
|
||||||
|
entity: 'App\\Models\\Client',
|
||||||
|
industry_id: '',
|
||||||
|
size_id: '',
|
||||||
|
currency_id: '1',
|
||||||
|
company_gateway_ids: gateway,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
headers: {
|
||||||
|
'X-Api-Secret': 'superdoopersecrethere',
|
||||||
|
'X-Api-Token':
|
||||||
|
'S0x8behDk8HG8PI0i8RXdpf2AVud5b993pE8vata7xmm4RgW6u3NeGC8ibWIUjZv',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
axios
|
||||||
|
.put(
|
||||||
|
`http://localhost:8000/api/v1/clients/${fixture.first}`,
|
||||||
|
body,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
.then((response) => console.log(response))
|
||||||
|
.catch((error) => console.log(error.message));
|
||||||
|
});
|
||||||
|
Binary file not shown.
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
48
public/flutter_service_worker.js
vendored
48
public/flutter_service_worker.js
vendored
@ -3,36 +3,36 @@ const MANIFEST = 'flutter-app-manifest';
|
|||||||
const TEMP = 'flutter-temp-cache';
|
const TEMP = 'flutter-temp-cache';
|
||||||
const CACHE_NAME = 'flutter-app-cache';
|
const CACHE_NAME = 'flutter-app-cache';
|
||||||
const RESOURCES = {
|
const RESOURCES = {
|
||||||
"version.json": "da8e821a4d19db954d411e3234e781d6",
|
"assets/assets/images/logo.png": "090f69e23311a4b6d851b3880ae52541",
|
||||||
"main.dart.js": "9daccb2bcb9c8f21aa76580004feddd0",
|
|
||||||
"assets/NOTICES": "23c057eefb1ee72da3e32d88380ea642",
|
|
||||||
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
|
||||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "51c686b86bc12382579d8283b7e76b6b",
|
|
||||||
"assets/fonts/MaterialIcons-Regular.otf": "132a5e63b5e510933ab4845577716106",
|
|
||||||
"assets/assets/images/google-icon.png": "0f118259ce403274f407f5e982e681c3",
|
|
||||||
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
|
||||||
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
|
|
||||||
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
|
|
||||||
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
|
|
||||||
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
|
|
||||||
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
|
|
||||||
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
|
"assets/assets/images/payment_types/unionpay.png": "7002f52004e0ab8cc0b7450b0208ccb2",
|
||||||
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
|
|
||||||
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
|
|
||||||
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
|
|
||||||
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
|
||||||
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
|
|
||||||
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
|
|
||||||
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
|
"assets/assets/images/payment_types/maestro.png": "e533b92bfb50339fdbfa79e3dfe81f08",
|
||||||
|
"assets/assets/images/payment_types/solo.png": "2030c3ccaccf5d5e87916a62f5b084d6",
|
||||||
|
"assets/assets/images/payment_types/switch.png": "4fa11c45327f5fdc20205821b2cfd9cc",
|
||||||
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
|
"assets/assets/images/payment_types/dinerscard.png": "06d85186ba858c18ab7c9caa42c92024",
|
||||||
"assets/assets/images/logo.png": "090f69e23311a4b6d851b3880ae52541",
|
"assets/assets/images/payment_types/ach.png": "7433f0aff779dc98a649b7a2daf777cf",
|
||||||
|
"assets/assets/images/payment_types/jcb.png": "07e0942d16c5592118b72e74f2f7198c",
|
||||||
|
"assets/assets/images/payment_types/discover.png": "6c0a386a00307f87db7bea366cca35f5",
|
||||||
|
"assets/assets/images/payment_types/amex.png": "c49a4247984b3732a4af50a3390aa978",
|
||||||
|
"assets/assets/images/payment_types/carteblanche.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||||
|
"assets/assets/images/payment_types/other.png": "d936e11fa3884b8c9f1bd5c914be8629",
|
||||||
|
"assets/assets/images/payment_types/mastercard.png": "6f6cdc29ee2e22e06b1ac029cb52ef71",
|
||||||
|
"assets/assets/images/payment_types/paypal.png": "8e06c094c1871376dfea1da8088c29d1",
|
||||||
|
"assets/assets/images/payment_types/laser.png": "b4e6e93dd35517ac429301119ff05868",
|
||||||
|
"assets/assets/images/payment_types/visa.png": "3ddc4a4d25c946e8ad7e6998f30fd4e3",
|
||||||
|
"assets/assets/images/google-icon.png": "0f118259ce403274f407f5e982e681c3",
|
||||||
"assets/AssetManifest.json": "ea09ed4b9b8b6c83d6896248aac7c527",
|
"assets/AssetManifest.json": "ea09ed4b9b8b6c83d6896248aac7c527",
|
||||||
|
"assets/fonts/MaterialIcons-Regular.otf": "1288c9e28052e028aba623321f7826ac",
|
||||||
|
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "51c686b86bc12382579d8283b7e76b6b",
|
||||||
|
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
||||||
|
"assets/NOTICES": "23c057eefb1ee72da3e32d88380ea642",
|
||||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||||
"/": "23224b5e03519aaa87594403d54412cf",
|
"version.json": "145e9ca4d5ac36ac8b2776b39b3856cc",
|
||||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
"main.dart.js": "019d5fb0183e30407b4467703b079e49",
|
||||||
"manifest.json": "77215c1737c7639764e64a192be2f7b8",
|
"manifest.json": "77215c1737c7639764e64a192be2f7b8",
|
||||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed"
|
"/": "23224b5e03519aaa87594403d54412cf",
|
||||||
|
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||||
|
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35"
|
||||||
};
|
};
|
||||||
|
|
||||||
// The application shell files that are downloaded before a service worker can
|
// The application shell files that are downloaded before a service worker can
|
||||||
|
246701
public/main.dart.js
vendored
246701
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"/js/app.js": "/js/app.js?id=a33a5a58bfc6e2174841",
|
"/js/app.js": "/js/app.js?id=a33a5a58bfc6e2174841",
|
||||||
"/css/app.css": "/css/app.css?id=b2e7d49a848e3cfb6370",
|
"/css/app.css": "/css/app.css?id=fc37092c9a39881e5e2e",
|
||||||
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=b0f29d5fdfa492962c22",
|
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=b0f29d5fdfa492962c22",
|
||||||
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=d7e708d66a9c769b4c6e",
|
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=d7e708d66a9c769b4c6e",
|
||||||
"/js/clients/payment_methods/authorize-ach.js": "/js/clients/payment_methods/authorize-ach.js?id=c73d32c192c36fe44123",
|
"/js/clients/payment_methods/authorize-ach.js": "/js/clients/payment_methods/authorize-ach.js?id=c73d32c192c36fe44123",
|
||||||
|
@ -1 +1 @@
|
|||||||
{"app_name":"invoiceninja_flutter","version":"5.0.17","build_number":"17"}
|
{"app_name":"invoiceninja_flutter","version":"5.0.18","build_number":"18"}
|
@ -3283,5 +3283,7 @@ return [
|
|||||||
'saved_at' => 'Saved at :time',
|
'saved_at' => 'Saved at :time',
|
||||||
'credit_payment' => 'Credit applied to Invoice :invoice_number',
|
'credit_payment' => 'Credit applied to Invoice :invoice_number',
|
||||||
|
|
||||||
|
'credit_subject' => 'New credit :number from :account',
|
||||||
|
'credit_message' => 'To view your credit for :amount, click the link below.',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@ -99,7 +99,7 @@
|
|||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
|
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
|
||||||
<input type="hidden" name="action" value="payment">
|
<input type="hidden" name="action" value="payment">
|
||||||
<button class="px-2 py-1 mr-3 text-xs uppercase button button-primary bg-primary">
|
<button class="px-2 py-1 mr-3 text-xs uppercase button button-primary bg-primary" data-cy="pay-now">
|
||||||
@lang('texts.pay_now')
|
@lang('texts.pay_now')
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -12,16 +12,16 @@
|
|||||||
<div class="relative" x-data="{ open: false }" x-on:click.away="open = false">
|
<div class="relative" x-data="{ open: false }" x-on:click.away="open = false">
|
||||||
<!-- Add payment method button -->
|
<!-- Add payment method button -->
|
||||||
@if($client->getCreditCardGateway() || $client->getBankTransferGateway())
|
@if($client->getCreditCardGateway() || $client->getBankTransferGateway())
|
||||||
<button x-on:click="open = !open" class="button button-primary bg-primary">{{ ctrans('texts.add_payment_method') }}</button>
|
<button x-on:click="open = !open" class="button button-primary bg-primary" data-cy="add-payment-method">{{ ctrans('texts.add_payment_method') }}</button>
|
||||||
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg">
|
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg">
|
||||||
<div class="py-1 rounded-md bg-white shadow-xs">
|
<div class="py-1 rounded-md bg-white shadow-xs">
|
||||||
@if($client->getCreditCardGateway())
|
@if($client->getCreditCardGateway())
|
||||||
<a href="{{ route('client.payment_methods.create', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
<a data-cy="add-credit-card-link" href="{{ route('client.payment_methods.create', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
||||||
{{ ctrans('texts.credit_card') }}
|
{{ ctrans('texts.credit_card') }}
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@if($client->getBankTransferGateway())
|
@if($client->getBankTransferGateway())
|
||||||
<a href="{{ route('client.payment_methods.create', ['method' => $client->getBankTransferMethodType()]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
<a data-cy="add-bank-account-link" href="{{ route('client.payment_methods.create', ['method' => $client->getBankTransferMethodType()]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
|
||||||
{{ ctrans('texts.bank_account') }}
|
{{ ctrans('texts.bank_account') }}
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@ -91,7 +91,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium">
|
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium" data-cy="view-payment-method">
|
||||||
<a href="{{ route('client.payment_methods.show', $payment_method->hashed_id) }}"
|
<a href="{{ route('client.payment_methods.show', $payment_method->hashed_id) }}"
|
||||||
class="text-blue-600 hover:text-indigo-900 focus:outline-none focus:underline">
|
class="text-blue-600 hover:text-indigo-900 focus:outline-none focus:underline">
|
||||||
@lang('texts.view')
|
@lang('texts.view')
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.subtotal') }}
|
||||||
|
</dt>
|
||||||
|
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ App\Utils\Number::formatMoney($total['invoice_totals'], $client) }}
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
@if($total['fee_total'] > 0)
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.gateway_fees') }}
|
||||||
|
</dt>
|
||||||
|
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ App\Utils\Number::formatMoney($total['fee_total'], $client) }}
|
||||||
|
</dd>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($total['credit_totals'] > 0)
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.credit_amount') }}
|
||||||
|
</dt>
|
||||||
|
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ App\Utils\Number::formatMoney($total['credit_totals'], $client) }}
|
||||||
|
</dd>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.amount_due') }}
|
||||||
|
</dt>
|
||||||
|
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ App\Utils\Number::formatMoney($total['amount_with_fee'], $client) }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
@ -2,96 +2,51 @@
|
|||||||
@section('meta_title', ctrans('texts.add_credit_card'))
|
@section('meta_title', ctrans('texts.add_credit_card'))
|
||||||
|
|
||||||
@push('head')
|
@push('head')
|
||||||
<meta name="stripe-publishable-key" content="{{ $gateway->getPublishableKey() }}">
|
<meta name="stripe-publishable-key" content="{{ $gateway->getPublishableKey() }}">
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
@section('body')
|
@section('body')
|
||||||
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" method="post" id="server_response">
|
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" method="post" id="server_response">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->gateway_id }}">
|
<input type="hidden" name="company_gateway_id" value="{{ $gateway->gateway_id }}">
|
||||||
<input type="hidden" name="payment_method_id" value="1">
|
<input type="hidden" name="payment_method_id" value="1">
|
||||||
<input type="hidden" name="gateway_response" id="gateway_response">
|
<input type="hidden" name="gateway_response" id="gateway_response">
|
||||||
<input type="hidden" name="is_default" id="is_default">
|
<input type="hidden" name="is_default" id="is_default">
|
||||||
</form>
|
</form>
|
||||||
<div class="container mx-auto">
|
|
||||||
<div class="grid grid-cols-6 gap-4">
|
<div class="container mx-auto">
|
||||||
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
<div class="grid grid-cols-6 gap-4">
|
||||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
||||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||||
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||||
{{ ctrans('texts.add_credit_card') }}
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
</h3>
|
{{ ctrans('texts.add_credit_card') }}
|
||||||
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
|
</h3>
|
||||||
{{ ctrans('texts.authorize_for_future_use') }}
|
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
|
||||||
</p>
|
{{ ctrans('texts.authorize_for_future_use') }}
|
||||||
</div>
|
</p>
|
||||||
<div>
|
</div>
|
||||||
<dl>
|
<div>
|
||||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 flex items-center">
|
@include('portal.ninja2020.gateways.stripe.includes.card_widget')
|
||||||
<dt class="text-sm leading-5 font-medium text-gray-500 mr-4">
|
|
||||||
{{ ctrans('texts.name') }}
|
<div class="bg-white px-4 py-5 flex justify-end">
|
||||||
</dt>
|
<button type="button" id="card-button" data-secret="{{ $intent->client_secret }}" class="button button-primary bg-primary">
|
||||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
<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">
|
||||||
<input class="input w-full" id="cardholder-name" type="text" placeholder="{{ ctrans('texts.name') }}">
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
</dd>
|
<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>
|
||||||
</div><div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
</svg>
|
||||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
<span>{{ __('texts.save') }}</span>
|
||||||
{{ ctrans('texts.credit_card') }}
|
</button>
|
||||||
</dt>
|
|
||||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
|
||||||
<div id="card-element"></div>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div class="{{ ($gateway->token_billing == 'optin' || $gateway->token_billing == 'optout') ? 'sm:grid' : 'hidden' }} bg-gray-50 px-4 py-5 sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
|
||||||
{{ ctrans('texts.token_billing_checkbox') }}
|
|
||||||
</dt>
|
|
||||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
|
||||||
<label class="mr-4">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
class="form-radio cursor-pointer"
|
|
||||||
name="token-billing-checkbox"
|
|
||||||
id="proxy_is_default"
|
|
||||||
value="true"
|
|
||||||
{{ ($gateway->token_billing == 'always' || $gateway->token_billing == 'optout') ? 'checked' : '' }} />
|
|
||||||
<span class="ml-1 cursor-pointer">{{ ctrans('texts.yes') }}</span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
class="form-radio cursor-pointer"
|
|
||||||
name="token-billing-checkbox"
|
|
||||||
id="proxy_is_default"
|
|
||||||
value="false"
|
|
||||||
{{ ($gateway->token_billing == 'off' || $gateway->token_billing == 'optin') ? 'checked' : '' }} />
|
|
||||||
<span class="ml-1 cursor-pointer">{{ ctrans('texts.no') }}</span>
|
|
||||||
</label>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white px-4 py-5 flex justify-end">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
id="card-button"
|
|
||||||
data-secret="{{ $intent->client_secret }}"
|
|
||||||
class="button button-primary bg-primary">
|
|
||||||
<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>
|
|
||||||
</svg>
|
|
||||||
<span>{{ __('texts.save') }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@push('footer')
|
@push('footer')
|
||||||
<script src="https://js.stripe.com/v3/"></script>
|
<script src="https://js.stripe.com/v3/"></script>
|
||||||
<script src="{{ asset('js/clients/payment_methods/authorize-stripe-card.js') }}"></script>
|
<script src="{{ asset('js/clients/payment_methods/authorize-stripe-card.js') }}"></script>
|
||||||
@endpush
|
@endpush
|
||||||
|
@ -16,13 +16,10 @@
|
|||||||
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
|
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
|
||||||
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
|
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
|
||||||
</form>
|
</form>
|
||||||
<form action="{{route('client.payments.credit_response')}}" method="post" id="credit-payment">
|
|
||||||
@csrf
|
|
||||||
<input type="hidden" name="payment_hash" value="{{$payment_hash}}">
|
|
||||||
</form>
|
|
||||||
<div class="container mx-auto">
|
<div class="container mx-auto">
|
||||||
<div class="grid grid-cols-6 gap-4">
|
<div class="grid grid-cols-6 gap-4">
|
||||||
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
||||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||||
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||||
@ -35,125 +32,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<dl>
|
<dl>
|
||||||
|
@include('portal.ninja2020.gateways.includes.payment_details')
|
||||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
|
||||||
{{ ctrans('texts.subtotal') }}
|
|
||||||
</dt>
|
|
||||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
|
||||||
{{ App\Utils\Number::formatMoney($total['invoice_totals'], $client) }}
|
|
||||||
</dd>
|
|
||||||
@if($total['fee_total'] > 0)
|
|
||||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
|
||||||
{{ ctrans('texts.gateway_fees') }}
|
|
||||||
</dt>
|
|
||||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
|
||||||
{{ App\Utils\Number::formatMoney($total['fee_total'], $client) }}
|
|
||||||
</dd>
|
|
||||||
@endif
|
|
||||||
@if($total['credit_totals'] > 0)
|
|
||||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
|
||||||
{{ ctrans('texts.credit_amount') }}
|
|
||||||
</dt>
|
|
||||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
|
||||||
{{ App\Utils\Number::formatMoney($total['credit_totals'], $client) }}
|
|
||||||
</dd>
|
|
||||||
@endif
|
|
||||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
|
||||||
{{ ctrans('texts.amount_due') }}
|
|
||||||
</dt>
|
|
||||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
|
||||||
{{ App\Utils\Number::formatMoney($total['amount_with_fee'], $client) }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
@if((int)$total['amount_with_fee'] == 0)
|
@if((int)$total['amount_with_fee'] == 0)
|
||||||
<!-- finalize with credits only -->
|
@include('portal.ninja2020.gateways.stripe.includes.pay_with_credit')
|
||||||
<div class="bg-white px-4 py-5 flex justify-end">
|
|
||||||
<button form="credit-payment" class="button button-primary bg-primary inline-flex items-center">Pay with credit</button>
|
|
||||||
</div>
|
|
||||||
@elseif($token)
|
@elseif($token)
|
||||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 flex items-center">
|
@include('portal.ninja2020.gateways.stripe.includes.pay_with_token')
|
||||||
<dt class="text-sm leading-5 font-medium text-gray-500 mr-4">
|
|
||||||
{{ ctrans('texts.credit_card') }}
|
|
||||||
</dt>
|
|
||||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
|
||||||
{{ strtoupper($token->meta->brand) }} - **** {{ $token->meta->last4 }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white px-4 py-5 flex justify-end">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-secret="{{ $intent->client_secret }}"
|
|
||||||
data-token="{{ $token->token }}"
|
|
||||||
id="pay-now-with-token"
|
|
||||||
class="button button-primary bg-primary inline-flex items-center">
|
|
||||||
<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>
|
|
||||||
</svg>
|
|
||||||
<span>{{ __('texts.save') }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
@else
|
@else
|
||||||
<div
|
@include('portal.ninja2020.gateways.stripe.includes.card_widget')
|
||||||
class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 flex items-center">
|
|
||||||
<dt class="text-sm leading-5 font-medium text-gray-500 mr-4">
|
|
||||||
{{ ctrans('texts.name') }}
|
|
||||||
</dt>
|
|
||||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
|
||||||
<input class="input w-full" id="cardholder-name" type="text"
|
|
||||||
placeholder="{{ ctrans('texts.name') }}">
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
|
||||||
{{ ctrans('texts.credit_card') }}
|
|
||||||
</dt>
|
|
||||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
|
||||||
<div id="card-element"></div>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="{{ ($gateway->company_gateway->token_billing == 'optin' || $gateway->company_gateway->token_billing == 'optout') ? 'sm:grid' : 'hidden' }} bg-gray-50 px-4 py-5 sm:grid-cols-3 sm:gap-4 sm:px-6">
|
|
||||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
|
||||||
{{ ctrans('texts.token_billing_checkbox') }}
|
|
||||||
</dt>
|
|
||||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
|
||||||
<label class="mr-4">
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
class="form-radio cursor-pointer"
|
|
||||||
name="token-billing-checkbox"
|
|
||||||
id="proxy_is_default"
|
|
||||||
value="true"
|
|
||||||
{{ ($gateway->company_gateway->token_billing == 'always' || $gateway->company_gateway->token_billing == 'optout') ? 'checked' : '' }} />
|
|
||||||
<span class="ml-1 cursor-pointer">{{ ctrans('texts.yes') }}</span>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
class="form-radio cursor-pointer"
|
|
||||||
name="token-billing-checkbox"
|
|
||||||
id="proxy_is_default"
|
|
||||||
value="false"
|
|
||||||
{{ ($gateway->company_gateway->token_billing == 'off' || $gateway->company_gateway->token_billing == 'optin') ? 'checked' : '' }} />
|
|
||||||
<span class="ml-1 cursor-pointer">{{ ctrans('texts.no') }}</span>
|
|
||||||
</label>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white px-4 py-5 flex justify-end">
|
<div class="bg-white px-4 py-5 flex justify-end">
|
||||||
<button
|
<button type="button" id="pay-now" data-secret="{{ $intent->client_secret }}" class="button button-primary bg-primary">
|
||||||
type="button"
|
|
||||||
id="pay-now"
|
|
||||||
data-secret="{{ $intent->client_secret }}"
|
|
||||||
class="button button-primary bg-primary">
|
|
||||||
<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">
|
<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>
|
<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>
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span>{{ __('texts.save') }}</span>
|
<span>{{ __('texts.pay_now') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
@unless(isset($show_name) && $show_name == false)
|
||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 flex items-center">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500 mr-4">
|
||||||
|
{{ ctrans('texts.name') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
<input class="input w-full" id="cardholder-name" type="text" placeholder="{{ ctrans('texts.name') }}">
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
@endunless
|
||||||
|
|
||||||
|
@unless(isset($show_card_element) && $show_card_element == false)
|
||||||
|
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.credit_card') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
<div id="card-element"></div>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
@endunless
|
||||||
|
|
||||||
|
@unless(isset($show_save) && $show_save == false)
|
||||||
|
<div class="{{ ($gateway->token_billing == 'optin' || $gateway->token_billing == 'optout') ? 'sm:grid' : 'hidden' }} bg-gray-50 px-4 py-5 sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||||
|
{{ ctrans('texts.token_billing_checkbox') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
<label class="mr-4">
|
||||||
|
<input type="radio" class="form-radio cursor-pointer" name="token-billing-checkbox" id="proxy_is_default" value="true" {{ ($gateway->token_billing == 'always' || $gateway->token_billing == 'optout') ? 'checked' : '' }} />
|
||||||
|
<span class="ml-1 cursor-pointer">{{ ctrans('texts.yes') }}</span>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" class="form-radio cursor-pointer" name="token-billing-checkbox" id="proxy_is_default" value="false" {{ ($gateway->token_billing == 'off' || $gateway->token_billing == 'optin') ? 'checked' : '' }} />
|
||||||
|
<span class="ml-1 cursor-pointer">{{ ctrans('texts.no') }}</span>
|
||||||
|
</label>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
@endunless
|
@ -0,0 +1,8 @@
|
|||||||
|
<form action="{{ route('client.payments.credit_response') }}" method="post" id="credit-payment">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="bg-white px-4 py-5 flex justify-end">
|
||||||
|
<button form="credit-payment" class="button button-primary bg-primary inline-flex items-center">Pay with credit</button>
|
||||||
|
</div>
|
@ -0,0 +1,17 @@
|
|||||||
|
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 flex items-center">
|
||||||
|
<dt class="text-sm leading-5 font-medium text-gray-500 mr-4">
|
||||||
|
{{ ctrans('texts.credit_card') }}
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ strtoupper($token->meta->brand) }} - **** {{ $token->meta->last4 }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white px-4 py-5 flex justify-end">
|
||||||
|
<button type="button" data-secret="{{ $intent->client_secret }}" data-token="{{ $token->token }}" id="pay-now-with-token" class="button button-primary bg-primary inline-flex items-center">
|
||||||
|
<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>
|
||||||
|
</svg>
|
||||||
|
<span>{{ __('texts.pay_now') }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
@ -21,7 +21,7 @@
|
|||||||
<div class="flex justify-end mb-2">
|
<div class="flex justify-end mb-2">
|
||||||
<!-- Pay now button -->
|
<!-- Pay now button -->
|
||||||
@if(count($payment_methods) > 0)
|
@if(count($payment_methods) > 0)
|
||||||
<div x-data="{ open: false }" @keydown.window.escape="open = false" @click.away="open = false" class="relative inline-block text-left">
|
<div x-data="{ open: false }" @keydown.window.escape="open = false" @click.away="open = false" class="relative inline-block text-left" data-cy="payment-methods-dropdown">
|
||||||
<div>
|
<div>
|
||||||
<div class="rounded-md shadow-sm">
|
<div class="rounded-md shadow-sm">
|
||||||
<button @click="open = !open" type="button" class="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800">
|
<button @click="open = !open" type="button" class="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800">
|
||||||
@ -36,7 +36,7 @@
|
|||||||
<div class="bg-white rounded-md shadow-xs">
|
<div class="bg-white rounded-md shadow-xs">
|
||||||
<div class="py-1">
|
<div class="py-1">
|
||||||
@foreach($payment_methods as $payment_method)
|
@foreach($payment_methods as $payment_method)
|
||||||
<a href="#" @click="{ open = false }" data-company-gateway-id="{{ $payment_method['company_gateway_id'] }}" data-gateway-type-id="{{ $payment_method['gateway_type_id'] }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">
|
<a href="#" @click="{ open = false }" data-company-gateway-id="{{ $payment_method['company_gateway_id'] }}" data-gateway-type-id="{{ $payment_method['gateway_type_id'] }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" data-cy="payment-method">
|
||||||
{{ $payment_method['label'] }}
|
{{ $payment_method['label'] }}
|
||||||
</a>
|
</a>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<form action="{{ route('client.payment_methods.destroy', [$payment_method->hashed_id, 'method' => $payment_method->gateway_type->id]) }}" method="post">
|
<form action="{{ route('client.payment_methods.destroy', [$payment_method->hashed_id, 'method' => $payment_method->gateway_type->id]) }}" method="post">
|
||||||
@csrf
|
@csrf
|
||||||
@method('DELETE')
|
@method('DELETE')
|
||||||
<button type="submit" class="button button-danger button-block">
|
<button type="submit" class="button button-danger button-block" data-cy="confirm-payment-removal">
|
||||||
{{ ctrans('texts.remove') }}
|
{{ ctrans('texts.remove') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -98,7 +98,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
||||||
<div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }">
|
<div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }">
|
||||||
<button class="button button-danger" translate @click="open = true">
|
<button class="button button-danger" @click="open = true" id="open-delete-popup">
|
||||||
{{ ctrans('texts.remove_payment_method') }}
|
{{ ctrans('texts.remove_payment_method') }}
|
||||||
</button>
|
</button>
|
||||||
@include('portal.ninja2020.payment_methods.includes.modals.removal')
|
@include('portal.ninja2020.payment_methods.includes.modals.removal')
|
||||||
|
@ -31,7 +31,7 @@ Route::group(['middleware' => ['auth:contact', 'locale'], 'prefix' => 'client',
|
|||||||
Route::get('invoices/{invoice_invitation}', 'ClientPortal\InvoiceController@show')->name('invoice.show_invitation');
|
Route::get('invoices/{invoice_invitation}', 'ClientPortal\InvoiceController@show')->name('invoice.show_invitation');
|
||||||
|
|
||||||
Route::get('recurring_invoices', 'ClientPortal\RecurringInvoiceController@index')->name('recurring_invoices.index')->middleware('portal_enabled');
|
Route::get('recurring_invoices', 'ClientPortal\RecurringInvoiceController@index')->name('recurring_invoices.index')->middleware('portal_enabled');
|
||||||
Route::get('recurring_invoices/{recurring_invoice}', 'ClientPortal\RecurringInvoiceController@show')->name('recurring_invoices.show');
|
Route::get('recurring_invoices/{recurring_invoice}', 'ClientPortal\RecurringInvoiceController@show')->name('recurring_invoice.show');
|
||||||
Route::get('recurring_invoices/{recurring_invoice}/request_cancellation', 'ClientPortal\RecurringInvoiceController@requestCancellation')->name('recurring_invoices.request_cancellation');
|
Route::get('recurring_invoices/{recurring_invoice}/request_cancellation', 'ClientPortal\RecurringInvoiceController@requestCancellation')->name('recurring_invoices.request_cancellation');
|
||||||
|
|
||||||
Route::post('payments/process', 'ClientPortal\PaymentController@process')->name('payments.process');
|
Route::post('payments/process', 'ClientPortal\PaymentController@process')->name('payments.process');
|
||||||
|
@ -99,6 +99,8 @@ class ShopInvoiceTest extends TestCase
|
|||||||
$this->company->enable_shop_api = true;
|
$this->company->enable_shop_api = true;
|
||||||
$this->company->save();
|
$this->company->save();
|
||||||
|
|
||||||
|
Product::truncate();
|
||||||
|
|
||||||
$product = Product::factory()->create([
|
$product = Product::factory()->create([
|
||||||
'user_id' => $this->user->id,
|
'user_id' => $this->user->id,
|
||||||
'company_id' => $this->company->id,
|
'company_id' => $this->company->id,
|
||||||
|
@ -333,6 +333,14 @@ trait MockAccountData
|
|||||||
$this->credit->amount = 10;
|
$this->credit->amount = 10;
|
||||||
$this->credit->balance = 10;
|
$this->credit->balance = 10;
|
||||||
|
|
||||||
|
$this->credit->tax_name1 = '';
|
||||||
|
$this->credit->tax_name2 = '';
|
||||||
|
$this->credit->tax_name3 = '';
|
||||||
|
|
||||||
|
$this->credit->tax_rate1 = 0;
|
||||||
|
$this->credit->tax_rate2 = 0;
|
||||||
|
$this->credit->tax_rate3 = 0;
|
||||||
|
|
||||||
$this->credit->uses_inclusive_taxes = false;
|
$this->credit->uses_inclusive_taxes = false;
|
||||||
|
|
||||||
$this->credit->save();
|
$this->credit->save();
|
||||||
|
Loading…
Reference in New Issue
Block a user