This commit is contained in:
David Bomba 2024-09-17 10:17:07 +10:00
commit 20b04a266f
27 changed files with 780 additions and 624 deletions

View File

@ -17,7 +17,7 @@ use App\Models\Subscription;
use Illuminate\Support\Facades\Cache;
class Methods extends Component
{
{//@todo this breaks down when the cart is in front of the login - we have no context on the user - nor their country/currency
public Subscription $subscription;
public array $context;
@ -28,10 +28,7 @@ class Methods extends Component
{
$total = collect($this->context['products'])->sum('total_raw');
$methods = auth()->guard('contact')->user()->client->service()->getPaymentMethods(
$total,
);
$methods = auth()->guard('contact')->user()->client->service()->getPaymentMethods($total); //@todo this breaks down when the cart is in front of the login - we have no context on the user - nor their country/currency()
$this->methods = $methods;
}

View File

@ -199,7 +199,8 @@ class BillingPortalPurchasev2 extends Component
$this->data = [];
$this->price = $this->subscription->price; // ?
$this->float_amount_total = $this->price;
$this->recurring_products = $this->subscription->service()->recurring_products();
$this->products = $this->subscription->service()->products();
$this->optional_recurring_products = $this->subscription->service()->optional_recurring_products();
@ -244,7 +245,8 @@ class BillingPortalPurchasev2 extends Component
Auth::guard('contact')->loginUsingId($contact->id, true);
$this->contact = $contact;
} else {
$this->createClientContact();
// $this->createClientContact();
$this->createBlankClient();
}
$this->getPaymentMethods();
@ -767,6 +769,8 @@ class BillingPortalPurchasev2 extends Component
if ($currency) {
$data['settings']->currency_id = $currency->id;
}
}else {
$data['settings']->currency_id = $this->subscription->company->getSetting('currency_id');
}
if (array_key_exists('locale', $this->request_data)) {
@ -785,8 +789,12 @@ class BillingPortalPurchasev2 extends Component
}
$client = $client_repo->save($data, ClientFactory::create($company->id, $user->id));
$contact = $client->fresh()->contacts->first();
$this->contact = $contact;
return $client->fresh()->contacts->first();
Auth::guard('contact')->loginUsingId($contact->id, true);
return $contact;
}

View File

@ -52,11 +52,11 @@ class UnderOverPayment extends Component
$input_amount = collect($payableInvoices)->sum('amount');
if($settings->client_portal_allow_under_payment && $settings->client_portal_under_payment_minimum != 0)
if($settings->client_portal_allow_under_payment)
{
if($input_amount <= $settings->client_portal_under_payment_minimum){
if($input_amount <= $settings->client_portal_under_payment_minimum || $input_amount <= 0){
// return error message under payment too low.
$this->errors = ctrans('texts.minimum_required_payment', ['amount' => $settings->client_portal_under_payment_minimum]);
$this->errors = ctrans('texts.minimum_required_payment', ['amount' => max($settings->client_portal_under_payment_minimum, 1)]);
$this->dispatch('errorMessageUpdate', errors: $this->errors);
}
}

View File

@ -194,13 +194,14 @@ class RequiredClientInfo extends Component
public function mount()
{
MultiDB::setDb($this->db);
$contact = ClientContact::withTrashed()->with(['client' => function ($query) {
$query->without('gateway_tokens', 'documents', 'contacts.company', 'contacts'); // Exclude 'grandchildren' relation of 'client'
}])->find($this->contact_id);
$this->company_gateway = CompanyGateway::withTrashed()->with('company')->find($this->company_gateway_id);
$company = $this->company_gateway->company;
$this->client_name = $contact->client->name;
@ -240,6 +241,13 @@ class RequiredClientInfo extends Component
$this->checkFields();
}
if (count($this->fields) === 0) {
$this->dispatch(
'passed-required-fields-check',
client_postal_code: $contact->client->postal_code
);
}
if($this->unfilled_fields > 0 || ($this->company_gateway->always_show_required_fields || $this->is_subscription)) {
$this->show_form = true;
}
@ -385,7 +393,6 @@ class RequiredClientInfo extends Component
public function checkFields()
{
MultiDB::setDb($this->db);
$_contact = ClientContact::withTrashed()->find($this->contact_id);

View File

@ -25,6 +25,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property int $user_id
* @property int|null $assigned_user_id
* @property int $company_id
* @property int $remaining_cycles
* @property string|null $product_ids
* @property int|null $frequency_id
* @property string|null $auto_bill
@ -117,6 +118,7 @@ class Subscription extends BaseModel
'optional_recurring_product_ids',
'use_inventory_management',
'steps',
'remaining_cycles',
];
protected $casts = [

View File

@ -39,9 +39,9 @@ class CreditCard implements LivewireMethodInterface
public function authorizeView(array $data)
{
$data['payment_method_id'] = GatewayType::CREDIT_CARD;
$data['threeds'] = $this->powerboard->company_gateway->getConfigField('threeds');
$view = $this->powerboard->company_gateway->getConfigField('threeds') ? 'gateways.powerboard.credit_card.authorize' : 'gateways.powerboard.credit_card.authorize_no_3ds';
return render($view, $this->paymentData($data));
return render('gateways.powerboard.credit_card.authorize', $this->paymentData($data));
}
public function authorizeResponse($request)
@ -76,7 +76,11 @@ class CreditCard implements LivewireMethodInterface
$r = $this->powerboard->gatewayRequest('/v1/charges/3ds', (\App\Enum\HttpVerb::POST)->value, $payload, []);
if ($r->failed()) {
return $this->processUnsuccessfulPayment($r);
$error_payload = $this->getErrorFromResponse($r);
return response()->json(['message' => $error_payload[0]], 400);
// return $this->processUnsuccessfulPayment($r);
}
$charge = $r->json();
@ -93,7 +97,7 @@ class CreditCard implements LivewireMethodInterface
$payload = [
'_3ds' => [
'id' => $charge_request['charge_3ds_id'],
'id' => array_key_exists('charge_3ds_id', $charge_request) ? $charge_request['charge_3ds_id'] : $charge_request['_3ds']['id'],
],
"capture" => false,
"authorization" => true,
@ -215,6 +219,8 @@ class CreditCard implements LivewireMethodInterface
public function paymentData(array $data): array
{
$this->powerboard->init();
if($this->cba_gateway->verification_status != "completed")
throw new PaymentFailed("This payment method is not configured as yet. Reference Powerboard portal for further information", 400);
@ -260,11 +266,8 @@ class CreditCard implements LivewireMethodInterface
nlog($r->body());
if($r->failed()){
$error_payload = $this->getErrorFromResponse($r);
throw new PaymentFailed($error_payload[0], $error_payload[1]);
}
$charge = (new \App\PaymentDrivers\CBAPowerBoard\Models\Parse())->encode(Charge::class, $r->object()->resource->data) ?? $r->throw();
@ -303,7 +306,8 @@ class CreditCard implements LivewireMethodInterface
$r = $this->powerboard->gatewayRequest('/v1/charges/3ds', (\App\Enum\HttpVerb::POST)->value, $payload, []);
if ($r->failed()) {
return $this->processUnsuccessfulPayment($r);
$error_payload = $this->getErrorFromResponse($r);
return response()->json(['message' => $error_payload[0]], 400);
}
$charge = $r->json();
@ -351,7 +355,7 @@ class CreditCard implements LivewireMethodInterface
$payload = [
'_3ds' => [
'id' => $charge_request['charge_3ds_id'],
'id' => array_key_exists('charge_3ds_id', $charge_request) ? $charge_request['charge_3ds_id'] : $charge_request['_3ds']['id'],
],
"amount"=> $this->powerboard->payment_hash->data->amount_with_fee, //@phpstan-ignore-line
"currency"=> $this->powerboard->client->currency()->code,
@ -394,6 +398,9 @@ class CreditCard implements LivewireMethodInterface
}
session()->flash('message', ctrans('texts.payment_token_not_found'));
return redirect()->back();
}
public function processSuccessfulPayment(Charge $charge)
@ -442,6 +449,7 @@ class CreditCard implements LivewireMethodInterface
$error_message = "Unknown error";
match($error_object->error->code) {
"UnfulfilledCondition" => $error_message = $error_object->error->details->messages[0] ?? $error_object->error->message ?? "Unknown error",
"GatewayError" => $error_message = $error_object->error->message,
"UnfulfilledCondition" => $error_message = $error_object->error->message,
"transaction_declined" => $error_message = $error_object->error->details[0]->status_code_description,
@ -455,11 +463,29 @@ class CreditCard implements LivewireMethodInterface
}
public function processUnsuccessfulPayment($response)
{
$error_payload = $this->getErrorFromResponse($response);
$error = $this->getErrorFromResponse($response);
return response()->json(['message' => $error_payload[0], 'code' => $error_payload[1]], $error_payload[1]);
$this->powerboard->sendFailureMail($error[0]);
// $message = [
// 'server_response' => $server_response,
// 'data' => $this->stripe->payment_hash->data,
// ];
SystemLogger::dispatch(
$error[0],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_POWERBOARD,
$this->powerboard->client,
$this->powerboard->client->company,
);
if (request()->wantsJson()) {
return response()->json($error[0], 200);
}
throw new PaymentFailed('Failed to process the payment.', $error[1]);
}
}

View File

@ -161,7 +161,16 @@ class CBAPowerBoardPaymentDriver extends BaseDriver
public function getClientRequiredFields(): array
{
return [];
$fields = [];
if ($this->company_gateway->require_client_name) {
$fields[] = ['name' => 'client_name', 'label' => ctrans('texts.client_name'), 'type' => 'text', 'validation' => 'required'];
}
$fields[] = ['name' => 'contact_first_name', 'label' => ctrans('texts.first_name'), 'type' => 'text', 'validation' => 'required'];
$fields[] = ['name' => 'contact_last_name', 'label' => ctrans('texts.last_name'), 'type' => 'text', 'validation' => 'required'];
return $fields;
}
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)

View File

@ -1055,8 +1055,8 @@ class SubscriptionService
$recurring_invoice->line_items = $subscription_repo->generateLineItems($this->subscription, true, false);
$recurring_invoice->subscription_id = $this->subscription->id;
$recurring_invoice->frequency_id = $this->subscription->frequency_id ?: RecurringInvoice::FREQUENCY_MONTHLY;
$recurring_invoice->remaining_cycles = $this->subscription->remaining_cycles ?? -1;
$recurring_invoice->date = now();
$recurring_invoice->remaining_cycles = -1;
$recurring_invoice->auto_bill = $client->getSetting('auto_bill');
$recurring_invoice->auto_bill_enabled = $this->setAutoBillFlag($recurring_invoice->auto_bill);
$recurring_invoice->due_date_days = 'terms';
@ -1089,7 +1089,7 @@ class SubscriptionService
$recurring_invoice->subscription_id = $this->subscription->id;
$recurring_invoice->frequency_id = $this->subscription->frequency_id ?: RecurringInvoice::FREQUENCY_MONTHLY;
$recurring_invoice->date = now()->addSeconds($client->timezone_offset());
$recurring_invoice->remaining_cycles = -1;
$recurring_invoice->remaining_cycles = $this->subscription->remaining_cycles ?? -1;
$recurring_invoice->auto_bill = $client->getSetting('auto_bill');
$recurring_invoice->auto_bill_enabled = $this->setAutoBillFlag($recurring_invoice->auto_bill);
$recurring_invoice->due_date_days = 'terms';

View File

@ -73,6 +73,7 @@ class SubscriptionTransformer extends EntityTransformer
'optional_product_ids' => (string) $subscription->optional_product_ids,
'registration_required' => (bool) $subscription->registration_required,
'steps' => $subscription->steps,
'remaining_cycles' => (int) $subscription->remaining_cycles,
];
}
}

