Merge pull request #8063 from turbo124/v5-develop

subscriptions
This commit is contained in:
David Bomba 2022-12-09 19:50:20 +11:00 committed by GitHub
commit f02ee29a47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 453 additions and 178 deletions

View File

@ -92,6 +92,20 @@ class ExpenseFilters extends QueryFilters
return $this->builder;
}
/**
* Returns a list of expenses that can be matched to bank transactions
*/
public function match_transactions($value = '')
{
if($value == 'true')
{
return $this->builder->where('is_deleted',0)->whereNull('transaction_id');
}
return $this->builder;
}
/**
* Filters the list based on the status

View File

@ -81,6 +81,20 @@ class PaymentFilters extends QueryFilters
});
}
/**
* Returns a list of payments that can be matched to bank transactions
*/
public function match_transactions($value = '')
{
if($value == 'true')
{
return $this->builder->where('is_deleted',0)->whereNull('transaction_id');
}
return $this->builder;
}
/**
* Sorts the list based on $sort.
*

View File

@ -49,7 +49,7 @@ class InvoiceSum
/**
* Constructs the object with Invoice and Settings object.
*
* @param Invoice $invoice The invoice
* @param \App\Models\RecurringInvoice|\App\Models\Quote|\App\Models\Credit|\App\Models\PurchaseOrder|\App\Models\Invoice $invoice The entity
*/
public function __construct($invoice)
{

View File

@ -46,7 +46,7 @@ class InvoiceSumInclusive
/**
* Constructs the object with Invoice and Settings object.
*
* @param Invoice $invoice The invoice
* @param \App\Models\RecurringInvoice|\App\Models\Quote|\App\Models\Credit|\App\Models\PurchaseOrder|\App\Models\Invoice $invoice The entity
*/
public function __construct($invoice)
{

View File

@ -31,6 +31,7 @@ use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Livewire\Component;
class BillingPortalPurchasev2 extends Component
@ -97,7 +98,6 @@ class BillingPortalPurchasev2 extends Component
*/
public $payment_method_id;
private $user_coupon;
/**
* List of steps that frontend form follows.
@ -188,12 +188,19 @@ class BillingPortalPurchasev2 extends Component
public $optional_recurring_products;
public $optional_products;
public $total;
public $discount;
public $sub_total;
public $authenticated = false;
public $otp;
public $login;
public $value;
public function mount()
{
MultiDB::setDb($this->company->db);
$this->quantity = 1;
$this->discount = 0;
$this->sub_total = 0;
$this->data = [];
@ -212,17 +219,71 @@ class BillingPortalPurchasev2 extends Component
$this->optional_recurring_products = $this->subscription->service()->optional_recurring_products();
$this->optional_products = $this->subscription->service()->optional_products();
// $this->buildBundle();
$this->bundle = collect();
}
public function loginValidation()
{
$this->resetErrorBag('login');
$this->resetValidation('login');
}
public function handleLogin()
{
$code = Cache::get("subscriptions:otp:{$this->email}");
$this->validateOnly('login', ['login' => ['required',Rule::in([$code])]], ['login' => ctrans('texts.invalid_code')]);
$contact = ClientContact::where('email', $this->email)->first();
if($contact){
Auth::guard('contact')->loginUsingId($contact->id, true);
$this->contact = $contact;
}
else {
}
}
public function handleEmail()
{
$this->validateOnly('email', ['email' => 'required|bail|email:rfc']);
$rand = rand(100000,999999);
$email_hash = "subscriptions:otp:{$this->email}";
Cache::put($email_hash, $rand, 120);
}
/**
* Handle a coupon being entered into the checkout
*/
public function handleCoupon()
{
if($this->coupon == $this->subscription->promo_code)
$this->buildBundle();
else
$this->discount = 0;
}
/**
* Build the bundle in the checkout
*/
public function buildBundle()
{
$this->bundle = collect();
$data = $this->data;
/* Recurring products can have a variable quantity */
foreach($this->recurring_products as $key => $p)
{
@ -234,10 +295,11 @@ class BillingPortalPurchasev2 extends Component
'price' => Number::formatMoney($total, $this->subscription->company).' / '. RecurringInvoice::frequencyForKey($this->subscription->frequency_id),
'total' => $total,
'qty' => $qty,
'is_recurring' => true
'is_recurring' => true,
]);
}
/* One time products can only have a single quantity */
foreach($this->products as $key => $p)
{
@ -251,10 +313,13 @@ class BillingPortalPurchasev2 extends Component
'qty' => $qty,
'is_recurring' => false
]);
}
foreach($this->data as $key => $value)
{
/* Optional recurring products can have a variable quantity */
if(isset($this->data[$key]['optional_recurring_qty']))
{
$p = $this->optional_recurring_products->first(function ($v,$k) use($key){
@ -278,6 +343,7 @@ class BillingPortalPurchasev2 extends Component
}
/* Optional products can have a variable quantity */
if(isset($this->data[$key]['optional_qty']))
{
$p = $this->optional_products->first(function ($v,$k) use($key){
@ -300,41 +366,67 @@ class BillingPortalPurchasev2 extends Component
}
}
$this->total = Number::formatMoney($this->bundle->sum('total'), $this->subscription->company);
$this->sub_total = Number::formatMoney($this->bundle->sum('total'), $this->subscription->company);
$this->total = $this->sub_total;
if($this->coupon == $this->subscription->promo_code)
{
if($this->subscription->is_amount_discount)
$discount = $this->subscription->promo_discount;
else
$discount = round($this->bundle->sum('total') * ($this->subscription->promo_discount / 100), 2);
$this->discount = Number::formatMoney($discount, $this->subscription->company);
$this->total = Number::formatMoney(($this->bundle->sum('total') - $discount), $this->subscription->company);
}
return $this;
}
public function updatedData()
private function createClientContact()
{
$company = $this->subscription->company;
$user = $this->subscription->user;
$user->setCompany($company);
$client_repo = new ClientRepository(new ClientContactRepository());
$data = [
'name' => '',
'contacts' => [
['email' => $this->email],
],
'client_hash' => Str::random(40),
'settings' => ClientSettings::defaults(),
];
$client = $client_repo->save($data, ClientFactory::create($company->id, $user->id));
$this->contact = $client->fresh()->contacts()->first();
Auth::guard('contact')->loginUsingId($this->contact->id, true);
}
public function updating($prop)
{
// $this->resetValidation($prop);
// $this->resetErrorBag($prop);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public function updated($propertyName)
{
$x = $this->validateOnly($propertyName, $this->rules(), [], $this->attributes());
if(in_array($propertyName, ['login','email']))
return;
// // $validatedData = $this->validate();
$this->buildBundle();
// $order_validator = Validator::make($this->all(), $this->rules(), [], $this->attributes());
}
public function rules()
{
$rules = [
'data.*.recurring_qty' => 'numeric|between:0,1000',
'data.*.optional_recurring_qty' => 'numeric|between:0,1000',
'data.*.optional_qty' => 'numeric|between:0,1000',
];
return $rules;
@ -343,9 +435,6 @@ class BillingPortalPurchasev2 extends Component
public function attributes()
{
$attributes = [
'data.*.recurring_qty' => 'recurring_qty',
'data.*.optional_recurring_qty' => 'optional_recurring_qty',
'data.*.optional_qty' => 'optional_qty',
];
return $attributes;
@ -390,6 +479,9 @@ class BillingPortalPurchasev2 extends Component
}
}
/**
* Create a blank client. Used for new customers purchasing.
*
@ -526,7 +618,7 @@ class BillingPortalPurchasev2 extends Component
]],
'user_input_promo_code' => $this->coupon,
'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon,
'quantity' => $this->quantity,
// 'quantity' => $this->quantity,
];
$is_eligible = $this->subscription->service()->isEligible($this->contact);
@ -588,7 +680,6 @@ class BillingPortalPurchasev2 extends Component
return;
}
return $this->subscription->service()->handleNoPaymentRequired([
'email' => $this->email ?? $this->contact->email,
'quantity' => $this->quantity,
@ -598,55 +689,6 @@ class BillingPortalPurchasev2 extends Component
]);
}
/**
* Update quantity property.
*
* @param string $option
* @return int
*/
public function updateQuantity(string $option): int
{
$this->handleCoupon();
if ($this->quantity == 1 && $option == 'decrement') {
$this->price = $this->price * 1;
return $this->quantity;
}
if ($this->quantity > $this->subscription->max_seats_limit && $option == 'increment') {
$this->price = $this->price * $this->subscription->max_seats_limit;
return $this->quantity;
}
if ($option == 'increment') {
$this->quantity++;
$this->price = $this->price * $this->quantity;
return $this->quantity;
}
$this->quantity--;
$this->price = $this->price * $this->quantity;
return $this->quantity;
}
public function handleCoupon()
{
if($this->steps['discount_applied']){
$this->price = $this->subscription->promo_price;
return;
}
if ($this->coupon == $this->subscription->promo_code) {
$this->price = $this->subscription->promo_price;
$this->quantity = 1;
$this->steps['discount_applied'] = true;
}
else
$this->price = $this->subscription->price;
}
public function passwordlessLogin()
{
$this->passwordless_login_btn = true;
@ -681,10 +723,10 @@ class BillingPortalPurchasev2 extends Component
return render('components.livewire.billing-portal-purchasev2');
}
public function changeData()
{
// public function changeData()
// {
nlog($this->data);
// nlog($this->data);
}
// }
}

View File

@ -12,6 +12,7 @@
namespace App\Http\Livewire\RecurringInvoices;
use App\Models\Invoice;
use Livewire\Component;
class UpdateAutoBilling extends Component
@ -24,6 +25,12 @@ class UpdateAutoBilling extends Component
if ($this->invoice->auto_bill == 'optin' || $this->invoice->auto_bill == 'optout') {
$this->invoice->auto_bill_enabled = ! $this->invoice->auto_bill_enabled;
$this->invoice->saveQuietly();
Invoice::where('recurring_id', $this->invoice->id)
->whereIn('status_id', [2,3])
->where('is_deleted',0)
->where('balance', '>', 0)
->update(['auto_bill_enabled' => $this->invoice->auto_bill_enabled]);
}
}

View File

@ -108,6 +108,8 @@ class Csv extends BaseImport implements ImportInterface
$bank_transaction_count = $this->ingest($data, $entity_type);
$this->entity_count['bank_transactions'] = $bank_transaction_count;
nlog("bank matching co id = {$this->company->id}");
BankMatchingService::dispatchSync($this->company->id, $this->company->db);
}

View File

@ -56,6 +56,7 @@ class PaymentType extends StaticModel
const INSTANT_BANK_PAY = 45;
const FPX = 46;
const KLARNA = 47;
const Interac_E_Transfer = 48;
public static function parseCardType($cardName)
{

View File

@ -52,6 +52,8 @@ class CreditCard
{
$amount = $this->mollie->convertToMollieAmount((float) $this->mollie->payment_hash->data->amount_with_fee);
$description = sprintf('%s: %s', ctrans('texts.invoices'), \implode(', ', collect($this->mollie->payment_hash->invoices())->pluck('invoice_number')->toArray()));
$this->mollie->payment_hash
->withData('gateway_type_id', GatewayType::CREDIT_CARD)
->withData('client_id', $this->mollie->client->id);
@ -68,7 +70,7 @@ class CreditCard
'mandateId' => $request->token,
'customerId' => $cgt->gateway_customer_reference,
'sequenceType' => 'recurring',
'description' => \sprintf('Hash: %s', $this->mollie->payment_hash->hash),
'description' => $description,
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
'idempotencyKey' => uniqid("st",true),
'metadata' => [
@ -91,7 +93,11 @@ class CreditCard
if ($payment->status === 'open') {
$this->mollie->payment_hash->withData('payment_id', $payment->id);
return redirect($payment->getCheckoutUrl());
if(!$payment->getCheckoutUrl())
return render('gateways.mollie.mollie_placeholder');
else
return redirect()->away($payment->getCheckoutUrl());
}
} catch (\Exception $e) {
return $this->processUnsuccessfulPayment($e);
@ -104,7 +110,7 @@ class CreditCard
'currency' => $this->mollie->client->currency()->code,
'value' => $amount,
],
'description' => \sprintf('Hash: %s', $this->mollie->payment_hash->hash),
'description' => $description,
'redirectUrl' => route('mollie.3ds_redirect', [
'company_key' => $this->mollie->client->company->company_key,
'company_gateway_id' => $this->mollie->company_gateway->hashed_id,
@ -151,7 +157,13 @@ class CreditCard
if ($payment->status === 'open') {
$this->mollie->payment_hash->withData('payment_id', $payment->id);
return redirect($payment->getCheckoutUrl());
nlog("Mollie");
nlog($payment);
if(!$payment->getCheckoutUrl())
return render('gateways.mollie.mollie_placeholder');
else
return redirect()->away($payment->getCheckoutUrl());
}
} catch (\Exception $e) {
$this->processUnsuccessfulPayment($e);

View File

@ -173,9 +173,9 @@ class ACH
->first();
if ($invoice) {
$description = ctrans('texts.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
} else {
$description = ctrans('texts.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
}
@ -211,9 +211,9 @@ class ACH
->first();
if ($invoice) {
$description = ctrans('texts.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
} else {
$description = ctrans('texts.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
}
if (substr($cgt->token, 0, 2) === 'pm') {
@ -455,9 +455,9 @@ class ACH
->first();
if ($invoice) {
$description = ctrans('texts.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
} else {
$description = ctrans('texts.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
}
if (substr($source->token, 0, 2) === 'pm') {

View File

@ -63,9 +63,9 @@ class Charge
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
if ($invoice) {
$description = ctrans('texts.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
} else {
$description = ctrans('texts.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
}
$this->stripe->init();

View File

@ -63,7 +63,7 @@ class CreditCard
// $description = $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')) . " for client {$this->stripe->client->present()->name()}";
$invoice_numbers = collect($data['invoices'])->pluck('invoice_number')->implode(',');
$description = ctrans('texts.stripe_paymenttext', ['invoicenumber' => $invoice_numbers, 'amount' => Number::formatMoney($data['total']['amount_with_fee'], $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice_numbers, 'amount' => Number::formatMoney($data['total']['amount_with_fee'], $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$payment_intent_data = [
'amount' => $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()),

View File

@ -53,9 +53,9 @@ class Klarna
$invoice_numbers = collect($data['invoices'])->pluck('invoice_number');
if ($invoice_numbers->count() > 0) {
$description = ctrans('texts.stripe_paymenttext', ['invoicenumber' => $invoice_numbers->implode(', '), 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$description = ctrans('texts.stripe_payment_text', ['invoicenumber' => $invoice_numbers->implode(', '), 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
} else {
$description = ctrans('texts.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
$description = ctrans('texts.stripe_payment_text_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
}
$intent = \Stripe\PaymentIntent::create([

View File

@ -98,7 +98,7 @@ class StripePaymentDriver extends BaseDriver
GatewayType::BECS => BECS::class,
GatewayType::ACSS => ACSS::class,
GatewayType::FPX => FPX::class,
GatewayType::KLARNA => KLARNA::class,
GatewayType::KLARNA => Klarna::class,
];
const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE;

View File

@ -37,6 +37,8 @@ class BankMatchingService implements ShouldQueue
protected $db;
protected $middleware_key;
public function __construct($company_id, $db)
{
$this->company_id = $company_id;

View File

@ -44,8 +44,6 @@ class ProcessBankRules extends AbstractService
private function matchCredit()
{
$this->credit_rules = $this->bank_transaction->company->credit_rules();
$this->invoices = Invoice::where('company_id', $this->bank_transaction->company_id)
->whereIn('status_id', [1,2,3])
->where('is_deleted', 0)
@ -65,6 +63,8 @@ class ProcessBankRules extends AbstractService
return;
}
$this->credit_rules = $this->bank_transaction->company->credit_rules();
//stub for credit rules
foreach($this->credit_rules as $rule)
{
@ -81,11 +81,16 @@ class ProcessBankRules extends AbstractService
$this->categories = collect(Cache::get('bank_categories'));
foreach($this->debit_rules as $bank_transaction_rule)
{
$matches = 0;
if(!is_array($bank_transaction_rule['rules']))
continue;
foreach($bank_transaction_rule['rules'] as $rule)
{
$rule_count = count($bank_transaction_rule['rules']);

View File

@ -145,8 +145,6 @@ class UpdateReminder extends AbstractService
$reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int) $this->settings->endless_reminder_frequency_id);
if ($reminder_date) {
// $reminder_date->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date))) {
$date_collection->push($reminder_date);
}

View File

@ -285,6 +285,9 @@ class HtmlEngine
$data['$amount_raw'] = ['value' => $this->entity->amount, 'label' => ctrans('texts.amount')];
}
// $data['$amount_in_words'] = ['value' => (new \NumberFormatter($this->client->locale(), \NumberFormatter::SPELLOUT))->format($this->entity->amount), 'label' => ''];
// $data['$balance_in_words'] = ['value' => (new \NumberFormatter($this->client->locale(), \NumberFormatter::SPELLOUT))->format($this->entity->balance), 'label' => ''];
// $data['$balance_due'] = $data['$balance_due'];
$data['$outstanding'] = &$data['$balance_due'];
$data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: ' ', 'label' => ctrans('texts.partial_due')];

View File

@ -21,9 +21,11 @@ class PDF extends FPDI
{
$this->SetXY(0, -5);
$this->SetFont('Arial', 'I', 9);
$this->SetTextColor(135, 135, 135);
$trans = ctrans('texts.pdf_page_info', ['current' => $this->PageNo(), 'total' => '{nb}']);
$trans = iconv('UTF-8', 'ISO-8859-7', $trans);
$this->Cell(0, 5, $trans, 0, 0, $this->text_alignment);
}

View File

@ -14,21 +14,37 @@ return new class extends Migration {
*/
public function up()
{
Schema::table('payment_types', function (Blueprint $table) {
$type = new PaymentType();
$pt = PaymentType::find(47);
if(!$pt)
{
$type = new PaymentType();
$type->id = 47;
$type->name = 'Klarna';
$type->gateway_type_id = GatewayType::KLARNA;
$type->save();
});
$type = new GatewayType();
}
$type->id = 23;
$type->alias = 'klarna';
$type->name = 'Klarna';
$type->save();
$pt = PaymentType::find(48);
if(!$pt)
{
$type = new PaymentType();
$type->id = 48;
$type->name = 'Interac E-Transfer';
$type->save();
}
$gt = GatewayType::find(23);
if(!$gt)
{
$type = new GatewayType();
$type->id = 23;
$type->alias = 'klarna';
$type->name = 'Klarna';
$type->save();
}
}
};

View File

@ -17,5 +17,6 @@ return new class extends Migration
$table->string('matomo_url',191)->nullable();
$table->bigInteger('matomo_id')->nullable();
});
}
};

View File

@ -254,8 +254,8 @@ $LANG = array(
'notification_invoice_paid' => 'A payment of :amount was made by client :client towards Invoice :invoice.',
'notification_invoice_sent' => 'The following client :client was emailed Invoice :invoice for :amount.',
'notification_invoice_viewed' => 'The following client :client viewed Invoice :invoice for :amount.',
'stripe_paymenttext' => 'Invoice :invoicenumber for :amount for client :client',
'stripe_paymenttext_without_invoice' => 'Payment with no invoice for amount :amount for client :client',
'stripe_payment_text' => 'Invoice :invoicenumber for :amount for client :client',
'stripe_payment_text_without_invoice' => 'Payment with no invoice for amount :amount for client :client',
'reset_password' => 'You can reset your account password by clicking the following button:',
'secure_payment' => 'Secure Payment',
'card_number' => 'Card Number',
@ -4778,7 +4778,7 @@ $LANG = array(
'invoice_task_project' => 'Invoice Task Project',
'invoice_task_project_help' => 'Add the project to the invoice line items',
'bulk_action' => 'Bulk Action',
'phone_validation_error' => 'This mobile/cell phone number is not valid, please enter in E.164 format',
'phone_validation_error' => 'This mobile (cell) phone number is not valid, please enter in E.164 format',
'transaction' => 'Transaction',
'disable_2fa' => 'Disable 2FA',
'change_number' => 'Change Number',
@ -4891,7 +4891,11 @@ $LANG = array(
'restored_transaction_rule' => 'Successfully restored transaction rule',
'search_transaction_rule' => 'Search Transaction Rule',
'search_transaction_rules' => 'Search Transaction Rules',
'payment_type_Interac E-Transfer' => 'Interac E-Transfer',
'delete_bank_account' => 'Delete Bank Account',
'archive_transaction' => 'Archive Transaction',
'delete_transaction' => 'Delete Transaction',
'otp_code_message' => 'Enter the code emailed.'
);
return $LANG;

View File

@ -14,9 +14,9 @@
@if(!empty($subscription->recurring_product_ids))
@foreach($recurring_products as $index => $product)
<li class="flex py-6">
@if(false)
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200">
<img src="https://tailwindui.com/img/ecommerce-images/shopping-cart-page-04-product-01.jpg" alt="Salmon orange fabric pouch with match zipper, gray zipper pull, and adjustable hip belt." class="h-full w-full object-cover object-center">
@if(filter_var($product->custom_value1, FILTER_VALIDATE_URL))
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
<img src="{{$product->custom_value1}}" alt="" class="h-full w-full object-cover object-center">
</div>
@endif
<div class="ml-0 flex flex-1 flex-col">
@ -34,8 +34,15 @@
<p class="text-gray-500 w-full"></p>
<div class="flex place-content-end">
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
<input wire:model.debounce.300ms="data.{{ $index }}.recurring_qty" type="text" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0"/>
<select wire:model.debounce.300ms="data.{{ $index }}.recurring_qty" class="rounded-md border-gray-300 shadow-sm sm:text-sm">
<option value="1" selected="selected">1</option>
@for ($i = 2; $i <= $subscription->max_seats_limit; $i++)
<option value="{{$i}}">{{$i}}</option>
@endfor
</select>
</div>
@endif
</div>
@error("data.{$index}.recurring_qty")
@ -52,9 +59,9 @@
@if(!empty($subscription->product_ids))
@foreach($products as $product)
<li class="flex py-6">
@if(false)
@if(filter_var($product->custom_value1, FILTER_VALIDATE_URL))
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200">
<img src="https://tailwindui.com/img/ecommerce-images/shopping-cart-page-04-product-01.jpg" alt="Salmon orange fabric pouch with match zipper, gray zipper pull, and adjustable hip belt." class="h-full w-full object-cover object-center">
<img src="{{$product->custom_value1}}" alt="" class="h-full w-full object-cover object-center">
</div>
@endif
<div class="ml-0 flex flex-1 flex-col">
@ -79,10 +86,11 @@
</ul>
</div>
@if(!empty($subscription->optional_recurring_product_ids) || !empty($subscription->optional_product_ids))
<div class="w-full p-4 md:max-w-3xl">
<h2 class="text-2xl font-normal text-left border-b-4">Optional products</h2>
</div>
@endif
<div class="w-full px-4 md:max-w-3xl">
<!-- Optional Recurring Products-->
@ -90,33 +98,41 @@
@if(!empty($subscription->optional_recurring_product_ids))
@foreach($optional_recurring_products as $index => $product)
<li class="flex py-6">
@if(false)
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
<img src="https://tailwindui.com/img/ecommerce-images/shopping-cart-page-04-product-01.jpg" alt="Salmon orange fabric pouch with match zipper, gray zipper pull, and adjustable hip belt." class="h-full w-full object-cover object-center">
</div>
@endif
@if(filter_var($product->custom_value1, FILTER_VALIDATE_URL))
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200">
<img src="{{$product->custom_value1}}" alt="" class="h-full w-full object-cover object-center">
</div>
@endif
<div class="ml-0 flex flex-1 flex-col">
<div>
<div class="flex justify-between text-base font-medium text-gray-900">
<h3>{!! nl2br($product->notes) !!}</h3>
<p class="ml-0">{{ \App\Utils\Number::formatMoney($product->price, $subscription->company) }} </p>
</div>
<p class="mt-1 text-sm text-gray-500"></p>
</div>
<div class="flex content-end text-sm mt-1">
<p class="text-gray-500 w-full"></p>
<div class="flex place-content-end">
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
<input wire:model.debounce.300ms="data.{{ $index }}.optional_recurring_qty" type="text" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0"/>
</div>
</div>
@error("data.{$index}.optional_recurring_qty")
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{{ $message }} </span>
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
</div>
@enderror
<div class="flex justify-between text-sm mt-1">
@if(is_numeric($product->custom_value2))
<p class="text-gray-500 w-3/4"></p>
<div class="flex place-content-end">
@if($subscription->use_inventory_management && $product->in_stock_quantity == 0)
<p class="w-full text-sm font-light text-red-500 text-right mr-2 mt-2">Out of stock</p>
@else
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
@endif
<select wire:model.debounce.300ms="data.{{ $index }}.optional_recurring_qty" class="rounded-md border-gray-300 shadow-sm sm:text-sm"
@if($subscription->use_inventory_management && $product->in_stock_quantity == 0)
disabled
@endif
>
<option value="0" selected="selected">0</option>
@for ($i = 1; $i <= ($subscription->use_inventory_management ? min($product->in_stock_quantity,$product->custom_value2) : $product->custom_value2); $i++)
<option value="{{$i}}">{{$i}}</option>
@endfor
</select>
</div>
@endif
</div>
</div>
</li>
@endforeach
@ -124,11 +140,11 @@
@if(!empty($subscription->optional_product_ids))
@foreach($optional_products as $index => $product)
<li class="flex py-6">
@if(false)
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200 mr-2">
<img src="https://tailwindui.com/img/ecommerce-images/shopping-cart-page-04-product-01.jpg" alt="Salmon orange fabric pouch with match zipper, gray zipper pull, and adjustable hip belt." class="h-full w-full object-cover object-center">
</div>
@endif
@if(filter_var($product->custom_value1, FILTER_VALIDATE_URL))
<div class="h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200">
<img src="{{$product->custom_value1}}" alt="" class="h-full w-full object-cover object-center">
</div>
@endif
<div class="ml-0 flex flex-1 flex-col">
<div>
<div class="flex justify-between text-base font-medium text-gray-900">
@ -137,19 +153,25 @@
</div>
<p class="mt-1 text-sm text-gray-500"></p>
</div>
<div class="flex content-end text-sm mt-1">
<p class="text-gray-500 w-full"></p>
<div class="flex justify-between text-sm mt-1">
@if(is_numeric($product->custom_value2))
<p class="text-gray-500 w-3/4"></p>
<div class="flex place-content-end">
@if($subscription->use_inventory_management && $product->in_stock_quantity == 0)
<p class="w-full text-sm font-light text-red-500 text-right mr-2 mt-2">Out of stock</p>
@else
<p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p>
<input type="text" wire:model.debounce.300ms="data.{{ $index }}.optional_qty" class="w-1/4 rounded-md border-gray-300 shadow-sm sm:text-sm text-center" placeholder="0">
@endif
<select wire:model.debounce.300ms="data.{{ $index }}.optional_qty" class="rounded-md border-gray-300 shadow-sm sm:text-sm">
<option value="0" selected="selected">0</option>
@for ($i = 1; $i <= ($subscription->use_inventory_management ? min($product->in_stock_quantity,$product->custom_value2) : $product->custom_value2); $i++)
<option value="{{$i}}">{{$i}}</option>
@endfor
</select>
</div>
@endif
</div>
@error("data.{$index}.optional_qty")
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{{ $message }} </span>
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
</div>
@enderror
</div>
</li>
@endforeach
@ -173,33 +195,142 @@
@endforeach
@if(!empty($subscription->promo_code) && !$subscription->trial_enabled)
<form wire:submit.prevent="handleCoupon" class="">
@csrf
<div class="mt-2">
<label for="email" class="block text-sm font-medium text-white">{{ ctrans('texts.promo_code') }}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<div class="relative flex flex-grow items-stretch focus-within:z-10">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
</div>
<input type="text" wire:model.debounce.300ms="coupon" class="block w-full rounded-none rounded-l-md border-gray-300 pl-10 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" placeholder="">
</div>
<button type="button" class="relative -ml-px inline-flex items-center space-x-2 rounded-r-md border border-gray-300 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
<form wire:submit.prevent="handleCoupon" class="">
@csrf
<div class="mt-4">
<label for="coupon" class="block text-sm font-medium text-white">{{ ctrans('texts.promo_code') }}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<div class="relative flex flex-grow items-stretch focus-within:z-10">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
</div>
<input type="text" wire:model.defer="coupon" class="block w-full rounded-none rounded-l-md border-gray-300 pl-2 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm text-gray-700" placeholder="">
</div>
<button class="relative -ml-px inline-flex items-center space-x-2 rounded-r-md border border-gray-300 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
<span>{{ ctrans('texts.apply') }}</span>
</button>
</div>
</div>
</form>
<span>{{ ctrans('texts.apply') }}</span>
</button>
</div>
</div>
</form>
@endif
<div class="border-t-2 border-gray-200 border-opacity-50 mt-8">
<div class="flex font-semibold justify-between py-6 text-sm uppercase">
<span>{{ ctrans('texts.total') }}</span>
<span>{{ $total }}</span>
</div>
<button class="bg-white font-semibold hover:bg-gray-600 py-3 text-sm text-blue-500 uppercase w-full">Checkout</button>
<div class="border-t-2 border-gray-200 border-opacity-50 mt-4">
@if($discount)
<div class="flex font-semibold justify-between py-1 text-sm uppercase">
<span>{{ ctrans('texts.subtotal') }}</span>
<span>{{ $sub_total }}</span>
</div>
<div class="flex font-semibold justify-between py-1 text-sm uppercase">
<span>{{ ctrans('texts.discount') }}</span>
<span>{{ $discount }}</span>
</div>
@endif
<div class="flex font-semibold justify-between py-1 text-sm uppercase border-t-2">
<span>{{ ctrans('texts.total') }}</span>
<span>{{ $total }}</span>
</div>
@if($authenticated)
<button class="bg-white font-semibold hover:bg-gray-600 py-3 text-sm text-blue-500 uppercase w-full">Checkout</button>
@else
<form wire:submit.prevent="handleEmail" class="">
@csrf
<div class="mt-4">
<label for="email" class="block text-sm font-medium text-white">{{ ctrans('texts.email') }}</label>
<div class="mt-1 flex rounded-md shadow-sm">
<div class="relative flex flex-grow items-stretch focus-within:z-10">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
</div>
<input type="text" wire:model.defer="email" class="block w-full rounded-none rounded-l-md border-gray-300 pl-2 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm text-gray-700" placeholder="">
</div>
<button class="relative -ml-px inline-flex items-center space-x-2 rounded-r-md border border-gray-300 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
<span>{{ ctrans('texts.login') }}</span>
</button>
</div>
@error("email")
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{{ $message }} </span>
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
</div>
@enderror
</div>
</form>
@endif
@if($email && !$errors->has('email'))
<div class="py-6 px-6 w-80 border mx-auto text-center my-6">
<form wire:submit.prevent="handleLogin" class="" x-data="otpForm()">
<p class="mb-4">{{ ctrans('texts.otp_code_message')}}</p>
<div class="flex justify-between">
<template x-for="(input, index) in length" :key="index">
<input
type="text"
maxlength="1"
class="border border-gray-500 w-10 h-10 text-center text-gray-700"
:x-ref="index"
x-on:input="handleInput($event)"
x-on:paste="handlePaste($event)"
x-on:keydown.backspace="$event.target.value || handleBackspace($event.target.getAttribute('x-ref'))"
/>
</template>
</div>
</form>
@error("login")
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<span class="block sm:inline">{{ $message }} </span>
<span class="absolute top-0 bottom-0 right-0 px-4 py-3">
</div>
@enderror
</div>
@endif
</div>
</div>
</div>
</div>
<script>
function otpForm() {
return {
length: 6,
login: "",
handleInput(e) {
const input = e.target;
this.login = Array.from(Array(this.length), (element, i) => {
return this.$refs[i].value || "";
}).join("");
if (input.nextElementSibling && input.value) {
input.nextElementSibling.focus();
input.nextElementSibling.select();
}
if(this.login.length == 6){
this.$wire.handleLogin(this.login);
}
},
handlePaste(e) {
const paste = e.clipboardData.getData('text');
this.value = paste;
const inputs = Array.from(Array(this.length));
inputs.forEach((element, i) => {
this.$refs[i].value = paste[i] || '';
});
},
handleBackspace(e) {
const previous = parseInt(e, 10) - 1;
this.$refs[previous] && this.$refs[previous].focus();
this.$wire.loginValidation();
},
};
}
</script>
</div>

View File

@ -0,0 +1,26 @@
@extends('portal.ninja2020.layout.clean')
@section('meta_title', 'Mollie')
@section('body')
<div class="grid lg:grid-cols-3">
<div class="hidden lg:block col-span-1 bg-red-100 h-screen">
<img src="{{ asset('images/client-portal-new-image.jpg') }}"
class="w-full h-screen object-cover"
alt="Background image">
</div>
<div class="col-span-2 h-screen flex">
<div class="m-auto md:w-1/2 lg:w-1/4 flex flex-col items-center">
<span class="flex items-center text-2xl">
{{ ctrans('texts.payment_error_code',['code' => 500]) }}
</span>
<a class="button-link text-sm mt-2" href="{{ url(request()->getSchemeAndHttpHost() . '/client') }}">
{{ ctrans('texts.back_to', ['url' => parse_url(request()->getHttpHost())['host'] ?? request()->getHttpHost()]) }}
</a>
</div>
</div>
</div>
@endsection

View File

@ -66,8 +66,6 @@
<script src="{{ asset('vendor/alpinejs@2.8.2/alpine.js') }}" defer></script>
<!-- Fonts -->
{{-- <link rel="dns-prefetch" href="https://fonts.gstatic.com"> --}}
{{-- <link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet" type="text/css" defer> --}}
<style>
@font-face {
font-family: 'Open Sans';

View File

@ -3,7 +3,7 @@
<head>
<!-- Error: {{ session('error') }} -->
@if (isset($company) && $company->matomo_url && $company->matomo_id)
@if (isset($company) && $company->matomo_url && $company->matomo_id)
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
@ -66,9 +66,6 @@
<script src="{{ asset('vendor/alpinejs@2.8.2/alpine.js') }}" defer></script>
<!-- Fonts -->
{{-- <link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet" type="text/css" defer>
--}}
<style>
@font-face {
font-family: 'Open Sans';