mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 05:02:36 +01:00
extend paypal classes
This commit is contained in:
parent
91078eb6a1
commit
8dde7024fc
427
app/PaymentDrivers/PayPal/PayPalBasePaymentDriver.php
Normal file
427
app/PaymentDrivers/PayPal/PayPalBasePaymentDriver.php
Normal file
@ -0,0 +1,427 @@
|
||||
<?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)
|
||||
{
|
||||
|
||||
// nlog(json_encode($request->all()));
|
||||
$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->paypal_payment_method == 29)
|
||||
return render('gateways.paypal.ppcp.card', $data);
|
||||
else
|
||||
return render('gateways.paypal.ppcp.pay', $data);
|
||||
|
||||
}
|
||||
|
||||
@ -372,74 +221,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 +241,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 +253,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,
|
||||
@ -502,119 +289,66 @@ class PayPalPPCPPaymentDriver extends BaseDriver
|
||||
|
||||
}
|
||||
|
||||
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::EVENT_GATEWAY_SUCCESS,
|
||||
SystemLog::TYPE_PAYPAL,
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,6 @@
|
||||
namespace App\PaymentDrivers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Omnipay\Omnipay;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\GatewayType;
|
||||
@ -23,136 +22,15 @@ use App\Jobs\Util\SystemLogger;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\PaymentDrivers\PayPal\PayPalBasePaymentDriver;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
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 +47,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->paypal_payment_method == 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 +68,10 @@ return render('gateways.paypal.ppcp.card', $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'];
|
||||
|
||||
@ -367,8 +153,6 @@ return render('gateways.paypal.ppcp.card', $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.ppcp.card', $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.ppcp.card', $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,79 @@ return render('gateways.paypal.ppcp.card', $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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
@ -62,8 +75,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 +146,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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user