1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 00:11:35 +02:00

Merge pull request #9874 from turbo124/v5-develop

Fixes for subscriptions
This commit is contained in:
David Bomba 2024-08-06 19:01:48 +10:00 committed by GitHub
commit 2027cb1bc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
85 changed files with 1359 additions and 3470 deletions

View File

@ -1,4 +1,13 @@
<?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\DataProviders;

View File

@ -1,19 +0,0 @@
<?php
namespace App\DataProviders;
use Omnipay\Rotessa\Object\Frequency;
final class Frequencies
{
public static function get() : array {
return Frequency::getTypes();
}
public static function getFromType() {
}
public static function getOnePayment() {
return Frequency::ONCE;
}
}

View File

@ -22,5 +22,5 @@
*/
function ctrans(string $string, $replace = [], $locale = null): string
{
return trans($string, $replace, $locale);
return html_entity_decode(trans($string, $replace, $locale));
}

View File

@ -80,7 +80,7 @@ class NinjaPlanController extends Controller
$data['intent'] = $setupIntent;
$data['client'] = Auth::guard('contact')->user()->client;
return $this->render('plan.trial', $data);
}
@ -88,6 +88,8 @@ class NinjaPlanController extends Controller
{
$trial_started = "Trial Started @ ".now()->format('Y-m-d H:i:s');
auth()->guard('contact')->user()->fill($request->only(['first_name','last_name']))->save();
$client = auth()->guard('contact')->user()->client;
$client->private_notes = $trial_started;
$client->fill($request->all());

View File

@ -14,7 +14,7 @@ namespace App\Http\Controllers\VendorPortal;
use App\Http\Controllers\Controller;
use App\Models\VendorContact;
use App\Utils\Traits\MakesHash;
use App\Utils\TranslationHelper;
use Illuminate\Http\Request;
class VendorContactController extends Controller
{
@ -58,14 +58,14 @@ class VendorContactController extends Controller
'settings' => $vendor_contact->vendor->company->settings,
'company' => $vendor_contact->vendor->company,
'sidebar' => $this->sidebarMenu(),
'countries' => TranslationHelper::getCountries(),
'countries' => app('countries'),
]);
}
public function update(VendorContact $vendor_contact)
public function update(Request $request, VendorContact $vendor_contact)
{
$vendor_contact->fill(request()->all());
$vendor_contact->vendor->fill(request()->all());
$vendor_contact->fill($request->all());
$vendor_contact->vendor->fill($request->all());
$vendor_contact->push();
return back()->withSuccess(ctrans('texts.profile_updated_successfully'));
@ -76,16 +76,10 @@ class VendorContactController extends Controller
$enabled_modules = auth()->guard('vendor')->user()->company->enabled_modules;
$data = [];
// TODO: Enable dashboard once it's completed.
// $this->settings->enable_client_portal_dashboard
// $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
if (self::MODULE_PURCHASE_ORDERS & $enabled_modules) {
$data[] = ['title' => ctrans('texts.purchase_orders'), 'url' => 'vendor.purchase_orders.index', 'icon' => 'file-text'];
}
// $data[] = ['title' => ctrans('texts.documents'), 'url' => 'client.documents.index', 'icon' => 'download'];
return $data;
}
}

View File

