1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-14 23:22:52 +01:00
invoiceninja/app/PaymentDrivers/PayPalRestPaymentDriver.php

438 lines
14 KiB
PHP
Raw Normal View History

2023-06-19 06:25:42 +02:00
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
2024-04-12 06:15:41 +02:00
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
2023-06-19 06:25:42 +02:00
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers;
2023-10-26 04:57:44 +02:00
use App\Models\Invoice;
use App\Models\SystemLog;
use App\Models\GatewayType;
2024-03-19 02:17:25 +01:00
use Illuminate\Support\Str;
use App\Jobs\Util\SystemLogger;
2023-06-19 06:25:42 +02:00
use App\Utils\Traits\MakesHash;
use App\Exceptions\PaymentFailed;
2024-04-22 14:37:34 +02:00
use App\Models\ClientGatewayToken;
2024-06-13 08:52:23 +02:00
use App\Models\PaymentHash;
2024-05-20 03:59:44 +02:00
use App\PaymentDrivers\PayPal\PayPalBasePaymentDriver;
2023-06-19 06:25:42 +02:00
2024-05-20 03:59:44 +02:00
class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
2023-06-19 06:25:42 +02:00
{
use MakesHash;
public const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYPAL;
public function processPaymentView($data)
{
2024-06-14 09:09:44 +02:00
2023-06-21 11:22:20 +02:00
$this->init();
2023-06-19 06:25:42 +02:00
$data['gateway'] = $this;
2024-01-14 05:05:00 +01:00
2023-06-19 06:25:42 +02:00
$this->payment_hash->data = array_merge((array) $this->payment_hash->data, ['amount' => $data['total']['amount_with_fee']]);
$this->payment_hash->save();
2023-06-21 11:22:20 +02:00
$data['client_id'] = $this->company_gateway->getConfigField('clientId');
$data['token'] = $this->getClientToken();
$data['order_id'] = $this->createOrder($data);
$data['funding_source'] = $this->paypal_payment_method;
$data['gateway_type_id'] = $this->gateway_type_id;
$data['currency'] = $this->client->currency()->code;
2024-06-13 08:52:23 +02:00
$data['guid'] = $this->risk_guid;
2024-06-13 11:59:29 +02:00
$data['identifier'] = "s:INN_ACDC_CHCK";
$data['pp_client_reference'] = $this->getClientHash();
2024-06-14 09:09:44 +02:00
if($this->gateway_type_id == 29) {
2024-05-20 03:59:44 +02:00
return render('gateways.paypal.ppcp.card', $data);
2024-06-14 09:09:44 +02:00
} else {
2024-05-20 03:59:44 +02:00
return render('gateways.paypal.pay', $data);
2024-06-14 09:09:44 +02:00
}
2024-04-22 06:38:29 +02:00
}
2024-06-14 09:09:44 +02:00
2024-05-20 03:59:44 +02:00
/**
* processPaymentResponse
*
* @param mixed $request
*/
2023-06-21 11:22:20 +02:00
public function processPaymentResponse($request)
{
2024-06-06 03:17:34 +02:00
nlog("response");
$this->init();
2024-06-09 01:08:31 +02:00
$r = false;
2024-06-14 09:09:44 +02:00
$request['gateway_response'] = str_replace("Error: ", "", $request['gateway_response']);
2023-06-21 11:22:20 +02:00
$response = json_decode($request['gateway_response'], true);
2024-06-14 09:09:44 +02:00
2024-05-24 02:38:07 +02:00
nlog($response);
2024-06-14 09:09:44 +02:00
if($request->has('token') && strlen($request->input('token')) > 2) {
2024-04-22 14:37:34 +02:00
return $this->processTokenPayment($request, $response);
2024-06-14 09:09:44 +02:00
}
2024-04-22 06:38:29 +02:00
//capture
2024-06-06 01:21:23 +02:00
2024-06-06 03:17:34 +02:00
$orderID = $response['orderID'] ?? $this->payment_hash->data->orderID;
if($this->company_gateway->require_shipping_address) {
$shipping_data =
[[
"op" => "replace",
"path" => "/purchase_units/@reference_id=='default'/shipping/address",
"value" => [
"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(),
],
]];
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}", 'patch', $shipping_data);
}
2024-06-14 09:09:44 +02:00
try {
2024-03-19 02:17:25 +01:00
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderID}/capture", 'post', ['body' => '']);
2024-05-24 02:38:07 +02:00
2024-06-14 09:09:44 +02:00
if($r->status() == 422) {
2024-05-24 02:38:07 +02:00
//handle conditions where the client may need to try again.
2024-06-06 03:17:34 +02:00
$r = $this->handleDuplicateInvoiceId($orderID);
2024-05-24 02:38:07 +02:00
}
2024-06-14 09:09:44 +02:00
} catch(\Exception $e) {
2024-03-19 02:17:25 +01:00
//Rescue for duplicate invoice_id
2024-06-14 09:09:44 +02:00
if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false) {
2024-03-19 02:17:25 +01:00
2024-06-06 01:21:23 +02:00
$r = $this->handleDuplicateInvoiceId($orderID);
2024-03-19 02:17:25 +01:00
}
}
$response = $r;
2024-05-20 05:14:50 +02:00
nlog("Process response =>");
nlog($response->json());
if(isset($response['status']) && $response['status'] == 'COMPLETED' && isset($response['purchase_units'])) {
2023-06-21 13:11:41 +02:00
2024-06-14 09:09:44 +02:00
return $this->createNinjaPayment($request, $response);
2023-06-21 13:11:41 +02:00
2023-10-26 04:57:44 +02:00
} else {
2023-07-23 04:43:23 +02:00
if(isset($response['headers']) ?? false) {
unset($response['headers']);
}
2023-07-23 04:43:23 +02:00
SystemLogger::dispatch(
['response' => $response],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_PAYPAL,
$this->client,
$this->client->company,
);
$message = $response['body']['details'][0]['description'] ?? 'Payment failed. Please try again.';
2023-07-23 04:43:23 +02:00
2024-05-24 02:38:07 +02:00
return response()->json(['message' => $message], 400);
2023-06-21 13:11:41 +02:00
}
2023-06-21 11:22:20 +02:00
}
2024-05-20 03:59:44 +02:00
public function createOrder(array $data): string
2023-06-21 11:22:20 +02:00
{
$_invoice = collect($this->payment_hash->data->invoices)->first();
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
$description = collect($invoice->line_items)->map(function ($item) {
return $item->notes;
})->implode("\n");
2023-06-21 11:22:20 +02:00
$order = [
"intent" => "CAPTURE",
2024-04-16 14:01:25 +02:00
"payment_source" => $this->getPaymentSource(),
"purchase_units" => [
[
"custom_id" => $this->payment_hash->hash,
"description" => ctrans('texts.invoice_number') . '# ' . $invoice->number,
"invoice_id" => $invoice->number,
"amount" => [
"value" => (string) $data['amount_with_fee'],
2023-06-21 11:22:20 +02:00
"currency_code" => $this->client->currency()->code,
"breakdown" => [
"item_total" => [
"currency_code" => $this->client->currency()->code,
"value" => (string) $data['amount_with_fee']
]
]
],
"items" => [
[
"name" => ctrans('texts.invoice_number') . '# ' . $invoice->number,
"description" => mb_substr($description, 0, 127),
"quantity" => "1",
"unit_amount" => [
"currency_code" => $this->client->currency()->code,
"value" => (string) $data['amount_with_fee']
],
],
2023-06-21 11:22:20 +02:00
],
],
]
];
if($shipping = $this->getShippingAddress()) {
$order['purchase_units'][0]["shipping"] = $shipping;
}
2024-01-14 05:05:00 +01:00
2024-06-14 09:09:44 +02:00
if(isset($data['payment_source'])) {
2024-04-22 14:37:34 +02:00
$order['payment_source'] = $data['payment_source'];
2024-06-14 09:09:44 +02:00
}
2024-05-20 03:59:44 +02:00
2024-08-02 05:07:34 +02:00
if(isset($data["payer"])){
$order['payer'] = $data["payer"];
}
2023-06-21 11:22:20 +02:00
$r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order);
2023-06-20 07:51:29 +02:00
2024-05-24 02:38:07 +02:00
nlog($r->json());
2024-06-06 03:17:34 +02:00
$response = $r->json();
2024-06-14 09:09:44 +02:00
2024-06-13 11:59:29 +02:00
if($r->status() == 422) {
//handle conditions where the client may need to try again.
$_invoice = collect($this->payment_hash->data->invoices)->first();
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
$new_invoice_number = $invoice->number."_".Str::random(5);
$order['purchase_units'][0]['invoice_id'] = $new_invoice_number;
$r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order);
nlog($r->json());
$response = $r->json();
}
2024-06-14 09:09:44 +02:00
if(!isset($response['id'])) {
2024-06-06 03:17:34 +02:00
$this->handleProcessingFailure($response);
2024-06-14 09:09:44 +02:00
}
2024-06-06 01:21:23 +02:00
2024-06-06 03:17:34 +02:00
$this->payment_hash->withData("orderID", $response['id']);
return $response['id'];
2023-06-19 06:25:42 +02:00
}
2024-06-06 03:17:34 +02:00
2024-06-06 04:31:24 +02:00
/**
2024-05-20 03:59:44 +02:00
* processTokenPayment
*
2024-06-14 09:09:44 +02:00
* With PayPal and token payments, the order needs to be
2024-05-20 03:59:44 +02:00
* deleted and then created with the payment source that
* has been selected by the client.
2024-06-14 09:09:44 +02:00
*
* This method handle the deletion of the current paypal order,
2024-05-20 03:59:44 +02:00
* and the automatic payment of the order with the selected payment source.
2024-06-14 09:09:44 +02:00
*
2024-06-14 09:32:07 +02:00
* ** Do not move to BasePPDriver **
2024-05-20 03:59:44 +02:00
* @param mixed $request
* @param array $response
*/
2024-06-14 09:09:44 +02:00
public function processTokenPayment($request, array $response)
{
2024-06-09 07:56:05 +02:00
/** @var \App\Models\ClientGatewayToken $cgt */
2024-05-20 03:59:44 +02:00
$cgt = ClientGatewayToken::where('client_id', $this->client->id)
->where('token', $request['token'])
->firstOrFail();
2023-06-21 11:22:20 +02:00
2024-05-20 03:59:44 +02:00
$orderId = $response['orderID'];
$r = $this->gatewayRequest("/v1/checkout/orders/{$orderId}/", 'delete', ['body' => '']);
2024-06-13 11:59:29 +02:00
nlog($r->body());
2024-08-02 05:07:34 +02:00
$data["payer"] = [
"name" => [
"given_name" => $this->client->present()->first_name(),
"surname" => $this->client->present()->last_name()
],
"email_address" => $this->client->present()->email(),
];
2024-05-20 03:59:44 +02:00
$data['amount_with_fee'] = $this->payment_hash->data->amount_with_fee;
$data["payment_source"] = [
"card" => [
"vault_id" => $cgt->token,
"stored_credential" => [
"payment_initiator" => "MERCHANT",
2024-06-14 09:09:44 +02:00
"payment_type" => "UNSCHEDULED",
2024-05-20 03:59:44 +02:00
"usage" => "SUBSEQUENT",
],
],
];
2024-06-14 09:09:44 +02:00
2024-05-20 03:59:44 +02:00
$orderId = $this->createOrder($data);
2024-06-14 09:09:44 +02:00
2024-06-13 11:59:29 +02:00
try {
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderId}", 'get', ['body' => '']);
if($r->status() == 422) {
//handle conditions where the client may need to try again.
nlog("hit 422");
$r = $this->handleDuplicateInvoiceId($orderId);
}
} catch(\Exception $e) {
//Rescue for duplicate invoice_id
if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false) {
nlog("hit 422 in exception");
$r = $this->handleDuplicateInvoiceId($orderId);
}
}
2024-05-20 03:59:44 +02:00
$response = $r->json();
2024-06-14 09:09:44 +02:00
2024-07-20 05:36:38 +02:00
if(isset($response['purchase_units'][0]['payments']['captures'][0]['status']) && $response['purchase_units'][0]['payments']['captures'][0]['status'] == 'COMPLETED')
{
$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)]);
}
2024-05-20 03:59:44 +02:00
2024-07-21 03:12:07 +02:00
return response()->json(['message' => 'Error processing token payment'], 400);
2024-03-16 02:36:40 +01:00
}
2024-06-13 08:52:23 +02:00
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
{
$data = [];
$this->payment_hash = $payment_hash;
2024-08-02 05:07:34 +02:00
$data['payer'] = [
"name" => [
"given_name" => $this->client->present()->first_name(),
"surname" => $this->client->present()->last_name()
],
"email_address" => $this->client->present()->email(),
];
2024-06-13 08:52:23 +02:00
$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 = false;
2024-06-14 09:09:44 +02:00
2024-06-13 08:52:23 +02:00
try {
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderId}", 'get', ['body' => '']);
if($r->status() == 422) {
//handle conditions where the client may need to try again.
$r = $this->handleDuplicateInvoiceId($orderId);
}
} catch(\Exception $e) {
//Rescue for duplicate invoice_id
if(stripos($e->getMessage(), 'DUPLICATE_INVOICE_ID') !== false) {
$r = $this->handleDuplicateInvoiceId($orderId);
}
}
$response = $r->json();
2024-07-20 05:36:38 +02:00
if(isset($response['purchase_units'][0]['payments']['captures'][0]['status']) && $response['purchase_units'][0]['payments']['captures'][0]['status'] == 'COMPLETED')
{
$data = [
'payment_type' => $this->getPaymentMethod((string)$cgt->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);
2024-06-13 08:52:23 +02:00
2024-07-20 05:36:38 +02:00
SystemLogger::dispatch(
['response' => $response, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_PAYPAL_PPCP,
$this->client,
$this->client->company,
);
2024-07-21 03:12:07 +02:00
2024-07-20 05:36:38 +02:00
}
2024-06-13 08:52:23 +02:00
2024-07-20 05:36:38 +02:00
$this->processInternallyFailedPayment($this, new \Exception('Auto billing failed.', 400));
2024-06-13 08:52:23 +02:00
2024-07-21 03:12:07 +02:00
SystemLogger::dispatch($response, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_PAYPAL, $this->client, $this->client->company);
2024-06-13 08:52:23 +02:00
}
2023-06-19 06:25:42 +02:00
}