mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-07 10:54:41 -04:00
New payment flow (#64)
* remove context from invoice-pay * withsecurecontext trait * update usages * wip * wip * wip * wip * wip
This commit is contained in:
parent
e70de19c9d
commit
8d4ab0cd69
@ -41,7 +41,7 @@ class InvoicePay extends Component
|
|||||||
'client_postal_code' => 'postal_code',
|
'client_postal_code' => 'postal_code',
|
||||||
'client_country_id' => 'country_id',
|
'client_country_id' => 'country_id',
|
||||||
|
|
||||||
'client_shipping_address_line_1' => 'shipping_address1',
|
'client_shipping_address_line_1' => 'shipping_address1',
|
||||||
'client_shipping_address_line_2' => 'shipping_address2',
|
'client_shipping_address_line_2' => 'shipping_address2',
|
||||||
'client_shipping_city' => 'shipping_city',
|
'client_shipping_city' => 'shipping_city',
|
||||||
'client_shipping_state' => 'shipping_state',
|
'client_shipping_state' => 'shipping_state',
|
||||||
@ -143,7 +143,7 @@ class InvoicePay extends Component
|
|||||||
|
|
||||||
$this->payment_method_accepted = true;
|
$this->payment_method_accepted = true;
|
||||||
|
|
||||||
$company_gateway = CompanyGateway::query()->find($company_gateway_id);
|
$company_gateway = CompanyGateway::find($company_gateway_id);
|
||||||
|
|
||||||
$this->checkRequiredFields($company_gateway);
|
$this->checkRequiredFields($company_gateway);
|
||||||
}
|
}
|
||||||
@ -165,7 +165,6 @@ class InvoicePay extends Component
|
|||||||
return $this->required_fields = true;
|
return $this->required_fields = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var \App\Models\ClientContact $contact */
|
|
||||||
$contact = $this->getContext()['contact'];
|
$contact = $this->getContext()['contact'];
|
||||||
|
|
||||||
foreach ($fields as $index => $field) {
|
foreach ($fields as $index => $field) {
|
||||||
@ -174,7 +173,7 @@ class InvoicePay extends Component
|
|||||||
if (\Illuminate\Support\Str::startsWith($field['name'], 'client_')) {
|
if (\Illuminate\Support\Str::startsWith($field['name'], 'client_')) {
|
||||||
if (
|
if (
|
||||||
empty($contact->client->{$_field})
|
empty($contact->client->{$_field})
|
||||||
|| is_null($contact->client->{$_field}) //@phpstan-ignore-line
|
|| is_null($contact->client->{$_field})
|
||||||
) {
|
) {
|
||||||
|
|
||||||
return $this->required_fields = true;
|
return $this->required_fields = true;
|
||||||
@ -183,12 +182,12 @@ class InvoicePay extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (\Illuminate\Support\Str::startsWith($field['name'], 'contact_')) {
|
if (\Illuminate\Support\Str::startsWith($field['name'], 'contact_')) {
|
||||||
if (empty($contact->{$_field}) || is_null($contact->{$_field}) || str_contains($contact->{$_field}, '@example.com')) { //@phpstan-ignore-line
|
if (empty($contact->{$_field}) || is_null($contact->{$_field}) || str_contains($contact->{$_field}, '@example.com')) {
|
||||||
return $this->required_fields = true;
|
return $this->required_fields = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->required_fields = false;
|
return $this->required_fields = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -241,10 +240,9 @@ class InvoicePay extends Component
|
|||||||
|
|
||||||
nlog($this->invoices);
|
nlog($this->invoices);
|
||||||
|
|
||||||
if(is_array($this->invoices)) {
|
if(is_array($this->invoices))
|
||||||
$this->invoices = Invoice::find($this->transformKeys($this->invoices));
|
$this->invoices = Invoice::find($this->transformKeys($this->invoices));
|
||||||
}
|
|
||||||
|
|
||||||
$invoices = $this->invoices->filter(function ($i) {
|
$invoices = $this->invoices->filter(function ($i) {
|
||||||
$i = $i->service()
|
$i = $i->service()
|
||||||
->markSent()
|
->markSent()
|
||||||
|
@ -42,6 +42,6 @@ class InvoiceSummary extends Component
|
|||||||
'invoice' => $this->invoices,
|
'invoice' => $this->invoices,
|
||||||
'client' => $this->invoices->first()->client,
|
'client' => $this->invoices->first()->client,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,4 +75,4 @@ class PaymentMethod extends Component
|
|||||||
{
|
{
|
||||||
return render('flow2.payment-method', ['methods' => $this->methods]);
|
return render('flow2.payment-method', ['methods' => $this->methods]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,285 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoice Ninja (https://invoiceninja.com).
|
|
||||||
*
|
|
||||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
|
||||||
*
|
|
||||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
|
||||||
*
|
|
||||||
* @license https://www.elastic.co/licensing/elastic-license
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Livewire;
|
|
||||||
|
|
||||||
use App\Utils\Number;
|
|
||||||
use App\Models\Invoice;
|
|
||||||
use Livewire\Component;
|
|
||||||
use App\Utils\HtmlEngine;
|
|
||||||
use App\Libraries\MultiDB;
|
|
||||||
use Livewire\Attributes\On;
|
|
||||||
use App\Livewire\Flow2\Terms;
|
|
||||||
use App\Models\CompanyGateway;
|
|
||||||
use App\Utils\Traits\MakesHash;
|
|
||||||
use App\Utils\Traits\MakesDates;
|
|
||||||
use App\Livewire\Flow2\Signature;
|
|
||||||
use Livewire\Attributes\Computed;
|
|
||||||
use Livewire\Attributes\Reactive;
|
|
||||||
use App\Livewire\Flow2\PaymentMethod;
|
|
||||||
use App\Livewire\Flow2\ProcessPayment;
|
|
||||||
use App\Livewire\Flow2\RequiredFields;
|
|
||||||
use App\Livewire\Flow2\UnderOverPayment;
|
|
||||||
|
|
||||||
class InvoicePay extends Component
|
|
||||||
{
|
|
||||||
use MakesDates;
|
|
||||||
use MakesHash;
|
|
||||||
|
|
||||||
private $mappings = [
|
|
||||||
'client_name' => 'name',
|
|
||||||
'client_website' => 'website',
|
|
||||||
'client_phone' => 'phone',
|
|
||||||
|
|
||||||
'client_address_line_1' => 'address1',
|
|
||||||
'client_address_line_2' => 'address2',
|
|
||||||
'client_city' => 'city',
|
|
||||||
'client_state' => 'state',
|
|
||||||
'client_postal_code' => 'postal_code',
|
|
||||||
'client_country_id' => 'country_id',
|
|
||||||
|
|
||||||
'client_shipping_address_line_1' => 'shipping_address1',
|
|
||||||
'client_shipping_address_line_2' => 'shipping_address2',
|
|
||||||
'client_shipping_city' => 'shipping_city',
|
|
||||||
'client_shipping_state' => 'shipping_state',
|
|
||||||
'client_shipping_postal_code' => 'shipping_postal_code',
|
|
||||||
'client_shipping_country_id' => 'shipping_country_id',
|
|
||||||
|
|
||||||
'client_custom_value1' => 'custom_value1',
|
|
||||||
'client_custom_value2' => 'custom_value2',
|
|
||||||
'client_custom_value3' => 'custom_value3',
|
|
||||||
'client_custom_value4' => 'custom_value4',
|
|
||||||
|
|
||||||
'contact_first_name' => 'first_name',
|
|
||||||
'contact_last_name' => 'last_name',
|
|
||||||
'contact_email' => 'email',
|
|
||||||
// 'contact_phone' => 'phone',
|
|
||||||
];
|
|
||||||
|
|
||||||
public $client_address_array = [
|
|
||||||
'address1',
|
|
||||||
'address2',
|
|
||||||
'city',
|
|
||||||
'state',
|
|
||||||
'postal_code',
|
|
||||||
'country_id',
|
|
||||||
'shipping_address1',
|
|
||||||
'shipping_address2',
|
|
||||||
'shipping_city',
|
|
||||||
'shipping_state',
|
|
||||||
'shipping_postal_code',
|
|
||||||
'shipping_country_id',
|
|
||||||
];
|
|
||||||
|
|
||||||
public $invitation_id;
|
|
||||||
|
|
||||||
public $invoices;
|
|
||||||
|
|
||||||
public $variables;
|
|
||||||
|
|
||||||
public $db;
|
|
||||||
|
|
||||||
public $settings;
|
|
||||||
|
|
||||||
public $terms_accepted = false;
|
|
||||||
|
|
||||||
public $signature_accepted = false;
|
|
||||||
|
|
||||||
public $payment_method_accepted = false;
|
|
||||||
|
|
||||||
public $under_over_payment = false;
|
|
||||||
|
|
||||||
public $required_fields = false;
|
|
||||||
|
|
||||||
public array $context = [];
|
|
||||||
|
|
||||||
#[On('update.context')]
|
|
||||||
public function handleContext(string $property, $value): self
|
|
||||||
{
|
|
||||||
|
|
||||||
data_set($this->context, $property, $value);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[On('terms-accepted')]
|
|
||||||
public function termsAccepted()
|
|
||||||
{
|
|
||||||
nlog("Terms accepted");
|
|
||||||
// $this->invite = \App\Models\InvoiceInvitation::withTrashed()->find($this->invitation_id)->withoutRelations();
|
|
||||||
$this->terms_accepted =true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[On('signature-captured')]
|
|
||||||
public function signatureCaptured($base64)
|
|
||||||
{
|
|
||||||
nlog("signature captured");
|
|
||||||
|
|
||||||
$this->signature_accepted = true;
|
|
||||||
$invite = \App\Models\InvoiceInvitation::withTrashed()->find($this->invitation_id)->withoutRelations();
|
|
||||||
$invite->signature_base64 = $base64;
|
|
||||||
$invite->signature_date = now()->addSeconds($invite->contact->client->timezone_offset());
|
|
||||||
$this->context['signature'] = $base64;
|
|
||||||
$invite->save();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[On('payable-amount')]
|
|
||||||
public function payableAmount($payable_amount)
|
|
||||||
{
|
|
||||||
$this->context['payable_invoices'][0]['amount'] = Number::parseFloat($payable_amount);
|
|
||||||
$this->under_over_payment = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[On('payment-method-selected')]
|
|
||||||
public function paymentMethodSelected($company_gateway_id, $gateway_type_id, $amount)
|
|
||||||
{
|
|
||||||
//@TODO only handles single invoice scenario
|
|
||||||
$this->context['company_gateway_id'] = $company_gateway_id;
|
|
||||||
$this->context['gateway_type_id'] = $gateway_type_id;
|
|
||||||
$this->context['amount'] = $amount;
|
|
||||||
$this->context['pre_payment'] = false;
|
|
||||||
$this->context['is_recurring'] = false;
|
|
||||||
$this->context['invitation_id'] = $this->invitation_id;
|
|
||||||
|
|
||||||
$this->payment_method_accepted = true;
|
|
||||||
|
|
||||||
$company_gateway = CompanyGateway::find($company_gateway_id);
|
|
||||||
|
|
||||||
$this->checkRequiredFields($company_gateway);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[On('required-fields')]
|
|
||||||
public function requiredFieldsFilled()
|
|
||||||
{
|
|
||||||
$this->required_fields = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function checkRequiredFields(CompanyGateway $company_gateway)
|
|
||||||
{
|
|
||||||
|
|
||||||
$fields = $company_gateway->driver()->getClientRequiredFields();
|
|
||||||
$this->context['fields'] = $fields;
|
|
||||||
|
|
||||||
if($company_gateway->always_show_required_fields){
|
|
||||||
return $this->required_fields = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$contact = $this->context['contact'];
|
|
||||||
|
|
||||||
foreach ($fields as $index => $field) {
|
|
||||||
$_field = $this->mappings[$field['name']];
|
|
||||||
|
|
||||||
if (\Illuminate\Support\Str::startsWith($field['name'], 'client_')) {
|
|
||||||
if (empty($contact->client->{$_field})
|
|
||||||
|| is_null($contact->client->{$_field})
|
|
||||||
) {
|
|
||||||
|
|
||||||
return $this->required_fields = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (\Illuminate\Support\Str::startsWith($field['name'], 'contact_')) {
|
|
||||||
if (empty($contact->{$_field}) || is_null($contact->{$_field}) || str_contains($contact->{$_field}, '@example.com')) {
|
|
||||||
return $this->required_fields = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Computed()]
|
|
||||||
public function component(): string
|
|
||||||
{
|
|
||||||
if(!$this->terms_accepted)
|
|
||||||
return Terms::class;
|
|
||||||
|
|
||||||
if(!$this->signature_accepted)
|
|
||||||
return Signature::class;
|
|
||||||
|
|
||||||
if($this->under_over_payment)
|
|
||||||
return UnderOverPayment::class;
|
|
||||||
|
|
||||||
if(!$this->payment_method_accepted)
|
|
||||||
return PaymentMethod::class;
|
|
||||||
|
|
||||||
if($this->required_fields)
|
|
||||||
return RequiredFields::class;
|
|
||||||
|
|
||||||
return ProcessPayment::class;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Computed()]
|
|
||||||
public function componentUniqueId(): string
|
|
||||||
{
|
|
||||||
return "purchase-".md5(microtime());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
|
|
||||||
MultiDB::setDb($this->db);
|
|
||||||
|
|
||||||
// @phpstan-ignore-next-line
|
|
||||||
$invite = \App\Models\InvoiceInvitation::with('contact.client','company')->withTrashed()->find($this->invitation_id);
|
|
||||||
$client = $invite->contact->client;
|
|
||||||
$settings = $client->getMergedSettings();
|
|
||||||
$this->context['contact'] = $invite->contact;
|
|
||||||
$this->context['settings'] = $settings;
|
|
||||||
$this->context['db'] = $this->db;
|
|
||||||
|
|
||||||
$invoices = Invoice::find($this->transformKeys($this->invoices));
|
|
||||||
$invoices = $invoices->filter(function ($i){
|
|
||||||
|
|
||||||
$i = $i->service()
|
|
||||||
->markSent()
|
|
||||||
->removeUnpaidGatewayFees()
|
|
||||||
->save();
|
|
||||||
|
|
||||||
return $i->isPayable();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
//under-over / payment
|
|
||||||
|
|
||||||
//required fields
|
|
||||||
$this->terms_accepted = !$settings->show_accept_invoice_terms;
|
|
||||||
$this->signature_accepted = !$settings->require_invoice_signature;
|
|
||||||
$this->under_over_payment = $settings->client_portal_allow_over_payment || $settings->client_portal_allow_under_payment;
|
|
||||||
$this->required_fields = false;
|
|
||||||
|
|
||||||
$this->context['variables'] = $this->variables;
|
|
||||||
$this->context['invoices'] = $invoices;
|
|
||||||
$this->context['settings'] = $settings;
|
|
||||||
$this->context['invitation'] = $invite;
|
|
||||||
|
|
||||||
$this->context['payable_invoices'] = $invoices->map(function ($i){
|
|
||||||
return [
|
|
||||||
'invoice_id' => $i->hashed_id,
|
|
||||||
'amount' => $i->partial > 0 ? $i->partial : $i->balance,
|
|
||||||
'formatted_amount' => Number::formatValue($i->partial > 0 ? $i->partial : $i->balance, $i->client->currency()),
|
|
||||||
'number' => $i->number,
|
|
||||||
'date' => $i->translateDate($i->date, $i->client->date_format(), $i->client->locale())
|
|
||||||
];
|
|
||||||
})->toArray();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return render('components.livewire.invoice-pay', [
|
|
||||||
'context' => $this->context
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoice Ninja (https://invoiceninja.com).
|
|
||||||
*
|
|
||||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
|
||||||
*
|
|
||||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
|
||||||
*
|
|
||||||
* @license https://www.elastic.co/licensing/elastic-license
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Livewire;
|
|
||||||
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class InvoiceSummary extends Component
|
|
||||||
{
|
|
||||||
public $context;
|
|
||||||
|
|
||||||
public $invoice;
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
//@TODO for a single invoice - show all details, for multi-invoices, only show the summaries
|
|
||||||
$this->invoice = $this->context['invitation']->invoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return render('components.livewire.invoice-summary',[
|
|
||||||
'invoice' => $this->invoice
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
9
public/build/assets/stripe-credit-card-c690d3d4.js
vendored
Normal file
9
public/build/assets/stripe-credit-card-c690d3d4.js
vendored
Normal file
@ -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
|
||||||
|
*/class l{constructor(e,t,n,o){this.key=e,this.secret=t,this.onlyAuthorization=n,this.stripeConnect=o}setupStripe(){return this.stripeConnect?this.stripe=Stripe(this.key,{stripeAccount:this.stripeConnect}):this.stripe=Stripe(this.key),this.elements=this.stripe.elements(),this}createElement(){var e;return this.cardElement=this.elements.create("card",{hidePostalCode:((e=document.querySelector("meta[name=stripe-require-postal-code]"))==null?void 0:e.content)==="0",value:{postalCode:document.querySelector("meta[name=client-postal-code]").content},hideIcon:!1}),this}mountCardElement(){return this.cardElement.mount("#card-element"),this}completePaymentUsingToken(){let e=document.querySelector("input[name=token]").value,t=document.getElementById("pay-now");this.payNowButton=t,this.payNowButton.disabled=!0,this.payNowButton.querySelector("svg").classList.remove("hidden"),this.payNowButton.querySelector("span").classList.add("hidden"),this.stripe.handleCardPayment(this.secret,{payment_method:e}).then(n=>n.error?this.handleFailure(n.error.message):this.handleSuccess(n))}completePaymentWithoutToken(){let e=document.getElementById("pay-now");this.payNowButton=e,this.payNowButton.disabled=!0,this.payNowButton.querySelector("svg").classList.remove("hidden"),this.payNowButton.querySelector("span").classList.add("hidden");let t=document.getElementById("cardholder-name");this.stripe.handleCardPayment(this.secret,this.cardElement,{payment_method_data:{billing_details:{name:t.value}}}).then(n=>n.error?this.handleFailure(n.error.message):this.handleSuccess(n))}handleSuccess(e){document.querySelector('input[name="gateway_response"]').value=JSON.stringify(e.paymentIntent);let t=document.querySelector('input[name="token-billing-checkbox"]:checked');t&&(document.querySelector('input[name="store_card"]').value=t.value),document.getElementById("server-response").submit()}handleFailure(e){let t=document.getElementById("errors");t.textContent="",t.textContent=e,t.hidden=!1,this.payNowButton.disabled=!1,this.payNowButton.querySelector("svg").classList.add("hidden"),this.payNowButton.querySelector("span").classList.remove("hidden")}handleAuthorization(){let e=document.getElementById("cardholder-name"),t=document.getElementById("authorize-card");this.payNowButton=t,this.payNowButton.disabled=!0,this.payNowButton.querySelector("svg").classList.remove("hidden"),this.payNowButton.querySelector("span").classList.add("hidden"),this.stripe.handleCardSetup(this.secret,this.cardElement,{payment_method_data:{billing_details:{name:e.value}}}).then(n=>n.error?this.handleFailure(n.error.message):this.handleSuccessfulAuthorization(n))}handleSuccessfulAuthorization(e){document.getElementById("gateway_response").value=JSON.stringify(e.setupIntent),document.getElementById("server_response").submit()}handle(){this.setupStripe(),this.onlyAuthorization?(this.createElement().mountCardElement(),document.getElementById("authorize-card").addEventListener("click",()=>this.handleAuthorization())):(Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach(e=>e.addEventListener("click",t=>{document.getElementById("stripe--payment-container").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=t.target.dataset.token})),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",e=>{document.getElementById("stripe--payment-container").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value=""}),this.createElement().mountCardElement(),document.getElementById("pay-now").addEventListener("click",()=>{try{return document.querySelector("input[name=token]").value?this.completePaymentUsingToken():this.completePaymentWithoutToken()}catch(e){console.log(e.message)}}))}}Livewire.hook("component.init",()=>{var a,i,s,d;console.log("running now");const r=((a=document.querySelector('meta[name="stripe-publishable-key"]'))==null?void 0:a.content)??"",e=((i=document.querySelector('meta[name="stripe-secret"]'))==null?void 0:i.content)??"",t=((s=document.querySelector('meta[name="only-authorization"]'))==null?void 0:s.content)??"",n=((d=document.querySelector('meta[name="stripe-account-id"]'))==null?void 0:d.content)??"";let o=new l(r,e,t,n);o.handle(),document.addEventListener("livewire:init",()=>{Livewire.on("passed-required-fields-check",()=>o.handle())})});
|
@ -243,6 +243,7 @@
|
|||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
|
<<<<<<< HEAD
|
||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
"file": "assets/app-fee1da41.css",
|
"file": "assets/app-fee1da41.css",
|
||||||
=======
|
=======
|
||||||
@ -257,6 +258,9 @@
|
|||||||
=======
|
=======
|
||||||
"file": "assets/app-8544e4cc.css",
|
"file": "assets/app-8544e4cc.css",
|
||||||
>>>>>>> c7cc0e084f (updates for html invoice layout)
|
>>>>>>> c7cc0e084f (updates for html invoice layout)
|
||||||
|
=======
|
||||||
|
"file": "assets/app-608daae2.css",
|
||||||
|
>>>>>>> 2a1947ea6e (New payment flow (#64))
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"src": "resources/sass/app.scss"
|
"src": "resources/sass/app.scss"
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
<div class="grid grid-cols-1 md:grid-cols-2">
|
|
||||||
<div class="p-2">
|
|
||||||
@livewire('invoice-summary',['context' => $context])
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-2">
|
|
||||||
@livewire($this->component,['context' => $context], key($this->componentUniqueId()))
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
@ -1,66 +0,0 @@
|
|||||||
<div class="flex flex-col space-y-4 p-4" x-data="{ isLoading: @entangle('isLoading') }">
|
|
||||||
|
|
||||||
<div x-show="isLoading" class="flex items-center justify-center min-h-screen">
|
|
||||||
<svg class="animate-spin h-10 w-10 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@foreach($methods as $index => $method)
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="button button-primary bg-primary payment-method flex items-center justify-center relative py-4"
|
|
||||||
@click="$wire.dispatch('payment-method-selected', { company_gateway_id: {{ $method['company_gateway_id'] }}, gateway_type_id: {{ $method['gateway_type_id'] }}, amount: {{ $amount }} })">
|
|
||||||
<svg class="animate-spin h-5 w-5 text-white hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
<span>{{ $method['label'] }}</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
@endforeach
|
|
||||||
|
|
||||||
|
|
||||||
@script
|
|
||||||
<script>
|
|
||||||
|
|
||||||
Livewire.on('loadingCompleted', () => {
|
|
||||||
isLoading = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
Livewire.on('singlePaymentMethodFound', (event) => {
|
|
||||||
$wire.dispatch('payment-method-selected', {company_gateway_id: event.company_gateway_id, gateway_type_id: event.gateway_type_id, amount: event.amount })
|
|
||||||
});
|
|
||||||
|
|
||||||
const buttons = document.querySelectorAll('.payment-method');
|
|
||||||
|
|
||||||
buttons.forEach(button => {
|
|
||||||
button.addEventListener('click', (event) => {
|
|
||||||
// Hide all buttons except the clicked one
|
|
||||||
buttons.forEach(btn => {
|
|
||||||
if (btn !== event.currentTarget) {
|
|
||||||
btn.style.display = 'none';
|
|
||||||
} else {
|
|
||||||
// Disable the clicked button
|
|
||||||
btn.disabled = true;
|
|
||||||
|
|
||||||
// Show the spinner by removing the 'hidden' class
|
|
||||||
const spinner = btn.querySelector('svg');
|
|
||||||
if (spinner) {
|
|
||||||
spinner.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
const span = btn.querySelector('span');
|
|
||||||
if (span) {
|
|
||||||
span.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@endscript
|
|
||||||
</div>
|
|
@ -1,37 +0,0 @@
|
|||||||
<div x-data="{ fields: @entangle('fields'), contact: @entangle('contact') }" class="px-4 py-5 bg-white sm:gap-4 sm:px-6">
|
|
||||||
@foreach($fields as $field)
|
|
||||||
|
|
||||||
@component('portal.ninja2020.components.general.card-element', ['title' => $field['label']])
|
|
||||||
@if($field['name'] == 'client_country_id' || $field['name'] == 'client_shipping_country_id')
|
|
||||||
<select id="client_country" class="input w-full form-select bg-white" name="{{ $field['name'] }}" wire:model="{{ $field['name'] }}">
|
|
||||||
<option value="none"></option>
|
|
||||||
|
|
||||||
@foreach($countries as $country)
|
|
||||||
<option value="{{ $country->id }}">
|
|
||||||
{{ $country->iso_3166_2 }} ({{ $country->name }})
|
|
||||||
</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
@else
|
|
||||||
<input class="input w-full" type="{{ $field['type'] ?? 'text' }}" name="{{ $field['name'] }}" wire:model="{{ $field['name'] }}">
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@if(session()->has('validation_errors') && array_key_exists($field['name'], session('validation_errors')))
|
|
||||||
<p class="mt-2 text-gray-900 border-red-300 px-2 py-1 bg-gray-100">{{ session('validation_errors')[$field['name']][0] }}</p>
|
|
||||||
@endif
|
|
||||||
@endcomponent
|
|
||||||
|
|
||||||
@endforeach
|
|
||||||
|
|
||||||
<div class="bg-white px-4 py-5 flex w-full justify-end">
|
|
||||||
<button
|
|
||||||
class="button button-primary bg-primary payment-method flex items-center justify-center relative py-4"
|
|
||||||
@click="$wire.dispatch('required-fields')">
|
|
||||||
<svg class="animate-spin h-5 w-5 text-white hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
<span>{{ ctrans('texts.next') }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,45 +0,0 @@
|
|||||||
<div x-data="{ payableInvoices: @entangle('payableInvoices'), errors: @entangle('errors') }" class="px-4 py-5 bg-white sm:gap-4 sm:px-6">
|
|
||||||
|
|
||||||
<dt class="text-sm font-medium leading-5 text-gray-500 mb-3">
|
|
||||||
{{ ctrans('texts.payment_amount') }}
|
|
||||||
</dt>
|
|
||||||
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2 flex flex-col">
|
|
||||||
|
|
||||||
|
|
||||||
<template x-for="(invoice, index) in payableInvoices" :key="index">
|
|
||||||
|
|
||||||
<div class="flex items-center mb-2">
|
|
||||||
<label>
|
|
||||||
<span x-text="'{{ ctrans('texts.invoice') }} ' + invoice.number" class="mt-2"></span>
|
|
||||||
<span class="pr-2">{{ $currency->code }} ({{ $currency->symbol }})</span>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="input mt-0 mr-4 relative"
|
|
||||||
name="payable_invoices[]"
|
|
||||||
x-model="payableInvoices[index].formatted_amount"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template x-if="errors.length > 0">
|
|
||||||
<div x-text="errors" class="alert alert-failure mb-4"></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
@if($settings->client_portal_allow_under_payment)
|
|
||||||
<span class="mt-1 text-sm text-gray-800">{{ ctrans('texts.minimum_payment') }}: {{ $settings->client_portal_under_payment_minimum }}</span>
|
|
||||||
@endif
|
|
||||||
</dd>
|
|
||||||
|
|
||||||
<div class="bg-white px-4 py-5 flex w-full justify-end">
|
|
||||||
<button
|
|
||||||
class="button button-primary bg-primary payment-method flex items-center justify-center relative py-4"
|
|
||||||
wire:click="checkValue(payableInvoices)">
|
|
||||||
<svg class="animate-spin h-5 w-5 text-white hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
<span>{{ ctrans('texts.next') }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,4 +1,4 @@
|
|||||||
<div style="w-full">
|
<div class="w-full">
|
||||||
<div class="rounded-lg border bg-card bg-white text-card-foreground shadow-sm overflow-hidden" x-chunk="An order details card with order details, shipping information, customer information and payment information.">
|
<div class="rounded-lg border bg-card bg-white text-card-foreground shadow-sm overflow-hidden" x-chunk="An order details card with order details, shipping information, customer information and payment information.">
|
||||||
<div class="space-y-1.5 p-6 flex flex-row items-start bg-muted/50">
|
<div class="space-y-1.5 p-6 flex flex-row items-start bg-muted/50">
|
||||||
<div class="grid gap-0.5">
|
<div class="grid gap-0.5">
|
||||||
@ -14,7 +14,7 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<p class="text-sm text-muted-foreground">{{ ctrans('texts.date') }}: {{ $invoice->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()) }}</p>
|
<p class="text-sm text-muted-foreground">{{ ctrans('texts.date') }}: {{ $invoice->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()) }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6 text-sm">
|
<div class="p-6 text-sm">
|
||||||
<div class="grid gap-3">
|
<div class="grid gap-3">
|
||||||
@ -64,4 +64,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -62,4 +62,4 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@endscript
|
@endscript
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,4 +58,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="bg-white">
|
<div class="rounded-lg border bg-card text-card-foreground shadow-sm overflow-hidden py-5 bg-white sm:gap-4">
|
||||||
@if($stripe_account_id)
|
@if($stripe_account_id)
|
||||||
<meta name="stripe-account-id" content="{{ $stripe_account_id }}">
|
<meta name="stripe-account-id" content="{{ $stripe_account_id }}">
|
||||||
<meta name="stripe-publishable-key" content="{{ config('ninja.ninja_stripe_publishable_key') }}">
|
<meta name="stripe-publishable-key" content="{{ config('ninja.ninja_stripe_publishable_key') }}">
|
||||||
|
@ -26,4 +26,4 @@
|
|||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@push('head')
|
@push('head')
|
||||||
@endpush
|
@endpush
|
||||||
|
Loading…
x
Reference in New Issue
Block a user