We have increased the price of our subscriptions but the renewals so far are going through at the old rate although new subscribers are at the new rate.
Is there any way to make the price increase reflect in the existing subscribers recurring payments.
Thank you
I found the way to do this was to modify the woocommerce database.
There are three values that need to be changed
1) In the 'wp_woocommerce_order_itemmeta' table the '_line_total' value
2) In the 'wp_woocommerce_order_itemmeta' table the '_line_subtotal' value
This is based on the item id, which is looked up in the 'wp_woocommerce_order_items' table by the order number
3) In the 'wp_postmeta table', the '_order_total' value
This is based on the order number, as shown in the subscriptions list
Some code that did this:
$query = "SELECT order_item_id FROM wp_woocommerce_order_items where order_id=$orderno";
if ($result = $mysqli->query($query))
{
while ($row = $result->fetch_object())
{
$itemid = $row->order_item_id;
$itemquery = "SELECT meta_value FROM wp_woocommerce_order_itemmeta where order_item_id=$itemid and meta_key='_variation_id'";
if ($itemresult = $mysqli->query($itemquery))
{
$variationid = $itemresult->fetch_object()->meta_value;
.... verify that have the right product.....
}
$sql = "UPDATE wp_woocommerce_order_itemmeta SET meta_value='$newamount' where order_item_id=$itemid and meta_key='_line_total'";
$modresult = $mysqli->query($sql);
$sql = "UPDATE wp_woocommerce_order_itemmeta SET meta_value='$newamount' where order_item_id=$itemid and meta_key='_line_subtotal'";
$modresult = $mysqli->query($sql);
}
}
$sql = "UPDATE wp_postmeta SET meta_value='$newamount' where post_id=$orderno and meta_key='_order_total'";
printf("SQL: $sql\n", FILE_APPEND);
$modresult = $mysqli->query($sql);
printf("Update order total result:" . print_r($modresult, true)."\n");
You need to Synchronize the renewal for that, or else they will get old rates
The Renewal Synchronization feature is switched off by default. To enable renewal synchronization:
Go to: WooCommerce > Settings > Subscriptions
Click the Synchronize Renewals checkbox
Click the Save Changes button
Read More Here
Using the available Woo Subscriptions APIs, you can:
Retrieve a list of all "active" subscriptions which include products you are using.
Update the subscription item's prices.
This snippet will include subscriptions with products 44 and 99. It will update all subscription item's prices to $75.
$product_ids = [ 44, 99 ];
$price = '75';
$subs = wcs_get_subscriptions_for_product( $product_ids, 'subscription', [
'subscription_status' => 'active',
] );
foreach ( $subs as $sub ) {
foreach ( $sub->get_items() as $item ) {
wc_save_order_items( $sub->get_id(), [
'order_item_id' => [
$item->get_id(),
],
'line_subtotal' => [
$item->get_id() => $price,
],
'line_total' => [
$item->get_id() => $price,
],
] );
echo "Subscription {$sub->get_id()} price updated to {$price}. <br />";
}
}
It seems to run into memory problems after about 150 items so you may limit the results to 100 and use an offset to paginate them.
$subs = wcs_get_subscriptions_for_product( $product_ids, 'subscription', [
'subscription_status' => 'active',
'limit' => 100,
'offset' => 0, // set to increments of 100.
] );
If you want to set different prices based on different products.
foreach ( $subs as $sub ) {
foreach ( $sub->get_items() as $item ) {
switch ( $item->get_data()['product_id'] ) {
case 44:
$price = '64';
break;
case 99:
$price = '47';
break;
default:
return;
}
}
}
If you only want to change prices for payment gateways which support changing the price (e.g. ! PayPal Standard ).
foreach ( $subs as $sub ) {
if ( ! \in_array( $sub->get_payment_method(), [ 'stripe', 'manual' ] ) ) {
continue;
}
}
Putting it all together.
$product_ids = [ 44, 99 ];
$price = '75';
$subs = wcs_get_subscriptions_for_product( $product_ids, 'subscription', [
'subscription_status' => 'active',
'limit' => 100,
'offset' => 0, // set to increments of 100.
] );
foreach ( $subs as $sub ) {
if ( ! \in_array( $sub->get_payment_method(), [ 'stripe', 'manual' ] ) ) {
continue;
}
foreach ( $sub->get_items() as $item ) {
switch ( $item->get_data()['product_id'] ) {
case 44:
$price = '64';
break;
case 99:
$price = '47';
break;
default:
return;
}
wc_save_order_items( $sub->get_id(), [
'order_item_id' => [
$item->get_id(),
],
'line_subtotal' => [
$item->get_id() => $price,
],
'line_total' => [
$item->get_id() => $price,
],
] );
echo "Subscription {$sub->get_id()} price updated to {$price}. <br />";
}
}
Related
I want sync stock quantity of site B (remote site) with site A, ie. when an order in site A is in process status, the quantity of same product in site B will be updated.
For mapping product IDs, I add post meta in both site A and B.
In order to do so, I use following codes. It works, but my problem is speed process when stock quantity wants to be updated in site B, it takes more seconds.
add_action('woocommerce_order_status_changed', 'update_base_site_inventory',11, 1);
function update_base_site_inventory($order_id){
$order = wc_get_order( $order_id );
if ($order->status == 'processing') {
$items = $order->get_items();
foreach($items as $item){
$product = wc_get_product($item->get_product_id());
$product_quantity = $product->get_stock_quantity();
$id_mapped = get_post_meta($item->get_product_id(), 'map_product_id', true);
$batch_update['update'] = array(array('id' => current($id_mapped), 'stock_quantity' => $product_quantity));
}
$api_response = wp_remote_request(
'https://...../wp-json/wc/v3/products/batch/', array(
'method' => 'POST',
'headers' => array('Authorization' => 'Basic ' . base64_encode( '....:.....' )),
'body' => $batch_update
)
);
}
}
is my approach correct? what should I do? is there any way to increase the API request speed?
Thanks,
Try using WooCommerce Client Library, install using composer:
composer require automattic/woocommerce
Then use it in your code like this:
add_action('woocommerce_order_status_changed', 'update_base_site_inventory', 11, 1);
function update_base_site_inventory($order_id)
{
$order = wc_get_order($order_id);
if ($order->status == 'processing') {
$data = array();
$items = $order->get_items();
foreach ($items as $item) {
$product = wc_get_product($item->get_product_id());
$product_quantity = $product->get_stock_quantity();
$id_mapped = get_post_meta($item->get_product_id(), 'map_product_id', true);
$data[] = array(
'id' => current($id_mapped),
'stock_quantity' => $product_quantity
);
}
// Include the library
require __DIR__ . '/vendor/autoload.php';
// Initialize the library
$woocommerce = new Automattic\WooCommerce\Client(
'https://example.com', // Your store URL
'ck_....................', // Your consumer key
'cs_....................', // Your consumer secret
[
'wp_api' => true, // Enable the WP REST API integration
'version' => 'wc/v3', // WooCommerce WP REST API version
'verify_ssl' => false
]
);
// Make the update
$woocommerce->post('products/batch', array(
'update' => array($data),
));
}
}
I have a bug with the edit quantity in an order while using a custom status. The edit arrow seems to have disappeared?
Is there another value I can include while registering a custom order status?
Here is my code:
// Register new status 'To order'
function register_new_on_hold_order_status2() {
register_post_status( 'wc-to-order', array(
'label' => 'To order',
'public' => true,
'exclude_from_search' => false,
'show_in_admin_all_list' => true,
'show_in_admin_status_list' => true,
'label_count' => _n_noop( 'To order (%s)', 'To order (%s)' )
) );
}
add_action( 'init', 'register_new_on_hold_order_status2' );
// Add to list of WC Order statuses
function add_on_hold_new_to_order_statuses2( $order_statuses ) {
$new_order_statuses = array();
// add new order status after processing
foreach ( $order_statuses as $key => $status ) {
$new_order_statuses[ $key ] = $status;
if ( 'wc-processing' === $key ) { // Here we Define after which to be added
$new_order_statuses['wc-to-order'] = 'To order';
}
}
return $new_order_statuses;
}
add_filter( 'wc_order_statuses', 'add_on_hold_new_to_order_statuses2' );
This is not a bug, certain order statuses such as processing do not allow the order to be editable. To change this you can use the wc_order_is_editable hook
So you get:
function filter_wc_order_is_editable( $editable, $order ) {
// Compare
if ( $order->get_status() == 'your-status' ) {
$editable = true;
}
return $editable;
}
add_filter( 'wc_order_is_editable', 'filter_wc_order_is_editable', 10, 2 );
Note: use woocommerce_register_shop_order_post_statuses
opposite init while registering a custom order status as applied in the 2nd part of this answer
I have added a new custom field in checkout page named " Date of event ", It's working fine. But i want one thing to be done which is " When user order single/multiple products then hide "Add to cart" button and show unavailable message instead of button for that selected date of event. " Like if user selected date " 7/2/2019 " in " Date of event field " during checkout, then after he ordered that product, hide " Add to cart " button and show unavailable message instead of button for " 7/2/2019 " date of event. I don't know how to do this.
Which hooks and actions will do this. I have googled it a lot, but didn't get any answer.
Please help me.
Custom field Code:
add_action('woocommerce_after_checkout_billing_form', 'date_of_event_field');
function date_of_event_field($checkout){
echo '<div id="date_of_event_field" class="margin-top-20">';
woocommerce_form_field( 'date_of_event', array(
'type' => 'date',
'class' => array('my-field-class form-row-wide'),
'label' => __('Date Of Event'),
'required' => true,
), $checkout->get_value( 'date_of_event' ));
echo '</div>';
}
Code to hide Add to cart button and show message instead of button:
function make_product_unavailable( $_product, $order ) {
if( $order->id == $_product ){
remove_action( 'woocommerce_after_shop_loop_item', 'woocommerce_template_loop_add_to_cart');
remove_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_add_to_cart');
}
}
It's a try from my side, because i don't know how to do to this and i don't know which filter/action hook will be used for this.
Please help me.
Source : https://wisdmlabs.com/blog/the-right-way-to-hide-add-to-cart-button-in-woocommerce/
Check if a customer has purchased a specific products in WooCommerce
Note : The below code is not tested but it will work if some part is modified as per your requirement..
add_filter( 'woocommerce_is_purchasable', 'hidecart',10,1);
function hidecart(){
$found = has_bought_items();
if(!empty($found["order_date"])){
$current_date = date("d/m/Y");
if($found["order_date"] == $current_date && $found["product_id"] == get_the_ID()){
return false;
}
}
}
function has_bought_items()
{
$bought = false;
$order_date = '';
//get product id if single or for shop page u will have to retrieve in some other way
$product_id = get_the_ID();
// Get all customer orders
$customer_orders = get_posts( array(
'numberposts' => -1,
'meta_key' => '_customer_user',
'meta_value' => get_current_user_id(),
'post_type' => 'shop_order', // WC orders post type
'post_status' => 'wc-completed' // Only orders with status "completed"
) );
foreach ( $customer_orders as $customer_order ) {
// Updated compatibility with WooCommerce 3+
$order_id = method_exists( $order, 'get_id' ) ? $order->get_id() : $order->id;
$order = wc_get_order( $customer_order );
// Iterating through each current customer products bought in the order
foreach ($order->get_items() as $item) {
// WC 3+ compatibility
if ( version_compare( WC_VERSION, '3.0', '<' ) )
$order_product_id = $item['product_id'];
else
$order_product_id = $item->get_product_id();
// Your condition related to your 2 specific products Ids
if ( $product_id == $product_id) ) {
$bought = true;
$order_date = $order->order_date;
// you can fetch your event date stored as order meta
$arr = array("product_id"=>$product_id,"order_date"=>$order_date,"bought"=>$bought);
return $arr;
}
}
}
$arr = array("product_id"=>"","order_date"=>$order_date,"bought"=>$bought);
return $order_date;
}
I've got a question - is it possible to add one more column (which will present data from custom field) to csv export file in woocommerce? all necessary data is stored for each order as meta_key: 'authors_income'. I have no idea how to make it. I've tried to adapt below code, but it doesnt work:
function wpg_add_columns($cols) {
$cols['wc_settings_tab_payment_method'] = __('Payment Method', 'mytheme');
return $cols;
}
add_filter('wpg_order_columns', 'wpg_add_columns');
This should work. Let me know this goes.
<?php
//Step 1: Add field to column
function wpg_add_columns($cols) {
$cols['wc_settings_tab_authors_income'] = __('Authors Income', 'mytheme');
return $cols;
}
add_filter('wpg_order_columns', 'wpg_add_columns');
//Step 2: Add same column to settings, so that it will appear in settings page.
function wpg_add_fields($settings) {
$settings['authors_income'] = array(
'name' => __( 'Authors Income', 'woocommerce-simply-order-export' ),
'type' => 'checkbox',
'desc' => __( 'Authors Income', 'woocommerce-simply-order-export' ),
'id' => 'wc_settings_tab_authors_income'
);
return $settings;
}
add_filter('wc_settings_tab_order_export', 'wpg_add_fields');
//Note: Notice that in above step, id attribute is identical to that of array key in step 1. ( wc_settings_tab_authors_income )
//Step 3: Add it to csv.
function csv_write( &$csv, $od, $fields ) {
if( !empty( $fields['wc_settings_tab_authors_income'] ) && $fields['wc_settings_tab_authors_income'] === true ){
$authors_income = get_post_meta( $od->id, 'authors_income', true );
array_push( $csv, $authors_income );
}
}
add_action('wpg_add_values_to_csv', 'csv_write', 10, 3);
I'm trying to use Triggers and a custom action to give special role to users (on add/edit) given a particular profile field. So here's my code:
function mymodule_action_info() {
return array(
'mymodule_user_appropriate_role' => array(
'description' => t('Assign user special role'),
'type' => 'user',
'configurable' => FALSE,
'hooks' => array(
'user' => array('insert', 'update'),
),
),
);
}
and then
function mymodule_user_appropriate_role(&$object, $context = array()) {
// get the uid from the object
if( isset( $object->uid ) ){
$thisUID = $object->uid;
}else if( isset( $context['uid'] ) ){
$thisUID = $context['uid'];
}else{
global $user;
$thisUID = $user->uid;
}
// make sure we have a user record
if( $thisUID ){
// load user object
$thisUser = user_load( $thisUID );
// get user profile object
profile_load_profile( $thisUser );
if( $thisUser->profile_special_field == "value1" ){
// FIRST APPROACH
db_query( 'INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $thisUser->uid, 5 ); // 5 is the custom role ID
// SECOND APPROACH
$thisUserRoles = $thisUser->roles;
if( !isset( $thisUserRoles[5] ) ){
$thisUserRoles[5] = "RID 5 Rule Name";
}
user_save( $thisUser, array( 'roles' => $thisUserRoles ) );
// THIRD APPROACH
$allUserRoles = user_roles();
user_save( $thisUser, array( 'roles' => array( array_search( 'RID 5 Rule Name', $allUserRoles ) => 1 ) ) );
}
}
}
But none of these 3 approaches worked. I'm sure the action is called and entering the if( $thisUser->profile_special_field == "value1" ) statement
I'm struggling with this since yesterday, so any help is most welcome...
I ended up with hook_user. Here is the code for reference:
function mymodule_helper_user($op, &$edit, &$account, $category = NULL){
// DEFINE OPERATIONS LIST TO ACT ON
$opList = array( 'insert', 'update', 'after_update', 'submit' );
if( in_array( $op, $opList ) ){
// REVOKE ALL CUSTOM ROLES, HERE RID 5 AND 6
db_query( 'DELETE FROM {users_roles} WHERE rid IN (5,6) AND uid = %d', $account->uid );
if( $account->profile_custom_field == 'value1' ){
// GIVE CUSTOM RID 5
db_query( 'INSERT IGNORE INTO {users_roles} (uid, rid) VALUES (%d, %d)', $account->uid, 5 );
}else if( $account->profile_custom_field == 'value2' ){
// GIVE CUSTOM RID 6
db_query( 'INSERT IGNORE INTO {users_roles} (uid, rid) VALUES (%d, %d)', $account->uid, 6 );
}
}
}
For the operation list, doing it on "update" didn't work for me, as opposed to "after_update", that's why I added the four suspected ops until I test and keep only what's needed
Hope it helps