Pay without need to login WooCommerce - wordpress

For a WooCommerce webshop we send out a lot of payment links through email. Before getting to the payment page customers are obligated to login first. We would like the customer to be able to complete payment without logging in as often they don't know their password because of different company departments.
I found this code but this only lets the administrator pay without logging in:
function your_custom_function_name($allcaps, $caps, $args)
{
if (isset($caps[0])) {
switch ($caps[0]) {
case 'pay_for_order':
$user_id = $args[1];
$order_id = isset($args[2]) ? $args[2] : null;
// When no order ID, we assume it's a new order
// and thus, customer can pay for it
if (!$order_id) {
$allcaps['pay_for_order'] = true;
break;
}
$user = get_userdata($user_id);
if (in_array('administrator', (array)$user->roles)) {
$allcaps['pay_for_order'] = true;
}
$order = wc_get_order($order_id);
if ($order && ($user_id == $order->get_user_id() || !$order - > get_user_id())) {
$allcaps['pay_for_order'] = true;
}
break;
}
}
return $allcaps;
}
add_filter('user_has_cap', 'your_custom_function_name', 10, 3);

here is working function with all users just test it :
function your_custom_function_name( $allcaps, $caps, $args ) {
if ( isset( $caps[0] ) ) {
switch ( $caps[0] ) {
case 'pay_for_order' :
$order_id = isset( $args[2] ) ? $args[2] : null;
$order = wc_get_order( $order_id );
$user = $order->get_user();
$user_id = $user->ID;
// When no order ID, we assume it's a new order
// and thus, customer can pay for it
if ( ! $order_id ) {
$allcaps['pay_for_order'] = true;
break;
}
$order = wc_get_order( $order_id );
if ( $order && ( $user_id == $order->get_user_id() || ! $order->get_user_id() ) ) {
$allcaps['pay_for_order'] = true;
}
break;
}
}
return $allcaps;
}
add_filter( 'user_has_cap', 'your_custom_function_name', 10, 3 );

I have created a different solution for this problem, allowing anyone who has the WooCommerce-generated Payment URL (which includes the Order Key) to complete the payment for that order. (So we retain some of the security/protection, rather than just allowing anyone to pay for anything and see any order.)
function allow_payment_without_login( $allcaps, $caps, $args ) {
// Check we are looking at the WooCommerce Pay For Order Page
if ( !isset( $caps[0] ) || $caps[0] != 'pay_for_order' )
return $allcaps;
// Check that a Key is provided
if ( !isset( $_GET['key'] ) )
return $allcaps;
// Find the Related Order
$order = wc_get_order( $args[2] );
if( !$order )
return $allcaps; # Invalid Order
// Get the Order Key from the WooCommerce Order
$order_key = $order->get_order_key();
// Get the Order Key from the URL Query String
$order_key_check = $_GET['key'];
// Set the Permission to TRUE if the Order Keys Match
$allcaps['pay_for_order'] = ( $order_key == $order_key_check );
return $allcaps;
}
add_filter( 'user_has_cap', 'allow_payment_without_login', 10, 3 );
Using this function, a user visiting a URL which has an Order Number, and the associated Order Key, will be able to complete the payment, but if the Order Key is not valid, or not present, then it will fail.
Examples:
your.domain/checkout/order-pay/987/?pay_for_order=true&key=wc_order_5c4155dd4462cPASS if "wc_order_5c4155dd4462c" is Order Key for Order #987
your.domain/checkout/order-pay/987/?pay_for_order=trueFAIL as No Key Parameter Present
your.domain/checkout/order-pay/987/FAIL as No Key Parameter Present

Related

Hide "ship to a different address" in Woocommerce based on user roles and specific products in cart

