Skip to content

Commit 4dc98c4

Browse files
peterwilsonccgpressutto5
authored andcommitted
Add support for WooCommerce Deposits when using Apple Pay and Google Pay (#7910)
Co-authored-by: Guilherme Pressutto <[email protected]>
1 parent 76576c7 commit 4dc98c4

File tree

8 files changed

+246
-18
lines changed

8 files changed

+246
-18
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: fix
3+
4+
Added support for WooCommerce Deposits when using Apple Pay and Google Pay

client/checkout/woopay/express-button/use-express-checkout-product-handler.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,7 @@ const useExpressCheckoutProductHandler = ( api ) => {
125125
const formData = new FormData( addOnForm );
126126

127127
formData.forEach( ( value, name ) => {
128-
if (
129-
/^addon-/.test( name ) ||
130-
/^wc_gc_giftcard_/.test( name )
131-
) {
128+
if ( /^(addon-|wc_)/.test( name ) ) {
132129
if ( /\[\]$/.test( name ) ) {
133130
const fieldName = name.substring( 0, name.length - 2 );
134131

client/payment-request/index.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,13 +310,27 @@ jQuery( ( $ ) => {
310310
0
311311
);
312312

313+
// WC Deposits Support.
314+
const depositObject = {};
315+
if ( $( 'input[name=wc_deposit_option]' ).length ) {
316+
depositObject.wc_deposit_option = $(
317+
'input[name=wc_deposit_option]:checked'
318+
).val();
319+
}
320+
if ( $( 'input[name=wc_deposit_payment_plan]' ).length ) {
321+
depositObject.wc_deposit_payment_plan = $(
322+
'input[name=wc_deposit_payment_plan]:checked'
323+
).val();
324+
}
325+
313326
const data = {
314327
product_id: productId,
315328
qty: $( '.quantity .qty' ).val(),
316329
attributes: $( '.variations_form' ).length
317330
? wcpayPaymentRequest.getAttributes().data
318331
: [],
319332
addon_value: addonValue,
333+
...depositObject,
320334
};
321335

322336
return api.paymentRequestGetSelectedProductData( data );
@@ -437,6 +451,18 @@ jQuery( ( $ ) => {
437451
wcpayPaymentRequest.addToCart();
438452
} );
439453

454+
// WooCommerce Deposits support.
455+
// Trigger the "woocommerce_variation_has_changed" event when the deposit option is changed.
456+
$(
457+
'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]'
458+
).on( 'change', () => {
459+
$( 'form' )
460+
.has(
461+
'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]'
462+
)
463+
.trigger( 'woocommerce_variation_has_changed' );
464+
} );
465+
440466
$( document.body ).on( 'woocommerce_variation_has_changed', () => {
441467
wcpayPaymentRequest.blockPaymentRequestButton();
442468

includes/class-wc-payments-payment-request-button-handler.php

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -204,18 +204,41 @@ public function get_button_settings() {
204204
* Gets the product total price.
205205
*
206206
* @param object $product WC_Product_* object.
207+
* @param bool $is_deposit Whether customer is paying a deposit.
208+
* @param int $deposit_plan_id The ID of the deposit plan.
207209
* @return mixed Total price.
208210
*
209211
* @throws Invalid_Price_Exception Whenever a product has no price.
210212
*/
211-
public function get_product_price( $product ) {
213+
public function get_product_price( $product, ?bool $is_deposit = null, int $deposit_plan_id = 0 ) {
212214
// If prices should include tax, using tax inclusive price.
213215
if ( $this->express_checkout_helper->cart_prices_include_tax() ) {
214216
$base_price = wc_get_price_including_tax( $product );
215217
} else {
216218
$base_price = wc_get_price_excluding_tax( $product );
217219
}
218220

221+
// If WooCommerce Deposits is active, we need to get the correct price for the product.
222+
if ( class_exists( 'WC_Deposits_Product_Manager' ) && WC_Deposits_Product_Manager::deposits_enabled( $product->get_id() ) ) {
223+
// If is_deposit is null, we use the default deposit type for the product.
224+
if ( is_null( $is_deposit ) ) {
225+
$is_deposit = 'deposit' === WC_Deposits_Product_Manager::get_deposit_selected_type( $product->get_id() );
226+
}
227+
if ( $is_deposit ) {
228+
$deposit_type = WC_Deposits_Product_Manager::get_deposit_type( $product->get_id() );
229+
$available_plan_ids = WC_Deposits_Plans_Manager::get_plan_ids_for_product( $product->get_id() );
230+
// Default to first (default) plan if no plan is specified.
231+
if ( 'plan' === $deposit_type && 0 === $deposit_plan_id && ! empty( $available_plan_ids ) ) {
232+
$deposit_plan_id = $available_plan_ids[0];
233+
}
234+
235+
// Ensure the selected plan is available for the product.
236+
if ( 0 === $deposit_plan_id || in_array( $deposit_plan_id, $available_plan_ids, true ) ) {
237+
$base_price = WC_Deposits_Product_Manager::get_deposit_amount( $product, $deposit_plan_id, 'display', $base_price );
238+
}
239+
}
240+
}
241+
219242
// Add subscription sign-up fees to product price.
220243
$sign_up_fee = 0;
221244
$subscription_types = [
@@ -979,12 +1002,14 @@ public function ajax_get_selected_product_data() {
9791002
check_ajax_referer( 'wcpay-get-selected-product-data', 'security' );
9801003

9811004
try {
982-
$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : false;
983-
$qty = ! isset( $_POST['qty'] ) ? 1 : apply_filters( 'woocommerce_add_to_cart_quantity', absint( $_POST['qty'] ), $product_id );
984-
$addon_value = isset( $_POST['addon_value'] ) ? max( (float) $_POST['addon_value'], 0 ) : 0;
985-
$product = wc_get_product( $product_id );
986-
$variation_id = null;
987-
$currency = get_woocommerce_currency();
1005+
$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : false;
1006+
$qty = ! isset( $_POST['qty'] ) ? 1 : apply_filters( 'woocommerce_add_to_cart_quantity', absint( $_POST['qty'] ), $product_id );
1007+
$addon_value = isset( $_POST['addon_value'] ) ? max( (float) $_POST['addon_value'], 0 ) : 0;
1008+
$product = wc_get_product( $product_id );
1009+
$variation_id = null;
1010+
$currency = get_woocommerce_currency();
1011+
$is_deposit = isset( $_POST['wc_deposit_option'] ) ? 'yes' === sanitize_text_field( wp_unslash( $_POST['wc_deposit_option'] ) ) : null;
1012+
$deposit_plan_id = isset( $_POST['wc_deposit_payment_plan'] ) ? absint( $_POST['wc_deposit_payment_plan'] ) : 0;
9881013

9891014
if ( ! is_a( $product, 'WC_Product' ) ) {
9901015
/* translators: product ID */
@@ -1012,7 +1037,7 @@ public function ajax_get_selected_product_data() {
10121037
throw new Exception( sprintf( __( 'You cannot add that amount of "%1$s"; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce-payments' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity(), $product ) ) );
10131038
}
10141039

1015-
$price = $this->get_product_price( $product );
1040+
$price = $this->get_product_price( $product, $is_deposit, $deposit_plan_id );
10161041
$total = $qty * $price + $addon_value;
10171042

10181043
$quantity_label = 1 < $qty ? ' (x' . $qty . ')' : '';

includes/express-checkout/class-wc-payments-express-checkout-button-helper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public function build_display_items( $itemized_display_items = false ) {
170170
$currency = get_woocommerce_currency();
171171

172172
// Default show only subtotal instead of itemization.
173-
if ( ! apply_filters( 'wcpay_payment_request_hide_itemization', true ) || $itemized_display_items ) {
173+
if ( ! apply_filters( 'wcpay_payment_request_hide_itemization', ! $itemized_display_items ) ) {
174174
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
175175
$amount = $cart_item['line_subtotal'];
176176
$subtotal += $cart_item['line_subtotal'];

tests/unit/helpers/class-wc-helper-deposit-product-manager.php

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,105 @@
55
* @package WooCommerce\Payments\Tests
66
*/
77

8+
// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
9+
810
/**
911
* Class WC_Deposits_Product_Manager.
1012
*
1113
* This helper class should ONLY be used for unit tests!.
1214
*/
1315
class WC_Deposits_Product_Manager {
14-
public static function get_deposit_type( WC_Product_Simple $product ) {
16+
/**
17+
* @param WC_Product_Simple|int $product
18+
* @return mixed
19+
*/
20+
public static function get_deposit_type( $product ) {
21+
$product = self::get_product( $product );
1522
return $product->get_meta( '_wc_deposit_type' );
1623
}
17-
public static function deposits_enabled( WC_Product_Simple $product ) {
18-
return true === $product->get_meta( '_wc_deposits_enabled' );
24+
25+
/**
26+
* @param WC_Product_Simple|int $product
27+
* @return mixed
28+
*/
29+
public static function get_deposit_selected_type( $product ) {
30+
$product = self::get_product( $product );
31+
return $product->get_meta( '_wc_deposit_selected_type' );
32+
}
33+
34+
/**
35+
* @param WC_Product_Simple|int $product
36+
* @return bool
37+
*/
38+
public static function deposits_enabled( $product ) {
39+
$product = self::get_product( $product );
40+
$setting = $product->get_meta( '_wc_deposit_enabled' );
41+
return 'optional' === $setting || 'forced' === $setting;
42+
}
43+
44+
/**
45+
* @param WC_Product_Simple|int $product
46+
* @return mixed
47+
*/
48+
public static function get_deposit_amount( $product, $plan_id = 0, $context = 'display', $product_price = null ) {
49+
$product = self::get_product( $product );
50+
$type = self::get_deposit_type( $product );
51+
$percentage = 'percent' === $type;
52+
$amount = $product->get_meta( '_wc_deposit_amount' );
53+
54+
if ( ! $amount ) {
55+
$amount = get_option( 'wc_deposits_default_amount' );
56+
}
57+
58+
if ( ! $amount ) {
59+
return false;
60+
}
61+
62+
if ( $percentage ) {
63+
$product_price = is_null( $product_price ) ? $product->get_price() : $product_price;
64+
$amount = ( $product_price / 100 ) * $amount;
65+
}
66+
67+
$price = $amount;
68+
return wc_format_decimal( $price );
69+
}
70+
71+
/**
72+
* @param $product
73+
* @return mixed|null
74+
*/
75+
public static function get_product( $product ) {
76+
if ( ! is_object( $product ) ) {
77+
$product = apply_filters( 'test_deposit_get_product', wc_get_product( $product ) );
78+
}
79+
80+
return $product;
81+
}
82+
}
83+
84+
/**
85+
* Class WC_Deposits_Plans_Manager.
86+
*/
87+
class WC_Deposits_Plans_Manager {
88+
/**
89+
* Get plan ids assigned to a product.
90+
*
91+
* @param int $product_id Product ID.
92+
* @return int[]
93+
*/
94+
public static function get_plan_ids_for_product( $product_id ) {
95+
$product = WC_Deposits_Product_Manager::get_product( $product_id );
96+
$map = array_map( 'absint', array_filter( (array) $product->get_meta( '_wc_deposit_payment_plans' ) ) );
97+
if ( count( $map ) <= 0 ) {
98+
$map = self::get_default_plan_ids();
99+
}
100+
return $map;
101+
}
102+
103+
/**
104+
* Get the default plan IDs.
105+
*/
106+
public static function get_default_plan_ids() {
107+
return array_map( 'absint', array_filter( (array) get_option( 'wc_deposits_default_plans', [] ) ) );
19108
}
20109
}

tests/unit/multi-currency/compatibility/test-class-woocommerce-deposits.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ function( $input ) {
9393

9494
$amount = 10.00;
9595
$product = WC_Helper_Product::create_simple_product();
96-
$product->add_meta_data( '_wc_deposits_enabled', true );
96+
$product->add_meta_data( '_wc_deposit_enabled', 'optional' );
9797
$product->add_meta_data( '_wc_deposit_type', 'plan' );
9898
$product->save();
9999

@@ -112,7 +112,7 @@ public function test_maybe_convert_product_prices_for_deposits() {
112112
->willReturn( true );
113113

114114
$product = WC_Helper_Product::create_simple_product();
115-
$product->add_meta_data( '_wc_deposits_enabled', true );
115+
$product->add_meta_data( '_wc_deposit_enabled', 'optional' );
116116
$product->add_meta_data( '_wc_deposit_type', 'plan' );
117117
$product->save();
118118

tests/unit/test-class-wc-payments-payment-request-button-handler.php

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,75 @@ public function test_get_product_price_returns_simple_price() {
339339
);
340340
}
341341

342+
public function test_get_product_price_returns_deposit_amount() {
343+
$product_price = 10;
344+
$this->simple_product->set_price( $product_price );
345+
346+
$this->assertEquals(
347+
$product_price,
348+
$this->pr->get_product_price( $this->simple_product, false ),
349+
'When deposit is disabled, the regular price should be returned.'
350+
);
351+
$this->assertEquals(
352+
$product_price,
353+
$this->pr->get_product_price( $this->simple_product, true ),
354+
'When deposit is enabled, but the product has no setting for deposit, the regular price should be returned.'
355+
);
356+
357+
$this->simple_product->update_meta_data( '_wc_deposit_enabled', 'optional' );
358+
$this->simple_product->update_meta_data( '_wc_deposit_type', 'percent' );
359+
$this->simple_product->update_meta_data( '_wc_deposit_amount', 50 );
360+
$this->simple_product->save_meta_data();
361+
362+
$this->assertEquals(
363+
$product_price,
364+
$this->pr->get_product_price( $this->simple_product, false ),
365+
'When deposit is disabled, the regular price should be returned.'
366+
);
367+
$this->assertEquals(
368+
$product_price * 0.5,
369+
$this->pr->get_product_price( $this->simple_product, true ),
370+
'When deposit is enabled, the deposit price should be returned.'
371+
);
372+
373+
$this->simple_product->delete_meta_data( '_wc_deposit_amount' );
374+
$this->simple_product->delete_meta_data( '_wc_deposit_type' );
375+
$this->simple_product->delete_meta_data( '_wc_deposit_enabled' );
376+
$this->simple_product->save_meta_data();
377+
}
378+
379+
public function test_get_product_price_returns_deposit_amount_default_values() {
380+
$product_price = 10;
381+
$this->simple_product->set_price( $product_price );
382+
383+
$this->assertEquals(
384+
$product_price,
385+
$this->pr->get_product_price( $this->simple_product ),
386+
'When deposit is disabled by default, the regular price should be returned.'
387+
);
388+
389+
$this->simple_product->update_meta_data( '_wc_deposit_enabled', 'optional' );
390+
$this->simple_product->update_meta_data( '_wc_deposit_type', 'percent' );
391+
$this->simple_product->update_meta_data( '_wc_deposit_amount', 50 );
392+
$this->simple_product->update_meta_data( '_wc_deposit_selected_type', 'full' );
393+
$this->simple_product->save_meta_data();
394+
395+
$this->assertEquals(
396+
$product_price,
397+
$this->pr->get_product_price( $this->simple_product ),
398+
'When deposit is optional and disabled by default, the regular price should be returned.'
399+
);
400+
401+
$this->simple_product->update_meta_data( '_wc_deposit_selected_type', 'deposit' );
402+
$this->simple_product->save_meta_data();
403+
404+
$this->assertEquals(
405+
$product_price * 0.5,
406+
$this->pr->get_product_price( $this->simple_product ),
407+
'When deposit is optional and selected by default, the deposit price should be returned.'
408+
);
409+
}
410+
342411
/**
343412
* @dataProvider provide_get_product_tax_tests
344413
*/
@@ -440,6 +509,12 @@ public function provide_get_product_tax_tests() {
440509

441510
public function test_get_product_price_includes_subscription_sign_up_fee() {
442511
$mock_product = $this->create_mock_subscription( 'subscription' );
512+
add_filter(
513+
'test_deposit_get_product',
514+
function() use ( $mock_product ) {
515+
return $mock_product;
516+
}
517+
);
443518

444519
// We have a helper because we are not loading subscriptions.
445520
WC_Subscriptions_Product::set_sign_up_fee( 10 );
@@ -452,6 +527,12 @@ public function test_get_product_price_includes_subscription_sign_up_fee() {
452527

453528
public function test_get_product_price_includes_variable_subscription_sign_up_fee() {
454529
$mock_product = $this->create_mock_subscription( 'subscription_variation' );
530+
add_filter(
531+
'test_deposit_get_product',
532+
function() use ( $mock_product ) {
533+
return $mock_product;
534+
}
535+
);
455536

456537
// We have a helper because we are not loading subscriptions.
457538
WC_Subscriptions_Product::set_sign_up_fee( 10 );
@@ -477,6 +558,12 @@ public function test_get_product_price_throws_exception_for_products_without_pri
477558

478559
public function test_get_product_price_throws_exception_for_a_non_numeric_signup_fee() {
479560
$mock_product = $this->create_mock_subscription( 'subscription' );
561+
add_filter(
562+
'test_deposit_get_product',
563+
function() use ( $mock_product ) {
564+
return $mock_product;
565+
}
566+
);
480567
WC_Subscriptions_Product::set_sign_up_fee( 'a' );
481568

482569
$this->expectException( WCPay\Exceptions\Invalid_Price_Exception::class );

0 commit comments

Comments
 (0)