Skip to content

Commit

Permalink
Terminal In-Person Payments auto-renewing subscription support (#8390)
Browse files Browse the repository at this point in the history
Co-authored-by: Vladimir Reznichenko <[email protected]>
  • Loading branch information
joshheald and kalessil authored May 3, 2024
1 parent 80e006e commit d27b16f
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 28 deletions.
4 changes: 4 additions & 0 deletions changelog/terminal-payment-subscription-support
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: add

Support for starting auto-renewing subscriptions for In-Person Payments.
42 changes: 37 additions & 5 deletions includes/admin/class-wc-rest-payments-orders-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,28 @@ class WC_REST_Payments_Orders_Controller extends WC_Payments_REST_Controller {
*/
private $order_service;

/**
* WC_Payments_Token instance for working with customer tokens
*
* @var WC_Payments_Token_Service
*/
private $token_service;

/**
* WC_Payments_REST_Controller constructor.
*
* @param WC_Payments_API_Client $api_client WooCommerce Payments API client.
* @param WC_Payment_Gateway_WCPay $gateway WooCommerce Payments payment gateway.
* @param WC_Payments_Customer_Service $customer_service Customer class instance.
* @param WC_Payments_Order_Service $order_service Order Service class instance.
* @param WC_Payments_Token_Service $token_service Token Service class instance.
*/
public function __construct( WC_Payments_API_Client $api_client, WC_Payment_Gateway_WCPay $gateway, WC_Payments_Customer_Service $customer_service, WC_Payments_Order_Service $order_service ) {
public function __construct( WC_Payments_API_Client $api_client, WC_Payment_Gateway_WCPay $gateway, WC_Payments_Customer_Service $customer_service, WC_Payments_Order_Service $order_service, WC_Payments_Token_Service $token_service ) {
parent::__construct( $api_client );
$this->gateway = $gateway;
$this->customer_service = $customer_service;
$this->order_service = $order_service;
$this->token_service = $token_service;
}

/**
Expand Down Expand Up @@ -217,6 +226,30 @@ public function capture_terminal_payment( WP_REST_Request $request ) {
}
// Store receipt generation URL for mobile applications in order meta-data.
$order->add_meta_data( 'receipt_url', get_rest_url( null, sprintf( '%s/payments/readers/receipts/%s', $this->namespace, $intent->get_id() ) ) );

// Add payment method for future subscription payments.
$generated_card = $intent->get_charge()->get_payment_method_details()[ Payment_Method::CARD_PRESENT ]['generated_card'] ?? null;
// If we don't get a generated card, e.g. because a digital wallet was used, we can still return that the initial payment was successful.
// The subscription will not be activated and customers will need to provide a new payment method for renewals.
if ( $generated_card ) {
$has_subscriptions = function_exists( 'wcs_order_contains_subscription' ) &&
function_exists( 'wcs_get_subscriptions_for_order' ) &&
function_exists( 'wcs_is_manual_renewal_required' ) &&
wcs_order_contains_subscription( $order_id );
if ( $has_subscriptions ) {
$token = $this->token_service->add_payment_method_to_user( $generated_card, $order->get_user() );
$this->gateway->add_token_to_order( $order, $token );
foreach ( wcs_get_subscriptions_for_order( $order ) as $subscription ) {
$subscription->set_payment_method( WC_Payment_Gateway_WCPay::GATEWAY_ID );
// Where the setting doesn't force manual renewals, we should turn them off, because we have an auto-renewal token now.
if ( ! wcs_is_manual_renewal_required() ) {
$subscription->set_requires_manual_renewal( false );
}
$subscription->save();
}
}
}

// Actualize order status.
$this->order_service->mark_terminal_payment_completed( $order, $intent_id, $result['status'] );

Expand Down Expand Up @@ -307,15 +340,14 @@ public function capture_authorization( WP_REST_Request $request ) {

/**
* Returns customer id from order. Create or update customer if needed.
* Use-cases: It was used by older versions of our Mobile apps in their workflows.
*
* @deprecated 3.9.0
* Use-cases:
* - It was used by older versions of our mobile apps to add the customer details to Payment Intents.
* - It is used by the apps to set customer details on Payment Intents for an order containing subscriptions. Required for capturing renewal payments off session.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_customer( $request ) {
wc_deprecated_function( __FUNCTION__, '3.9.0' );
try {
$order_id = $request['order_id'];

Expand Down
8 changes: 8 additions & 0 deletions includes/class-wc-payments-token-service.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ public function add_token_to_user( $payment_method, $user ) {
$token->set_gateway_id( $gateway_id );
$token->set_email( $payment_method[ Payment_Method::LINK ]['email'] );
break;
case Payment_Method::CARD_PRESENT:
$token = new WC_Payment_Token_CC();
$token->set_gateway_id( CC_Payment_Gateway::GATEWAY_ID );
$token->set_expiry_month( $payment_method[ Payment_Method::CARD_PRESENT ]['exp_month'] );
$token->set_expiry_year( $payment_method[ Payment_Method::CARD_PRESENT ]['exp_year'] );
$token->set_card_type( strtolower( $payment_method[ Payment_Method::CARD_PRESENT ]['brand'] ) );
$token->set_last4( $payment_method[ Payment_Method::CARD_PRESENT ]['last4'] );
break;
default:
$token = new WC_Payment_Token_CC();
$token->set_gateway_id( CC_Payment_Gateway::GATEWAY_ID );
Expand Down
2 changes: 1 addition & 1 deletion includes/class-wc-payments.php
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@ public static function init_rest_api() {
$conn_tokens_controller->register_routes();

include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-orders-controller.php';
$orders_controller = new WC_REST_Payments_Orders_Controller( self::$api_client, self::get_gateway(), self::$customer_service, self::$order_service );
$orders_controller = new WC_REST_Payments_Orders_Controller( self::$api_client, self::get_gateway(), self::$customer_service, self::$order_service, self::$token_service );
$orders_controller->register_routes();

include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-fraud-outcomes-controller.php';
Expand Down
7 changes: 7 additions & 0 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,11 @@
<code>$stripe_billing_migrator</code>
</MissingDependency>
</file>
<file src="includes/admin/class-wc-rest-payments-orders-controller.php">
<UndefinedFunction occurrences="3">
<code>wcs_get_subscriptions_for_order( $order )</code>
<code>wcs_is_manual_renewal_required()</code>
<code>wcs_order_contains_subscription( $order_id )</code>
</UndefinedFunction>
</file>
</files>
Loading

0 comments on commit d27b16f

Please sign in to comment.