I'm trying to hide the "ship to a different address" on WooCommerce checkout page for 2 user roles, if their cart contains any of the specified product ID's.
My code is working for a single product ID:
/** Shipping Address */
function filter_woocommerce_cart_needs_shipping_address( $needs_shipping ) {
// The targeted products & roles
$targeted_variation_id = 414;
$targeted_user_role = 'team';
$targeted_user_role2 = 'team2';
// Flag
$found = false;
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( in_array( $targeted_variation_id, array( $cart_item['product_id'], $cart_item['variation_id'] ) ) ) {
$found = true;
break;
}
}
// True & user Role is
if ( $found && current_user_can( $targeted_user_role )) {
$needs_shipping = false;
}
// True & only 1 item in cart
if ( $found && current_user_can( $targeted_user_role2 )) {
$needs_shipping = false;
}
return $needs_shipping;
}
add_filter( 'woocommerce_cart_needs_shipping_address', 'filter_woocommerce_cart_needs_shipping_address', 10, 1 );
But then when I try to create an array of multiple IDs, I can't achieve the desired result, this is my attempt:
**Multiple Variants**
/** Shipping Address */
function filter_woocommerce_cart_needs_shipping_address( $needs_shipping ) {
// The targeted products & roles
$targeted_variation_id = array(414,617);
$targeted_user_role = 'team';
$targeted_user_role2 = 'team2';
// Flag
$found = false;
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( in_array( $targeted_variation_id, array( $cart_item['product_id'], $cart_item['variation_id'] ) ) ) {
$found = true;
break;
}
}
// True & user Role is
if ( $found && current_user_can( $targeted_user_role )) {
$needs_shipping = false;
}
// True & only 1 item in cart
if ( $found && current_user_can( $targeted_user_role2 )) {
$needs_shipping = false;
}
return $needs_shipping;
}
add_filter( 'woocommerce_cart_needs_shipping_address', 'filter_woocommerce_cart_needs_shipping_address', 10, 1 );
Anyone able to help figure out how to get it to work with multiple ID's and not just one?
The mistakes in your code is in the following line:
if ( in_array( $targeted_variation_id, array( $cart_item['product_id'], $cart_item['variation_id'] ) ) )
$targeted_variation_id has been changed to an array,
while the searched value must be a string when using in_array()
It also seems more logical to check the user role first, before iterate all cart items
So you get:
function filter_woocommerce_cart_needs_shipping_address( $needs_shipping ) {
// Retrieve the current user object
$user = wp_get_current_user();
// Add your user roles, several can be entered, separated by a comma
$user_roles = array( 'team1', 'team2', 'administrator' );
// Targeted product IDs, several can be entered, separated by a comma
$targeted_product_ids = array( 30, 813, 414, 617 );
// User role is found
if ( array_intersect( $user_roles, (array) $user->roles ) ) {
// Loop through cart contents
foreach ( WC()->cart->get_cart_contents() as $cart_item ) {
// Get product ID
$product_id = $cart_item['variation_id'] > 0 ? $cart_item['variation_id'] : $cart_item['product_id'];
// Checks if a value exists in an array
if ( in_array( $product_id, $targeted_product_ids ) ) {
$needs_shipping = false;
break;
}
}
}
return $needs_shipping;
}
add_filter( 'woocommerce_cart_needs_shipping_address', 'filter_woocommerce_cart_needs_shipping_address', 10, 1 );

Cash on delivery (COD) based on user role and product category

