mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-05 08:59:14 +08:00
Merge pull request #1986 from woocommerce/PCP-2521-apple-pay-recurring-payments
Apple Pay recurring payments (2521)
This commit is contained in:
commit
037f650288
20 changed files with 437 additions and 37 deletions
|
@ -25,8 +25,8 @@ class FraudProcessorResponseFactory {
|
||||||
* @return FraudProcessorResponse
|
* @return FraudProcessorResponse
|
||||||
*/
|
*/
|
||||||
public function from_paypal_response( stdClass $data ): FraudProcessorResponse {
|
public function from_paypal_response( stdClass $data ): FraudProcessorResponse {
|
||||||
$avs_code = $data->avs_code ?: null;
|
$avs_code = ( $data->avs_code ?? null ) ?: null;
|
||||||
$cvv_code = $data->cvv_code ?: null;
|
$cvv_code = ( $data->cvv_code ?? null ) ?: null;
|
||||||
|
|
||||||
return new FraudProcessorResponse( $avs_code, $cvv_code );
|
return new FraudProcessorResponse( $avs_code, $cvv_code );
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,10 +70,6 @@ class ApplepayButton {
|
||||||
|
|
||||||
if (this.isEligible) {
|
if (this.isEligible) {
|
||||||
this.fetchTransactionInfo().then(() => {
|
this.fetchTransactionInfo().then(() => {
|
||||||
const isSubscriptionProduct = this.ppcpConfig?.data_client_id?.has_subscriptions === true;
|
|
||||||
if (isSubscriptionProduct) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.addButton();
|
this.addButton();
|
||||||
const id_minicart = "#apple-" + this.buttonConfig.button.mini_cart_wrapper;
|
const id_minicart = "#apple-" + this.buttonConfig.button.mini_cart_wrapper;
|
||||||
const id = "#apple-" + this.buttonConfig.button.wrapper;
|
const id = "#apple-" + this.buttonConfig.button.wrapper;
|
||||||
|
@ -214,6 +210,8 @@ class ApplepayButton {
|
||||||
|
|
||||||
const paymentRequest = this.paymentRequest();
|
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.
|
// Trigger woocommerce validation if we are in the checkout page.
|
||||||
if (this.context === 'checkout') {
|
if (this.context === 'checkout') {
|
||||||
const checkoutFormSelector = 'form.woocommerce-checkout';
|
const checkoutFormSelector = 'form.woocommerce-checkout';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
|
import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
|
||||||
import CartActionHandler
|
import CartActionHandler
|
||||||
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler";
|
from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler";
|
||||||
|
import {isPayPalSubscription} from "../../../../ppcp-blocks/resources/js/Helper/Subscription";
|
||||||
|
|
||||||
class BaseHandler {
|
class BaseHandler {
|
||||||
|
|
||||||
|
@ -9,9 +10,15 @@ class BaseHandler {
|
||||||
this.ppcpConfig = ppcpConfig;
|
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() {
|
validateContext() {
|
||||||
if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) {
|
if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) {
|
||||||
return false;
|
return this.isVaultV3Mode();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ class PayNowHandler extends BaseHandler {
|
||||||
|
|
||||||
validateContext() {
|
validateContext() {
|
||||||
if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) {
|
if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) {
|
||||||
return false;
|
return this.isVaultV3Mode();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ class SingleProductHandler extends BaseHandler {
|
||||||
|
|
||||||
validateContext() {
|
validateContext() {
|
||||||
if ( this.ppcpConfig?.locations_with_subscription_product?.product ) {
|
if ( this.ppcpConfig?.locations_with_subscription_product?.product ) {
|
||||||
return false;
|
return this.isVaultV3Mode();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import {useEffect, useState} from '@wordpress/element';
|
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 {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
|
||||||
|
import {cartHasSubscriptionProducts} from '../../../ppcp-blocks/resources/js/Helper/Subscription'
|
||||||
import ApplepayManager from "./ApplepayManager";
|
import ApplepayManager from "./ApplepayManager";
|
||||||
import {loadCustomScript} from "@paypal/paypal-js";
|
import {loadCustomScript} from "@paypal/paypal-js";
|
||||||
|
import CheckoutHandler from "./Context/CheckoutHandler";
|
||||||
|
|
||||||
const ppcpData = wc.wcSettings.getSetting('ppcp-gateway_data');
|
const ppcpData = wc.wcSettings.getSetting('ppcp-gateway_data');
|
||||||
const ppcpConfig = ppcpData.scriptData;
|
const ppcpConfig = ppcpData.scriptData;
|
||||||
|
@ -50,6 +52,13 @@ const ApplePayComponent = () => {
|
||||||
|
|
||||||
const features = ['products'];
|
const features = ['products'];
|
||||||
|
|
||||||
|
if (
|
||||||
|
cartHasSubscriptionProducts(ppcpConfig)
|
||||||
|
&& (new CheckoutHandler(buttonConfig, ppcpConfig)).isVaultV3Mode()
|
||||||
|
) {
|
||||||
|
features.push('subscriptions');
|
||||||
|
}
|
||||||
|
|
||||||
registerExpressPaymentMethod({
|
registerExpressPaymentMethod({
|
||||||
name: buttonData.id,
|
name: buttonData.id,
|
||||||
label: <div dangerouslySetInnerHTML={{__html: buttonData.title}}/>,
|
label: <div dangerouslySetInnerHTML={{__html: buttonData.title}}/>,
|
||||||
|
|
|
@ -87,6 +87,7 @@ const PayPalComponent = ({
|
||||||
bn_code: '',
|
bn_code: '',
|
||||||
context: config.scriptData.context,
|
context: config.scriptData.context,
|
||||||
payment_method: 'ppcp-gateway',
|
payment_method: 'ppcp-gateway',
|
||||||
|
funding_source: window.ppcpFundingSource ?? 'paypal',
|
||||||
createaccount: false
|
createaccount: false
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
@ -325,8 +326,6 @@ const PayPalComponent = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubscriptionShippingChange = async (data, actions) => {
|
handleSubscriptionShippingChange = async (data, actions) => {
|
||||||
console.log('--- handleSubscriptionShippingChange', data, actions);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const shippingOptionId = data.selected_shipping_option?.id;
|
const shippingOptionId = data.selected_shipping_option?.id;
|
||||||
if (shippingOptionId) {
|
if (shippingOptionId) {
|
||||||
|
@ -476,6 +475,14 @@ if(cartHasSubscriptionProducts(config.scriptData)) {
|
||||||
block_enabled = false;
|
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
|
// Don't render buttons if in subscription mode and product not associated with a PayPal subscription
|
||||||
if(
|
if(
|
||||||
isPayPalSubscription(config.scriptData)
|
isPayPalSubscription(config.scriptData)
|
||||||
|
|
|
@ -7,7 +7,6 @@ class ButtonModuleWatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
watchContextBootstrap(callable) {
|
watchContextBootstrap(callable) {
|
||||||
console.log('ButtonModuleWatcher.js: watchContextBootstrap', this.contextBootstrapRegistry)
|
|
||||||
this.contextBootstrapWatchers.push(callable);
|
this.contextBootstrapWatchers.push(callable);
|
||||||
Object.values(this.contextBootstrapRegistry).forEach(callable);
|
Object.values(this.contextBootstrapRegistry).forEach(callable);
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,6 +148,21 @@ class SavePaymentMethodsModule implements ModuleInterface {
|
||||||
'vault' => array(
|
'vault' => array(
|
||||||
'store_in_vault' => 'ON_SUCCESS',
|
'store_in_vault' => 'ON_SUCCESS',
|
||||||
'usage_type' => 'MERCHANT',
|
'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',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -159,6 +174,7 @@ class SavePaymentMethodsModule implements ModuleInterface {
|
||||||
'vault' => array(
|
'vault' => array(
|
||||||
'store_in_vault' => 'ON_SUCCESS',
|
'store_in_vault' => 'ON_SUCCESS',
|
||||||
'usage_type' => 'MERCHANT',
|
'usage_type' => 'MERCHANT',
|
||||||
|
'permit_multiple_payment_tokens' => true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -207,11 +223,29 @@ class SavePaymentMethodsModule implements ModuleInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $wc_order->get_payment_method() === PayPalGateway::ID ) {
|
if ( $wc_order->get_payment_method() === PayPalGateway::ID ) {
|
||||||
$wc_payment_tokens->create_payment_token_paypal(
|
switch ( $payment_source->name() ) {
|
||||||
$wc_order->get_customer_id(),
|
case 'venmo':
|
||||||
$token_id,
|
$wc_payment_tokens->create_payment_token_venmo(
|
||||||
$payment_source->properties()->email_address ?? ''
|
$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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,9 +14,11 @@ use Psr\Log\LoggerInterface;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
use WC_Payment_Token_CC;
|
use WC_Payment_Token_CC;
|
||||||
use WC_Payment_Tokens;
|
use WC_Payment_Tokens;
|
||||||
|
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenApplePay;
|
||||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory;
|
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory;
|
||||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper;
|
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper;
|
||||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal;
|
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal;
|
||||||
|
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenVenmo;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||||
|
|
||||||
|
@ -66,9 +68,9 @@ class WooCommercePaymentTokens {
|
||||||
/**
|
/**
|
||||||
* Creates a WC Payment Token for PayPal payment.
|
* Creates a WC Payment Token for PayPal payment.
|
||||||
*
|
*
|
||||||
* @param int $customer_id The WC customer ID.
|
* @param int $customer_id The WC customer ID.
|
||||||
* @param string $token The PayPal payment token.
|
* @param string $token The PayPal payment token.
|
||||||
* @param string $email The PayPal customer email.
|
* @param string $email The PayPal customer email.
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
|
@ -79,11 +81,17 @@ class WooCommercePaymentTokens {
|
||||||
): int {
|
): int {
|
||||||
|
|
||||||
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID );
|
$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;
|
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 );
|
assert( $payment_token_paypal instanceof PaymentTokenPayPal );
|
||||||
|
|
||||||
$payment_token_paypal->set_token( $token );
|
$payment_token_paypal->set_token( $token );
|
||||||
|
@ -105,6 +113,96 @@ class WooCommercePaymentTokens {
|
||||||
return $payment_token_paypal->get_id();
|
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.
|
* Creates a WC Payment Token for Credit Card payment.
|
||||||
*
|
*
|
||||||
|
|
31
modules/ppcp-vaulting/src/PaymentTokenApplePay.php
Normal file
31
modules/ppcp-vaulting/src/PaymentTokenApplePay.php
Normal file
|
@ -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();
|
||||||
|
}
|
|
@ -19,12 +19,16 @@ class PaymentTokenFactory {
|
||||||
*
|
*
|
||||||
* @param string $type The type of WC payment token.
|
* @param string $type The type of WC payment token.
|
||||||
*
|
*
|
||||||
* @return void|PaymentTokenPayPal
|
* @return void|PaymentTokenPayPal|PaymentTokenVenmo|PaymentTokenApplePay
|
||||||
*/
|
*/
|
||||||
public function create( string $type ) {
|
public function create( string $type ) {
|
||||||
switch ( $type ) {
|
switch ( $type ) {
|
||||||
case 'paypal':
|
case 'paypal':
|
||||||
return new PaymentTokenPayPal();
|
return new PaymentTokenPayPal();
|
||||||
|
case 'venmo':
|
||||||
|
return new PaymentTokenVenmo();
|
||||||
|
case 'apple_pay':
|
||||||
|
return new PaymentTokenApplePay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,15 +21,39 @@ class PaymentTokenHelper {
|
||||||
*
|
*
|
||||||
* @param WC_Payment_Token[] $wc_tokens WC Payment Tokens.
|
* @param WC_Payment_Token[] $wc_tokens WC Payment Tokens.
|
||||||
* @param string $token_id Payment Token ID.
|
* @param string $token_id Payment Token ID.
|
||||||
|
* @param ?string $class_name Class name of the token.
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function token_exist( array $wc_tokens, string $token_id ): bool {
|
public function token_exist( array $wc_tokens, string $token_id, string $class_name = null ): bool {
|
||||||
foreach ( $wc_tokens as $wc_token ) {
|
foreach ( $wc_tokens as $wc_token ) {
|
||||||
if ( $wc_token->get_token() === $token_id ) {
|
if ( $wc_token->get_token() === $token_id ) {
|
||||||
return true;
|
if ( null !== $class_name ) {
|
||||||
|
if ( $wc_token instanceof $class_name ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if given token exist as WC Payment Token.
|
||||||
|
*
|
||||||
|
* @param array $wc_tokens WC Payment Tokens.
|
||||||
|
* @param string $class_name Class name of the token.
|
||||||
|
* @return null|WC_Payment_Token
|
||||||
|
*/
|
||||||
|
public function first_token_of_type( array $wc_tokens, string $class_name ) {
|
||||||
|
foreach ( $wc_tokens as $wc_token ) {
|
||||||
|
if ( $wc_token instanceof $class_name ) {
|
||||||
|
return $wc_token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
51
modules/ppcp-vaulting/src/PaymentTokenVenmo.php
Normal file
51
modules/ppcp-vaulting/src/PaymentTokenVenmo.php
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* WooCommerce Payment token for Venmo.
|
||||||
|
*
|
||||||
|
* @package WooCommerce\PayPalCommerce\Vaulting
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace WooCommerce\PayPalCommerce\Vaulting;
|
||||||
|
|
||||||
|
use WC_Payment_Token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class PaymentTokenVenmo
|
||||||
|
*/
|
||||||
|
class PaymentTokenVenmo extends WC_Payment_Token {
|
||||||
|
/**
|
||||||
|
* Token Type String.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $type = 'Venmo';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extra data.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $extra_data = array(
|
||||||
|
'email' => '',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get PayPal account email.
|
||||||
|
*
|
||||||
|
* @return string PayPal account email.
|
||||||
|
*/
|
||||||
|
public function get_email() {
|
||||||
|
return $this->get_meta( 'email' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set PayPal account email.
|
||||||
|
*
|
||||||
|
* @param string $email PayPal account email.
|
||||||
|
*/
|
||||||
|
public function set_email( $email ) {
|
||||||
|
$this->add_meta_data( 'email', $email, true );
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,7 +107,7 @@ class PaymentTokensMigration {
|
||||||
}
|
}
|
||||||
} elseif ( $token->source()->paypal ) {
|
} elseif ( $token->source()->paypal ) {
|
||||||
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $id, PayPalGateway::ID );
|
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $id, PayPalGateway::ID );
|
||||||
if ( $this->payment_token_helper->token_exist( $wc_tokens, $token->id() ) ) {
|
if ( $this->payment_token_helper->token_exist( $wc_tokens, $token->id(), PaymentTokenPayPal::class ) ) {
|
||||||
$this->logger->info( 'Token already exist for user ' . (string) $id );
|
$this->logger->info( 'Token already exist for user ' . (string) $id );
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,11 +81,50 @@ class VaultingModule implements ModuleInterface {
|
||||||
if ( $type === 'WC_Payment_Token_PayPal' ) {
|
if ( $type === 'WC_Payment_Token_PayPal' ) {
|
||||||
return PaymentTokenPayPal::class;
|
return PaymentTokenPayPal::class;
|
||||||
}
|
}
|
||||||
|
if ( $type === 'WC_Payment_Token_Venmo' ) {
|
||||||
|
return PaymentTokenVenmo::class;
|
||||||
|
}
|
||||||
|
if ( $type === 'WC_Payment_Token_ApplePay' ) {
|
||||||
|
return PaymentTokenApplePay::class;
|
||||||
|
}
|
||||||
|
|
||||||
return $type;
|
return $type;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_filter(
|
||||||
|
'woocommerce_get_customer_payment_tokens',
|
||||||
|
/**
|
||||||
|
* Filter available payment tokens depending on context.
|
||||||
|
*
|
||||||
|
* @psalm-suppress MissingClosureParamType
|
||||||
|
* @psalm-suppress MissingClosureReturnType
|
||||||
|
*/
|
||||||
|
function( $tokens, $customer_id, $gateway_id ) {
|
||||||
|
if ( ! is_array( $tokens ) ) {
|
||||||
|
return $tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_post = isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'POST';
|
||||||
|
|
||||||
|
// Exclude ApplePay tokens from payment pages.
|
||||||
|
if (
|
||||||
|
( is_checkout() || is_cart() || is_product() )
|
||||||
|
&& ! $is_post // Don't check on POST so we have all payment methods on form submissions.
|
||||||
|
) {
|
||||||
|
foreach ( $tokens as $index => $token ) {
|
||||||
|
if ( $token instanceof PaymentTokenApplePay ) {
|
||||||
|
unset( $tokens[ $index ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tokens;
|
||||||
|
},
|
||||||
|
10,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
|
||||||
add_filter(
|
add_filter(
|
||||||
'woocommerce_payment_methods_list_item',
|
'woocommerce_payment_methods_list_item',
|
||||||
/**
|
/**
|
||||||
|
@ -98,10 +137,18 @@ class VaultingModule implements ModuleInterface {
|
||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( strtolower( $payment_token->get_type() ) === 'paypal' ) {
|
if ( $payment_token instanceof PaymentTokenPayPal ) {
|
||||||
assert( $payment_token instanceof PaymentTokenPayPal );
|
$item['method']['brand'] = 'PayPal / ' . $payment_token->get_email();
|
||||||
$item['method']['brand'] = $payment_token->get_email();
|
return $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $payment_token instanceof PaymentTokenVenmo ) {
|
||||||
|
$item['method']['brand'] = 'Venmo / ' . $payment_token->get_email();
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $payment_token instanceof PaymentTokenApplePay ) {
|
||||||
|
$item['method']['brand'] = 'ApplePay #' . ( (string) $payment_token->get_id() );
|
||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,8 @@ class FundingSourceRenderer {
|
||||||
* @param string $id The ID of the funding source, such as 'venmo'.
|
* @param string $id The ID of the funding source, such as 'venmo'.
|
||||||
*/
|
*/
|
||||||
public function render_name( string $id ): string {
|
public function render_name( string $id ): string {
|
||||||
|
$id = $this->sanitize_id( $id );
|
||||||
|
|
||||||
if ( array_key_exists( $id, $this->funding_sources ) ) {
|
if ( array_key_exists( $id, $this->funding_sources ) ) {
|
||||||
if ( in_array( $id, $this->own_funding_sources, true ) ) {
|
if ( in_array( $id, $this->own_funding_sources, true ) ) {
|
||||||
return $this->funding_sources[ $id ];
|
return $this->funding_sources[ $id ];
|
||||||
|
@ -78,6 +80,8 @@ class FundingSourceRenderer {
|
||||||
* @param string $id The ID of the funding source, such as 'venmo'.
|
* @param string $id The ID of the funding source, such as 'venmo'.
|
||||||
*/
|
*/
|
||||||
public function render_description( string $id ): string {
|
public function render_description( string $id ): string {
|
||||||
|
$id = $this->sanitize_id( $id );
|
||||||
|
|
||||||
if ( array_key_exists( $id, $this->funding_sources ) ) {
|
if ( array_key_exists( $id, $this->funding_sources ) ) {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
/* translators: %s - Sofort, BLIK, iDeal, Mercado Pago, etc. */
|
/* translators: %s - Sofort, BLIK, iDeal, Mercado Pago, etc. */
|
||||||
|
@ -90,4 +94,14 @@ class FundingSourceRenderer {
|
||||||
$this->settings->get( 'description' )
|
$this->settings->get( 'description' )
|
||||||
: __( 'Pay via PayPal.', 'woocommerce-paypal-payments' );
|
: __( 'Pay via PayPal.', 'woocommerce-paypal-payments' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitizes the id to a standard format.
|
||||||
|
*
|
||||||
|
* @param string $id The funding source id.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function sanitize_id( string $id ): string {
|
||||||
|
return str_replace( '_', '', strtolower( $id ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ return array(
|
||||||
$environment = $container->get( 'onboarding.environment' );
|
$environment = $container->get( 'onboarding.environment' );
|
||||||
$settings = $container->get( 'wcgateway.settings' );
|
$settings = $container->get( 'wcgateway.settings' );
|
||||||
$authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' );
|
$authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' );
|
||||||
|
$funding_source_renderer = $container->get( 'wcgateway.funding-source.renderer' );
|
||||||
return new RenewalHandler(
|
return new RenewalHandler(
|
||||||
$logger,
|
$logger,
|
||||||
$repository,
|
$repository,
|
||||||
|
@ -36,7 +37,8 @@ return array(
|
||||||
$payer_factory,
|
$payer_factory,
|
||||||
$environment,
|
$environment,
|
||||||
$settings,
|
$settings,
|
||||||
$authorized_payments_processor
|
$authorized_payments_processor,
|
||||||
|
$funding_source_renderer
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'wc-subscriptions.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository {
|
'wc-subscriptions.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository {
|
||||||
|
|
|
@ -22,9 +22,13 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
||||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
|
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
|
||||||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||||
|
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenApplePay;
|
||||||
|
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal;
|
||||||
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenVenmo;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||||
|
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
|
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
|
||||||
|
@ -105,6 +109,13 @@ class RenewalHandler {
|
||||||
*/
|
*/
|
||||||
protected $authorized_payments_processor;
|
protected $authorized_payments_processor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The funding source renderer.
|
||||||
|
*
|
||||||
|
* @var FundingSourceRenderer
|
||||||
|
*/
|
||||||
|
protected $funding_source_renderer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RenewalHandler constructor.
|
* RenewalHandler constructor.
|
||||||
*
|
*
|
||||||
|
@ -117,6 +128,7 @@ class RenewalHandler {
|
||||||
* @param Environment $environment The environment.
|
* @param Environment $environment The environment.
|
||||||
* @param Settings $settings The Settings.
|
* @param Settings $settings The Settings.
|
||||||
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor.
|
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor.
|
||||||
|
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
|
@ -127,7 +139,8 @@ class RenewalHandler {
|
||||||
PayerFactory $payer_factory,
|
PayerFactory $payer_factory,
|
||||||
Environment $environment,
|
Environment $environment,
|
||||||
Settings $settings,
|
Settings $settings,
|
||||||
AuthorizedPaymentsProcessor $authorized_payments_processor
|
AuthorizedPaymentsProcessor $authorized_payments_processor,
|
||||||
|
FundingSourceRenderer $funding_source_renderer
|
||||||
) {
|
) {
|
||||||
|
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
|
@ -139,6 +152,7 @@ class RenewalHandler {
|
||||||
$this->environment = $environment;
|
$this->environment = $environment;
|
||||||
$this->settings = $settings;
|
$this->settings = $settings;
|
||||||
$this->authorized_payments_processor = $authorized_payments_processor;
|
$this->authorized_payments_processor = $authorized_payments_processor;
|
||||||
|
$this->funding_source_renderer = $funding_source_renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -202,11 +216,31 @@ class RenewalHandler {
|
||||||
if ( $wc_order->get_payment_method() === PayPalGateway::ID ) {
|
if ( $wc_order->get_payment_method() === PayPalGateway::ID ) {
|
||||||
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $wc_order->get_customer_id(), PayPalGateway::ID );
|
$wc_tokens = WC_Payment_Tokens::get_customer_tokens( $wc_order->get_customer_id(), PayPalGateway::ID );
|
||||||
foreach ( $wc_tokens as $token ) {
|
foreach ( $wc_tokens as $token ) {
|
||||||
|
$name = 'paypal';
|
||||||
|
$properties = array(
|
||||||
|
'vault_id' => $token->get_token(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $token instanceof PaymentTokenPayPal ) {
|
||||||
|
$name = 'paypal';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $token instanceof PaymentTokenVenmo ) {
|
||||||
|
$name = 'venmo';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $token instanceof PaymentTokenApplePay ) {
|
||||||
|
$name = 'apple_pay';
|
||||||
|
$properties['stored_credential'] = array(
|
||||||
|
'payment_initiator' => 'MERCHANT',
|
||||||
|
'payment_type' => 'RECURRING',
|
||||||
|
'usage' => 'SUBSEQUENT',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$payment_source = new PaymentSource(
|
$payment_source = new PaymentSource(
|
||||||
'paypal',
|
$name,
|
||||||
(object) array(
|
(object) $properties
|
||||||
'vault_id' => $token->get_token(),
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -387,6 +421,11 @@ class RenewalHandler {
|
||||||
if ( $transaction_id ) {
|
if ( $transaction_id ) {
|
||||||
$this->update_transaction_id( $transaction_id, $wc_order );
|
$this->update_transaction_id( $transaction_id, $wc_order );
|
||||||
|
|
||||||
|
$payment_source = $order->payment_source();
|
||||||
|
if ( $payment_source instanceof PaymentSource ) {
|
||||||
|
$this->update_payment_source( $payment_source, $wc_order );
|
||||||
|
}
|
||||||
|
|
||||||
$subscriptions = wcs_get_subscriptions_for_order( $wc_order->get_id(), array( 'order_type' => 'any' ) );
|
$subscriptions = wcs_get_subscriptions_for_order( $wc_order->get_id(), array( 'order_type' => 'any' ) );
|
||||||
foreach ( $subscriptions as $id => $subscription ) {
|
foreach ( $subscriptions as $id => $subscription ) {
|
||||||
$subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id );
|
$subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id );
|
||||||
|
@ -440,4 +479,29 @@ class RenewalHandler {
|
||||||
(object) $properties
|
(object) $properties
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the payment source name to the one really used for the payment.
|
||||||
|
*
|
||||||
|
* @param PaymentSource $payment_source The Payment Source.
|
||||||
|
* @param \WC_Order $wc_order WC order.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function update_payment_source( PaymentSource $payment_source, \WC_Order $wc_order ): void {
|
||||||
|
if ( ! $payment_source->name() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$wc_order->set_payment_method_title( $this->funding_source_renderer->render_name( $payment_source->name() ) );
|
||||||
|
$wc_order->save();
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
$this->logger->error(
|
||||||
|
sprintf(
|
||||||
|
'Failed to update payment source to "%1$s" on order %2$d',
|
||||||
|
$payment_source->name(),
|
||||||
|
$wc_order->get_id()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,17 @@ class WcSubscriptionsModule implements ModuleInterface {
|
||||||
$subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id );
|
$subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id );
|
||||||
$subscription->save();
|
$subscription->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the initial payment method title if not the same as the first order.
|
||||||
|
$payment_method_title = $parent_order->get_payment_method_title();
|
||||||
|
if (
|
||||||
|
$payment_method_title
|
||||||
|
&& $subscription instanceof \WC_Subscription
|
||||||
|
&& $subscription->get_payment_method_title() !== $payment_method_title
|
||||||
|
) {
|
||||||
|
$subscription->set_payment_method_title( $payment_method_title );
|
||||||
|
$subscription->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,7 +322,7 @@ class WcSubscriptionsModule implements ModuleInterface {
|
||||||
foreach ( $tokens as $token ) {
|
foreach ( $tokens as $token ) {
|
||||||
$output .= '<li>';
|
$output .= '<li>';
|
||||||
$output .= sprintf( '<input name="saved_paypal_payment" type="radio" value="%s" style="width:auto;" checked="checked">', $token->get_id() );
|
$output .= sprintf( '<input name="saved_paypal_payment" type="radio" value="%s" style="width:auto;" checked="checked">', $token->get_id() );
|
||||||
$output .= sprintf( '<label for="saved_paypal_payment">%s</label>', $token->get_meta( 'email' ) ?? '' );
|
$output .= sprintf( '<label for="saved_paypal_payment">%s / %s</label>', $token->get_type(), $token->get_meta( 'email' ) ?? '' );
|
||||||
$output .= '</li>';
|
$output .= '</li>';
|
||||||
}
|
}
|
||||||
$output .= '</ul>';
|
$output .= '</ul>';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue