Skip to content

Commit

Permalink
feat: new webhooks handling
Browse files Browse the repository at this point in the history
  • Loading branch information
021-projects committed Mar 19, 2024
1 parent 50ea2d0 commit 8443ff5
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 11 deletions.
24 changes: 13 additions & 11 deletions src/addons/BS/BtcPayProvider/Payment/BTCPayServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

class BTCPayServer extends AbstractProvider
{
use Concerns\Webhook;

public function getTitle()
{
return 'BTCPay Server';
Expand Down Expand Up @@ -87,6 +89,7 @@ public function setupCallback(\XF\Http\Request $request)
$state->requestKey = $metadata['request_key'] ?? '';
$state->transactionId = $payload['invoiceId'] ?? '';
$state->hookType = $payload['type'] ?? '';
$state->payload = $payload;

return $state;
}
Expand All @@ -103,14 +106,11 @@ public function validateCallback(CallbackState $state)

$secret = $paymentProfile->options['secret'];

if ($state->hookType !== 'InvoiceSettled') {
$state->logType = false;
$state->logMessage = 'Invalid hook type.';
$state->httpCode = 200;
return false;
}

if (! Webhook::isIncomingWebhookRequestValid($state->inputRaw, $state->signature, $secret)) {
if (! Webhook::isIncomingWebhookRequestValid(
$state->inputRaw,
$state->signature,
$secret
)) {
$state->logType = 'error';
$state->logMessage = 'Invalid signature.';
return false;
Expand Down Expand Up @@ -156,11 +156,13 @@ public function validateTransaction(CallbackState $state)

public function getPaymentResult(CallbackState $state)
{
if ($state->hookType !== 'InvoiceSettled') {
return;
$result = $this->getWebhookPaymentResult($state);
if (! $result) {
return null;
}

$state->paymentResult = CallbackState::PAYMENT_RECEIVED;
$state->paymentResult = $result;
return $result;
}

public function prepareLogData(CallbackState $state)
Expand Down
114 changes: 114 additions & 0 deletions src/addons/BS/BtcPayProvider/Payment/Concerns/Webhook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

namespace BS\BtcPayProvider\Payment\Concerns;

use BTCPayServer\Client\Invoice;
use XF\Payment\CallbackState;

use function BS\BtcPayProvider\Helpers\data_get;

trait Webhook
{
protected function getWebhookPaymentResult(CallbackState $state): ?int
{
$payload = $state->payload ?? [];

switch ($state->hookType) {
case 'InvoiceSettled':
if ($payload['overPaid'] ?? false) {
$state->logType = 'info';
$state->logMessage = 'Invoice payment settled but was overpaid.';
}

return CallbackState::PAYMENT_RECEIVED;

case 'InvoicePaymentSettled':
if (! data_get($state->purchaseRequest->extra_data, 'invoiceExpired')) {
$state->logType = 'info';
$state->logMessage = 'Invoice (partial) payment settled.';
return null;
}

// here invoice expired
$state->logType = 'info';
if ($this->invoiceIsFullyPaid($state->paymentProfile->options, $state->transactionId)) {
$state->logMessage = 'Invoice fully settled after invoice was already expired. Needs manual checking.';
} else {
$state->logMessage = '(Partial) payment settled but invoice not settled yet (could be more transactions incoming). Needs manual checking.';
}
return null;

case 'InvoiceReceivedPayment':
$state->logType = 'info';
if (data_get($payload, 'afterExpiration')) {
$state->logMessage = 'Invoice (partial) payment incoming (unconfirmed) after invoice was already expired.';
} else {
$state->logMessage = 'Invoice (partial) payment incoming (unconfirmed). Waiting for settlement.';
}
return null;

case 'InvoiceProcessing':
$state->logType = 'info';
if (data_get($payload, 'overPaid')) {
$state->logMessage = 'Invoice payment received fully with overpayment, waiting for settlement.';
} else {
$state->logMessage = 'Invoice payment received fully, waiting for settlement.';
}
return null;

case 'InvoiceExpired':
$state->logType = 'info';
if (data_get($payload, 'partiallyPaid')) {
$state->logMessage = 'Invoice expired but was paid partially, please check.';
} else {
$state->logMessage = 'Invoice expired. No action to take.';
}
$this->updateStatePurchaseRequestExtraData($state, 'invoiceExpired', true);
return null;

case 'InvoiceInvalid':
$state->logType = 'info';
if (data_get($payload, 'manuallyMarked')) {
$state->logMessage = 'Invoice manually marked invalid.';
} else {
$state->logMessage = 'Invoice became invalid.';
}
return null;

default:
return null;
}
}

protected function updateStatePurchaseRequestExtraData(
CallbackState $state,
array|string $key,
mixed $value = null
): void {
$request = $state->purchaseRequest;
$extraData = $request->extra_data;

if (is_array($key)) {
$extraData = array_merge($extraData, $key);
} else {
$extraData[$key] = $value;
}

$request->extra_data = $extraData;
$request->save();
}

protected function invoiceIsFullyPaid(array $options, string $invoiceId): bool
{
$client = new Invoice($options['host'], $options['api_key']);

try {
return $client->getInvoice($options['store_id'], $invoiceId)
->isSettled();
} catch (\Throwable $e) {
\XF::logException($e, false, 'BTCPay Server invoice retrieval failed: ');
}

return false;
}
}
8 changes: 8 additions & 0 deletions src/addons/BS/BtcPayProvider/helpers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace BS\BtcPayProvider\Helpers;

function data_get(array $arr, mixed $key, mixed $default = null): mixed
{
return $arr[$key] ?? $default;
}

0 comments on commit 8443ff5

Please sign in to comment.