1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-11 05:32:39 +01:00
invoiceninja/app/PaymentDrivers/PayPal/PayPalWebhook.php

402 lines
13 KiB
PHP
Raw Normal View History

2023-12-13 07:10:29 +01:00
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers\PayPal;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\SystemLog;
use App\Libraries\MultiDB;
use App\Models\GatewayType;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use Illuminate\Bus\Queueable;
use App\Models\CompanyGateway;
use App\Jobs\Util\SystemLogger;
use Illuminate\Support\Facades\Http;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Notifications\Ninja\PayPalUnlinkedTransaction;
class PayPalWebhook implements ShouldQueue
{
2024-01-14 05:05:00 +01:00
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
2023-12-13 07:10:29 +01:00
public $tries = 1; //number of retries
public $deleteWhenMissingModels = true;
private $gateway_key = '80af24a6a691230bbec33e930ab40666';
private string $test_endpoint = 'https://api-m.sandbox.paypal.com';
private string $endpoint = 'https://api-m.paypal.com';
2023-12-13 07:10:29 +01:00
public function __construct(protected array $webhook_request, protected array $headers, protected string $access_token)
{
}
public function handle()
{
//testing
2023-12-13 10:04:52 +01:00
// $this->endpoint = $this->test_endpoint;
//this can cause problems verifying the webhook, so unset it if it exists
2024-01-14 05:05:00 +01:00
if(isset($this->webhook_request['q'])) {
unset($this->webhook_request['q']);
2024-01-14 05:05:00 +01:00
}
2023-12-13 07:10:29 +01:00
if($this->verifyWebhook()) {
nlog('verified');
match($this->webhook_request['event_type']) {
'CHECKOUT.ORDER.COMPLETED' => $this->checkoutOrderCompleted(),
};
return;
2023-12-13 07:10:29 +01:00
}
nlog(" NOT VERIFIED ");
2023-12-13 07:10:29 +01:00
}
2024-01-14 05:05:00 +01:00
/*
'id' => 'WH-COC11055RA711503B-4YM959094A144403T',
'create_time' => '2018-04-16T21:21:49.000Z',
'event_type' => 'CHECKOUT.ORDER.COMPLETED',
'resource_type' => 'checkout-order',
'resource_version' => '2.0',
'summary' => 'Checkout Order Completed',
'resource' =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
'id' => '5O190127TN364715T',
'status' => 'COMPLETED',
'intent' => 'CAPTURE',
'gross_amount' =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
'currency_code' => 'USD',
'value' => '100.00',
2023-12-13 07:10:29 +01:00
),
2024-01-14 05:05:00 +01:00
'payer' =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
'name' =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
'given_name' => 'John',
'surname' => 'Doe',
2023-12-13 07:10:29 +01:00
),
2024-01-14 05:05:00 +01:00
'email_address' => 'buyer@example.com',
'payer_id' => 'QYR5Z8XDVJNXQ',
),
'purchase_units' =>
array (
0 =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
'reference_id' => 'd9f80740-38f0-11e8-b467-0ed5f89f718b',
'amount' =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
'currency_code' => 'USD',
'value' => '100.00',
2023-12-13 07:10:29 +01:00
),
2024-01-14 05:05:00 +01:00
'payee' =>
array (
'email_address' => 'seller@example.com',
),
'shipping' =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
'method' => 'United States Postal Service',
'address' =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
'address_line_1' => '2211 N First Street',
'address_line_2' => 'Building 17',
'admin_area_2' => 'San Jose',
'admin_area_1' => 'CA',
'postal_code' => '95131',
'country_code' => 'US',
),
),
'payments' =>
array (
'captures' =>
array (
0 =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
'id' => '3C679366HH908993F',
'status' => 'COMPLETED',
'amount' =>
2023-12-13 07:10:29 +01:00
array (
'currency_code' => 'USD',
'value' => '100.00',
),
2024-01-14 05:05:00 +01:00
'seller_protection' =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
'status' => 'ELIGIBLE',
'dispute_categories' =>
array (
0 => 'ITEM_NOT_RECEIVED',
1 => 'UNAUTHORIZED_TRANSACTION',
),
2023-12-13 07:10:29 +01:00
),
2024-01-14 05:05:00 +01:00
'final_capture' => true,
'seller_receivable_breakdown' =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
'gross_amount' =>
array (
'currency_code' => 'USD',
'value' => '100.00',
),
'paypal_fee' =>
array (
'currency_code' => 'USD',
'value' => '3.00',
),
'net_amount' =>
array (
'currency_code' => 'USD',
'value' => '97.00',
),
2023-12-13 07:10:29 +01:00
),
2024-01-14 05:05:00 +01:00
'create_time' => '2018-04-01T21:20:49Z',
'update_time' => '2018-04-01T21:20:49Z',
'links' =>
2023-12-13 07:10:29 +01:00
array (
2024-01-14 05:05:00 +01:00
0 =>
array (
'href' => 'https://api.paypal.com/v2/payments/captures/3C679366HH908993F',
'rel' => 'self',
'method' => 'GET',
),
1 =>
array (
'href' => 'https://api.paypal.com/v2/payments/captures/3C679366HH908993F/refund',
'rel' => 'refund',
'method' => 'POST',
),
2023-12-13 07:10:29 +01:00
),
),
),
),
),
),
2024-01-14 05:05:00 +01:00
'create_time' => '2018-04-01T21:18:49Z',
'update_time' => '2018-04-01T21:20:49Z',
'links' =>
*/
private function checkoutOrderCompleted()
2023-12-13 07:10:29 +01:00
{
$order = $this->webhook_request['resource'];
$transaction_reference = $order['purchase_units'][0]['payments']['captures'][0]['id'];
$amount = $order['purchase_units'][0]['payments']['captures'][0]['amount']['value'];
$payment_hash = MultiDB::findAndSetByPaymentHash($order['purchase_units'][0]['custom_id']);
$merchant_id = $order['purchase_units'][0]['payee']['merchant_id'];
if(!$payment_hash) {
$ninja_company = Company::on('db-ninja-01')->find(config('ninja.ninja_default_company_id'));
$ninja_company->notification(new PayPalUnlinkedTransaction($order['id'], $transaction_reference))->ninja();
return;
}
nlog("payment completed check");
2024-01-14 05:05:00 +01:00
if($payment_hash->payment && $payment_hash->payment->status_id == Payment::STATUS_COMPLETED) { // Payment made, all good!
2023-12-13 07:10:29 +01:00
return;
2024-01-14 05:05:00 +01:00
}
2023-12-13 07:10:29 +01:00
nlog("invoice paid check");
2024-01-14 05:05:00 +01:00
if($payment_hash->fee_invoice && $payment_hash->fee_invoice->status_id == Invoice::STATUS_PAID) { // Payment made, all good!
2023-12-13 07:10:29 +01:00
nlog("payment status check");
if($payment_hash->payment && $payment_hash->payment->status_id != Payment::STATUS_COMPLETED) { // Make sure the payment is marked as completed
$payment_hash->payment->status_id = Payment::STATUS_COMPLETED;
$payment_hash->push();
}
return;
}
nlog("create payment check");
2024-01-14 05:05:00 +01:00
if($payment_hash->fee_invoice && in_array($payment_hash->fee_invoice->status_id, [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])) {
2023-12-13 07:10:29 +01:00
$payment = Payment::where('transaction_reference', $transaction_reference)->first();
2024-01-14 05:05:00 +01:00
if(!$payment) {
nlog("make payment here!");
2023-12-13 07:10:29 +01:00
$payment = $this->createPayment($payment_hash, [
'amount' => $amount,
'transaction_reference' => $transaction_reference,
'merchant_id' => $merchant_id,
]);
}
2023-12-13 10:04:52 +01:00
2023-12-13 07:10:29 +01:00
}
}
private function getPaymentType($source): int
{
$method = 'paypal';
match($source) {
"card" => $method = PaymentType::CREDIT_CARD_OTHER,
"paypal" => $method = PaymentType::PAYPAL,
"venmo" => $method = PaymentType::VENMO,
"paylater" => $method = PaymentType::PAY_LATER,
default => $method = PaymentType::PAYPAL,
};
return $method;
}
private function createPayment(PaymentHash $payment_hash, array $data)
{
$client = $payment_hash->fee_invoice->client;
$company_gateway = $this->harvestGateway($client->company, $data['merchant_id']);
$driver = $company_gateway->driver($client)->init();
$driver->setPaymentHash($payment_hash);
$order = $driver->getOrder($this->webhook_request['resource']['id']);
$source = 'paypal';
if(isset($order['payment_source'])) {
$source = array_key_first($order['payment_source']);
}
2024-01-14 05:05:00 +01:00
2023-12-13 07:10:29 +01:00
$data = [
'payment_type' => $this->getPaymentType($source),
'amount' => $data['amount'],
'transaction_reference' => $data['transaction_reference'],
'gateway_type_id' => GatewayType::PAYPAL,
];
$payment = $driver->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => $this->webhook_request, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_PAYPAL,
$client,
$client->company,
);
}
private function harvestGateway(Company $company, string $merchant_id): ?CompanyGateway
{
$gateway = CompanyGateway::query()
->where('company_id', $company->id)
->where('gateway_key', $this->gateway_key)
->cursor()
2024-01-14 05:05:00 +01:00
->first(function ($cg) use ($merchant_id) {
2023-12-13 07:10:29 +01:00
$config = $cg->getConfig();
2024-01-14 05:05:00 +01:00
if($config->merchantId == $merchant_id) {
2023-12-13 07:10:29 +01:00
return $cg;
2024-01-14 05:05:00 +01:00
}
2023-12-13 07:10:29 +01:00
});
return $gateway ?? false;
}
//--------------------------------------------------------------------------------------//
private function verifyWebhook(): bool
2024-01-14 05:05:00 +01:00
{
nlog($this->headers);
2023-12-13 07:10:29 +01:00
$request = [
'auth_algo' => $this->headers['paypal-auth-algo'][0],
'cert_url' => $this->headers['paypal-cert-url'][0],
'transmission_id' => $this->headers['paypal-transmission-id'][0],
'transmission_sig' => $this->headers['paypal-transmission-sig'][0],
'transmission_time' => $this->headers['paypal-transmission-time'][0],
2023-12-13 07:10:29 +01:00
'webhook_id' => config('ninja.paypal.webhook_id'),
'webhook_event' => $this->webhook_request
2023-12-13 07:10:29 +01:00
];
nlog($request);
2023-12-13 07:10:29 +01:00
$headers = [
'Content-type' => 'application/json',
];
$r = Http::withToken($this->access_token)
->withHeaders($headers)
->post("{$this->endpoint}/v1/notifications/verify-webhook-signature", $request);
2023-12-13 07:10:29 +01:00
nlog($r);
nlog($r->json());
if($r->successful() && $r->json()['verification_status'] == 'SUCCESS') {
return true;
}
return false;
}
}
/*
{
"auth_algo": "SHA256withRSA",
"cert_url": "cert_url",
"transmission_id": "69cd13f0-d67a-11e5-baa3-778b53f4ae55",
"transmission_sig": "lmI95Jx3Y9nhR5SJWlHVIWpg4AgFk7n9bCHSRxbrd8A9zrhdu2rMyFrmz+Zjh3s3boXB07VXCXUZy/UFzUlnGJn0wDugt7FlSvdKeIJenLRemUxYCPVoEZzg9VFNqOa48gMkvF+XTpxBeUx/kWy6B5cp7GkT2+pOowfRK7OaynuxUoKW3JcMWw272VKjLTtTAShncla7tGF+55rxyt2KNZIIqxNMJ48RDZheGU5w1npu9dZHnPgTXB9iomeVRoD8O/jhRpnKsGrDschyNdkeh81BJJMH4Ctc6lnCCquoP/GzCzz33MMsNdid7vL/NIWaCsekQpW26FpWPi/tfj8nLA==",
"transmission_time": "2016-02-18T20:01:35Z",
"webhook_id": "1JE4291016473214C",
"webhook_event": {
"id": "8PT597110X687430LKGECATA",
"create_time": "2013-06-25T21:41:28Z",
"resource_type": "authorization",
"event_type": "PAYMENT.AUTHORIZATION.CREATED",
"summary": "A payment authorization was created",
"resource": {
"id": "2DC87612EK520411B",
"create_time": "2013-06-25T21:39:15Z",
"update_time": "2013-06-25T21:39:17Z",
"state": "authorized",
"amount": {
"total": "7.47",
"currency": "USD",
"details": {
"subtotal": "7.47"
}
},
"parent_payment": "PAY-36246664YD343335CKHFA4AY",
"valid_until": "2013-07-24T21:39:15Z",
"links": [
{
"href": "https://api-m.paypal.com/v1/payments/authorization/2DC87612EK520411B",
"rel": "self",
"method": "GET"
},
{
"href": "https://api-m.paypal.com/v1/payments/authorization/2DC87612EK520411B/capture",
"rel": "capture",
"method": "POST"
},
{
"href": "https://api-m.paypal.com/v1/payments/authorization/2DC87612EK520411B/void",
"rel": "void",
"method": "POST"
},
{
"href": "https://api-m.paypal.com/v1/payments/payment/PAY-36246664YD343335CKHFA4AY",
"rel": "parent_payment",
"method": "GET"
}
]
}
}
}
*/