I need to customize cash on delivery (COD) in WooCommerce based on user role and product category.
Requirements:
By default COD is hidden
COD is visible when a product in cart, belongs to a certain category (category 1)
Users who have the role "x" must always see COD, even if they don't have category 1 products in the cart.
This is my code attempt to meet the above requirements:
function disable_cod($available_gateways)
{
if (is_admin()) return $available_gateways;
$role = false;
//check whether the avaiable payment gateways have Cash on delivery and user is not logged in or he is a user with role customer
if (isset($available_gateways['cod']) && (current_user_can('customer') || !is_user_logged_in()))
{
$role = true;
}
foreach (WC()
->cart
->get_cart() as $cart_item_key => $cart_item)
{
$prod_simple = true;
// Get the WC_Product object
$product = wc_get_product($cart_item['product_id']);
// Get the product types in cart (example)
if ($product->is_product_category('X')) $prod_simple = false;
}
if ($prod_simple = $role = true) unset($available_gateways['cod']); // unset 'cod'
return $available_gateways;
}
add_filter('woocommerce_available_payment_gateways', 'disable_cod', 99, 1);
I definitely got something wrong in the syntax, but I hope the logical concept is correct? Any advice?
Your code contains some minor mistakes:
is_product_category() is a conditional tag which returns true when viewing a product category archive. Use has_term() instead.
Loop through the cart is only necessary when the user role is not fulfilled.
So you get:
function filter_woocommerce_available_payment_gateways( $payment_gateways ) {
// Not on admin
if ( is_admin() ) return $payment_gateways;
// Initialize: flag - default true
$flag = true;
// Has certain user role
if ( current_user_can( 'certain-user-role' ) ) {
// False
$flag = false;
} else {
// Specific categories: the term name/term_id/slug. Several could be added, separated by a comma
$categories = array( 63, 15, 'categorie-1' );
// Isset
if ( WC()->cart ) {
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ) {
// Has term (product category)
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
// False, break loop
$flag = false;
break;
}
}
}
}
// True
if ( $flag ) {
// Cod
if ( isset( $payment_gateways['cod'] ) ) {
// Remove
unset( $payment_gateways['cod'] );
}
}
return $payment_gateways;
}
add_filter( 'woocommerce_available_payment_gateways', 'filter_woocommerce_available_payment_gateways', 10, 1 );

Filter which BACS account information is sent to users via email

Implemented the following solution https://stackoverflow.com/a/55174664/1759546 with success but, how could one apply the same logic in the order confirmation emails that are sent to the users? I'm trying to send just one of many accounts available, depending on meta data from order.
Thanks in advance
Ok, so I was being dumb.
We just have to pass a second argument to woocommerce_bacs_accounts which is the order_id. And as the filter runs inside the order_email it will apply the same rules.
add_filter( 'woocommerce_bacs_accounts', 'filter_woocommerce_bacs_accounts_callback', 10, 2 );
function filter_woocommerce_bacs_accounts_callback( $bacs_accounts, $order_id ){
if ( empty($bacs_accounts) ) {
return $bacs_accounts; // Exit
}
if( is_wc_endpoint_url('order-received') ) {
$endpoint = 'order-received';
// Get the WC_Order Object
$order = wc_get_order( get_query_var($endpoint) );
} elseif( is_wc_endpoint_url('view-order') ) {
$endpoint = 'view-order';
// Get the WC_Order Object
$order = wc_get_order( get_query_var($endpoint) );
} else if ($order_id){
// Get the WC_Order Object
$order = wc_get_order($order_id );
}
$sort_codes = []; // Initializing variable array
// Loop through order items
foreach ( $order->get_items() as $item ) {
$sort_codes[] = $item->get_meta("pa_sede");
}
if ( empty($sort_codes) ) {
return $bacs_accounts; // Exit
}
// Loop through Bacs accounts
foreach ( $bacs_accounts as $key => $bacs_account ) {
$bacs_account = (object) $bacs_account;
// Remove the non matching bank accounts
if ( ! in_array($bacs_account->sort_code, $sort_codes ) ) {
unset($bacs_accounts[$key]);
}
}
return $bacs_accounts;
}

How to add order status class to the body tag?

