Skip to content

Commit

Permalink
Merge pull request #1986 from woocommerce/PCP-2521-apple-pay-recurrin…
Browse files Browse the repository at this point in the history
…g-payments

Apple Pay recurring payments (2521)
  • Loading branch information
Dinamiko authored Feb 7, 2024
2 parents bcbcd4a + 5272087 commit 037f650
Show file tree
Hide file tree
Showing 20 changed files with 437 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ class FraudProcessorResponseFactory {
* @return FraudProcessorResponse
*/
public function from_paypal_response( stdClass $data ): FraudProcessorResponse {
$avs_code = $data->avs_code ?: null;
$cvv_code = $data->cvv_code ?: null;
$avs_code = ( $data->avs_code ?? null ) ?: null;
$cvv_code = ( $data->cvv_code ?? null ) ?: null;

return new FraudProcessorResponse( $avs_code, $cvv_code );
}
Expand Down
6 changes: 2 additions & 4 deletions modules/ppcp-applepay/resources/js/ApplepayButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ class ApplepayButton {

if (this.isEligible) {
this.fetchTransactionInfo().then(() => {
const isSubscriptionProduct = this.ppcpConfig?.data_client_id?.has_subscriptions === true;
if (isSubscriptionProduct) {
return;
}
this.addButton();
const id_minicart = "#apple-" + this.buttonConfig.button.mini_cart_wrapper;
const id = "#apple-" + this.buttonConfig.button.wrapper;
Expand Down Expand Up @@ -214,6 +210,8 @@ class ApplepayButton {

const paymentRequest = this.paymentRequest();

window.ppcpFundingSource = 'apple_pay'; // Do this on another place like on create order endpoint handler.

// Trigger woocommerce validation if we are in the checkout page.
if (this.context === 'checkout') {
const checkoutFormSelector = 'form.woocommerce-checkout';
Expand Down
9 changes: 8 additions & 1 deletion modules/ppcp-applepay/resources/js/Context/BaseHandler.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
import CartActionHandler
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler";
import {isPayPalSubscription} from "../../../../ppcp-blocks/resources/js/Helper/Subscription";

class BaseHandler {

Expand All @@ -9,9 +10,15 @@ class BaseHandler {
this.ppcpConfig = ppcpConfig;
}

isVaultV3Mode() {
return this.ppcpConfig?.save_payment_methods?.id_token // vault v3
&& ! this.ppcpConfig?.data_client_id?.paypal_subscriptions_enabled // not PayPal Subscriptions mode
&& this.ppcpConfig?.can_save_vault_token; // vault is enabled
}

validateContext() {
if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) {
return false;
return this.isVaultV3Mode();
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class PayNowHandler extends BaseHandler {

validateContext() {
if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) {
return false;
return this.isVaultV3Mode();
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class SingleProductHandler extends BaseHandler {

validateContext() {
if ( this.ppcpConfig?.locations_with_subscription_product?.product ) {
return false;
return this.isVaultV3Mode();
}
return true;
}
Expand Down
11 changes: 10 additions & 1 deletion modules/ppcp-applepay/resources/js/boot-block.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {useEffect, useState} from '@wordpress/element';
import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry';
import {registerExpressPaymentMethod} from '@woocommerce/blocks-registry';
import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
import {cartHasSubscriptionProducts} from '../../../ppcp-blocks/resources/js/Helper/Subscription'
import ApplepayManager from "./ApplepayManager";
import {loadCustomScript} from "@paypal/paypal-js";
import CheckoutHandler from "./Context/CheckoutHandler";

const ppcpData = wc.wcSettings.getSetting('ppcp-gateway_data');
const ppcpConfig = ppcpData.scriptData;
Expand Down Expand Up @@ -50,6 +52,13 @@ const ApplePayComponent = () => {

const features = ['products'];

if (
cartHasSubscriptionProducts(ppcpConfig)
&& (new CheckoutHandler(buttonConfig, ppcpConfig)).isVaultV3Mode()
) {
features.push('subscriptions');
}

registerExpressPaymentMethod({
name: buttonData.id,
label: <div dangerouslySetInnerHTML={{__html: buttonData.title}}/>,
Expand Down
11 changes: 9 additions & 2 deletions modules/ppcp-blocks/resources/js/checkout-block.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const PayPalComponent = ({
bn_code: '',
context: config.scriptData.context,
payment_method: 'ppcp-gateway',
funding_source: window.ppcpFundingSource ?? 'paypal',
createaccount: false
}),
});
Expand Down Expand Up @@ -325,8 +326,6 @@ const PayPalComponent = ({
};

handleSubscriptionShippingChange = async (data, actions) => {
console.log('--- handleSubscriptionShippingChange', data, actions);

try {
const shippingOptionId = data.selected_shipping_option?.id;
if (shippingOptionId) {
Expand Down Expand Up @@ -476,6 +475,14 @@ if(cartHasSubscriptionProducts(config.scriptData)) {
block_enabled = false;
}

// Don't render if vaulting disabled and is in vault subscription mode
if(
! isPayPalSubscription(config.scriptData)
&& ! config.scriptData.can_save_vault_token
) {
block_enabled = false;
}

// Don't render buttons if in subscription mode and product not associated with a PayPal subscription
if(
isPayPalSubscription(config.scriptData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ class ButtonModuleWatcher {
}

watchContextBootstrap(callable) {
console.log('ButtonModuleWatcher.js: watchContextBootstrap', this.contextBootstrapRegistry)
this.contextBootstrapWatchers.push(callable);
Object.values(this.contextBootstrapRegistry).forEach(callable);
}
Expand Down
44 changes: 39 additions & 5 deletions modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,21 @@ function( array $data, string $payment_method, array $request_data ) use ( $sett
'vault' => array(
'store_in_vault' => 'ON_SUCCESS',
'usage_type' => 'MERCHANT',
'permit_multiple_payment_tokens' => true,
),
),
),
);
} elseif ( $funding_source && $funding_source === 'apple_pay' ) {
$data['payment_source'] = array(
'apple_pay' => array(
'stored_credential' => array(
'payment_initiator' => 'CUSTOMER',
'payment_type' => 'RECURRING',
),
'attributes' => array(
'vault' => array(
'store_in_vault' => 'ON_SUCCESS',
),
),
),
Expand All @@ -159,6 +174,7 @@ function( array $data, string $payment_method, array $request_data ) use ( $sett
'vault' => array(
'store_in_vault' => 'ON_SUCCESS',
'usage_type' => 'MERCHANT',
'permit_multiple_payment_tokens' => true,
),
),
),
Expand Down Expand Up @@ -207,11 +223,29 @@ function( WC_Order $wc_order, Order $order ) use ( $c ) {
}

if ( $wc_order->get_payment_method() === PayPalGateway::ID ) {
$wc_payment_tokens->create_payment_token_paypal(
$wc_order->get_customer_id(),
$token_id,
$payment_source->properties()->email_address ?? ''
);
switch ( $payment_source->name() ) {
case 'venmo':
$wc_payment_tokens->create_payment_token_venmo(
$wc_order->get_customer_id(),
$token_id,
$payment_source->properties()->email_address ?? ''
);
break;
case 'apple_pay':
$wc_payment_tokens->create_payment_token_applepay(
$wc_order->get_customer_id(),
$token_id
);
break;
case 'paypal':
default:
$wc_payment_tokens->create_payment_token_paypal(
$wc_order->get_customer_id(),
$token_id,
$payment_source->properties()->email_address ?? ''
);
break;
}
}
}
},
Expand Down
108 changes: 103 additions & 5 deletions modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
use stdClass;
use WC_Payment_Token_CC;
use WC_Payment_Tokens;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenApplePay;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenVenmo;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;

Expand Down Expand Up @@ -66,9 +68,9 @@ public function __construct(
/**
* Creates a WC Payment Token for PayPal payment.
*
* @param int $customer_id The WC customer ID.
* @param string $token The PayPal payment token.
* @param string $email The PayPal customer email.
* @param int $customer_id The WC customer ID.
* @param string $token The PayPal payment token.
* @param string $email The PayPal customer email.
*
* @return int
*/
Expand All @@ -79,11 +81,17 @@ public function create_payment_token_paypal(
): int {

$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID );
if ( $this->payment_token_helper->token_exist( $wc_tokens, $token ) ) {
if ( $this->payment_token_helper->token_exist( $wc_tokens, $token, PaymentTokenPayPal::class ) ) {
return 0;
}

$payment_token_paypal = $this->payment_token_factory->create( 'paypal' );
// Try to update existing token of type before creating a new one.
$payment_token_paypal = $this->payment_token_helper->first_token_of_type( $wc_tokens, PaymentTokenPayPal::class );

if ( ! $payment_token_paypal ) {
$payment_token_paypal = $this->payment_token_factory->create( 'paypal' );
}

assert( $payment_token_paypal instanceof PaymentTokenPayPal );

$payment_token_paypal->set_token( $token );
Expand All @@ -105,6 +113,96 @@ public function create_payment_token_paypal(
return $payment_token_paypal->get_id();
}

/**
* Creates a WC Payment Token for Venmo payment.
*
* @param int $customer_id The WC customer ID.
* @param string $token The Venmo payment token.
* @param string $email The Venmo customer email.
*
* @return int
*/
public function create_payment_token_venmo(
int $customer_id,
string $token,
string $email
): int {

$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID );
if ( $this->payment_token_helper->token_exist( $wc_tokens, $token, PaymentTokenVenmo::class ) ) {
return 0;
}

// Try to update existing token of type before creating a new one.
$payment_token_venmo = $this->payment_token_helper->first_token_of_type( $wc_tokens, PaymentTokenVenmo::class );

if ( ! $payment_token_venmo ) {
$payment_token_venmo = $this->payment_token_factory->create( 'venmo' );
}

assert( $payment_token_venmo instanceof PaymentTokenVenmo );

$payment_token_venmo->set_token( $token );
$payment_token_venmo->set_user_id( $customer_id );
$payment_token_venmo->set_gateway_id( PayPalGateway::ID );

if ( $email && is_email( $email ) ) {
$payment_token_venmo->set_email( $email );
}

try {
$payment_token_venmo->save();
} catch ( Exception $exception ) {
$this->logger->error(
"Could not create WC payment token Venmo for customer {$customer_id}. " . $exception->getMessage()
);
}

return $payment_token_venmo->get_id();
}

/**
* Creates a WC Payment Token for ApplePay payment.
*
* @param int $customer_id The WC customer ID.
* @param string $token The ApplePay payment token.
*
* @return int
*/
public function create_payment_token_applepay(
int $customer_id,
string $token
): int {

$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID );
if ( $this->payment_token_helper->token_exist( $wc_tokens, $token, PaymentTokenApplePay::class ) ) {
return 0;
}

// Try to update existing token of type before creating a new one.
$payment_token_applepay = $this->payment_token_helper->first_token_of_type( $wc_tokens, PaymentTokenApplePay::class );

if ( ! $payment_token_applepay ) {
$payment_token_applepay = $this->payment_token_factory->create( 'apple_pay' );
}

assert( $payment_token_applepay instanceof PaymentTokenApplePay );

$payment_token_applepay->set_token( $token );
$payment_token_applepay->set_user_id( $customer_id );
$payment_token_applepay->set_gateway_id( PayPalGateway::ID );

try {
$payment_token_applepay->save();
} catch ( Exception $exception ) {
$this->logger->error(
"Could not create WC payment token ApplePay for customer {$customer_id}. " . $exception->getMessage()
);
}

return $payment_token_applepay->get_id();
}

/**
* Creates a WC Payment Token for Credit Card payment.
*
Expand Down
31 changes: 31 additions & 0 deletions modules/ppcp-vaulting/src/PaymentTokenApplePay.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
/**
* WooCommerce Payment token for ApplePay.
*
* @package WooCommerce\PayPalCommerce\Vaulting
*/

declare(strict_types=1);

namespace WooCommerce\PayPalCommerce\Vaulting;

use WC_Payment_Token;

/**
* Class PaymentTokenApplePay
*/
class PaymentTokenApplePay extends WC_Payment_Token {
/**
* Token Type String.
*
* @var string
*/
protected $type = 'ApplePay';

/**
* Extra data.
*
* @var string[]
*/
protected $extra_data = array();
}
6 changes: 5 additions & 1 deletion modules/ppcp-vaulting/src/PaymentTokenFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ class PaymentTokenFactory {
*
* @param string $type The type of WC payment token.
*
* @return void|PaymentTokenPayPal
* @return void|PaymentTokenPayPal|PaymentTokenVenmo|PaymentTokenApplePay
*/
public function create( string $type ) {
switch ( $type ) {
case 'paypal':
return new PaymentTokenPayPal();
case 'venmo':
return new PaymentTokenVenmo();
case 'apple_pay':
return new PaymentTokenApplePay();
}
}
}
Loading

0 comments on commit 037f650

Please sign in to comment.