Skip to content

Commit

Permalink
Add WooPayments payment gateway (#6)
Browse files Browse the repository at this point in the history
* Add WooPayments payment gateway

* Load gateway definition file

* Move woocommerce-gateway-stripe specific code out of the shared Stripe lib

* Delint

* Add StripeLibTest

* Try increasing test coverage

* Try increasing test coverage

* Build tests for the remaining implemented properties

* Remove debugging echo statements
  • Loading branch information
chrismccluskey authored Nov 14, 2024
1 parent 8a0903b commit 4da08e0
Show file tree
Hide file tree
Showing 11 changed files with 870 additions and 156 deletions.
119 changes: 0 additions & 119 deletions src/inc/payment-gateways/lib/stripe.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,125 +6,6 @@
* A class to share Stripe-specific logic with payment gateways that use Stripe in some way.
*/
class Stripe {
/**
* Get all payment methods for customer from order.
*
* @param \WC_Order $order The WC Order.
*
* @return array|null
*/
public static function get_all_payment_methods_for_customer_from_order( \WC_Order $order ): ?array {
$stripe_customer_id = $order->get_meta( '_stripe_customer_id', true );

$stripe_customer = new \WC_Stripe_Customer();
$stripe_customer->set_id( $stripe_customer_id );

$sources = array_merge(
$stripe_customer->get_payment_methods( 'card' ),
$stripe_customer->get_payment_methods( 'sepa_debit' )
);

if ( $sources ) {
return $sources;
}
}

/**
* Get payment method from order.
*
* @param \WC_Order $order The WC Order.
*
* @return object The payment method from the order.
*/
public static function get_payment_method_from_order( \WC_Order $order ) {
$stripe_source_id = $order->get_meta( '_stripe_source_id', true );
$sources = static::get_all_payment_methods_for_customer_from_order( $order );

if ( $sources ) {
foreach ( $sources as $source ) {
if ( $source->id === $stripe_source_id ) {
return $source;
}
}
}
}

/**
* Get payment intent from order.
*
* @param \WC_Order $order The WC Order.
*
* @return object The payment intent from the order.
*/
public static function get_intent_from_order( \WC_Order $order ) {
$intent_id = $order->get_meta( '_stripe_intent_id' );

if ( $intent_id ) {
return static::get_intent( 'payment_intents', $intent_id );
}

// The order doesn't have a payment intent, but it may have a setup intent.
$intent_id = $order->get_meta( '_stripe_setup_intent' );

if ( $intent_id ) {
return static::get_intent( 'setup_intents', $intent_id );
}

return false;
}

/**
* Get the intent by ID from the Stripe API.
*
* @param string $intent_type The intent type.
* @param string $intent_id The intent's ID.
*
* @return object The intent from the API.
*/
public static function get_intent( string $intent_type, string $intent_id ) {
if ( ! in_array( $intent_type, array( 'payment_intents', 'setup_intents' ), true ) ) {
throw new \Exception( sprintf( 'Failed to get intent of type %s. Type is not allowed', esc_attr( $intent_type ) ) );
}

$response = \WC_Stripe_API::request( array(), "$intent_type/$intent_id?expand[]=payment_method", 'GET' );

if ( $response && isset( $response->{ 'error' } ) ) {
return false;
}

return $response;
}

/**
* Get the charge for a payment intent from a WC_Order object.
*
* @param \WC_Order $order The WC Order.
*
* @return object The charge for the intent from the order.
*/
public static function get_charge_for_intent_from_order( \WC_Order $order ) {
$intent = self::get_intent_from_order( $order );
if ( ! empty( $intent ) ) {
$result = \WC_Stripe_API::request(
array(),
'payment_intents/' . $intent->id
);
if ( empty( $result->error ) ) {
return end( $result->charges->data );
}
}
}

/**
* Get the Stripe UPE payment type from Order metadata.
*
* @param \WC_Order $order The WC Order.
*
* @return null|string
*/
public static function get_payment_type_from_order( \WC_Order $order ): ?string {
return $order->get_meta( '_stripe_upe_payment_type' );
}

/**
* Convert a payment method string from a string Stripe would use to a string Sift would use.
Expand Down
4 changes: 4 additions & 0 deletions src/inc/payment-gateways/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ function () {
case 'woopayments':
require_once __DIR__ . '/transact.php';
break;
case 'woocommerce_payments':
require_once __DIR__ . '/lib/stripe.php';
require_once __DIR__ . '/woocommerce-payments.php';
break;
}
}
}
Expand Down
132 changes: 130 additions & 2 deletions src/inc/payment-gateways/stripe.php
Original file line number Diff line number Diff line change
@@ -1,20 +1,148 @@
<?php declare( strict_types=1 );

namespace Sift_For_WooCommerce\Sift_For_WooCommerce\PaymentGateways;

use Sift_For_WooCommerce\Sift_For_WooCommerce\PaymentGateways\Lib\Stripe;

/**
* Utility functions used by multiple filters when working with the woocommerce-gateway-stripe plugin.
*/
class WooCommerce_Gateway_Stripe_Utils {

/**
* Get all payment methods for customer from order.
*
* @param \WC_Order $order The WC Order.
*
* @return array|null
*/
public static function get_all_payment_methods_for_customer_from_order( \WC_Order $order ): ?array {
$stripe_customer_id = $order->get_meta( '_stripe_customer_id', true );

$stripe_customer = new \WC_Stripe_Customer();
$stripe_customer->set_id( $stripe_customer_id );

$sources = array_merge(
$stripe_customer->get_payment_methods( 'card' ),
$stripe_customer->get_payment_methods( 'sepa_debit' )
);

if ( $sources ) {
return $sources;
}
}

/**
* Get payment method from order.
*
* @param \WC_Order $order The WC Order.
*
* @return object The payment method from the order.
*/
public static function get_payment_method_from_order( \WC_Order $order ) {
$stripe_source_id = $order->get_meta( '_stripe_source_id', true );
$sources = static::get_all_payment_methods_for_customer_from_order( $order );

if ( $sources ) {
foreach ( $sources as $source ) {
if ( $source->id === $stripe_source_id ) {
return $source;
}
}
}
}

/**
* Get payment intent from order.
*
* @param \WC_Order $order The WC Order.
*
* @return object The payment intent from the order.
*/
public static function get_intent_from_order( \WC_Order $order ) {
$intent_id = $order->get_meta( '_stripe_intent_id' );

if ( $intent_id ) {
return static::get_intent( 'payment_intents', $intent_id );
}

// The order doesn't have a payment intent, but it may have a setup intent.
$intent_id = $order->get_meta( '_stripe_setup_intent' );

if ( $intent_id ) {
return static::get_intent( 'setup_intents', $intent_id );
}

return false;
}

/**
* Get the intent by ID from the Stripe API.
*
* @param string $intent_type The intent type.
* @param string $intent_id The intent's ID.
*
* @return object The intent from the API.
*/
public static function get_intent( string $intent_type, string $intent_id ) {
if ( ! in_array( $intent_type, array( 'payment_intents', 'setup_intents' ), true ) ) {
throw new \Exception( sprintf( 'Failed to get intent of type %s. Type is not allowed', esc_attr( $intent_type ) ) );
}

$response = \WC_Stripe_API::request( array(), "$intent_type/$intent_id?expand[]=payment_method", 'GET' );

if ( $response && isset( $response->{ 'error' } ) ) {
return false;
}

return $response;
}

/**
* Get the charge for a payment intent from a WC_Order object.
*
* @param \WC_Order $order The WC Order.
*
* @return object The charge for the intent from the order.
*/
public static function get_charge_for_intent_from_order( \WC_Order $order ) {
$intent = self::get_intent_from_order( $order );
if ( ! empty( $intent ) ) {
$result = \WC_Stripe_API::request(
array(),
'payment_intents/' . $intent->id
);
if ( empty( $result->error ) ) {
return end( $result->charges->data );
}
}
}

/**
* Get the Stripe UPE payment type from Order metadata.
*
* @param \WC_Order $order The WC Order.
*
* @return null|string
*/
public static function get_payment_type_from_order( \WC_Order $order ): ?string {
return $order->get_meta( '_stripe_upe_payment_type' );
}
}

add_filter( 'sift_for_woocommerce_stripe_payment_gateway_string', fn() => '$stripe' );

add_filter(
'sift_for_woocommerce_stripe_payment_method_details_from_order',
function ( \WC_Order $order ) {
return Stripe::get_payment_method_from_order( $order );
return WooCommerce_Gateway_Stripe_Utils::get_payment_method_from_order( $order );
}
);

add_filter(
'sift_for_woocommerce_stripe_charge_details_from_order',
function ( \WC_Order $order ) {
return Stripe::get_charge_for_intent_from_order( $order );
return WooCommerce_Gateway_Stripe_Utils::get_charge_for_intent_from_order( $order );
}
);

Expand Down
10 changes: 1 addition & 9 deletions src/inc/payment-gateways/transact.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,7 @@

add_filter( 'sift_for_woocommerce_woopayments_payment_gateway_string', fn() => '$stripe' );

add_filter(
'sift_for_woocommerce_woopayments_payment_type_string',
function ( $payment_type ) {
switch ( strtolower( $payment_type ) ) {
case 'card':
return '$credit_card';
}
}
);
add_filter( 'sift_for_woocommerce_woopayments_payment_type_string', array( \Sift_For_WooCommerce\Sift_For_WooCommerce\PaymentGateways\Lib\Stripe::class, 'convert_payment_type_to_sift_payment_type' ) );

add_filter( 'sift_for_woocommerce_woopayments_payment_method_details_from_order', fn( $value, $order ) => $order ?? $value, 10, 2 );

Expand Down
44 changes: 44 additions & 0 deletions src/inc/payment-gateways/woocommerce-payments.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php declare(strict_types=1);

add_filter( 'sift_for_woocommerce_woocommerce_payments_payment_gateway_string', fn() => '$stripe' );

add_filter(
'sift_for_woocommerce_woocommerce_payments_payment_method_details_from_order',
function ( $value, \WC_Order $order ) {
try {
$charge_id = \WC_Payments::get_order_service()->get_charge_id_for_order( $order );
$api_client = \WC_Payments::get_payments_api_client();
$charge = $api_client->get_charge( $charge_id );
return $charge->get_payment_method_details();
} catch ( \Exception ) {
return $value;
}
}
);

add_filter(
'sift_for_woocommerce_woocommerce_payments_charge_details_from_order',
function ( $value, \WC_Order $order ) {
try {
$charge_id = \WC_Payments::get_order_service()->get_charge_id_for_order( $order );
$api_client = \WC_Payments::get_payments_api_client();
return $api_client->get_charge( $charge_id );
} catch ( \Exception ) {
return $value;
}
}
);

add_filter( 'sift_for_woocommerce_woocommerce_payments_payment_type_string', array( \Sift_For_WooCommerce\Sift_For_WooCommerce\PaymentGateways\Lib\Stripe::class, 'convert_payment_type_to_sift_payment_type' ) );
add_filter( 'sift_for_woocommerce_woocommerce_payments_card_last4', fn( $value, $woocommerce_payments_payment_method_details ) => $woocommerce_payments_payment_method_details['card']['last4'] ?? $value, 10, 2 );
add_filter( 'sift_for_woocommerce_woocommerce_payments_card_bin', fn( $value, $woocommerce_payments_payment_method_details ) => $woocommerce_payments_payment_method_details['card']['iin'] ?? $value, 10, 2 );

add_filter( 'sift_for_woocommerce_woocommerce_payments_cvv_result_code', fn( $value, $woocommerce_payments_charge ) => $woocommerce_payments_charge->get_payment_method_details()['card']['checks']['cvc_check'] ?? $value, 10, 2 );
add_filter( 'sift_for_woocommerce_woocommerce_payments_sepa_direct_debit_mandate', fn( $value, $woocommerce_payments_charge ) => $woocommerce_payments_charge->get_payment_method_details()['sepa_debit']['mandate'] ?? $value, 10, 2 );
add_filter( 'sift_for_woocommerce_woocommerce_payments_wallet_type', fn( $value, $woocommerce_payments_charge ) => $woocommerce_payments_charge->get_payment_method_details()['card']['wallet']['type'] ?? $value, 10, 2 );

add_filter( 'sift_for_woocommerce_woocommerce_payments_stripe_cvc_check', fn( $value, $woocommerce_payments_charge ) => $woocommerce_payments_charge->get_payment_method_details()['card']['checks']['cvc_check'] ?? $value, 10, 2 );
add_filter( 'sift_for_woocommerce_woocommerce_payments_stripe_address_line1_check', fn( $value, $woocommerce_payments_charge ) => $woocommerce_payments_charge->get_payment_method_details()['card']['checks']['address_line1_check'] ?? $value, 10, 2 );
add_filter( 'sift_for_woocommerce_woocommerce_payments_stripe_address_zip_check', fn( $value, $woocommerce_payments_charge ) => $woocommerce_payments_charge->get_payment_method_details()['card']['checks']['address_postal_code_check'] ?? $value, 10, 2 );
add_filter( 'sift_for_woocommerce_woocommerce_payments_stripe_funding', fn( $value, $woocommerce_payments_charge ) => $woocommerce_payments_charge->get_payment_method_details()['card']['funding'] ?? $value, 10, 2 );
add_filter( 'sift_for_woocommerce_woocommerce_payments_stripe_brand', fn( $value, $woocommerce_payments_charge ) => $woocommerce_payments_charge->get_payment_method_details()['card']['brand'] ?? $value, 10, 2 );
10 changes: 8 additions & 2 deletions tests/PaymentGatewayTest.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<?php
/**
* Class PaymentGatewayTest
*
* @package Sift_Decisions
*/

use Sift_For_WooCommerce\Sift_For_WooCommerce\Payment_Gateway;

require_once __DIR__ . '/../src/inc/payment-gateway.php';
require_once __DIR__ . '/../src/inc/payment-gateways/index.php';
require_once __DIR__ . '/../src/inc/payment-gateways/lib/stripe.php';
require_once __DIR__ . '/../src/inc/payment-gateways/stripe.php';
require_once __DIR__ . '/../src/inc/payment-gateways/transact.php';
require_once __DIR__ . '/../src/inc/payment-gateways/woocommerce-payments.php';

/**
* Tests for payment gateway interoperability
Expand Down Expand Up @@ -55,6 +55,12 @@ public function payment_gateway_provider(): array {
'is_valid' => true,
'sift_slug' => '$stripe',
),
'WooCommerce Payments is a valid payment gateway' => array(
'woo_gateway_id' => 'woocommerce_payments',
'expected_woo_gateway_id' => 'woocommerce_payments',
'is_valid' => true,
'sift_slug' => '$stripe',
),
'WooPayments is a valid payment gateway' => array(
'woo_gateway_id' => 'woopayments',
'expected_woo_gateway_id' => 'woopayments',
Expand Down
Loading

0 comments on commit 4da08e0

Please sign in to comment.