From 8dde7024fc0d9e64f6e890f253cd3102a3cdba48 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 20 May 2024 11:59:44 +1000 Subject: [PATCH] extend paypal classes --- .../PayPal/PayPalBasePaymentDriver.php | 427 +++++++++++++++ .../PayPalPPCPPaymentDriver.php | 412 +++----------- .../PayPalRestPaymentDriver.php | 505 +++--------------- .../gateways/paypal/ppcp/card.blade.php | 24 +- 4 files changed, 585 insertions(+), 783 deletions(-) create mode 100644 app/PaymentDrivers/PayPal/PayPalBasePaymentDriver.php diff --git a/app/PaymentDrivers/PayPal/PayPalBasePaymentDriver.php b/app/PaymentDrivers/PayPal/PayPalBasePaymentDriver.php new file mode 100644 index 000000000000..8eb75b043494 --- /dev/null +++ b/app/PaymentDrivers/PayPal/PayPalBasePaymentDriver.php @@ -0,0 +1,427 @@ + '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); + } + +} \ No newline at end of file diff --git a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php index 12758fa10171..0c099689e5bb 100644 --- a/app/PaymentDrivers/PayPalPPCPPaymentDriver.php +++ b/app/PaymentDrivers/PayPalPPCPPaymentDriver.php @@ -21,188 +21,34 @@ use Illuminate\Http\Request; use App\Jobs\Util\SystemLogger; use App\Utils\Traits\MakesHash; use App\Exceptions\PaymentFailed; +use App\Models\ClientGatewayToken; use Illuminate\Support\Facades\Http; use App\PaymentDrivers\PayPal\PayPalWebhook; +use App\PaymentDrivers\PayPal\PayPalBasePaymentDriver; -class PayPalPPCPPaymentDriver extends BaseDriver +class PayPalPPCPPaymentDriver extends PayPalBasePaymentDriver { use MakesHash; - - public $token_billing = false; - - public $can_authorise_credit_card = false; - - private $omnipay_gateway; - - private float $fee = 0; + +///v1/customer/partners/merchant-accounts/{merchant_id}/capabilities - test if advanced cards is available. +// { +// "capabilities": [ +// { +// "name": "ADVANCED_CARD_PAYMENTS", +// "status": "ENABLED" +// }, +// { +// "name": "VAULTING", +// "status": "ENABLED" +// } +// ] +// } public const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYPAL_PPCP; - - private string $api_endpoint_url = ''; - - private string $paypal_payment_method = ''; - - private ?int $gateway_type_id = null; - - protected mixed $access_token = null; - - protected ?Carbon $token_expiry = null; - - private array $funding_options = [ - 3 => 'paypal', - 1 => 'card', - 25 => 'venmo', - // 9 => 'sepa', - // 12 => 'bancontact', - // 17 => 'eps', - // 15 => 'giropay', - // 13 => 'ideal', - // 26 => 'mercadopago', - // 27 => 'mybank', - 28 => 'paylater', - // 16 => 'p24', - // 7 => 'sofort' - ]; - + /** - * Return an array of - * enabled gateway payment methods - * - * @return array - */ - public function gatewayTypes(): array - { - - return collect($this->company_gateway->fees_and_limits) - ->filter(function ($fee) { - return $fee->is_enabled; - })->map(function ($fee, $key) { - return (int)$key; - })->toArray(); - - } - - private function getPaymentMethod($gateway_type_id): int - { - $method = PaymentType::PAYPAL; - - match($gateway_type_id) { - "1" => $method = PaymentType::CREDIT_CARD_OTHER, - "3" => $method = PaymentType::PAYPAL, - "25" => $method = PaymentType::VENMO, - "28" => $method = PaymentType::PAY_LATER, - }; - - return $method; - } - - private function getFundingOptions(): string - { - - $enums = [ - 1 => 'card', - 3 => 'paypal', - 25 => 'venmo', - 28 => 'paylater', - // 9 => 'sepa', - // 12 => 'bancontact', - // 17 => 'eps', - // 15 => 'giropay', - // 13 => 'ideal', - // 26 => 'mercadopago', - // 27 => 'mybank', - // 28 => 'paylater', - // 16 => 'p24', - // 7 => 'sofort' - ]; - - $funding_options = ''; - - foreach($this->company_gateway->fees_and_limits as $key => $value) { - - if($value->is_enabled) { - - $funding_options .= $enums[$key].','; - - } - - } - - return rtrim($funding_options, ','); - - } - - /** - * Initialize the Paypal gateway. - * - * Attempt to generate and return the access token. - * - * @return self - */ - public function init(): self - { - - $this->api_endpoint_url = 'https://api-m.paypal.com'; - // $this->api_endpoint_url = 'https://api-m.sandbox.paypal.com'; - $secret = config('ninja.paypal.secret'); - $client_id = config('ninja.paypal.client_id'); - - if($this->access_token && $this->token_expiry && $this->token_expiry->isFuture()) { - return $this; - } - - $response = Http::withBasicAuth($client_id, $secret) - ->withHeaders(['Content-Type' => 'application/x-www-form-urlencoded']) - ->withQueryParameters(['grant_type' => 'client_credentials']) - ->post("{$this->api_endpoint_url}/v1/oauth2/token"); - - if($response->successful()) { - $this->access_token = $response->json()['access_token']; - $this->token_expiry = now()->addSeconds($response->json()['expires_in'] - 60); - } else { - throw new PaymentFailed('Unable to gain access token from Paypal. Check your configuration', 401); - } - - return $this; - - } - - /** - * Payment method setter - * - * @param mixed $payment_method_id - * @return self - */ - public function setPaymentMethod($payment_method_id): self - { - if(!$payment_method_id) { - return $this; - } - - $this->gateway_type_id = $payment_method_id; - - $this->paypal_payment_method = $this->funding_options[$payment_method_id]; - - return $this; - } - - public function authorizeView($payment_method) - { - // PayPal doesn't support direct authorization. - - return $this; - } - - public function authorizeResponse($request) - { - // PayPal doesn't support direct authorization. - - return $this; - } - - /** - * Checks whether payments are enabled on the account - * + * Checks whether payments are enabled on the merchant account + * * @return self */ private function checkPaymentsReceivable(): self @@ -252,7 +98,10 @@ class PayPalPPCPPaymentDriver extends BaseDriver $data['merchantId'] = $this->company_gateway->getConfigField('merchantId'); $data['currency'] = $this->client->currency()->code; - return render('gateways.paypal.ppcp.pay', $data); + if($this->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; - } } diff --git a/app/PaymentDrivers/PayPalRestPaymentDriver.php b/app/PaymentDrivers/PayPalRestPaymentDriver.php index 37689580651b..e250713f0c18 100644 --- a/app/PaymentDrivers/PayPalRestPaymentDriver.php +++ b/app/PaymentDrivers/PayPalRestPaymentDriver.php @@ -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; - } - } diff --git a/resources/views/portal/ninja2020/gateways/paypal/ppcp/card.blade.php b/resources/views/portal/ninja2020/gateways/paypal/ppcp/card.blade.php index d28545083011..df3a7b50f004 100644 --- a/resources/views/portal/ninja2020/gateways/paypal/ppcp/card.blade.php +++ b/resources/views/portal/ninja2020/gateways/paypal/ppcp/card.blade.php @@ -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 @@ - + @@ -62,8 +75,12 @@ @push('footer') - +@if(isset($merchantId)) + +@else + +@endif