Skip to content

Commit

Permalink
Merge pull request bitpay#94 from p-maguire/6.0.x
Browse files Browse the repository at this point in the history
SP-965: Validating Incoming Webhooks
  • Loading branch information
p-maguire authored Sep 19, 2024
2 parents 17da22c + a0a2c33 commit a7698f6
Show file tree
Hide file tree
Showing 8 changed files with 365 additions and 23 deletions.
26 changes: 26 additions & 0 deletions BitPayLib/class-bitpaycreateorder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace BitPayLib;

class BitPayCreateOrder {

public const BITPAY_TOKEN_ORDER_METADATA_KEY = '_bitpay_token';

private BitPayPaymentSettings $bitpay_payment_settings;

public function __construct(
BitPayPaymentSettings $bitpay_payment_settings,
) {
$this->bitpay_payment_settings = $bitpay_payment_settings;
}

public function execute( int $order_id ): void {
$token = $this->bitpay_payment_settings->get_bitpay_token();
$order = new \WC_Order( $order_id );

$order->update_meta_data( self::BITPAY_TOKEN_ORDER_METADATA_KEY, $token );
$order->save_meta_data();
}
}
32 changes: 26 additions & 6 deletions BitPayLib/class-bitpayipnprocess.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,23 @@ class BitPayIpnProcess {
private BitPayLogger $logger;
private BitPayClientFactory $factory;
private BitPayWordpressHelper $bitpay_wordpress_helper;
private BitPayWebhookVerifier $bitpay_webhook_verifier;
private BitPayPaymentSettings $bitpay_payment_settings;

public function __construct(
BitPayCheckoutTransactions $bitpay_checkout_transactions,
BitPayClientFactory $factory,
BitPayWordpressHelper $bitpay_wordpress_helper,
BitPayLogger $logger
BitPayLogger $logger,
BitPayWebhookVerifier $bitpay_webhook_verifier,
BitPayPaymentSettings $bitpay_payment_settings,
) {
$this->bitpay_checkout_transactions = $bitpay_checkout_transactions;
$this->logger = $logger;
$this->factory = $factory;
$this->bitpay_wordpress_helper = $bitpay_wordpress_helper;
$this->bitpay_webhook_verifier = $bitpay_webhook_verifier;
$this->bitpay_payment_settings = $bitpay_payment_settings;
}

public function execute( WP_REST_Request $request ): void {
Expand All @@ -45,9 +51,11 @@ public function execute( WP_REST_Request $request ): void {
$data['event'] = $event;
$data['requestDate'] = date( 'Y-m-d H:i:s' );
$invoice_id = $data['id'] ?? null;
$x_signature = $request->get_header( 'x-signature' );

$this->logger->execute( $data, 'INCOMING IPN', true );
if ( ! $event || ! $data || ! $invoice_id ) {

if ( ! $event || ! $data || ! $invoice_id || ! $x_signature ) {
$this->logger->execute( 'Wrong IPN request', 'INCOMING IPN ERROR', false, true );
return;
}
Expand All @@ -57,6 +65,8 @@ public function execute( WP_REST_Request $request ): void {
do_action( 'bitpay_checkout_woocoomerce_after_get_invoice', $bitpay_invoice );
$order = $this->bitpay_wordpress_helper->get_order( $bitpay_invoice->getOrderId() );
$this->validate_order( $order, $invoice_id );
$this->validate_webhook( $x_signature, $request->get_body(), $order );

$this->process( $bitpay_invoice, $order, $event['name'] );
} catch ( BitPayInvalidOrder $e ) { // phpcs:ignore
// do nothing.
Expand Down Expand Up @@ -295,10 +305,6 @@ private function process_processing( Invoice $bitpay_invoice, WC_Order $order ):
$order->update_status( $new_status, __( 'BitPay payment processing', 'woocommerce' ) );
}

private function has_final_status( WC_Order $order ): bool {
return \in_array( $order->get_status(), self::FINAL_WC_ORDER_STATUSES, true );
}

/**
* We don't want to change complete order to process.
*
Expand All @@ -322,4 +328,18 @@ private function should_process_refund(): bool {
$should_process_refund_status = $this->get_wc_order_statuses()['bitpay_checkout_order_process_refund'] ?? '1';
return '1' === $should_process_refund_status;
}

private function validate_webhook( string $x_signature, string $webhook_body, WC_Order $order ): void {
$order_bitpay_token = $order->get_meta( BitPayCreateOrder::BITPAY_TOKEN_ORDER_METADATA_KEY );

if ( $order_bitpay_token &&
! $this->bitpay_webhook_verifier->verify(
$this->bitpay_payment_settings->get_bitpay_token(),
$x_signature,
$webhook_body
)
) {
throw new \Exception( 'IPN Request failed HMAC validation' );
}
}
}
13 changes: 12 additions & 1 deletion BitPayLib/class-bitpaypluginsetup.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class BitPayPluginSetup {
private BitPayPaymentSettings $bitpay_payment_settings;
private BitPayInvoiceCreate $bitpay_invoice_create;
private BitPayCheckoutTransactions $bitpay_checkout_transactions;
private BitPayCreateOrder $bitpay_create_order;
private BitPaySupportPackage $bitpay_support_package;

public function __construct() {
Expand All @@ -33,8 +34,9 @@ public function __construct() {
$cart = new BitPayCart();
$logger = new BitPayLogger();
$wordpress_helper = new BitPayWordpressHelper();
$webhook_verifier = new BitPayWebhookVerifier();
$this->bitpay_checkout_transactions = new BitPayCheckoutTransactions( $wordpress_helper );
$this->bitpay_ipn_process = new BitPayIpnProcess( $this->bitpay_checkout_transactions, $factory, $wordpress_helper, $logger );
$this->bitpay_ipn_process = new BitPayIpnProcess( $this->bitpay_checkout_transactions, $factory, $wordpress_helper, $logger, $webhook_verifier, $this->bitpay_payment_settings );
$this->bitpay_cancel_order = new BitPayCancelOrder( $cart, $this->bitpay_checkout_transactions, $logger );
$this->bitpay_invoice_create = new BitPayInvoiceCreate(
$factory,
Expand All @@ -43,6 +45,9 @@ public function __construct() {
$wordpress_helper,
$logger
);
$this->bitpay_create_order = new BitPayCreateOrder(
$this->bitpay_payment_settings
);
$this->bitpay_support_package = new BitPaySupportPackage(
$wordpress_helper,
$logger
Expand All @@ -63,6 +68,8 @@ public function execute(): void {
add_filter( 'woocommerce_payment_gateways', array( $this, 'wc_bitpay_checkout_add_to_gateways' ) );
add_filter( 'woocommerce_order_button_html', array( $this, 'bitpay_checkout_replace_order_button_html' ), 10, 2 );
add_action( 'woocommerce_blocks_loaded', array( $this, 'register_payment_block' ) );
add_action( 'woocommerce_new_order', array( $this, 'bitpay_create_order' ) );
add_action( 'woocommerce_update_order', array( $this, 'bitpay_create_order' ) );

// http://<host>/wp-json/bitpay/ipn/status url.
// http://<host>/wp-json/bitpay/cartfix/restore url.
Expand Down Expand Up @@ -237,4 +244,8 @@ function () {
5
);
}

public function bitpay_create_order( int $order_id ): void {
$this->bitpay_create_order->execute( $order_id );
}
}
21 changes: 21 additions & 0 deletions BitPayLib/class-bitpaywebhookverifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace BitPayLib;

class BitPayWebhookVerifier {
public function verify( string $signing_key, string $sig_header, string $webhook_body ): bool {
// phpcs:ignore
$hmac = base64_encode(
hash_hmac(
'sha256',
$webhook_body,
$signing_key,
true
)
);

return $sig_header === $hmac;
}
}
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
],
"post-update-cmd": [
"composer add-prefix"
],
"phpcbf": [
"./vendor/bin/phpcbf BitPayLib"
]
},
"config": {
Expand Down
3 changes: 2 additions & 1 deletion tests/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.env
.env
.phpunit.result.cache
Loading

0 comments on commit a7698f6

Please sign in to comment.