@ -79,7 +79,7 @@ class ContactRegister
// As a fallback for self-hosted, it will use default company in the system
// if key isn't provided in the url.
if (! $request->route()->parameter('company_key') && Ninja::isSelfHost()) {
$company = Account::query()->first()->default_company;
$company = Account::query()->first()->default_company ?? Account::query()->first()->companies->first();
if (! $company->client_can_register) {
abort(400, 'Registration disabled');

View File

@ -32,19 +32,15 @@ class ThrottleRequestsWithPredis extends \Illuminate\Routing\Middleware\Throttle
/**
* Create a new request throttler.
*
* @param \Illuminate\Cache\RateLimiter $limiter
* @param \Illuminate\Contracts\Redis\Factory $redis
* @return void
*/
/** @phpstan-ignore-next-line */
public function __construct(RateLimiter $limiter, Redis $redis)
{
parent::__construct($limiter);
/** @phpstan-ignore-next-line */
$this->redis = \Illuminate\Support\Facades\Redis::connection('sentinel-cache');
// $this->redis = $redis;
}
/**
@ -126,7 +122,6 @@ class ThrottleRequestsWithPredis extends \Illuminate\Routing\Middleware\Throttle
/**
* Get the Redis connection that should be used for throttling.
*
* @return \Illuminate\Redis\Connections\Connection
*/
protected function getRedisConnection()
{

View File

@ -1,4 +1,13 @@
<?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\Http\ViewComposers\Components\Rotessa;

View File

@ -1,4 +1,13 @@
<?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\Http\ViewComposers\Components\Rotessa;

View File

@ -1,4 +1,13 @@
<?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\Http\ViewComposers\Components\Rotessa;

View File

@ -88,11 +88,11 @@ class PortalComposer
$data['sidebar'] = $this->sidebarMenu();
$data['header'] = [];
$data['footer'] = [];
$data['countries'] = TranslationHelper::getCountries();
$data['countries'] = app('countries');
$data['company'] = auth()->guard('contact')->user()->company;
$data['client'] = auth()->guard('contact')->user()->client;
$data['settings'] = $this->settings;
$data['currencies'] = TranslationHelper::getCurrencies();
$data['currencies'] = app('currencies');
$data['contact'] = auth()->guard('contact')->user();
$data['multiple_contacts'] = session()->get('multiple_contacts') ?: collect();
@ -136,11 +136,11 @@ class PortalComposer
$data[] = ['title' => ctrans('texts.statement'), 'url' => 'client.statement', 'icon' => 'activity'];
if (Ninja::isHosted() && auth()->guard('contact')->user()->company->id == config('ninja.ninja_default_company_id')) {
// if (Ninja::isHosted() && auth()->guard('contact')->user()->company->id == config('ninja.ninja_default_company_id')) {
$data[] = ['title' => ctrans('texts.plan'), 'url' => 'client.plan', 'icon' => 'credit-card'];
} else {
// } else {
$data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar'];
}
// }
if (auth()->guard('contact')->user()->client->getSetting('client_initiated_payments')) {
$data[] = ['title' => ctrans('texts.pre_payment'), 'url' => 'client.pre_payments.index', 'icon' => 'dollar-sign'];

View File

@ -1,4 +1,13 @@
<?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
*/
use Illuminate\Support\Facades\View;
use App\DataProviders\CAProvinces;
@ -9,7 +18,6 @@ View::composer(['*.rotessa.components.address','*.rotessa.components.banks.US.ba
$view->with('states', $states);
});
// CAProvinces View Composer
View::composer(['*.rotessa.components.address','*.rotessa.components.banks.CA.bank','*.rotessa.components.dropdowns.country.CA'], function ($view) {
$provinces = CAProvinces::get();
$view->with('provinces', $provinces);

View File

@ -356,15 +356,14 @@ class BillingPortalPurchase extends Component
$this->methods = $contact->client->service()->getPaymentMethods($this->price);
foreach($this->methods as $method){
if($method['is_paypal'] == '1' && !$this->steps['check_rff']){
$this->rff();
break;
}
}
$method_values = array_column($this->methods, 'is_paypal');
$is_paypal = in_array('1', $method_values);
if($is_paypal && !$this->steps['check_rff'])
$this->rff();
elseif(!$is_paypal && !$this->steps['check_rff'])
$this->steps['fetched_payment_methods'] = true;
$this->heading_text = ctrans('texts.payment_methods');
return $this;

View File

@ -16,6 +16,7 @@ use App\Models\CompanyUser;
use Illuminate\Support\Carbon;
use App\Utils\Traits\MakesHash;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Libraries\Currency\Conversion\CurrencyApi;
/**
* App\Models\Task
@ -159,27 +160,55 @@ class Task extends BaseModel
return $this->morphMany(Document::class, 'documentable');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function assigned_user()
{
return $this->belongsTo(User::class, 'assigned_user_id', 'id')->withTrashed();
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class)->withTrashed();
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function client()
{
return $this->belongsTo(Client::class)->withTrashed();
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function status()
{
return $this->belongsTo(TaskStatus::class)->withTrashed();
}
public function stringStatus()
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function invoice()
{
return $this->belongsTo(Invoice::class)->withTrashed();
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function project()
{
return $this->belongsTo(Project::class)->withTrashed();
}
public function stringStatus(): string
{
if($this->invoice_id) {
return '<h5><span class="badge badge-success">'.ctrans('texts.invoiced').'</span></h5>';
@ -193,16 +222,6 @@ class Task extends BaseModel
}
public function invoice()
{
return $this->belongsTo(Invoice::class)->withTrashed();
}
public function project()
{
return $this->belongsTo(Project::class)->withTrashed();
}
public function calcStartTime()
{
$parts = json_decode($this->time_log) ?: [];
@ -230,7 +249,7 @@ class Task extends BaseModel
public function calcDuration($start_time_cutoff = 0, $end_time_cutoff = 0)
{
$duration = 0;
$parts = json_decode($this->time_log) ?: [];
$parts = json_decode($this->time_log ?? '{}') ?: [];
foreach ($parts as $part) {
$start_time = $part[0];
@ -272,6 +291,26 @@ class Task extends BaseModel
return $this->company->settings->default_task_rate ?? 0;
}
public function taskCompanyValue(): float
{
$client_currency = $this->client->getSetting('currency_id');
$company_currency = $this->company->getSetting('currency_id');
if($client_currency != $company_currency)
{
$converter = new CurrencyApi();
return $converter->convert($this->taskValue(), $client_currency, $company_currency);
}
return $this->taskValue();
}
public function taskValue(): float
{
return round(($this->calcDuration() / 3600) * $this->getRate(),2);
}
public function processLogs()
{

View File

@ -20,7 +20,7 @@ abstract class AbstractPaymentDriver
{
abstract public function authorizeView(array $data);
abstract public function authorizeResponse(Request $request);
abstract public function authorizeResponse(\App\Http\Requests\Request | Request $request);
abstract public function processPaymentView(array $data);

View File

@ -1,249 +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\PaymentDrivers;
use App\Exceptions\PaymentFailed;
use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\Utils\Traits\MakesHash;
use Omnipay\Common\Item;
use Omnipay\Omnipay;
class PayPalExpressPaymentDriver extends BaseDriver
{
use MakesHash;
public $token_billing = false;
public $can_authorise_credit_card = false;
private $omnipay_gateway;
private float $fee = 0;
public const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYPAL;
public function gatewayTypes()
{
return [
GatewayType::PAYPAL,
];
}
public function init()
{
return $this;
}
/**
* Initialize Omnipay PayPal_Express gateway.
*
* @return void
*/
private function initializeOmnipayGateway(): void
{
$this->omnipay_gateway = Omnipay::create(
$this->company_gateway->gateway->provider
);
$this->omnipay_gateway->initialize((array) $this->company_gateway->getConfig());
}
public function setPaymentMethod($payment_method_id)
{
// PayPal doesn't have multiple ways of paying.
// There's just one, off-site redirect.
return $this;
}
public function authorizeView($payment_method)
{
// PayPal doesn't support direct authorization.
return $this;
}
public function authorizeResponse($request)
{
// PayPal doesn't support direct authorization.
return $this;
}
public function processPaymentView($data)
{
$this->initializeOmnipayGateway();
$this->payment_hash->data = array_merge((array) $this->payment_hash->data, ['amount' => $data['total']['amount_with_fee']]);
$this->payment_hash->save();
$response = $this->omnipay_gateway
->purchase($this->generatePaymentDetails($data))
->setItems($this->generatePaymentItems($data))
->send();
if ($response->isRedirect()) {
return $response->redirect();
}
// $this->sendFailureMail($response->getMessage() ?: '');
$message = [
'server_response' => $response->getMessage(),
'data' => $this->payment_hash->data,
];
SystemLogger::dispatch(
$message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_PAYPAL,
$this->client,
$this->client->company,
);
throw new PaymentFailed($response->getMessage(), $response->getCode());
}
public function processPaymentResponse($request)
{
$this->initializeOmnipayGateway();
$response = $this->omnipay_gateway
->completePurchase(['amount' => $this->payment_hash->data->amount, 'currency' => $this->client->getCurrencyCode()])
->send();
if ($response->isCancelled() && $this->client->getSetting('enable_client_portal')) {
return redirect()->route('client.invoices.index')->with('warning', ctrans('texts.status_cancelled'));
} elseif($response->isCancelled() && !$this->client->getSetting('enable_client_portal')) {
redirect()->route('client.invoices.show', ['invoice' => $this->payment_hash->fee_invoice])->with('warning', ctrans('texts.status_cancelled'));
}
if ($response->isSuccessful()) {
$data = [
'payment_method' => $response->getData()['TOKEN'],
'payment_type' => PaymentType::PAYPAL,
'amount' => $this->payment_hash->data->amount,
'transaction_reference' => $response->getTransactionReference(),
'gateway_type_id' => GatewayType::PAYPAL,
];
$payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => (array) $response->getData(), 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_PAYPAL,
$this->client,
$this->client->company,
);
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
}
if (! $response->isSuccessful()) {
$data = $response->getData();
$this->sendFailureMail($response->getMessage() ?: '');
$message = [
'server_response' => $data['L_LONGMESSAGE0'],
'data' => $this->payment_hash->data,
];
SystemLogger::dispatch(
$message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_PAYPAL,
$this->client,
$this->client->company,
);
throw new PaymentFailed($response->getMessage(), $response->getCode());
}
}
public function generatePaymentDetails(array $data)
{
$_invoice = collect($this->payment_hash->data->invoices)->first();
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
// $this->fee = $this->feeCalc($invoice, $data['total']['amount_with_fee']);
return [
'currency' => $this->client->getCurrencyCode(),
'transactionType' => 'Purchase',
'clientIp' => request()->getClientIp(),
// 'amount' => round(($data['total']['amount_with_fee'] + $this->fee),2),
'amount' => round($data['total']['amount_with_fee'], 2),
'returnUrl' => route('client.payments.response', [
'company_gateway_id' => $this->company_gateway->id,
'payment_hash' => $this->payment_hash->hash,
'payment_method_id' => GatewayType::PAYPAL,
]),
'cancelUrl' => $this->client->company->domain()."/client/invoices/{$invoice->hashed_id}",
'description' => implode(',', collect($this->payment_hash->data->invoices)
->map(function ($invoice) {
return sprintf('%s: %s', ctrans('texts.invoice_number'), $invoice->invoice_number);
})->toArray()),
'transactionId' => $this->payment_hash->hash.'-'.time(),
'ButtonSource' => 'InvoiceNinja_SP',
'solutionType' => 'Sole',
'no_shipping' => $this->company_gateway->require_shipping_address ? 0 : 1,
];
}
public function generatePaymentItems(array $data)
{
$_invoice = collect($this->payment_hash->data->invoices)->first();
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
$items = [];
$items[] = new Item([
'name' => ' ',
'description' => ctrans('texts.invoice_number').'# '.$invoice->number,
'price' => $data['total']['amount_with_fee'],
'quantity' => 1,
]);
return $items;
}
private function feeCalc($invoice, $invoice_total)
{
$invoice->service()->removeUnpaidGatewayFees();
$invoice = $invoice->fresh();
$balance = floatval($invoice->balance);
$_updated_invoice = $invoice->service()->addGatewayFee($this->company_gateway, GatewayType::PAYPAL, $invoice_total)->save();
if (floatval($_updated_invoice->balance) > $balance) {
$fee = floatval($_updated_invoice->balance) - $balance;
$this->payment_hash->fee_total = $fee;
$this->payment_hash->save();
return $fee;
}
return 0;
}
}

View File

@ -12,14 +12,11 @@
namespace App\PaymentDrivers\Rotessa;
use Carbon\Carbon;
use App\Models\Client;
use App\Models\Payment;
use App\Models\SystemLog;
use Illuminate\View\View;
use App\Models\GatewayType;
use App\Models\PaymentType;
use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use App\Jobs\Util\SystemLogger;
use App\Exceptions\PaymentFailed;
@ -28,16 +25,18 @@ use App\Models\ClientGatewayToken;
use Illuminate\Http\RedirectResponse;
use App\PaymentDrivers\RotessaPaymentDriver;
use App\PaymentDrivers\Common\MethodInterface;
use App\PaymentDrivers\Rotessa\Resources\Customer;
use App\PaymentDrivers\Rotessa\Resources\Transaction;
use Omnipay\Common\Exception\InvalidRequestException;
use Omnipay\Common\Exception\InvalidResponseException;
use App\Exceptions\Ninja\ClientPortalAuthorizationException;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
class PaymentMethod implements MethodInterface
{
private array $transaction = [
"financial_transactions" => [],
"frequency" =>'Once',
"installments" =>1
];
public function __construct(protected RotessaPaymentDriver $rotessa)
{
$this->rotessa->init();
@ -51,7 +50,7 @@ class PaymentMethod implements MethodInterface
*/
public function authorizeView(array $data): View
{
$data['contact'] = collect($data['client']->contacts->firstWhere('is_primary', 1)->toArray())->merge([
$data['contact'] = collect($data['client']->contacts->first()->toArray())->merge([
'home_phone' => $data['client']->phone,
'custom_identifier' => $data['client']->number,
'name' => $data['client']->name,
@ -73,42 +72,38 @@ class PaymentMethod implements MethodInterface
* @param Request $request
* @return RedirectResponse
*/
public function authorizeResponse(Request $request): RedirectResponse
public function authorizeResponse($request)
{
try {
$request->validate([
'gateway_type_id' => ['required','integer'],
'country' => ['required'],
'name' => ['required'],
'address_1' => ['required'],
// 'address_2' => ['required'],
'city' => ['required'],
'email' => ['required','email:filter'],
'province_code' => ['required','size:2','alpha'],
'postal_code' => ['required'],
'authorization_type' => ['required'],
'account_number' => ['required'],
'bank_name' => ['required'],
'phone' => ['required'],
'home_phone' => ['required','size:10'],
'bank_account_type'=>['required_if:country,US'],
'routing_number'=>['required_if:country,US'],
'institution_number'=>['required_if:country,CA','numeric'],
'transit_number'=>['required_if:country,CA','numeric'],
'custom_identifier'=>['required_without:customer_id'],
'customer_id'=>['required_without:custom_identifier','integer'],
]);
$customer = new Customer( ['address' => $request->only('address_1','address_2','city','postal_code','province_code','country'), 'custom_identifier' => $request->input('custom_identifier') ] + $request->all());
$this->rotessa->findOrCreateCustomer($customer->resolve());
return redirect()->route('client.payment_methods.index')->withMessage(ctrans('texts.payment_method_added'));
$request->validate([
'gateway_type_id' => ['required','integer'],
'country' => ['required','in:US,CA,United States,Canada'],
'name' => ['required'],
'address_1' => ['required'],
'city' => ['required'],
'email' => ['required','email:filter'],
'province_code' => ['required','size:2','alpha'],
'postal_code' => ['required'],
'authorization_type' => ['required'],
'account_number' => ['required'],
'bank_name' => ['required'],
'phone' => ['required'],
'home_phone' => ['required','size:10'],
'bank_account_type'=>['required_if:country,US'],
'routing_number'=>['required_if:country,US'],
'institution_number'=>['required_if:country,CA','numeric','digits:3'],
'transit_number'=>['required_if:country,CA','numeric','digits:5'],
'custom_identifier'=>['required_without:customer_id'],
'customer_id'=>['required_without:custom_identifier','integer'],
'customer_type' => ['required', 'in:Personal,Business'],
]);
} catch (\Throwable $e) {
return $this->rotessa->processInternallyFailedPayment($this->rotessa, new ClientPortalAuthorizationException( get_class( $e) . " : {$e->getMessage()}", (int) $e->getCode() ));
}
$customer = array_merge(['address' => $request->only('address_1','address_2','city','postal_code','province_code','country'), 'custom_identifier' => $request->input('custom_identifier') ], $request->all());
$this->rotessa->findOrCreateCustomer($customer);
return redirect()->route('client.payment_methods.index')->withMessage(ctrans('texts.payment_method_added'));
// return back()->withMessage(ctrans('texts.unable_to_verify_payment_method'));
}
/**
@ -124,7 +119,7 @@ class PaymentMethod implements MethodInterface
$data['due_date'] = date('Y-m-d', min(max(strtotime($data['invoices']->max('due_date')), strtotime('now')), strtotime('+1 day')));
$data['process_date'] = $data['due_date'];
$data['currency'] = $this->rotessa->client->getCurrencyCode();
$data['frequency'] = Frequencies::getOnePayment();
$data['frequency'] = 'Once';
$data['installments'] = 1;
$data['invoice_nums'] = $data['invoices']->pluck('invoice_number')->join(', ');
return render('gateways.rotessa.bank_transfer.pay', $data );
@ -142,11 +137,7 @@ class PaymentMethod implements MethodInterface
$customer = null;
try {
$request->validate([
'source' => ['required','string','exists:client_gateway_tokens,token'],
'amount' => ['required','numeric'],
'process_date'=> ['required','date','after_or_equal:today'],
]);
$customer = ClientGatewayToken::query()
->where('company_gateway_id', $this->rotessa->company_gateway->id)
->where('client_id', $this->rotessa->client->id)
@ -156,19 +147,23 @@ class PaymentMethod implements MethodInterface
if(!$customer) throw new \Exception('Client gateway token not found!', SystemLog::TYPE_ROTESSA);
$transaction = new Transaction($request->only('frequency' ,'installments','amount','process_date') + ['comment' => $this->rotessa->getDescription(false) ]);
$transaction->additional(['customer_id' => $customer->gateway_customer_reference]);
$transaction = array_filter( $transaction->resolve());
$transaction = array_merge($this->transaction,[
'amount' => $request->input('amount'),
'process_date' => now()->addSeconds($customer->client->utc_offset())->format('Y-m-d'),
'comment' => $this->rotessa->getDescription(false),
'customer_id' => $customer->gateway_customer_reference,
]);
$response = $this->rotessa->gatewayRequest('post','transaction_schedules', $transaction);
if($response->failed())
$response->throw();
$response = $response->json();
nlog($response);
return $this->processPendingPayment($response['id'], (float) $response['amount'], PaymentType::ACSS , $customer->token);
} catch(\Throwable $e) {
$this->processUnsuccessfulPayment( new InvalidResponseException($e->getMessage(), (int) $e->getCode()) );
$this->processUnsuccessfulPayment( new \Exception($e->getMessage(), (int) $e->getCode()) );
}
}

View File

@ -1,22 +0,0 @@
<?php
namespace App\PaymentDrivers\Rotessa\Resources;
use Illuminate\Http\Request;
use Omnipay\Rotessa\Model\CustomerModel;
use Illuminate\Http\Resources\Json\JsonResource;
class Customer extends JsonResource
{
function __construct($resource) {
parent::__construct( new CustomerModel($resource));
}
function jsonSerialize() : array {
return $this->resource->jsonSerialize();
}
function toArray(Request $request) : array {
return $this->additional + parent::toArray($request);
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace App\PaymentDrivers\Rotessa\Resources;
use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Omnipay\Rotessa\Model\TransactionScheduleModel;
class Transaction extends JsonResource
{
function __construct($resource) {
parent::__construct( new TransactionScheduleModel( $resource));
}
function jsonSerialize() : array {
return $this->resource->jsonSerialize();
}
function toArray(Request $request) : array {
return $this->additional + parent::toArray($request);
}
}

View File

@ -1,21 +0,0 @@
<?php
namespace Omnipay\Rotessa;
use Omnipay\Common\AbstractGateway;
use Omnipay\Rotessa\ClientInterface;
use Omnipay\Rotessa\Message\RequestInterface;
abstract class AbstractClient extends AbstractGateway implements ClientInterface
{
protected $default_parameters = [];
public function getDefaultParameters() : array {
return $this->default_parameters;
}
public function setDefaultParameters(array $params) {
$this->default_parameters = $params;
}
}

View File

@ -1,41 +0,0 @@
<?php
namespace Omnipay\Rotessa;
use Omnipay\Rotessa\Message\Request\RequestInterface;
trait ApiTrait
{
public function getCustomers() : RequestInterface {
return $this->createRequest('GetCustomers', [] );
}
public function postCustomers(array $params) : RequestInterface {
return $this->createRequest('PostCustomers', $params );
}
public function getCustomersId(array $params) : RequestInterface {
return $this->createRequest('GetCustomersId', $params );
}
public function patchCustomersId(array $params) : RequestInterface {
return $this->createRequest('PatchCustomersId', $params );
}
public function postCustomersShowWithCustomIdentifier(array $params) : RequestInterface {
return $this->createRequest('PostCustomersShowWithCustomIdentifier', $params );
}
public function getTransactionSchedulesId(array $params) : RequestInterface {
return $this->createRequest('GetTransactionSchedulesId', $params );
}
public function deleteTransactionSchedulesId(array $params) : RequestInterface {
return $this->createRequest('DeleteTransactionSchedulesId', $params );
}
public function patchTransactionSchedulesId(array $params) : RequestInterface {
return $this->createRequest('PatchTransactionSchedulesId', $params );
}
public function postTransactionSchedules(array $params) : RequestInterface {
return $this->createRequest('PostTransactionSchedules', $params );
}
public function postTransactionSchedulesCreateWithCustomIdentifier(array $params) : RequestInterface {
return $this->createRequest('PostTransactionSchedulesCreateWithCustomIdentifier', $params );
}
public function postTransactionSchedulesUpdateViaPost(array $params) : RequestInterface {
return $this->createRequest('PostTransactionSchedulesUpdateViaPost', $params );
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace Omnipay\Rotessa;
use Omnipay\Common\GatewayInterface;
use Omnipay\Rotessa\Message\Request\RequestInterface;
interface ClientInterface extends GatewayInterface
{
public function getDefaultParameters(): array;
public function setDefaultParameters(array $data);
}

View File

@ -1,43 +0,0 @@
<?php
namespace Omnipay\Rotessa\Exception;
class BadRequestException extends \Exception {
protected $message = "Your request includes invalid parameters";
protected $code = 400;
}
class UnauthorizedException extends \Exception {
protected $message = "Your API key is not valid or is missing";
protected $code = 401;
}
class NotFoundException extends \Exception {
protected $message = "The specified resource could not be found";
protected $code = 404;
}
class NotAcceptableException extends \Exception {
protected $message = "You requested a format that isnt json";
protected $code = 406;
}
class UnprocessableEntityException extends \Exception {
protected $message = "Your request results in invalid data";
protected $code = 422;
}
class InternalServerErrorException extends \Exception {
protected $message = "We had a problem with our server. Try again later";
protected $code = 500;
}
class ServiceUnavailableException extends \Exception {
protected $message = "We're temporarily offline for maintenance. Please try again later";
protected $code = 503;
}
class ValidationException extends \Exception {
protected $message = "A validation error has occured";
protected $code = 600;
}

View File

@ -1,74 +0,0 @@
<?php
namespace Omnipay\Rotessa;
use Omnipay\Rotessa\ApiTrait;
use Omnipay\Rotessa\AbstractClient;
use Omnipay\Rotessa\ClientInterface;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class Gateway extends AbstractClient implements ClientInterface {
use ApiTrait;
protected $default_parameters = ['api_key' => 1234567890 ];
protected $test_mode = true;
protected $api_key;
public function getName()
{
return 'Rotessa';
}
public function getDefaultParameters() : array
{
return array_merge($this->default_parameters, array('api_key' => $this->api_key, 'test_mode' => $this->test_mode ) );
}
public function setTestMode($value) {
$this->test_mode = $value;
}
public function getTestMode() {
return $this->test_mode;
}
protected function createRequest($class_name, ?array $parameters = [] ) :RequestInterface {
$class = null;
$class_name = "Omnipay\\Rotessa\\Message\\Request\\$class_name";
$parameters = $class_name::hasModel() ? (($parameters = ($class_name::getModel($parameters)))->validate() ? $parameters->jsonSerialize() : null ) : $parameters;
try {
$class = new $class_name($this->httpClient, $this->httpRequest, $this->getDefaultParameters() + $parameters );
} catch (\Throwable $th) {
throw $th;
}
return $class;
}
function setApiKey($value) {
$this->api_key = $value;
}
function getApiKey() {
return $this->api_key;
}
function authorize(array $options = []) : RequestInterface {
return $this->postCustomers($options);
}
function capture(array $options = []) : RequestInterface {
return array_key_exists('customer_id', $options)? $this->postTransactionSchedules($options) : $this->postTransactionSchedulesCreateWithCustomIdentifier($options) ;
}
function updateCustomer(array $options) : RequestInterface {
return $this->patchCustomersId($options);
}
function fetchTransaction($id = null) : RequestInterface {
return $this->getTransactionSchedulesId(compact('id'));
}
}

View File

@ -1,82 +0,0 @@
<?php
namespace Omnipay\Rotessa\Http;
use Omnipay\Common\Http\Client as HttpClient;
use Omnipay\Common\Http\Exception\NetworkException;
use Omnipay\Common\Http\Exception\RequestException;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Message\RequestFactory;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class Client extends HttpClient
{
/**
* The Http Client which implements `public function sendRequest(RequestInterface $request)`
* Note: Will be changed to PSR-18 when released
*
* @var HttpClient
*/
private $httpClient;
/**
* @var RequestFactory
*/
private $requestFactory;
public function __construct($httpClient = null, RequestFactory $requestFactory = null)
{
$this->httpClient = $httpClient ?: HttpClientDiscovery::find();
$this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find();
parent::__construct($httpClient, $requestFactory);
}
/**
* @param $method
* @param $uri
* @param array $headers
* @param string|array|resource|StreamInterface|null $body
* @param string $protocolVersion
* @return ResponseInterface
* @throws \Http\Client\Exception
*/
public function request(
$method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1'
) {
return $this->sendRequest($method, $uri, $headers, $body, $protocolVersion);
}
/**
* @param RequestInterface $request
* @return ResponseInterface
* @throws \Http\Client\Exception
*/
private function sendRequest( $method,
$uri,
array $headers = [],
$body = null,
$protocolVersion = '1.1')
{
$response = null;
try {
if( method_exists($this->httpClient, 'sendRequest'))
$response = $this->httpClient->sendRequest( $this->requestFactory->createRequest($method, $uri, $headers, $body, $protocolVersion));
else $response = $this->httpClient->request($method, $uri, compact('body','headers'));
} catch (\Http\Client\Exception\NetworkException $networkException) {
throw new \Exception($networkException->getMessage());
} catch (\Exception $exception) {
throw new \Exception($exception->getMessage());
}
return $response;
}
}

View File

@ -1,32 +0,0 @@
<?php
namespace Omnipay\Rotessa\Http\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class Response extends JsonResponse
{
protected $reason_phrase = '';
protected $reason_code = '';
public function __construct(mixed $data = null, int $status = 200, array $headers = [])
{
parent::__construct($data , $status, $headers, true);
if(array_key_exists('errors',$data = json_decode( $this->content, true) )) {
$data = $data['errors'][0];
$this->reason_phrase = $data['error_message'] ;
$this->reason_code = $data['error_message'] ;
}
}
public function getReasonPhrase() {
return $this->reason_phrase;
}
public function getReasonCode() {
return $this->reason_code;
}
}

View File

@ -1,12 +0,0 @@
<?php
namespace Omnipay\Rotessa;
trait IsValidTypeTrait {
public static function isValid(string $value) {
return in_array($value, self::getTypes());
}
abstract public static function getTypes() : array;
}

View File

@ -1,52 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
use Omnipay\Rotessa\Message\Request\RequestInterface;
use Omnipay\Common\Message\AbstractRequest as Request;
use Omnipay\Rotessa\Message\Response\ResponseInterface;
abstract class AbstractRequest extends Request implements RequestInterface
{
protected $test_mode = false;
protected $api_version;
protected $method = 'GET';
protected $endpoint;
protected $api_key;
public function setApiKey(string $value) {
$this->api_key = $value;
}
public function getData() {
try {
if(empty($this->api_key)) throw new \Exception('No Api Key Found!');
$this->validate( ...array_keys($data = $this->getParameters()));
} catch (\Throwable $th) {
throw new \Omnipay\Rotessa\Exception\ValidationException($th->getMessage() , 600, $th);
}
return (array) $data;
}
abstract public function sendData($data) : ResponseInterface;
abstract protected function sendRequest(string $method, string $endpoint, array $headers = [], array $data = [] );
abstract protected function createResponse(array $data) : ResponseInterface;
abstract public function getEndpointUrl(): string;
public function getEndpoint() : string {
return $this->endpoint;
}
public function getTestMode() {
return $this->test_mode;
}
public function setTestMode($mode) {
$this->test_mode = $mode;
}
}

View File

@ -1,93 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
use Omnipay\Common\Http\ClientInterface;
use Omnipay\Rotessa\Http\Response\Response;
use Omnipay\Rotessa\Message\Response\BaseResponse;
use Omnipay\Rotessa\Message\Request\RequestInterface;
use Omnipay\Rotessa\Message\Response\ResponseInterface;
use Symfony\Component\HttpFoundation\Request as HttpRequest;
class BaseRequest extends AbstractRequest implements RequestInterface
{
protected $base_url = 'rotessa.com';
protected $api_version = 1;
protected $endpoint = '';
const ENVIRONMENT_SANDBOX = 'sandbox-api';
const ENVIRONMENT_LIVE = 'api';
function __construct(ClientInterface $http_client = null, HttpRequest $http_request, $model ) {
parent::__construct($http_client, $http_request );
$this->initialize($model);
}
protected function sendRequest(string $method, string $endpoint, array $headers = [], array $data = [])
{
/**
* @param $method
* @param $uri
* @param array $headers
* @param string|resource|StreamInterface|null $body
* @param string $protocolVersion
* @return ResponseInterface
* @throws \Http\Client\Exception
*/
$response = $this->httpClient->request($method, $endpoint, $headers, json_encode($data) ) ;
$this->response = new Response ($response->getBody()->getContents(), $response->getStatusCode(), $response->getHeaders(), true);
}
protected function createResponse(array $data): ResponseInterface {
return new BaseResponse($this, $data, $this->response->getStatusCode(), $this->response->getReasonPhrase());
}
protected function replacePlaceholder($string, $array) {
$pattern = "/\{([^}]+)\}/";
$replacement = function($matches) use($array) {
$key = $matches[1];
if (array_key_exists($key, $array)) {
return $array[$key];
} else {
return $matches[0];
}
};
return preg_replace_callback($pattern, $replacement, $string);
}
public function sendData($data) :ResponseInterface {
$headers = [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'Authorization' => "Token token={$this->api_key}"
];
$this->sendRequest(
$this->method,
$this->getEndpointUrl(),
$headers,
$data);
return $this->createResponse(json_decode($this->response->getContent(), true));
}
public function getEndpoint() : string {
return $this->replacePlaceholder($this->endpoint, $this->getParameters());
}
public function getEndpointUrl() : string {
return sprintf('https://%s.%s/v%d%s',$this->test_mode ? self::ENVIRONMENT_SANDBOX : self::ENVIRONMENT_LIVE ,$this->base_url, $this->api_version, $this->getEndpoint());
}
public static function hasModel() : bool {
return (bool) static::$model;
}
public static function getModel($parameters = []) {
$class_name = static::$model;
$class_name = "Omnipay\\Rotessa\\Model\\{$class_name}Model";
return new $class_name($parameters);
}
}

View File

@ -1,18 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
use Omnipay\Rotessa\Message\Request\BaseRequest;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class DeleteTransactionSchedulesId extends BaseRequest implements RequestInterface
{
protected $endpoint = '/transaction_schedules/{id}';
protected $method = 'DELETE';
protected static $model = '';
public function setId(string $value) {
$this->setParameter('id',$value);
}
}

View File

@ -1,14 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
use Omnipay\Rotessa\Message\Request\BaseRequest;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class GetCustomers extends BaseRequest implements RequestInterface
{
protected $endpoint = '/customers';
protected $method = 'GET';
protected static $model = '';
}

View File

@ -1,19 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
use Omnipay\Rotessa\Message\Request\BaseRequest;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class GetCustomersId extends BaseRequest implements RequestInterface
{
protected $endpoint = '/customers/{id}';
protected $method = 'GET';
protected static $model = '';
public function setId(int $value) {
$this->setParameter('id',$value);
}
}

View File

@ -1,17 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
use Omnipay\Rotessa\Message\Request\BaseRequest;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class GetTransactionSchedulesId extends BaseRequest implements RequestInterface
{
protected $endpoint = '/transaction_schedules/{id}';
protected $method = 'GET';
protected static $model = '';
public function setId(int $value) {
$this->setParameter('id',$value);
}
}

View File

@ -1,65 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
use Omnipay\Rotessa\Message\Request\BaseRequest;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class PatchCustomersId extends BaseRequest implements RequestInterface
{
protected $endpoint = '/customers/{id}';
protected $method = 'PATCH';
protected static $model = 'CustomerPatch';
public function setId(string $value) {
$this->setParameter('id',$value);
}
public function setCustomIdentifier(string $value) {
$this->setParameter('custom_identifier',$value);
}
public function setName(string $value) {
$this->setParameter('name',$value);
}
public function setEmail(string $value) {
$this->setParameter('email',$value);
}
public function setCustomerType(string $value) {
$this->setParameter('customer_type',$value);
}
public function setHomePhone(string $value) {
$this->setParameter('home_phone',$value);
}
public function setPhone(string $value) {
$this->setParameter('phone',$value);
}
public function setBankName(string $value) {
$this->setParameter('bank_name',$value);
}
public function setInstitutionNumber(string $value) {
$this->setParameter('institution_number',$value);
}
public function setTransitNumber(string $value) {
$this->setParameter('transit_number',$value);
}
public function setBankAccountType(string $value) {
$this->setParameter('bank_account_type',$value);
}
public function setAuthorizationType(string $value) {
$this->setParameter('authorization_type',$value);
}
public function setRoutingNumber(string $value) {
$this->setParameter('routing_number',$value);
}
public function setAccountNumber(string $value) {
$this->setParameter('account_number',$value);
}
public function setAddress(array $value) {
$this->setParameter('address',$value);
}
public function setTransactionSchedules(array $value) {
$this->setParameter('transaction_schedules',$value);
}
public function setFinancialTransactions(array $value) {
$this->setParameter('financial_transactions',$value);
}
}

View File

@ -1,22 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
use Omnipay\Rotessa\Message\Request\BaseRequest;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class PatchTransactionSchedulesId extends BaseRequest implements RequestInterface
{
protected $endpoint = '/transaction_schedules/{id}';
protected $method = 'PATCH';
public function setId(int $value) {
$this->setParameter('id',$value);
}
public function setAmount($value) {
$this->setParameter('amount',$value);
}
public function setComment(string $value) {
$this->setParameter('comment',$value);
}
}

View File

@ -1,60 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
use Omnipay\Rotessa\Message\Request\BaseRequest;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class PostCustomers extends BaseRequest implements RequestInterface
{
protected $endpoint = '/customers';
protected $method = 'POST';
protected static $model = 'Customer';
public function setId(string $value) {
$this->setParameter('id',$value);
}
public function setCustomIdentifier(string $value) {
$this->setParameter('custom_identifier',$value);
}
public function setName(string $value) {
$this->setParameter('name',$value);
}
public function setEmail(string $value) {
$this->setParameter('email',$value);
}
public function setCustomerType(string $value) {
$this->setParameter('customer_type',$value);
}
public function setHomePhone(string $value) {
$this->setParameter('home_phone',$value);
}
public function setPhone(string $value) {
$this->setParameter('phone',$value);
}
public function setBankName(string $value) {
$this->setParameter('bank_name',$value);
}
public function setInstitutionNumber(string $value = '') {
$this->setParameter('institution_number',$value);
}
public function setTransitNumber(string $value = '') {
$this->setParameter('transit_number',$value);
}
public function setBankAccountType(string $value) {
$this->setParameter('bank_account_type',$value);
}
public function setAuthorizationType(string $value = '') {
$this->setParameter('authorization_type',$value);
}
public function setRoutingNumber(string $value = '') {
$this->setParameter('routing_number',$value);
}
public function setAccountNumber(string $value) {
$this->setParameter('account_number',$value);
}
public function setAddress(array $value) {
$this->setParameter('address',$value);
}
}

View File

@ -1,19 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
use Omnipay\Rotessa\Message\Request\BaseRequest;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class PostCustomersShowWithCustomIdentifier extends BaseRequest implements RequestInterface
{
protected $endpoint = '/customers/show_with_custom_identifier';
protected $method = 'POST';
protected static $model = null;
public function setCustomIdentifier(string $value) {
$this->setParameter('custom_identifier',$value);
}
}

View File

@ -1,31 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
use Omnipay\Rotessa\Message\Request\BaseRequest;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class PostTransactionSchedules extends BaseRequest implements RequestInterface
{
protected $endpoint = '/transaction_schedules';
protected $method = 'POST';
protected static $model = 'TransactionSchedule';
public function setCustomerId(string $value) {
$this->setParameter('customer_id',$value);
}
public function setProcessDate(string $value) {
$this->setParameter('process_date',$value);
}
public function setFrequency(string $value) {
$this->setParameter('frequency',$value);
}
public function setInstallments(int $value) {
$this->setParameter('installments',$value);
}
public function setComment(string $value) {
$this->setParameter('comment',$value);
}
}

View File

@ -1,16 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class PostTransactionSchedulesCreateWithCustomIdentifier extends PostTransactionSchedules implements RequestInterface
{
protected $endpoint = '/transaction_schedules/create_with_custom_identifier';
protected $method = 'POST';
public function setCustomIdentifier(string $value) {
$this->setParameter('custom_identifier',$value);
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
// You will need to create this BaseRequest class as abstracted from the AbstractRequest;
use Omnipay\Rotessa\Message\Request\BaseRequest;
use Omnipay\Rotessa\Message\Request\RequestInterface;
class PostTransactionSchedulesUpdateViaPost extends BaseRequest implements RequestInterface
{
protected $endpoint = '/transaction_schedules/update_via_post';
protected $method = 'POST';
public function setId(int $value) {
$this->setParameter('id',$value);
}
public function setAmount($value) {
$this->setParameter('amount',$value);
}
public function setComment(string $value) {
$this->setParameter('comment',$value);
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Request;
use Omnipay\Rotessa\Message\Response\ResponseInterface;
use Omnipay\Common\Message\RequestInterface as MessageInterface;
interface RequestInterface extends MessageInterface
{
}

View File

@ -1,16 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Response;
use Omnipay\Common\Message\AbstractResponse as Response;
abstract class AbstractResponse extends Response implements ResponseInterface
{
abstract public function getData();
abstract public function getCode();
abstract public function getMessage();
abstract public function getParameter(string $key);
}

View File

@ -1,44 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Response;
use Omnipay\Rotessa\Message\Request\RequestInterface;
use Omnipay\Rotessa\Message\Response\ResponseInterface;
use Omnipay\Common\Message\AbstractResponse as Response;
class BaseResponse extends Response implements ResponseInterface
{
protected $code = 0;
protected $message = null;
function __construct(RequestInterface $request, array $data = [], int $code = 200, string $message = null ) {
parent::__construct($request, $data);
$this->code = $code;
$this->message = $message;
}
public function getData() {
return $this->getParameters();
}
public function getCode() {
return (int) $this->code;
}
public function isSuccessful() {
return $this->code < 300;
}
public function getMessage() {
return $this->message;
}
protected function getParameters() {
return $this->data;
}
public function getParameter(string $key) {
return $this->getParameters()[$key];
}
}

View File

@ -1,9 +0,0 @@
<?php
namespace Omnipay\Rotessa\Message\Response;
use Omnipay\Common\Message\ResponseInterface as MessageInterface;
interface ResponseInterface extends MessageInterface
{
public function getParameter(string $key) ;
}

View File

@ -1,63 +0,0 @@
<?php
namespace Omnipay\Rotessa\Model;
use Omnipay\Common\ParametersTrait;
use Omnipay\Rotessa\Model\ModelInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
use Omnipay\Rotessa\Exception\ValidationException;
abstract class AbstractModel implements ModelInterface {
use ParametersTrait;
abstract public function jsonSerialize() : array;
public function validate() : bool {
$required = array_diff_key( array_flip($this->required), array_filter($this->getParameters()) );
if(!empty($required)) throw new ValidationException("Could not validate " . implode(",", array_keys($required)) );
return true;
}
public function __get($key) {
return array_key_exists($key, $this->attributes) ? $this->getParameter($key) : null;
}
public function __set($key, $value) {
if(array_key_exists($key, $this->attributes)) $this->setParameter($key, $value);
}
public function __toString() : string {
return json_encode($this);
}
public function toString() : string {
return $this->__toString();
}
public function __toArray() : array {
return $this->getParameters();
}
public function toArray() : array {
return $this->__toArray();
}
public function initialize(array $params = []) {
$this->parameters = new ParameterBag;
$parameters = array_merge($this->defaults, $params);
if ($parameters) {
foreach ($this->attributes as $param => $type) {
$value = @$parameters[$param];
if($value){
settype($value, $type);
$this->setParameter($param, $value);
}
}
}
return $this;
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace Omnipay\Rotessa\Model;
use \DateTime;
use Omnipay\Rotessa\Model\AbstractModel;
use Omnipay\Rotessa\Model\ModelInterface;
class BaseModel extends AbstractModel implements ModelInterface {
protected $attributes = [
"id" => "string"
];
protected $required = ['id'];
protected $defaults = ['id' => 0 ];
public function __construct($parameters = array()) {
$this->initialize($parameters);
}
public function jsonSerialize() : array {
return array_intersect_key($this->toArray(), array_flip($this->required) );
}
}

View File

@ -1,94 +0,0 @@
<?php
namespace Omnipay\Rotessa\Model;
use Omnipay\Rotessa\Object\Country;
use Omnipay\Rotessa\Object\Address;
use Omnipay\Rotessa\Model\BaseModel;
use Omnipay\Rotessa\Object\CustomerType;
use Omnipay\Rotessa\Model\ModelInterface;
use Omnipay\Rotessa\Object\BankAccountType;
use Omnipay\Rotessa\Object\AuthorizationType;
use Omnipay\Rotessa\Exception\ValidationException;
class CustomerModel extends BaseModel implements ModelInterface {
protected $attributes = [
"id" => "string",
"custom_identifier" => "string",
"name" => "string",
"email" => "string",
"customer_type" => "string",
"home_phone" => "string",
"phone" => "string",
"bank_name" => "string",
"institution_number" => "string",
"transit_number" => "string",
"bank_account_type" => "string",
"authorization_type" => "string",
"routing_number" => "string",
"account_number" => "string",
"address" => "object",
"transaction_schedules" => "array",
"financial_transactions" => "array",
"active" => "bool"
];
protected $defaults = ["active" => false,"customer_type" =>'Business',"bank_account_type" =>'Savings',"authorization_type" =>'Online',];
protected $required = ["name","email","customer_type","home_phone","phone","bank_name","institution_number","transit_number","bank_account_type","authorization_type","routing_number","account_number","address",'custom_identifier'];
public function validate() : bool {
try {
$country = $this->address->country;
if(!self::isValidCountry($country)) throw new \Exception("Invalid country!");
$this->required = array_diff($this->required, Country::isAmerican($country) ? ["institution_number", "transit_number"] : ["bank_account_type", "routing_number"]);
parent::validate();
if(Country::isCanadian($country) ) {
if(!self::isValidTransitNumber($this->getParameter('transit_number'))) throw new \Exception("Invalid transit number!");
if(!self::isValidInstitutionNumber($this->getParameter('institution_number'))) throw new \Exception("Invalid institution number!");
}
if(!self::isValidCustomerType($this->getParameter('customer_type'))) throw new \Exception("Invalid customer type!");
if(!self::isValidBankAccountType($this->getParameter('bank_account_type'))) throw new \Exception("Invalid bank account type!");
if(!self::isValidAuthorizationType($this->getParameter('authorization_type'))) throw new \Exception("Invalid authorization type!");
} catch (\Throwable $th) {
throw new ValidationException($th->getMessage());
}
return true;
}
public static function isValidCountry(string $country ) : bool {
return Country::isValidCountryCode($country) || Country::isValidCountryName($country);
}
public static function isValidTransitNumber(string $value ) : bool {
return strlen($value) == 5;
}
public static function isValidInstitutionNumber(string $value ) : bool {
return strlen($value) == 3;
}
public static function isValidCustomerType(string $value ) : bool {
return CustomerType::isValid($value);
}
public static function isValidBankAccountType(string $value ) : bool {
return BankAccountType::isValid($value);
}
public static function isValidAuthorizationType(string $value ) : bool {
return AuthorizationType::isValid($value);
}
public function toArray() : array {
return [ 'address' => (array) $this->getParameter('address') ] + parent::toArray();
}
public function jsonSerialize() : array {
$address = (array) $this->getParameter('address');
unset($address['country']);
return compact('address') + parent::jsonSerialize();
}
}

View File

@ -1,16 +0,0 @@
<?php
namespace Omnipay\Rotessa\Model;
use Omnipay\Rotessa\Object\Country;
use Omnipay\Rotessa\Object\Address;
use Omnipay\Rotessa\Object\CustomerType;
use Omnipay\Rotessa\Model\ModelInterface;
use Omnipay\Rotessa\Object\BankAccountType;
use Omnipay\Rotessa\Object\AuthorizationType;
use Omnipay\Rotessa\Exception\ValidationException;
class CustomerPatchModel extends CustomerModel implements ModelInterface {
protected $required = ["id","custom_identifier","name","email","customer_type","home_phone","phone","bank_name","institution_number","transit_number","bank_account_type","authorization_type","routing_number","account_number","address"];
}

View File

@ -1,8 +0,0 @@
<?php
namespace Omnipay\Rotessa\Model;
interface ModelInterface extends \JsonSerializable
{
public function __toArray();
public function __toString();
}

View File

@ -1,84 +0,0 @@
<?php
namespace Omnipay\Rotessa\Model;
use \DateTime;
use Omnipay\Rotessa\Model\BaseModel;
use Omnipay\Rotessa\Object\Frequency;
use Omnipay\Rotessa\Model\ModelInterface;
use Omnipay\Rotessa\Exception\ValidationException;
class TransactionScheduleModel extends BaseModel implements ModelInterface {
protected $properties;
protected $attributes = [
"id" => "string",
"amount" => "float",
"comment" => "string",
"created_at" => "date",
"financial_transactions" => "array",
"frequency" => "string",
"installments" => "integer",
"next_process_date" => "date",
"process_date" => "date",
"updated_at" => "date",
"customer_id" => "string",
"custom_identifier" => "string",
];
public const DATE_FORMAT = 'F j, Y';
protected $defaults = ["amount" =>0.00,"comment" =>' ',"financial_transactions" =>0,"frequency" =>'Once',"installments" =>1];
protected $required = ["amount","comment","frequency","installments","process_date"];
public function validate() : bool {
try {
parent::validate();
if(!self::isValidDate($this->process_date)) throw new \Exception("Could not validate date ");
if(!self::isValidFrequency($this->frequency)) throw new \Exception("Invalid frequency");
if(is_null($this->customer_id) && is_null($this->custom_identifier)) throw new \Exception("customer id or custom identifier is invalid");
} catch (\Throwable $th) {
throw new ValidationException($th->getMessage());
}
return true;
}
public function jsonSerialize() : array {
return ['customer_id' => $this->getParameter('customer_id'), 'custom_identifier' => $this->getParameter('custom_identifier') ] + parent::jsonSerialize() ;
}
public function __toArray() : array {
return parent::__toArray() ;
}
public function initialize(array $params = [] ) {
$o_params = array_intersect_key(
$params = array_intersect_key($params, $this->attributes),
($attr = array_filter($this->attributes, fn($p) => $p != "date"))
);
parent::initialize($o_params);
$d_params = array_diff_key($params, $attr);
array_walk($d_params, function($v,$k) {
$this->setParameter($k, self::formatDate( $v) );
}, );
return $this;
}
public static function isValidDate($date) : bool {
$d = DateTime::createFromFormat(self::DATE_FORMAT, $date);
// Check if the date is valid and matches the format
return $d && $d->format(self::DATE_FORMAT) === $date;
}
public static function isValidFrequency($value) : bool {
return Frequency::isValid($value);
}
protected static function formatDate($date) : string {
$d = new DateTime($date);
return $d->format(self::DATE_FORMAT);
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace Omnipay\Rotessa\Model;
use Omnipay\Rotessa\Model\BaseModel;
use Omnipay\Rotessa\Model\ModelInterface;
class TransactionSchedulesIdBodyModel extends BaseModel implements ModelInterface {
protected $properties;
protected $attributes = [
"amount" => "int",
"comment" => "string",
];
public const DATE_FORMAT = 'Y-m-d H:i:s';
private $_is_error = false;
protected $defaults = ["amount" =>0,"comment" =>'0',];
protected $required = ["amount","comment",];
}

View File

@ -1,24 +0,0 @@
<?php
namespace Omnipay\Rotessa\Model;
use Omnipay\Rotessa\Model\BaseModel;
use Omnipay\Rotessa\Model\ModelInterface;
class TransactionSchedulesUpdateViaPostBodyModel extends BaseModel implements ModelInterface {
protected $properties;
protected $attributes = [
"id" => "int",
"amount" => "int",
"comment" => "string",
];
public const DATE_FORMAT = 'Y-m-d H:i:s';
private $_is_error = false;
protected $defaults = ["amount" =>0,"comment" =>'0',];
protected $required = ["amount","comment",];
}

View File

@ -1,53 +0,0 @@
<?php
namespace Omnipay\Rotessa\Object;
use Omnipay\Common\ParametersTrait;
final class Address implements \JsonSerializable {
use ParametersTrait;
protected $attributes = [
"address_1" => "string",
"address_2" => "string",
"city" => "string",
"id" => "int",
"postal_code" => "string",
"province_code" => "string",
"country" => "string"
];
protected $required = ["address_1","address_2","city","postal_code","province_code",];
public function jsonSerialize() {
return array_intersect_key($this->getParameters(), array_flip($this->required));
}
public function getCountry() : string {
return $this->getParameter('country');
}
public function initialize(array $parameters) {
foreach($this->attributes as $param => $type) {
$value = @$parameters[$param] ;
settype($value, $type);
$value = $value ?? null;
$this->parameters->set($param, $value);
}
}
public function __toArray() : array {
return $this->getParameters();
}
public function __toString() : string {
return $this->getFullAddress();
}
public function getFullAddress() :string {
$full_address = $this->getParameters();
extract($full_address);
return "$address_1 $address_2, $city, $postal_code $province_code, $country";
}
}

View File

@ -1,28 +0,0 @@
<?php
namespace Omnipay\Rotessa\Object;
use Omnipay\Rotessa\IsValidTypeTrait;
final class AuthorizationType {
use isValidTypeTrait;
const IN_PERSON = "In Person";
const ONLINE = "Online";
public static function isInPerson($value) {
return $value === self::IN_PERSON;
}
public static function isOnline($value) {
return $value === self::ONLINE;
}
public static function getTypes() : array {
return [
self::IN_PERSON,
self::ONLINE
];
}
}

View File

@ -1,28 +0,0 @@
<?php
namespace Omnipay\Rotessa\Object;
use Omnipay\Rotessa\IsValidTypeTrait;
final class BankAccountType {
use IsValidTypeTrait;
const SAVINGS = "Savings";
const CHECKING = "Checking";
public static function isSavings($value) {
return $value === self::SAVINGS;
}
public static function isChecking($value) {
return $value === self::Checking;
}
public static function getTypes() : array {
return [
self::SAVINGS,
self::CHECKING
];
}
}

View File

@ -1,33 +0,0 @@
<?php
namespace Omnipay\Rotessa\Object;
use Omnipay\Rotessa\IsValidTypeTrait;
final class Country {
use IsValidTypeTrait;
protected static $codes = ['CA','US'];
protected static $names = ['United States', 'Canada'];
public static function isValidCountryName(string $value) {
return in_array($value, self::$names);
}
public static function isValidCountryCode(string $value) {
return in_array($value, self::$codes);
}
public static function isAmerican(string $value) : bool {
return $value == 'US' || $value == 'United States';
}
public static function isCanadian(string $value) : bool {
return $value == 'CA' || $value == 'Canada';
}
public static function getTypes() : array {
return $codes + $names;
}
}

View File

@ -1,28 +0,0 @@
<?php
namespace Omnipay\Rotessa\Object;
use Omnipay\Rotessa\IsValidTypeTrait;
final class CustomerType {
use IsValidTypeTrait;
const PERSONAL = "Personal";
const BUSINESS = "Business";
public static function isPersonal($value) {
return $value === self::PERSONAL;
}
public static function isBusiness($value) {
return $value === self::BUSINESS;
}
public static function getTypes() : array {
return [
self::PERSONAL,
self::BUSINESS
];
}
}

View File

@ -1,64 +0,0 @@
<?php
namespace Omnipay\Rotessa\Object;
use Omnipay\Rotessa\IsValidTypeTrait;
final class Frequency {
use IsValidTypeTrait;
const ONCE = "Once";
const WEEKLY = "Weekly";
const OTHER_WEEK = "Every Other Week";
const MONTHLY= "Monthly";
const OTHER_MONTH = "Every Other Month";
const QUARTERLY = "Quarterly";
const SEMI_ANNUALLY = "Semi-Annually";
const YEARLY = "Yearly";
public static function isOnce($value) {
return $value === self::ONCE;
}
public static function isWeekly($value) {
return $value === self::WEEKLY;
}
public static function isOtherWeek($value) {
return $value === self::OTHER_WEEK;
}
public static function isMonthly($value) {
return $value === self::MONTHLY;
}
public static function isOtherMonth($value) {
return $value === self::OTHER_MONTH;
}
public static function isQuarterly($value) {
return $value === self::QUARTERLY;
}
public static function isSemiAnnually($value) {
return $value === self::SEMI_ANNUALLY;
}
public static function isYearly($value) {
return $value === self::YEARLY;
}
public static function getTypes() : array {
return [
self::ONCE,
self::WEEKLY,
self::OTHER_WEEK,
self::MONTHLY,
self::OTHER_MONTH,
self::QUARTERLY,
self::SEMI_ANNUALLY,
self::YEARLY
];
}
}

View File

@ -12,22 +12,15 @@
namespace App\PaymentDrivers;
use App\DataMapper\ClientSettings;
use Omnipay\Omnipay;
use App\Models\Client;
use App\Models\Payment;
use App\Models\SystemLog;
use App\Models\PaymentHash;
use Illuminate\Support\Arr;
use App\Models\GatewayType;
use Omnipay\Rotessa\Gateway;
use App\Models\ClientContact;
use App\Utils\Traits\MakesHash;
use App\Jobs\Util\SystemLogger;
use App\PaymentDrivers\BaseDriver;
use App\Models\ClientGatewayToken;
use Illuminate\Support\Facades\Cache;
use Illuminate\Database\Eloquent\Builder;
use App\PaymentDrivers\Rotessa\Resources\Customer;
use App\PaymentDrivers\Rotessa\PaymentMethod as Acss;
use App\PaymentDrivers\Rotessa\PaymentMethod as BankTransfer;
use Illuminate\Support\Facades\Http;
@ -42,15 +35,11 @@ class RotessaPaymentDriver extends BaseDriver
public $can_authorise_credit_card = true;
public Gateway $gateway;
public $payment_method;
public static $methods = [
GatewayType::BANK_TRANSFER => BankTransfer::class,
//GatewayType::BACS => Bacs::class,
GatewayType::ACSS => Acss::class,
// GatewayType::DIRECT_DEBIT => DirectDebit::class
];
public function init(): self
@ -115,14 +104,15 @@ class RotessaPaymentDriver extends BaseDriver
public function importCustomers() {
try {
$result = $this->gatewayRequest('get','customers',[]);
$result = $this->gatewayRequest('get','customers',[]); //Rotessa customers
if($result->failed())
$result->throw();
$customers = collect($result->json())->unique('email');
$customers = collect($result->json())->unique('email'); //Rotessa customer emails
$client_emails = $customers->pluck('email')->all();
$company_id = $this->company_gateway->company->id;
// get existing customers
$client_contacts = ClientContact::where('company_id', $company_id)
@ -138,60 +128,32 @@ class RotessaPaymentDriver extends BaseDriver
} );
// create payment methods
$client_contacts->each(
collect($client_contacts)->each(
function($contact) {
// $result = $this->gateway->getCustomersId(['id' => ($contact = (object) $contact)->id])->send();
$contact = (object)$contact;
$result = $this->gatewayRequest("get","customers/{$contact->id}");
$result = $result->json();
$this->client = Client::find($contact->client_id);
$this->client = Client::query()->find($contact->client_id);
$customer = (new Customer($result))->additional(['id' => $contact->id, 'custom_identifier' => $contact->custom_identifier ] );
$this->findOrCreateCustomer($customer->additional + $customer->jsonSerialize());
$customer = array_merge($result, ['id' => $contact->id, 'custom_identifier' => $contact->custom_identifier ]);
$this->findOrCreateCustomer($customer);
}
);
// create new clients from rotessa customers
$client_emails = $client_contacts->pluck('email')->all();
$client_contacts = $customers->filter(function ($value, $key) use ($client_emails) {
return !in_array(((object) $value)->email, $client_emails);
})->each( function($customer) use ($company_id) {
$customer = $this->gatewayRequest("get", "customers/{$customer['id']}")->json();
/**
{
"account_number": "11111111"
"active": true,
"address": {
"address_1": "123 Main Street",
"address_2": "Unit 4",
"city": "Birmingham",
"id": 114397,
"postal_code": "36016",
"province_code": "AL"
},
"authorization_type": "Online",
"bank_account_type": "Checking",
"bank_name": "Scotiabank",
"created_at": "2015-02-10T23:50:45.000-06:00",
"custom_identifier": "Mikey",
"customer_type": "Personal",
"email": "mikesmith@test.com",
"financial_transactions": [],
"home_phone": "(204) 555 5555",
"id": 1,
"identifier": "Mikey",
"institution_number": "",
"name": "Mike Smith",
"phone": "(204) 555 4444",
"routing_number": "111111111",
"transaction_schedules": [],
"transit_number": "",
"updated_at": "2015-02-10T23:50:45.000-06:00"
}
*/
$settings = ClientSettings::defaults();
$settings->currency_id = $this->company_gateway->company->getSetting('currency_id');
$customer = (object)$customer;
@ -220,8 +182,7 @@ class RotessaPaymentDriver extends BaseDriver
$client->contacts()->saveMany([$contact]);
$contact = $client->contacts()->first();
$this->client = $client;
$customer = (new Customer((array) $customer))->additional(['id' => $customer->id, 'custom_identifier' => $customer->custom_identifier ?? $contact->id ] );
$this->findOrCreateCustomer($customer->additional + $customer->jsonSerialize());
});
} catch (\Throwable $th) {
$data = [
@ -241,6 +202,8 @@ class RotessaPaymentDriver extends BaseDriver
public function findOrCreateCustomer(array $data)
{
nlog($data);
$result = null;
try {
@ -248,37 +211,34 @@ class RotessaPaymentDriver extends BaseDriver
->where('company_gateway_id', $this->company_gateway->id)
->where('client_id', $this->client->id)
->where('is_deleted',0)
->orWhere(function (Builder $query) use ($data) {
$query->where('token', join(".", Arr::only($data, ['id','custom_identifier'])))
->where('gateway_customer_reference', Arr::only($data,'id'));
})
->where('gateway_customer_reference', Arr::only($data,'id'))
->exists();
if ($existing)
return true;
else if(!Arr::has($data,'id')) {
// $result = $this->gateway->authorize($data)->send();
// if (!$result->isSuccessful()) throw new \Exception($result->getMessage(), (int) $result->getCode());
if(!isset($data['id'])) {
nlog("no id, lets goo");
$result = $this->gatewayRequest('post', 'customers', $data);
if($result->failed())
$result->throw();
$customer = new Customer($result->json());
$data = array_filter($customer->resolve());
$data = $result->json();
nlog($data);
}
// $payment_method_id = Arr::has($data,'address.postal_code') && ((int) $data['address']['postal_code'])? GatewayType::BANK_TRANSFER: GatewayType::ACSS;
// TODO: Check/ Validate postal code between USA vs CAN
$payment_method_id = GatewayType::ACSS;
$gateway_token = $this->storeGatewayToken( [
'payment_meta' => $data + ['brand' => 'Bank Transfer', 'last4' => substr($data['account_number'], -4), 'type' => GatewayType::ACSS ],
'payment_meta' => ['brand' => 'Bank Transfer', 'last4' => substr($data['account_number'], -4), 'type' => GatewayType::ACSS ],
'token' => join(".", Arr::only($data, ['id','custom_identifier'])),
'payment_method_id' => $payment_method_id ,
], ['gateway_customer_reference' =>
$data['id']
, 'routing_number' => Arr::has($data,'routing_number') ? $data['routing_number'] : $data['transit_number'] ]);
], [
'gateway_customer_reference' => $data['id'],
'routing_number' => Arr::has($data,'routing_number') ? $data['routing_number'] : $data['transit_number']
]);
return $data['id'];

View File

@ -59,6 +59,11 @@ class ChargeRefunded implements ShouldQueue
$payment_hash_key = $source['metadata']['payment_hash'] ?? null;
if(is_null($payment_hash_key)){
nlog("charge.refunded not found");
return;
}
$payment_hash = PaymentHash::query()->where('hash', $payment_hash_key)->first();
$company_gateway = $payment_hash->payment->company_gateway;

View File

@ -59,38 +59,22 @@ class PaymentIntentProcessingWebhook implements ShouldQueue
/* Stub processing payment intents with a pending payment */
public function handle()
{
nlog($this->stripe_request);
// The first payment will always be a PI payment - subsequent are PY
MultiDB::findAndSetDbByCompanyKey($this->company_key);
$company = Company::query()->where('company_key', $this->company_key)->first();
foreach ($this->stripe_request as $transaction) {
$payment = Payment::query()
->where('company_id', $company->id)
->where(function ($query) use ($transaction) {
if(isset($transaction['payment_intent'])) {
$query->where('transaction_reference', $transaction['payment_intent']);
}
if(isset($transaction['payment_intent']) && isset($transaction['id'])) {
$query->orWhere('transaction_reference', $transaction['id']);
}
if(!isset($transaction['payment_intent']) && isset($transaction['id'])) {
$query->where('transaction_reference', $transaction['id']);
}
})
->where('transaction_reference', $transaction['id'])
->first();
if ($payment) {
$payment->status_id = Payment::STATUS_PENDING;
$payment->save();
nlog("found payment");
$this->payment_completed = true;
}

View File

@ -33,7 +33,6 @@ class ComposerServiceProvider extends ServiceProvider
$view->with('states', $states);
});
// CAProvinces View Composer
view()->composer(['*.rotessa.components.address','*.rotessa.components.banks.CA.bank','*.rotessa.components.dropdowns.country.CA'], function ($view) {
$provinces = CAProvinces::get();
$view->with('provinces', $provinces);

View File

@ -11,9 +11,12 @@
namespace App\Services\Chart;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Quote;
use App\Models\Task;
use Illuminate\Contracts\Database\Eloquent\Builder;
/**
* Class ChartCalculations.
@ -170,4 +173,215 @@ trait ChartCalculations
return $result;
}
public function getLoggedTasks($data): int|float
{
$q = $this->taskQuery($data);
return $this->taskCalculations($q, $data);
}
public function getPaidTasks($data): int|float
{
$q = $this->taskQuery($data);
$q->whereHas('invoice', function ($query){
$query->where('status_id', 4)->where('is_deleted', 0);
});
return $this->taskCalculations($q, $data);
}
public function getInvoicedTasks($data): int|float
{
$q = $this->taskQuery($data);
$q->whereHas('invoice');
return $this->taskCalculations($q, $data);
}
/**
* All Expenses
*/
public function getLoggedExpenses($data): int|float
{
$q = $this->expenseQuery($data);
return $this->expenseCalculations($q, $data);
}
/**
* Expenses that should be invoiced - but are not yet invoiced.
*/
public function getPendingExpenses($data): int|float
{
$q = $this->expenseQuery($data);
$q->where('should_be_invoiced', true)->whereNull('invoice_id');
return $this->expenseCalculations($q, $data);
}
/**
* Invoiced.
*/
public function getInvoicedExpenses($data): int|float
{
$q = $this->expenseQuery($data);
$q->whereNotNull('invoice_id');
return $this->expenseCalculations($q, $data);
}
/**
* Paid.
*/
public function getPaidExpenses($data): int|float
{
$q = $this->expenseQuery($data);
$q->whereNotNull('payment_date');
return $this->expenseCalculations($q, $data);
}
/**
* Paid.
*/
public function getInvoicedPaidExpenses($data): int|float
{
$q = $this->expenseQuery($data);
$q->whereNotNull('invoice_id')->whereNotNull('payment_date');
return $this->expenseCalculations($q, $data);
}
private function expenseCalculations(Builder $query, array $data): int|float
{
$result = 0;
$calculated = $this->expenseCalculator($query, $data);
match ($data['calculation']) {
'sum' => $result = $calculated->sum(),
'avg' => $result = $calculated->avg(),
'count' => $result = $query->count(),
default => $result = 0,
};
return $result;
}
private function expenseCalculator(Builder $query, array $data)
{
return $query->get()
->when($data['currency_id'] == '999', function ($collection) {
$collection->map(function ($e) {
/** @var \App\Models\Expense $e */
return $e->amount * $e->exchange_rate;
});
})
->when($data['currency_id'] != '999', function ($collection) {
$collection->map(function ($e) {
/** @var \App\Models\Expense $e */
return $e->amount;
});
});
}
private function expenseQuery($data): Builder
{
$query = Expense::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0);
if(in_array($data['period'], ['current,previous'])) {
$query->whereBetween('date', [$data['start_date'], $data['end_date']]);
}
return $query;
}
////////////////////////////////////////////////////////////////
private function taskMoneyCalculator($query, $data)
{
return $query->get()
->when($data['currency_id'] == '999', function ($collection) {
$collection->map(function ($t) {
return $t->taskCompanyValue();
});
})
->when($data['currency_id'] != '999', function ($collection) {
$collection->map(function ($t) {
return $t->taskValue();
});
});
}
private function taskQuery($data): Builder
{
$q = Task::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0);
if(in_array($data['period'], ['current,previous'])) {
$q->whereBetween('calculated_start_date', [$data['start_date'], $data['end_date']]);
}
return $q;
}
private function taskCalculations(Builder $q, array $data): int|float
{
$result = 0;
$calculated = collect();
if($data['calculation'] != 'count' && $data['format'] == 'money') {
if($data['currency_id'] != '999') {
$q->whereHas('client', function ($query) use ($data) {
$query->where('settings->currency_id', $data['currency_id']);
});
}
$calculated = $this->taskMoneyCalculator($q, $data);
}
if($data['calculation'] != 'count' && $data['format'] == 'time') {
$calculated = $q->get()->map(function ($t) {
return $t->calcDuration();
});
}
match ($data['calculation']) {
'sum' => $result = $calculated->sum(),
'avg' => $result = $calculated->avg(),
'count' => $result = $q->count(),
default => $result = 0,
};
return $result;
}
}

View File

@ -224,6 +224,8 @@ class ChartService
* period - current/previous
* calculation - sum/count/avg
*
* May require currency_id
*
* date_range - this_month
* or
* start_date - end_date
@ -234,18 +236,18 @@ class ChartService
match($data['field']){
'active_invoices' => $results = $this->getActiveInvoices($data),
'outstanding_invoices' => $results = 0,
'completed_payments' => $results = 0,
'refunded_payments' => $results = 0,
'active_quotes' => $results = 0,
'unapproved_quotes' => $results = 0,
'logged_tasks' => $results = 0,
'invoiced_tasks' => $results = 0,
'paid_tasks' => $results = 0,
'logged_expenses' => $results = 0,
'pending_expenses' => $results = 0,
'invoiced_expenses' => $results = 0,
'invoice_paid_expenses' => $results = 0,
'outstanding_invoices' => $results = $this->getOutstandingInvoices($data),
'completed_payments' => $results = $this->getCompletedPayments($data),
'refunded_payments' => $results = $this->getRefundedPayments($data),
'active_quotes' => $results = $this->getActiveQuotes($data),
'unapproved_quotes' => $results = $this->getUnapprovedQuotes($data),
'logged_tasks' => $results = $this->getLoggedTasks($data),
'invoiced_tasks' => $results = $this->getInvoicedTasks($data),
'paid_tasks' => $results = $this->getPaidTasks($data),
'logged_expenses' => $results = $this->getLoggedExpenses($data),
'pending_expenses' => $results = $this->getPendingExpenses($data),
'invoiced_expenses' => $results = $this->getInvoicedExpenses($data),
'invoice_paid_expenses' => $results = $this->getInvoicedPaidExpenses($data),
default => $results = 0,
};

View File

@ -124,7 +124,7 @@ class TemplateService
$this->twig->addFilter($filter);
$allowedTags = ['if', 'for', 'set', 'filter'];
$allowedFilters = ['escape', 'e', 'upper', 'lower', 'capitalize', 'filter', 'length', 'merge','format_currency', 'format_number','format_percent_number','map', 'join', 'first', 'date', 'sum', 'number_format'];
$allowedFilters = ['replace', 'escape', 'e', 'upper', 'lower', 'capitalize', 'filter', 'length', 'merge','format_currency', 'format_number','format_percent_number','map', 'join', 'first', 'date', 'sum', 'number_format','nl2br'];
$allowedFunctions = ['range', 'cycle', 'constant', 'date',];
$allowedProperties = ['type_id'];
$allowedMethods = ['img','t'];
@ -323,6 +323,9 @@ class TemplateService
$template = $template->render($this->data);
$f = $this->document->createDocumentFragment();
$template = htmlspecialchars($template, ENT_XML1, 'UTF-8');
$f->appendXML(html_entity_decode($template));
$replacements[] = $f;

View File

@ -17,80 +17,67 @@ use Illuminate\Support\Str;
class TranslationHelper
{
public static function getIndustries()
{
// public static function getIndustries()
// {
/** @var \Illuminate\Support\Collection<\App\Models\Currency> */
$industries = app('industries');
// /** @var \Illuminate\Support\Collection<\App\Models\Currency> */
// $industries = app('industries');
return $industries->each(function ($industry) {
$industry->name = ctrans('texts.industry_'.$industry->name);
})->sortBy(function ($industry) {
return $industry->name;
});
}
// return $industries->each(function ($industry) {
// $industry->name = ctrans('texts.industry_'.$industry->name);
// })->sortBy(function ($industry) {
// return $industry->name;
// });
// }
public static function getCountries()
{
/** @var \Illuminate\Support\Collection<\App\Models\Country> */
$countries = app('countries');
return app('countries');
return $countries;
return \App\Models\Country::all()->each(function ($country) {
$country->name = ctrans('texts.country_'.$country->name);
})->sortBy(function ($country) {
return $country->iso_3166_2;
});
}
public static function getPaymentTypes()
{
// public static function getPaymentTypes()
// {
/** @var \Illuminate\Support\Collection<\App\Models\PaymentType> */
// $payment_types = app('payment_types');
// /** @var \Illuminate\Support\Collection<\App\Models\PaymentType> */
// // $payment_types = app('payment_types');
return \App\Models\PaymentType::all()->each(function ($pType) {
$pType->name = ctrans('texts.payment_type_'.$pType->name);
})->sortBy(function ($pType) {
return $pType->name;
});
}
// return \App\Models\PaymentType::all()->each(function ($pType) {
// $pType->name = ctrans('texts.payment_type_'.$pType->name);
// })->sortBy(function ($pType) {
// return $pType->name;
// });
// }
public static function getLanguages()
{
// public static function getLanguages()
// {
/** @var \Illuminate\Support\Collection<\App\Models\Language> */
// $languages = app('languages');
// /** @var \Illuminate\Support\Collection<\App\Models\Language> */
// // $languages = app('languages');
return \App\Models\Language::all()->each(function ($lang) {
$lang->name = ctrans('texts.lang_'.$lang->name);
})->sortBy(function ($lang) {
return $lang->name;
});
}
// return \App\Models\Language::all()->each(function ($lang) {
// $lang->name = ctrans('texts.lang_'.$lang->name);
// })->sortBy(function ($lang) {
// return $lang->name;
// });
// }
public static function getCurrencies()
{
/** @var \Illuminate\Support\Collection<\App\Models\Currency> */
$currencies = app('currencies');
return $currencies;
return app('currencies');
return \App\Models\Currency::all()->each(function ($currency) {
$currency->name = ctrans('texts.currency_'.Str::slug($currency->name, '_'));
})->sortBy(function ($currency) {
return $currency->name;
});
}
public static function getPaymentTerms()
{
return PaymentTerm::getCompanyTerms()->map(function ($term) {
$term['name'] = ctrans('texts.payment_terms_net').' '.$term['num_days'];
// public static function getPaymentTerms()
// {
// return PaymentTerm::getCompanyTerms()->map(function ($term) {
// $term['name'] = ctrans('texts.payment_terms_net').' '.$term['num_days'];
return $term;
});
}
// return $term;
// });
// }
}

View File

@ -73,14 +73,12 @@
"league/csv": "^9.6",
"league/flysystem-aws-s3-v3": "^3.0",
"league/fractal": "^0.20.0",
"league/omnipay": "^3.1",
"livewire/livewire": "^3",
"microsoft/microsoft-graph": "^1.69",
"mollie/mollie-api-php": "^2.36",
"nelexa/zip": "^4.0",
"nordigen/nordigen-php": "^1.1",
"nwidart/laravel-modules": "^11.0",
"omnipay/paypal": "^3.0",
"phpoffice/phpspreadsheet": "^1.29",
"pragmarx/google2fa": "^8.0",
"predis/predis": "^2",
@ -132,7 +130,6 @@
"app/Helpers/ClientPortal.php"
],
"classmap": [
"app/PaymentDrivers/Rotessa/vendor/karneaud/omnipay-rotessa/src/Omnipay/Rotessa/"
]
},
"autoload-dev": {

755
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "8fdb8245fbc563f8c09da161876f52a7",
"content-hash": "6eda3a2962158b87dab46711e65a8438",
"packages": [
{
"name": "adrienrn/php-mimetyper",
@ -1038,72 +1038,6 @@
},
"time": "2024-08-02T08:07:53+00:00"
},
{
"name": "clue/stream-filter",
"version": "v1.7.0",
"source": {
"type": "git",
"url": "https://github.com/clue/stream-filter.git",
"reference": "049509fef80032cb3f051595029ab75b49a3c2f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/clue/stream-filter/zipball/049509fef80032cb3f051595029ab75b49a3c2f7",
"reference": "049509fef80032cb3f051595029ab75b49a3c2f7",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
},
"type": "library",
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"Clue\\StreamFilter\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Christian Lück",
"email": "christian@clue.engineering"
}
],
"description": "A simple and modern approach to stream filtering in PHP",
"homepage": "https://github.com/clue/stream-filter",
"keywords": [
"bucket brigade",
"callback",
"filter",
"php_user_filter",
"stream",
"stream_filter_append",
"stream_filter_register"
],
"support": {
"issues": "https://github.com/clue/stream-filter/issues",
"source": "https://github.com/clue/stream-filter/tree/v1.7.0"
},
"funding": [
{
"url": "https://clue.engineering/support",
"type": "custom"
},
{
"url": "https://github.com/clue",
"type": "github"
}
],
"time": "2023-12-20T15:40:13+00:00"
},
{
"name": "composer/ca-bundle",
"version": "1.5.1",
@ -2585,7 +2519,7 @@
},
{
"name": "google/apiclient-services",
"version": "v0.366.0",
"version": "v0.367.0",
"source": {
"type": "git",
"url": "https://github.com/googleapis/google-api-php-client-services.git",
@ -2623,7 +2557,7 @@
],
"support": {
"issues": "https://github.com/googleapis/google-api-php-client-services/issues",
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.366.0"
"source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.367.0"
},
"time": "2024-07-11T01:08:44+00:00"
},
@ -6008,69 +5942,6 @@
},
"time": "2022-04-15T14:02:14+00:00"
},
{
"name": "league/omnipay",
"version": "v3.2.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/omnipay.git",
"reference": "38f66a0cc043ed51d6edf7956d6439a2f263501f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay/zipball/38f66a0cc043ed51d6edf7956d6439a2f263501f",
"reference": "38f66a0cc043ed51d6edf7956d6439a2f263501f",
"shasum": ""
},
"require": {
"omnipay/common": "^3.1",
"php": "^7.2|^8.0",
"php-http/discovery": "^1.14",
"php-http/guzzle7-adapter": "^1"
},
"require-dev": {
"omnipay/tests": "^3|^4"
},
"type": "metapackage",
"extra": {
"branch-alias": {
"dev-master": "3.2.x-dev"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Adrian Macneil",
"email": "adrian@adrianmacneil.com"
},
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "Omnipay payment processing library",
"homepage": "https://omnipay.thephpleague.com/",
"keywords": [
"checkout",
"creditcard",
"omnipay",
"payment"
],
"support": {
"issues": "https://github.com/thephpleague/omnipay/issues",
"source": "https://github.com/thephpleague/omnipay/tree/v3.2.1"
},
"funding": [
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2021-06-05T11:34:12+00:00"
},
{
"name": "livewire/livewire",
"version": "v3.5.4",
@ -6546,94 +6417,6 @@
},
"time": "2024-07-17T08:02:14+00:00"
},
{
"name": "moneyphp/money",
"version": "v4.5.0",
"source": {
"type": "git",
"url": "https://github.com/moneyphp/money.git",
"reference": "a1daa7daf159b4044e3d0c34c41fe2be5860e850"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/moneyphp/money/zipball/a1daa7daf159b4044e3d0c34c41fe2be5860e850",
"reference": "a1daa7daf159b4044e3d0c34c41fe2be5860e850",
"shasum": ""
},
"require": {
"ext-bcmath": "*",
"ext-filter": "*",
"ext-json": "*",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0"
},
"require-dev": {
"cache/taggable-cache": "^1.1.0",
"doctrine/coding-standard": "^12.0",
"doctrine/instantiator": "^1.5.0 || ^2.0",
"ext-gmp": "*",
"ext-intl": "*",
"florianv/exchanger": "^2.8.1",
"florianv/swap": "^4.3.0",
"moneyphp/crypto-currencies": "^1.1.0",
"moneyphp/iso-currencies": "^3.4",
"php-http/message": "^1.16.0",
"php-http/mock-client": "^1.6.0",
"phpbench/phpbench": "^1.2.5",
"phpunit/phpunit": "^10.5.9",
"psalm/plugin-phpunit": "^0.18.4",
"psr/cache": "^1.0.1 || ^2.0 || ^3.0",
"vimeo/psalm": "~5.20.0"
},
"suggest": {
"ext-gmp": "Calculate without integer limits",
"ext-intl": "Format Money objects with intl",
"florianv/exchanger": "Exchange rates library for PHP",
"florianv/swap": "Exchange rates library for PHP",
"psr/cache-implementation": "Used for Currency caching"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Money\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mathias Verraes",
"email": "mathias@verraes.net",
"homepage": "http://verraes.net"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
},
{
"name": "Frederik Bosch",
"email": "f.bosch@genkgo.nl"
}
],
"description": "PHP implementation of Fowler's Money pattern",
"homepage": "http://moneyphp.org",
"keywords": [
"Value Object",
"money",
"vo"
],
"support": {
"issues": "https://github.com/moneyphp/money/issues",
"source": "https://github.com/moneyphp/money/tree/v4.5.0"
},
"time": "2024-02-15T19:47:21+00:00"
},
{
"name": "monolog/monolog",
"version": "3.7.0",
@ -7727,164 +7510,6 @@
],
"time": "2023-11-13T09:31:12+00:00"
},
{
"name": "omnipay/common",
"version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/omnipay-common.git",
"reference": "2eca3823e9069e2c36b6007a090577d5584f9518"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-common/zipball/2eca3823e9069e2c36b6007a090577d5584f9518",
"reference": "2eca3823e9069e2c36b6007a090577d5584f9518",
"shasum": ""
},
"require": {
"moneyphp/money": "^3.1|^4.0.3",
"php": "^7.2|^8",
"php-http/client-implementation": "^1",
"php-http/discovery": "^1.14",
"php-http/message": "^1.5",
"php-http/message-factory": "^1.1",
"symfony/http-foundation": "^2.1|^3|^4|^5|^6|^7"
},
"require-dev": {
"http-interop/http-factory-guzzle": "^1.1",
"omnipay/tests": "^4.1",
"php-http/guzzle7-adapter": "^1",
"php-http/mock-client": "^1.6",
"squizlabs/php_codesniffer": "^3.8.1"
},
"suggest": {
"league/omnipay": "The default Omnipay package provides a default HTTP Adapter."
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.1.x-dev"
}
},
"autoload": {
"psr-4": {
"Omnipay\\Common\\": "src/Common"
},
"classmap": [
"src/Omnipay.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Adrian Macneil",
"email": "adrian@adrianmacneil.com"
},
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
},
{
"name": "Jason Judge",
"email": "jason.judge@consil.co.uk"
},
{
"name": "Del"
},
{
"name": "Omnipay Contributors",
"homepage": "https://github.com/thephpleague/omnipay-common/contributors"
}
],
"description": "Common components for Omnipay payment processing library",
"homepage": "https://github.com/thephpleague/omnipay-common",
"keywords": [
"gateway",
"merchant",
"omnipay",
"pay",
"payment",
"purchase"
],
"support": {
"issues": "https://github.com/thephpleague/omnipay-common/issues",
"source": "https://github.com/thephpleague/omnipay-common/tree/v3.3.0"
},
"funding": [
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2024-03-08T11:56:40+00:00"
},
{
"name": "omnipay/paypal",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/omnipay-paypal.git",
"reference": "519db61b32ff0c1e56cbec94762b970ee9674f65"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/omnipay-paypal/zipball/519db61b32ff0c1e56cbec94762b970ee9674f65",
"reference": "519db61b32ff0c1e56cbec94762b970ee9674f65",
"shasum": ""
},
"require": {
"omnipay/common": "^3"
},
"require-dev": {
"omnipay/tests": "^3",
"phpro/grumphp": "^0.14",
"squizlabs/php_codesniffer": "^3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Omnipay\\PayPal\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Adrian Macneil",
"email": "adrian@adrianmacneil.com"
},
{
"name": "Omnipay Contributors",
"homepage": "https://github.com/thephpleague/omnipay-paypal/contributors"
}
],
"description": "PayPal gateway for Omnipay payment processing library",
"homepage": "https://github.com/thephpleague/omnipay-paypal",
"keywords": [
"gateway",
"merchant",
"omnipay",
"pay",
"payment",
"paypal",
"purchase"
],
"support": {
"issues": "https://github.com/thephpleague/omnipay-paypal/issues",
"source": "https://github.com/thephpleague/omnipay-paypal/tree/v3.0.2"
},
"time": "2018-05-15T10:35:58+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v2.7.0",
@ -8178,380 +7803,6 @@
},
"time": "2024-04-08T12:52:34+00:00"
},
{
"name": "php-http/discovery",
"version": "1.19.4",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "0700efda8d7526335132360167315fdab3aeb599"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/0700efda8d7526335132360167315fdab3aeb599",
"reference": "0700efda8d7526335132360167315fdab3aeb599",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0|^2.0",
"php": "^7.1 || ^8.0"
},
"conflict": {
"nyholm/psr7": "<1.0",
"zendframework/zend-diactoros": "*"
},
"provide": {
"php-http/async-client-implementation": "*",
"php-http/client-implementation": "*",
"psr/http-client-implementation": "*",
"psr/http-factory-implementation": "*",
"psr/http-message-implementation": "*"
},
"require-dev": {
"composer/composer": "^1.0.2|^2.0",
"graham-campbell/phpspec-skip-example-extension": "^5.0",
"php-http/httplug": "^1.0 || ^2.0",
"php-http/message-factory": "^1.0",
"phpspec/phpspec": "^5.1 || ^6.1 || ^7.3",
"sebastian/comparator": "^3.0.5 || ^4.0.8",
"symfony/phpunit-bridge": "^6.4.4 || ^7.0.1"
},
"type": "composer-plugin",
"extra": {
"class": "Http\\Discovery\\Composer\\Plugin",
"plugin-optional": true
},
"autoload": {
"psr-4": {
"Http\\Discovery\\": "src/"
},
"exclude-from-classmap": [
"src/Composer/Plugin.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations",
"homepage": "http://php-http.org",
"keywords": [
"adapter",
"client",
"discovery",
"factory",
"http",
"message",
"psr17",
"psr7"
],
"support": {
"issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.19.4"
},
"time": "2024-03-29T13:00:05+00:00"
},
{
"name": "php-http/guzzle7-adapter",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/guzzle7-adapter.git",
"reference": "fb075a71dbfa4847cf0c2938c4e5a9c478ef8b01"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/guzzle7-adapter/zipball/fb075a71dbfa4847cf0c2938c4e5a9c478ef8b01",
"reference": "fb075a71dbfa4847cf0c2938c4e5a9c478ef8b01",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^7.0",
"php": "^7.2 | ^8.0",
"php-http/httplug": "^2.0",
"psr/http-client": "^1.0"
},
"provide": {
"php-http/async-client-implementation": "1.0",
"php-http/client-implementation": "1.0",
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"php-http/client-integration-tests": "^3.0",
"phpunit/phpunit": "^8.0|^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.2.x-dev"
}
},
"autoload": {
"psr-4": {
"Http\\Adapter\\Guzzle7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com"
}
],
"description": "Guzzle 7 HTTP Adapter",
"homepage": "http://httplug.io",
"keywords": [
"Guzzle",
"http"
],
"support": {
"issues": "https://github.com/php-http/guzzle7-adapter/issues",
"source": "https://github.com/php-http/guzzle7-adapter/tree/1.0.0"
},
"time": "2021-03-09T07:35:15+00:00"
},
{
"name": "php-http/httplug",
"version": "2.4.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/httplug.git",
"reference": "625ad742c360c8ac580fcc647a1541d29e257f67"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67",
"reference": "625ad742c360c8ac580fcc647a1541d29e257f67",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
"php-http/promise": "^1.1",
"psr/http-client": "^1.0",
"psr/http-message": "^1.0 || ^2.0"
},
"require-dev": {
"friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0",
"phpspec/phpspec": "^5.1 || ^6.0 || ^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Eric GELOEN",
"email": "geloen.eric@gmail.com"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://sagikazarmark.hu"
}
],
"description": "HTTPlug, the HTTP client abstraction for PHP",
"homepage": "http://httplug.io",
"keywords": [
"client",
"http"
],
"support": {
"issues": "https://github.com/php-http/httplug/issues",
"source": "https://github.com/php-http/httplug/tree/2.4.0"
},
"time": "2023-04-14T15:10:03+00:00"
},
{
"name": "php-http/message",
"version": "1.16.1",
"source": {
"type": "git",
"url": "https://github.com/php-http/message.git",
"reference": "5997f3289332c699fa2545c427826272498a2088"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/message/zipball/5997f3289332c699fa2545c427826272498a2088",
"reference": "5997f3289332c699fa2545c427826272498a2088",
"shasum": ""
},
"require": {
"clue/stream-filter": "^1.5",
"php": "^7.2 || ^8.0",
"psr/http-message": "^1.1 || ^2.0"
},
"provide": {
"php-http/message-factory-implementation": "1.0"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.6",
"ext-zlib": "*",
"guzzlehttp/psr7": "^1.0 || ^2.0",
"laminas/laminas-diactoros": "^2.0 || ^3.0",
"php-http/message-factory": "^1.0.2",
"phpspec/phpspec": "^5.1 || ^6.3 || ^7.1",
"slim/slim": "^3.0"
},
"suggest": {
"ext-zlib": "Used with compressor/decompressor streams",
"guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories",
"laminas/laminas-diactoros": "Used with Diactoros Factories",
"slim/slim": "Used with Slim Framework PSR-7 implementation"
},
"type": "library",
"autoload": {
"files": [
"src/filters.php"
],
"psr-4": {
"Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "HTTP Message related tools",
"homepage": "http://php-http.org",
"keywords": [
"http",
"message",
"psr-7"
],
"support": {
"issues": "https://github.com/php-http/message/issues",
"source": "https://github.com/php-http/message/tree/1.16.1"
},
"time": "2024-03-07T13:22:09+00:00"
},
{
"name": "php-http/message-factory",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/message-factory.git",
"reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/message-factory/zipball/4d8778e1c7d405cbb471574821c1ff5b68cc8f57",
"reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57",
"shasum": ""
},
"require": {
"php": ">=5.4",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Factory interfaces for PSR-7 HTTP Message",
"homepage": "http://php-http.org",
"keywords": [
"factory",
"http",
"message",
"stream",
"uri"
],
"support": {
"issues": "https://github.com/php-http/message-factory/issues",
"source": "https://github.com/php-http/message-factory/tree/1.1.0"
},
"abandoned": "psr/http-factory",
"time": "2023-04-14T14:16:17+00:00"
},
{
"name": "php-http/promise",
"version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/php-http/promise.git",
"reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/promise/zipball/fc85b1fba37c169a69a07ef0d5a8075770cc1f83",
"reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3",
"phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4"
},
"type": "library",
"autoload": {
"psr-4": {
"Http\\Promise\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Joel Wurtz",
"email": "joel.wurtz@gmail.com"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Promise used for asynchronous HTTP requests",
"homepage": "http://httplug.io",
"keywords": [
"promise"
],
"support": {
"issues": "https://github.com/php-http/promise/issues",
"source": "https://github.com/php-http/promise/tree/1.3.1"
},
"time": "2024-03-15T13:55:21+00:00"
},
{
"name": "php-jsonpointer/php-jsonpointer",
"version": "v3.0.2",

View File

@ -16,7 +16,7 @@ return new class extends Migration
// ->cursor()
// ->each(function($company){
// if($company->tax_data?->version == 'alpha')
// if($company->tax_data?->version == 'alpha' && ($company->tax_data->seller_subregion ?? false))
// {
// $company->update(['tax_data' => new \App\DataMapper\Tax\TaxModel($company->tax_data)]);

View File

@ -0,0 +1,41 @@
<?php
use App\Utils\Ninja;
use App\Models\Company;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if(Ninja::isSelfHost())
{
Company::whereNotNull('tax_data')
->cursor()
->each(function ($company) {
if($company->tax_data?->version == 'alpha' && ($company->tax_data->seller_subregion ?? false)) {
$company->update(['tax_data' => new \App\DataMapper\Tax\TaxModel($company->tax_data)]);
}
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
//
}
};

View File

@ -5309,7 +5309,8 @@ $lang = array(
'account_holder_information' => 'Account Holder Information',
'enter_information_for_the_account_holder' => 'Enter Information for the Account Holder',
'customer_type' => 'Customer Type',
'process_date' => 'Process Date'
'process_date' => 'Process Date',
'forever_free' => 'Forever Free',
);
return $lang;

File diff suppressed because it is too large Load Diff

View File

@ -47,7 +47,7 @@
@foreach(App\Utils\TranslationHelper::getCurrencies() as $currency)
<option
{{ $currency->id == $subscription->company->settings->currency_id ? 'selected' : null }} value="{{ $currency->id }}">
{{ $currency->name }}
{{ $currency->getName() }}
</option>
@endforeach
</select>

View File

@ -70,7 +70,7 @@
@foreach(App\Utils\TranslationHelper::getCurrencies() as $currency)
<option
{{ $currency->id == $register_company->settings->currency_id ? 'selected' : null }} value="{{ $currency->id }}">
{{ $currency->name }}
{{ $currency->getName() }}
</option>
@endforeach
</select>

View File

@ -15,8 +15,8 @@
@endphp
@section('gateway_head')
<meta http-equiv="Content-Security-Policy" content="
frame-src 'self' https://c.paypal.com https://www.sandbox.paypal.com https://www.paypal.com;
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://c.paypal.com https://www.paypalobjects.com https://www.paypal.com https://www.sandbox.paypal.com/;
frame-src 'self' https://c.paypal.com https://www.sandbox.paypal.com https://www.paypal.com https://www.paypalobjects.com;
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://c.paypal.com https://www.paypalobjects.com https://www.paypal.com https://www.sandbox.paypal.com https://www.google-analytics.com;
img-src * data: 'self';
style-src 'self' 'unsafe-inline';"
>
@ -171,8 +171,18 @@
document.getElementById('errors').textContent = `Sorry, your transaction could not be processed...\n\n${error.message}`;
document.getElementById('errors').hidden = false;
document.getElementById('pay-now').disabled = false;
document.querySelector('#pay-now > svg').classList.add('hidden');
document.querySelector('#pay-now > span').classList.remove('hidden');
});
},
onError: function(error) {
throw new Error(error);
},
onCancel: function() {
@ -231,14 +241,12 @@
cardField.submit().then(() => {
}).catch((error) => {
console.log(error);
let msg;
if(!['INVALID_NUMBER','INVALID_CVV','INVALID_EXPIRY'].includes(error.message))
{
const errorM = parseError(error.message);
const errorM = parseError(error);
msg = handle422Error(errorM);
}
@ -255,7 +263,7 @@
else if(error.message == 'INVALID_EXPIRY') {
document.getElementById('errors').textContent = "{{ ctrans('texts.invalid_cvv') }}";
}
else if(msg.description){
else if(msg?.description){
document.getElementById('errors').textContent = msg?.description;
}
document.getElementById('errors').hidden = false;
@ -270,7 +278,7 @@
}
function handle422Error(errorData) {
const errorDetails = errorData.details || [];
const errorDetails = errorData?.details || [];
const detail = errorDetails[0];
return detail;
}

View File

@ -24,8 +24,8 @@
<input type="radio" data-token="{{ $token->token }}" name="payment-type"
class="form-radio cursor-pointer toggle-payment-with-token" />
<span class="ml-1 cursor-pointer">
{{ App\Models\GatewayType::getAlias($token->gateway_type_id) }} ({{ $token->meta->brand }})
&nbsp; {{ ctrans('texts.account_number') }}#: {{ $token->meta->account_number }}
{{ App\Models\GatewayType::getAlias($token->gateway_type_id) }} ({{ $token->meta->brand ?? 'Bank Transfer' }})
&nbsp; {{ ctrans('texts.account_number') }}#: {{ $token->meta?->last4 ?? '' }}
</span>
</label><br/>
@endforeach

View File

@ -13,6 +13,10 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="bank_name" name="bank_name" type="text" placeholder="{{ ctrans('texts.bank_name') }}" required value="{{ old('bank_name', $bank_name) }}">
@error('bank_name')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>
@ -22,6 +26,9 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="account_number" name="account_number" type="text" placeholder="{{ ctrans('texts.account_number') }}" required value="{{ old('account_number', $account_number) }}">
@error('account_number')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>

View File

@ -14,6 +14,9 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="address_1" name="address_1" type="text" placeholder="Address Line 1" required value="{{ old('address_1', $address_1) }}">
@error('address_1')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>
@ -23,6 +26,9 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="address_2" name="address_2" type="text" placeholder="Address Line 2" value="{{ old('address_2', $address_2) }}">
@error('address_2')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>
@ -32,6 +38,9 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="city" name="city" type="text" placeholder="City" required value="{{ old('city', $city) }}">
@error('city')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>
@ -41,6 +50,9 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="postal_code" name="postal_code" type="text" placeholder="Postal Code" required value="{{ old('postal_code', $postal_code ) }}">
@error('postal_code')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>
@ -56,6 +68,10 @@
<input type="radio" id="ca" name="country" value="CA" required @checked(old('country', $country) == 'CA')>
<label for="ca">{{ ctrans('texts.canada') }}</label><br>
@endif
@error('country')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>

View File

@ -4,6 +4,9 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="transit_number" max="5" name="transit_number" type="text" placeholder="{{ ctrans('texts.transit_number') }}" required value="{{ old('transit_number', $transit_number) }}">
@error('transit_number')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>
@ -13,5 +16,8 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="institution_number" max="3" name="institution_number" type="text" placeholder="{{ ctrans('texts.institution_number') }}" required value="{{ old('institution_number', $institution_number) }}">
@error('institution_number')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>

View File

@ -4,6 +4,9 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="routing_number" name="routing_number" type="text" placeholder="{{ ctrans('texts.routing_number') }}" required value="{{ old('routing_number', $routing_number) }}">
@error('routing_number')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>
@ -16,10 +19,16 @@
<div class="flex items-center px-2">
<input id="bank_account_type_savings" name="bank_account_type" value="Savings" required @checked(old('bank_account_type', $bank_account_type)) type="radio" class="focus:ring-gray-500 h-4 w-4 border-gray-300 disabled:opacity-75 disabled:cursor-not-allowed">
<label for="bank_account_type_savings" class="ml-3 block text-sm font-medium cursor-pointer">{{ ctrans('texts.savings') }}</label>
@error('bank_account_type')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
<div class="flex items-center px-2">
<input id="bank_account_type_checking" name="bank_account_type" value="Checking" required @checked(old('bank_account_type', $bank_account_type)) type="radio" class="focus:ring-gray-500 h-4 w-4 border-gray-300 disabled:opacity-75 disabled:cursor-not-allowed">
<label for="bank_account_type_checking" class="ml-3 block text-sm font-medium cursor-pointer">{{ ctrans('texts.checking') }}</label>
@error('bank_account_type')
ed-500 text-sm mt-1">{{ $message }}</p>
@enderror
</div>
</div>
</dd>

View File

@ -14,6 +14,9 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="name" name="name" type="text" placeholder="{{ ctrans('texts.name') }}" required value="{{ old('name', $name) }}">
@error('name')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>
@ -23,6 +26,9 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" name="email" id="email" type="email" placeholder="{{ ctrans('texts.email_address') }}" required value="{{ old('email', $email) }}">
@error('email')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>
@ -32,6 +38,9 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="home_phone" name="home_phone" type="text" placeholder="{{ ctrans('texts.phone') }}" required value="{{ old('home_phone', $home_phone) }}">
@error('home_phone')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>
@ -41,6 +50,9 @@
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<input class="input w-full" id="phone" name="phone" type="text" placeholder="{{ ctrans('texts.work_phone') }}" required value="{{ old('phone', $phone) }}">
@error('phone')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>
@ -59,6 +71,9 @@
<label for="customer_type_business" class="ml-3 block text-sm font-medium cursor-pointer">{{ ctrans('texts.business') }}</label>
</div>
</div>
@error('customer_type')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>

View File

@ -8,5 +8,9 @@
<option value="{{ $code }}" @selected(old('province_code', $province_code) == $code ) >{{ $province }}</option>
@endforeach
</select>
@error('province_code')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>

View File

@ -8,5 +8,8 @@
<option value="{{ $code }}" @selected(old('province_code', $province_code) == $code ) >{{ $state }}</option>
@endforeach
</select>
@error('province_code')
<p class="text-red-500 text-sm mt-1">{{ $message }}</p>
@enderror
</dd>
</div>

View File

@ -1451,16 +1451,31 @@ Ensure the default browser behavior of the `hidden` attribute.
@csrf
<input type="hidden" name="gateway_response"/>
<div class="alert alert-failure mb-4" hidden="" id="errors"></div>
<div class="form-group mb-[10px]">
<div class="form-group mb-[10px] flex">
<div class="w-1/2">
<input
type="text"
class="form-control block w-full px-3 py-2 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-light-grey rounded transition ease-in-out m-0 focus:primary-blue focus:outline-none"
id="name"
placeholder="{{ ctrans('texts.name') }}"
name="name"
value="{{$client->name}}"
id="first_name"
placeholder="{{ ctrans('texts.first_name') }}"
name="first_name"
value="{{ auth()->guard('contact')->user()->first_name}}"
required
/>
</div>
<div class="w-1/2">
<input
type="text"
class="form-control block w-full px-3 py-2 text-base font-normal text-gray-700 bg-white bg-clip-padding border border-light-grey rounded transition ease-in-out m-0 focus:primary-blue focus:outline-none"
id="lastt_name"
placeholder="{{ ctrans('texts.last_name') }}"
name="last_name"
value="{{ auth()->guard('contact')->user()->last_name}}"
required
/>
</div>
</div>
<div class="form-group mb-[10px]">
<input
@ -1810,7 +1825,7 @@ var elements = stripe.elements({
var cardElement = elements.create('card', {
value: {
postalCode: document.querySelector('input[name=postal_code]').content,
name: document.querySelector('input[name=name]').content,
name: document.querySelector('input[name=first_name]').content + ' ' + document.querySelector('input[name=last_name]').content,
}
});
@ -1827,12 +1842,12 @@ var country_value = e.options[e.selectedIndex].value;
//make sure the user has entered their name
if (document.querySelector('input[name=name]').value == '') {
if (document.querySelector('input[name=first_name]').value == '') {
let errors = document.getElementById('errors');
let payNowButton = document.getElementById('pay-now');
errors.textContent = '';
errors.textContent = "{{ ctrans('texts.please_enter_a_name') }}";
errors.textContent = "{{ ctrans('texts.please_enter_a_first_name') }}";
errors.hidden = false;
payNowButton.disabled = false;
@ -1841,6 +1856,19 @@ var country_value = e.options[e.selectedIndex].value;
return;
}
if (document.querySelector('input[name=last_name]').value == '') {
let errors = document.getElementById('errors');
let payNowButton = document.getElementById('pay-now');
errors.textContent = '';
errors.textContent = "{{ ctrans('texts.please_enter_a_last_name') }}";
errors.hidden = false;
payNowButton.disabled = false;
payNowButton.querySelector('svg').classList.add('hidden');
payNowButton.querySelector('span').classList.remove('hidden');
return;
}
let payNowButton = document.getElementById('pay-now');
payNowButton = payNowButton;
@ -1851,7 +1879,7 @@ var country_value = e.options[e.selectedIndex].value;
stripe.handleCardSetup(this.client_secret, cardElement, {
payment_method_data: {
billing_details: {
name: document.querySelector('input[name=name]').content,
name: document.querySelector('input[name=first_name]').content + ' ' + document.querySelector('input[name=first_name]').content,
email: '{{ $client->present()->email() }}',
address: {
line1: document.querySelector('input[name=address1]').content,

View File

@ -1450,7 +1450,7 @@ Ensure the default browser behavior of the `hidden` attribute.
type="button"
class="mx-[auto] max-w-[212px] bg-primary-blue hover:opacity-80 button button-primary bg-primary rounded-sm text-sm transition duration-300 ease-in md:mx-[0]"
>
Account Login
{{ ctrans('texts.return_to_app') }}
</a>
</div>
</div>