View File

@ -3891,7 +3891,7 @@ $lang = array(
'payment_method_saving_failed' => 'Payment method can\'t be saved for future use.',
'pay_with' => 'Pay with',
'n/a' => 'N/A',
'by_clicking_next_you_accept_terms' => 'By clicking "Next step" you accept terms.',
'by_clicking_next_you_accept_terms' => 'By clicking "Next" you accept terms.',
'not_specified' => 'Not specified',
'before_proceeding_with_payment_warning' => 'Before proceeding with payment, you have to fill following fields',
'after_completing_go_back_to_previous_page' => 'After completing, go back to previous page.',

1
public/build/assets/app-106dcc58.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
import{i as a,w as d}from"./wait-8f4ae121.js";/**
* 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
*/async function i(){const t={name:navigator.userAgent.substring(0,100),java_enabled:navigator.javaEnabled()?"true":"false",language:navigator.language||navigator.userLanguage,screen_height:window.screen.height.toString(),screen_width:window.screen.width.toString(),time_zone:(new Date().getTimezoneOffset()*-1).toString(),color_depth:window.screen.colorDepth.toString()};document.querySelector('input[name="browser_details"]').value=JSON.stringify(t);const n=JSON.stringify({...Object.fromEntries(new FormData(document.getElementById("server-response"))),gateway_response:Array.from(document.querySelectorAll("input[name=gateway_response]")).find(e=>e.value).value}),o=document.querySelector("meta[name=store_route]");try{const e=await fetch(o.content,{method:"POST",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest",Accept:"application/json","X-CSRF-Token":document.querySelector('meta[name="csrf-token"]').content},body:n});return e.ok?await e.json():await e.json().then(r=>{throw new Error(r.message??"Unknown error.")})}catch(e){document.getElementById("errors").textContent=`Sorry, your transaction could not be processed...
${e.message}`,document.getElementById("errors").hidden=!1;let r=document.getElementById("authorize-card");throw r.disabled=!1,r.querySelector("svg").classList.add("hidden"),r.querySelector("span").classList.remove("hidden"),console.error("Fetch error:",e),e}}async function c(){try{const t=await i();if(t.status==="not_authenticated"||t==="not_authenticated")throw s(),new Error("There was an issue authenticating this payment method.");if(t.status==="authentication_not_supported")return document.querySelector('input[name="browser_details"]').value=null,document.querySelector('input[name="charge"]').value=JSON.stringify(t),document.getElementById("server-response").submit();const n=new cba.Canvas3ds("#widget-3dsecure",t._3ds.token);n.load(),document.getElementById("widget").classList.add("hidden"),n.on("chargeAuthSuccess",function(e){document.querySelector('input[name="browser_details"]').value=null,document.querySelector('input[name="charge"]').value=JSON.stringify(e),document.getElementById("server-response").submit()}),n.on("chargeAuthReject",function(e){document.getElementById("errors").textContent="Sorry, your transaction could not be processed...",document.getElementById("errors").hidden=!1,s()}),n.load()}catch(t){console.error("Error fetching 3DS Token:",t),document.getElementById("errors").textContent=`Sorry, your transaction could not be processed...
${t}`,document.getElementById("errors").hidden=!1;let n=document.getElementById("authorize-card");n.disabled=!1,n.querySelector("svg").classList.add("hidden"),n.querySelector("span").classList.remove("hidden")}}function u(){l();const t=document.querySelector("meta[name=public_key]"),n=document.querySelector("meta[name=gateway_id]"),o=document.querySelector("meta[name=environment]"),e=new cba.HtmlWidget("#widget",t==null?void 0:t.content,n==null?void 0:n.content);e.setEnv(o==null?void 0:o.content),e.useAutoResize(),e.interceptSubmitForm("#stepone"),e.onFinishInsert('#server-response input[name="gateway_response"]',"payment_source"),e.setFormFields(["card_name*"]),e.load();let r=document.getElementById("authorize-card");return r.disabled=!1,r.querySelector("svg").classList.add("hidden"),r.querySelector("span").classList.remove("hidden"),e}function l(){var t;document.querySelector("#widget").innerHTML="",(t=document.querySelector("#widget"))==null||t.classList.remove("hidden"),document.querySelector("#widget-3dsecure").innerHTML=""}function s(){const t=u();function n(){let r=document.getElementById("pay-now");r.disabled=t.isInvalidForm()}t.trigger("tab",n),t.trigger("submit_form",n),t.trigger("tab",n),t.on("finish",function(r){document.getElementById("errors").hidden=!0,c()});const o=document.querySelector('input[name="payment-type"]');o&&o.click();let e=document.getElementById("authorize-card");e.addEventListener("click",()=>{const r=document.getElementById("widget");if(t.getValidationState(),!t.isValidForm()&&r.offsetParent!==null){e.disabled=!1,e.querySelector("svg").classList.add("hidden"),e.querySelector("span").classList.remove("hidden");return}e.disabled=!0,e.querySelector("svg").classList.remove("hidden"),e.querySelector("span").classList.add("hidden"),r.offsetParent!==null?document.getElementById("stepone_submit").click():document.getElementById("server-response").submit()})}a()?s():d("#").then(s);

View File

@ -0,0 +1,13 @@
import{i as s,w as c}from"./wait-8f4ae121.js";/**
* 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
*/function i(){const t=document.querySelector("meta[name=public_key]"),n=document.querySelector("meta[name=gateway_id]"),o=document.querySelector("meta[name=environment]"),e=new cba.HtmlWidget("#widget",t==null?void 0:t.content,n==null?void 0:n.content);e.setEnv(o==null?void 0:o.content),e.useAutoResize(),e.interceptSubmitForm("#stepone"),e.onFinishInsert('#server-response input[name="gateway_response"]',"payment_source"),e.setFormFields(["card_name*"]),e.reload();let r=document.getElementById("pay-now");return r.disabled=!1,r.querySelector("svg").classList.add("hidden"),r.querySelector("span").classList.remove("hidden"),document.querySelector('#server-response input[name="gateway_response"]').value="",e}function u(){var t,n,o;(t=document.querySelector("#widget"))==null||t.replaceChildren(),(n=document.querySelector("#widget"))==null||n.classList.remove("hidden"),(o=document.querySelector("#widget-3dsecure"))==null||o.replaceChildren()}function d(){u();const t=i();t.on("finish",()=>{document.getElementById("errors").hidden=!0,l()}),t.on("submit",function(e){document.getElementById("errors").hidden=!0});let n=document.getElementById("pay-now");n.addEventListener("click",()=>{const e=document.getElementById("widget");if(t.getValidationState(),!t.isValidForm()&&e.offsetParent!==null){n.disabled=!1,n.querySelector("svg").classList.add("hidden"),n.querySelector("span").classList.remove("hidden");return}n.disabled=!0,n.querySelector("svg").classList.remove("hidden"),n.querySelector("span").classList.add("hidden");let r=document.querySelector("input[name=token-billing-checkbox]:checked");r&&(document.getElementById("store_card").value=r.value),e.offsetParent!==null?document.getElementById("stepone_submit").click():document.getElementById("server-response").submit()}),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",e=>{var a;document.getElementById("widget").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value="",(a=document.querySelector("#powerboard-payment-container"))==null||a.classList.remove("hidden")}),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach(e=>e.addEventListener("click",r=>{var a;document.getElementById("widget").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=r.target.dataset.token,(a=document.querySelector("#powerboard-payment-container"))==null||a.classList.add("hidden")}));const o=document.querySelector('input[name="payment-type"]');o&&o.click()}async function l(){try{const t=await m();if(t.status==="not_authenticated"||t==="not_authenticated")throw d(),new Error("There was an issue authenticating this payment method.");if(t.status==="authentication_not_supported"){document.querySelector('input[name="browser_details"]').value=null,document.querySelector('input[name="charge"]').value=JSON.stringify(t);let e=document.querySelector("input[name=token-billing-checkbox]:checked");return e&&(document.getElementById("store_card").value=e.value),document.getElementById("server-response").submit()}const n=new cba.Canvas3ds("#widget-3dsecure",t._3ds.token);n.load(),document.getElementById("widget").classList.add("hidden"),n.on("chargeAuthSuccess",function(e){document.querySelector('input[name="browser_details"]').value=null,document.querySelector('input[name="charge"]').value=JSON.stringify(e);let r=document.querySelector("input[name=token-billing-checkbox]:checked");r&&(document.getElementById("store_card").value=r.value),document.getElementById("server-response").submit()}),n.on("chargeAuthReject",function(e){document.getElementById("errors").textContent="Sorry, your transaction could not be processed...",document.getElementById("errors").hidden=!1,d()}),n.load()}catch(t){document.getElementById("errors").textContent=`Sorry, your transaction could not be processed...
${t}`,document.getElementById("errors").hidden=!1}}async function m(){const t={name:navigator.userAgent.substring(0,100),java_enabled:navigator.javaEnabled()?"true":"false",language:navigator.language||navigator.userLanguage,screen_height:window.screen.height.toString(),screen_width:window.screen.width.toString(),time_zone:(new Date().getTimezoneOffset()*-1).toString(),color_depth:window.screen.colorDepth.toString()};document.querySelector('input[name="browser_details"]').value=JSON.stringify(t);const n=JSON.stringify(Object.fromEntries(new FormData(document.getElementById("server-response")))),o=document.querySelector("meta[name=payments_route]");try{const e=await fetch(o.content,{method:"POST",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest",Accept:"application/json","X-CSRF-Token":document.querySelector('meta[name="csrf-token"]').content},body:n});return e.ok?await e.json():await e.json().then(r=>{throw new Error(r.message??"Unknown error.")})}catch(e){throw document.getElementById("errors").textContent=`Sorry, your transaction could not be processed...
${e.message}`,document.getElementById("errors").hidden=!1,console.error("Fetch error:",e),e}}s()?d():c("#powerboard-credit-card-payment").then(()=>d());

View File

@ -0,0 +1,13 @@
import{i as s,w as c}from"./wait-8f4ae121.js";/**
* 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
*/function i(){const t=document.querySelector("meta[name=public_key]"),n=document.querySelector("meta[name=gateway_id]"),o=document.querySelector("meta[name=environment]"),e=new cba.HtmlWidget("#widget",t==null?void 0:t.content,n==null?void 0:n.content);e.setEnv(o==null?void 0:o.content),e.useAutoResize(),e.interceptSubmitForm("#stepone"),e.onFinishInsert('#server-response input[name="gateway_response"]',"payment_source"),e.setFormFields(["card_name*"]),e.reload();let r=document.getElementById("pay-now");return r.disabled=!1,r.querySelector("svg").classList.add("hidden"),r.querySelector("span").classList.remove("hidden"),document.querySelector('#server-response input[name="gateway_response"]').value="",e}function u(){var t,n,o;(t=document.querySelector("#widget"))==null||t.replaceChildren(),(n=document.querySelector("#widget"))==null||n.classList.remove("hidden"),(o=document.querySelector("#widget-3dsecure"))==null||o.replaceChildren()}function a(){u();const t=i();t.on("finish",()=>{document.getElementById("errors").hidden=!0,l()}),t.on("submit",function(e){document.getElementById("errors").hidden=!0});let n=document.getElementById("pay-now");n.addEventListener("click",()=>{const e=document.getElementById("widget");if(t.getValidationState(),!t.isValidForm()&&e.offsetParent!==null){n.disabled=!1,n.querySelector("svg").classList.add("hidden"),n.querySelector("span").classList.remove("hidden");return}n.disabled=!0,n.querySelector("svg").classList.remove("hidden"),n.querySelector("span").classList.add("hidden");let r=document.querySelector("input[name=token-billing-checkbox]:checked");r&&(document.getElementById("store_card").value=r.value),e.offsetParent!==null?document.getElementById("stepone_submit").click():document.getElementById("server-response").submit()}),document.getElementById("toggle-payment-with-credit-card").addEventListener("click",e=>{var d;document.getElementById("widget").classList.remove("hidden"),document.getElementById("save-card--container").style.display="grid",document.querySelector("input[name=token]").value="",(d=document.querySelector("#powerboard-payment-container"))==null||d.classList.remove("hidden")}),Array.from(document.getElementsByClassName("toggle-payment-with-token")).forEach(e=>e.addEventListener("click",r=>{var d;document.getElementById("widget").classList.add("hidden"),document.getElementById("save-card--container").style.display="none",document.querySelector("input[name=token]").value=r.target.dataset.token,(d=document.querySelector("#powerboard-payment-container"))==null||d.classList.add("hidden")}));const o=document.querySelector('input[name="payment-type"]');o&&o.click()}async function l(){try{const t=await m();if(t.status==="not_authenticated"||t==="not_authenticated")throw a(),new Error("There was an issue authenticating this payment method.");if(t.status==="authentication_not_supported"){document.querySelector('input[name="browser_details"]').value=null,document.querySelector('input[name="charge"]').value=JSON.stringify(t);let e=document.querySelector("input[name=token-billing-checkbox]:checked");return e&&(document.getElementById("store_card").value=e.value),document.getElementById("server-response").submit()}const n=new cba.Canvas3ds("#widget-3dsecure",t._3ds.token);n.load(),document.getElementById("widget").classList.add("hidden"),n.on("chargeAuthSuccess",function(e){document.querySelector('input[name="browser_details"]').value=null,document.querySelector('input[name="charge"]').value=JSON.stringify(e);let r=document.querySelector("input[name=token-billing-checkbox]:checked");r&&(document.getElementById("store_card").value=r.value),document.getElementById("server-response").submit()}),n.on("chargeAuthReject",function(e){document.getElementById("errors").textContent="Sorry, your transaction could not be processed...",document.getElementById("errors").hidden=!1,a()}),n.load()}catch(t){console.error("Error fetching 3DS Token:",t),document.getElementById("errors").textContent=`Sorry, your transaction could not be processed...
${t}`,document.getElementById("errors").hidden=!1,a()}}async function m(){const t={name:navigator.userAgent.substring(0,100),java_enabled:navigator.javaEnabled()?"true":"false",language:navigator.language||navigator.userLanguage,screen_height:window.screen.height.toString(),screen_width:window.screen.width.toString(),time_zone:(new Date().getTimezoneOffset()*-1).toString(),color_depth:window.screen.colorDepth.toString()};document.querySelector('input[name="browser_details"]').value=JSON.stringify(t);const n=JSON.stringify(Object.fromEntries(new FormData(document.getElementById("server-response")))),o=document.querySelector("meta[name=payments_route]");try{const e=await fetch(o.content,{method:"POST",headers:{"Content-Type":"application/json","X-Requested-With":"XMLHttpRequest",Accept:"application/json","X-CSRF-Token":document.querySelector('meta[name="csrf-token"]').content},body:n});return e.ok?await e.json():await e.json().then(r=>{throw new Error(r.message??"Unknown error.")})}catch(e){document.getElementById("errors").textContent=`Sorry, your transaction could not be processed...
${e.message}`,document.getElementById("errors").hidden=!1,console.error("Fetch error:",e),a()}}s()?a():c("#powerboard-credit-card-payment").then(()=>a());

View File

@ -48,6 +48,14 @@
"isEntry": true,
"src": "resources/js/clients/payment_methods/authorize-checkout-card.js"
},
"resources/js/clients/payment_methods/authorize-powerboard-card.js": {
"file": "assets/authorize-powerboard-card-9db77713.js",
"imports": [
"_wait-8f4ae121.js"
],
"isEntry": true,
"src": "resources/js/clients/payment_methods/authorize-powerboard-card.js"
},
"resources/js/clients/payment_methods/authorize-stripe-acss.js": {
"file": "assets/authorize-stripe-acss-f6bd46c1.js",
"imports": [
@ -141,6 +149,14 @@
"isEntry": true,
"src": "resources/js/clients/payments/paytrace-credit-card.js"
},
"resources/js/clients/payments/powerboard-credit-card.
"file": "assets/powerboard-credit-card-127361fb.js",
"imports": [
"_wait-8f4ae121.js"
],
"isEntry": true,
"src": "resources/js/clients/payments/powerboard-credit-card.js"
},
"resources/js/clients/payments/razorpay-aio.js": {
"file": "assets/razorpay-aio-f8e8c7f0.js",
"imports": [

View File

@ -0,0 +1,239 @@
/**
* 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
*/
import { instant, wait } from '../wait';
async function get3dsToken() {
const browserDetails = {
name: navigator.userAgent.substring(0, 100), // The full user agent string, which contains the browser name and version
java_enabled: navigator.javaEnabled() ? 'true' : 'false', // Indicates if Java is enabled in the browser
language: navigator.language || navigator.userLanguage, // The browser language
screen_height: window.screen.height.toString(), // Screen height in pixels
screen_width: window.screen.width.toString(), // Screen width in pixels
time_zone: (new Date().getTimezoneOffset() * -1).toString(), // Timezone offset in minutes (negative for behind UTC)
color_depth: window.screen.colorDepth.toString(), // Color depth in bits per pixel
};
document.querySelector('input[name="browser_details"]').value =
JSON.stringify(browserDetails);
const formData = JSON.stringify({
...Object.fromEntries(
new FormData(document.getElementById('server-response'))
),
gateway_response: Array.from(document.querySelectorAll('input[name=gateway_response]')).find(input => input.value).value,
});
const paymentsRoute = document.querySelector('meta[name=store_route]');
try {
// Return the fetch promise to handle it externally
const response = await fetch(paymentsRoute.content, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
Accept: 'application/json',
'X-CSRF-Token': document.querySelector(
'meta[name="csrf-token"]'
).content,
},
body: formData,
});
if (!response.ok) {
return await response.json().then((errorData) => {
throw new Error(errorData.message ?? 'Unknown error.');
});
// const text = await response.text();
// throw new Error(`Network response was not ok: ${response.statusText}. Response text: ${text}`);
}
return await response.json();
} catch (error) {
document.getElementById(
'errors'
).textContent = `Sorry, your transaction could not be processed...\n\n${error.message}`;
document.getElementById('errors').hidden = false;
let payNow = document.getElementById('authorize-card');
payNow.disabled = false;
payNow.querySelector('svg').classList.add('hidden');
payNow.querySelector('span').classList.remove('hidden');
console.error('Fetch error:', error); // Log error for debugging
throw error; //
}
}
async function process3ds() {
try {
const resource = await get3dsToken();
if (resource.status === 'not_authenticated' || resource === 'not_authenticated') {
authorize();
throw new Error(
'There was an issue authenticating this payment method.'
);
}
if (resource.status === 'authentication_not_supported') {
document.querySelector('input[name="browser_details"]').value =
null;
document.querySelector('input[name="charge"]').value =
JSON.stringify(resource);
return document.getElementById('server-response').submit();
}
const canvas = new cba.Canvas3ds(
'#widget-3dsecure',
resource._3ds.token
);
canvas.load();
let widget = document.getElementById('widget');
widget.classList.add('hidden');
canvas.on('chargeAuthSuccess', function (data) {
document.querySelector('input[name="browser_details"]').value =
null;
document.querySelector('input[name="charge"]').value =
JSON.stringify(data);
document.getElementById('server-response').submit();
});
canvas.on('chargeAuthReject', function (data) {
document.getElementById(
'errors'
).textContent = `Sorry, your transaction could not be processed...`;
document.getElementById('errors').hidden = false;
authorize();
});
canvas.load();
} catch (error) {
console.error('Error fetching 3DS Token:', error);
document.getElementById(
'errors'
).textContent = `Sorry, your transaction could not be processed...\n\n${error}`;
document.getElementById('errors').hidden = false;
let payNow = document.getElementById('authorize-card');
payNow.disabled = false;
payNow.querySelector('svg').classList.add('hidden');
payNow.querySelector('span').classList.remove('hidden');
}
}
function setup() {
reload();
const publicKey = document.querySelector('meta[name=public_key]');
const gatewayId = document.querySelector('meta[name=gateway_id]');
const env = document.querySelector('meta[name=environment]');
const widget = new cba.HtmlWidget(
'#widget',
publicKey?.content,
gatewayId?.content
);
widget.setEnv(env?.content);
widget.useAutoResize();
widget.interceptSubmitForm('#stepone');
widget.onFinishInsert(
'#server-response input[name="gateway_response"]',
'payment_source'
);
widget.setFormFields(['card_name*']);
widget.load();
let payNow = document.getElementById('authorize-card');
payNow.disabled = false;
payNow.querySelector('svg').classList.add('hidden');
payNow.querySelector('span').classList.remove('hidden');
return widget;
}
function reload() {
document.querySelector('#widget').innerHTML = '';
document.querySelector('#widget')?.classList.remove('hidden');
document.querySelector('#widget-3dsecure').innerHTML = '';
}
export function authorize() {
const widget = setup();
function handleTrigger() {
let payNow = document.getElementById('pay-now');
payNow.disabled = widget.isInvalidForm();
}
widget.trigger('tab', handleTrigger);
widget.trigger('submit_form', handleTrigger);
widget.trigger('tab', handleTrigger);
widget.on('finish', function (data) {
document.getElementById('errors').hidden = true;
process3ds();
});
const first = document.querySelector('input[name="payment-type"]');
if (first) {
first.click();
}
let authorizeCard = document.getElementById('authorize-card');
authorizeCard.addEventListener('click', () => {
const div = document.getElementById('widget');
widget.getValidationState();
if (!widget.isValidForm() && div.offsetParent !== null) {
authorizeCard.disabled = false;
authorizeCard.querySelector('svg').classList.add('hidden');
authorizeCard.querySelector('span').classList.remove('hidden');
return;
}
authorizeCard.disabled = true;
authorizeCard.querySelector('svg').classList.remove('hidden');
authorizeCard.querySelector('span').classList.add('hidden');
if (div.offsetParent !== null) {
document.getElementById('stepone_submit').click();
} else {
document.getElementById('server-response').submit();
}
});
}
instant() ? authorize() : wait('#').then(authorize);

View File

@ -0,0 +1,276 @@
/**
* 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
*/
import { instant, wait } from '../wait';
function setup() {
const publicKey = document.querySelector('meta[name=public_key]');
const gatewayId = document.querySelector('meta[name=gateway_id]');
const env = document.querySelector('meta[name=environment]');
const widget = new cba.HtmlWidget(
'#widget',
publicKey?.content,
gatewayId?.content
);
widget.setEnv(env?.content);
widget.useAutoResize();
widget.interceptSubmitForm('#stepone');
widget.onFinishInsert(
'#server-response input[name="gateway_response"]',
'payment_source'
);
widget.setFormFields(['card_name*']);
widget.reload();
let payNow = document.getElementById('pay-now');
payNow.disabled = false;
payNow.querySelector('svg').classList.add('hidden');
payNow.querySelector('span').classList.remove('hidden');
document.querySelector(
'#server-response input[name="gateway_response"]'
).value = '';
return widget;
}
function reload() {
document.querySelector('#widget')?.replaceChildren();
document.querySelector('#widget')?.classList.remove('hidden');
document.querySelector('#widget-3dsecure')?.replaceChildren();
}
function pay() {
reload();
const widget = setup();
widget.on('finish', () => {
document.getElementById('errors').hidden = true;
process3ds();
});
widget.on('submit', function (data) {
document.getElementById('errors').hidden = true;
});
let payNow = document.getElementById('pay-now');
payNow.addEventListener('click', () => {
const div = document.getElementById('widget');
widget.getValidationState();
if (!widget.isValidForm() && div.offsetParent !== null) {
payNow.disabled = false;
payNow.querySelector('svg').classList.add('hidden');
payNow.querySelector('span').classList.remove('hidden');
return;
}
payNow.disabled = true;
payNow.querySelector('svg').classList.remove('hidden');
payNow.querySelector('span').classList.add('hidden');
let storeCard = document.querySelector(
'input[name=token-billing-checkbox]:checked'
);
if (storeCard) {
document.getElementById('store_card').value = storeCard.value;
}
if (div.offsetParent !== null)
document.getElementById('stepone_submit').click();
else document.getElementById('server-response').submit();
});
document
.getElementById('toggle-payment-with-credit-card')
.addEventListener('click', (element) => {
let widget = document.getElementById('widget');
widget.classList.remove('hidden');
document.getElementById('save-card--container').style.display =
'grid';
document.querySelector('input[name=token]').value = '';
document
.querySelector('#powerboard-payment-container')
?.classList.remove('hidden');
});
Array.from(
document.getElementsByClassName('toggle-payment-with-token')
).forEach((element) =>
element.addEventListener('click', (element) => {
document.getElementById('widget').classList.add('hidden');
document.getElementById('save-card--container').style.display =
'none';
document.querySelector('input[name=token]').value =
element.target.dataset.token;
document
.querySelector('#powerboard-payment-container')
?.classList.add('hidden');
})
);
const first = document.querySelector('input[name="payment-type"]');
if (first) {
first.click();
}
}
async function process3ds() {
try {
const resource = await get3dsToken();
if (resource.status === 'not_authenticated' || resource === 'not_authenticated') {
pay();
throw new Error(
'There was an issue authenticating this payment method.'
);
}
if (resource.status === 'authentication_not_supported') {
document.querySelector('input[name="browser_details"]').value =
null;
document.querySelector('input[name="charge"]').value =
JSON.stringify(resource);
let storeCard = document.querySelector(
'input[name=token-billing-checkbox]:checked'
);
if (storeCard) {
document.getElementById('store_card').value = storeCard.value;
}
return document.getElementById('server-response').submit();
}
const canvas = new cba.Canvas3ds(
'#widget-3dsecure',
resource._3ds.token
);
canvas.load();
let widget = document.getElementById('widget');
widget.classList.add('hidden');
canvas.on('chargeAuthSuccess', function (data) {
document.querySelector('input[name="browser_details"]').value =
null;
document.querySelector('input[name="charge"]').value =
JSON.stringify(data);
let storeCard = document.querySelector(
'input[name=token-billing-checkbox]:checked'
);
if (storeCard) {
document.getElementById('store_card').value = storeCard.value;
}
document.getElementById('server-response').submit();
});
canvas.on('chargeAuthReject', function (data) {
document.getElementById(
'errors'
).textContent = `Sorry, your transaction could not be processed...`;
document.getElementById('errors').hidden = false;
pay();
});
canvas.load();
} catch (error) {
document.getElementById(
'errors'
).textContent = `Sorry, your transaction could not be processed...\n\n${error}`;
document.getElementById('errors').hidden = false;
pay();
}
}
async function get3dsToken() {
const browserDetails = {
name: navigator.userAgent.substring(0, 100), // The full user agent string, which contains the browser name and version
java_enabled: navigator.javaEnabled() ? 'true' : 'false', // Indicates if Java is enabled in the browser
language: navigator.language || navigator.userLanguage, // The browser language
screen_height: window.screen.height.toString(), // Screen height in pixels
screen_width: window.screen.width.toString(), // Screen width in pixels
time_zone: (new Date().getTimezoneOffset() * -1).toString(), // Timezone offset in minutes (negative for behind UTC)
color_depth: window.screen.colorDepth.toString(), // Color depth in bits per pixel
};
document.querySelector('input[name="browser_details"]').value =
JSON.stringify(browserDetails);
const formData = JSON.stringify(
Object.fromEntries(
new FormData(document.getElementById('server-response'))
)
);
const paymentsRoute = document.querySelector('meta[name=payments_route]');
try {
// Return the fetch promise to handle it externally
const response = await fetch(paymentsRoute.content, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
Accept: 'application/json',
'X-CSRF-Token': document.querySelector(
'meta[name="csrf-token"]'
).content,
},
body: formData,
});
if (!response.ok) {
return await response.json().then((errorData) => {
throw new Error(errorData.message ?? 'Unknown error.');
});
// const text = await response.text();
// throw new Error(`Network response was not ok: ${response.statusText}. Response text: ${text}`);
}
return await response.json();
} catch (error) {
document.getElementById(
'errors'
).textContent = `Sorry, your transaction could not be processed...\n\n${error.message}`;
document.getElementById('errors').hidden = false;
console.error('Fetch error:', error); // Log error for debugging
pay();
}
}
instant() ? pay() : wait('#powerboard-credit-card-payment').then(() => pay());

View File

@ -304,6 +304,14 @@
<span>{{ $total }}</span>
</div>
@if(isset($tax))
<div class="flex font-semibold justify-between py-1 text-sm uppercase border-t-2">
<span>{{ ctrans('texts.tax') }}</span>
<span>{{ $tax }}</span>
</div>
@endif
<div class="mx-auto text-center mt-20 content-center" x-data="{open: @entangle('payment_started').live, toggle: @entangle('payment_confirmed').live, buttonDisabled: false}" x-show.important="open" x-transition>
<h2 class="text-2xl font-bold tracking-wide border-b-2 pb-4">{{ $heading_text ?? ctrans('texts.checkout') }}</h2>
@if (session()->has('message'))

View File

@ -13,8 +13,11 @@
@endif
</div>
</div>
<div class="flex justify-end items-center px-4 py-4">
<button id="accept-terms-button" class="button button-primary bg-primary hover:bg-primary-darken float-end">{{ ctrans('texts.next') }}</button>
<div class="flex flex-col items-end px-4 py-4">
<div class="w-full flex justify-end mb-2">
<button id="accept-terms-button" class="button button-primary bg-primary hover:bg-primary-darken">{{ ctrans('texts.next') }}</button>
</div>
<span class="text-xs text-gray-600 text-right">{{ ctrans('texts.by_clicking_next_you_accept_terms')}}</span>
</div>
</div>

View File

@ -22,7 +22,7 @@
<div x-text="errors" class="alert alert-failure mb-4"></div>
</template>
@if($settings->client_portal_allow_under_payment)
@if($settings->client_portal_allow_under_payment && $settings->client_portal_under_payment_minimum != 0)
<span class="mt-1 text-sm text-gray-800">{{ ctrans('texts.minimum_payment') }}:
{{ $settings->client_portal_under_payment_minimum }}</span>
@endif

View File

@ -2,9 +2,17 @@
@section('gateway_head')
<meta name="instant-payment" content="yes" />
<meta name="public_key" content="{{ $public_key }}" />
<meta name="gateway_id" content="{{ $gateway_id }}" />
<meta name="store_route" content="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" />
<meta name="environment" content="{{ $environment }}" />
@endsection
@section('gateway_content')
<form action="javascript:void(0);" id="stepone">
<input type="hidden" name="gateway_response">
<button type="submit" class="hidden" id="stepone_submit">Submit</button>
</form>
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" method="post" id="server-response">
@csrf
@ -18,19 +26,17 @@
<div class="alert alert-failure mb-4" hidden id="errors"></div>
<div id="powerboard-payment-container" class="w-full">
<div id="powerboard-payment-container" class="w-full p-4" style="background-color: rgb(249, 249, 249);">
<div id="widget" style="block"></div>
<div id="widget-3dsecure"></div>
</div>
@component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card'])
{{ ctrans('texts.add_payment_method') }}
@endcomponent
@endsection
@section('gateway_footer')
<style>
iframe {
border: 0;
@ -40,204 +46,8 @@
</style>
<script src="{{ $widget_endpoint }}"></script>
<script>
var widget = new cba.HtmlWidget('#widget', '{{ $public_key }}', '{{ $gateway_id }}');
widget.setEnv("{{ $environment }}");
widget.useAutoResize();
// widget.interceptSubmitForm('#server-response');
widget.onFinishInsert('input[name="gateway_response"]', "payment_source");
widget.load();
widget.trigger('tab', function (data){
console.log("tab Response", data);
console.log(widget.isValidForm());
let payNow = document.getElementById('pay-now');
payNow.disabled = widget.isInvalidForm();
});
widget.trigger('submit_form',function (data){
console.log("submit_form Response", data);
console.log(widget.isValidForm());
let payNow = document.getElementById('pay-now');
payNow.disabled = widget.isInvalidForm();
});
widget.trigger('tab',function (data){
console.log("tab Response", data);
console.log(widget.isValidForm());
let payNow = document.getElementById('pay-now');
payNow.disabled = widget.isInvalidForm();
});
widget.on("systemError", function(data) {
console.log("systemError Response", data);
});
widget.on("validationError", function(data) {
console.log("validationError", data);
});
widget.on("finish", async function(data) {
document.getElementById('errors').hidden = true;
console.log("finish", data);
try {
const resource = await get3dsToken();
console.log("3DS Token:", resource);
console.log("pre canvas");
console.log(resource._3ds.token);
var canvas = new cba.Canvas3ds('#widget-3dsecure', resource._3ds.token);
canvas.load();
let widget = document.getElementById('widget');
widget.classList.add('hidden');
} catch (error) {
console.error("Error fetching 3DS Token:", error);
}
canvas.on("chargeAuthSuccess", function(data) {
console.log(data);
document.querySelector(
'input[name="browser_details"]'
).value = null;
document.querySelector(
'input[name="charge"]'
).value = JSON.stringify(data);
document.getElementById('server-response').submit();
});
canvas.on("chargeAuthReject", function(data) {
console.log(data);
document.getElementById('errors').textContent = `Sorry, your transaction could not be processed...`;
document.getElementById('errors').hidden = false;
});
canvas.load();
});
widget.on("submit", async function (data){
console.log("submit");
console.log(data);
document.getElementById('errors').hidden = true;
})
widget.on('form_submit', function (data) {
console.log("form_submit", data);
console.log(data);
});
widget.on('submit', function (data) {
console.log("submit", data);
console.log(data);
});
widget.on('tab', function (data) {
console.log("tab", data);
console.log(data);
});
let payNow = document.getElementById('authorize-card');
payNow.addEventListener('click', () => {
payNow.disabled = true;
payNow.querySelector('svg').classList.remove('hidden');
payNow.querySelector('span').classList.add('hidden');
document.getElementById('server-response').submit();
});
async function get3dsToken() {
const browserDetails = {
name: navigator.userAgent.substring(0, 100), // The full user agent string, which contains the browser name and version
java_enabled: navigator.javaEnabled() ? "true" : "false", // Indicates if Java is enabled in the browser
language: navigator.language || navigator.userLanguage, // The browser language
screen_height: window.screen.height.toString(), // Screen height in pixels
screen_width: window.screen.width.toString(), // Screen width in pixels
time_zone: (new Date().getTimezoneOffset() * -1).toString(), // Timezone offset in minutes (negative for behind UTC)
color_depth: window.screen.colorDepth.toString() // Color depth in bits per pixel
};
document.querySelector(
'input[name="browser_details"]'
).value = JSON.stringify(browserDetails);
const formData = JSON.stringify(Object.fromEntries(new FormData(document.getElementById("server-response"))));
try {
// Return the fetch promise to handle it externally
const response = await fetch('{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"X-Requested-With": "XMLHttpRequest",
"Accept": 'application/json',
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content
},
body: formData
})
if (!response.ok) {
return await response.json().then(errorData => {
throw new Error(errorData.message ?? 'Unknown error.');
});
}
return await response.json()
}
catch(error) {
document.getElementById('errors').textContent = `Sorry, your card could not be authorized...\n\n${error.message}`;
document.getElementById('errors').hidden = false;
console.error('Fetch error:', error); // Log error for debugging
throw error; //
}
}
const first = document.querySelector('input[name="payment-type"]');
if (first) {
first.click();
}
</script>
@vite('resources/js/clients/payment_methods/authorize-powerboard-card.js')
@endsection

View File

@ -1,140 +0,0 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => 'Credit card', 'card_title' => 'Credit card'])
@section('gateway_head')
<meta name="instant-payment" content="yes" />
@endsection
@section('gateway_content')
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" method="post" id="server-response">
@csrf
<input type="hidden" name="gateway_response">
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
<input type="hidden" name="charge_no3d">
<button type="submit" class="hidden" id="stub">Submit</button>
</form>
<div class="alert alert-failure mb-4" hidden id="errors"></div>
<div id="powerboard-payment-container" class="w-full">
<div id="widget" style="block"></div>
</div>
@component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card'])
{{ ctrans('texts.add_payment_method') }}
@endcomponent
@endsection
@section('gateway_footer')
<style>
iframe {
border: 0;
width: 100%;
height: 400px;
}
</style>
<script src="{{ $widget_endpoint }}"></script>
<script>
var widget = new cba.HtmlWidget('#widget', '{{ $public_key }}', '{{ $gateway_id }}');
widget.setEnv("{{ $environment }}");
widget.useAutoResize();
widget.interceptSubmitForm('#server-response');
widget.onFinishInsert('input[name="gateway_response"]', "payment_source");
widget.load();
widget.trigger('tab', function (data){
console.log("tab Response", data);
console.log(widget.isValidForm());
let payNow = document.getElementById('pay-now');
payNow.disabled = widget.isInvalidForm();
});
widget.trigger('submit_form',function (data){
console.log("submit_form Response", data);
console.log(widget.isValidForm());
let payNow = document.getElementById('pay-now');
payNow.disabled = widget.isInvalidForm();
});
widget.trigger('tab',function (data){
console.log("tab Response", data);
console.log(widget.isValidForm());
let payNow = document.getElementById('pay-now');
payNow.disabled = widget.isInvalidForm();
});
widget.on("systemError", function(data) {
console.log("systemError Response", data);
});
widget.on("validationError", function(data) {
console.log("validationError", data);
});
widget.on("finish", async function(data) {
document.getElementById('errors').hidden = true;
console.log("finish", data);
});
widget.on("submit", async function (data){
console.log("submit");
console.log(data);
document.getElementById('errors').hidden = true;
})
widget.on('form_submit', function (data) {
console.log("form_submit", data);
console.log(data);
});
widget.on('submit', function (data) {
console.log("submit", data);
console.log(data);
});
widget.on('tab', function (data) {
console.log("tab", data);
console.log(data);
});
let payNow = document.getElementById('authorize-card');
payNow.addEventListener('click', () => {
document.querySelector(
'input[name="charge_no3d"]'
).value = true;
payNow.disabled = true;
payNow.querySelector('svg').classList.remove('hidden');
payNow.querySelector('span').classList.add('hidden');
document.getElementById('stub').click();
});
</script>
@endsection

View File

@ -2,6 +2,10 @@
@section('gateway_head')
<meta name="instant-payment" content="yes" />
<meta name="public_key" content="{{ $public_key }}" />
<meta name="gateway_id" content="{{ $gateway_id }}" />
<meta name="environment" content="{{ $environment }}">
<meta name="payments_route" content="{{ route('client.payments.response') }}" />
@endsection
@section('gateway_content')
@ -23,6 +27,7 @@
<input type="hidden" name="token">
<input type="hidden" name="browser_details">
<input type="hidden" name="charge">
<input type="hidden" name="charge_3ds_id">
<button type="submit" class="hidden" id="stub">Submit</button>
</form>
@ -65,14 +70,14 @@
</ul>
@endcomponent
<div id="powerboard-payment-container" class="w-full">
@include('portal.ninja2020.gateways.includes.save_card')
<div id="powerboard-payment-container" class="w-full p-4 hidden" style="background-color: rgb(249, 249, 249);">
<div id="widget" style="block" class="hidden"></div>
<div id="widget-3dsecure"></div>
</div>
@include('portal.ninja2020.gateways.includes.save_card')
@include('portal.ninja2020.gateways.includes.pay_now')
@endsection
@section('gateway_footer')
@ -87,250 +92,7 @@
<script src="{{ $widget_endpoint }}"></script>
<script>
/** Loads the widget, links the widget to a dummy form */
var widget = new cba.HtmlWidget('#widget', '{{ $public_key }}', '{{ $gateway_id }}');
widget.setEnv("{{ $environment }}");
widget.useAutoResize();
widget.interceptSubmitForm('#stepone');
widget.onFinishInsert('#server-response input[name="gateway_response"]', "payment_source");
widget.load();
widget.on("systemError", function(data) {
console.log("systemError Response", data);
});
widget.on("validationError", function(data) {
console.log("validationError", data);
});
/** Retrieves the payment_source token and passes this to the 3ds handler */
widget.on("finish", function(data) {
document.getElementById('errors').hidden = true;
console.log("finish");
console.log(data);
process3ds();
});
widget.on("submit", function (data){
console.log("submit");
console.log(data);
document.getElementById('errors').hidden = true;
})
widget.on('form_submit', function (data) {
console.log("form_submit", data);
console.log(data);
});
widget.on('tab', function (data) {
console.log("tab", data);
console.log(data);
});
let payNow = document.getElementById('pay-now');
payNow.addEventListener('click', () => {
const div = document.getElementById('widget');
widget.getValidationState();
if(!widget.isValidForm() && div.offsetParent !== null){
console.log("invalid");
payNow.disabled = false;
payNow.querySelector('svg').classList.add('hidden');
payNow.querySelector('span').classList.remove('hidden');
return;
}
payNow.disabled = true;
payNow.querySelector('svg').classList.remove('hidden');
payNow.querySelector('span').classList.add('hidden');
let storeCard = document.querySelector(
'input[name=token-billing-checkbox]:checked'
);
if (storeCard) {
document.getElementById('store_card').value = storeCard.value;
}
if(div.offsetParent !== null)
document.getElementById('stepone_submit').click();
else
document.getElementById('server-response').submit();
});
async function process3ds()
{
try {
const resource = await get3dsToken();
console.log("3DS Token:", resource);
console.log("pre canvas");
console.log(resource._3ds.token);
if (resource.status == "not_authenticated" || resource.status == "authentication_not_supported") { // if status = authentication_not_supported, there will be no resource._3ds.token, so we need to fall back to a NON 3ds payment flow from here
throw new Error('There was an issue authenticating this payment method.');
return;
}
var canvas = new cba.Canvas3ds('#widget-3dsecure', resource._3ds.token);
canvas.load();
let widget = document.getElementById('widget');
widget.classList.add('hidden');
} catch (error) {
console.error("Error fetching 3DS Token:", error);
document.getElementById('errors').textContent = `Sorry, your transaction could not be processed...\n\n${error}`;
document.getElementById('errors').hidden = false;
return;
}
canvas.on("chargeAuthSuccess", function(data) {
console.log(data);
document.querySelector(
'input[name="browser_details"]'
).value = null;
document.querySelector(
'input[name="charge"]'
).value = JSON.stringify(data);
let storeCard = document.querySelector(
'input[name=token-billing-checkbox]:checked'
);
if (storeCard) {
document.getElementById('store_card').value = storeCard.value;
}
document.getElementById('server-response').submit();
});
canvas.on("chargeAuthReject", function(data) {
console.log(data);
document.getElementById('errors').textContent = `Sorry, your transaction could not be processed...`;
document.getElementById('errors').hidden = false;
});
canvas.load();
}
async function get3dsToken() {
const browserDetails = {
name: navigator.userAgent.substring(0, 100), // The full user agent string, which contains the browser name and version
java_enabled: navigator.javaEnabled() ? "true" : "false", // Indicates if Java is enabled in the browser
language: navigator.language || navigator.userLanguage, // The browser language
screen_height: window.screen.height.toString(), // Screen height in pixels
screen_width: window.screen.width.toString(), // Screen width in pixels
time_zone: (new Date().getTimezoneOffset() * -1).toString(), // Timezone offset in minutes (negative for behind UTC)
color_depth: window.screen.colorDepth.toString() // Color depth in bits per pixel
};
document.querySelector(
'input[name="browser_details"]'
).value = JSON.stringify(browserDetails);
const formData = JSON.stringify(Object.fromEntries(new FormData(document.getElementById("server-response"))));
try {
// Return the fetch promise to handle it externally
const response = await fetch('{{ route('client.payments.response') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
"X-Requested-With": "XMLHttpRequest",
"Accept": 'application/json',
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content
},
body: formData
})
if (!response.ok) {
return await response.json().then(errorData => {
throw new Error(errorData.message ?? 'Unknown error.');
});
// const text = await response.text();
// throw new Error(`Network response was not ok: ${response.statusText}. Response text: ${text}`);
}
return await response.json()
}
catch(error) {
document.getElementById('errors').textContent = `Sorry, your transaction could not be processed...\n\n${error.message}`;
document.getElementById('errors').hidden = false;
console.error('Fetch error:', error); // Log error for debugging
throw error; //
}
}
const first = document.querySelector('input[name="payment-type"]');
if (first) {
first.click();
}
document
.getElementById('toggle-payment-with-credit-card')
.addEventListener('click', (element) => {
let widget = document.getElementById('widget');
widget.classList.remove('hidden');
document.getElementById('save-card--container').style.display ='grid';
document.querySelector('input[name=token]').value = '';
});
Array.from(
document.getElementsByClassName('toggle-payment-with-token')
).forEach((element) =>
element.addEventListener('click', (element) => {
document
.getElementById('widget')
.classList.add('hidden');
document.getElementById(
'save-card--container'
).style.display = 'none';
document.querySelector('input[name=token]').value =
element.target.dataset.token;
})
);
</script>
@vite('resources/js/clients/payments/powerboard-credit-card.js')
@endsection

View File

@ -0,0 +1,90 @@
<div class="rounded-lg border bg-card text-card-foreground shadow-sm overflow-hidden py-5 bg-white sm:gap-4"
id="powerboard-credit-card-payment">
<meta name="public_key" content="{{ $public_key }}" />
<meta name="gateway_id" content="{{ $gateway_id }}" />
<meta name="environment" content="{{ $environment }}">
<meta name="payments_route" content="{{ route('client.payments.response') }}" />
<form action="javascript:void(0);" id="stepone">
<input type="hidden" name="gateway_response">
<button type="submit" class="hidden" id="stepone_submit">Submit</button>
</form>
<form action="{{ route('client.payments.response') }}" method="post" id="server-response">
@csrf
<input type="hidden" name="gateway_response">
<input type="hidden" name="store_card" id="store_card"/>
<input type="hidden" name="payment_hash" value="{{ $payment_hash }}">
<input type="hidden" name="company_gateway_id" value="{{ $gateway->getCompanyGatewayId() }}">
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}">
<input type="hidden" name="token">
<input type="hidden" name="browser_details">
<input type="hidden" name="charge">
<input type="hidden" name="charge_3ds_id">
<button type="submit" class="hidden" id="stub">Submit</button>
</form>
<div class="alert alert-failure mb-4" hidden id="errors"></div>
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.payment_type')])
{{ ctrans('texts.credit_card') }}
@endcomponent
@include('portal.ninja2020.gateways.includes.payment_details')
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.pay_with')])
<ul class="list-none">
@if(count($tokens) > 0)
@foreach($tokens as $token)
<li class="py-2 cursor-pointer">
<label class="mr-4">
<input
type="radio"
data-token="{{ $token->token }}"
name="payment-type"
class="form-check-input text-indigo-600 rounded-full cursor-pointer toggle-payment-with-token toggle-payment-with-token"/>
<span class="ml-1 cursor-pointer">**** {{ $token->meta?->last4 }}</span>
</label>
</li>
@endforeach
@endisset
<li class="py-2 cursor-pointer">
<label>
<input
type="radio"
id="toggle-payment-with-credit-card"
class="form-check-input text-indigo-600 rounded-full cursor-pointer"
name="payment-type"
checked/>
<span class="ml-1 cursor-pointer">{{ __('texts.new_card') }}</span>
</label>
</li>
</ul>
@endcomponent
@include('portal.ninja2020.gateways.includes.save_card')
<div id="powerboard-payment-container" class="w-full p-4 hidden" style="background-color: rgb(249, 249, 249);">
<div id="widget" style="block" class="hidden"></div>
<div id="widget-3dsecure"></div>
</div>
@include('portal.ninja2020.gateways.includes.pay_now')
<style>
iframe {
border: 0;
width: 100%;
height: 400px;
}
</style>
</div>
@assets
<script src="{{ $widget_endpoint }}"></script>
@vite('resources/js/clients/payments/powerboard-credit-card.js')
@endassets

View File

@ -52,8 +52,8 @@
Livewire.on('passed-required-fields-check', () => {
document.querySelector('div[data-ref="required-fields-container"]').classList.toggle('h-0');
// document.querySelector('div[data-ref="required-fields-container"]').classList.add('opacity-25');
// document.querySelector('div[data-ref="required-fields-container"]').classList.add('pointer-events-none');
document.querySelector('div[data-ref="required-fields-container"]').classList.add('opacity-25');
document.querySelector('div[data-ref="required-fields-container"]').classList.add('pointer-events-none');
document.querySelector('div[data-ref="gateway-container"]').classList.remove('opacity-25');
document.querySelector('div[data-ref="gateway-container"]').classList.remove('pointer-events-none');

View File

@ -16,10 +16,10 @@
<dl>
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-500">
{{ ctrans('texts.start_date') }}
{{ ctrans('texts.last_sent') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ $invoice->translateDate($invoice->start_date, $invoice->client->date_format(), $invoice->client->locale()) }}
{{ $invoice->translateDate($invoice->last_sent_date, $invoice->client->date_format(), $invoice->client->locale()) }}
</dd>
</div>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">

View File

@ -53,6 +53,8 @@ export default defineConfig({
'resources/js/clients/payments/stripe-ach-pay.js',
'resources/js/clients/payments/stripe-bank-transfer.js',
'resources/js/clients/payment_methods/authorize-stripe-acss.js',
'resources/js/clients/payment_methods/authorize-powerboard-card.js',
'resources/js/clients/payments/powerboard-credit-card.js',
]),
viteStaticCopy({
targets: [