diff --git a/app/DataMapper/Analytics/DbQuery.php b/app/DataMapper/Analytics/DbQuery.php
index c5718278dab0..ac65c29b36c0 100644
--- a/app/DataMapper/Analytics/DbQuery.php
+++ b/app/DataMapper/Analytics/DbQuery.php
@@ -52,6 +52,10 @@ class DbQuery extends GenericMixedMetric
public $string_metric7 = 'ip_address';
+ public $string_metric8 = 'client_version';
+
+ public $string_metric9 = 'platform';
+
/**
* The counter
* set to 1.
diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php
index d3b1d56c6da1..079030b16670 100644
--- a/app/Http/Controllers/BaseController.php
+++ b/app/Http/Controllers/BaseController.php
@@ -32,7 +32,6 @@ use App\Models\BankIntegration;
use App\Models\BankTransaction;
use App\Models\ExpenseCategory;
use League\Fractal\Resource\Item;
-use App\DataMapper\EDoc\Schema\RO;
use App\Models\BankTransactionRule;
use Illuminate\Support\Facades\Auth;
use App\Transformers\ArraySerializer;
@@ -42,6 +41,7 @@ use Illuminate\Database\Eloquent\Builder;
use League\Fractal\Serializer\JsonApiSerializer;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use Illuminate\Contracts\Container\BindingResolutionException;
+use Invoiceninja\Einvoice\Decoder\Schema;
/**
* Class BaseController.
@@ -890,7 +890,6 @@ class BaseController extends Controller
/** @phpstan-ignore-next-line **/
$query = $paginator->getCollection();// @phpstan-ignore-line
-
$resource = new Collection($query, $transformer, $this->entity_type);
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
}
@@ -998,8 +997,8 @@ class BaseController extends Controller
if(request()->has('einvoice')){
- $ro = new RO();
- $response_data['einvoice_schema'] = $ro();
+ $ro = new Schema();
+ $response_data['einvoice_schema'] = $ro('FACT1');
}
diff --git a/app/Http/Middleware/QueryLogging.php b/app/Http/Middleware/QueryLogging.php
index 106d498cb55f..efe0ef707702 100644
--- a/app/Http/Middleware/QueryLogging.php
+++ b/app/Http/Middleware/QueryLogging.php
@@ -73,7 +73,22 @@ class QueryLogging
$ip = $request->ip();
}
- LightLogs::create(new DbQuery($request->method(), substr(urldecode($request->url()), 0, 180), $count, $time, $ip))
+ $client_version = $request->server('HTTP_USER_AGENT');
+ $platform = '';
+
+ if ($request->hasHeader('X-CLIENT-PLATFORM')) {
+ $platform = $request->header('X-CLIENT-PLATFORM');
+ }
+ elseif($request->hasHeader('X-React')){
+ $platform = 'react';
+ }
+
+ if ($request->hasHeader('X-CLIENT-VERSION'))
+ {
+ $client_version = $request->header('X-CLIENT-VERSION');
+ }
+
+ LightLogs::create(new DbQuery($request->method(), substr(urldecode($request->url()), 0, 180), $count, $time, $ip, $client_version, $platform))
->batch();
}
diff --git a/app/Http/Requests/Company/UpdateCompanyRequest.php b/app/Http/Requests/Company/UpdateCompanyRequest.php
index 4cdd76bddcf6..f415386a0889 100644
--- a/app/Http/Requests/Company/UpdateCompanyRequest.php
+++ b/app/Http/Requests/Company/UpdateCompanyRequest.php
@@ -17,6 +17,7 @@ use App\Http\ValidationRules\Company\ValidSubdomain;
use App\Http\ValidationRules\ValidSettingsRule;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
+use Invoiceninja\Einvoice\Models\FatturaPA\FatturaElettronica;
class UpdateCompanyRequest extends Request
{
@@ -113,6 +114,11 @@ class UpdateCompanyRequest extends Request
$input['smtp_verify_peer'] == 'true' ? true : false;
}
+ // if(isset($input['e_invoice'])){
+ // nlog("am i set?");
+ // $r = FatturaElettronica::validate($input['e_invoice']);
+ // }
+
$this->replace($input);
}
diff --git a/app/Jobs/Company/CompanyImport.php b/app/Jobs/Company/CompanyImport.php
index eb288bcf31f0..c6a1054f2eaa 100644
--- a/app/Jobs/Company/CompanyImport.php
+++ b/app/Jobs/Company/CompanyImport.php
@@ -241,6 +241,32 @@ class CompanyImport implements ShouldQueue
CompanyGateway::class => [
'always_show_required_fields',
]
+ ],
+ '5.8.57' => [
+ Company::class => [
+ 'einvoice',
+ 'e_invoice',
+ ],
+ Invoice::class => [
+ 'einvoice',
+ 'e_invoice',
+ ],
+ Quote::class => [
+ 'einvoice',
+ 'e_invoice',
+ ],
+ Credit::class => [
+ 'einvoice',
+ 'e_invoice',
+ ],
+ PurchaseOrder::class => [
+ 'einvoice',
+ 'e_invoice',
+ ],
+ Expense::class => [
+ 'einvoice',
+ 'e_invoice',
+ ],
]
];
diff --git a/app/Jobs/Mail/NinjaMailerJob.php b/app/Jobs/Mail/NinjaMailerJob.php
index 7abfdbc29c12..b1b1c94690eb 100644
--- a/app/Jobs/Mail/NinjaMailerJob.php
+++ b/app/Jobs/Mail/NinjaMailerJob.php
@@ -136,8 +136,8 @@ class NinjaMailerJob implements ShouldQueue
$mailable = $this->nmo->mailable;
- /** May need to re-build it here */
- if (Ninja::isHosted() && method_exists($mailable, 'build')) {
+ /** May need to re-build it here @todo explain why we need this? */
+ if (Ninja::isHosted() && method_exists($mailable, 'build') && $this->nmo->settings->email_style != "custom") {
$mailable->build();
}
diff --git a/app/Jobs/Payment/EmailPayment.php b/app/Jobs/Payment/EmailPayment.php
index 9e3428ccf9f5..1d1c88313e02 100644
--- a/app/Jobs/Payment/EmailPayment.php
+++ b/app/Jobs/Payment/EmailPayment.php
@@ -116,13 +116,17 @@ class EmailPayment implements ShouldQueue
$invoice->invitations->each(function ($invite) use ($email_builder) {
+
+ $cloned_mailable = unserialize(serialize($email_builder));
+
$nmo = new NinjaMailerObject();
- $nmo->mailable = new TemplateEmail($email_builder, $invite->contact, $invite);
+ $nmo->mailable = new TemplateEmail($cloned_mailable, $invite->contact, $invite);
$nmo->to_user = $invite->contact;
$nmo->settings = $this->settings;
$nmo->company = $this->company;
$nmo->entity = $this->payment;
(new NinjaMailerJob($nmo))->handle();
+ $nmo = null;
event(new PaymentWasEmailed($this->payment, $this->payment->company, $invite->contact, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php
index 64c0ebce9270..f5f0a7ef871b 100644
--- a/app/Jobs/Util/ReminderJob.php
+++ b/app/Jobs/Util/ReminderJob.php
@@ -126,7 +126,7 @@ class ReminderJob implements ShouldQueue
}
$reminder_template = $invoice->calculateTemplate('invoice');
- nrlog("reminder template = {$reminder_template}");
+ nrlog("#{$invoice->number} => reminder template = {$reminder_template}");
$invoice->service()->touchReminder($reminder_template)->save();
$fees = $this->calcLateFee($invoice, $reminder_template);
diff --git a/app/Mail/Engine/InvoiceEmailEngine.php b/app/Mail/Engine/InvoiceEmailEngine.php
index 9e66b4524fe1..8f9e15f8dbf0 100644
--- a/app/Mail/Engine/InvoiceEmailEngine.php
+++ b/app/Mail/Engine/InvoiceEmailEngine.php
@@ -134,10 +134,6 @@ class InvoiceEmailEngine extends BaseEmailEngine
$this->setAttachments([['file' => base64_encode($pdf), 'name' => $this->invoice->numberFormatter().'.pdf']]);
}
- // $hash = Str::uuid();
- // $url = \Illuminate\Support\Facades\URL::temporarySignedRoute('protected_download', now()->addHour(), ['hash' => $hash]);
- // Cache::put($hash, $url, now()->addHour());
-
//attach third party documents
if ($this->client->getSetting('document_email_attachment') !== false && $this->invoice->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) {
if ($this->invoice->recurring_invoice()->exists()) {
diff --git a/app/Mail/Engine/PaymentEmailEngine.php b/app/Mail/Engine/PaymentEmailEngine.php
index 32613b80f192..c2d68aef570f 100644
--- a/app/Mail/Engine/PaymentEmailEngine.php
+++ b/app/Mail/Engine/PaymentEmailEngine.php
@@ -99,7 +99,6 @@ class PaymentEmailEngine extends BaseEmailEngine
->setViewLink('')
->setViewText('');
-
if ($this->client->getSetting('pdf_email_attachment') !== false && $this->company->account->hasFeature(Account::FEATURE_PDF_ATTACHMENT)) {
$template_in_use = false;
diff --git a/app/Models/Account.php b/app/Models/Account.php
index c2b54f25211e..81d262454b79 100644
--- a/app/Models/Account.php
+++ b/app/Models/Account.php
@@ -31,6 +31,7 @@ use Laracasts\Presenter\PresentableTrait;
* App\Models\Account
*
* @property int $id
+ * @property int $email_quota
* @property string|null $plan
* @property string|null $plan_term
* @property string|null $plan_started
diff --git a/app/Models/Company.php b/app/Models/Company.php
index 0538ddddf5df..e83c465cef79 100644
--- a/app/Models/Company.php
+++ b/app/Models/Company.php
@@ -364,6 +364,7 @@ class Company extends BaseModel
'smtp_encryption',
'smtp_local_domain',
'smtp_verify_peer',
+ 'e_invoice',
];
protected $hidden = [
@@ -388,6 +389,7 @@ class Company extends BaseModel
'e_invoice_certificate_passphrase' => EncryptedCast::class,
'smtp_username' => 'encrypted',
'smtp_password' => 'encrypted',
+ 'e_invoice' => 'object',
];
protected $with = [];
diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php
index 530c0bb46196..44f3c606ea6e 100644
--- a/app/Models/CompanyGateway.php
+++ b/app/Models/CompanyGateway.php
@@ -155,6 +155,7 @@ class CompanyGateway extends BaseModel
'b9886f9257f0c6ee7c302f1c74475f6c' => 321, //GoCardless
'hxd6gwg3ekb9tb3v9lptgx1mqyg69zu9' => 322,
'80af24a6a691230bbec33e930ab40666' => 323,
+ 'vpyfbmdrkqcicpkjqdusgjfluebftuva' => 324, //BTPay
];
protected $touches = [];
diff --git a/app/Models/Credit.php b/app/Models/Credit.php
index d73b497b9381..b20000e8ff44 100644
--- a/app/Models/Credit.php
+++ b/app/Models/Credit.php
@@ -28,6 +28,7 @@ use Laracasts\Presenter\PresentableTrait;
* App\Models\Credit
*
* @property int $id
+ * @property object|null $e_invoice
* @property int $client_id
* @property int $user_id
* @property int|null $assigned_user_id
@@ -179,6 +180,7 @@ class Credit extends BaseModel
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
'is_amount_discount' => 'bool',
+ 'e_invoice' => 'object',
];
diff --git a/app/Models/Expense.php b/app/Models/Expense.php
index 22032fafc152..eb9ee205ca6f 100644
--- a/app/Models/Expense.php
+++ b/app/Models/Expense.php
@@ -17,6 +17,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* App\Models\Expense
*
* @property int $id
+ * @property object|null $e_invoice
* @property int|null $created_at
* @property int|null $updated_at
* @property int|null $deleted_at
@@ -141,6 +142,7 @@ class Expense extends BaseModel
'updated_at' => 'timestamp',
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
+ 'e_invoice' => 'object',
];
protected $touches = [];
diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php
index 78cd6ba231c5..135f420ed8f5 100644
--- a/app/Models/Invoice.php
+++ b/app/Models/Invoice.php
@@ -32,6 +32,7 @@ use Laracasts\Presenter\PresentableTrait;
* App\Models\Invoice
*
* @property int $id
+ * @property object|null $e_invoice
* @property int $client_id
* @property int $user_id
* @property int|null $assigned_user_id
@@ -209,6 +210,7 @@ class Invoice extends BaseModel
'custom_surcharge_tax2' => 'bool',
'custom_surcharge_tax3' => 'bool',
'custom_surcharge_tax4' => 'bool',
+ 'e_invoice' => 'object',
];
protected $with = [];
diff --git a/app/Models/Presenters/CompanyPresenter.php b/app/Models/Presenters/CompanyPresenter.php
index 085fc2806dd9..00fd2ccbc2dc 100644
--- a/app/Models/Presenters/CompanyPresenter.php
+++ b/app/Models/Presenters/CompanyPresenter.php
@@ -83,9 +83,9 @@ class CompanyPresenter extends EntityPresenter
];
if (strlen($settings->company_logo) >= 1 && (strpos($settings->company_logo, 'http') !== false)) {
- return "data:image/png;base64, ". base64_encode(@file_get_contents($settings->company_logo, false, stream_context_create($context_options)));
+ return "data:image/png;base64,". base64_encode(@file_get_contents($settings->company_logo, false, stream_context_create($context_options)));
} elseif (strlen($settings->company_logo) >= 1) {
- return "data:image/png;base64, ". base64_encode(@file_get_contents(url('') . $settings->company_logo, false, stream_context_create($context_options)));
+ return "data:image/png;base64,". base64_encode(@file_get_contents(url('') . $settings->company_logo, false, stream_context_create($context_options)));
} else {
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
}
diff --git a/app/Models/PurchaseOrder.php b/app/Models/PurchaseOrder.php
index 3d89be54aa07..63912a90ca91 100644
--- a/app/Models/PurchaseOrder.php
+++ b/app/Models/PurchaseOrder.php
@@ -22,6 +22,7 @@ use Illuminate\Support\Carbon;
* App\Models\PurchaseOrder
*
* @property int $id
+ * @property object|null $e_invoice
* @property int|null $client_id
* @property int $user_id
* @property int|null $assigned_user_id
@@ -187,7 +188,7 @@ class PurchaseOrder extends BaseModel
'created_at' => 'timestamp',
'deleted_at' => 'timestamp',
'is_amount_discount' => 'bool',
-
+ 'e_invoice' => 'object',
];
public const STATUS_DRAFT = 1;
diff --git a/app/Models/Quote.php b/app/Models/Quote.php
index 5a1d73391ab8..71770a531f8a 100644
--- a/app/Models/Quote.php
+++ b/app/Models/Quote.php
@@ -27,6 +27,7 @@ use Laracasts\Presenter\PresentableTrait;
* App\Models\Quote
*
* @property int $id
+ * @property object|null $e_invoice
* @property int $client_id
* @property int $user_id
* @property int|null $assigned_user_id
@@ -174,6 +175,7 @@ class Quote extends BaseModel
'deleted_at' => 'timestamp',
'is_deleted' => 'boolean',
'is_amount_discount' => 'bool',
+ 'e_invoice' => 'object',
];
public const STATUS_DRAFT = 1;
diff --git a/app/Models/SystemLog.php b/app/Models/SystemLog.php
index 344c8d832c05..5ef3305d525b 100644
--- a/app/Models/SystemLog.php
+++ b/app/Models/SystemLog.php
@@ -150,6 +150,8 @@ class SystemLog extends Model
public const TYPE_PAYPAL_PPCP = 323;
+ public const TYPE_BTC_PAY = 324;
+
public const TYPE_QUOTA_EXCEEDED = 400;
public const TYPE_UPSTREAM_FAILURE = 401;
diff --git a/app/PaymentDrivers/AuthorizePaymentDriver.php b/app/PaymentDrivers/AuthorizePaymentDriver.php
index bd1ee2274e79..465474a5d595 100644
--- a/app/PaymentDrivers/AuthorizePaymentDriver.php
+++ b/app/PaymentDrivers/AuthorizePaymentDriver.php
@@ -67,7 +67,7 @@ class AuthorizePaymentDriver extends BaseDriver
public function getClientRequiredFields(): array
{
$data = [
- ['name' => 'client_name', 'label' => ctrans('texts.name'), 'type' => 'text', 'validation' => 'required|min:2'],
+ // ['name' => 'client_name', 'label' => ctrans('texts.name'), 'type' => 'text', 'validation' => 'required|min:2'],
['name' => 'client_phone', 'label' => ctrans('texts.phone'), 'type' => 'text', 'validation' => 'required'],
['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required|email:rfc'],
['name' => 'client_address_line_1', 'label' => ctrans('texts.address1'), 'type' => 'text', 'validation' => 'required'],
diff --git a/app/PaymentDrivers/PayPal/PayPalBasePaymentDriver.php b/app/PaymentDrivers/PayPal/PayPalBasePaymentDriver.php
new file mode 100644
index 000000000000..a3059c20cdcd
--- /dev/null
+++ b/app/PaymentDrivers/PayPal/PayPalBasePaymentDriver.php
@@ -0,0 +1,426 @@
+ 'paypal',
+ 1 => 'card',
+ 25 => 'venmo',
+ 29 => 'paypal_advanced_cards',
+ // 9 => 'sepa',
+ // 12 => 'bancontact',
+ // 17 => 'eps',
+ // 15 => 'giropay',
+ // 13 => 'ideal',
+ // 26 => 'mercadopago',
+ // 27 => 'mybank',
+ 28 => 'paylater',
+ // 16 => 'p24',
+ // 7 => 'sofort'
+ ];
+
+ public function gatewayTypes()
+ {
+
+ $funding_options =
+
+ collect($this->company_gateway->fees_and_limits)
+ ->filter(function ($fee) {
+ return $fee->is_enabled;
+ })->map(function ($fee, $key) {
+ return (int)$key;
+ })->toArray();
+
+ /** Parse funding options and remove card option if advanced cards is enabled. */
+ if(in_array(1, $funding_options) && in_array(29, $funding_options)){
+
+ if (($key = array_search(1, $funding_options)) !== false)
+ unset($funding_options[$key]);
+
+ }
+
+ return $funding_options;
+
+ }
+
+ public function getPaymentMethod($gateway_type_id): int
+ {
+ $method = PaymentType::PAYPAL;
+
+ match($gateway_type_id) {
+ "1" => $method = PaymentType::CREDIT_CARD_OTHER,
+ "3" => $method = PaymentType::PAYPAL,
+ "25" => $method = PaymentType::VENMO,
+ "28" => $method = PaymentType::PAY_LATER,
+ "29" => $method = PaymentType::CREDIT_CARD_OTHER,
+ };
+
+ return $method;
+ }
+
+ public function init()
+ {
+
+ $this->api_endpoint_url = $this->company_gateway->getConfigField('testMode') ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
+
+ $secret = $this->company_gateway->getConfigField('secret');
+ $client_id = $this->company_gateway->getConfigField('clientId');
+
+ if($this->access_token && $this->token_expiry && $this->token_expiry->isFuture()) {
+ return $this;
+ }
+
+ $response = Http::withBasicAuth($client_id, $secret)
+ ->withHeaders(['Content-Type' => 'application/x-www-form-urlencoded'])
+ ->withQueryParameters(['grant_type' => 'client_credentials'])
+ ->post("{$this->api_endpoint_url}/v1/oauth2/token");
+
+ if($response->successful()) {
+ $this->access_token = $response->json()['access_token'];
+ $this->token_expiry = now()->addSeconds($response->json()['expires_in'] - 60);
+ } else {
+ throw new PaymentFailed('Unable to gain access token from Paypal. Check your configuration', 401);
+ }
+
+ return $this;
+
+ }
+
+
+ /**
+ * getFundingOptions
+ *
+ * Hosted fields requires this.
+ *
+ * @return string
+ */
+ public function getFundingOptions(): string
+ {
+
+ $enums = [
+ 1 => 'card',
+ 3 => 'paypal',
+ 25 => 'venmo',
+ 28 => 'paylater',
+ // 9 => 'sepa',
+ // 12 => 'bancontact',
+ // 17 => 'eps',
+ // 15 => 'giropay',
+ // 13 => 'ideal',
+ // 26 => 'mercadopago',
+ // 27 => 'mybank',
+ // 28 => 'paylater',
+ // 16 => 'p24',
+ // 7 => 'sofort'
+ ];
+
+ $funding_options = '';
+
+ foreach($this->company_gateway->fees_and_limits as $key => $value) {
+
+ if($value->is_enabled) {
+
+ $funding_options .= $enums[$key].',';
+
+ }
+
+ }
+
+ return rtrim($funding_options, ',');
+
+ }
+
+ public function getShippingAddress(): ?array
+ {
+ return $this->company_gateway->require_shipping_address ?
+ [
+ "address" =>
+ [
+ "address_line_1" => strlen($this->client->shipping_address1) > 1 ? $this->client->shipping_address1 : $this->client->address1,
+ "address_line_2" => $this->client->shipping_address2,
+ "admin_area_2" => strlen($this->client->shipping_city) > 1 ? $this->client->shipping_city : $this->client->city,
+ "admin_area_1" => strlen($this->client->shipping_state) > 1 ? $this->client->shipping_state : $this->client->state,
+ "postal_code" => strlen($this->client->shipping_postal_code) > 1 ? $this->client->shipping_postal_code : $this->client->postal_code,
+ "country_code" => $this->client->present()->shipping_country_code(),
+ ],
+ ]
+
+ : [
+ "name" => [
+ "full_name" => $this->client->present()->name()
+ ]
+ ];
+
+ }
+
+ public function getBillingAddress(): array
+ {
+ return
+ [
+ "address_line_1" => $this->client->address1,
+ "address_line_2" => $this->client->address2,
+ "admin_area_2" => $this->client->city,
+ "admin_area_1" => $this->client->state,
+ "postal_code" => $this->client->postal_code,
+ "country_code" => $this->client->country->iso_3166_2,
+ ];
+ }
+
+ public function getPaymentSource(): array
+ {
+ //@todo - roll back here for advanced payments vs hosted card fields.
+ if($this->gateway_type_id == GatewayType::PAYPAL_ADVANCED_CARDS) {
+
+ return [
+ "card" => [
+ "attributes" => [
+ "verification" => [
+ "method" => "SCA_WHEN_REQUIRED", //SCA_ALWAYS
+ // "method" => "SCA_ALWAYS", //SCA_ALWAYS
+ ],
+ "vault" => [
+ "store_in_vault" => "ON_SUCCESS", //must listen to this webhook - VAULT.PAYMENT-TOKEN.CREATED webhook.
+ ],
+ ],
+ "experience_context" => [
+ "shipping_preference" => "SET_PROVIDED_ADDRESS"
+ ],
+ "stored_credential" => [
+ // "payment_initiator" => "MERCHANT", //"CUSTOMER" who initiated the transaction?
+ "payment_initiator" => "CUSTOMER", //"" who initiated the transaction?
+ "payment_type" => "UNSCHEDULED", //UNSCHEDULED
+ "usage"=> "DERIVED",
+ ],
+ ],
+ ];
+
+ }
+
+ $order = [
+ "paypal" => [
+ "name" => [
+ "given_name" => $this->client->present()->first_name(),
+ "surname" => $this->client->present()->last_name(),
+ ],
+ "email_address" => $this->client->present()->email(),
+ "experience_context" => [
+ "user_action" => "PAY_NOW"
+ ],
+ ],
+ ];
+
+ /** If we have a complete address, add it to the order, otherwise leave it blank! */
+ if(
+ strlen($this->client->shipping_address1 ?? '') > 2 &&
+ strlen($this->client->shipping_city ?? '') > 2 &&
+ strlen($this->client->shipping_state ?? '') >= 2 &&
+ strlen($this->client->shipping_postal_code ?? '') > 2 &&
+ strlen($this->client->shipping_country->iso_3166_2 ?? '') >= 2
+ ) {
+ $order['paypal']['address'] = [
+ "address_line_1" => $this->client->shipping_address1,
+ "address_line_2" => $this->client->shipping_address2,
+ "admin_area_2" => $this->client->shipping_city,
+ "admin_area_1" => $this->client->shipping_state,
+ "postal_code" => $this->client->shipping_postal_code,
+ "country_code" => $this->client->present()->shipping_country_code(),
+ ];
+ }
+ elseif(
+ strlen($this->client->address1 ?? '') > 2 &&
+ strlen($this->client->city ?? '') > 2 &&
+ strlen($this->client->state ?? '') >= 2 &&
+ strlen($this->client->postal_code ?? '') > 2 &&
+ strlen($this->client->country->iso_3166_2 ?? '') >= 2
+ )
+ {
+ $order['paypal']['address'] = [
+ "address_line_1" => $this->client->address1,
+ "address_line_2" => $this->client->address2,
+ "admin_area_2" => $this->client->city,
+ "admin_area_1" => $this->client->state,
+ "postal_code" => $this->client->postal_code,
+ "country_code" => $this->client->country->iso_3166_2,
+ ];
+ }
+
+ return $order;
+
+ }
+
+ /**
+ * Payment method setter
+ *
+ * @param mixed $payment_method_id
+ * @return self
+ */
+ public function setPaymentMethod($payment_method_id): self
+ {
+ if(!$payment_method_id) {
+ return $this;
+ }
+
+ $this->gateway_type_id = $payment_method_id;
+
+ $this->paypal_payment_method = $this->funding_options[$payment_method_id];
+
+ 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;
+ }
+
+ /**
+ * Generates the gateway request
+ *
+ * @param string $uri
+ * @param string $verb
+ * @param array $data
+ * @param ?array $headers
+ * @return \Illuminate\Http\Client\Response
+ */
+ public function gatewayRequest(string $uri, string $verb, array $data, ?array $headers = [])
+ {
+ $this->init();
+
+ $r = Http::withToken($this->access_token)
+ ->withHeaders($this->getHeaders($headers))
+ ->{$verb}("{$this->api_endpoint_url}{$uri}", $data);
+
+ if($r->successful()) {
+ return $r;
+ }
+
+ SystemLogger::dispatch(
+ ['response' => $r->body()],
+ SystemLog::CATEGORY_GATEWAY_RESPONSE,
+ SystemLog::EVENT_GATEWAY_FAILURE,
+ SystemLog::TYPE_PAYPAL,
+ $this->client,
+ $this->client->company ?? $this->company_gateway->company,
+ );
+
+ throw new PaymentFailed("Gateway failure - {$r->body()}", 401);
+
+ }
+
+ /**
+ * Generates the request headers
+ *
+ * @param array $headers
+ * @return array
+ */
+ public function getHeaders(array $headers = []): array
+ {
+ return array_merge([
+ 'Accept' => 'application/json',
+ 'Content-type' => 'application/json',
+ 'Accept-Language' => 'en_US',
+ 'PayPal-Partner-Attribution-Id' => 'invoiceninja_SP_PPCP',
+ 'PayPal-Request-Id' => Str::uuid()->toString(),
+ ], $headers);
+ }
+
+ /**
+ * Generates a client token for the payment form.
+ *
+ * @return string
+ */
+ public function getClientToken(): string
+ {
+
+ $r = $this->gatewayRequest('/v1/identity/generate-token', 'post', ['body' => '']);
+
+ if($r->successful()) {
+ return $r->json()['client_token'];
+ }
+
+ throw new PaymentFailed('Unable to gain client token from Paypal. Check your configuration', 401);
+
+ }
+
+ public function auth(): bool
+ {
+
+ try {
+ $this->init()->getClientToken();
+ return true;
+ }
+ catch(\Exception $e) {
+
+ }
+
+ return false;
+ }
+
+ public function importCustomers()
+ {
+ return true;
+ }
+
+ public function processWebhookRequest(Request $request)
+ {
+
+ $this->init();
+
+ PayPalWebhook::dispatch($request->all(), $request->headers->all(), $this->access_token);
+ }
+
+}
\ No newline at end of file
diff --git a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php
index 12758fa10171..055e41d1587e 100644
--- a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php
+++ b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php
@@ -21,188 +21,34 @@ use Illuminate\Http\Request;
use App\Jobs\Util\SystemLogger;
use App\Utils\Traits\MakesHash;
use App\Exceptions\PaymentFailed;
+use App\Models\ClientGatewayToken;
use Illuminate\Support\Facades\Http;
use App\PaymentDrivers\PayPal\PayPalWebhook;
+use App\PaymentDrivers\PayPal\PayPalBasePaymentDriver;
-class PayPalPPCPPaymentDriver extends BaseDriver
+class PayPalPPCPPaymentDriver extends PayPalBasePaymentDriver
{
use MakesHash;
-
- public $token_billing = false;
-
- public $can_authorise_credit_card = false;
-
- private $omnipay_gateway;
-
- private float $fee = 0;
+
+///v1/customer/partners/merchant-accounts/{merchant_id}/capabilities - test if advanced cards is available.
+// {
+// "capabilities": [
+// {
+// "name": "ADVANCED_CARD_PAYMENTS",
+// "status": "ENABLED"
+// },
+// {
+// "name": "VAULTING",
+// "status": "ENABLED"
+// }
+// ]
+// }
public const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYPAL_PPCP;
-
- private string $api_endpoint_url = '';
-
- private string $paypal_payment_method = '';
-
- private ?int $gateway_type_id = null;
-
- protected mixed $access_token = null;
-
- protected ?Carbon $token_expiry = null;
-
- private array $funding_options = [
- 3 => 'paypal',
- 1 => 'card',
- 25 => 'venmo',
- // 9 => 'sepa',
- // 12 => 'bancontact',
- // 17 => 'eps',
- // 15 => 'giropay',
- // 13 => 'ideal',
- // 26 => 'mercadopago',
- // 27 => 'mybank',
- 28 => 'paylater',
- // 16 => 'p24',
- // 7 => 'sofort'
- ];
-
+
/**
- * Return an array of
- * enabled gateway payment methods
- *
- * @return array
- */
- public function gatewayTypes(): array
- {
-
- return collect($this->company_gateway->fees_and_limits)
- ->filter(function ($fee) {
- return $fee->is_enabled;
- })->map(function ($fee, $key) {
- return (int)$key;
- })->toArray();
-
- }
-
- private function getPaymentMethod($gateway_type_id): int
- {
- $method = PaymentType::PAYPAL;
-
- match($gateway_type_id) {
- "1" => $method = PaymentType::CREDIT_CARD_OTHER,
- "3" => $method = PaymentType::PAYPAL,
- "25" => $method = PaymentType::VENMO,
- "28" => $method = PaymentType::PAY_LATER,
- };
-
- return $method;
- }
-
- private function getFundingOptions(): string
- {
-
- $enums = [
- 1 => 'card',
- 3 => 'paypal',
- 25 => 'venmo',
- 28 => 'paylater',
- // 9 => 'sepa',
- // 12 => 'bancontact',
- // 17 => 'eps',
- // 15 => 'giropay',
- // 13 => 'ideal',
- // 26 => 'mercadopago',
- // 27 => 'mybank',
- // 28 => 'paylater',
- // 16 => 'p24',
- // 7 => 'sofort'
- ];
-
- $funding_options = '';
-
- foreach($this->company_gateway->fees_and_limits as $key => $value) {
-
- if($value->is_enabled) {
-
- $funding_options .= $enums[$key].',';
-
- }
-
- }
-
- return rtrim($funding_options, ',');
-
- }
-
- /**
- * Initialize the Paypal gateway.
- *
- * Attempt to generate and return the access token.
- *
- * @return self
- */
- public function init(): self
- {
-
- $this->api_endpoint_url = 'https://api-m.paypal.com';
- // $this->api_endpoint_url = 'https://api-m.sandbox.paypal.com';
- $secret = config('ninja.paypal.secret');
- $client_id = config('ninja.paypal.client_id');
-
- if($this->access_token && $this->token_expiry && $this->token_expiry->isFuture()) {
- return $this;
- }
-
- $response = Http::withBasicAuth($client_id, $secret)
- ->withHeaders(['Content-Type' => 'application/x-www-form-urlencoded'])
- ->withQueryParameters(['grant_type' => 'client_credentials'])
- ->post("{$this->api_endpoint_url}/v1/oauth2/token");
-
- if($response->successful()) {
- $this->access_token = $response->json()['access_token'];
- $this->token_expiry = now()->addSeconds($response->json()['expires_in'] - 60);
- } else {
- throw new PaymentFailed('Unable to gain access token from Paypal. Check your configuration', 401);
- }
-
- return $this;
-
- }
-
- /**
- * Payment method setter
- *
- * @param mixed $payment_method_id
- * @return self
- */
- public function setPaymentMethod($payment_method_id): self
- {
- if(!$payment_method_id) {
- return $this;
- }
-
- $this->gateway_type_id = $payment_method_id;
-
- $this->paypal_payment_method = $this->funding_options[$payment_method_id];
-
- 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;
- }
-
- /**
- * Checks whether payments are enabled on the account
- *
+ * Checks whether payments are enabled on the merchant account
+ *
* @return self
*/
private function checkPaymentsReceivable(): self
@@ -252,7 +98,10 @@ class PayPalPPCPPaymentDriver extends BaseDriver
$data['merchantId'] = $this->company_gateway->getConfigField('merchantId');
$data['currency'] = $this->client->currency()->code;
- return render('gateways.paypal.ppcp.pay', $data);
+ if($this->gateway_type_id == 29)
+ return render('gateways.paypal.ppcp.card', $data);
+ else
+ return render('gateways.paypal.ppcp.pay', $data);
}
@@ -268,6 +117,10 @@ class PayPalPPCPPaymentDriver extends BaseDriver
$request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']);
$response = json_decode($request['gateway_response'], true);
+ if($request->has('token') && strlen($request->input('token')) > 2) {
+ return $this->processTokenPayment($request, $response);
+ }
+
//capture
$orderID = $response['orderID'];
@@ -335,7 +188,7 @@ class PayPalPPCPPaymentDriver extends BaseDriver
['response' => $response, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
- SystemLog::TYPE_PAYPAL,
+ SystemLog::TYPE_PAYPAL_PPCP,
$this->client,
$this->client->company,
);
@@ -352,7 +205,7 @@ class PayPalPPCPPaymentDriver extends BaseDriver
['response' => $response],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
- SystemLog::TYPE_PAYPAL,
+ SystemLog::TYPE_PAYPAL_PPCP,
$this->client,
$this->client->company,
);
@@ -372,74 +225,13 @@ class PayPalPPCPPaymentDriver extends BaseDriver
return $r->json();
}
- /**
- * Generates a client token for the payment form.
- *
- * @return string
- */
- private function getClientToken(): string
- {
-
- $r = $this->gatewayRequest('/v1/identity/generate-token', 'post', ['body' => '']);
-
- if($r->successful()) {
- return $r->json()['client_token'];
- }
-
- throw new PaymentFailed('Unable to gain client token from Paypal. Check your configuration', 401);
-
- }
-
- /**
- * Builds the payment request.
- *
- * @return array
- */
- private function paymentSource(): array
- {
- /** we only need to support paypal as payment source until as we are only using hosted payment buttons */
- return $this->injectPayPalPaymentSource();
-
- }
-
- private function injectPayPalPaymentSource(): array
- {
-
- $order = [
- "paypal" => [
- "name" => [
- "given_name" => $this->client->present()->first_name(),
- "surname" => $this->client->present()->last_name(),
- ],
- "email_address" => $this->client->present()->email(),
- "experience_context" => [
- "user_action" => "PAY_NOW"
- ],
- ],
- ];
-
- if(
- strlen($this->client->address1 ?? '') > 2 &&
- strlen($this->client->city ?? '') > 2 &&
- strlen($this->client->state ?? '') >= 2 &&
- strlen($this->client->postal_code ?? '') > 2 &&
- strlen($this->client->country->iso_3166_2 ?? '') >= 2
- )
- {
- $order["paypal"]["address"] = $this->getBillingAddress();
- }
-
- return $order;
-
- }
-
/**
* Creates the PayPal Order object
*
* @param array $data
* @return string
*/
- private function createOrder(array $data): string
+ public function createOrder(array $data): string
{
$_invoice = collect($this->payment_hash->data->invoices)->first();
@@ -453,7 +245,7 @@ class PayPalPPCPPaymentDriver extends BaseDriver
$order = [
"intent" => "CAPTURE",
- "payment_source" => $this->paymentSource(),
+ "payment_source" => $this->getPaymentSource(),
"purchase_units" => [
[
"custom_id" => $this->payment_hash->hash,
@@ -465,7 +257,6 @@ class PayPalPPCPPaymentDriver extends BaseDriver
"payment_instruction" => [
"disbursement_mode" => "INSTANT",
],
- $this->getShippingAddress(),
"amount" => [
"value" => (string)$data['amount_with_fee'],
"currency_code" => $this->client->currency()->code,
@@ -496,125 +287,76 @@ class PayPalPPCPPaymentDriver extends BaseDriver
$order['purchase_units'][0]["shipping"] = $shipping;
}
+ if(isset($data['payment_source'])) {
+ $order['payment_source'] = $data['payment_source'];
+ }
+
$r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order);
return $r->json()['id'];
}
- private function getBillingAddress(): array
- {
- return
- [
- "address_line_1" => $this->client->address1,
- "address_line_2" => $this->client->address2,
- "admin_area_2" => $this->client->city,
- "admin_area_1" => $this->client->state,
- "postal_code" => $this->client->postal_code,
- "country_code" => $this->client->country->iso_3166_2,
- ];
- }
-
- private function getShippingAddress(): ?array
- {
- return $this->company_gateway->require_shipping_address ?
- [
- "address" =>
- [
- "address_line_1" => strlen($this->client->shipping_address1 ?? '') > 1 ? $this->client->shipping_address1 : $this->client->address1,
- "address_line_2" => $this->client->shipping_address2,
- "admin_area_2" => strlen($this->client->shipping_city ?? '') > 1 ? $this->client->shipping_city : $this->client->city,
- "admin_area_1" => strlen($this->client->shipping_state ?? '') > 1 ? $this->client->shipping_state : $this->client->state,
- "postal_code" => strlen($this->client->shipping_postal_code ?? '') > 1 ? $this->client->shipping_postal_code : $this->client->postal_code,
- "country_code" => $this->client->present()->shipping_country_code(),
- ],
- ]
-
- : null;
-
- }
-
/**
- * Generates the gateway request
+ * processTokenPayment
*
- * @param string $uri
- * @param string $verb
- * @param array $data
- * @param ?array $headers
- * @return \Illuminate\Http\Client\Response
+ * With PayPal and token payments, the order needs to be
+ * deleted and then created with the payment source that
+ * has been selected by the client.
+ *
+ * This method handle the deletion of the current paypal order,
+ * and the automatic payment of the order with the selected payment source.
+ *
+ * @param mixed $request
+ * @param array $response
+ * @return void
*/
- public function gatewayRequest(string $uri, string $verb, array $data, ?array $headers = [])
- {
- $this->init();
+ public function processTokenPayment($request, array $response) {
- $r = Http::withToken($this->access_token)
- ->withHeaders($this->getHeaders($headers))
- ->{$verb}("{$this->api_endpoint_url}{$uri}", $data);
+ $cgt = ClientGatewayToken::where('client_id', $this->client->id)
+ ->where('token', $request['token'])
+ ->firstOrFail();
- if($r->successful()) {
- return $r;
- }
+ $orderId = $response['orderID'];
+ $r = $this->gatewayRequest("/v1/checkout/orders/{$orderId}/", 'delete', ['body' => '']);
+
+ $data['amount_with_fee'] = $this->payment_hash->data->amount_with_fee;
+ $data["payment_source"] = [
+ "card" => [
+ "vault_id" => $cgt->token,
+ "stored_credential" => [
+ "payment_initiator" => "MERCHANT",
+ "payment_type" => "UNSCHEDULED",
+ "usage" => "SUBSEQUENT",
+ ],
+ ],
+ ];
+
+ $orderId = $this->createOrder($data);
+
+ $r = $this->gatewayRequest("/v2/checkout/orders/{$orderId}", 'get', ['body' => '']);
+
+ $response = $r->json();
+
+ $data = [
+ 'payment_type' => $this->getPaymentMethod($request->gateway_type_id),
+ 'amount' => $response['purchase_units'][0]['payments']['captures'][0]['amount']['value'],
+ 'transaction_reference' => $response['purchase_units'][0]['payments']['captures'][0]['id'],
+ 'gateway_type_id' => $this->gateway_type_id,
+ ];
+
+ $payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
- ['response' => $r->body()],
+ ['response' => $response, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
- SystemLog::EVENT_GATEWAY_FAILURE,
- SystemLog::TYPE_PAYPAL,
+ SystemLog::EVENT_GATEWAY_SUCCESS,
+ SystemLog::TYPE_PAYPAL_PPCP,
$this->client,
$this->client->company,
);
- throw new PaymentFailed("Gateway failure - {$r->body()}", 401);
+ return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
}
-
- /**
- * Generates the request headers
- *
- * @param array $headers
- * @return array
- */
- private function getHeaders(array $headers = []): array
- {
- return array_merge([
- 'Accept' => 'application/json',
- 'Content-type' => 'application/json',
- 'Accept-Language' => 'en_US',
- 'PayPal-Partner-Attribution-Id' => 'invoiceninja_SP_PPCP',
- 'PayPal-Request-Id' => Str::uuid()->toString(),
- ], $headers);
- }
-
- public function processWebhookRequest(Request $request)
- {
-
- // nlog(json_encode($request->all()));
- $this->init();
-
- PayPalWebhook::dispatch($request->all(), $request->headers->all(), $this->access_token);
- }
-
- public function auth(): bool
- {
-
- try {
- $this->init()->getClientToken();
- return true;
- }
- catch(\Exception $e) {
-
- }
-
- return false;
- }
-
- public function importCustomers()
- {
-
- // $response = $this->gatewayRequest('/v1/reporting/transactions', 'get', ['fields' => 'all','page_size' => 500,'start_date' => '2024-02-01T00:00:00-0000', 'end_date' => '2024-03-01T00:00:00-0000']);
-
- // nlog($response->json());
-
- return true;
- }
-}
+}
\ No newline at end of file
diff --git a/app/PaymentDrivers/PayPalRestPaymentDriver.php b/app/PaymentDrivers/PayPalRestPaymentDriver.php
index 5745ab86d725..24f44b127487 100644
--- a/app/PaymentDrivers/PayPalRestPaymentDriver.php
+++ b/app/PaymentDrivers/PayPalRestPaymentDriver.php
@@ -12,147 +12,22 @@
namespace App\PaymentDrivers;
-use Carbon\Carbon;
-use Omnipay\Omnipay;
use App\Models\Invoice;
use App\Models\SystemLog;
use App\Models\GatewayType;
-use App\Models\PaymentType;
use Illuminate\Support\Str;
use App\Jobs\Util\SystemLogger;
use App\Utils\Traits\MakesHash;
use App\Exceptions\PaymentFailed;
use App\Models\ClientGatewayToken;
-use Illuminate\Support\Facades\Http;
+use App\PaymentDrivers\PayPal\PayPalBasePaymentDriver;
-class PayPalRestPaymentDriver extends BaseDriver
+class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
{
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;
- private string $api_endpoint_url = '';
-
- private string $paypal_payment_method = '';
-
- private ?int $gateway_type_id = null;
-
- protected mixed $access_token = null;
-
- protected ?Carbon $token_expiry = null;
-
- private array $funding_options = [
- 3 => 'paypal',
- 1 => 'card',
- 25 => 'venmo',
- 29 => 'paypal_advanced_cards',
- // 9 => 'sepa',
- // 12 => 'bancontact',
- // 17 => 'eps',
- // 15 => 'giropay',
- // 13 => 'ideal',
- // 26 => 'mercadopago',
- // 27 => 'mybank',
- 28 => 'paylater',
- // 16 => 'p24',
- // 7 => 'sofort'
- ];
-
-
- public function gatewayTypes()
- {
-
- $funding_options = [];
-
- foreach ($this->company_gateway->fees_and_limits as $key => $value) {
- if ($value->is_enabled) {
- $funding_options[] = $key;
- }
- }
-
- return $funding_options;
-
- }
-
- public function init()
- {
-
- $this->api_endpoint_url = $this->company_gateway->getConfigField('testMode') ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
-
- $secret = $this->company_gateway->getConfigField('secret');
- $client_id = $this->company_gateway->getConfigField('clientId');
-
- if($this->access_token && $this->token_expiry && $this->token_expiry->isFuture()) {
- return $this;
- }
-
- $response = Http::withBasicAuth($client_id, $secret)
- ->withHeaders(['Content-Type' => 'application/x-www-form-urlencoded'])
- ->withQueryParameters(['grant_type' => 'client_credentials'])
- ->post("{$this->api_endpoint_url}/v1/oauth2/token");
-
- if($response->successful()) {
- $this->access_token = $response->json()['access_token'];
- $this->token_expiry = now()->addSeconds($response->json()['expires_in'] - 60);
- } else {
- throw new PaymentFailed('Unable to gain access token from Paypal. Check your configuration', 401);
- }
-
- return $this;
-
- }
-
- private function getPaymentMethod($gateway_type_id): int
- {
- $method = PaymentType::PAYPAL;
-
- match($gateway_type_id) {
- "1" => $method = PaymentType::CREDIT_CARD_OTHER,
- "3" => $method = PaymentType::PAYPAL,
- "25" => $method = PaymentType::VENMO,
- "28" => $method = PaymentType::PAY_LATER,
- "29" => $method = PaymentType::CREDIT_CARD_OTHER,
- };
-
- return $method;
- }
-
- public function setPaymentMethod($payment_method_id): self
- {
- if(!$payment_method_id) {
- return $this;
- }
-
- $this->gateway_type_id = $payment_method_id;
-
- $this->paypal_payment_method = $this->funding_options[$payment_method_id];
-
-
- 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->init();
@@ -169,110 +44,20 @@ class PayPalRestPaymentDriver extends BaseDriver
$data['gateway_type_id'] = $this->gateway_type_id;
$data['currency'] = $this->client->currency()->code;
-
-// return render('gateways.paypal.ppcp.card', $data);
-
-return render('gateways.paypal.pay', $data);
+ if($this->gateway_type_id == 29)
+ return render('gateways.paypal.ppcp.card', $data);
+ else
+ return render('gateways.paypal.pay', $data);
}
-
- private function getFundingOptions(): string
- {
-
- $enums = [
- 3 => 'paypal',
- 1 => 'card',
- 25 => 'venmo',
- // 9 => 'sepa',
- // 12 => 'bancontact',
- // 17 => 'eps',
- // 15 => 'giropay',
- // 13 => 'ideal',
- // 26 => 'mercadopago',
- // 27 => 'mybank',
- // 28 => 'paylater',
- // 16 => 'p24',
- // 7 => 'sofort'
- ];
-
- $funding_options = '';
-
- foreach($this->company_gateway->fees_and_limits as $key => $value) {
-
- if($value->is_enabled) {
-
- $funding_options .= $enums[$key].',';
-
- }
-
- }
-
- return rtrim($funding_options, ',');
-
- }
-
- public function processTokenPayment($request, array $response) {
-
- $cgt = ClientGatewayToken::where('client_id', $this->client->id)
- ->where('token', $request['token'])
- ->firstOrFail();
- nlog("process token");
-
- nlog($request->all());
- nlog($response);
-
- $orderId = $response['orderID'];
- $r = $this->gatewayRequest("/v1/checkout/orders/{$orderId}/", 'delete', ['body' => '']);
-
- nlog($r);
-
- $data['amount_with_fee'] = $this->payment_hash->data->amount_with_fee;
- $data["payment_source"] = [
- "card" => [
- "vault_id" => $cgt->token,
- "stored_credential" => [
- "payment_initiator" => "MERCHANT",
- "payment_type" => "UNSCHEDULED",
- "usage" => "SUBSEQUENT",
- // "previous_transaction_reference" => $cgt->gateway_customer_reference,
- ],
- ],
- ];
-
- $orderId = $this->createOrder($data);
-
- nlog("post order creation");
- nlog($orderId);
-
-
- $r = $this->gatewayRequest("/v2/checkout/orders/{$orderId}", 'get', ['body' => '']);
- nlog($r);
-
- $response = $r->json();
- nlog($response);
-
- $data = [
- 'payment_type' => $this->getPaymentMethod($request->gateway_type_id),
- 'amount' => $response['purchase_units'][0]['payments']['captures'][0]['amount']['value'],
- 'transaction_reference' => $response['purchase_units'][0]['payments']['captures'][0]['id'],
- 'gateway_type_id' => $this->gateway_type_id,
- ];
-
- $payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
-
- SystemLogger::dispatch(
- ['response' => $response, '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)]);
-
- }
-
+
+
+ /**
+ * processPaymentResponse
+ *
+ * @param mixed $request
+ * @return void
+ */
public function processPaymentResponse($request)
{
@@ -280,12 +65,10 @@ return render('gateways.paypal.pay', $data);
$request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']);
$response = json_decode($request['gateway_response'], true);
- nlog($request->all());
if($request->has('token') && strlen($request->input('token')) > 2)
return $this->processTokenPayment($request, $response);
- // nlog($response);
//capture
$orderID = $response['orderID'];
@@ -339,6 +122,9 @@ return render('gateways.paypal.pay', $data);
$response = $r;
+ nlog("Process response =>");
+ nlog($response->json());
+
if(isset($response['status']) && $response['status'] == 'COMPLETED' && isset($response['purchase_units'])) {
return $this->createNinjaPayment($request, $response);
@@ -367,8 +153,6 @@ return render('gateways.paypal.pay', $data);
private function createNinjaPayment($request, $response) {
- nlog($response->json());
-
$data = [
'payment_type' => $this->getPaymentMethod($request->gateway_type_id),
'amount' => $response['purchase_units'][0]['payments']['captures'][0]['amount']['value'],
@@ -401,7 +185,6 @@ return render('gateways.paypal.pay', $data);
$data['token'] = $token;
$data['payment_method_id'] = GatewayType::PAYPAL_ADVANCED_CARDS;
$data['payment_meta'] = $payment_meta;
- // $data['payment_method_id'] = GatewayType::CREDIT_CARD;
$additional['gateway_customer_reference'] = $gateway_customer_reference;
@@ -423,116 +206,7 @@ return render('gateways.paypal.pay', $data);
}
- private function getClientToken(): string
- {
-
- $r = $this->gatewayRequest('/v1/identity/generate-token', 'post', ['body' => '']);
-
- if($r->successful()) {
- return $r->json()['client_token'];
- }
-
- throw new PaymentFailed('Unable to gain client token from Paypal. Check your configuration', 401);
-
- }
-
- private function getPaymentSource(): array
- {
- //@todo - roll back here for advanced payments vs hosted card fields.
- if($this->gateway_type_id == GatewayType::PAYPAL_ADVANCED_CARDS) {
-
- return [
- "card" => [
- "attributes" => [
- "verification" => [
- "method" => "SCA_WHEN_REQUIRED", //SCA_ALWAYS
- // "method" => "SCA_ALWAYS", //SCA_ALWAYS
- ],
- "vault" => [
- "store_in_vault" => "ON_SUCCESS", //must listen to this webhook - VAULT.PAYMENT-TOKEN.CREATED webhook.
- ],
- ],
- "experience_context" => [
- "shipping_preference" => "SET_PROVIDED_ADDRESS"
- ],
- // "name" => $this->client->present()->primary_contact_name(),
- // "email_address" => $this->client->present()->email(),
- // "address" => [
- // "address_line_1" => $this->client->address1,
- // "address_line_2" => $this->client->address2,
- // "admin_area_2" => $this->client->city,
- // "admin_area_1" => $this->client->state,
- // "postal_code" => $this->client->postal_code,
- // "country_code" => $this->client->country->iso_3166_2,
- // ],
- // "experience_context" => [
- // "user_action" => "PAY_NOW"
- // ],
- "stored_credential" => [
- // "payment_initiator" => "MERCHANT", //"CUSTOMER" who initiated the transaction?
- "payment_initiator" => "CUSTOMER", //"" who initiated the transaction?
- "payment_type" => "UNSCHEDULED", //UNSCHEDULED
- "usage"=> "DERIVED",
- ],
- ],
- ];
-
- }
-
- $order = [
- "paypal" => [
- "name" => [
- "given_name" => $this->client->present()->first_name(),
- "surname" => $this->client->present()->last_name(),
- ],
- "email_address" => $this->client->present()->email(),
- "experience_context" => [
- "user_action" => "PAY_NOW"
- ],
- ],
- ];
-
- /** If we have a complete address, add it to the order, otherwise leave it blank! */
- if(
- strlen($this->client->shipping_address1 ?? '') > 2 &&
- strlen($this->client->shipping_city ?? '') > 2 &&
- strlen($this->client->shipping_state ?? '') >= 2 &&
- strlen($this->client->shipping_postal_code ?? '') > 2 &&
- strlen($this->client->shipping_country->iso_3166_2 ?? '') >= 2
- ) {
- $order['paypal']['address'] = [
- "address_line_1" => $this->client->shipping_address1,
- "address_line_2" => $this->client->shipping_address2,
- "admin_area_2" => $this->client->shipping_city,
- "admin_area_1" => $this->client->shipping_state,
- "postal_code" => $this->client->shipping_postal_code,
- "country_code" => $this->client->present()->shipping_country_code(),
- ];
- }
- elseif(
- strlen($this->client->address1 ?? '') > 2 &&
- strlen($this->client->city ?? '') > 2 &&
- strlen($this->client->state ?? '') >= 2 &&
- strlen($this->client->postal_code ?? '') > 2 &&
- strlen($this->client->country->iso_3166_2 ?? '') >= 2
- )
- {
- $order['paypal']['address'] = [
- "address_line_1" => $this->client->address1,
- "address_line_2" => $this->client->address2,
- "admin_area_2" => $this->client->city,
- "admin_area_1" => $this->client->state,
- "postal_code" => $this->client->postal_code,
- "country_code" => $this->client->country->iso_3166_2,
- ];
- }
-
- return $order;
-
- }
-
-
- private function createOrder(array $data): string
+ public function createOrder(array $data): string
{
$_invoice = collect($this->payment_hash->data->invoices)->first();
@@ -576,126 +250,81 @@ return render('gateways.paypal.pay', $data);
]
];
-
if($shipping = $this->getShippingAddress()) {
$order['purchase_units'][0]["shipping"] = $shipping;
}
if(isset($data['payment_source']))
$order['payment_source'] = $data['payment_source'];
-
+
+
+
$r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order);
return $r->json()['id'];
}
- private function getShippingAddress(): ?array
- {
- return $this->company_gateway->require_shipping_address ?
- [
- "address" =>
- [
- "address_line_1" => strlen($this->client->shipping_address1) > 1 ? $this->client->shipping_address1 : $this->client->address1,
- "address_line_2" => $this->client->shipping_address2,
- "admin_area_2" => strlen($this->client->shipping_city) > 1 ? $this->client->shipping_city : $this->client->city,
- "admin_area_1" => strlen($this->client->shipping_state) > 1 ? $this->client->shipping_state : $this->client->state,
- "postal_code" => strlen($this->client->shipping_postal_code) > 1 ? $this->client->shipping_postal_code : $this->client->postal_code,
- "country_code" => $this->client->present()->shipping_country_code(),
- ],
- ]
+ /**
+ * processTokenPayment
+ *
+ * With PayPal and token payments, the order needs to be
+ * deleted and then created with the payment source that
+ * has been selected by the client.
+ *
+ * This method handle the deletion of the current paypal order,
+ * and the automatic payment of the order with the selected payment source.
+ *
+ * @param mixed $request
+ * @param array $response
+ * @return void
+ */
+ public function processTokenPayment($request, array $response) {
- : [
- "name" => [
- "full_name" => $this->client->present()->name()
- ]
+ $cgt = ClientGatewayToken::where('client_id', $this->client->id)
+ ->where('token', $request['token'])
+ ->firstOrFail();
+
+ $orderId = $response['orderID'];
+ $r = $this->gatewayRequest("/v1/checkout/orders/{$orderId}/", 'delete', ['body' => '']);
+
+ $data['amount_with_fee'] = $this->payment_hash->data->amount_with_fee;
+ $data["payment_source"] = [
+ "card" => [
+ "vault_id" => $cgt->token,
+ "stored_credential" => [
+ "payment_initiator" => "MERCHANT",
+ "payment_type" => "UNSCHEDULED",
+ "usage" => "SUBSEQUENT",
+ ],
+ ],
+ ];
+
+ $orderId = $this->createOrder($data);
+
+ $r = $this->gatewayRequest("/v2/checkout/orders/{$orderId}", 'get', ['body' => '']);
+
+ $response = $r->json();
+
+ $data = [
+ 'payment_type' => $this->getPaymentMethod($request->gateway_type_id),
+ 'amount' => $response['purchase_units'][0]['payments']['captures'][0]['amount']['value'],
+ 'transaction_reference' => $response['purchase_units'][0]['payments']['captures'][0]['id'],
+ 'gateway_type_id' => $this->gateway_type_id,
];
- }
-
- /**
- * Generates the gateway request
- *
- * @param string $uri
- * @param string $verb
- * @param array $data
- * @param ?array $headers
- * @return \Illuminate\Http\Client\Response
- */
- public function gatewayRequest(string $uri, string $verb, array $data, ?array $headers = [])
- {
- $this->init();
-
- $r = Http::withToken($this->access_token)
- ->withHeaders($this->getHeaders($headers))
- ->{$verb}("{$this->api_endpoint_url}{$uri}", $data);
-
- if($r->successful()) {
- return $r;
- }
+ $payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
- ['response' => $r->body()],
+ ['response' => $response, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
- SystemLog::EVENT_GATEWAY_FAILURE,
+ SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_PAYPAL,
$this->client,
- $this->client->company ?? $this->company_gateway->company,
+ $this->client->company,
);
- throw new PaymentFailed("Gateway failure - {$r->body()}", 401);
+ return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
}
-
- private function getHeaders(array $headers = []): array
- {
- return array_merge([
- 'Accept' => 'application/json',
- 'Content-type' => 'application/json',
- 'Accept-Language' => 'en_US',
- 'PayPal-Partner-Attribution-Id' => 'invoiceninja_SP_PPCP',
- 'PayPal-Request-Id' => Str::uuid()->toString(),
- ], $headers);
- }
-
- 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;
- }
-
- public function auth(): bool
- {
-
- try {
- $this->init()->getClientToken();
- return true;
- }
- catch(\Exception $e) {
-
- }
-
- return false;
- }
-
- public function importCustomers()
- {
- return true;
- }
-
}
diff --git a/app/Repositories/TaskRepository.php b/app/Repositories/TaskRepository.php
index c95207c25a1b..7d19a122665f 100644
--- a/app/Repositories/TaskRepository.php
+++ b/app/Repositories/TaskRepository.php
@@ -150,7 +150,7 @@ class TaskRepository extends BaseRepository
{
if(isset($time_log[0][0])) {
- return \Carbon\Carbon::createFromTimestamp($time_log[0][0]);
+ return \Carbon\Carbon::createFromTimestamp($time_log[0][0])->addSeconds($task->company->utc_offset());
}
return null;
diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php
index 661b8aef52c4..e56091c909d5 100644
--- a/app/Services/Email/Email.php
+++ b/app/Services/Email/Email.php
@@ -314,7 +314,7 @@ class Email implements ShouldQueue
$this->logMailError($e->getMessage(), $this->company->clients()->first());
$this->cleanUpMailers();
-$this->entityEmailFailed($message);
+ $this->entityEmailFailed($message);
return;
}
diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php
index 7c94978d7260..f13090bb63de 100644
--- a/app/Transformers/CompanyTransformer.php
+++ b/app/Transformers/CompanyTransformer.php
@@ -211,6 +211,7 @@ class CompanyTransformer extends EntityTransformer
'smtp_password' => $company->smtp_password ? '********' : '',
'smtp_local_domain' => (string)$company->smtp_local_domain ?? '',
'smtp_verify_peer' => (bool)$company->smtp_verify_peer,
+ 'e_invoice' => $company->e_invoice ?: new \stdClass(),
];
}
diff --git a/app/Transformers/CreditTransformer.php b/app/Transformers/CreditTransformer.php
index 63b6053bdf21..4e9f143516c2 100644
--- a/app/Transformers/CreditTransformer.php
+++ b/app/Transformers/CreditTransformer.php
@@ -133,6 +133,7 @@ class CreditTransformer extends EntityTransformer
'subscription_id' => $this->encodePrimaryKey($credit->subscription_id),
'invoice_id' => $credit->invoice_id ? $this->encodePrimaryKey($credit->invoice_id) : '',
'tax_info' => $credit->tax_data ?: new \stdClass(),
+ 'e_invoice' => $credit->e_invoice ?: new \stdClass(),
];
}
diff --git a/app/Transformers/ExpenseTransformer.php b/app/Transformers/ExpenseTransformer.php
index 1f825ba81796..e8e130e2ec10 100644
--- a/app/Transformers/ExpenseTransformer.php
+++ b/app/Transformers/ExpenseTransformer.php
@@ -148,6 +148,8 @@ class ExpenseTransformer extends EntityTransformer
'uses_inclusive_taxes' => (bool) $expense->uses_inclusive_taxes,
'calculate_tax_by_amount' => (bool) $expense->calculate_tax_by_amount,
'entity_type' => 'expense',
+ 'e_invoice' => $expense->e_invoice ?: new \stdClass(),
+
];
}
}
diff --git a/app/Transformers/InvoiceTransformer.php b/app/Transformers/InvoiceTransformer.php
index fd18eb49a0c5..415f6f5b7092 100644
--- a/app/Transformers/InvoiceTransformer.php
+++ b/app/Transformers/InvoiceTransformer.php
@@ -158,6 +158,8 @@ class InvoiceTransformer extends EntityTransformer
'subscription_id' => $this->encodePrimaryKey($invoice->subscription_id),
'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled,
'tax_info' => $invoice->tax_data ?: new \stdClass(),
+ 'e_invoice' => $invoice->e_invoice ?: new \stdClass(),
+
];
if (request()->has('reminder_schedule') && request()->query('reminder_schedule') == 'true') {
diff --git a/app/Transformers/PurchaseOrderTransformer.php b/app/Transformers/PurchaseOrderTransformer.php
index 6df9bfed3647..4d1a2469beeb 100644
--- a/app/Transformers/PurchaseOrderTransformer.php
+++ b/app/Transformers/PurchaseOrderTransformer.php
@@ -150,6 +150,8 @@ class PurchaseOrderTransformer extends EntityTransformer
'expense_id' => $this->encodePrimaryKey($purchase_order->expense_id),
'currency_id' => $purchase_order->currency_id ? (string) $purchase_order->currency_id : '',
'tax_info' => $purchase_order->tax_data ?: new \stdClass(),
+ 'e_invoice' => $purchase_order->e_invoice ?: new \stdClass(),
+
];
}
}
diff --git a/app/Transformers/QuoteTransformer.php b/app/Transformers/QuoteTransformer.php
index f918a90653e8..c981568043ae 100644
--- a/app/Transformers/QuoteTransformer.php
+++ b/app/Transformers/QuoteTransformer.php
@@ -149,6 +149,8 @@ class QuoteTransformer extends EntityTransformer
'project_id' => $this->encodePrimaryKey($quote->project_id),
'subscription_id' => $this->encodePrimaryKey($quote->subscription_id),
'tax_info' => $quote->tax_data ?: new \stdClass(),
+ 'e_invoice' => $quote->e_invoice ?: new \stdClass(),
+
];
}
}
diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php
index c3946d619309..111ec0f4f74a 100644
--- a/app/Utils/HtmlEngine.php
+++ b/app/Utils/HtmlEngine.php
@@ -561,10 +561,19 @@ class HtmlEngine
$data['$spc_qr_code'] = ['value' => $this->company->present()->getSpcQrCode($this->client->currency()->code, $this->entity->number, $this->entity->balance, $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client)), 'label' => ''];
- $logo = $this->company->present()->logo_base64($this->settings);
+ if(Ninja::isHosted())
+ $logo = $this->company->present()->logo($this->settings);
+ else
+ $logo = $this->company->present()->logo_base64($this->settings);
+
+ $logo_url = $this->company->present()->logo($this->settings);
+
$data['$company.logo'] = ['value' => $logo ?: ' ', 'label' => ctrans('texts.logo')];
$data['$company_logo'] = &$data['$company.logo'];
+
+ $data['$company.logo_url'] = ['value' => $logo_url ?: ' ', 'label' => ctrans('texts.logo')];
+
$data['$company1'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company1')];
$data['$company2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company2', $this->settings->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company2')];
$data['$company3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company3', $this->settings->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'company3')];
diff --git a/composer.lock b/composer.lock
index a9fb7f986e18..f572fe345741 100644
--- a/composer.lock
+++ b/composer.lock
@@ -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": "da485c7cec773404ffe59d450b6505cf",
+ "content-hash": "1356155e46e797b140685c105df97b8e",
"packages": [
{
"name": "adrienrn/php-mimetyper",
@@ -3221,26 +3221,26 @@
},
{
"name": "firebase/php-jwt",
- "version": "v6.10.0",
+ "version": "v6.10.1",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
- "reference": "a49db6f0a5033aef5143295342f1c95521b075ff"
+ "reference": "500501c2ce893c824c801da135d02661199f60c5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/firebase/php-jwt/zipball/a49db6f0a5033aef5143295342f1c95521b075ff",
- "reference": "a49db6f0a5033aef5143295342f1c95521b075ff",
+ "url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5",
+ "reference": "500501c2ce893c824c801da135d02661199f60c5",
"shasum": ""
},
"require": {
- "php": "^7.4||^8.0"
+ "php": "^8.0"
},
"require-dev": {
- "guzzlehttp/guzzle": "^6.5||^7.4",
+ "guzzlehttp/guzzle": "^7.4",
"phpspec/prophecy-phpunit": "^2.0",
"phpunit/phpunit": "^9.5",
- "psr/cache": "^1.0||^2.0",
+ "psr/cache": "^2.0||^3.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
},
@@ -3278,9 +3278,9 @@
],
"support": {
"issues": "https://github.com/firebase/php-jwt/issues",
- "source": "https://github.com/firebase/php-jwt/tree/v6.10.0"
+ "source": "https://github.com/firebase/php-jwt/tree/v6.10.1"
},
- "time": "2023-12-01T16:26:39+00:00"
+ "time": "2024-05-18T18:05:11+00:00"
},
{
"name": "fruitcake/php-cors",
@@ -3602,23 +3602,23 @@
},
{
"name": "google/apiclient-services",
- "version": "v0.355.0",
+ "version": "v0.356.0",
"source": {
"type": "git",
"url": "https://github.com/googleapis/google-api-php-client-services.git",
- "reference": "235e6a45ecafd77accc102b5ab6d529aab54da23"
+ "reference": "8e22b0a6f661f2db3f99abb6ee5a1dcf28d370e7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/235e6a45ecafd77accc102b5ab6d529aab54da23",
- "reference": "235e6a45ecafd77accc102b5ab6d529aab54da23",
+ "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/8e22b0a6f661f2db3f99abb6ee5a1dcf28d370e7",
+ "reference": "8e22b0a6f661f2db3f99abb6ee5a1dcf28d370e7",
"shasum": ""
},
"require": {
- "php": "^7.4||^8.0"
+ "php": "^8.0"
},
"require-dev": {
- "phpunit/phpunit": "^5.7||^8.5.13"
+ "phpunit/phpunit": "^9.6"
},
"type": "library",
"autoload": {
@@ -3640,9 +3640,9 @@
],
"support": {
"issues": "https://github.com/googleapis/google-api-php-client-services/issues",
- "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.355.0"
+ "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.356.0"
},
- "time": "2024-05-11T01:02:11+00:00"
+ "time": "2024-05-18T01:10:18+00:00"
},
{
"name": "google/auth",
@@ -5034,12 +5034,12 @@
"source": {
"type": "git",
"url": "https://github.com/invoiceninja/einvoice.git",
- "reference": "6028038ff94e6c0090ba5c444bd0be56a65bc0bc"
+ "reference": "39aec367c528ba66d923dc1d9e5c96f673bb46c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/invoiceninja/einvoice/zipball/6028038ff94e6c0090ba5c444bd0be56a65bc0bc",
- "reference": "6028038ff94e6c0090ba5c444bd0be56a65bc0bc",
+ "url": "https://api.github.com/repos/invoiceninja/einvoice/zipball/39aec367c528ba66d923dc1d9e5c96f673bb46c7",
+ "reference": "39aec367c528ba66d923dc1d9e5c96f673bb46c7",
"shasum": ""
},
"require": {
@@ -5075,7 +5075,7 @@
"source": "https://github.com/invoiceninja/einvoice/tree/main",
"issues": "https://github.com/invoiceninja/einvoice/issues"
},
- "time": "2024-05-18T12:35:18+00:00"
+ "time": "2024-05-20T11:42:32+00:00"
},
{
"name": "invoiceninja/inspector",
diff --git a/database/factories/AccountFactory.php b/database/factories/AccountFactory.php
index 371c2a3f9193..17cb13d7e89e 100644
--- a/database/factories/AccountFactory.php
+++ b/database/factories/AccountFactory.php
@@ -27,6 +27,7 @@ class AccountFactory extends Factory
'default_company_id' => 1,
'key' => Str::random(32),
'report_errors' => 1,
+ 'referral_code' => Str::lower(Str::random(32)),
];
}
}
diff --git a/database/migrations/2024_05_03_145535_btcpay_gateway.php b/database/migrations/2024_05_03_145535_btcpay_gateway.php
index 5f952ce4ff76..b618db1f5679 100644
--- a/database/migrations/2024_05_03_145535_btcpay_gateway.php
+++ b/database/migrations/2024_05_03_145535_btcpay_gateway.php
@@ -12,24 +12,28 @@ return new class extends Migration
*/
public function up(): void
{
- $gateway = new Gateway;
- $gateway->name = 'BTCPay';
- $gateway->key = 'vpyfbmdrkqcicpkjqdusgjfluebftuva';
- $gateway->provider = 'BTCPay';
- $gateway->is_offsite = true;
+ if(!Gateway::find(62))
+ {
+ $gateway = new Gateway;
+ $gateway->id = 62;
+ $gateway->name = 'BTCPay';
+ $gateway->key = 'vpyfbmdrkqcicpkjqdusgjfluebftuva';
+ $gateway->provider = 'BTCPay';
+ $gateway->is_offsite = true;
- $btcpayFieds = new \stdClass;
- $btcpayFieds->btcpayUrl = "";
- $btcpayFieds->apiKey = "";
- $btcpayFieds->storeId = "";
- $btcpayFieds->webhookSecret = "";
- $gateway->fields = \json_encode($btcpayFieds);
+ $btcpayFieds = new \stdClass;
+ $btcpayFieds->btcpayUrl = "";
+ $btcpayFieds->apiKey = "";
+ $btcpayFieds->storeId = "";
+ $btcpayFieds->webhookSecret = "";
+ $gateway->fields = \json_encode($btcpayFieds);
- $gateway->visible = true;
- $gateway->site_url = 'https://btcpayserver.org';
- $gateway->default_gateway_type_id = GatewayType::CRYPTO;
- $gateway->save();
+ $gateway->visible = true;
+ $gateway->site_url = 'https://btcpayserver.org';
+ $gateway->default_gateway_type_id = GatewayType::CRYPTO;
+ $gateway->save();
+ }
}
/**
@@ -39,4 +43,4 @@ return new class extends Migration
{
//
}
-};
+};
\ No newline at end of file
diff --git a/database/migrations/2024_05_19_215103_2024_05_20_einvoice_columns.php b/database/migrations/2024_05_19_215103_2024_05_20_einvoice_columns.php
new file mode 100644
index 000000000000..5fca9a9feb95
--- /dev/null
+++ b/database/migrations/2024_05_19_215103_2024_05_20_einvoice_columns.php
@@ -0,0 +1,53 @@
+mediumText('e_invoice')->nullable();
+ });
+
+ Schema::table('invoices', function (Blueprint $table) {
+ $table->mediumText('e_invoice')->nullable();
+ });
+
+ Schema::table('quotes', function (Blueprint $table) {
+ $table->mediumText('e_invoice')->nullable();
+ });
+
+ Schema::table('credits', function (Blueprint $table) {
+ $table->mediumText('e_invoice')->nullable();
+ });
+
+ Schema::table('purchase_orders', function (Blueprint $table) {
+ $table->mediumText('e_invoice')->nullable();
+ });
+
+ Schema::table('expenses', function (Blueprint $table) {
+ $table->mediumText('e_invoice')->nullable();
+ });
+
+
+ Schema::table('accounts', function (Blueprint $table) {
+ $table->integer('email_quota')->default(20)->nullable();
+ });
+
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ //
+ }
+};
diff --git a/database/seeders/RandomDataSeeder.php b/database/seeders/RandomDataSeeder.php
index 2e08f0171fec..b747764d0113 100644
--- a/database/seeders/RandomDataSeeder.php
+++ b/database/seeders/RandomDataSeeder.php
@@ -252,7 +252,7 @@ class RandomDataSeeder extends Seeder
$invoice->service()->createInvitations()->markSent()->save();
- $invoice->ledger()->updateInvoiceBalance($invoice->balance);
+ $invoice->ledger()->update_invoiceBalance($invoice->balance);
if (rand(0, 1)) {
$payment = Payment::create([
@@ -277,7 +277,7 @@ class RandomDataSeeder extends Seeder
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
- // $payment->service()->updateInvoicePayment($payment_hash);
+ // $payment->service()->update_invoicePayment($payment_hash);
}
});
diff --git a/lang/en/texts.php b/lang/en/texts.php
index 63654d484928..ed11493f87b1 100644
--- a/lang/en/texts.php
+++ b/lang/en/texts.php
@@ -5320,6 +5320,11 @@ $lang = array(
'task_round_to_nearest' => 'Round To Nearest',
'bulk_updated' => 'Successfully updated data',
'bulk_update' => 'Bulk Update',
+ 'calculate' => 'Calculate',
+ 'sum' => 'Sum',
+ 'money' => 'Money',
+ 'web_app' => 'Web App',
+ 'desktop_app' => 'Desktop App',
);
return $lang;
diff --git a/lang/fr_CA/texts.php b/lang/fr_CA/texts.php
index adecee4bb1b7..ee826a75d2c0 100644
--- a/lang/fr_CA/texts.php
+++ b/lang/fr_CA/texts.php
@@ -5297,6 +5297,26 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'local_domain_help' => 'Domaine EHLO (facultatif)',
'port_help' => 'ex. 25,587,465',
'host_help' => 'ex. smtp.gmail.com',
+ 'always_show_required_fields' => 'Permet l\'affichage des champs requis d\'un formulaire',
+ 'always_show_required_fields_help' => 'Affiche toujours les champs requis d\'un formulaire au paiement',
+ 'advanced_cards' => 'Cartes avancées',
+ 'activity_140' => 'État de compte envoyé à :client',
+ 'invoice_net_amount' => 'Montant net de la facture',
+ 'round_to_minutes' => 'Arrondir aux minutes',
+ '1_minute' => '1 minute',
+ '5_minutes' => '5 minutes',
+ '15_minutes' => '15 minutes',
+ '30_minutes' => '30 minutes',
+ '1_hour' => '1 heure',
+ '1_day' => '1 jour',
+ 'round_tasks' => 'Arrondir les tâches',
+ 'round_tasks_help' => 'Arrondir les intervales à la sauvegarde des tâches',
+ 'direction' => 'Direction',
+ 'round_up' => 'Arrondir à hausse',
+ 'round_down' => 'Arrondir à la baisse',
+ 'task_round_to_nearest' => 'Arrondir au plus près',
+ 'bulk_updated' => 'Les données ont été mises à jour',
+ 'bulk_update' => 'Mise à jour groupée',
);
return $lang;
diff --git a/lang/ja/texts.php b/lang/ja/texts.php
index 9f046c23ceb4..dfec27c63c5c 100644
--- a/lang/ja/texts.php
+++ b/lang/ja/texts.php
@@ -700,7 +700,7 @@ $lang = array(
'total_invoiced' => 'Total Invoiced',
'open_balance' => 'Open Balance',
'verify_email' => 'Please visit the link in the account confirmation email to verify your email address.',
- 'basic_settings' => 'Basic Settings',
+ 'basic_settings' => '基本設定',
'pro' => 'Pro',
'gateways' => 'ペイメントゲートウェイ',
'next_send_on' => 'Send Next: :date',
@@ -1344,7 +1344,7 @@ $lang = array(
'auto_bill_payment_method_credit_card' => 'クレジットカード',
'auto_bill_payment_method_paypal' => 'PayPal アカウント',
'auto_bill_notification_placeholder' => 'この請求書は、期日にて登録されているクレジットカードに自動的に請求されます。',
- 'payment_settings' => 'Payment Settings',
+ 'payment_settings' => '支払い設定',
'on_send_date' => '発送日',
'on_due_date' => '支払期日',
@@ -5300,6 +5300,26 @@ $lang = array(
'local_domain_help' => 'EHLO domain (optional)',
'port_help' => 'ie. 25,587,465',
'host_help' => 'ie. smtp.gmail.com',
+ 'always_show_required_fields' => 'Allows show required fields form',
+ 'always_show_required_fields_help' => 'Displays the required fields form always at checkout',
+ 'advanced_cards' => 'Advanced Cards',
+ 'activity_140' => 'Statement sent to :client',
+ 'invoice_net_amount' => 'Invoice Net Amount',
+ 'round_to_minutes' => 'Round To Minutes',
+ '1_minute' => '1 Minute',
+ '5_minutes' => '5 Minutes',
+ '15_minutes' => '15 Minutes',
+ '30_minutes' => '30 Minutes',
+ '1_hour' => '1 Hour',
+ '1_day' => '1 Day',
+ 'round_tasks' => 'Round Tasks',
+ 'round_tasks_help' => 'Round time intervals when saving tasks',
+ 'direction' => 'Direction',
+ 'round_up' => 'Round Up',
+ 'round_down' => 'Round Down',
+ 'task_round_to_nearest' => 'Round To Nearest',
+ 'bulk_updated' => 'Successfully updated data',
+ 'bulk_update' => 'Bulk Update',
);
return $lang;
diff --git a/public/build/assets/app-bfac6a32.js b/public/build/assets/app-a52d5f77.js
similarity index 96%
rename from public/build/assets/app-bfac6a32.js
rename to public/build/assets/app-a52d5f77.js
index d1ce4cff3984..ce2f066a6600 100644
--- a/public/build/assets/app-bfac6a32.js
+++ b/public/build/assets/app-a52d5f77.js
@@ -83,7 +83,7 @@ Would you like to refresh the page?`)&&window.location.reload()}function ec(e){X
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
- `;let r=Du();r&&(e.nonce=r),document.head.appendChild(e)}var ea=[],ss=["data-csrf","aria-hidden"];function Eo(e,r){let n=new DOMParser().parseFromString(e,"text/html"),a=document.adoptNode(n.body),s=document.adoptNode(n.head);ea=ea.concat(Array.from(document.body.querySelectorAll("script")).map(m=>fs(ds(m.outerHTML,ss))));let l=()=>{};Bc(s).finally(()=>{l()}),Fc(a,ea);let v=document.body;document.body.replaceWith(a),Alpine.destroyTree(v),r(m=>l=m)}function Fc(e,r){e.querySelectorAll("script").forEach(n=>{if(n.hasAttribute("data-navigate-once")){let a=fs(ds(n.outerHTML,ss));if(r.includes(a))return}n.replaceWith(ls(n))})}function Bc(e){let r=Array.from(document.head.children),n=r.map(l=>l.outerHTML),a=document.createDocumentFragment(),s=[];for(let l of Array.from(e.children))if(Co(l))if(n.includes(l.outerHTML))a.appendChild(l);else if(us(l)&&Hc(l,r)&&setTimeout(()=>window.location.reload()),cs(l))try{s.push(Uc(ls(l)))}catch{}else document.head.appendChild(l);for(let l of Array.from(document.head.children))Co(l)||l.remove();for(let l of Array.from(e.children))document.head.appendChild(l);return Promise.all(s)}async function Uc(e){return new Promise((r,n)=>{e.src?(e.onload=()=>r(),e.onerror=()=>n()):r(),document.head.appendChild(e)})}function ls(e){let r=document.createElement("script");r.textContent=e.textContent,r.async=e.async;for(let n of e.attributes)r.setAttribute(n.name,n.value);return r}function us(e){return e.hasAttribute("data-navigate-track")}function Hc(e,r){let[n,a]=Oo(e);return r.some(s=>{if(!us(s))return!1;let[l,v]=Oo(s);if(l===n&&a!==v)return!0})}function Oo(e){return(cs(e)?e.src:e.href).split("?")}function Co(e){return e.tagName.toLowerCase()==="link"&&e.getAttribute("rel").toLowerCase()==="stylesheet"||e.tagName.toLowerCase()==="style"||e.tagName.toLowerCase()==="script"}function cs(e){return e.tagName.toLowerCase()==="script"}function fs(e){return e.split("").reduce((r,n)=>(r=(r<<5)-r+n.charCodeAt(0),r&r),0)}function ds(e,r){let n=e;return r.forEach(a=>{const s=new RegExp(`${a}="[^"]*"|${a}='[^']*'`,"g");n=n.replace(s,"")}),n=n.replaceAll(" ",""),n.trim()}var ta=!0;function qc(e){e.navigate=n=>{let a=Yr(n);Yt("alpine:navigate",{url:a,history:!1,cached:!1})||r(a)},e.navigate.disableProgressBar=()=>{ta=!1},e.addInitSelector(()=>`[${e.prefixed("navigate")}]`),e.directive("navigate",(n,{modifiers:a})=>{a.includes("hover")&&Rc(n,60,()=>{let l=ho(n);go(l,(v,m)=>{mo(v,l,m)})}),Pc(n,l=>{let v=ho(n);go(v,(m,R)=>{mo(m,v,R)}),l(()=>{Yt("alpine:navigate",{url:v,history:!1,cached:!1})||r(v)})})});function r(n,a=!0){ta&&jc(),Vc(n,(s,l)=>{Yt("alpine:navigating"),_o(),ta&&Ic(),zc(),Ec(),Ao(e,v=>{xo(m=>{vo(m)}),a?Ac(s,l):is(l,s),Eo(s,m=>{yo(document.body),So((R,B)=>{bo(R)}),wo(),m(()=>{v(()=>{setTimeout(()=>{}),To(e),Yt("alpine:navigated")})})})})})}Cc(n=>{n(a=>{let s=Yr(a);if(Yt("alpine:navigate",{url:s,history:!0,cached:!1}))return;r(s,!1)})},(n,a,s,l)=>{let v=Yr(a);Yt("alpine:navigate",{url:v,history:!0,cached:!0})||(_o(),Yt("alpine:navigating"),Oc(s,l),Ao(e,R=>{xo(B=>{vo(B)}),Eo(n,()=>{Dc(),yo(document.body),So((B,ue)=>{bo(B)}),wo(),R(()=>{To(e),Yt("alpine:navigated")})})}))}),setTimeout(()=>{Yt("alpine:navigated")})}function Vc(e,r){Mc(e,r,()=>{kc(e,r)})}function Ao(e,r){e.stopObservingMutations(),r(n=>{e.startObservingMutations(),queueMicrotask(()=>{n()})})}function Yt(e,r){let n=new CustomEvent(e,{cancelable:!0,bubbles:!0,detail:r});return document.dispatchEvent(n),n.defaultPrevented}function To(e){e.initTree(document.body,void 0,(r,n)=>{r._x_wasPersisted&&n()})}function zc(){let e=function(r,n){Alpine.walk(r,(a,s)=>{Lc(a)&&s(),Nc(a)?s():n(a,s)})};Alpine.destroyTree(document.body,e)}function Wc(e){e.magic("queryString",(r,{interceptor:n})=>{let a,s=!1,l=!1;return n((v,m,R,B,ue)=>{let ne=a||B,{initial:z,replace:Y,push:S,pop:y}=ua(ne,v,s);return R(z),l?(e.effect(()=>S(m())),y(async _=>{R(_),await(()=>Promise.resolve())()})):e.effect(()=>Y(m())),z},v=>{v.alwaysShow=()=>(s=!0,v),v.usePush=()=>(l=!0,v),v.as=m=>(a=m,v)})}),e.history={track:ua}}function ua(e,r,n=!1){let{has:a,get:s,set:l,remove:v}=Jc(),m=new URL(window.location.href),R=a(m,e),B=R?s(m,e):r,ue=JSON.stringify(B),ne=S=>JSON.stringify(S)===ue;n&&(m=l(m,e,B)),Po(m,e,{value:B});let z=!1,Y=(S,y)=>{if(z)return;let _=new URL(window.location.href);!n&&!R&&ne(y)||y===void 0?_=v(_,e):_=l(_,e,y),S(_,e,{value:y})};return{initial:B,replace(S){Y(Po,S)},push(S){Y(Kc,S)},pop(S){let y=_=>{!_.state||!_.state.alpine||Object.entries(_.state.alpine).forEach(([O,{value:P}])=>{if(O!==e)return;z=!0;let I=S(P);I instanceof Promise?I.finally(()=>z=!1):z=!1})};return window.addEventListener("popstate",y),()=>window.removeEventListener("popstate",y)}}}function Po(e,r,n){let a=window.history.state||{};a.alpine||(a.alpine={}),a.alpine[r]=Sa(n),window.history.replaceState(a,"",e.toString())}function Kc(e,r,n){let a=window.history.state||{};a.alpine||(a.alpine={}),a={alpine:{...a.alpine,[r]:Sa(n)}},window.history.pushState(a,"",e.toString())}function Sa(e){if(e!==void 0)return JSON.parse(JSON.stringify(e))}function Jc(){return{has(e,r){let n=e.search;if(!n)return!1;let a=ei(n);return Object.keys(a).includes(r)},get(e,r){let n=e.search;return n?ei(n)[r]:!1},set(e,r,n){let a=ei(e.search);return a[r]=ps(Sa(n)),e.search=Ro(a),e},remove(e,r){let n=ei(e.search);return delete n[r],e.search=Ro(n),e}}}function ps(e){if(!va(e))return e;for(let r in e)e[r]===null?delete e[r]:e[r]=ps(e[r]);return e}function Ro(e){let r=s=>typeof s=="object"&&s!==null,n=(s,l={},v="")=>(Object.entries(s).forEach(([m,R])=>{let B=v===""?m:`${v}[${m}]`;R===null?l[B]="":r(R)?l={...l,...n(R,l,B)}:l[B]=encodeURIComponent(R).replaceAll("%20","+").replaceAll("%2C",",")}),l),a=n(e);return Object.entries(a).map(([s,l])=>`${s}=${l}`).join("&")}function ei(e){if(e=e.replace("?",""),e==="")return{};let r=(s,l,v)=>{let[m,R,...B]=s.split(".");if(!R)return v[s]=l;v[m]===void 0&&(v[m]=isNaN(R)?{}:[]),r([R,...B].join("."),l,v[m])},n=e.split("&").map(s=>s.split("=")),a=Object.create(null);return n.forEach(([s,l])=>{if(!(typeof l>"u"))if(l=decodeURIComponent(l.replaceAll("+","%20")),!s.includes("["))a[s]=l;else{let v=s.replaceAll("[",".").replaceAll("]","");r(v,l,a)}}),a}var Gc=tt(Lu()),Yc=tt(ju()),Et=tt(Ot());function Xc(){setTimeout(()=>Qc()),Gi(document,"livewire:init"),Gi(document,"livewire:initializing"),Et.default.plugin(Gc.default),Et.default.plugin(Wc),Et.default.plugin(xc.default),Et.default.plugin(bc.default),Et.default.plugin(Sc.default),Et.default.plugin(_c.default),Et.default.plugin(wc.default),Et.default.plugin(qc),Et.default.plugin(Yc.default),Et.default.addRootSelector(()=>"[wire\\:id]"),Et.default.onAttributesAdded((e,r)=>{if(!Array.from(r).some(a=>_n(a.name)))return;let n=or(e,!1);n&&r.forEach(a=>{if(!_n(a.name))return;let s=aa(e,a.name);lt("directive.init",{el:e,component:n,directive:s,cleanup:l=>{Et.default.onAttributeRemoved(e,s.raw,l)}})})}),Et.default.interceptInit(Et.default.skipDuringClone(e=>{if(!Array.from(e.attributes).some(n=>_n(n.name)))return;if(e.hasAttribute("wire:id")){let n=oc(e);Et.default.onAttributeRemoved(e,"wire:id",()=>{sc(n.id)})}let r=or(e,!1);r&&(lt("element.init",{el:e,component:r}),Array.from(e.getAttributeNames()).filter(a=>_n(a)).map(a=>aa(e,a)).forEach(a=>{lt("directive.init",{el:e,component:r,directive:a,cleanup:s=>{Et.default.onAttributeRemoved(e,a.raw,s)}})}))})),Et.default.start(),setTimeout(()=>window.Livewire.initialRenderIsFinished=!0),Gi(document,"livewire:initialized")}function Qc(){let e=document.querySelector("script[data-update-uri][data-csrf]");if(!e)return;let r=e.closest("[wire\\:id]");r&&console.warn("Livewire: missing closing tags found. Ensure your template elements contain matching closing tags.",r)}var Ea=tt(Ot());ze("effect",({component:e,effects:r})=>{Zc(e,r.listeners||[])});function Zc(e,r){r.forEach(n=>{let a=s=>{s.__livewire&&s.__livewire.receivedBy.push(e),e.$wire.call("__dispatch",n,s.detail||{})};window.addEventListener(n,a),e.addCleanup(()=>window.removeEventListener(n,a)),e.el.addEventListener(n,s=>{s.__livewire&&(s.bubbles||(s.__livewire&&s.__livewire.receivedBy.push(e.id),e.$wire.call("__dispatch",n,s.detail||{})))})})}var ko=tt(Ot()),Vr=new WeakMap,ri=new Set;ze("payload.intercept",async({assets:e})=>{if(e)for(let[r,n]of Object.entries(e))await rf(r,async()=>{await nf(n)})});ze("component.init",({component:e})=>{let r=e.snapshot.memo.assets;r&&r.forEach(n=>{ri.has(n)||ri.add(n)})});ze("effect",({component:e,effects:r})=>{let n=r.scripts;n&&Object.entries(n).forEach(([a,s])=>{ef(e,a,()=>{let l=tf(s);ko.default.dontAutoEvaluateFunctions(()=>{ko.default.evaluate(e.el,l,{$wire:e.$wire})})})})});function ef(e,r,n){if(Vr.has(e)&&Vr.get(e).includes(r))return;n(),Vr.has(e)||Vr.set(e,[]);let a=Vr.get(e);a.push(r),Vr.set(e,a)}function tf(e){let n=/
+@if(isset($merchantId))
+
+@else
+
+@endif