mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-13 14:42:42 +01:00
Merge branch 'v5-develop' into v5-stable
This commit is contained in:
commit
240eac5523
74
CHANGELOG.md
74
CHANGELOG.md
@ -1,74 +0,0 @@
|
||||
# Release notes
|
||||
|
||||
## [Unreleased (daily channel)](https://github.com/invoiceninja/invoiceninja/tree/v5-develop)
|
||||
## Added:
|
||||
- Client portal: Show message when trying to approve non-approvable quotes
|
||||
- Client portal: Remove "Approve" button from single quote page if quote is non-approvable
|
||||
- Client portal: Hide "Pay now" buttons if no gateways are configured
|
||||
- Client portal: "Download" and "Open in new tab" buttons on documents show page
|
||||
- Client portal: Make "Invoice Ninja" link clickable in footer
|
||||
|
||||
## Fixed:
|
||||
- Client portal: Showing message instead of blank page when trying to download zero quotes
|
||||
- Client portal: Fixed bug with payment gateways after checking for required fields
|
||||
|
||||
## [v5.2.0-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.2.0-release)
|
||||
## Added:
|
||||
- Timezone Offset: Schedule emails based on timezone and time offsets.
|
||||
- Force client country to system country if none is set.
|
||||
- GMail Oauth via web
|
||||
|
||||
## Fixed:
|
||||
- Add Cache-control: no-cache to prevent overaggressive caching of assets
|
||||
- Improved labelling in the settings (client portal)
|
||||
- Client portal: Multiple accounts access improvements (#5703)
|
||||
- Client portal: "Credits" updates (#5734)
|
||||
- Client portal: Make sidebar white color, in order to make logo displaying more simple. (#5753)
|
||||
- Inject small delay into emails to allow all resources to be produced (ie PDFs) prior to sending
|
||||
- Fixes for endless reminders not firing
|
||||
|
||||
## [v5.1.56-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.56-release)
|
||||
## Fixed:
|
||||
- Fix User created/updated/deleted Actvity display format
|
||||
- Fix for Stripe autobill / token regression
|
||||
|
||||
## Added:
|
||||
- Invoice / Quote / Credit created notifications
|
||||
- Logout route - deletes all auth tokens
|
||||
|
||||
## [v5.1.54-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.54-release)
|
||||
## Fixed:
|
||||
- Fixes for e-mails, encoding & parsing invalid HTML
|
||||
|
||||
## [v5.1.50-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.50-release)
|
||||
## Fixed:
|
||||
- Refactor of e-mail templates
|
||||
- Client portal: Invoices & recurring invoices are now sorted by date (by default)
|
||||
|
||||
## Added:
|
||||
- Public notes of entities will now show in #footer section of designs (previously totals table).
|
||||
|
||||
## [v5.1.47-release](https://github.com/invoiceninja/invoiceninja/releases/tag/v5.1.47-release)
|
||||
|
||||
### Added:
|
||||
- Subscriptions are now going to show the frequency in the table (#5412)
|
||||
- Subscriptions: During upgrade webhook request message will be shown for easier debugging (#5411)
|
||||
- PDF: Custom fields now will be shared across invoices, quotes & credits (#5410)
|
||||
- Client portal: Invoices are now sorted in the descending order (#5408)
|
||||
- Payments: ACH notification after the initial bank account connecting process (#5405)
|
||||
|
||||
### Fixed:
|
||||
- Fixes for counters where patterns without {$counter} could causes endless recursion.
|
||||
- Fixes for surcharge tax displayed amount on PDF.
|
||||
- Fixes for custom designs not rendering the custom template
|
||||
- Fixes for missing bulk actions on Subscriptions
|
||||
- Fixes CSS padding on the show page for recurring invoices (#5412)
|
||||
- Fixes for rendering invalid HTML & parsing invalid XML (#5395)
|
||||
|
||||
### Removed:
|
||||
- Removed one-time payments table (#5412)
|
||||
|
||||
## v5.1.43
|
||||
|
||||
### Fixed:
|
||||
- Whitelabel regression.
|
@ -1 +1 @@
|
||||
5.2.17
|
||||
5.2.18
|
@ -742,6 +742,51 @@ class CreateSingleAccount extends Command
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->save();
|
||||
}
|
||||
|
||||
|
||||
if (config('ninja.testvars.paytrace.decrypted') && ($this->gateway == 'all' || $this->gateway == 'paytrace')) {
|
||||
$cg = new CompanyGateway;
|
||||
$cg->company_id = $company->id;
|
||||
$cg->user_id = $user->id;
|
||||
$cg->gateway_key = 'bbd736b3254b0aabed6ad7fda1298c88';
|
||||
$cg->require_cvv = true;
|
||||
$cg->require_billing_address = true;
|
||||
$cg->require_shipping_address = true;
|
||||
$cg->update_details = true;
|
||||
$cg->config = encrypt(config('ninja.testvars.paytrace.decrypted'));
|
||||
|
||||
$cg->save();
|
||||
|
||||
|
||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||
|
||||
$fees_and_limits = new stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->save();
|
||||
}
|
||||
|
||||
if (config('ninja.testvars.mollie') && ($this->gateway == 'all' || $this->gateway == 'mollie')) {
|
||||
$cg = new CompanyGateway;
|
||||
$cg->company_id = $company->id;
|
||||
$cg->user_id = $user->id;
|
||||
$cg->gateway_key = '1bd651fb213ca0c9d66ae3c336dc77e8';
|
||||
$cg->require_cvv = true;
|
||||
$cg->require_billing_address = true;
|
||||
$cg->require_shipping_address = true;
|
||||
$cg->update_details = true;
|
||||
$cg->config = encrypt(config('ninja.testvars.mollie'));
|
||||
$cg->save();
|
||||
|
||||
$gateway_types = $cg->driver(new Client)->gatewayTypes();
|
||||
|
||||
$fees_and_limits = new stdClass;
|
||||
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
|
||||
|
||||
$cg->fees_and_limits = $fees_and_limits;
|
||||
$cg->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function createRecurringInvoice($client)
|
||||
|
@ -69,7 +69,7 @@ class Kernel extends ConsoleKernel
|
||||
/* Run hosted specific jobs */
|
||||
if (Ninja::isHosted()) {
|
||||
|
||||
$schedule->job(new AdjustEmailQuota)->daily()->withoutOverlapping();
|
||||
$schedule->job(new AdjustEmailQuota)->dailyAt('23:00')->withoutOverlapping();
|
||||
$schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
|
||||
$schedule->command('ninja:check-data --database=db-ninja-02')->daily()->withoutOverlapping();
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
@ -10,6 +9,8 @@
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
use App\Utils\Ninja;
|
||||
|
||||
/**
|
||||
* Simple helper function that will log into "invoiceninja.log" file
|
||||
* only when extended logging is enabled.
|
||||
@ -32,7 +33,16 @@ function nlog($output, $context = []): void
|
||||
$trace = debug_backtrace();
|
||||
//nlog( debug_backtrace()[1]['function']);
|
||||
// \Illuminate\Support\Facades\Log::channel('invoiceninja')->info(print_r($trace[1]['class'],1), []);
|
||||
\Illuminate\Support\Facades\Log::channel('invoiceninja')->info($output, $context);
|
||||
if(Ninja::isHosted()) {
|
||||
try{
|
||||
info($output);
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
\Illuminate\Support\Facades\Log::channel('invoiceninja')->info($output, $context);
|
||||
|
||||
}
|
||||
|
||||
|
@ -730,11 +730,11 @@ class BaseController extends Controller
|
||||
|
||||
$data = [];
|
||||
|
||||
if (Ninja::isSelfHost()) {
|
||||
$data['report_errors'] = $account->report_errors;
|
||||
} else {
|
||||
$data['report_errors'] = true;
|
||||
}
|
||||
//pass report errors bool to front end
|
||||
$data['report_errors'] = Ninja::isSelfHost() ? $account->report_errors : true;
|
||||
|
||||
//pass referral code to front end
|
||||
$data['rc'] = request()->has('rc') ? request()->input('rc') : '';
|
||||
|
||||
$this->buildCache();
|
||||
|
||||
|
@ -118,16 +118,22 @@ class PaymentMethodController extends Controller
|
||||
*/
|
||||
public function destroy(ClientGatewayToken $payment_method)
|
||||
{
|
||||
// $gateway = $this->getClientGateway();
|
||||
|
||||
$payment_method->gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod(request()->query('method'))
|
||||
->detach($payment_method);
|
||||
if($payment_method->gateway()->exists()){
|
||||
|
||||
$payment_method->gateway
|
||||
->driver(auth()->user()->client)
|
||||
->setPaymentMethod(request()->query('method'))
|
||||
->detach($payment_method);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
event(new MethodDeleted($payment_method, auth('contact')->user()->company, Ninja::eventVars(auth('contact')->user()->id)));
|
||||
|
||||
$payment_method->delete();
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
||||
nlog($e->getMessage());
|
||||
|
27
app/Http/Controllers/Gateways/Mollie3dsController.php
Normal file
27
app/Http/Controllers/Gateways/Mollie3dsController.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?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\Http\Controllers\Gateways;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Gateways\Mollie\Mollie3dsRequest;
|
||||
use App\Models\PaymentHash;
|
||||
|
||||
class Mollie3dsController extends Controller
|
||||
{
|
||||
public function index(Mollie3dsRequest $request)
|
||||
{
|
||||
return $request->getCompanyGateway()
|
||||
->driver($request->getClient())
|
||||
->process3dsConfirmation($request);
|
||||
}
|
||||
}
|
@ -13,26 +13,14 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Payments\PaymentWebhookRequest;
|
||||
use App\Libraries\MultiDB;
|
||||
use Auth;
|
||||
|
||||
class PaymentWebhookController extends Controller
|
||||
{
|
||||
public function __invoke(PaymentWebhookRequest $request, string $company_key, string $company_gateway_id)
|
||||
public function __invoke(PaymentWebhookRequest $request)
|
||||
{
|
||||
|
||||
$payment = $request->getPayment();
|
||||
|
||||
if(!$payment)
|
||||
return response()->json(['message' => 'Payment record not found.'], 400);
|
||||
|
||||
$client = is_null($payment) ? $request->getClient() : $payment->client;
|
||||
|
||||
if(!$client)
|
||||
return response()->json(['message' => 'Client record not found.'], 400);
|
||||
|
||||
return $request->getCompanyGateway()
|
||||
->driver($client)
|
||||
->processWebhookRequest($request, $payment);
|
||||
return $request
|
||||
->getCompanyGateway()
|
||||
->driver()
|
||||
->processWebhookRequest($request);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\DataMapper\Analytics\EmailBounce;
|
||||
use App\DataMapper\Analytics\EmailSpam;
|
||||
use App\DataMapper\Analytics\Mail\EmailSpam;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\CreditInvitation;
|
||||
|
@ -136,6 +136,7 @@ class PreviewController extends BaseController
|
||||
'products' => request()->design['design']['product'],
|
||||
]),
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
'process_markdown' => $entity_obj->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$design = new Design(request()->design['name']);
|
||||
@ -251,6 +252,7 @@ class PreviewController extends BaseController
|
||||
'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'),
|
||||
'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'),
|
||||
],
|
||||
'process_markdown' => $entity_obj->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
|
||||
@ -362,6 +364,7 @@ class PreviewController extends BaseController
|
||||
'products' => request()->design['design']['product'],
|
||||
]),
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
'process_markdown' => $invoice->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMaker($state);
|
||||
|
@ -16,6 +16,7 @@ use App\Http\Requests\Setup\CheckDatabaseRequest;
|
||||
use App\Http\Requests\Setup\CheckMailRequest;
|
||||
use App\Http\Requests\Setup\StoreSetupRequest;
|
||||
use App\Jobs\Account\CreateAccount;
|
||||
use App\Jobs\Util\SchedulerCheck;
|
||||
use App\Jobs\Util\VersionCheck;
|
||||
use App\Models\Account;
|
||||
use App\Utils\CurlUtils;
|
||||
@ -279,10 +280,7 @@ class SetupController extends Controller
|
||||
|
||||
public function update()
|
||||
{
|
||||
// if(Ninja::isHosted())
|
||||
// return redirect('/');
|
||||
|
||||
// if( Ninja::isNinja() || !request()->has('secret') || (request()->input('secret') != config('ninja.update_secret')) )
|
||||
|
||||
if(!request()->has('secret') || (request()->input('secret') != config('ninja.update_secret')) )
|
||||
return redirect('/');
|
||||
|
||||
@ -311,6 +309,8 @@ class SetupController extends Controller
|
||||
|
||||
$this->buildCache(true);
|
||||
|
||||
SchedulerCheck::dispatchNow();
|
||||
|
||||
return redirect('/');
|
||||
|
||||
}
|
||||
|
@ -354,6 +354,8 @@ class SubscriptionController extends BaseController
|
||||
|
||||
event(new SubscriptionWasUpdated($subscription, $subscription->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
nlog($subscription->id);
|
||||
|
||||
return $this->itemResponse($subscription);
|
||||
}
|
||||
|
||||
|
@ -71,11 +71,16 @@ class ContactKeyLogin
|
||||
}
|
||||
} elseif ($request->segment(2) && $request->segment(2) == 'key_login' && $request->segment(3)) {
|
||||
if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) {
|
||||
|
||||
if(empty($client_contact->email))
|
||||
if(empty($client_contact->email)) {
|
||||
$client_contact->email = Str::random(6) . "@example.com"; $client_contact->save();
|
||||
}
|
||||
|
||||
auth()->guard('contact')->login($client_contact, true);
|
||||
|
||||
if ($request->query('next')) {
|
||||
return redirect($request->query('next'));
|
||||
}
|
||||
|
||||
return redirect()->to('client/dashboard');
|
||||
}
|
||||
} elseif ($request->has('client_hash') && config('ninja.db.multi_db_enabled')) {
|
||||
@ -106,7 +111,6 @@ class ContactKeyLogin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ class StoreClientRequest extends Request
|
||||
/* Ensure we have a client name, and that all emails are unique*/
|
||||
//$rules['name'] = 'required|min:1';
|
||||
$rules['settings'] = new ValidClientGroupSettingsRule();
|
||||
$rules['contacts'] = 'array';
|
||||
$rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email';
|
||||
$rules['contacts.*.password'] = [
|
||||
'nullable',
|
||||
|
@ -62,6 +62,7 @@ class UpdateClientRequest extends Request
|
||||
$rules['number'] = Rule::unique('clients')->where('company_id', auth()->user()->company()->id)->ignore($this->client->id);
|
||||
|
||||
$rules['settings'] = new ValidClientGroupSettingsRule();
|
||||
$rules['contacts'] = 'array';
|
||||
$rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email';
|
||||
$rules['contacts.*.password'] = [
|
||||
'nullable',
|
||||
|
@ -37,6 +37,11 @@ class PaymentResponseRequest extends FormRequest
|
||||
return PaymentHash::whereRaw('BINARY `hash`= ?', [$input['payment_hash']])->first();
|
||||
}
|
||||
|
||||
public function shouldStoreToken(): bool
|
||||
{
|
||||
return (bool) $this->store_card;
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
if ($this->has('store_card')) {
|
||||
|
73
app/Http/Requests/Gateways/Mollie/Mollie3dsRequest.php
Normal file
73
app/Http/Requests/Gateways/Mollie/Mollie3dsRequest.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?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\Http\Requests\Gateways\Mollie;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class Mollie3dsRequest extends FormRequest
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public function getCompany(): ?Company
|
||||
{
|
||||
return Company::where('company_key', $this->company_key)->first();
|
||||
}
|
||||
|
||||
public function getCompanyGateway(): ?CompanyGateway
|
||||
{
|
||||
return CompanyGateway::find($this->decodePrimaryKey($this->company_gateway_id));
|
||||
}
|
||||
|
||||
public function getPaymentHash(): ?PaymentHash
|
||||
{
|
||||
return PaymentHash::where('hash', $this->hash)->first();
|
||||
}
|
||||
|
||||
public function getClient(): ?Client
|
||||
{
|
||||
return Client::find($this->getPaymentHash()->data->client_id);
|
||||
}
|
||||
|
||||
public function getPaymentId(): ?string
|
||||
{
|
||||
return $this->getPaymentHash()->data->payment_id;
|
||||
}
|
||||
}
|
@ -65,54 +65,6 @@ class PaymentWebhookRequest extends Request
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve possible payment in the request.
|
||||
*
|
||||
* @return null|\App\Models\Payment
|
||||
*/
|
||||
public function getPayment()
|
||||
{
|
||||
// For testing purposes we'll slow down the webhook processing by 2 seconds
|
||||
// to make sure webhook request doesn't came before our processing.
|
||||
//if (app()->environment() !== 'production') {
|
||||
sleep(2);
|
||||
//}
|
||||
|
||||
// Some gateways, like Checkout, we can dynamically pass payment hash,
|
||||
// which we will resolve here and get payment information from it.
|
||||
if ($this->getPaymentHash()) {
|
||||
return $this->getPaymentHash()->payment;
|
||||
}
|
||||
|
||||
// While for some gateways, we need to extract the payment source/reference from the webhook request.
|
||||
// Gateways like this: Stripe
|
||||
if ($this->has('api_version') && $this->has('type') && $this->has('data')) {
|
||||
$src = $this->data['object']['id'];
|
||||
|
||||
return Payment::where('transaction_reference', $src)->firstOrFail();
|
||||
}
|
||||
|
||||
// If none of previously done logics is correct, we'll just display
|
||||
// not found page.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve client from payment hash.
|
||||
*
|
||||
* @return null|\App\Models\Client|bool
|
||||
*/
|
||||
public function getClient()
|
||||
{
|
||||
$hash = $this->getPaymentHash();
|
||||
|
||||
if($hash) {
|
||||
return Client::find($hash->data->client_id)->firstOrFail();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve company from company_key parameter.
|
||||
*
|
||||
|
@ -134,7 +134,7 @@ class Request extends FormRequest
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($input['contacts'])) {
|
||||
if (isset($input['contacts']) && is_array($input['contacts'])) {
|
||||
foreach ($input['contacts'] as $key => $contact) {
|
||||
if (array_key_exists('id', $contact) && is_numeric($contact['id'])) {
|
||||
unset($input['contacts'][$key]['id']);
|
||||
|
@ -71,7 +71,8 @@ class CreateAccount
|
||||
$sp794f3f = new Account();
|
||||
$sp794f3f->fill($this->request);
|
||||
|
||||
$sp794f3f->referral_code = Str::random(32);
|
||||
if(array_key_exists('rc', $this->request))
|
||||
$sp794f3f->referral_code = $this->request['rc'];
|
||||
|
||||
if (! $sp794f3f->key) {
|
||||
$sp794f3f->key = Str::random(32);
|
||||
|
@ -166,6 +166,7 @@ class CreateEntityPdf implements ShouldQueue
|
||||
'all_pages_header' => $this->entity->client->getSetting('all_pages_header'),
|
||||
'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'),
|
||||
],
|
||||
'process_markdown' => $this->entity->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMakerService($state);
|
||||
|
@ -107,7 +107,10 @@ class EmailEntity implements ShouldQueue
|
||||
/* Set DB */
|
||||
MultiDB::setDB($this->company->db);
|
||||
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
App::setLocale($this->invitation->contact->preferredLocale());
|
||||
$t->replace(Ninja::transformTranslations($this->settings));
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new TemplateEmail($this->email_entity_builder, $this->invitation->contact, $this->invitation);
|
||||
|
@ -573,7 +573,7 @@ class CSVImport implements ShouldQueue {
|
||||
}
|
||||
|
||||
private function findUser( $user_hash ) {
|
||||
$user = User::where( 'company_id', $this->company->id )
|
||||
$user = User::where( 'account_id', $this->company->account->id )
|
||||
->where( \DB::raw( 'CONCAT_WS(" ", first_name, last_name)' ), 'like', '%' . $user_hash . '%' )
|
||||
->first();
|
||||
|
||||
|
@ -40,6 +40,7 @@ use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Turbo124\Beacon\Facades\LightLogs;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/*Multi Mailer implemented*/
|
||||
|
||||
@ -71,9 +72,6 @@ class NinjaMailerJob implements ShouldQueue
|
||||
|
||||
public function handle()
|
||||
{
|
||||
|
||||
if($this->preFlightChecksFail())
|
||||
return;
|
||||
|
||||
/*Set the correct database*/
|
||||
MultiDB::setDb($this->nmo->company->db);
|
||||
@ -81,6 +79,9 @@ class NinjaMailerJob implements ShouldQueue
|
||||
/* Serializing models from other jobs wipes the primary key */
|
||||
$this->company = Company::where('company_key', $this->nmo->company->company_key)->first();
|
||||
|
||||
if($this->preFlightChecksFail())
|
||||
return;
|
||||
|
||||
/* Set the email driver */
|
||||
$this->setMailDriver();
|
||||
|
||||
@ -110,11 +111,10 @@ class NinjaMailerJob implements ShouldQueue
|
||||
LightLogs::create(new EmailSuccess($this->nmo->company->company_key))
|
||||
->batch();
|
||||
|
||||
/* Count the amount of emails sent across all the users accounts */
|
||||
Cache::increment($this->company->account->key);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
// if($e instanceof GuzzleHttp\Exception\ClientException){
|
||||
|
||||
// }
|
||||
|
||||
nlog("error failed with {$e->getMessage()}");
|
||||
|
||||
@ -227,6 +227,15 @@ class NinjaMailerJob implements ShouldQueue
|
||||
if(Ninja::isHosted() && strpos($this->nmo->to_user->email, '@example.com') !== false)
|
||||
return true;
|
||||
|
||||
/* GMail users are uncapped */
|
||||
if(Ninja::isHosted() && $this->nmo->settings->email_sending_method == 'gmail')
|
||||
return false;
|
||||
|
||||
/* On the hosted platform, if the user is over the email quotas, we do not send the email. */
|
||||
if(Ninja::isHosted() && $this->company->account->emailQuotaExceeded())
|
||||
return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -254,4 +263,5 @@ class NinjaMailerJob implements ShouldQueue
|
||||
LightLogs::create($job_failure)
|
||||
->batch();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -13,26 +13,18 @@ namespace App\Jobs\Ninja;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class AdjustEmailQuota implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
const FREE_PLAN_DAILY_QUOTA = 10;
|
||||
const PRO_PLAN_DAILY_QUOTA = 50;
|
||||
const ENTERPRISE_PLAN_DAILY_QUOTA = 200;
|
||||
|
||||
const FREE_PLAN_DAILY_CAP = 20;
|
||||
const PRO_PLAN_DAILY_CAP = 100;
|
||||
const ENTERPRISE_PLAN_DAILY_CAP = 300;
|
||||
|
||||
const DAILY_MULTIPLIER = 1.1;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
@ -50,22 +42,28 @@ class AdjustEmailQuota implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$this->adjust();
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
if(!Ninja::isHosted())
|
||||
return;
|
||||
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$this->adjust();
|
||||
|
||||
$this->adjust();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function adjust()
|
||||
{
|
||||
foreach (Account::cursor() as $account) {
|
||||
//@TODO once we add in the two columns daily_emails_quota daily_emails_sent_
|
||||
}
|
||||
|
||||
Account::query()->cursor()->each(function ($account){
|
||||
nlog("resetting email quota for {$account->key}");
|
||||
Cache::forget($account->key);
|
||||
Cache::forget("throttle_notified:{$account->key}");
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,6 @@ class EmailPayment implements ShouldQueue
|
||||
if ($this->company->is_disabled)
|
||||
return true;
|
||||
|
||||
|
||||
if ($this->contact->email) {
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
@ -56,6 +56,17 @@ class SendRecurring implements ShouldQueue
|
||||
*/
|
||||
public function handle() : void
|
||||
{
|
||||
//reset all contacts here
|
||||
$this->recurring_invoice->client->contacts()->update(['send_email' => false]);
|
||||
|
||||
$this->recurring_invoice->invitations->each(function ($invitation){
|
||||
|
||||
$contact = $invitation->contact;
|
||||
$contact->send_email = true;
|
||||
$contact->save();
|
||||
|
||||
});
|
||||
|
||||
// Generate Standard Invoice
|
||||
$invoice = RecurringInvoiceToInvoiceFactory::create($this->recurring_invoice, $this->recurring_invoice->client);
|
||||
|
||||
@ -67,7 +78,7 @@ class SendRecurring implements ShouldQueue
|
||||
$invoice = $invoice->service()
|
||||
->markSent()
|
||||
->applyNumber()
|
||||
->createInvitations()
|
||||
->createInvitations() //need to only link invitations to those in the recurring invoice
|
||||
->fillDefaults()
|
||||
->save();
|
||||
|
||||
|
@ -1398,7 +1398,7 @@ class Import implements ShouldQueue
|
||||
$nmo->company = $this->company;
|
||||
$nmo->settings = $this->company->settings;
|
||||
$nmo->to_user = $this->user;
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
NinjaMailerJob::dispatch($nmo, true);
|
||||
|
||||
$modified['gateway_key'] = 'd14dd26a47cecc30fdd65700bfb67b34';
|
||||
|
||||
|
@ -30,6 +30,8 @@ class ReminderJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesReminders, MakesDates;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
@ -48,6 +50,7 @@ class ReminderJob implements ShouldQueue
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
nlog("set db {$db}");
|
||||
$this->processReminders();
|
||||
}
|
||||
}
|
||||
@ -172,8 +175,10 @@ class ReminderJob implements ShouldQueue
|
||||
$invoice->line_items = $invoice_items;
|
||||
|
||||
/**Refresh Invoice values*/
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
|
||||
$invoice = $invoice->calc()->getInvoice()->save();
|
||||
$invoice->service()->deletePdf();
|
||||
|
||||
nlog("adjusting client balance and invoice balance by ". ($invoice->balance - $temp_invoice_balance));
|
||||
$invoice->client->service()->updateBalance($invoice->balance - $temp_invoice_balance)->save();
|
||||
$invoice->ledger()->updateInvoiceBalance($invoice->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$invoice->number}");
|
||||
|
||||
|
@ -133,6 +133,15 @@ class StartMigration implements ShouldQueue
|
||||
|
||||
Mail::to($this->user->email, $this->user->name())->send(new MigrationFailed($e, $this->company, $e->getMessage()));
|
||||
|
||||
if(Ninja::isHosted()){
|
||||
|
||||
$migration_failed = new MigrationFailed($e, $this->company, $e->getMessage());
|
||||
$migration_failed->is_system = true;
|
||||
|
||||
Mail::to('contact@invoiceninja.com', 'Failed Migration')->send($migration_failed);
|
||||
|
||||
}
|
||||
|
||||
if (app()->environment() !== 'production') {
|
||||
info($e->getMessage());
|
||||
}
|
||||
|
@ -37,6 +37,10 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
|
||||
private $helpers;
|
||||
|
||||
private $payment_template_body;
|
||||
|
||||
private $payment_template_subject;
|
||||
|
||||
public function __construct($payment, $contact, $template_data = null)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
@ -55,20 +59,22 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
App::setLocale($this->contact->preferredLocale());
|
||||
$t->replace(Ninja::transformTranslations($this->client->getMergedSettings()));
|
||||
|
||||
$this->resolvePaymentTemplate();
|
||||
|
||||
if (is_array($this->template_data) && array_key_exists('body', $this->template_data) && strlen($this->template_data['body']) > 0) {
|
||||
$body_template = $this->template_data['body'];
|
||||
} elseif (strlen($this->client->getSetting('email_template_payment')) > 0) {
|
||||
$body_template = $this->client->getSetting('email_template_payment');
|
||||
} elseif (strlen($this->client->getSetting($this->payment_template_body)) > 0) {
|
||||
$body_template = $this->client->getSetting($this->payment_template_body);
|
||||
} else {
|
||||
$body_template = EmailTemplateDefaults::getDefaultTemplate('email_template_payment', $this->client->locale());
|
||||
$body_template = EmailTemplateDefaults::getDefaultTemplate($this->payment_template_body, $this->client->locale());
|
||||
}
|
||||
|
||||
if (is_array($this->template_data) && array_key_exists('subject', $this->template_data) && strlen($this->template_data['subject']) > 0) {
|
||||
$subject_template = $this->template_data['subject'];
|
||||
} elseif (strlen($this->client->getSetting('email_subject_payment')) > 0) {
|
||||
$subject_template = $this->client->getSetting('email_subject_payment');
|
||||
} elseif (strlen($this->client->getSetting($this->payment_template_subject)) > 0) {
|
||||
$subject_template = $this->client->getSetting($this->payment_template_subject);
|
||||
} else {
|
||||
$subject_template = EmailTemplateDefaults::getDefaultTemplate('email_subject_payment', $this->client->locale());
|
||||
$subject_template = EmailTemplateDefaults::getDefaultTemplate($this->payment_template_subject, $this->client->locale());
|
||||
}
|
||||
|
||||
$this->setTemplate($this->client->getSetting('email_style'))
|
||||
@ -96,6 +102,34 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to resolve which payment template
|
||||
* to use. We need to check the invoice balances to
|
||||
* determine if this is a partial payment, or full payment.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
private function resolvePaymentTemplate()
|
||||
{
|
||||
|
||||
$partial = $this->payment->invoices->contains(function ($invoice){
|
||||
|
||||
return $invoice->balance > 0;
|
||||
|
||||
});
|
||||
|
||||
if($partial){
|
||||
$this->payment_template_body = "email_template_payment_partial";
|
||||
$this->payment_template_subject = "email_subject_payment_partial";
|
||||
}
|
||||
else {
|
||||
$this->payment_template_body = "email_template_payment";
|
||||
$this->payment_template_subject = "email_subject_payment";
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function makePaymentVariables()
|
||||
{
|
||||
@ -194,12 +228,14 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->payment->getLink().'">'.ctrans('texts.view_payment').'</a>', 'label' => ctrans('texts.view_payment')];
|
||||
$data['$paymentLink'] = &$data['$view_link'];
|
||||
$data['$portalButton'] = &$data['$view_link'];
|
||||
$data['$portalButton'] = ['value' => "<a href='{$this->payment->getPortalLink()}'>".ctrans('texts.login')."</a>", 'label' =>''];
|
||||
$data['$portal_url'] = &$data['$portalButton'];
|
||||
|
||||
$data['$view_url'] = ['value' => $this->payment->getLink(), 'label' => ctrans('texts.view_payment')];
|
||||
$data['$signature'] = ['value' => $this->settings->email_signature ?: ' ', 'label' => ''];
|
||||
|
||||
$data['$invoices'] = ['value' => $this->formatInvoices(), 'label' => ctrans('texts.invoices')];
|
||||
$data['$invoice_references'] = ['value' => $this->formatInvoiceReferences(), 'label' => ctrans('texts.invoices')];
|
||||
|
||||
return $data;
|
||||
}
|
||||
@ -215,6 +251,25 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
return $invoice_list;
|
||||
}
|
||||
|
||||
private function formatInvoiceReferences()
|
||||
{
|
||||
|
||||
$invoice_list = '<br><br>';
|
||||
|
||||
foreach ($this->payment->invoices as $invoice) {
|
||||
|
||||
$invoice_list .= ctrans('texts.po_number'). " {$invoice->po_number} <br>";
|
||||
$invoice_list .= ctrans('texts.invoice_number_short') . " {$invoice->number} <br>";
|
||||
$invoice_list .= ctrans('texts.invoice_amount') ." ". Number::formatMoney($invoice->pivot->amount, $this->client) . "<br>";
|
||||
$invoice_list .= ctrans('texts.invoice_balance') ." ". Number::formatMoney($invoice->fresh()->balance, $this->client) . "<br>";
|
||||
$invoice_list .= "-----<br>";
|
||||
|
||||
}
|
||||
|
||||
return $invoice_list;
|
||||
|
||||
}
|
||||
|
||||
public function makeValues() :array
|
||||
{
|
||||
$data = [];
|
||||
|
@ -13,6 +13,8 @@ class MigrationFailed extends Mailable
|
||||
|
||||
public $company;
|
||||
|
||||
public $is_system = false;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
@ -38,6 +40,7 @@ class MigrationFailed extends Mailable
|
||||
->view('email.migration.failed', [
|
||||
'logo' => $this->company->present()->logo(),
|
||||
'settings' => $this->company->settings,
|
||||
'is_system' => $this->is_system,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
61
app/Mail/Ninja/EmailQuotaExceeded.php
Normal file
61
app/Mail/Ninja/EmailQuotaExceeded.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?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\Mail\Ninja;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class EmailQuotaExceeded extends Mailable
|
||||
{
|
||||
|
||||
public $company;
|
||||
|
||||
public $settings;
|
||||
|
||||
public $logo;
|
||||
|
||||
public $title;
|
||||
|
||||
public $body;
|
||||
|
||||
public $whitelabel;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($company)
|
||||
{
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$this->settings = $this->company->settings;
|
||||
$this->logo = $this->company->present()->logo();
|
||||
$this->title = ctrans('texts.email_quota_exceeded_subject');
|
||||
$this->body = ctrans('texts.email_quota_exceeded_body', ['quota' => $this->company->account->getDailyEmailLimit()]);
|
||||
$this->whitelabel = $this->company->account->isPaid();
|
||||
$this->replyTo('contact@invoiceninja.com', 'Contact');
|
||||
|
||||
return $this->from(config('mail.from.address'), config('mail.from.name'))
|
||||
->subject(ctrans('texts.email_quota_exceeded_subject'))
|
||||
->view('email.admin.email_quota_exceeded');
|
||||
}
|
||||
}
|
@ -60,11 +60,12 @@ class SupportMessageSent extends Mailable
|
||||
|
||||
$company = auth()->user()->company();
|
||||
$user = auth()->user();
|
||||
$db = str_replace("db-ninja-", "", $company->db);
|
||||
|
||||
if(Ninja::isHosted())
|
||||
$subject = "{$priority}Hosted-{$company->db} :: Customer Support - {$plan} ".date('M jS, g:ia');
|
||||
$subject = "{$priority}Hosted-{$db} :: {ucfirst($plan)} :: ".date('M jS, g:ia');
|
||||
else
|
||||
$subject = "{$priority}Self Hosted :: Customer Support - [{$plan}] ".date('M jS, g:ia');
|
||||
$subject = "{$priority}Self Hosted :: {ucfirst($plan)} :: ".date('M jS, g:ia');
|
||||
|
||||
return $this->from(config('mail.from.address'), $user->present()->name())
|
||||
->replyTo($user->email, $user->present()->name())
|
||||
|
@ -11,12 +11,16 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Mail\Ninja\EmailQuotaExceeded;
|
||||
use App\Models\Presenters\AccountPresenter;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Carbon\Carbon;
|
||||
use DateTime;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
|
||||
class Account extends BaseModel
|
||||
@ -24,6 +28,9 @@ class Account extends BaseModel
|
||||
use PresentableTrait;
|
||||
use MakesHash;
|
||||
|
||||
private $free_plan_email_quota = 250;
|
||||
|
||||
private $paid_plan_email_quota = 500;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@ -127,6 +134,9 @@ class Account extends BaseModel
|
||||
|
||||
public function getPlan()
|
||||
{
|
||||
if(Carbon::parse($this->plan_expires)->lt(now()))
|
||||
return '';
|
||||
|
||||
return $this->plan ?: '';
|
||||
}
|
||||
|
||||
@ -341,4 +351,57 @@ class Account extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function getDailyEmailLimit()
|
||||
{
|
||||
|
||||
if($this->isPaid()){
|
||||
$limit = $this->paid_plan_email_quota;
|
||||
$limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 50;
|
||||
}
|
||||
else{
|
||||
$limit = $this->free_plan_email_quota;
|
||||
$limit += Carbon::createFromTimestamp($this->created_at)->diffInMonths() * 100;
|
||||
}
|
||||
|
||||
return min($limit, 5000);
|
||||
}
|
||||
|
||||
public function emailsSent()
|
||||
{
|
||||
if(is_null(Cache::get($this->key)))
|
||||
return 0;
|
||||
|
||||
return Cache::get($this->key);
|
||||
}
|
||||
|
||||
public function emailQuotaExceeded() :bool
|
||||
{
|
||||
if(is_null(Cache::get($this->key)))
|
||||
return false;
|
||||
|
||||
try {
|
||||
if(Cache::get($this->key) > $this->getDailyEmailLimit()) {
|
||||
|
||||
if(is_null(Cache::get("throttle_notified:{$this->key}"))) {
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new EmailQuotaExceeded($this->companies()->first());
|
||||
$nmo->company = $this->companies()->first();
|
||||
$nmo->settings = $this->companies()->first()->settings;
|
||||
$nmo->to_user = $this->companies()->first()->owner();
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
Cache::put("throttle_notified:{$this->key}", true, 60 * 24);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch(\Exception $e){
|
||||
\Sentry\captureMessage("I encountered an error with email quotas - defaulting to SEND");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ class Company extends BaseModel
|
||||
protected $presenter = CompanyPresenter::class;
|
||||
|
||||
protected $fillable = [
|
||||
'markdown_enabled',
|
||||
'calculate_expense_tax_by_amount',
|
||||
'invoice_expense_documents',
|
||||
'invoice_task_documents',
|
||||
|
@ -69,16 +69,20 @@ class CompanyGateway extends BaseModel
|
||||
// const TYPE_CUSTOM = 306;
|
||||
// const TYPE_BRAINTREE = 307;
|
||||
// const TYPE_WEPAY = 309;
|
||||
// const TYPE_PAYFAST = 310;
|
||||
// const TYPE_PAYTRACE = 311;
|
||||
|
||||
public $gateway_consts = [
|
||||
'38f2c48af60c7dd69e04248cbb24c36e' => 300,
|
||||
'd14dd26a37cecc30fdd65700bfb55b23' => 301,
|
||||
'd14dd26a47cecc30fdd65700bfb67b34' => 301,
|
||||
'3758e7f7c6f4cecf0f4f348b9a00f456' => 304,
|
||||
'3b6621f970ab18887c4f6dca78d3f8bb' => 305,
|
||||
'54faab2ab6e3223dbe848b1686490baa' => 306,
|
||||
'd14dd26a47cecc30fdd65700bfb67b34' => 301,
|
||||
'8fdeed552015b3c7b44ed6c8ebd9e992' => 309,
|
||||
'f7ec488676d310683fb51802d076d713' => 307,
|
||||
'8fdeed552015b3c7b44ed6c8ebd9e992' => 309,
|
||||
'd6814fc83f45d2935e7777071e629ef9' => 310,
|
||||
'bbd736b3254b0aabed6ad7fda1298c88' => 311,
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
@ -118,7 +122,7 @@ class CompanyGateway extends BaseModel
|
||||
}
|
||||
|
||||
/* This is the public entry point into the payment superclass */
|
||||
public function driver(Client $client)
|
||||
public function driver(Client $client = null)
|
||||
{
|
||||
$class = static::driver_class();
|
||||
|
||||
|
@ -81,6 +81,9 @@ class Gateway extends StaticModel
|
||||
case 1:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]];//Authorize.net
|
||||
break;
|
||||
case 1:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => false]];//Payfast
|
||||
break;
|
||||
case 15:
|
||||
return [GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]]; //Paypal
|
||||
break;
|
||||
@ -95,16 +98,23 @@ class Gateway extends StaticModel
|
||||
case 39:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Checkout
|
||||
break;
|
||||
case 46:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Paytrace
|
||||
case 49:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true]]; //WePay
|
||||
break;
|
||||
case 50:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], //Braintree
|
||||
GatewayType::PAYPAL => ['refund' => true, 'token_billing' => true]
|
||||
];
|
||||
break;
|
||||
case 7:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], // Mollie
|
||||
];
|
||||
break;
|
||||
default:
|
||||
return [];
|
||||
break;
|
||||
|
@ -16,6 +16,7 @@ use App\Services\Ledger\LedgerService;
|
||||
use App\Services\Payment\PaymentService;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\Inviteable;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\Payment\Refundable;
|
||||
@ -28,7 +29,8 @@ class Payment extends BaseModel
|
||||
use MakesDates;
|
||||
use SoftDeletes;
|
||||
use Refundable;
|
||||
|
||||
use Inviteable;
|
||||
|
||||
const STATUS_PENDING = 1;
|
||||
const STATUS_CANCELLED = 2;
|
||||
const STATUS_FAILED = 3;
|
||||
|
@ -20,7 +20,6 @@ class PaymentHash extends Model
|
||||
protected $casts = [
|
||||
'data' => 'object',
|
||||
];
|
||||
|
||||
|
||||
public function invoices()
|
||||
{
|
||||
@ -41,4 +40,12 @@ class PaymentHash extends Model
|
||||
{
|
||||
return $this->belongsTo(Invoice::class, 'fee_invoice_id', 'id');
|
||||
}
|
||||
|
||||
public function withData(string $property, $value): self
|
||||
{
|
||||
$this->data = array_merge((array) $this->data, [$property => $value]);
|
||||
$this->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -68,8 +68,9 @@ class SystemLog extends Model
|
||||
const TYPE_BRAINTREE = 307;
|
||||
const TYPE_WEPAY = 309;
|
||||
const TYPE_PAYFAST = 310;
|
||||
const TYPE_PAYTRACE = 311;
|
||||
const TYPE_MOLLIE = 312;
|
||||
|
||||
|
||||
const TYPE_QUOTA_EXCEEDED = 400;
|
||||
const TYPE_UPSTREAM_FAILURE = 401;
|
||||
|
||||
@ -113,4 +114,107 @@ class SystemLog extends Model
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getCategoryName()
|
||||
{
|
||||
switch ($this->category_id) {
|
||||
case self::CATEGORY_GATEWAY_RESPONSE:
|
||||
return "Gateway";
|
||||
case self::CATEGORY_MAIL:
|
||||
return "Mail";
|
||||
case self::CATEGORY_WEBHOOK:
|
||||
return "Webhook";
|
||||
case self::CATEGORY_PDF:
|
||||
return "PDF";
|
||||
case self::CATEGORY_SECURITY:
|
||||
return "Security";
|
||||
|
||||
default:
|
||||
return 'undefined';
|
||||
}
|
||||
}
|
||||
|
||||
public function getEventName()
|
||||
{
|
||||
switch ($this->event_id) {
|
||||
case self::EVENT_PAYMENT_RECONCILIATION_FAILURE:
|
||||
return "Payment reco failure";
|
||||
case self::EVENT_PAYMENT_RECONCILIATION_SUCCESS:
|
||||
return "Payment reco success";
|
||||
case self::EVENT_GATEWAY_SUCCESS:
|
||||
return "Success";
|
||||
case self::EVENT_GATEWAY_FAILURE:
|
||||
return "Failure";
|
||||
case self::EVENT_GATEWAY_ERROR:
|
||||
return "Error";
|
||||
case self::EVENT_MAIL_SEND:
|
||||
return "Send";
|
||||
case self::EVENT_MAIL_RETRY_QUEUE:
|
||||
return "Retry";
|
||||
case self::EVENT_MAIL_BOUNCED:
|
||||
return "Bounced";
|
||||
case self::EVENT_MAIL_SPAM_COMPLAINT:
|
||||
return "Spam";
|
||||
case self::EVENT_MAIL_DELIVERY:
|
||||
return "Delivery";
|
||||
case self::EVENT_WEBHOOK_RESPONSE:
|
||||
return "Webhook Response";
|
||||
case self::EVENT_PDF_RESPONSE:
|
||||
return "Pdf Response";
|
||||
case self::EVENT_AUTHENTICATION_FAILURE:
|
||||
return "Auth Failure";
|
||||
case self::EVENT_USER:
|
||||
return "User";
|
||||
default:
|
||||
return 'undefined';
|
||||
}
|
||||
}
|
||||
|
||||
public function getTypeName()
|
||||
{
|
||||
switch ($this->type_id) {
|
||||
case self::TYPE_QUOTA_EXCEEDED:
|
||||
return "Quota Exceeded";
|
||||
case self::TYPE_UPSTREAM_FAILURE:
|
||||
return "Upstream Failure";
|
||||
case self::TYPE_WEBHOOK_RESPONSE:
|
||||
return "Webhook";
|
||||
case self::TYPE_PDF_FAILURE:
|
||||
return "Failure";
|
||||
case self::TYPE_PDF_SUCCESS:
|
||||
return "Success";
|
||||
case self::TYPE_MODIFIED:
|
||||
return "Modified";
|
||||
case self::TYPE_DELETED:
|
||||
return "Deleted";
|
||||
case self::TYPE_LOGIN_SUCCESS:
|
||||
return "Login Success";
|
||||
case self::TYPE_LOGIN_FAILURE:
|
||||
return "Login Failure";
|
||||
case self::TYPE_PAYPAL:
|
||||
return "PayPal";
|
||||
case self::TYPE_STRIPE:
|
||||
return "Stripe";
|
||||
case self::TYPE_LEDGER:
|
||||
return "Ledger";
|
||||
case self::TYPE_FAILURE:
|
||||
return "Failure";
|
||||
case self::TYPE_CHECKOUT:
|
||||
return "Checkout";
|
||||
case self::TYPE_AUTHORIZE:
|
||||
return "Auth.net";
|
||||
case self::TYPE_CUSTOM:
|
||||
return "Custom";
|
||||
case self::TYPE_BRAINTREE:
|
||||
return "Braintree";
|
||||
case self::TYPE_WEPAY:
|
||||
return "WePay";
|
||||
case self::TYPE_PAYFAST:
|
||||
return "Payfast";
|
||||
default:
|
||||
return 'undefined';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -431,6 +431,61 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
return false;
|
||||
}
|
||||
|
||||
/*Generic Global unsuccessful transaction method when the client is present*/
|
||||
public function processUnsuccessfulTransaction($response, $client_present = true)
|
||||
{
|
||||
$error = $response['error'];
|
||||
$error_code = $response['error_code'];
|
||||
|
||||
$this->unWindGatewayFees($this->payment_hash);
|
||||
|
||||
PaymentFailureMailer::dispatch($this->client, $error, $this->client->company, $this->payment_hash->data->amount_with_fee);
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new NinjaMailer( (new ClientPaymentFailureObject($this->client, $error, $this->client->company, $this->payment_hash))->build() );
|
||||
$nmo->company = $this->client->company;
|
||||
$nmo->settings = $this->client->company->settings;
|
||||
|
||||
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
|
||||
|
||||
$invoices->each(function ($invoice){
|
||||
|
||||
$invoice->service()->deletePdf();
|
||||
|
||||
});
|
||||
|
||||
$invoices->first()->invitations->each(function ($invitation) use ($nmo){
|
||||
|
||||
if ($invitation->contact->send_email && $invitation->contact->email) {
|
||||
|
||||
$nmo->to_user = $invitation->contact;
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
$message = [
|
||||
'server_response' => $response,
|
||||
'data' => $this->payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$message,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
$this::SYSTEM_LOG_TYPE,
|
||||
$this->client,
|
||||
$this->client->company,
|
||||
);
|
||||
|
||||
if($client_present)
|
||||
throw new PaymentFailed($error, 500);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function checkRequirements()
|
||||
{
|
||||
if ($this->company_gateway->require_billing_address) {
|
||||
|
@ -332,7 +332,7 @@ class CheckoutComPaymentDriver extends BaseDriver
|
||||
}
|
||||
}
|
||||
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
239
app/PaymentDrivers/Mollie/CreditCard.php
Normal file
239
app/PaymentDrivers/Mollie/CreditCard.php
Normal file
@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
namespace App\PaymentDrivers\Mollie;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
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\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\MolliePaymentDriver;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class CreditCard
|
||||
{
|
||||
/**
|
||||
* @var MolliePaymentDriver
|
||||
*/
|
||||
protected $mollie;
|
||||
|
||||
public function __construct(MolliePaymentDriver $mollie)
|
||||
{
|
||||
$this->mollie = $mollie;
|
||||
|
||||
$this->mollie->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the page for credit card payments.
|
||||
*
|
||||
* @param array $data
|
||||
* @return Factory|View
|
||||
*/
|
||||
public function paymentView(array $data)
|
||||
{
|
||||
$data['gateway'] = $this->mollie;
|
||||
|
||||
return render('gateways.mollie.credit_card.pay', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a payment object.
|
||||
*
|
||||
* @param PaymentResponseRequest $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function paymentResponse(PaymentResponseRequest $request)
|
||||
{
|
||||
$amount = $this->mollie->convertToMollieAmount((float) $this->mollie->payment_hash->data->amount_with_fee);
|
||||
|
||||
$this->mollie->payment_hash
|
||||
->withData('gateway_type_id', GatewayType::CREDIT_CARD)
|
||||
->withData('client_id', $this->mollie->client->id);
|
||||
|
||||
if (!empty($request->token)) {
|
||||
try {
|
||||
$cgt = ClientGatewayToken::where('token', $request->token)->firstOrFail();
|
||||
|
||||
$payment = $this->mollie->gateway->payments->create([
|
||||
'amount' => [
|
||||
'currency' => $this->mollie->client->currency()->code,
|
||||
'value' => $amount,
|
||||
],
|
||||
'mandateId' => $request->token,
|
||||
'customerId' => $cgt->gateway_customer_reference,
|
||||
'sequenceType' => 'recurring',
|
||||
'description' => \sprintf('Hash: %s', $this->mollie->payment_hash->hash),
|
||||
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
|
||||
]);
|
||||
|
||||
if ($payment->status === 'paid') {
|
||||
$this->mollie->logSuccessfulGatewayResponse(
|
||||
['response' => $payment, 'data' => $this->mollie->payment_hash],
|
||||
SystemLog::TYPE_MOLLIE
|
||||
);
|
||||
|
||||
return $this->processSuccessfulPayment($payment);
|
||||
}
|
||||
|
||||
if ($payment->status === 'open') {
|
||||
$this->mollie->payment_hash->withData('payment_id', $payment->id);
|
||||
|
||||
return redirect($payment->getCheckoutUrl());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return $this->processUnsuccessfulPayment($e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$data = [
|
||||
'amount' => [
|
||||
'currency' => $this->mollie->client->currency()->code,
|
||||
'value' => $amount,
|
||||
],
|
||||
'description' => \sprintf('Hash: %s', $this->mollie->payment_hash->hash),
|
||||
'redirectUrl' => route('mollie.3ds_redirect', [
|
||||
'company_key' => $this->mollie->client->company->company_key,
|
||||
'company_gateway_id' => $this->mollie->company_gateway->hashed_id,
|
||||
'hash' => $this->mollie->payment_hash->hash,
|
||||
]),
|
||||
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
|
||||
'cardToken' => $request->gateway_response,
|
||||
];
|
||||
|
||||
if ($request->shouldStoreToken()) {
|
||||
$customer = $this->mollie->gateway->customers->create([
|
||||
'name' => $this->mollie->client->name,
|
||||
'email' => $this->mollie->client->present()->email(),
|
||||
'metadata' => [
|
||||
'id' => $this->mollie->client->hashed_id,
|
||||
],
|
||||
]);
|
||||
|
||||
$data['customerId'] = $customer->id;
|
||||
$data['sequenceType'] = 'first';
|
||||
|
||||
$this->mollie->payment_hash
|
||||
->withData('mollieCustomerId', $customer->id)
|
||||
->withData('shouldStoreToken', true);
|
||||
}
|
||||
|
||||
$payment = $this->mollie->gateway->payments->create($data);
|
||||
|
||||
if ($payment->status === 'paid') {
|
||||
$this->mollie->logSuccessfulGatewayResponse(
|
||||
['response' => $payment, 'data' => $this->mollie->payment_hash],
|
||||
SystemLog::TYPE_MOLLIE
|
||||
);
|
||||
|
||||
return $this->processSuccessfulPayment($payment);
|
||||
}
|
||||
|
||||
if ($payment->status === 'open') {
|
||||
$this->mollie->payment_hash->withData('payment_id', $payment->id);
|
||||
|
||||
return redirect($payment->getCheckoutUrl());
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->processUnsuccessfulPayment($e);
|
||||
|
||||
throw new PaymentFailed($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
public function processSuccessfulPayment(\Mollie\Api\Resources\Payment $payment)
|
||||
{
|
||||
$payment_hash = $this->mollie->payment_hash;
|
||||
|
||||
if (property_exists($payment_hash->data, 'shouldStoreToken') && $payment_hash->data->shouldStoreToken) {
|
||||
try {
|
||||
$mandates = \iterator_to_array($this->mollie->gateway->mandates->listForId($payment_hash->data->mollieCustomerId));
|
||||
} catch (\Mollie\Api\Exceptions\ApiException $e) {
|
||||
return $this->processUnsuccessfulPayment($e);
|
||||
}
|
||||
|
||||
$payment_meta = new \stdClass;
|
||||
$payment_meta->exp_month = (string) $mandates[0]->details->cardExpiryDate;
|
||||
$payment_meta->exp_year = (string) '';
|
||||
$payment_meta->brand = (string) $mandates[0]->details->cardLabel;
|
||||
$payment_meta->last4 = (string) $mandates[0]->details->cardNumber;
|
||||
$payment_meta->type = GatewayType::CREDIT_CARD;
|
||||
|
||||
$this->mollie->storeGatewayToken([
|
||||
'token' => $mandates[0]->id,
|
||||
'payment_method_id' => GatewayType::CREDIT_CARD,
|
||||
'payment_meta' => $payment_meta,
|
||||
], ['gateway_customer_reference' => $payment_hash->data->mollieCustomerId]);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
'amount' => array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total,
|
||||
'payment_type' => PaymentType::CREDIT_CARD_OTHER,
|
||||
'transaction_reference' => $payment->id,
|
||||
];
|
||||
|
||||
$payment_record = $this->mollie->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $payment, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->mollie->client,
|
||||
$this->mollie->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->mollie->encodePrimaryKey($payment_record->id)]);
|
||||
}
|
||||
|
||||
public function processUnsuccessfulPayment(\Exception $e)
|
||||
{
|
||||
PaymentFailureMailer::dispatch(
|
||||
$this->mollie->client,
|
||||
$e->getMessage(),
|
||||
$this->mollie->client->company,
|
||||
$this->mollie->payment_hash->data->amount_with_fee
|
||||
);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$e->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->mollie->client,
|
||||
$this->mollie->client->company,
|
||||
);
|
||||
|
||||
throw new PaymentFailed($e->getMessage(), $e->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show authorization page.
|
||||
*
|
||||
* @param array $data
|
||||
* @return Factory|View
|
||||
*/
|
||||
public function authorizeView(array $data)
|
||||
{
|
||||
return render('gateways.mollie.credit_card.authorize', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle authorization response.
|
||||
*
|
||||
* @param mixed $request
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function authorizeResponse($request): RedirectResponse
|
||||
{
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
}
|
||||
}
|
354
app/PaymentDrivers/MolliePaymentDriver.php
Normal file
354
app/PaymentDrivers/MolliePaymentDriver.php
Normal file
@ -0,0 +1,354 @@
|
||||
<?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\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Http\Requests\Gateways\Mollie\Mollie3dsRequest;
|
||||
use App\Http\Requests\Payments\PaymentWebhookRequest;
|
||||
use App\Jobs\Mail\PaymentFailureMailer;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Mollie\CreditCard;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Mollie\Api\Exceptions\ApiException;
|
||||
use Mollie\Api\MollieApiClient;
|
||||
|
||||
class MolliePaymentDriver extends BaseDriver
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
public $refundable = true;
|
||||
|
||||
/**
|
||||
* @var true
|
||||
*/
|
||||
public $token_billing = true;
|
||||
|
||||
/**
|
||||
* @var true
|
||||
*/
|
||||
public $can_authorise_credit_card = true;
|
||||
|
||||
/**
|
||||
* @var MollieApiClient
|
||||
*/
|
||||
public $gateway;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
public $payment_method;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public static $methods = [
|
||||
GatewayType::CREDIT_CARD => CreditCard::class,
|
||||
];
|
||||
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_MOLLIE;
|
||||
|
||||
public function init(): self
|
||||
{
|
||||
$this->gateway = new MollieApiClient();
|
||||
|
||||
$this->gateway->setApiKey(
|
||||
$this->company_gateway->getConfigField('apiKey'),
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function gatewayTypes(): array
|
||||
{
|
||||
$types = [];
|
||||
|
||||
$types[] = GatewayType::CREDIT_CARD;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public function processPaymentResponse($request)
|
||||
{
|
||||
return $this->payment_method->paymentResponse($request);
|
||||
}
|
||||
|
||||
public function refund(Payment $payment, $amount, $return_client_response = false)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
try {
|
||||
$payment = $this->gateway->payments->get($payment->transaction_reference);
|
||||
|
||||
$refund = $this->gateway->payments->refund($payment, [
|
||||
'amount' => [
|
||||
'currency' => $this->client->currency()->code,
|
||||
'value' => $this->convertToMollieAmount((float) $amount),
|
||||
],
|
||||
]);
|
||||
|
||||
if ($refund->status === 'refunded') {
|
||||
SystemLogger::dispatch(
|
||||
['server_response' => $refund, 'data' => request()->all()],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->client,
|
||||
$this->client->company
|
||||
);
|
||||
|
||||
return [
|
||||
'transaction_reference' => $refund->id,
|
||||
'transaction_response' => json_encode($refund),
|
||||
'success' => $refund->status === 'refunded' ? true : false,
|
||||
'description' => $refund->description,
|
||||
'code' => 200,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'transaction_reference' => $refund->id,
|
||||
'transaction_response' => json_encode($refund),
|
||||
'success' => true,
|
||||
'description' => $refund->description,
|
||||
'code' => 0,
|
||||
];
|
||||
} catch (ApiException $e) {
|
||||
SystemLogger::dispatch(
|
||||
['server_response' => $refund, 'data' => request()->all()],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->client,
|
||||
$this->client->companyk
|
||||
);
|
||||
|
||||
nlog($e->getMessage());
|
||||
|
||||
return [
|
||||
'transaction_reference' => null,
|
||||
'transaction_response' => $e->getMessage(),
|
||||
'success' => false,
|
||||
'description' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = "Invoice {$invoice->number} for {$amount} for client {$this->client->present()->name()}";
|
||||
} else {
|
||||
$description = "Payment with no invoice for amount {$amount} for client {$this->client->present()->name()}";
|
||||
}
|
||||
|
||||
$request = new PaymentResponseRequest();
|
||||
$request->setMethod('POST');
|
||||
$request->request->add(['payment_hash' => $payment_hash->hash]);
|
||||
|
||||
$this->init();
|
||||
|
||||
try {
|
||||
$payment = $this->gateway->payments->create([
|
||||
'amount' => [
|
||||
'currency' => $this->client->currency()->code,
|
||||
'value' => $this->convertToMollieAmount($amount),
|
||||
],
|
||||
'mandateId' => $cgt->token,
|
||||
'customerId' => $cgt->gateway_customer_reference,
|
||||
'sequenceType' => 'recurring',
|
||||
'description' => $description,
|
||||
'webhookUrl' => $this->company_gateway->webhookUrl(),
|
||||
]);
|
||||
|
||||
if ($payment->status === 'paid') {
|
||||
$this->confirmGatewayFee($request);
|
||||
|
||||
$data = [
|
||||
'payment_method' => $cgt->token,
|
||||
'payment_type' => PaymentType::CREDIT_CARD_OTHER,
|
||||
'amount' => $amount,
|
||||
'transaction_reference' => $payment->id,
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
|
||||
$payment = $this->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $payment, 'data' => $data],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->client
|
||||
);
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
$this->unWindGatewayFees($payment_hash);
|
||||
|
||||
PaymentFailureMailer::dispatch(
|
||||
$this->client,
|
||||
$payment->details,
|
||||
$this->client->company,
|
||||
$amount
|
||||
);
|
||||
|
||||
$message = [
|
||||
'server_response' => $payment,
|
||||
'data' => $payment_hash->data,
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(
|
||||
$message,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_CHECKOUT,
|
||||
$this->client
|
||||
);
|
||||
|
||||
return false;
|
||||
} catch (ApiException $e) {
|
||||
$this->unWindGatewayFees($payment_hash);
|
||||
|
||||
$data = [
|
||||
'status' => '',
|
||||
'error_type' => '',
|
||||
'error_code' => $e->getCode(),
|
||||
'param' => '',
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
|
||||
SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_MOLLIE, $this->client, $this->client->company);
|
||||
}
|
||||
}
|
||||
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'id' => ['required', 'starts_with:tr'],
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json($validator->errors(), 422);
|
||||
}
|
||||
|
||||
$this->init();
|
||||
|
||||
$codes = [
|
||||
'open' => Payment::STATUS_PENDING,
|
||||
'canceled' => Payment::STATUS_CANCELLED,
|
||||
'pending' => Payment::STATUS_PENDING,
|
||||
'expired' => Payment::STATUS_CANCELLED,
|
||||
'failed' => Payment::STATUS_FAILED,
|
||||
'paid' => Payment::STATUS_COMPLETED,
|
||||
];
|
||||
|
||||
try {
|
||||
$payment = $this->gateway->payments->get($request->id);
|
||||
|
||||
$record = Payment::where('transaction_reference', $payment->id)->firstOrFail();
|
||||
$record->status_id = $codes[$payment->status];
|
||||
$record->save();
|
||||
|
||||
return response()->json([], 200);
|
||||
} catch (ApiException $e) {
|
||||
return response()->json(['message' => $e->getMessage(), 'gatewayStatusCode' => $e->getCode()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function process3dsConfirmation(Mollie3dsRequest $request)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
$this->setPaymentHash($request->getPaymentHash());
|
||||
|
||||
try {
|
||||
$payment = $this->gateway->payments->get($request->getPaymentId());
|
||||
|
||||
return (new CreditCard($this))->processSuccessfulPayment($payment);
|
||||
} catch (\Mollie\Api\Exceptions\ApiException $e) {
|
||||
return (new CreditCard($this))->processUnsuccessfulPayment($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function detach(ClientGatewayToken $token)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
try {
|
||||
$this->gateway->mandates->revokeForId($token->gateway_customer_reference, $token->token);
|
||||
} catch (\Mollie\Api\Exceptions\ApiException $e) {
|
||||
SystemLogger::dispatch(
|
||||
[
|
||||
'server_response' => $e->getMessage(),
|
||||
'data' => request()->all(),
|
||||
],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
SystemLog::TYPE_MOLLIE,
|
||||
$this->client,
|
||||
$this->client->company
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the amount to the format that Mollie supports.
|
||||
*
|
||||
* @param mixed|float $amount
|
||||
* @return string
|
||||
*/
|
||||
public function convertToMollieAmount($amount): string
|
||||
{
|
||||
return \number_format((float) $amount, 2, '.', '');
|
||||
}
|
||||
}
|
258
app/PaymentDrivers/PayTrace/CreditCard.php
Normal file
258
app/PaymentDrivers/PayTrace/CreditCard.php
Normal file
@ -0,0 +1,258 @@
|
||||
<?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\PayTrace;
|
||||
|
||||
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\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\PayFastPaymentDriver;
|
||||
use App\PaymentDrivers\PaytracePaymentDriver;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreditCard
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $paytrace;
|
||||
|
||||
public function __construct(PaytracePaymentDriver $paytrace)
|
||||
{
|
||||
$this->paytrace = $paytrace;
|
||||
}
|
||||
|
||||
public function authorizeView($data)
|
||||
{
|
||||
|
||||
$data['client_key'] = $this->paytrace->getAuthToken();
|
||||
$data['gateway'] = $this->paytrace;
|
||||
|
||||
return render('gateways.paytrace.authorize', $data);
|
||||
}
|
||||
|
||||
// +"success": true
|
||||
// +"response_code": 160
|
||||
// +"status_message": "The customer profile for PLS5U60OoLUfQXzcmtJYNefPA0gTthzT/11 was successfully created."
|
||||
// +"customer_id": "PLS5U60OoLUfQXzcmtJYNefPA0gTthzT"
|
||||
|
||||
//if(!$response->success)
|
||||
//handle failure
|
||||
|
||||
public function authorizeResponse($request)
|
||||
{
|
||||
$data = $request->all();
|
||||
|
||||
$response = $this->createCustomer($data);
|
||||
|
||||
return redirect()->route('client.payment_methods.index');
|
||||
|
||||
}
|
||||
|
||||
// "_token" => "Vl1xHflBYQt9YFSaNCPTJKlY5x3rwcFE9kvkw71I"
|
||||
// "company_gateway_id" => "1"
|
||||
// "HPF_Token" => "e484a92c-90ed-4468-ac4d-da66824c75de"
|
||||
// "enc_key" => "zqz6HMHCXALWdX5hyBqrIbSwU7TBZ0FTjjLB3Cp0FQY="
|
||||
// "amount" => "Amount"
|
||||
// "q" => "/client/payment_methods"
|
||||
// "method" => "1"
|
||||
// ]
|
||||
|
||||
// "customer_id":"customer789",
|
||||
// "hpf_token":"e369847e-3027-4174-9161-fa0d4e98d318",
|
||||
// "enc_key":"lI785yOBMet4Rt9o4NLXEyV84WBU3tdStExcsfoaOoo=",
|
||||
// "integrator_id":"xxxxxxxxxx",
|
||||
// "billing_address":{
|
||||
// "name":"Mark Smith",
|
||||
// "street_address":"8320 E. West St.",
|
||||
// "city":"Spokane",
|
||||
// "state":"WA",
|
||||
// "zip":"85284"
|
||||
// }
|
||||
|
||||
private function createCustomer($data)
|
||||
{
|
||||
$post_data = [
|
||||
'customer_id' => Str::random(32),
|
||||
'hpf_token' => $data['HPF_Token'],
|
||||
'enc_key' => $data['enc_key'],
|
||||
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
|
||||
'billing_address' => $this->buildBillingAddress(),
|
||||
];
|
||||
|
||||
$response = $this->paytrace->gatewayRequest('/v1/customer/pt_protect_create', $post_data);
|
||||
|
||||
$cgt = [];
|
||||
$cgt['token'] = $response->customer_id;
|
||||
$cgt['payment_method_id'] = GatewayType::CREDIT_CARD;
|
||||
|
||||
$profile = $this->getCustomerProfile($response->customer_id);
|
||||
|
||||
$payment_meta = new \stdClass;
|
||||
$payment_meta->exp_month = $profile->credit_card->expiration_month;
|
||||
$payment_meta->exp_year = $profile->credit_card->expiration_year;
|
||||
$payment_meta->brand = 'CC';
|
||||
$payment_meta->last4 = $profile->credit_card->masked_number;
|
||||
$payment_meta->type = GatewayType::CREDIT_CARD;
|
||||
|
||||
$cgt['payment_meta'] = $payment_meta;
|
||||
|
||||
$token = $this->paytrace->storeGatewayToken($cgt, []);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function getCustomerProfile($customer_id)
|
||||
{
|
||||
$profile = $this->paytrace->gatewayRequest('/v1/customer/export', [
|
||||
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
|
||||
'customer_id' => $customer_id,
|
||||
// 'include_bin' => true,
|
||||
]);
|
||||
|
||||
return $profile->customers[0];
|
||||
|
||||
}
|
||||
|
||||
private function buildBillingAddress()
|
||||
{
|
||||
return [
|
||||
'name' => $this->paytrace->client->present()->name(),
|
||||
'street_address' => $this->paytrace->client->address1,
|
||||
'city' => $this->paytrace->client->city,
|
||||
'state' => $this->paytrace->client->state,
|
||||
'zip' => $this->paytrace->client->postal_code
|
||||
];
|
||||
}
|
||||
|
||||
public function paymentView($data)
|
||||
{
|
||||
|
||||
$data['client_key'] = $this->paytrace->getAuthToken();
|
||||
$data['gateway'] = $this->paytrace;
|
||||
|
||||
return render('gateways.paytrace.pay', $data);
|
||||
|
||||
}
|
||||
|
||||
public function paymentResponse(Request $request)
|
||||
{
|
||||
$response_array = $request->all();
|
||||
|
||||
if($request->token){
|
||||
$token = ClientGatewayToken::find($this->decodePrimaryKey($request->token));
|
||||
return $this->processTokenPayment($token->token, $request);
|
||||
}
|
||||
|
||||
if ($request->has('store_card') && $request->input('store_card') === true) {
|
||||
|
||||
$response = $this->createCustomer($request->all());
|
||||
|
||||
return $this->processTokenPayment($response->customer_id, $request);
|
||||
}
|
||||
|
||||
//process a regular charge here:
|
||||
$data = [
|
||||
'hpf_token' => $response_array['HPF_Token'],
|
||||
'enc_key' => $response_array['enc_key'],
|
||||
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
|
||||
'billing_address' => $this->buildBillingAddress(),
|
||||
'amount' => $request->input('amount_with_fee'),
|
||||
'invoice_id' => $this->harvestInvoiceId(),
|
||||
];
|
||||
|
||||
$response = $this->paytrace->gatewayRequest('/v1/transactions/sale/pt_protect', $data);
|
||||
|
||||
if($response->success)
|
||||
return $this->processSuccessfulPayment($response);
|
||||
|
||||
return $this->processUnsuccessfulPayment($response);
|
||||
|
||||
}
|
||||
|
||||
public function processTokenPayment($token, $request)
|
||||
{
|
||||
|
||||
$data = [
|
||||
'customer_id' => $token,
|
||||
'integrator_id' => $this->paytrace->company_gateway->getConfigField('integratorId'),
|
||||
'amount' => $request->input('amount_with_fee'),
|
||||
];
|
||||
|
||||
$response = $this->paytrace->gatewayRequest('/v1/transactions/sale/by_customer', $data);
|
||||
|
||||
if($response->success){
|
||||
$this->paytrace->logSuccessfulGatewayResponse(['response' => $response, 'data' => $this->paytrace->payment_hash], SystemLog::TYPE_PAYTRACE);
|
||||
|
||||
return $this->processSuccessfulPayment($response);
|
||||
}
|
||||
|
||||
return $this->processUnsuccessfulPayment($response);
|
||||
}
|
||||
|
||||
private function harvestInvoiceId()
|
||||
{
|
||||
$_invoice = collect($this->paytrace->payment_hash->data->invoices)->first();
|
||||
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
|
||||
|
||||
if($invoice)
|
||||
return ctrans('texts.invoice_number') . "# " . $invoice->number;
|
||||
|
||||
return ctrans('texts.invoice_number') . "####";
|
||||
}
|
||||
|
||||
private function processSuccessfulPayment($response)
|
||||
{
|
||||
$amount = array_sum(array_column($this->paytrace->payment_hash->invoices(), 'amount')) + $this->paytrace->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->paytrace->createPayment($payment_record, Payment::STATUS_COMPLETED);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||
|
||||
}
|
||||
|
||||
private function processUnsuccessfulPayment($response)
|
||||
{
|
||||
|
||||
$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->paytrace->processUnsuccessfulTransaction($data);
|
||||
|
||||
}
|
||||
|
||||
}
|
234
app/PaymentDrivers/PaytracePaymentDriver.php
Normal file
234
app/PaymentDrivers/PaytracePaymentDriver.php
Normal file
@ -0,0 +1,234 @@
|
||||
<?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\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\PayTrace\CreditCard;
|
||||
use App\Utils\CurlUtils;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class PaytracePaymentDriver extends BaseDriver
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $refundable = true;
|
||||
|
||||
public $token_billing = true;
|
||||
|
||||
public $can_authorise_credit_card = true;
|
||||
|
||||
public $gateway;
|
||||
|
||||
public $payment_method;
|
||||
|
||||
public static $methods = [
|
||||
GatewayType::CREDIT_CARD => CreditCard::class, //maps GatewayType => Implementation class
|
||||
];
|
||||
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYTRACE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model
|
||||
|
||||
public function init()
|
||||
{
|
||||
return $this; /* This is where you boot the gateway with your auth credentials*/
|
||||
}
|
||||
|
||||
/* 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); //this is your custom implementation from here
|
||||
}
|
||||
|
||||
public function authorizeResponse($request)
|
||||
{
|
||||
return $this->payment_method->authorizeResponse($request); //this is your custom implementation from here
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
public function refund(Payment $payment, $amount, $return_client_response = false)
|
||||
{
|
||||
// $cgt = ClientGatewayToken::where('company_gateway_id', $payment->company_gateway_id)
|
||||
// ->where('gateway_type_id', $payment->gateway_type_id)
|
||||
// ->first();
|
||||
|
||||
$data = [
|
||||
'amount' => $amount,
|
||||
//'customer_id' => $cgt->token,
|
||||
'transaction_id' => $payment->transaction_reference,
|
||||
'integrator_id' => '959195xd1CuC'
|
||||
];
|
||||
|
||||
$response = $this->gatewayRequest('/v1/transactions/refund/for_transaction', $data);
|
||||
|
||||
if($response && $response->success)
|
||||
{
|
||||
|
||||
SystemLogger::dispatch(['server_response' => $response, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_PAYTRACE, $this->client, $this->client->company);
|
||||
|
||||
return [
|
||||
'transaction_reference' => $response->transaction_id,
|
||||
'transaction_response' => json_encode($response),
|
||||
'success' => true,
|
||||
'description' => $response->status_message,
|
||||
'code' => $response->response_code,
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
SystemLogger::dispatch(['server_response' => $response, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_PAYTRACE, $this->client, $this->client->company);
|
||||
|
||||
return [
|
||||
'transaction_reference' => null,
|
||||
'transaction_response' => json_encode($response),
|
||||
'success' => false,
|
||||
'description' => $response->status_message,
|
||||
'code' => 422,
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
{
|
||||
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
|
||||
|
||||
$data = [
|
||||
'customer_id' => $cgt->token,
|
||||
'integrator_id' => $this->company_gateway->getConfigField('integratorId'),
|
||||
'amount' => $amount,
|
||||
];
|
||||
|
||||
$response = $this->gatewayRequest('/v1/transactions/sale/by_customer', $data);
|
||||
|
||||
if($response && $response->success)
|
||||
{
|
||||
$data = [
|
||||
'gateway_type_id' => $cgt->gateway_type_id,
|
||||
'payment_type' => PaymentType::CREDIT_CARD_OTHER,
|
||||
'transaction_reference' => $response->transaction_id,
|
||||
'amount' => $amount,
|
||||
];
|
||||
|
||||
$payment = $this->createPayment($data);
|
||||
$payment->meta = $cgt->meta;
|
||||
$payment->save();
|
||||
|
||||
$payment_hash->payment_id = $payment->id;
|
||||
$payment_hash->save();
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
||||
$error = $response->status_message;
|
||||
|
||||
if(property_exists($response, 'approval_message') && $response->approval_message)
|
||||
$error .= " - {$response->approval_message}";
|
||||
|
||||
$data = [
|
||||
'response' => $response,
|
||||
'error' => $error,
|
||||
'error_code' => 500,
|
||||
];
|
||||
|
||||
$this->processUnsuccessfulTransaction($data, false);
|
||||
}
|
||||
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null)
|
||||
{
|
||||
}
|
||||
|
||||
/*Helpers*/
|
||||
private function generateAuthHeaders()
|
||||
{
|
||||
|
||||
$url = 'https://api.paytrace.com/oauth/token';
|
||||
$data = [
|
||||
'grant_type' => 'password',
|
||||
'username' => $this->company_gateway->getConfigField('username'),
|
||||
'password' => $this->company_gateway->getConfigField('password')
|
||||
];
|
||||
|
||||
$response = CurlUtils::post($url, $data, $headers = false);
|
||||
|
||||
$auth_data = json_decode($response);
|
||||
|
||||
$headers = [];
|
||||
$headers[] = 'Content-type: application/json';
|
||||
$headers[] = 'Authorization: Bearer '.$auth_data->access_token;
|
||||
|
||||
return $headers;
|
||||
|
||||
}
|
||||
|
||||
public function getAuthToken()
|
||||
{
|
||||
|
||||
$headers = $this->generateAuthHeaders();
|
||||
|
||||
$response = CurlUtils::post('https://api.paytrace.com/v1/payment_fields/token/create', [], $headers);
|
||||
|
||||
$response = json_decode($response);
|
||||
|
||||
if($response)
|
||||
return $response->clientKey;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function gatewayRequest($uri, $data, $headers = false)
|
||||
{
|
||||
|
||||
$base_url = "https://api.paytrace.com{$uri}";
|
||||
|
||||
$headers = $this->generateAuthHeaders();
|
||||
|
||||
$response = CurlUtils::post($base_url, json_encode($data), $headers);
|
||||
|
||||
$response = json_decode($response);
|
||||
|
||||
if($response)
|
||||
return $response;
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
@ -68,8 +68,15 @@ class ACH
|
||||
|
||||
$client_gateway_token = $this->storePaymentMethod($source, $request->input('method'), $customer);
|
||||
|
||||
$verification = route('client.payment_methods.verification', ['payment_method' => $client_gateway_token->hashed_id, 'method' => GatewayType::BANK_TRANSFER], false);
|
||||
|
||||
$mailer = new NinjaMailerObject();
|
||||
$mailer->mailable = new ACHVerificationNotification(auth('contact')->user()->client->company, route('client.payment_methods.verification', ['payment_method' => $client_gateway_token->hashed_id, 'method' => GatewayType::BANK_TRANSFER]));
|
||||
|
||||
$mailer->mailable = new ACHVerificationNotification(
|
||||
auth('contact')->user()->client->company,
|
||||
route('client.contact_login', ['contact_key' => auth('contact')->user()->contact_key, 'next' => $verification])
|
||||
);
|
||||
|
||||
$mailer->company = auth('contact')->user()->client->company;
|
||||
$mailer->settings = auth('contact')->user()->client->company->settings;
|
||||
$mailer->to_user = auth('contact')->user();
|
||||
@ -250,9 +257,10 @@ class ACH
|
||||
{
|
||||
try {
|
||||
$payment_meta = new \stdClass;
|
||||
$payment_meta->brand = (string)sprintf('%s (%s)', $method->bank_name, ctrans('texts.ach'));
|
||||
$payment_meta->last4 = (string)$method->last4;
|
||||
$payment_meta->brand = (string) \sprintf('%s (%s)', $method->bank_name, ctrans('texts.ach'));
|
||||
$payment_meta->last4 = (string) $method->last4;
|
||||
$payment_meta->type = GatewayType::BANK_TRANSFER;
|
||||
$payment_meta->state = 'unauthorized';
|
||||
|
||||
$data = [
|
||||
'payment_meta' => $payment_meta,
|
||||
|
@ -74,7 +74,7 @@ class Charge
|
||||
'confirm' => true,
|
||||
'description' => $description,
|
||||
];
|
||||
nlog($data);
|
||||
|
||||
$response = $this->stripe->createPaymentIntent($data, $this->stripe->stripe_connect_auth);
|
||||
// $response = $local_stripe->paymentIntents->create($data);
|
||||
|
||||
|
@ -195,8 +195,9 @@ class StripePaymentDriver extends BaseDriver
|
||||
$fields[] = ['name' => 'client_country_id', 'label' => ctrans('texts.country'), 'type' => 'text', 'validation' => 'required'];
|
||||
}
|
||||
|
||||
if($this->company_gateway->require_postal_code)
|
||||
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'];
|
||||
@ -387,21 +388,23 @@ class StripePaymentDriver extends BaseDriver
|
||||
return $this->payment_method->processVerification($request, $payment_method);
|
||||
}
|
||||
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment)
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request)
|
||||
{
|
||||
if ($request->type == 'source.chargeable') {
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
if ($request->type === 'charge.succeeded' || $request->type === 'source.chargeable') {
|
||||
foreach ($request->data as $transaction) {
|
||||
$payment = Payment::query()
|
||||
->where('transaction_reference', $transaction['id'])
|
||||
->where('company_id', $request->getCompany()->id)
|
||||
->first();
|
||||
|
||||
if ($payment) {
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->type == 'charge.succeeded') {
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
}
|
||||
|
||||
// charge.failed, charge.refunded
|
||||
|
||||
return response([], 200);
|
||||
return response()->json([], 200);
|
||||
}
|
||||
|
||||
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
|
||||
|
@ -168,6 +168,8 @@ class WePayPaymentDriver extends BaseDriver
|
||||
|
||||
$input = $request->all();
|
||||
|
||||
$config = $this->company_gateway->getConfig();
|
||||
|
||||
$accountId = $this->company_gateway->getConfigField('accountId');
|
||||
|
||||
foreach (array_keys($input) as $key) {
|
||||
|
@ -153,6 +153,7 @@ class ActivityRepository extends BaseRepository
|
||||
'all_pages_header' => $entity->client->getSetting('all_pages_header'),
|
||||
'all_pages_footer' => $entity->client->getSetting('all_pages_footer'),
|
||||
],
|
||||
'process_markdown' => $entity->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMakerService($state);
|
||||
|
@ -126,7 +126,7 @@ class ApplyPayment extends AbstractService
|
||||
|
||||
});
|
||||
|
||||
$this->invoice->service()->applyNumber()->save();
|
||||
$this->invoice->service()->applyNumber()->workFlow()->save();
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
128
app/Services/Invoice/ApplyPaymentAmount.php
Normal file
128
app/Services/Invoice/ApplyPaymentAmount.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?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\Services\Invoice;
|
||||
|
||||
use App\Events\Invoice\InvoiceWasPaid;
|
||||
use App\Events\Payment\PaymentWasCreated;
|
||||
use App\Factory\PaymentFactory;
|
||||
use App\Jobs\Invoice\InvoiceWorkflowSettings;
|
||||
use App\Jobs\Payment\EmailPayment;
|
||||
use App\Libraries\Currency\Conversion\CurrencyApi;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Services\AbstractService;
|
||||
use App\Services\Client\ClientService;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class ApplyPaymentAmount extends AbstractService
|
||||
{
|
||||
use GeneratesCounter;
|
||||
|
||||
private $invoice;
|
||||
|
||||
private $amount;
|
||||
|
||||
public function __construct(Invoice $invoice, $amount)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
$this->amount = $amount;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
if ($this->invoice->status_id == Invoice::STATUS_DRAFT) {
|
||||
$this->invoice->service()->markSent();
|
||||
}
|
||||
|
||||
/*Don't double pay*/
|
||||
if ($this->invoice->statud_id == Invoice::STATUS_PAID) {
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
if($this->amount == 0)
|
||||
return $this->invoice;
|
||||
|
||||
/* Create Payment */
|
||||
$payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id);
|
||||
|
||||
$payment->amount = $this->amount;
|
||||
$payment->applied = min($this->amount, $this->invoice->balance);
|
||||
$payment->number = $this->getNextPaymentNumber($this->invoice->client);
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->client_id = $this->invoice->client_id;
|
||||
$payment->transaction_reference = ctrans('texts.manual_entry');
|
||||
$payment->currency_id = $this->invoice->client->getSetting('currency_id');
|
||||
$payment->is_manual = true;
|
||||
/* Create a payment relationship to the invoice entity */
|
||||
$payment->save();
|
||||
|
||||
$this->setExchangeRate($payment);
|
||||
|
||||
$payment->invoices()->attach($this->invoice->id, [
|
||||
'amount' => $payment->amount,
|
||||
]);
|
||||
|
||||
$this->invoice->next_send_date = null;
|
||||
|
||||
$this->invoice->service()
|
||||
->setExchangeRate()
|
||||
->updateBalance($payment->amount * -1)
|
||||
->updatePaidToDate($payment->amount)
|
||||
->setCalculatedStatus()
|
||||
->applyNumber()
|
||||
->deletePdf()
|
||||
->save();
|
||||
|
||||
if ($this->invoice->client->getSetting('client_manual_payment_notification'))
|
||||
$payment->service()->sendEmail();
|
||||
|
||||
/* Update Invoice balance */
|
||||
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
event(new InvoiceWasPaid($this->invoice, $payment, $payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
$payment->ledger()
|
||||
->updatePaymentBalance($payment->amount * -1);
|
||||
|
||||
$this->invoice
|
||||
->client
|
||||
->service()
|
||||
->updateBalance($payment->amount * -1)
|
||||
->updatePaidToDate($payment->amount)
|
||||
->save();
|
||||
|
||||
$this->invoice->service()->workFlow()->save();
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
private function setExchangeRate(Payment $payment)
|
||||
{
|
||||
|
||||
$client_currency = $payment->client->getSetting('currency_id');
|
||||
$company_currency = $payment->client->company->settings->currency_id;
|
||||
|
||||
if ($company_currency != $client_currency) {
|
||||
|
||||
$exchange_rate = new CurrencyApi();
|
||||
|
||||
$payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date));
|
||||
//$payment->exchange_currency_id = $client_currency; // 23/06/2021
|
||||
$payment->exchange_currency_id = $company_currency;
|
||||
|
||||
$payment->save();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -85,6 +85,7 @@ class GenerateDeliveryNote
|
||||
'contact' => $this->contact,
|
||||
], 'delivery_note'),
|
||||
'variables' => $html->generateLabelsAndValues(),
|
||||
'process_markdown' => $this->invoice->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMakerService($state);
|
||||
|
@ -20,7 +20,9 @@ use App\Models\Expense;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Task;
|
||||
use App\Repositories\BaseRepository;
|
||||
use App\Services\Client\ClientService;
|
||||
use App\Services\Invoice\ApplyPaymentAmount;
|
||||
use App\Services\Invoice\UpdateReminder;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
@ -50,6 +52,13 @@ class InvoiceService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function applyPaymentAmount($amount)
|
||||
{
|
||||
$this->invoice = (new ApplyPaymentAmount($this->invoice, $amount))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the invoice number.
|
||||
* @return $this InvoiceService object
|
||||
@ -271,9 +280,8 @@ class InvoiceService
|
||||
{
|
||||
if ((int)$this->invoice->balance == 0) {
|
||||
|
||||
InvoiceWorkflowSettings::dispatchNow($this->invoice);
|
||||
|
||||
$this->setStatus(Invoice::STATUS_PAID);
|
||||
$this->setStatus(Invoice::STATUS_PAID)->workFlow();
|
||||
// InvoiceWorkflowSettings::dispatchNow($this->invoice);
|
||||
}
|
||||
|
||||
if ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) {
|
||||
@ -449,6 +457,18 @@ class InvoiceService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function workFlow()
|
||||
{
|
||||
|
||||
if ($this->invoice->status_id == Invoice::STATUS_PAID && $this->invoice->client->getSetting('auto_archive_invoice')) {
|
||||
/* Throws: Payment amount xxx does not match invoice totals. */
|
||||
$base_repository = new BaseRepository();
|
||||
$base_repository->archive($this->invoice);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the invoice.
|
||||
* @return Invoice object
|
||||
|
@ -50,7 +50,8 @@ class MarkInvoiceDeleted extends AbstractService
|
||||
|
||||
private function adjustLedger()
|
||||
{
|
||||
$this->invoice->ledger()->updatePaymentBalance($this->adjustment_amount * -1, 'Invoice Deleted - reducing ledger balance'); //reduces the payment balance by payment totals
|
||||
// $this->invoice->ledger()->updatePaymentBalance($this->adjustment_amount * -1, 'Invoice Deleted - reducing ledger balance'); //reduces the payment balance by payment totals
|
||||
$this->invoice->ledger()->updatePaymentBalance($this->invoice->balance * -1, 'Invoice Deleted - reducing ledger balance'); //reduces the payment balance by payment totals
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -95,7 +95,8 @@ class MarkPaid extends AbstractService
|
||||
->updatePaidToDate($payment->amount)
|
||||
->save();
|
||||
|
||||
InvoiceWorkflowSettings::dispatchNow($this->invoice);
|
||||
$this->invoice->service()->workFlow()->save();
|
||||
// InvoiceWorkflowSettings::dispatchNow($this->invoice);
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
@ -44,6 +44,10 @@ class TriggeredActions extends AbstractService
|
||||
$this->invoice = $this->invoice->service()->markPaid()->save();
|
||||
}
|
||||
|
||||
if ($this->request->has('amount_paid') && is_numeric($this->request->input('amount_paid')) ) {
|
||||
$this->invoice = $this->invoice->service()->applyPaymentAmount($this->request->input('amount_paid'))->save();
|
||||
}
|
||||
|
||||
if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') {
|
||||
$this->sendEmail();
|
||||
}
|
||||
@ -52,6 +56,7 @@ class TriggeredActions extends AbstractService
|
||||
$this->invoice = $this->invoice->service()->markSent()->save();
|
||||
}
|
||||
|
||||
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
|
@ -232,7 +232,7 @@ class RefundPayment
|
||||
|
||||
if (isset($this->refund_data['invoices']) && count($this->refund_data['invoices']) > 0) {
|
||||
foreach ($this->refund_data['invoices'] as $refunded_invoice) {
|
||||
$invoice = Invoice::find($refunded_invoice['invoice_id']);
|
||||
$invoice = Invoice::withTrashed()->find($refunded_invoice['invoice_id']);
|
||||
|
||||
$invoice->service()->updateBalance($refunded_invoice['amount'])->save();
|
||||
$invoice->ledger()->updateInvoiceBalance($refunded_invoice['amount'], "Refund of payment # {$this->payment->number}")->save();
|
||||
|
@ -85,8 +85,6 @@ class UpdateInvoicePayment
|
||||
->deletePdf()
|
||||
->save();
|
||||
|
||||
InvoiceWorkflowSettings::dispatchNow($invoice);
|
||||
|
||||
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
});
|
||||
|
||||
|
@ -348,7 +348,7 @@ class Design extends BaseDesign
|
||||
|
||||
$items = $this->transformLineItems($this->entity->line_items, $type);
|
||||
|
||||
$this->processMarkdownOnLineItems($items);
|
||||
$this->processNewLines($items);
|
||||
|
||||
if (count($items) == 0) {
|
||||
return [];
|
||||
|
@ -331,7 +331,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
return $converter->convertToHtml($markdown);
|
||||
}
|
||||
|
||||
public function processMarkdownOnLineItems(array &$items)
|
||||
public function processMarkdownOnLineItems(array &$items): void
|
||||
{
|
||||
foreach ($items as $key => $item) {
|
||||
foreach ($item as $variable => $value) {
|
||||
@ -341,4 +341,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
$items[$key] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function processNewLines(array &$items): void
|
||||
{
|
||||
foreach ($items as $key => $item) {
|
||||
foreach ($item as $variable => $value) {
|
||||
$item[$variable] = nl2br($value);
|
||||
}
|
||||
|
||||
$items[$key] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,11 +92,11 @@ trait PdfMakerUtilities
|
||||
$contains_html = false;
|
||||
|
||||
if ($child['element'] !== 'script') {
|
||||
$child['content'] = $this->commonmark->convertToHtml($child['content'] ?? '');
|
||||
if (array_key_exists('process_markdown', $this->data) && $this->data['process_markdown']) {
|
||||
$child['content'] = $this->commonmark->convertToHtml($child['content'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
// $child['content'] = array_key_exists('content', $child) ? nl2br($child['content']) : '';
|
||||
|
||||
if (isset($child['content'])) {
|
||||
if (isset($child['is_empty']) && $child['is_empty'] === true) {
|
||||
continue;
|
||||
|
@ -15,10 +15,14 @@ namespace App\Services\Quote;
|
||||
use App\Factory\CloneQuoteToInvoiceFactory;
|
||||
use App\Models\Quote;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class ConvertQuote
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
private $client;
|
||||
|
||||
private $invoice_repo;
|
||||
|
||||
public function __construct($client)
|
||||
@ -34,7 +38,7 @@ class ConvertQuote
|
||||
public function run($quote)
|
||||
{
|
||||
$invoice = CloneQuoteToInvoiceFactory::create($quote, $quote->user_id);
|
||||
$invoice->design_id = $this->client->getSetting('invoice_design_id');
|
||||
$invoice->design_id = $this->decodePrimaryKey($this->client->getSetting('invoice_design_id'));
|
||||
$invoice = $this->invoice_repo->save([], $invoice);
|
||||
|
||||
$invoice->fresh();
|
||||
|
@ -49,16 +49,15 @@ class RecurringService
|
||||
|
||||
public function start()
|
||||
{
|
||||
//make sure next_send_date is either now or in the future else return.
|
||||
// if(Carbon::parse($this->recurring_entity->next_send_date)->lt(now()))
|
||||
// return $this;
|
||||
|
||||
if ($this->recurring_entity->remaining_cycles == 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->createInvitations()->setStatus(RecurringInvoice::STATUS_ACTIVE);
|
||||
// $this->createInvitations()->setStatus(RecurringInvoice::STATUS_ACTIVE);
|
||||
|
||||
$this->setStatus(RecurringInvoice::STATUS_ACTIVE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -777,7 +777,15 @@ class SubscriptionService
|
||||
*/
|
||||
public function products()
|
||||
{
|
||||
return Product::whereIn('id', $this->transformKeys(explode(",", $this->subscription->product_ids)))->get();
|
||||
if(!$this->subscription->product_ids)
|
||||
return collect();
|
||||
|
||||
$keys = $this->transformKeys(explode(",", $this->subscription->product_ids));
|
||||
|
||||
if(is_array($keys))
|
||||
return Product::whereIn('id', $keys)->get();
|
||||
else
|
||||
return Product::where('id', $keys)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -788,7 +796,18 @@ class SubscriptionService
|
||||
*/
|
||||
public function recurring_products()
|
||||
{
|
||||
return Product::whereIn('id', $this->transformKeys(explode(",", $this->subscription->recurring_product_ids)))->get();
|
||||
if(!$this->subscription->recurring_product_ids)
|
||||
return collect();
|
||||
|
||||
$keys = $this->transformKeys(explode(",", $this->subscription->recurring_product_ids));
|
||||
|
||||
if(is_array($keys)){
|
||||
return Product::whereIn('id', $keys)->get();
|
||||
}
|
||||
else{
|
||||
return Product::where('id', $keys)->get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -799,10 +818,10 @@ class SubscriptionService
|
||||
public function getPlans()
|
||||
{
|
||||
return Subscription::query()
|
||||
->where('company_id', $this->subscription->company_id)
|
||||
->where('group_id', $this->subscription->group_id)
|
||||
->where('id', '!=', $this->subscription->id)
|
||||
->get();
|
||||
->where('company_id', $this->subscription->company_id)
|
||||
->where('group_id', $this->subscription->group_id)
|
||||
->where('id', '!=', $this->subscription->id)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,6 +80,8 @@ class AccountTransformer extends EntityTransformer
|
||||
'is_scheduler_running' => (bool) $account->is_scheduler_running,
|
||||
'default_company_id' => (string) $this->encodePrimaryKey($account->default_company_id),
|
||||
'disable_auto_update' => (bool) config('ninja.disable_auto_update'),
|
||||
'emails_sent' => (int) $account->emailsSent(),
|
||||
'email_quota' => (int) $account->getDailyEmailLimit(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -159,6 +159,7 @@ class CompanyTransformer extends EntityTransformer
|
||||
'default_password_timeout' => (int) $company->default_password_timeout,
|
||||
'invoice_task_datelog' => (bool) $company->invoice_task_datelog,
|
||||
'show_task_end_date' => (bool) $company->show_task_end_date,
|
||||
'markdown_enabled' => (bool) $company->markdown_enabled,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -168,7 +168,12 @@ class HtmlEngine
|
||||
$data['$invoice.discount'] = ['value' => Number::formatMoney($this->entity_calc->getTotalDiscount(), $this->client) ?: ' ', 'label' => ctrans('texts.discount')];
|
||||
$data['$discount'] = &$data['$invoice.discount'];
|
||||
$data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.subtotal')];
|
||||
$data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes), $this->client) ?: ' ', 'label' => ctrans('texts.net_subtotal')];
|
||||
|
||||
if($this->entity->uses_inclusive_taxes)
|
||||
$data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes), $this->client) ?: ' ', 'label' => ctrans('texts.net_subtotal')];
|
||||
else
|
||||
$data['$net_subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->client) ?: ' ', 'label' => ctrans('texts.net_subtotal')];
|
||||
|
||||
$data['$invoice.subtotal'] = &$data['$subtotal'];
|
||||
|
||||
if ($this->entity->partial > 0) {
|
||||
@ -274,6 +279,7 @@ class HtmlEngine
|
||||
$data['$client_address'] = ['value' => $this->client->present()->address() ?: ' ', 'label' => ctrans('texts.address')];
|
||||
$data['$client.address'] = &$data['$client_address'];
|
||||
$data['$client.postal_code'] = ['value' => $this->client->postal_code ?: ' ', 'label' => ctrans('texts.postal_code')];
|
||||
$data['$client.city'] = ['value' => $this->client->city ?: ' ', 'label' => ctrans('texts.city')];
|
||||
$data['$client.id_number'] = &$data['$id_number'];
|
||||
$data['$client.vat_number'] = &$data['$vat_number'];
|
||||
$data['$client.website'] = &$data['$website'];
|
||||
|
@ -193,6 +193,7 @@ class Phantom
|
||||
'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'),
|
||||
'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'),
|
||||
],
|
||||
'process_markdown' => $entity_obj->client->company->markdown_enabled,
|
||||
];
|
||||
|
||||
$maker = new PdfMakerService($state);
|
||||
|
@ -358,6 +358,24 @@ trait MakesInvoiceValues
|
||||
':MONTH' => Carbon::createFromDate(now()->year, now()->month)->translatedFormat('F'),
|
||||
':YEAR' => now()->year,
|
||||
':QUARTER' => 'Q' . now()->quarter,
|
||||
':WEEK_BEFORE' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->subDays(7)->translatedFormat($this->client->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->translatedFormat($this->client->date_format())
|
||||
),
|
||||
':WEEK_AHEAD' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->addDays(7)->translatedFormat($this->client->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addDays(14)->translatedFormat($this->client->date_format())
|
||||
),
|
||||
':WEEK' => \sprintf(
|
||||
'%s %s %s',
|
||||
Carbon::now()->translatedFormat($this->client->date_format()),
|
||||
ctrans('texts.to'),
|
||||
Carbon::now()->addDays(7)->translatedFormat($this->client->date_format())
|
||||
),
|
||||
],
|
||||
'raw' => [
|
||||
':MONTH' => now()->month,
|
||||
|
@ -62,6 +62,7 @@
|
||||
"league/omnipay": "^3.1",
|
||||
"livewire/livewire": "^2.4",
|
||||
"maennchen/zipstream-php": "^1.2",
|
||||
"mollie/mollie-api-php": "^2.36",
|
||||
"nwidart/laravel-modules": "^8.0",
|
||||
"omnipay/paypal": "^3.0",
|
||||
"payfast/payfast-php-sdk": "^1.1",
|
||||
|
93
composer.lock
generated
93
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "d2beb37ff5fbee59ad4bb792e944eb10",
|
||||
"content-hash": "275a9dd3910b6ec79607b098406dc6c7",
|
||||
"packages": [
|
||||
{
|
||||
"name": "asm/php-ansible",
|
||||
@ -4386,6 +4386,97 @@
|
||||
},
|
||||
"time": "2019-07-17T11:01:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mollie/mollie-api-php",
|
||||
"version": "v2.36.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mollie/mollie-api-php.git",
|
||||
"reference": "19f69c116d47a3600f0ed629e0df925a43d3a8f5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/19f69c116d47a3600f0ed629e0df925a43d3a8f5",
|
||||
"reference": "19f69c116d47a3600f0ed629e0df925a43d3a8f5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer/ca-bundle": "^1.1",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-openssl": "*",
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"eloquent/liberator": "^2.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.0",
|
||||
"guzzlehttp/guzzle": "^6.3 || ^7.0",
|
||||
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.1 || ^8.5"
|
||||
},
|
||||
"suggest": {
|
||||
"mollie/oauth2-mollie-php": "Use OAuth to authenticate with the Mollie API. This is needed for some endpoints. Visit https://docs.mollie.com/ for more information."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Mollie\\Api\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mollie B.V.",
|
||||
"email": "info@mollie.com"
|
||||
}
|
||||
],
|
||||
"description": "Mollie API client library for PHP. Mollie is a European Payment Service provider and offers international payment methods such as Mastercard, VISA, American Express and PayPal, and local payment methods such as iDEAL, Bancontact, SOFORT Banking, SEPA direct debit, Belfius Direct Net, KBC Payment Button and various gift cards such as Podiumcadeaukaart and fashioncheque.",
|
||||
"homepage": "https://www.mollie.com/en/developers",
|
||||
"keywords": [
|
||||
"Apple Pay",
|
||||
"CBC",
|
||||
"Przelewy24",
|
||||
"api",
|
||||
"bancontact",
|
||||
"banktransfer",
|
||||
"belfius",
|
||||
"belfius direct net",
|
||||
"charges",
|
||||
"creditcard",
|
||||
"direct debit",
|
||||
"fashioncheque",
|
||||
"gateway",
|
||||
"gift cards",
|
||||
"ideal",
|
||||
"inghomepay",
|
||||
"intersolve",
|
||||
"kbc",
|
||||
"klarna",
|
||||
"mistercash",
|
||||
"mollie",
|
||||
"paylater",
|
||||
"payment",
|
||||
"payments",
|
||||
"paypal",
|
||||
"paysafecard",
|
||||
"podiumcadeaukaart",
|
||||
"recurring",
|
||||
"refunds",
|
||||
"sepa",
|
||||
"service",
|
||||
"sliceit",
|
||||
"sofort",
|
||||
"sofortbanking",
|
||||
"subscriptions"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/mollie/mollie-api-php/issues",
|
||||
"source": "https://github.com/mollie/mollie-api-php/tree/v2.36.1"
|
||||
},
|
||||
"time": "2021-06-23T12:55:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "moneyphp/money",
|
||||
"version": "v3.3.1",
|
||||
|
@ -46,7 +46,7 @@ return [
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'strict' => env('DB_STRICT', false),
|
||||
// 'engine' => 'InnoDB ROW_FORMAT=DYNAMIC',
|
||||
'engine' => 'InnoDB',
|
||||
],
|
||||
|
||||
'sqlite' => [
|
||||
|
@ -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.17',
|
||||
'app_tag' => '5.2.17',
|
||||
'app_version' => '5.2.18',
|
||||
'app_tag' => '5.2.18',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
@ -84,6 +84,12 @@ return [
|
||||
'test_email' => env('TEST_EMAIL', 'test@example.com'),
|
||||
'wepay' => env('WEPAY_KEYS', ''),
|
||||
'braintree' => env('BRAINTREE_KEYS', ''),
|
||||
'paytrace' => [
|
||||
'username' => env('PAYTRACE_U', ''),
|
||||
'password' => env('PAYTRACE_P',''),
|
||||
'decrypted' => env('PAYTRACE_KEYS', ''),
|
||||
],
|
||||
'mollie' => env('MOLLIE_KEYS', ''),
|
||||
],
|
||||
'contact' => [
|
||||
'email' => env('MAIL_FROM_ADDRESS'),
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Gateway;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ActivatePaytracePaymentDriver extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
|
||||
if($paytrace = Gateway::find(46))
|
||||
{
|
||||
$fields = json_decode($paytrace->fields);
|
||||
$fields->integratorId = "";
|
||||
|
||||
$paytrace->fields = json_encode($fields);
|
||||
$paytrace->provider = 'Paytrace';
|
||||
$paytrace->visible = true;
|
||||
$paytrace->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?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
|
||||
*/
|
||||
|
||||
use App\Models\Gateway;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ActivateMolliePaymentDriver extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if($mollie = Gateway::find(7))
|
||||
{
|
||||
$mollie->visible = true;
|
||||
|
||||
$fields = json_decode($mollie->fields);
|
||||
$fields->testMode = false;
|
||||
$fields->profileId = '';
|
||||
|
||||
$mollie->fields = json_encode($fields);
|
||||
|
||||
$mollie->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddMarkdownEnabledColumnToCompaniesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->boolean('markdown_enabled')->default(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
55
database/migrations/2021_08_10_034407_add_more_languages.php
Normal file
55
database/migrations/2021_08_10_034407_add_more_languages.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Language;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddMoreLanguages extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Language::unguard();
|
||||
|
||||
$language = Language::find(30);
|
||||
|
||||
if(!$language){
|
||||
|
||||
Language::create(['id' => 30, 'name' => 'Arabic', 'locale' => 'ar']);
|
||||
|
||||
}
|
||||
|
||||
$language = Language::find(31);
|
||||
|
||||
if(!$language){
|
||||
|
||||
Language::create(['id' => 31, 'name' => 'Persian', 'locale' => 'fa']);
|
||||
|
||||
}
|
||||
|
||||
$language = Language::find(32);
|
||||
|
||||
if(!$language){
|
||||
|
||||
Language::create(['id' => 32, 'name' => 'Latvian', 'locale' => 'lv_LV']);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -52,6 +52,10 @@ class LanguageSeeder extends Seeder
|
||||
['id' => 26, 'name' => 'Thai', 'locale' => 'th'],
|
||||
['id' => 27, 'name' => 'Macedonian', 'locale' => 'mk_MK'],
|
||||
['id' => 28, 'name' => 'Chinese - Taiwan', 'locale' => 'zh_TW'],
|
||||
['id' => 29, 'name' => 'Russian (Russia)', 'locale' => 'ru_RU'],
|
||||
['id' => 30, 'name' => 'Arabic', 'locale' => 'ar'],
|
||||
['id' => 31, 'name' => 'Persian', 'locale' => 'fa'],
|
||||
['id' => 32, 'name' => 'Latvian', 'locale' => 'lv_LV'],
|
||||
];
|
||||
|
||||
foreach ($languages as $language) {
|
||||
|
@ -31,7 +31,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
['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":""}'],
|
||||
['id' => 7, 'name' => 'Mollie', 'provider' => 'Mollie', 'is_offsite' => true, 'sort_order' => 8, 'key' => '1bd651fb213ca0c9d66ae3c336dc77e8', 'fields' => '{"apiKey":""}'],
|
||||
['id' => 7, 'name' => 'Mollie', 'provider' => 'Mollie', 'is_offsite' => true, 'sort_order' => 8, 'key' => '1bd651fb213ca0c9d66ae3c336dc77e8', 'fields' => '{"apiKey":"","profileId":"","testMode":false}'],
|
||||
['id' => 8, 'name' => 'MultiSafepay', 'provider' => 'MultiSafepay', 'key' => 'c3dec814e14cbd7d86abd92ce6789f8c', 'fields' => '{"accountId":"","siteId":"","siteCode":"","testMode":false}'],
|
||||
['id' => 9, 'name' => 'Netaxept', 'provider' => 'Netaxept', 'key' => '070dffc5ca94f4e66216e44028ebd52d', 'fields' => '{"merchantId":"","password":"","testMode":false}'],
|
||||
['id' => 10, 'name' => 'NetBanx', 'provider' => 'NetBanx', 'key' => '334d419939c06bd99b4dfd8a49243f0f', 'fields' => '{"accountNumber":"","storeId":"","storePassword":"","testMode":false}'],
|
||||
@ -70,7 +70,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
['id' => 43, 'name' => 'Fasapay', 'provider' => 'Fasapay', 'key' => '1b2cef0e8c800204a29f33953aaf3360', 'fields' => ''],
|
||||
['id' => 44, 'name' => 'Komoju', 'provider' => 'Komoju', 'key' => '7ea2d40ecb1eb69ef8c3d03e5019028a', 'fields' => '{"apiKey":"","accountId":"","paymentMethod":"credit_card","testMode":false,"locale":"en"}'],
|
||||
['id' => 45, 'name' => 'Paysafecard', 'provider' => 'Paysafecard', 'key' => '70ab90cd6c5c1ab13208b3cef51c0894', 'fields' => '{"username":"","password":"","testMode":false}'],
|
||||
['id' => 46, 'name' => 'Paytrace', 'provider' => 'Paytrace_CreditCard', 'key' => 'bbd736b3254b0aabed6ad7fda1298c88', 'fields' => '{"username":"","password":"","testMode":false,"endpoint":"https:\/\/paytrace.com\/api\/default.pay"}'],
|
||||
['id' => 46, 'name' => 'Paytrace', 'provider' => 'Paytrace', 'key' => 'bbd736b3254b0aabed6ad7fda1298c88', 'fields' => '{"username":"","password":"","integratorId":"","testMode":false,"endpoint":"https:\/\/paytrace.com\/api\/default.pay"}'],
|
||||
['id' => 47, 'name' => 'Secure Trading', 'provider' => 'SecureTrading', 'key' => '231cb401487b9f15babe04b1ac4f7a27', 'fields' => '{"siteReference":"","username":"","password":"","applyThreeDSecure":false,"accountType":"ECOM"}'],
|
||||
['id' => 48, 'name' => 'SecPay', 'provider' => 'SecPay', 'key' => 'bad8699d581d9fa040e59c0bb721a76c', 'fields' => '{"mid":"","vpnPswd":"","remotePswd":"","usageType":"","confirmEmail":"","testStatus":"true","mailCustomer":"true","additionalOptions":""}'],
|
||||
['id' => 49, 'name' => 'WePay', 'provider' => 'WePay', 'is_offsite' => false, 'sort_order' => 3, 'key' => '8fdeed552015b3c7b44ed6c8ebd9e992', 'fields' => '{"accountId":"","accessToken":"","type":"goods","testMode":false,"feePayer":"payee"}'],
|
||||
@ -96,7 +96,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
|
||||
Gateway::query()->update(['visible' => 0]);
|
||||
|
||||
Gateway::whereIn('id', [1,15,20,39,55,50])->update(['visible' => 1]);
|
||||
Gateway::whereIn('id', [1,7,15,20,39,46,55,50])->update(['visible' => 1]);
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
Gateway::whereIn('id', [20])->update(['visible' => 0]);
|
||||
|
12193
package-lock.json
generated
12193
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,7 @@
|
||||
"card-validator": "^6.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"jsignature": "^2.1.3",
|
||||
"json-formatter-js": "^2.3.4",
|
||||
"laravel-mix": "^5.0.9",
|
||||
"linkify-urls": "^3.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
187606
public/css/admin.css
vendored
187606
public/css/admin.css
vendored
File diff suppressed because one or more lines are too long
2
public/css/app.css
vendored
2
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
10
public/flutter_service_worker.js
vendored
10
public/flutter_service_worker.js
vendored
@ -23,16 +23,16 @@ const RESOURCES = {
|
||||
"assets/assets/images/logo.png": "e5f46d5a78e226e7a9553d4ca6f69219",
|
||||
"assets/AssetManifest.json": "753bba1dee0531d5fad970b5ce1d296d",
|
||||
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "1288c9e28052e028aba623321f7826ac",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "4e6447691c9509f7acdbf8a931a85ca1",
|
||||
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",
|
||||
"assets/NOTICES": "687b68d41e137cfbdee105c0b9be3e9d",
|
||||
"assets/NOTICES": "f44f710ef9af0b68d977d458631873e1",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"manifest.json": "ce1b79950eb917ea619a0a30da27c6a3",
|
||||
"version.json": "3f9e03374a3e78d2cab3afd8723d0993",
|
||||
"version.json": "46d4015fc9abcefe5371cafcf2084173",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"main.dart.js": "02238eed5a325865e74b8a1e7afd03a6",
|
||||
"main.dart.js": "7bcab146a5f6ade3cd027cc9e429f732",
|
||||
"/": "d389ab59423a76b2aaaa683ed382c78e"
|
||||
};
|
||||
|
||||
@ -50,7 +50,7 @@ self.addEventListener("install", (event) => {
|
||||
return event.waitUntil(
|
||||
caches.open(TEMP).then((cache) => {
|
||||
return cache.addAll(
|
||||
CORE.map((value) => new Request(value + '?revision=' + RESOURCES[value], {'cache': 'reload'})));
|
||||
CORE.map((value) => new Request(value, {'cache': 'reload'})));
|
||||
})
|
||||
);
|
||||
});
|
||||
|
167
public/js/admin.js
vendored
167
public/js/admin.js
vendored
@ -1 +1,166 @@
|
||||
(()=>{var r,e={847:()=>{},113:()=>{}},o={};function n(r){var t=o[r];if(void 0!==t)return t.exports;var a=o[r]={exports:{}};return e[r](a,a.exports,n),a.exports}n.m=e,r=[],n.O=(e,o,t,a)=>{if(!o){var v=1/0;for(p=0;p<r.length;p++){for(var[o,t,a]=r[p],l=!0,i=0;i<o.length;i++)(!1&a||v>=a)&&Object.keys(n.O).every((r=>n.O[r](o[i])))?o.splice(i--,1):(l=!1,a<v&&(v=a));l&&(r.splice(p--,1),e=t())}return e}a=a||0;for(var p=r.length;p>0&&r[p-1][2]>a;p--)r[p]=r[p-1];r[p]=[o,t,a]},n.o=(r,e)=>Object.prototype.hasOwnProperty.call(r,e),(()=>{var r={467:0,703:0};n.O.j=e=>0===r[e];var e=(e,o)=>{var t,a,[v,l,i]=o,p=0;for(t in l)n.o(l,t)&&(n.m[t]=l[t]);if(i)var f=i(n);for(e&&e(o);p<v.length;p++)a=v[p],n.o(r,a)&&r[a]&&r[a][0](),r[v[p]]=0;return n.O(f)},o=self.webpackChunk=self.webpackChunk||[];o.forEach(e.bind(null,0)),o.push=e.bind(null,o.push.bind(o))})(),n.O(void 0,[703],(()=>n(847)));var t=n.O(void 0,[703],(()=>n(113)));t=n.O(t)})();
|
||||
/******/ (() => { // webpackBootstrap
|
||||
/******/ var __webpack_modules__ = ({
|
||||
|
||||
/***/ "./Resources/assets/js/app.js":
|
||||
/*!************************************!*\
|
||||
!*** ./Resources/assets/js/app.js ***!
|
||||
\************************************/
|
||||
/***/ (() => {
|
||||
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./Resources/assets/css/app.css":
|
||||
/*!**************************************!*\
|
||||
!*** ./Resources/assets/css/app.css ***!
|
||||
\**************************************/
|
||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
// extracted by mini-css-extract-plugin
|
||||
|
||||
|
||||
/***/ })
|
||||
|
||||
/******/ });
|
||||
/************************************************************************/
|
||||
/******/ // The module cache
|
||||
/******/ var __webpack_module_cache__ = {};
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/ // Check if module is in cache
|
||||
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
||||
/******/ if (cachedModule !== undefined) {
|
||||
/******/ return cachedModule.exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = __webpack_module_cache__[moduleId] = {
|
||||
/******/ // no module.id needed
|
||||
/******/ // no module.loaded needed
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = __webpack_modules__;
|
||||
/******/
|
||||
/************************************************************************/
|
||||
/******/ /* webpack/runtime/chunk loaded */
|
||||
/******/ (() => {
|
||||
/******/ var deferred = [];
|
||||
/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => {
|
||||
/******/ if(chunkIds) {
|
||||
/******/ priority = priority || 0;
|
||||
/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];
|
||||
/******/ deferred[i] = [chunkIds, fn, priority];
|
||||
/******/ return;
|
||||
/******/ }
|
||||
/******/ var notFulfilled = Infinity;
|
||||
/******/ for (var i = 0; i < deferred.length; i++) {
|
||||
/******/ var [chunkIds, fn, priority] = deferred[i];
|
||||
/******/ var fulfilled = true;
|
||||
/******/ for (var j = 0; j < chunkIds.length; j++) {
|
||||
/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {
|
||||
/******/ chunkIds.splice(j--, 1);
|
||||
/******/ } else {
|
||||
/******/ fulfilled = false;
|
||||
/******/ if(priority < notFulfilled) notFulfilled = priority;
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ if(fulfilled) {
|
||||
/******/ deferred.splice(i--, 1)
|
||||
/******/ result = fn();
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ return result;
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/make namespace object */
|
||||
/******/ (() => {
|
||||
/******/ // define __esModule on exports
|
||||
/******/ __webpack_require__.r = (exports) => {
|
||||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
/******/ }
|
||||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/jsonp chunk loading */
|
||||
/******/ (() => {
|
||||
/******/ // no baseURI
|
||||
/******/
|
||||
/******/ // object to store loaded and loading chunks
|
||||
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
|
||||
/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
|
||||
/******/ var installedChunks = {
|
||||
/******/ "/js/admin": 0,
|
||||
/******/ "css/admin": 0
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // no chunk on demand loading
|
||||
/******/
|
||||
/******/ // no prefetching
|
||||
/******/
|
||||
/******/ // no preloaded
|
||||
/******/
|
||||
/******/ // no HMR
|
||||
/******/
|
||||
/******/ // no HMR manifest
|
||||
/******/
|
||||
/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);
|
||||
/******/
|
||||
/******/ // install a JSONP callback for chunk loading
|
||||
/******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
|
||||
/******/ var [chunkIds, moreModules, runtime] = data;
|
||||
/******/ // add "moreModules" to the modules object,
|
||||
/******/ // then flag all "chunkIds" as loaded and fire callback
|
||||
/******/ var moduleId, chunkId, i = 0;
|
||||
/******/ for(moduleId in moreModules) {
|
||||
/******/ if(__webpack_require__.o(moreModules, moduleId)) {
|
||||
/******/ __webpack_require__.m[moduleId] = moreModules[moduleId];
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ if(runtime) var result = runtime(__webpack_require__);
|
||||
/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);
|
||||
/******/ for(;i < chunkIds.length; i++) {
|
||||
/******/ chunkId = chunkIds[i];
|
||||
/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
|
||||
/******/ installedChunks[chunkId][0]();
|
||||
/******/ }
|
||||
/******/ installedChunks[chunkIds[i]] = 0;
|
||||
/******/ }
|
||||
/******/ return __webpack_require__.O(result);
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ var chunkLoadingGlobal = self["webpackChunk"] = self["webpackChunk"] || [];
|
||||
/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
|
||||
/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
|
||||
/******/ })();
|
||||
/******/
|
||||
/************************************************************************/
|
||||
/******/
|
||||
/******/ // startup
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ // This entry module depends on other loaded chunks and execution need to be delayed
|
||||
/******/ __webpack_require__.O(undefined, ["css/admin"], () => (__webpack_require__("./Resources/assets/js/app.js")))
|
||||
/******/ var __webpack_exports__ = __webpack_require__.O(undefined, ["css/admin"], () => (__webpack_require__("./Resources/assets/css/app.css")))
|
||||
/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__);
|
||||
/******/
|
||||
/******/ })()
|
||||
;
|
2
public/js/clients/payments/mollie-credit-card.js
vendored
Normal file
2
public/js/clients/payments/mollie-credit-card.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! For license information please see mollie-credit-card.js.LICENSE.txt */
|
||||
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=22)}({22:function(e,t,n){e.exports=n("i12I")},i12I:function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function e(){var t,n;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.mollie=Mollie(null===(t=document.querySelector("meta[name=mollie-profileId]"))||void 0===t?void 0:t.content,{testmode:null===(n=document.querySelector("meta[name=mollie-testmode]"))||void 0===n?void 0:n.content,locale:"en_US"})}var t,r,o;return t=e,(r=[{key:"createCardHolderInput",value:function(){var e=this.mollie.createComponent("cardHolder");e.mount("#card-holder");var t=document.getElementById("card-holder-error");return e.addEventListener("change",(function(e){e.error&&e.touched?t.textContent=e.error:t.textContent=""})),this}},{key:"createCardNumberInput",value:function(){var e=this.mollie.createComponent("cardNumber");e.mount("#card-number");var t=document.getElementById("card-number-error");return e.addEventListener("change",(function(e){e.error&&e.touched?t.textContent=e.error:t.textContent=""})),this}},{key:"createExpiryDateInput",value:function(){var e=this.mollie.createComponent("expiryDate");e.mount("#expiry-date");var t=document.getElementById("expiry-date-error");return e.addEventListener("change",(function(e){e.error&&e.touched?t.textContent=e.error:t.textContent=""})),this}},{key:"createCvvInput",value:function(){var e=this.mollie.createComponent("verificationCode");e.mount("#cvv");var t=document.getElementById("cvv-error");return e.addEventListener("change",(function(e){e.error&&e.touched?t.textContent=e.error:t.textContent=""})),this}},{key:"handlePayNowButton",value:function(){if(document.getElementById("pay-now").disabled=!0,""!==document.querySelector("input[name=token]").value)return document.querySelector("input[name=gateway_response]").value="",document.getElementById("server-response").submit();this.mollie.createToken().then((function(e){var t=e.token,n=e.error;if(n){document.getElementById("pay-now").disabled=!1;var r=document.getElementById("errors");return r.innerText=n.message,void(r.hidden=!1)}var o=document.querySelector('input[name="token-billing-checkbox"]:checked');o&&(document.querySelector('input[name="store_card"]').value=o.value),document.querySelector("input[name=gateway_response]").value=t,document.querySelector("input[name=token]").value="",document.getElementById("server-response").submit()}))}},{key:"handle",value:function(){var e=this;this.createCardHolderInput().createCardNumberInput().createExpiryDateInput().createCvvInput(),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(e){return e.addEventListener("click",(function(e){document.getElementById("mollie--payment-container").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=e.target.dataset.token}))})),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",(function(e){document.getElementById("mollie--payment-container").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value=""})),document.getElementById("pay-now").addEventListener("click",(function(){return e.handlePayNowButton()}))}}])&&n(t.prototype,r),o&&n(t,o),e}())).handle()}});
|
@ -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
|
||||
*/
|
2
public/js/clients/payments/paytrace-credit-card.js
vendored
Normal file
2
public/js/clients/payments/paytrace-credit-card.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/*! For license information please see paytrace-credit-card.js.LICENSE.txt */
|
||||
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=21)}({"0Swb":function(e,t){function n(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}(new(function(){function e(){var t;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.clientKey=null===(t=document.querySelector("meta[name=paytrace-client-key]"))||void 0===t?void 0:t.content}var t,r,o;return t=e,(r=[{key:"creditCardStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"100%"}}},{key:"codeStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"300px"}}},{key:"expStyles",get:function(){return{font_color:"#111827",border_color:"rgba(210,214,220,1)",label_color:"#111827",label_size:"12pt",background_color:"white",border_style:"solid",font_size:"15pt",height:"30px",width:"85px",type:"dropdown"}}},{key:"updatePayTraceLabels",value:function(){window.PTPayment.getControl("securityCode").label.text(document.querySelector("meta[name=ctrans-cvv]").content),window.PTPayment.getControl("creditCard").label.text(document.querySelector("meta[name=ctrans-card_number]").content),window.PTPayment.getControl("expiration").label.text(document.querySelector("meta[name=ctrans-expires]").content)}},{key:"setupPayTrace",value:function(){return window.PTPayment.setup({styles:{code:this.codeStyles,cc:this.creditCardStyles,exp:this.expStyles},authorization:{clientKey:this.clientKey}})}},{key:"handlePaymentWithCreditCard",value:function(e){var t=this;e.target.parentElement.disabled=!0,document.getElementById("errors").hidden=!0,window.PTPayment.validate((function(n){if(n.length>=1){var r=document.getElementById("errors");return r.textContent=n[0].description,r.hidden=!1,e.target.parentElement.disabled=!1}t.ptInstance.process().then((function(e){document.getElementById("HPF_Token").value=e.message.hpf_token,document.getElementById("enc_key").value=e.message.enc_key;var t=document.querySelector('input[name="token-billing-checkbox"]:checked');t&&(document.querySelector('input[name="store_card"]').value=t.value),document.getElementById("server_response").submit()})).catch((function(e){document.getElementById("errors").textContent=JSON.stringify(e),document.getElementById("errors").hidden=!1,console.log(e)}))}))}},{key:"handlePaymentWithToken",value:function(e){e.target.parentElement.disabled=!0,document.getElementById("server_response").submit()}},{key:"handle",value:function(){var e=this;this.setupPayTrace().then((function(t){var n;e.ptInstance=t,e.updatePayTraceLabels(),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach((function(e){return e.addEventListener("click",(function(e){document.getElementById("paytrace--credit-card-container").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=e.target.dataset.token}))})),null===(n=document.getElementById("toggle-payment-with-credit-card"))||void 0===n||n.addEventListener("click",(function(e){document.getElementById("paytrace--credit-card-container").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value=""})),document.getElementById("pay-now").addEventListener("click",(function(t){return""===document.querySelector("input[name=token]").value?e.handlePaymentWithCreditCard(t):e.handlePaymentWithToken(t)}))}))}}])&&n(t.prototype,r),o&&n(t,o),e}())).handle()},21:function(e,t,n){e.exports=n("0Swb")}});
|
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
File diff suppressed because one or more lines are too long
194
public/js/jsformatter.css
vendored
Normal file
194
public/js/jsformatter.css
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
.json-formatter-row {
|
||||
font-family: monospace;
|
||||
}
|
||||
.json-formatter-row,
|
||||
.json-formatter-row a,
|
||||
.json-formatter-row a:hover {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
.json-formatter-row .json-formatter-row {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.json-formatter-row .json-formatter-children.json-formatter-empty {
|
||||
opacity: 0.5;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.json-formatter-row .json-formatter-children.json-formatter-empty:after {
|
||||
display: none;
|
||||
}
|
||||
.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-object:after {
|
||||
content: "No properties";
|
||||
}
|
||||
.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-array:after {
|
||||
content: "[]";
|
||||
}
|
||||
.json-formatter-row .json-formatter-string,
|
||||
.json-formatter-row .json-formatter-stringifiable {
|
||||
color: green;
|
||||
white-space: pre;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.json-formatter-row .json-formatter-number {
|
||||
color: blue;
|
||||
}
|
||||
.json-formatter-row .json-formatter-boolean {
|
||||
color: red;
|
||||
}
|
||||
.json-formatter-row .json-formatter-null {
|
||||
color: #855A00;
|
||||
}
|
||||
.json-formatter-row .json-formatter-undefined {
|
||||
color: #ca0b69;
|
||||
}
|
||||
.json-formatter-row .json-formatter-function {
|
||||
color: #FF20ED;
|
||||
}
|
||||
.json-formatter-row .json-formatter-date {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.json-formatter-row .json-formatter-url {
|
||||
text-decoration: underline;
|
||||
color: blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
.json-formatter-row .json-formatter-bracket {
|
||||
color: blue;
|
||||
}
|
||||
.json-formatter-row .json-formatter-key {
|
||||
color: #00008B;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
.json-formatter-row .json-formatter-toggler-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
.json-formatter-row .json-formatter-toggler {
|
||||
line-height: 1.2rem;
|
||||
font-size: 0.7rem;
|
||||
vertical-align: middle;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
.json-formatter-row .json-formatter-toggler:after {
|
||||
display: inline-block;
|
||||
transition: transform 100ms ease-in;
|
||||
content: "►";
|
||||
}
|
||||
.json-formatter-row > a > .json-formatter-preview-text {
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in;
|
||||
font-style: italic;
|
||||
}
|
||||
.json-formatter-row:hover > a > .json-formatter-preview-text {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.json-formatter-row.json-formatter-open > .json-formatter-toggler-link .json-formatter-toggler:after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.json-formatter-row.json-formatter-open > .json-formatter-children:after {
|
||||
display: inline-block;
|
||||
}
|
||||
.json-formatter-row.json-formatter-open > a > .json-formatter-preview-text {
|
||||
display: none;
|
||||
}
|
||||
.json-formatter-row.json-formatter-open.json-formatter-empty:after {
|
||||
display: block;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row {
|
||||
font-family: monospace;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row,
|
||||
.json-formatter-dark.json-formatter-row a,
|
||||
.json-formatter-dark.json-formatter-row a:hover {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-row {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty {
|
||||
opacity: 0.5;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty:after {
|
||||
display: none;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-object:after {
|
||||
content: "No properties";
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-children.json-formatter-empty.json-formatter-array:after {
|
||||
content: "[]";
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-string,
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-stringifiable {
|
||||
color: #31F031;
|
||||
white-space: pre;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-number {
|
||||
color: #66C2FF;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-boolean {
|
||||
color: #EC4242;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-null {
|
||||
color: #EEC97D;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-undefined {
|
||||
color: #ef8fbe;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-function {
|
||||
color: #FD48CB;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-date {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-url {
|
||||
text-decoration: underline;
|
||||
color: #027BFF;
|
||||
cursor: pointer;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-bracket {
|
||||
color: #9494FF;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-key {
|
||||
color: #23A0DB;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-toggler-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-toggler {
|
||||
line-height: 1.2rem;
|
||||
font-size: 0.7rem;
|
||||
vertical-align: middle;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
padding-right: 0.2rem;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row .json-formatter-toggler:after {
|
||||
display: inline-block;
|
||||
transition: transform 100ms ease-in;
|
||||
content: "►";
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row > a > .json-formatter-preview-text {
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in;
|
||||
font-style: italic;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row:hover > a > .json-formatter-preview-text {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row.json-formatter-open > .json-formatter-toggler-link .json-formatter-toggler:after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row.json-formatter-open > .json-formatter-children:after {
|
||||
display: inline-block;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row.json-formatter-open > a > .json-formatter-preview-text {
|
||||
display: none;
|
||||
}
|
||||
.json-formatter-dark.json-formatter-row.json-formatter-open.json-formatter-empty:after {
|
||||
display: block;
|
||||
}
|
344177
public/main.dart.js
vendored
344177
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
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user