do you know how can we add CSS (let's call pending-payment-class) class into the body tag when a user has an order which is on pending payment status?
I tried this one but no luck
add_filter( 'body_class', 'order_class');
function order_class($orderclasses) {
$order = wc_get_order( $order_id );
$order_status = $order->get_status();
if( is_page( 30 )) {
$orderclasses[] = $order_status;
}
return $orderclasses;
}
Thanks.
I fixed it with this one
add_filter( 'body_class', 'order_class');
function order_class( $orderclasses ) {
// bail if Memberships isn't active
if ( ! function_exists( 'wc_memberships' ) ) {
return;
}
if ( wc_memberships_is_user_active_member( $user_id, 'membership-slug' ) ) {
$user_id = get_current_user_id(); // The current user ID
// Get the WC_Customer instance Object for the current user
$customer = new WC_Customer( $user_id );
// Get the last WC_Order Object instance from current customer
$last_order = $customer->get_last_order();
$order_id = $last_order->get_id(); // Get the order id
$order_data = $last_order->get_data(); // Get the order unprotected data in an array
$order_status = $last_order->get_status(); // Get the order status
if (is_account_page()) {
$orderclasses[] = $order_status;
}
return $orderclasses;
}
}
Note: it only works for the setup that has WC Memberships and last order. You can modify it according to your needs.

How to check that we are not in a Woocommerce endpoint

since WooCommerce 2.1 pages such as order-received has been removed and replaced with WC endpoints. My checkout page had a custom page template (page-checkout.php) and now all checkout endpoints are also using this custom page template.
I need to modify my header and footer only when my customers are in the /checkout/ page, but I want to show different content when they are in a checkout endpoints. I have found this conditional:
if(is_wc_endpoint_url("order-received")) echo "yes";
It works when we are in the "order-received" checkout endpoint. But I am looking for a conditional logic that tells me when we are not in an endpoint, something like:
if(!is_wc_endpoint()) echo "yes";
Thank you.
The questions seemed to be answered and bit old. But i found a better solution, which may help somebody else.
you can use the following function. Documentation
is_wc_endpoint_url()
if used without a parameter, it will check current url against all endpoints. If an endpoint is specified like
is_wc_endpoint_url('edit-account');
it will check either the url is of specific endpoint or not.
Try following function:
function is_wc_endpoint() {
if ( empty( $_SERVER['REQUEST_URI'] ) ) return false;
$url = parse_url( $_SERVER['REQUEST_URI'] );
if ( empty( $url['query'] ) ) return false;
global $wpdb;
$all_woocommerce_endpoints = array();
$results = $wpdb->get_results( "SELECT option_name, option_value FROM {$wpdb->prefix}options WHERE option_name LIKE 'woocommerce_%_endpoint'", 'ARRAY_A' );
foreach ( $results as $result ) {
$all_woocommerce_endpoints[$result['option_name']] = $result['option_value'];
}
foreach ( $all_woocommerce_endpoints as $woocommerce_endpoint ) {
if ( strpos( $url['query'], $woocommerce_endpoint ) !== false ) {
return true;
}
}
return false;
}
Hope it'll give you result you are expecting.
This is a never version of the is_wc_endpoint_url function that will be included in the future woocommerce version. So just give it a different name and put into your functions.php for example.
function is_wc_endpoint_url( $endpoint = false ) {
global $wp;
$wc_endpoints = WC()->query->get_query_vars();
if ( $endpoint ) {
if ( ! isset( $wc_endpoints[ $endpoint ] ) ) {
return false;
} else {
$endpoint_var = $wc_endpoints[ $endpoint ];
}
return isset( $wp->query_vars[ $endpoint_var ] );
} else {
foreach ( $wc_endpoints as $key => $value ) {
if ( isset( $wp->query_vars[ $key ] ) ) {
return true;
}
}
return false;
}
}
I tried the default WC function:
is_wc_endpoint_url('my-custom-endpoint');
but it always returned false for me. So I created my own function:
function yourtheme_is_wc_endpoint($endpoint) {
// Use the default WC function if the $endpoint is not provided
if (empty($endpoint)) return is_wc_endpoint_url();
// Query vars check
global $wp;
if (empty($wp->query_vars)) return false;
$queryVars = $wp->query_vars;
if (
!empty($queryVars['pagename'])
// Check if we are on the Woocommerce my-account page
&& $queryVars['pagename'] == 'my-account'
) {
// Endpoint matched i.e. we are on the endpoint page
if (isset($queryVars[$endpoint])) return true;
// Dashboard my-account page special check - check whether the url ends with "my-account"
if ($endpoint == 'dashboard') {
$requestParts = explode('/', trim($wp->request, ' \/'));
if (end($requestParts) == 'my-account') return true;
}
}
return false;
}
Example:
yourtheme_is_wc_endpoint('my-custom-endpoint');
Or:
yourtheme_is_wc_endpoint('edit-account');

Resources