I am trying to create a "Product Redeem Page as shown in this tutorial on SitePoint.
Problem is that the product does indeed gets added to the cart and you can proceed to the checkout, but the discount that is associated with the coupon code is not automagically applied. In the coupon code I created the value is set to 100% discount.
You could apply te coupon code again via the "Do you have a coupon code" fly out on the checkout page, but this defeats the whole purpose.
I also did not get this code working to begin with, but I was able to figure out that:
// Check coupon to make determine if its valid or not
if( ! $coupon->id && ! isset( $coupon->id ) ) {
...Rest of code here...
should be:
// Check coupon to make determine if its valid or not
if( ! $coupon->id && ! isset( $coupon_id ) ) {
Please note the second Not isset variable name. Maybe this does work, but is not the proper way of handling things, everybody knows, but me.
Sadly I am out of my comfort zone a.t.m., but I am willing to learn by making mistakes and figuring out how to fix them and learn from people who are way smarter then me and/or way more advanced. In my direct cirlce of friends I have no one that I could aks and get any other answer then: "Huh?!?", so I am giving it a shot here on Stackoverflow.
A link to the tutorial on SitePoint only is probably not appreciated, so here is the complete code I am using:
The Ajax handlers added in functions.php
add_action( 'wp_ajax_spyr_coupon_redeem_handler', 'spyr_coupon_redeem_handler' );
add_action( 'wp_ajax_nopriv_spyr_coupon_redeem_handler', 'spyr_coupon_redeem_handler' );
The coupon login also added to functions.php
function spyr_coupon_redeem_handler() {
// Get the value of the coupon code
$code = $_REQUEST['coupon_code'];
// Check coupon code to make sure is not empty
if( empty( $code ) || !isset( $code ) ) {
// Build our response
$response = array(
'result' => 'error',
'message' => 'Code text field can not be empty.'
);
header( 'Content-Type: application/json' );
echo json_encode( $response );
// Always exit when doing ajax
exit();
}
// Create an instance of WC_Coupon with our code
$coupon = new WC_Coupon( $code );
// Check coupon to make determine if its valid or not
if( ! $coupon->id && ! isset( $coupon_id ) ) {
// Build our response
$response = array(
'result' => 'error',
'message' => 'Invalid code entered. Please try again.'
);
header( 'Content-Type: application/json' );
echo json_encode( $response );
// Always exit when doing ajax
exit();
} else {
// Attempting to add the coupon code as a discount.
WC()->cart->add_discount( $code );
// Coupon must be valid so we must
// populate the cart with the attached products
foreach( $coupon->product_ids as $prod_id ) {
WC()->cart->add_to_cart( $prod_id );
}
// Build our response
$response = array(
'result' => 'success',
'href' => WC()->cart->get_cart_url()
);
header( 'Content-Type: application/json' );
echo json_encode( $response );
// Always exit when doing ajax
exit();
}
}
The jQuery form submission code, enqueued via the registered Ajax handlers in functions.php
jQuery( document ).ready( function() {
jQuery( '#ajax-coupon-redeem input[type="submit"]').click( function( ev ) {
// Get the coupon code
var code = jQuery( 'input#coupon').val();
// We are going to send this for processing
data = {
action: 'spyr_coupon_redeem_handler',
coupon_code: code
}
// Send it over to WordPress.
jQuery.post( woocommerce_params.ajax_url, data, function( returned_data ) {
if( returned_data.result == 'error' ) {
jQuery( 'p.result' ).html( returned_data.message );
} else {
// Hijack the browser and redirect user to cart page
window.location.href = returned_data.href;
}
})
// Prevent the form from submitting
ev.preventDefault();
});
});
Thanks in advance of pointing me in the right direction.
Update: At this moment I got the wanted functionality working.
What needed to be done is to add:
// Let's add the discount to the cart.
global $woocommerce;
WC()->cart->add_discount( $code );
inside the foreach statement. The complete else statement now looks like this:
} else {
// Coupon must be valid so we must
// populate the cart with the attached products
foreach( $coupon->product_ids as $prod_id ) {
WC()->cart->add_to_cart( $prod_id );
// Let's add the discount to the cart.
global $woocommerce;
WC()->cart->add_discount( $code );
}
// Build our response
$response = array(
'result' => 'success',
'href' => WC()->cart->get_cart_url()
);
header( 'Content-Type: application/json' );
echo json_encode( $response );
// Always exit when doing ajax
exit();
I'm still not sure if this is the correct way of handeling this, but it seems to work.
For instance I am calling(?!) the global $woocommerce variable, but below I am using the global(?!) class WC() to add the coupon. Not sure if this is as clean and logical as it gets.
If anybody knows of a better/cleaner way, please let me know! I am happy to learn from you guys and maybe one day I may be able to return the favour, who knows.
Related
Im trying to use the code below for my plugin development but there is a caching issue (I think). Please below for the details.
Problem
I created a filter to accept the customized url, and I'm querying the database but it seems like the result value is not changing in the browser but changing in the database. So basically the data passed is not returning the updated value from the database.
Code
add_action( 'init', function() {
add_rewrite_rule('coupon/([a-zA-Z0-9]+)[/]?$', 'index.php?coupon=$matches[1]', 'top');
});
add_filter('query_vars', function( $query_vars ) {
$query_vars[] = 'coupon';
return $query_vars;
});
add_action( 'template_include', function( $template ) {
if ( get_query_var( 'coupon' ) == false || get_query_var( 'coupon' ) == '' ) {
return $template;
}
ob_clean();
ob_start();
// Check if coupon exists
global $wpdb;
$coupon = get_query_var('coupon');
$coupon_query = $wpdb->prepare('SELECT * FROM wp_urls WHERE coupon = %s AND claimed_at IS NULL', $coupon);
$coupon_available = $wpdb->query($coupon_query) ? true : false;
if(!$coupon_available) {
header('Location: /'); // Redirect to landing page
exit();
}
return plugin_dir_path(__FILE__) . 'views/client/index.php';;
} );
Expected Result
The expected result should redirect the user to the header('Location: /'); if the coupon is not available.
Any idea?
You need update the permalinks in the WordPress admin area. For example:
Step 1: In the WordPress admin area, go to `Settings > Permalinks`
Step 2: Click `Save Changes`
Step 3: Permalinks and rewrite rules are flushed.
I've built an add to cart process that successfully creates a "personalisation" field when the item is added to the cart. Using the standard 'add to cart' button, the page reloads and all is well. I wanted to improve the UX by making the basket update via AJAX, which I've implemented and again working ok.
However, this stops the cart item data meta from registering because the request is not defined - the hook isn't able to grab input values from the page any more. The hook I'm using to define and write the meta fields to is 'woocommerce_add_cart_item_data'
I tried creating my own separate AJAX function to define the content of the field separately, using $_SESSION instead of $_REQUEST (inspired by this answer -> how to pass ajax data to woocommerce filter?)
Unfortunately this seemingly fires after 'woocommerce_add_cart_item_data' has run, so the meta data does not come through (in fact it ends up showing on the next product added, since the variable is defined late and not accessed until the 2nd product is added).
I'd really appreciate some help with either:
(1) Amending the code so 'woocommerce_add_cart_item_data' is able to take data from the AJAX add to cart action I made, or
(2) Amend my custom AJAX call so session data is able to be applied in a timely order (unsure if this is achievable based on what I've seen so far).
// Create AJAX add to cart button
function add_cart_btn($prod) {
echo apply_filters( 'woocommerce_loop_add_to_cart_link',
sprintf( 'Add to bag',
esc_url( $prod->add_to_cart_url() ),
esc_attr( $prod->id ),
esc_attr( $prod->get_sku() ),
$prod->is_purchasable() ? 'add_to_cart_button' : '',
esc_attr( $prod->product_type ),
esc_html( $prod->add_to_cart_text() )
),
$prod );
}
add_cart_btn($product);
// functions.php
/* Saves field data */
function save_add_custom_info_field( $cart_item_data, $product_id ) {
if( isset( $_REQUEST['custom_info_message'] ) ) {
// *** $_REQUEST['custom_info_message'] not defined ***
$cart_item_data[ 'custom_info_message' ] = $_REQUEST['custom_info_message'];
$cart_item_data['unique_key'] = md5( microtime().rand() );
}
return $cart_item_data;
}
add_action( 'woocommerce_add_cart_item_data', 'save_add_custom_info_field', 10, 2 );
/* Renders field entry on cart and checkout */
function render_mssg_meta_on_cart_and_checkout( $cart_data, $cart_item = null ) {
$custom_items = array();
if( !empty( $cart_data ) ) {
$custom_items = $cart_data;
}
if( isset( $cart_item['custom_info_message'] ) ) {
$custom_items[] = array( "name" => 'Personalisation / Customisation', "value" => $cart_item['custom_info_message'] );
}
return $custom_items;
}
add_filter( 'woocommerce_get_item_data',
'render_mssg_meta_on_cart_and_checkout', 10, 2 );
// *** Attempted solution with AJAX - Use $_SESSION['message'] in 'save_add_custom_info_field' function above instead of $_REQUEST... delivered after 'woocommerce_add_cart_item_data' so doesn't work
function set_customised_message() {
$message = $_POST['message'];
define( 'DOING_AJAX', true );
if ( ! defined( 'WP_ADMIN' ) ) {
define( 'WP_ADMIN', true );
}
session_start();
$_SESSION['message'] = $message;
echo $message;
die();
}
I expect custom meta to show on the cart/checkout pages, but it isn't due to the variable being either $cart_item['custom_info_message'] being undefined, or session variable from AJAX call not being available.
I've managed to solve this by updating the product meta data after it is added to the cart (via AJAX) - amending set_customised_message function above. Note the AJAX call required a timeout delay of 1s in order to work.
Leaving it here in case anyone else is having a similar issue.
function set_customised_message() {
$cart = WC()->cart->cart_contents;
foreach( $cart as $cart_item_id=>$cart_item ) {
echo $cart_item['unique_key'];
$cart_item['custom_info_message'] = $message;
echo $cart_item['custom_info_message'];
WC()->cart->cart_contents[$cart_item_id] = $cart_item;
}
WC()->cart->set_session();
}
Thanks to https://pluginrepublic.com/how-to-update-existing-woocommerce-cart-meta-data/ for this solution
Looking to do enable or disable shortcode in a function, depending on a value. For instance, if someone says purchases a license key to use some shortcode on their site, I'd like to have the shortcode become disabled if they license expires. Example function I've been working on below:
my_premium_shortcodes(); // Fires early in my php file
function my_premium_shortcodes(){
$status = get_option( 'key_status' );
if( $status !== false && $status == 'valid' ) {
add_shortcode('shortcode_handle_1','shortcode_function_1');
add_shortcode('shortcode_handle_2','shortcode_function_2');
} else {
remove_shortcode('shortcode_handle_1','shortcode_function_1');
remove_shortcode('shortcode_handle_2','shortcode_function_2');
}
}
Any input on my approach or if the function should be handled in a separate way would be appreciated. As it stands, the shortcodes are working when everything is enabled, but stay enabled even after the 'status' changes to 'invalid'.
You should use the code in two different functions instead of one single function. Please review below code and implement into your site.
$status = get_option( 'key_status' );
if( $status !== false && $status == 'valid' ) {
//add a custom shortcode
add_action( 'init', 'my_add_shortcodes' );
function my_add_shortcodes() {
add_shortcode( 'myShortcode', 'my_shortcode_function' );
}
else{
//which can also remove
add_action( 'init', 'remove_my_shortcodes',20 );
function remove_my_shortcodes() {
remove_shortcode( 'myShortcode' );
}
Just wanted to post an update. The value of the status was apparently not properly being read by previous functions I had in my code. Essentially, just having my 'if' statement and removing the 'else' was all I needed to do.
So this works fine FYI:
function my_premium_shortcodes(){
$status = get_option( 'key_status' );
if( $status !== false && $status == 'valid' ) {
add_shortcode('shortcode_handle_1','shortcode_function_1');
add_shortcode('shortcode_handle_2','shortcode_function_2');
}
}
If the 'key_status' is actually being read, otherwise the shortcode was always firing.
Add shortcode function code into the function.php file or in the plugin file.
function my_add_shortcodes() {
// Add your code here
}
add_shortcode( 'myShortcode', 'my_shortcode_function' );
Use the below condition to call the shortcode in any file of your theme.
$status = get_option( 'key_status' );
if( $status !== false && $status == 'valid' ) {
echo do_shortcode('[myShortcode]');
}
I hope it will help you.
When registering user, I am hooking into user_register hook to send some call to the external API.
add_action( 'user_register', 'create_user_on_api' );
inside the create_user_on_api function I am making the call (nonces are there, and tons of security checks, but I'm omitting those for brevity) using wp_remote_post()
function create_user_on_api( $user_id ) {
$user_create_response = wp_remote_post( "someurlgoeshere.api", $curl_args );
if ( is_wp_error( $user_create_response ) ) {
throw new Exception( 'wp error' );
} else {
$code = wp_remote_retrieve_response_code( $user_create_response );
$msg = wp_remote_retrieve_response_message( $user_create_response );
$body = wp_remote_retrieve_body( $user_create_response );
if ( $code !== 200 ) {
throw new Exceprion( 'error' );
}
}
}
This is the gist of it.
The problem is following: using any error handles causes white screen of death, error is logged in my debug.log, and I would like to throw a notice on my admin dashboard that, even though api call failed, the user is created in WP, and you'll have to create it manually on the API or try again.
I tried echoing stuff, custom exception handling mentioned here, but so far no luck.
Any idea how to do that? Can I hook into admin notices in any way?
Ok, found a solution. A hacky one but it works.
On api error I am storing a error message in a transient, which has an expiry of 60 seconds and I created a user_notices function that I hook on admin_notices hook. In it I check two things: if I'm on users screen
$current_screen = get_current_screen();
if ( $current_screen->id === 'users' ) {
}
And then inside this I check if the transients are empty, and then assign them to the $error_message array, which I output in a foreach (in case I have multiple error messages)
if ( ! empty( $error_msg_array ) ) {
foreach ( $error_msg_array as $error_msg_key => $error_msg ) {
?>
<div class="error notice">
<p><?php printf( '<strong>Apigee error:</strong> %s', esc_html( $error_msg ) ); ?></p>
</div>
<?php
}
}
And this seems to be working.
I am working on a WordPress plugin which adds an auth code to the login form.
This is the process of checking if the auth code is valid:
add_action( 'wp_authenticate', 'authcode_check', 5 );
function authcode_check($username) {
$options = get_option( 'authcode_settings' );
if(!empty($options['code'])) {
global $wpdb;
if ( !username_exists( $username ) ) {
return;
}
$set_code = $options['code'];
$submit_code = $_POST['auth_key'];
if(empty($submit_code)) {
add_filter( 'login_errors', function( $error ) {$error = '<strong>ERROR</strong>: Authentication code cannot be empty.';return $error;} );
return;
} elseif ( ! ( $set_code == $submit_code ) ) {
add_filter( 'login_errors', function( $error ) {$error = '<strong>ERROR</strong>: Authentication code is invalid.';return $error;} );
return;
}
}
}
The problem is; when the user enters their WordPress name and password correctly, but not the auth code, the form still submits and log the user in.
I tried return false but that didn't work.
Is there any way to prevent the form from logging the user in when they have entered a wrong auth code?
Working Example
Updated after chat with #J.Doe
We can hook into the login_form hook, to display the input for the authentication code:
/**
* 'Authentication Code' Input
*/
add_action( 'login_form', function()
{
// Fetch the stored code
$options = get_option( 'authcode_settings' );
// Display code input
if( isset( $options['code'] ) )
printf(
'<p class="login-authenticate">
<label for="auth_key">%s</label>
<input type="text" name="so38551606_auth_key" id="so38551606_auth_key"
class="input" value="" size="20" autocomplete="off" />
</p>',
esc_html__( 'Authentication Code', 'mydomain' )
);
} );
You can hook into the authenticate filter, within the wp_authenticate() function, for the validating part:
/**
* Validate 'Authentication Code' Input
*/
add_filter( 'authenticate', function( $user )
{
// Fetch stored code value
$options = get_option( 'authcode_settings' );
// Nothing to do if there's no stored code value
if( ! isset( $options['code'] ) )
return $user;
// Fetch the user's code input
$submit_code = isset( $_POST['so38551606_auth_key'] )
? $_POST['so38551606_auth_key']
: null;
// Validation's logic
$is_valid_auth_code = ! is_null( $submit_code )
&& ( $options['code'] === $submit_code );
// Valid auth code
if( $is_valid_auth_code )
return $user;
// Add an unvalid auth code error
if( is_wp_error( $user ) )
$user->add(
'invalid_auth_code',
sprintf(
'<strong>%s</strong>: %s',
esc_html__( 'ERROR', 'mydomain' ),
esc_html__( 'Authentication code is invalid.', 'mydomain' )
)
);
// Create a new auth code error
else
$user = new WP_Error(
'invalid_auth_code',
sprintf(
'<strong>%s</strong>: %s',
esc_html__( 'ERROR', 'mydomain' ),
esc_html__( 'Authentication code is invalid.', 'mydomain' )
)
);
return $user;
}, 100 );
Here we use a priority of 100 because we want to run it after the default callbacks of:
add_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 );
add_filter( 'authenticate', 'wp_authenticate_email_password', 20, 3 );
add_filter( 'authenticate', 'wp_authenticate_spam_check', 99 );
We prefixed the POST variable with so38551606_ to avoid possible name collision.
Output example:
Instead of return false use return as apparently WordPress prefers it that way, look at the existing username code it just returns, not false, not true, just return
Also place the add filter functions before the return statements as a return signifies that the code stops running at that point, nothing below it is executed anymore, so your filters won't appear unless you move them
Can you prove the user isn't being logged in? Or are you just assuming they are being logged in because the error messages aren't being shown?
Anything after the return keyword won't be run. So in your if statements, you have two return false;s if the auth key doesn't match and then you are calling a function afterwards. This function won't be run so the error code won't be displayed.
What you can do however, is move the function above the return keyword. It will then display the error and return false.