mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-09 20:52:56 +01:00
Merge pull request #9535 from turbo124/v5-develop
Updates for BTC payment driver
This commit is contained in:
commit
643f8a64c3
@ -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.
|
||||
|
@ -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');
|
||||
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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)));
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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 = [];
|
||||
|
@ -155,6 +155,7 @@ class CompanyGateway extends BaseModel
|
||||
'b9886f9257f0c6ee7c302f1c74475f6c' => 321, //GoCardless
|
||||
'hxd6gwg3ekb9tb3v9lptgx1mqyg69zu9' => 322,
|
||||
'80af24a6a691230bbec33e930ab40666' => 323,
|
||||
'vpyfbmdrkqcicpkjqdusgjfluebftuva' => 324, //BTPay
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
|
@ -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',
|
||||
|
||||
];
|
||||
|
||||
|
@ -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 = [];
|
||||
|
@ -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 = [];
|
||||
|
@ -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=";
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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'],
|
||||
|
426
app/PaymentDrivers/PayPal/PayPalBasePaymentDriver.php
Normal file
426
app/PaymentDrivers/PayPal/PayPalBasePaymentDriver.php
Normal file
@ -0,0 +1,426 @@
|
||||
<?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\PayPal;
|
||||
|
||||
use Str;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentType;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\PaymentDrivers\BaseDriver;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\PaymentDrivers\PayPal\PayPalWebhook;
|
||||
|
||||
class PayPalBasePaymentDriver extends BaseDriver
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
public $token_billing = true;
|
||||
|
||||
public $can_authorise_credit_card = false;
|
||||
|
||||
public float $fee = 0;
|
||||
|
||||
public const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYPAL;
|
||||
|
||||
public string $api_endpoint_url = '';
|
||||
|
||||
public string $paypal_payment_method = '';
|
||||
|
||||
public ?int $gateway_type_id = null;
|
||||
|
||||
public mixed $access_token = null;
|
||||
|
||||
public ?Carbon $token_expiry = null;
|
||||
|
||||
public 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 =
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
|
||||
];
|
||||
}
|
||||
|
@ -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(),
|
||||
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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') {
|
||||
|
@ -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(),
|
||||
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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')];
|
||||
|
44
composer.lock
generated
44
composer.lock
generated
@ -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",
|
||||
|
@ -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)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
||||
};
|
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->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
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -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);
|
||||
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
File diff suppressed because one or more lines are too long
@ -9,7 +9,7 @@
|
||||
]
|
||||
},
|
||||
"resources/js/app.js": {
|
||||
"file": "assets/app-bfac6a32.js",
|
||||
"file": "assets/app-a52d5f77.js",
|
||||
"imports": [
|
||||
"_index-08e160a7.js",
|
||||
"__commonjsHelpers-725317a4.js"
|
||||
|
@ -171,15 +171,10 @@ class SquareCreditCard {
|
||||
document.querySelector('input[name=token]').value = '';
|
||||
});
|
||||
|
||||
// let toggleWithToken = document.querySelector(
|
||||
// '.toggle-payment-with-token'
|
||||
// );
|
||||
|
||||
// if (!toggleWithToken) {
|
||||
document.getElementById('loader').classList.add('hidden');
|
||||
document.getElementById('payment-list').classList.remove('hidden');
|
||||
document.getElementById('toggle-payment-with-credit-card')?.click();
|
||||
// }
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,18 @@
|
||||
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title' => ''])
|
||||
@php
|
||||
$gateway_instance = $gateway instanceof \App\Models\CompanyGateway ? $gateway : $gateway->company_gateway;
|
||||
$token_billing_string = 'true';
|
||||
|
||||
if($gateway_instance->token_billing == 'off' || $gateway_instance->token_billing == 'optin'){
|
||||
$token_billing_string = 'false';
|
||||
}
|
||||
|
||||
if (isset($pre_payment) && $pre_payment == '1' && isset($is_recurring) && $is_recurring == '1') {
|
||||
$token_billing_string = 'true';
|
||||
}
|
||||
|
||||
|
||||
@endphp
|
||||
@section('gateway_head')
|
||||
|
||||
@endsection
|
||||
@ -12,7 +25,7 @@
|
||||
<input type="hidden" name="gateway_type_id" id="gateway_type_id" value="{{ $gateway_type_id }}">
|
||||
<input type="hidden" name="gateway_response" id="gateway_response">
|
||||
<input type="hidden" name="amount_with_fee" id="amount_with_fee" value="{{ $total['amount_with_fee'] }}"/>
|
||||
<input type="hidden" name="store_card" id="store_card">
|
||||
<input type="hidden" name="store_card" id="store_card" value="{{ $token_billing_string }}">
|
||||
<input type="hidden" name="token" value="" id="token">
|
||||
</form>
|
||||
|
||||
@ -44,6 +57,7 @@
|
||||
<div id="checkout-form">
|
||||
<!-- Containers for Card Fields hosted by PayPal -->
|
||||
<div id="card-number-field-container"></div>
|
||||
<div id="card-name-field-container"></div>
|
||||
<div class="expcvv" style="display:flex;">
|
||||
<div id="card-expiry-field-container" style="width:50%"></div>
|
||||
<div id="card-cvv-field-container" style="width:50%"></div>
|
||||
@ -62,8 +76,12 @@
|
||||
|
||||
@push('footer')
|
||||
<link rel="stylesheet" type="text/css" href=https://www.paypalobjects.com/webstatic/en_US/developer/docs/css/cardfields.css />
|
||||
<script src="https://www.paypal.com/sdk/js?client-id={!! $client_id !!}&components=card-fields" data-partner-attribution-id="invoiceninja_SP_PPCP"></script>
|
||||
|
||||
@if(isset($merchantId))
|
||||
<script src="https://www.paypal.com/sdk/js?client-id={!! $client_id !!}&merchantId={!! $merchantId !!}&components=card-fields" data-partner-attribution-id="invoiceninja_SP_PPCP"></script>
|
||||
@else
|
||||
<script src="https://www.paypal.com/sdk/js?client-id={!! $client_id !!}&components=card-fields" data-partner-attribution-id="invoiceninja_SP_PPCP"></script>
|
||||
@endif
|
||||
<script>
|
||||
|
||||
const clientId = "{{ $client_id }}";
|
||||
@ -129,9 +147,6 @@
|
||||
},
|
||||
onError: function(error) {
|
||||
|
||||
// console.log("on error")
|
||||
// console.log(error);
|
||||
|
||||
document.getElementById('errors').textContent = `Sorry, your transaction could not be processed...\n\n${error.message}`;
|
||||
document.getElementById('errors').hidden = false;
|
||||
|
||||
@ -144,6 +159,9 @@
|
||||
|
||||
// Render each field after checking for eligibility
|
||||
if (cardField.isEligible()) {
|
||||
|
||||
const nameField = cardField.NameField();
|
||||
nameField.render("#card-name-field-container");
|
||||
|
||||
const numberField = cardField.NumberField({
|
||||
inputEvents: {
|
||||
@ -152,6 +170,7 @@
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
numberField.render("#card-number-field-container");
|
||||
|
||||
const cvvField = cardField.CVVField({
|
||||
|
@ -12,6 +12,8 @@
|
||||
namespace Tests\Integration\Einvoice;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\Service;
|
||||
use Invoiceninja\Einvoice\Models\FACT1\Invoice;
|
||||
|
||||
/**
|
||||
@ -20,54 +22,182 @@ use Invoiceninja\Einvoice\Models\FACT1\Invoice;
|
||||
class FACT1Test extends TestCase
|
||||
{
|
||||
|
||||
public array $set = [];
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
|
||||
public function testValidationFact1()
|
||||
{
|
||||
|
||||
$files = [
|
||||
'tests/Integration/Einvoice/samples/fact1.xml',
|
||||
'tests/Integration/Einvoice/samples/fact1_no_prefixes.xml',
|
||||
];
|
||||
|
||||
foreach($files as $f) {
|
||||
|
||||
$xmlstring = file_get_contents($f);
|
||||
$xml = file_get_contents($f);
|
||||
|
||||
// nlog($xmlstring);
|
||||
|
||||
$xml = simplexml_load_string($xmlstring, "SimpleXMLElement");
|
||||
$xml = simplexml_load_string($xml, "SimpleXMLElement", LIBXML_NOCDATA);
|
||||
$json = json_encode($xml);
|
||||
$payload = json_decode($json, true);
|
||||
$array = json_decode($json, true);
|
||||
|
||||
nlog($xml);
|
||||
nlog($payload);
|
||||
$validation_array = false;
|
||||
try {
|
||||
$rules = Invoice::getValidationRules($payload);
|
||||
nlog($rules);
|
||||
$i = Invoice::from($array);
|
||||
|
||||
$rules = Invoice::getValidationRules($array);
|
||||
|
||||
$this->assertIsArray($rules);
|
||||
$this->assertIsArray($rules);
|
||||
|
||||
$payload = Invoice::from($payload)->toArray();
|
||||
nlog($payload);
|
||||
$this->assertIsArray($payload);
|
||||
|
||||
$validation_array = Invoice::validate($payload);
|
||||
|
||||
$this->assertIsArray($validation_array);
|
||||
|
||||
} catch(\Illuminate\Validation\ValidationException $e) {
|
||||
|
||||
nlog($e->errors());
|
||||
}
|
||||
$validation_array = Invoice::validate($array);
|
||||
|
||||
$this->assertIsArray($validation_array);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function removeNamespacesFromArray($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
foreach ($data as &$item) {
|
||||
if (isset($item['name'])) {
|
||||
// Remove the namespace from the name
|
||||
$item['name'] = preg_replace('/^\{\}(.+)/', '$1', $item['name']);
|
||||
}
|
||||
if (isset($item['value']) && is_array($item['value'])) {
|
||||
// Recursively process child elements
|
||||
$item['value'] = $this->removeNamespacesFromArray($item['value']);
|
||||
}
|
||||
if (isset($item['attributes'])) {
|
||||
unset($item['attributes']);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// function convertToKeyValue($data)
|
||||
// {
|
||||
// $result = [];
|
||||
// foreach ($data as $item) {
|
||||
// // Remove namespace prefix if present
|
||||
// $name = preg_replace('/^\{\}(.+)/', '$1', $item['name']);
|
||||
// $result[$name] = $item['value'];
|
||||
// }
|
||||
// return $result;
|
||||
// }
|
||||
|
||||
|
||||
// public function keyValueDeserializer(Reader $reader)
|
||||
// {
|
||||
// $values = [];
|
||||
// $reader->read();
|
||||
// $reader->next();
|
||||
// foreach ($reader->parseGetElements() as $element) {
|
||||
// // Strip the namespace prefix
|
||||
// echo "merp".PHP_EOL;
|
||||
// $name = preg_replace('/^\{\}.*/', '', $element['name']);
|
||||
// $values[$name] = $element['value'];
|
||||
// }
|
||||
// return $values;
|
||||
// }
|
||||
|
||||
|
||||
// public function testFactToArray()
|
||||
// {
|
||||
|
||||
// $xml = file_get_contents('tests/Integration/Einvoice/samples/fact1_no_prefixes.xml');
|
||||
// $service = new Service();
|
||||
|
||||
// // $service->elementMap = [
|
||||
// // '{}' => 'Sabre\Xml\Deserializer\keyValue',
|
||||
// // ];
|
||||
|
||||
// // $service->elementMap = [
|
||||
// // '{}*' => function (Reader $reader) use ($service) {
|
||||
// // return $this->keyValueDeserializer($reader);
|
||||
// // }
|
||||
// // ];
|
||||
|
||||
|
||||
// $result = $this->removeNamespacesFromArray($service->parse($xml));
|
||||
|
||||
|
||||
// // Convert parsed XML to key-value array
|
||||
// if (isset($result['value']) && is_array($result['value'])) {
|
||||
// $keyValueArray = $this->convertToKeyValue($result['value']);
|
||||
// } else {
|
||||
// $keyValueArray = [];
|
||||
// }
|
||||
|
||||
// // Output the result
|
||||
// nlog($keyValueArray);
|
||||
|
||||
|
||||
// // nlog($cleanedArray);
|
||||
// nlog($service->parse($xml));
|
||||
|
||||
// }
|
||||
|
||||
// Output the result
|
||||
// ($xmlWithoutNamespaces);
|
||||
|
||||
// $reader = new Reader();
|
||||
// $service = new Service();
|
||||
|
||||
// $service->elementMap = [
|
||||
// '*' => 'Sabre\Xml\Deserializer\keyValue',
|
||||
// ];
|
||||
|
||||
// nlog($service->parse($xmlstring));
|
||||
|
||||
// $payload ='';
|
||||
|
||||
// // $reader->xml($xmlstring);
|
||||
// // $payload = $reader->parse();
|
||||
|
||||
// // nlog($payload);
|
||||
// $validation_array = false;
|
||||
// try {
|
||||
// $rules = Invoice::getValidationRules($payload);
|
||||
// nlog($rules);
|
||||
|
||||
// $this->assertIsArray($rules);
|
||||
|
||||
// $payload = Invoice::from($payload)->toArray();
|
||||
// nlog($payload);
|
||||
// $this->assertIsArray($payload);
|
||||
|
||||
// $validation_array = Invoice::validate($payload);
|
||||
|
||||
// $this->assertIsArray($validation_array);
|
||||
|
||||
// } catch(\Illuminate\Validation\ValidationException $e) {
|
||||
|
||||
// nlog($e->errors());
|
||||
// }
|
||||
|
||||
// $this->assertIsArray($validation_array);
|
||||
|
||||
|
||||
// }
|
||||
|
||||
// private function extractName($name): string
|
||||
// {
|
||||
|
||||
// $pattern = '/\{[^{}]*\}([^{}]*)/';
|
||||
|
||||
// if (preg_match($pattern, $name, $matches)) {
|
||||
// $extracted = $matches[1];
|
||||
// return $extracted;
|
||||
// }
|
||||
|
||||
// return $name;
|
||||
// }
|
||||
}
|
File diff suppressed because one or more lines are too long
113
tests/Integration/Einvoice/samples/fact1_no_prefixes.xml
Normal file
113
tests/Integration/Einvoice/samples/fact1_no_prefixes.xml
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0"?>
|
||||
<Invoice>
|
||||
<UBLVersionID>2.1</UBLVersionID>
|
||||
<CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:efactura.mfinante.ro:CIUS-RO:1.0.1</CustomizationID>
|
||||
<ID>ABC 0020</ID>
|
||||
<IssueDate>2024-01-01</IssueDate>
|
||||
<DueDate>2024-01-15</DueDate>
|
||||
<InvoiceTypeCode>384</InvoiceTypeCode>
|
||||
<DocumentCurrencyCode>RON</DocumentCurrencyCode>
|
||||
<TaxCurrencyCode>RON</TaxCurrencyCode>
|
||||
<AccountingSupplierParty>
|
||||
<Party>
|
||||
<PartyIdentification>
|
||||
<ID>234234234</ID>
|
||||
</PartyIdentification>
|
||||
<PostalAddress>
|
||||
<StreetName>This can be the full address , not just the street and street nr.</StreetName>
|
||||
<CityName>SECTOR2</CityName>
|
||||
<CountrySubentity>RO-B</CountrySubentity>
|
||||
<Country>
|
||||
<IdentificationCode>RO</IdentificationCode>
|
||||
</Country>
|
||||
</PostalAddress>
|
||||
<PartyTaxScheme>
|
||||
<CompanyID>RO234234234</CompanyID>
|
||||
<TaxScheme>
|
||||
<ID>VAT</ID>
|
||||
</TaxScheme>
|
||||
</PartyTaxScheme>
|
||||
<PartyLegalEntity>
|
||||
<RegistrationName>Some Copany Name</RegistrationName>
|
||||
<CompanyID>J40/2222/2009</CompanyID>
|
||||
</PartyLegalEntity>
|
||||
<Contact>
|
||||
<Name>Someone</Name>
|
||||
<Telephone>88282819832</Telephone>
|
||||
<ElectronicMail>some@email.com</ElectronicMail>
|
||||
</Contact>
|
||||
</Party>
|
||||
</AccountingSupplierParty>
|
||||
<AccountingCustomerParty>
|
||||
<Party>
|
||||
<PartyIdentification>
|
||||
<ID>646546549</ID>
|
||||
</PartyIdentification>
|
||||
<PostalAddress>
|
||||
<StreetName>This can be the full address , not just the street and street nr.</StreetName>
|
||||
<CityName>SECTOR3</CityName>
|
||||
<CountrySubentity>RO-B</CountrySubentity>
|
||||
<Country>
|
||||
<IdentificationCode>RO</IdentificationCode>
|
||||
</Country>
|
||||
</PostalAddress>
|
||||
<PartyLegalEntity>
|
||||
<RegistrationName>Some Comapny</RegistrationName>
|
||||
<CompanyID>646546549</CompanyID>
|
||||
</PartyLegalEntity>
|
||||
<Contact>
|
||||
<Name>Someone</Name>
|
||||
<Telephone>88282819832</Telephone>
|
||||
<ElectronicMail>some@email.com</ElectronicMail>
|
||||
</Contact>
|
||||
</Party>
|
||||
</AccountingCustomerParty>
|
||||
<PaymentMeans>
|
||||
<PaymentMeansCode>42</PaymentMeansCode>
|
||||
<PayeeFinancialAccount>
|
||||
<ID>some account nr</ID>
|
||||
<Name>Bank name</Name>
|
||||
</PayeeFinancialAccount>
|
||||
</PaymentMeans>
|
||||
<TaxTotal>
|
||||
<TaxAmount currencyID="RON">63.65</TaxAmount>
|
||||
<TaxSubtotal>
|
||||
<TaxableAmount currencyID="RON">335.00</TaxableAmount>
|
||||
<TaxAmount currencyID="RON">63.65</TaxAmount>
|
||||
<TaxCategory>
|
||||
<ID>S</ID> // this is a speciffic identifier for the VAT type <Percent>
|
||||
19</Percent>
|
||||
<TaxScheme>
|
||||
<ID>VAT</ID>
|
||||
</TaxScheme>
|
||||
</TaxCategory>
|
||||
</TaxSubtotal>
|
||||
</TaxTotal>
|
||||
<LegalMonetaryTotal>
|
||||
<LineExtensionAmount currencyID="RON">335.00</LineExtensionAmount>
|
||||
<TaxExclusiveAmount currencyID="RON">335.00</TaxExclusiveAmount>
|
||||
<TaxInclusiveAmount currencyID="RON">398.65</TaxInclusiveAmount>
|
||||
<AllowanceTotalAmount currencyID="RON">0.00</AllowanceTotalAmount>
|
||||
<PayableAmount currencyID="RON">398.65</PayableAmount>
|
||||
</LegalMonetaryTotal>
|
||||
<InvoiceLine>
|
||||
<ID>1</ID>
|
||||
<InvoicedQuantity unitCode="H87">1</InvoicedQuantity> // unitcode is a speciffic
|
||||
identifier for the type of product <LineExtensionAmount currencyID="RON">
|
||||
335.00</LineExtensionAmount>
|
||||
<Item>
|
||||
<Description>Some Description</Description>
|
||||
<Name>Some product</Name>
|
||||
<ClassifiedTaxCategory>
|
||||
<ID>S</ID> // this is a speciffic identifier for the VAT type <Percent>
|
||||
19</Percent>
|
||||
<TaxScheme>
|
||||
<ID>VAT</ID>
|
||||
</TaxScheme>
|
||||
</ClassifiedTaxCategory>
|
||||
</Item>
|
||||
<Price>
|
||||
<PriceAmount currencyID="RON">335</PriceAmount>
|
||||
</Price>
|
||||
</InvoiceLine>
|
||||
</Invoice>
|
Loading…
Reference in New Issue
Block a user