diff --git a/assets/src/js/withdraw.js b/assets/src/js/withdraw.js index 9835b7a01c..f7ddf676f9 100644 --- a/assets/src/js/withdraw.js +++ b/assets/src/js/withdraw.js @@ -26,6 +26,28 @@ $("input[name='withdraw-schedule']").on( 'change', (e) => { Dokan_Withdraw.handleScheduleChange( e ); }); + + $( "[name='withdraw_method'][id='withdraw-method']" ).on( 'change', ( e ) => { + Dokan_Withdraw.calculateWithdrawCharges(); + } ); + + $( 'input#withdraw-amount' ).on( 'keyup', Dokan_Withdraw.debounce( Dokan_Withdraw.calculateWithdrawCharges, 500 ) ); + }, + + debounce( func, wait, immediate ) { + var timeout; + return function () { + var context = this, + args = arguments; + var later = function () { + timeout = null; + if ( ! immediate ) func.apply( context, args ); + }; + var callNow = immediate && ! timeout; + clearTimeout( timeout ); + timeout = setTimeout( later, wait ); + if ( callNow ) func.apply( context, args ); + }; }, openRequestWithdrawWindow: () => { const withdrawTemplate = wp.template( 'withdraw-request-popup' ), @@ -33,6 +55,9 @@ width : 690, overlayColor: 'rgba(0, 0, 0, 0.8)', headerColor : dokan.modal_header_color, + onOpening : function ( modal ) { + Dokan_Withdraw.calculateWithdrawCharges(); + }, } ); modal.iziModal( 'setContent', withdrawTemplate().trim() ); @@ -200,6 +225,75 @@ const nextDate = $(e.target).data('next-schedule'); $( '#dokan-withdraw-next-scheduled-date').html(nextDate); }, + calculateWithdrawCharges: () => { + let charges = $( "select[name='withdraw_method'][id='withdraw-method'] option:selected" ).data(); + if ( + $( '#dokan-send-withdraw-request-popup-form > .dokan-alert-danger' ).length + || ! charges + ) { + return; + } + + let withdrawMethod = $( "[name='withdraw_method'][id='withdraw-method']" ).val(); + let withdrawAmount = $( "[name='withdraw_amount'][id='withdraw-amount']" ).val(); + + withdrawAmount = accounting.unformat( + withdrawAmount, + dokan.mon_decimal_point + ); + let { chargePercentage, chargeFixed } = $( + "select[name='withdraw_method'][id='withdraw-method'] option:selected" + ).data(); + let chargeAmount = 0; + let chargeText = ''; + + if ( chargeFixed ) { + chargeText += Dokan_Withdraw.formatMoney( chargeFixed ); + chargeAmount += chargeFixed; + } + if ( chargePercentage ) { + let percentageAmount = chargePercentage / 100 * withdrawAmount; + chargeAmount += percentageAmount; + chargeText += chargeText ? ' + ' : ''; + chargeText += parseFloat( accounting.formatNumber( chargePercentage, dokan.rounding_precision, '' ) ) + .toString() + .replace('.', dokan.mon_decimal_point ) + '%'; + chargeText += ` = ${ Dokan_Withdraw.formatMoney( chargeAmount ) }`; + } + + if ( ! chargeText ) { + chargeText = Dokan_Withdraw.formatMoney( chargeAmount, dokan.currency ); + } + + Dokan_Withdraw.showWithdrawChargeHtml( chargeText, chargeAmount, withdrawAmount ); + }, + + formatMoney( money ) { + return accounting.formatMoney( money, { + symbol: dokan.currency_format_symbol, + decimal: dokan.currency_format_decimal_sep, + thousand: dokan.currency_format_thousand_sep, + precision: dokan.currency_format_num_decimals, + format: dokan.currency_format + } ) + }, + + showWithdrawChargeHtml(chargeText, chargeAmount, withdrawAmount) { + let chargeSection = $('#dokan-withdraw-charge-section'); + let revivableSection = $('#dokan-withdraw-revivable-section'); + + if (!withdrawAmount) { + chargeSection.hide(); + revivableSection.hide(); + return; + } + + $('#dokan-withdraw-charge-section-text').html(chargeText); + $('#dokan-withdraw-revivable-section-text').html(Dokan_Withdraw.formatMoney(withdrawAmount - chargeAmount)); + + chargeSection.show(); + revivableSection.show(); + } }; $(document).ready(function() { diff --git a/dokan.php b/dokan.php index ff08ce1aa6..d2ad3c1b5d 100755 --- a/dokan.php +++ b/dokan.php @@ -54,6 +54,7 @@ * @property WeDevs\Dokan\Product\Manager $product Instance of Order Manager class * @property WeDevs\Dokan\Vendor\Manager $vendor Instance of Vendor Manager Class * @property WeDevs\Dokan\BackgroundProcess\Manager $bg_process Instance of WeDevs\Dokan\BackgroundProcess\Manager class + * @property WeDevs\Dokan\Withdraw\Manager $withdraw Instance of WeDevs\Dokan\Withdraw\Manager class * @property WeDevs\Dokan\Frontend\Frontend $frontend_manager Instance of \WeDevs\Dokan\Frontend\Frontend class */ final class WeDevs_Dokan { @@ -584,7 +585,7 @@ public function get_db_version_key() { * * @return WeDevs_Dokan */ -function dokan() { +function dokan() { // phpcs:ignore return WeDevs_Dokan::init(); } diff --git a/includes/Admin/Settings.php b/includes/Admin/Settings.php index bf19079aea..fcaba5c1cd 100644 --- a/includes/Admin/Settings.php +++ b/includes/Admin/Settings.php @@ -319,7 +319,7 @@ public function get_settings_sections() { 'description' => __( 'Withdraw Settings, Threshold', 'dokan-lite' ), 'document_link' => 'https://wedevs.com/docs/dokan/settings/withdraw-options/', 'settings_title' => __( 'Withdraw Settings', 'dokan-lite' ), - 'settings_description' => __( 'You can configure your store\'s withdrawal methods, limits, order status and more.', 'dokan-lite' ), + 'settings_description' => __( 'You can configure your store\'s withdrawal methods, charges, limits, order status and more.', 'dokan-lite' ), ], [ 'id' => 'dokan_reverse_withdrawal', @@ -617,6 +617,24 @@ public function get_settings_fields() { 'options' => dokan_withdraw_get_methods(), 'tooltip' => __( 'Check to add available payment methods for vendors to withdraw money.', 'dokan-lite' ), ], + 'withdraw_charges' => [ + 'name' => 'withdraw_charges', + 'label' => __( 'Withdraw Charges', 'dokan-lite' ), + 'desc' => __( 'Select suitable withdraw charges for vendors', 'dokan-lite' ), + 'type' => 'charges', + 'options' => dokan_withdraw_get_methods(), + 'chargeable_methods' => dokan_withdraw_get_chargeable_methods(), + 'default' => dokan_withdraw_get_method_charges(), + 'show_if' => [ + 'withdraw_methods' => [ + 'contains-any' => array_keys( dokan_withdraw_get_methods() ), + ], + ], + 'items_show_if' => [ + 'key' => 'withdraw_methods', + 'condition' => 'contains-key-value', + ], + ], 'withdraw_limit' => [ 'name' => 'withdraw_limit', 'label' => __( 'Minimum Withdraw Limit', 'dokan-lite' ), diff --git a/includes/Assets.php b/includes/Assets.php index db566c4305..f6842b1690 100644 --- a/includes/Assets.php +++ b/includes/Assets.php @@ -478,7 +478,7 @@ public function get_scripts() { ], 'dokan-script' => [ 'src' => $asset_url . '/js/dokan.js', - 'deps' => [ 'imgareaselect', 'customize-base', 'customize-model', 'dokan-i18n-jed', 'jquery-tiptip', 'moment', 'dokan-date-range-picker' ], + 'deps' => [ 'imgareaselect', 'customize-base', 'customize-model', 'dokan-i18n-jed', 'jquery-tiptip', 'moment', 'dokan-date-range-picker', 'dokan-accounting' ], 'version' => filemtime( $asset_path . 'js/dokan.js' ), ], 'dokan-vue-vendor' => [ @@ -576,24 +576,30 @@ public function enqueue_front_scripts() { } $default_script = [ - 'ajaxurl' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'dokan_reviews' ), - 'ajax_loader' => DOKAN_PLUGIN_ASSEST . '/images/ajax-loader.gif', - 'seller' => [ + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'dokan_reviews' ), + 'ajax_loader' => DOKAN_PLUGIN_ASSEST . '/images/ajax-loader.gif', + 'seller' => [ 'available' => __( 'Available', 'dokan-lite' ), 'notAvailable' => __( 'Not Available', 'dokan-lite' ), ], - 'delete_confirm' => __( 'Are you sure?', 'dokan-lite' ), - 'wrong_message' => __( 'Something went wrong. Please try again.', 'dokan-lite' ), - 'vendor_percentage' => dokan_get_seller_percentage( dokan_get_current_user_id() ), - 'commission_type' => dokan_get_commission_type( dokan_get_current_user_id() ), - 'rounding_precision' => wc_get_rounding_precision(), - 'mon_decimal_point' => wc_get_price_decimal_separator(), - 'product_types' => apply_filters( 'dokan_product_types', [ 'simple' ] ), - 'loading_img' => DOKAN_PLUGIN_ASSEST . '/images/loading.gif', - 'store_product_search_nonce' => wp_create_nonce( 'dokan_store_product_search_nonce' ), - 'i18n_download_permission' => __( 'Are you sure you want to revoke access to this download?', 'dokan-lite' ), - 'i18n_download_access' => __( 'Could not grant access - the user may already have permission for this file or billing email is not set. Ensure the billing email is set, and the order has been saved.', 'dokan-lite' ), + 'delete_confirm' => __( 'Are you sure?', 'dokan-lite' ), + 'wrong_message' => __( 'Something went wrong. Please try again.', 'dokan-lite' ), + 'vendor_percentage' => dokan_get_seller_percentage( dokan_get_current_user_id() ), + 'commission_type' => dokan_get_commission_type( dokan_get_current_user_id() ), + 'rounding_precision' => wc_get_rounding_precision(), + 'mon_decimal_point' => wc_get_price_decimal_separator(), + 'currency_format_num_decimals' => wc_get_price_decimals(), + 'currency_format_symbol' => get_woocommerce_currency_symbol(), + 'currency_format_decimal_sep' => esc_attr( wc_get_price_decimal_separator() ), + 'currency_format_thousand_sep' => esc_attr( wc_get_price_thousand_separator() ), + 'currency_format' => esc_attr( str_replace( [ '%1$s', '%2$s' ], [ '%s', '%v' ], get_woocommerce_price_format() ) ), // For accounting JS + 'round_at_subtotal' => get_option( 'woocommerce_tax_round_at_subtotal', 'no' ), + 'product_types' => apply_filters( 'dokan_product_types', [ 'simple' ] ), + 'loading_img' => DOKAN_PLUGIN_ASSEST . '/images/loading.gif', + 'store_product_search_nonce' => wp_create_nonce( 'dokan_store_product_search_nonce' ), + 'i18n_download_permission' => __( 'Are you sure you want to revoke access to this download?', 'dokan-lite' ), + 'i18n_download_access' => __( 'Could not grant access - the user may already have permission for this file or billing email is not set. Ensure the billing email is set, and the order has been saved.', 'dokan-lite' ), /** * Filter of maximun a vendor can add tags. * diff --git a/includes/Dashboard/Templates/Withdraw.php b/includes/Dashboard/Templates/Withdraw.php index 0664237822..13347a54fb 100755 --- a/includes/Dashboard/Templates/Withdraw.php +++ b/includes/Dashboard/Templates/Withdraw.php @@ -434,13 +434,16 @@ public function withdraw_dashboard_layout_display() { return; } + /** + * @var $last_withdraw \WeDevs\Dokan\Withdraw\Withdraw[] + */ $last_withdraw = dokan()->withdraw->get_withdraw_requests( dokan_get_current_user_id(), 1, 1 ); $payment_details = __( 'You do not have any approved withdraw yet.', 'dokan-lite' ); if ( ! empty( $last_withdraw ) ) { - $last_withdraw_amount = '' . wc_price( $last_withdraw[0]->amount ) . ''; - $last_withdraw_date = '' . dokan_format_date( $last_withdraw[0]->date ) . ''; - $last_withdraw_method_used = '' . dokan_withdraw_get_method_title( $last_withdraw[0]->method ) . ''; + $last_withdraw_amount = '' . wc_price( $last_withdraw[0]->get_amount() ) . ''; + $last_withdraw_date = '' . dokan_format_date( $last_withdraw[0]->get_date() ) . ''; + $last_withdraw_method_used = '' . dokan_withdraw_get_method_title( $last_withdraw[0]->get_method() ) . ''; // translators: 1: Last formatted withdraw amount 2: Last formatted withdraw date 3: Last formatted withdraw method used. $payment_details = sprintf( __( '%1$s on %2$s to %3$s', 'dokan-lite' ), $last_withdraw_amount, $last_withdraw_date, $last_withdraw_method_used ); @@ -502,9 +505,10 @@ public function withdraw_request_popup_form_content() { $default_withdraw_method = dokan_withdraw_get_default_method( $current_user_id ); dokan_get_template_part( 'withdraw/request-form', '', array( - 'amount' => NumberUtil::round( $balance, wc_get_price_decimals(), PHP_ROUND_HALF_DOWN ), // we are setting 12.3456 to 12.34 not 12.35 - 'withdraw_method' => $default_withdraw_method, - 'payment_methods' => $payment_methods, + 'amount' => NumberUtil::round( $balance, wc_get_price_decimals(), PHP_ROUND_HALF_DOWN ), // we are setting 12.3456 to 12.34 not 12.35 + 'withdraw_method' => $default_withdraw_method, + 'payment_methods' => $payment_methods, + 'withdraw_charges' => dokan_withdraw_get_method_charges(), ) ); } diff --git a/includes/REST/WithdrawController.php b/includes/REST/WithdrawController.php index 171a9a59ad..36a22a2455 100644 --- a/includes/REST/WithdrawController.php +++ b/includes/REST/WithdrawController.php @@ -5,6 +5,7 @@ use Cassandra\Date; use Exception; use WeDevs\Dokan\Cache; +use WeDevs\Dokan\Withdraw\Withdraw; use WP_Error; use WP_REST_Controller; use WP_REST_Request; @@ -134,6 +135,43 @@ public function register_routes() { 'schema' => [ $this, 'get_public_batch_schema' ], ] ); + + register_rest_route( + $this->namespace, '/' . $this->rest_base . '/charges', [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_all_method_charges' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], + ], + ] + ); + + $methods = array_keys( dokan_withdraw_get_methods() ); + + register_rest_route( + $this->namespace, '/' . $this->rest_base . '/charge', [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_method_charge' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], + 'args' => [ + 'method' => [ + 'description' => __( 'Withdraw method key', 'dokan-lite' ), + 'type' => 'string', + 'context' => [ 'view' ], + 'enum' => $methods, + 'required' => true, + ], + 'amount' => [ + 'description' => __( 'Withdraw amount', 'dokan-lite' ), + 'type' => 'number', + 'context' => [ 'view' ], + 'required' => true, + ], + ], + ], + ] + ); } /** @@ -417,32 +455,40 @@ public function create_item( $request ) { throw new DokanException( 'dokan_rest_withdraw_error', __( 'No vendor found', 'dokan-lite' ), 404 ); } - $args = [ - 'user_id' => $user_id, - 'amount' => $request['amount'], - 'date' => current_time( 'mysql' ), - 'method' => $request['method'], - 'note' => $note, - 'ip' => dokan_get_client_ip(), - ]; - if ( dokan()->withdraw->has_pending_request( $user_id ) ) { throw new DokanException( 'dokan_rest_withdraw_error', __( 'You already have a pending withdraw request', 'dokan-lite' ), 400 ); } - $validate_request = dokan()->withdraw->is_valid_approval_request( $args ); + $validate_request = dokan()->withdraw->is_valid_approval_request( + [ + 'user_id' => $user_id, + 'amount' => $request['amount'], + 'method' => $request['method'], + ] + ); if ( is_wp_error( $validate_request ) ) { throw new DokanException( 'dokan_rest_withdraw_error', $validate_request->get_error_message(), 400 ); } - $withdraw = dokan()->withdraw->create( $args ); + $withdraw = new Withdraw(); - if ( is_wp_error( $withdraw ) ) { - throw new DokanException( $withdraw->get_error_code(), $withdraw->get_error_message(), 400 ); + $withdraw + ->set_user_id( $user_id ) + ->set_amount( $request['amount'] ) + ->set_date( dokan_current_datetime()->format( 'Y-m-d H:i:s' ) ) + ->set_status( dokan()->withdraw->get_status_code( 'pending' ) ) + ->set_method( $request['method'] ) + ->set_ip( dokan_get_client_ip() ) + ->set_note( $note ); + + $result = $withdraw->save(); + + if ( is_wp_error( $result ) ) { + throw new DokanException( $result->get_error_code(), $result->get_error_message(), 400 ); } - $response = $this->prepare_item_for_response( $withdraw, $request ); + $response = $this->prepare_item_for_response( $result, $request ); $response = rest_ensure_response( $response ); $response->set_status( 201 ); @@ -702,11 +748,61 @@ public function batch_items( $request ) { ); } + /** + * Get all withdraw method charges. + * + * @since DOKAN_SINCE + * + * @return WP_Error|\WP_HTTP_Response|WP_REST_Response + */ + public function get_all_method_charges() { + $all_charges = dokan_withdraw_get_method_charges(); + + return rest_ensure_response( $all_charges ); + } + + /** + * Get withdraw method charge. + * + * @since DOKAN_SINCE + * + * @return WP_Error|\WP_HTTP_Response|WP_REST_Response + */ + public function get_method_charge( $request ) { + $method = $request->get_param( 'method' ); + $amount = wc_format_decimal( $request->get_param( 'amount' ) ); + + $withdraw = new Withdraw(); + + $withdraw->set_method( $method ) + ->set_amount( $amount ) + ->calculate_charge(); + + $response = [ + 'charge' => $withdraw->get_charge(), + 'receivable' => $withdraw->get_receivable_amount(), + 'charge_data' => $withdraw->get_charge_data(), + ]; + + if ( $withdraw->get_receivable_amount() < 0 ) { + return new WP_Error( + 'invalid-withdraw-amount', + __( 'Invalid withdraw amount. The withdraw charge is greater than the withdraw amount', 'dokan-lite' ), + $response + ); + } + + return rest_ensure_response( $response ); + } + /** * Prepare data for response * * @since 2.8.0 * + * @param $withdraw \WeDevs\Dokan\Withdraw\Withdraw + * @param $request \WP_REST_Request + * * @return WP_REST_Response|WP_Error */ public function prepare_item_for_response( $withdraw, $request ) { @@ -735,6 +831,9 @@ public function prepare_item_for_response( $withdraw, $request ) { 'note' => $withdraw->get_note(), 'details' => $details, 'ip' => $withdraw->get_ip(), + 'charge' => $withdraw->get_charge(), + 'receivable' => $withdraw->get_receivable_amount(), + 'charge_data' => $withdraw->get_charge_data(), ]; $response = rest_ensure_response( $data ); diff --git a/includes/Withdraw/Hooks.php b/includes/Withdraw/Hooks.php index 5d7dd09637..23acb51a9c 100644 --- a/includes/Withdraw/Hooks.php +++ b/includes/Withdraw/Hooks.php @@ -56,7 +56,7 @@ public function download_withdraw_log_export_file() { * * @param string $title * @param string $method_key - * @param object|null $request + * @param Withdraw $request * * @return string */ @@ -67,8 +67,8 @@ public function dokan_withdraw_dokan_custom_method_title( $title, $method_key, $ if ( empty( $title ) ) { $title = __( 'Custom', 'dokan-lite' ); } - if ( null !== $request ) { - $details = maybe_unserialize( $request->details ); + if ( null !== $request && null !== $request->get_details() ) { + $details = maybe_unserialize( $request->get_details() ); if ( isset( $details['value'] ) ) { $title .= ' - ' . $details['value']; } @@ -184,19 +184,21 @@ public function ajax_handle_withdraw_request() { wp_send_json_error( $validate_request->get_error_message(), $validate_request->get_error_code() ); } - $data = [ - 'user_id' => $user_id, - 'amount' => $amount, - 'status' => dokan()->withdraw->get_status_code( 'pending' ), - 'method' => $method, - 'ip' => dokan_get_client_ip(), - 'note' => '', - ]; + $withdraw = new Withdraw(); + + $withdraw + ->set_user_id( $user_id ) + ->set_amount( $amount ) + ->set_date( dokan_current_datetime()->format( 'Y-m-d H:i:s' ) ) + ->set_status( dokan()->withdraw->get_status_code( 'pending' ) ) + ->set_method( $method ) + ->set_ip( dokan_get_client_ip() ) + ->set_note( '' ); - $withdraw = dokan()->withdraw->create( $data ); + $result = $withdraw->save(); - if ( is_wp_error( $withdraw ) ) { - wp_send_json_error( $withdraw->get_error_message(), $withdraw->get_error_code() ); + if ( is_wp_error( $result ) ) { + wp_send_json_error( $result->get_error_message(), $result->get_error_code() ); } do_action( 'dokan_after_withdraw_request', $user_id, $amount, $method ); diff --git a/includes/Withdraw/Manager.php b/includes/Withdraw/Manager.php index 55ab2f5dac..0d87f8cc14 100644 --- a/includes/Withdraw/Manager.php +++ b/includes/Withdraw/Manager.php @@ -27,12 +27,16 @@ class Manager { * @return bool|\WP_Error */ public function is_valid_approval_request( $args ) { + $withdraw = new Withdraw(); $user_id = $args['user_id']; $limit = $this->get_withdraw_limit(); $balance = wc_format_decimal( dokan_get_seller_balance( $user_id, false ), 2 ); $amount = wc_format_decimal( $args['amount'], 2 ); $method = $args['method']; + $all_withdraw_charges = dokan_withdraw_get_method_charges(); + $charge_data = $all_withdraw_charges[ $method ]; + if ( empty( $amount ) ) { return new WP_Error( 'dokan_withdraw_empty', __( 'Withdraw amount required ', 'dokan-lite' ) ); } @@ -58,6 +62,18 @@ public function is_valid_approval_request( $args ) { return new WP_Error( 'dokan_withdraw_already_approved', __( 'Withdraw is already approved.', 'dokan-lite' ) ); } + $withdraw + ->set_user_id( $user_id ) + ->set_amount( $amount ) + ->set_method( $method ) + ->set_charge_data( $charge_data ) + ->calculate_charge(); + + // Check if withdraw amount is equal or greater than the charge. + if ( $withdraw->get_receivable_amount() < 0 ) { + return new WP_Error( 'dokan_withdraw_not_enough_balance', __( 'Withdraw amount is less then the withdraw charge.', 'dokan-lite' ) ); + } + /** * Filter validated withdraw request * @@ -227,9 +243,9 @@ public function has_pending_request( $user_id ) { * @param integer $limit * @param integer $offset * - * @return array + * @return Withdraw[] */ - public function get_withdraw_requests( $user_id = '', $status = 0, $limit = 10, $offset = 0 ) { + public function get_withdraw_requests( $user_id = '', $status = 0, $limit = 10, $offset = 0 ): array { // get all function arguments as key => value pairs $args = get_defined_vars(); @@ -245,6 +261,12 @@ public function get_withdraw_requests( $user_id = '', $status = 0, $limit = 10, $result = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->dokan_withdraw} WHERE user_id = %d AND status = %d ORDER BY id DESC LIMIT %d, %d", $user_id, $status, $offset, $limit ) ); } + $result = array_map( + function ( $withdraw ) { + return new Withdraw( $withdraw ); + }, $result + ); + Cache::set( $cache_key, $result, $cache_group ); } diff --git a/includes/Withdraw/Withdraw.php b/includes/Withdraw/Withdraw.php index 38b76f70bb..813144018a 100644 --- a/includes/Withdraw/Withdraw.php +++ b/includes/Withdraw/Withdraw.php @@ -22,15 +22,18 @@ class Withdraw { */ public function __construct( $data = [] ) { $defaults = [ - 'id' => 0, - 'user_id' => 0, - 'amount' => 0, - 'date' => current_time( 'mysql' ), - 'status' => dokan()->withdraw->get_status_code( 'pending' ), - 'method' => 'paypal', - 'note' => '', - 'details' => '', - 'ip' => '', + 'id' => 0, + 'user_id' => 0, + 'amount' => 0, + 'date' => dokan_current_datetime(), + 'status' => dokan()->withdraw->get_status_code( 'pending' ), + 'method' => 'paypal', + 'note' => '', + 'details' => '', + 'charge' => 0, + 'recivable' => 0, + 'charge_data' => [], + 'ip' => '', ]; $data = wp_parse_args( $data, $defaults ); @@ -46,6 +49,16 @@ public function __construct( $data = [] ) { 'details' => $data['details'], 'ip' => $data['ip'], ]; + + $details = maybe_unserialize( $data['details'] ); + $charge = isset( $details['charge'] ) ? wc_format_decimal( $details['charge'] ) : 0; + $receivable = isset( $details['receivable'] ) ? wc_format_decimal( $details['receivable'] ) : $this->get_amount(); + $charge_data = isset( $details['charge_data'] ) ? $details['charge_data'] : []; + + $this + ->set_charge( $charge ) + ->set_recivable( $receivable ) + ->set_charge_data( $charge_data ); } /** @@ -67,7 +80,7 @@ public function get_withdraw() { * @return int */ public function get_id() { - return $this->data['id']; + return $this->data['id'] ?? 0; } /** @@ -158,6 +171,51 @@ public function get_ip() { return $this->data['ip']; } + /** + * Get withdraw charge + * + * @since DOKAN_SINCE + * + * @returns int|float + */ + public function get_charge() { + return $this->data['charge']; + } + + /** + * Get withdraw revivable amount after deducting the charge amount. + * + * @since DOKAN_SINCE + * + * @returns int|float + */ + public function get_receivable_amount() { + return $this->data['receivable']; + } + + /** + * Get withdraw charge information. + * + * @since DOKAN_SINCE + * + * @returns array + */ + public function get_charge_data() { + $charge_data = $this->data['charge_data']; + if ( ! empty( $this->get_method() ) && empty( $this->data['charge_data'] ) ) { + $default_val = [ + 'fixed' => 0.00, + 'percentage' => 0.00, + ]; + $all_charges = dokan_withdraw_get_method_charges(); + + $charge_data = array_key_exists( $this->get_method(), $all_charges ) ? $all_charges[ $this->get_method() ] : $default_val; + $this->set_charge_data( $charge_data ); + } + + return $charge_data; + } + /** * Set user_id * @@ -270,6 +328,89 @@ public function set_ip( $ip ) { return $this; } + /** + * Sets charge. + * + * @since DOKAN_SINCE + * + * @param $amount + * + * @return \WeDevs\Dokan\Withdraw\Withdraw + */ + public function set_charge( $amount ) { + $this->data['charge'] = floatval( $amount ); + + return $this; + } + + /** + * Set receivable amount + * + * @since DOKAN_SINCE + * + * @param $receivable + * + * @return \WeDevs\Dokan\Withdraw\Withdraw + */ + public function set_recivable( $receivable ) { + $this->data['receivable'] = floatval( $receivable ); + + return $this; + } + + /** + * Sets charge data. + * + * @since DOKAN_SINCE + * + * @param $charge_data array + * + * @return \WeDevs\Dokan\Withdraw\Withdraw + */ + public function set_charge_data( $charge_data ) { + $this->data['charge_data'] = $charge_data; + + return $this; + } + + /** + * Calculate withdraw charge + * + * @since DOKAN_SINCE + * + * @return \WeDevs\Dokan\Withdraw\Withdraw + */ + public function calculate_charge() { + $charge_data = $this->get_charge_data(); + $fixed = $charge_data['fixed']; + $percentage = $charge_data['percentage']; + $charge = 0; + + if ( ! empty( $fixed ) ) { + $charge += (float) $fixed; + } + + if ( ! empty( $percentage ) ) { + $charge += $percentage / 100 * (float) $this->get_amount(); + } + + $this->set_charge( $charge ); + $this->set_recivable( floatval( $this->get_amount() - floatval( $charge ) ) ); + + return $this; + } + + /** + * Returns withdraw data. + * + * @since DOKAN_SINCE + * + * @return array + */ + public function get_data(): array { + return $this->data; + } + /** * Create or update a withdraw * @@ -295,13 +436,30 @@ public function save() { protected function create() { global $wpdb; - $this->data['details'] = maybe_serialize( dokan()->withdraw->get_formatted_details( $this->data['method'], absint( $this->data['user_id'] ) ) ); + $this->calculate_charge(); + + $details = dokan()->withdraw->get_formatted_details( $this->data['method'], absint( $this->data['user_id'] ) ); + + $details['charge'] = $this->get_charge(); + $details['receivable'] = $this->get_receivable_amount(); + $details['charge_data'] = $this->get_charge_data(); + + $this->data['details'] = maybe_serialize( apply_filters( 'dokan_withdraw_request_details_data', $details, $this ) ); unset( $this->data['id'] ); $inserted = $wpdb->insert( $wpdb->dokan_withdraw, - $this->data, + [ + 'user_id' => $this->get_user_id(), + 'amount' => $this->get_amount(), + 'date' => $this->get_date(), + 'status' => $this->get_status(), + 'method' => $this->get_method(), + 'note' => $this->get_note(), + 'details' => $this->get_details(), + 'ip' => $this->get_ip(), + ], [ '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s' ] ); diff --git a/includes/Withdraw/functions.php b/includes/Withdraw/functions.php index 9696351a61..760f009b21 100644 --- a/includes/Withdraw/functions.php +++ b/includes/Withdraw/functions.php @@ -12,12 +12,14 @@ function dokan_withdraw_register_methods() { $methods = [ 'paypal' => [ - 'title' => __( 'PayPal', 'dokan-lite' ), - 'callback' => 'dokan_withdraw_method_paypal', + 'title' => __( 'PayPal', 'dokan-lite' ), + 'callback' => 'dokan_withdraw_method_paypal', + 'apply_charge' => true, ], 'bank' => [ - 'title' => __( 'Bank Transfer', 'dokan-lite' ), - 'callback' => 'dokan_withdraw_method_bank', + 'title' => __( 'Bank Transfer', 'dokan-lite' ), + 'callback' => 'dokan_withdraw_method_bank', + 'apply_charge' => true, ], ]; @@ -113,7 +115,7 @@ function dokan_withdraw_get_method_title( $method_key, $request = null ) { /** * @since 3.3.7 added filter dokan_get_withdraw_method_title */ - return apply_filters( 'dokan_get_withdraw_method_title', isset( $registered[ $method_key ] ) ? $registered[ $method_key ]['title'] : ucfirst( $method_key ), $method_key, $request ); + return apply_filters( 'dokan_get_withdraw_method_title', isset( $registered[ $method_key ] ) ? $registered[ $method_key ]['title'] : ucfirst( $method_key ), $method_key, $request ); } /** @@ -228,7 +230,7 @@ function dokan_bank_payment_required_fields() { // Filtering out all payment fields except dokan bank payment available fields. $fields = array_filter( $required_fields, - function( $key ) use ( $available ) { + function ( $key ) use ( $available ) { return in_array( $key, $available, true ); }, ARRAY_FILTER_USE_KEY @@ -313,7 +315,7 @@ function dokan_bank_payment_fields_placeholders() { ], 'declaration' => [ 'label' => __( 'I attest that I am the owner and have full authorization to this bank account', 'dokan-lite' ), - 'placeholder' => __( '', 'dokan-lite' ), + 'placeholder' => __( '', 'dokan-lite' ), // phpcs:ignore ], 'form_caution' => [ 'label' => __( 'Please double-check your account information!', 'dokan-lite' ), @@ -512,3 +514,49 @@ function dokan_is_withdraw_method_enabled( $method_id ) { && array_key_exists( $method_id, $payment_methods ) && ! empty( $payment_methods[ $method_id ] ); } + +/** + * Get registered withdraw methods suitable for Settings Api + * + * @return array + */ +function dokan_withdraw_get_chargeable_methods() { + $methods = []; + $registered = dokan_withdraw_register_methods(); + + foreach ( $registered as $key => $value ) { + if ( ! empty( $value['apply_charge'] ) ) { + $methods[ $key ] = $value['title']; + } + } + + return $methods; +} + +/** + * Returns all withdraw methods charges saved. + * + * @since DOKAN_SINCE + * + * @return array + */ +function dokan_withdraw_get_method_charges() { + $charges = dokan_get_option( 'withdraw_charges', 'dokan_withdraw', [] ); + $all_methods = array_keys( dokan_withdraw_get_methods() ); + $chargeable_methods = array_keys( dokan_withdraw_get_chargeable_methods() ); + $default_val = [ + 'fixed' => 0.00, + 'percentage' => 0.00, + ]; + + foreach ( $all_methods as $method ) { + if ( empty( $charges ) || ! is_array( $charges ) || ! in_array( $method, $chargeable_methods, true ) ) { + $charges[ $method ] = $default_val; + } else { + $charges[ $method ]['fixed'] = ! empty( $charges[ $method ]['fixed'] ) ? (float) wc_format_decimal( $charges[ $method ]['fixed'] ) : 0.00; + $charges[ $method ]['percentage'] = ! empty( $charges[ $method ]['percentage'] ) ? (float) wc_format_decimal( $charges[ $method ]['percentage'] ) : 0.00; + } + } + + return $charges; +} diff --git a/src/admin/components/CombineInput.vue b/src/admin/components/CombineInput.vue new file mode 100644 index 0000000000..9ad2173f24 --- /dev/null +++ b/src/admin/components/CombineInput.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/admin/components/Fields.vue b/src/admin/components/Fields.vue index b73eb2771a..7fa859e746 100644 --- a/src/admin/components/Fields.vue +++ b/src/admin/components/Fields.vue @@ -416,6 +416,15 @@ + + + + + + @@ -220,6 +228,8 @@ export default { 'amount': { label: this.__( 'Amount', 'dokan-lite' ) }, 'status': { label: this.__( 'Status', 'dokan-lite' ) }, 'method_title': { label: this.__( 'Method', 'dokan-lite' ) }, + 'charge': { label: this.__( 'Charge', 'dokan-lite' ) }, + 'payable': { label: this.__( 'Payable', 'dokan-lite' ) }, 'method_details': { label: this.__( 'Details', 'dokan-lite' ) }, 'note': { label: this.__( 'Note', 'dokan-lite' ) }, 'created': { label: this.__( 'Date', 'dokan-lite' ) }, diff --git a/templates/withdraw/approved-request-listing.php b/templates/withdraw/approved-request-listing.php index 7790cd9d88..3fb220fb10 100644 --- a/templates/withdraw/approved-request-listing.php +++ b/templates/withdraw/approved-request-listing.php @@ -4,6 +4,8 @@ * * @since 2.4 * + * @var $requests \WeDevs\Dokan\Withdraw\Withdraw[] + * * @package dokan */ ?> @@ -13,6 +15,8 @@ + + @@ -20,9 +24,11 @@ - amount ) ); ?> - method, $row ) ); ?> - date ) ) ); ?> + get_amount() ) ); ?> + get_method(), $row ) ); ?> + get_charge() ) ); ?> + get_receivable_amount() ) ); ?> + get_date() ) ); ?> diff --git a/templates/withdraw/cancelled-request-listing.php b/templates/withdraw/cancelled-request-listing.php index d85772858b..3ccde62391 100644 --- a/templates/withdraw/cancelled-request-listing.php +++ b/templates/withdraw/cancelled-request-listing.php @@ -4,6 +4,8 @@ * * @since 2.4 * + * @var $requests WeDevs\Dokan\Withdraw\Withdraw[] + * * @package dokan */ ?> @@ -13,6 +15,8 @@ + + @@ -21,10 +25,12 @@ - amount ) ); ?> - method, $row ) ); ?> - date ) ) ); ?> - note ); ?> + get_amount() ) ); ?> + get_method(), $row ) ); ?> + get_charge() ) ); ?> + get_receivable_amount() ) ); ?> + get_date() ) ); ?> + get_note() ); ?> diff --git a/templates/withdraw/pending-request-listing-dashboard.php b/templates/withdraw/pending-request-listing-dashboard.php index cbbe343dc4..5f84d235ce 100644 --- a/templates/withdraw/pending-request-listing-dashboard.php +++ b/templates/withdraw/pending-request-listing-dashboard.php @@ -4,6 +4,8 @@ * * @since 3.3.1 * + * @var $withdraw_requests \WeDevs\Dokan\Withdraw\Withdraw[] + * * @package dokan */ @@ -17,21 +19,25 @@ + + - amount ) ); ?> - method ) ); ?> - date ) ); ?> + get_amount() ) ); ?> + get_method() ) ); ?> + get_date() ) ); ?> + get_charge() ) ); ?> + get_receivable_amount() ) ); ?> 'cancel', - 'id' => $request->id, + 'id' => $request->get_id(), ], dokan_get_navigation_url( 'withdraw-requests' ) ); @@ -42,9 +48,9 @@ status ) === 0 ) { + if ( intval( $request->get_status() ) === 0 ) { echo '' . esc_html__( 'Pending Review', 'dokan-lite' ) . ''; - } elseif ( intval( $request->status ) === 1 ) { + } elseif ( intval( $request->get_status() ) === 1 ) { echo '' . esc_html__( 'Accepted', 'dokan-lite' ) . ''; } ?> diff --git a/templates/withdraw/pending-request-listing.php b/templates/withdraw/pending-request-listing.php index 95776495cf..c76d472d57 100644 --- a/templates/withdraw/pending-request-listing.php +++ b/templates/withdraw/pending-request-listing.php @@ -4,6 +4,8 @@ * * @since 2.4 * + * @var $withdraw_requests WeDevs\Dokan\Withdraw\Withdraw[] + * * @package dokan */ @@ -21,15 +23,15 @@ - amount ) ); ?> - method, $request ) ); ?> - date ) ); ?> + get_amount() ) ); ?> + get_method(), $request->get_data() ) ); ?> + get_date() ) ); ?> 'cancel', - 'id' => $request->id, + 'id' => $request->get_id(), ], dokan_get_navigation_url( 'withdraw-requests' ) ); ?> @@ -39,9 +41,9 @@ status === 0 ) { + if ( $request->get_status() === 0 ) { echo '' . esc_html__( 'Pending Review', 'dokan-lite' ) . ''; - } elseif ( $request->status === 1 ) { + } elseif ( $request->get_status() === 1 ) { echo '' . esc_html__( 'Accepted', 'dokan-lite' ) . ''; } ?> diff --git a/templates/withdraw/request-form.php b/templates/withdraw/request-form.php index aa77579169..7fba3f9678 100644 --- a/templates/withdraw/request-form.php +++ b/templates/withdraw/request-form.php @@ -10,6 +10,30 @@ ?>
+
+ +
+ +
+
+
-
+ + + @@ -46,6 +75,6 @@
- %s', esc_attr__( 'No withdraw method is available. Please update your payment method to withdraw funds.', 'dokan-lite' ), esc_url( dokan_get_navigation_url( 'settings/payment' ) ), esc_attr__( 'Payment Settings Setup', 'dokan-lite' ) ); ?> + %s', esc_attr__( 'No withdraw method is available. Please update your payment method to withdraw funds.', 'dokan-lite' ), esc_url( dokan_get_navigation_url( 'settings/payment' ) ), esc_attr__( 'Payment Settings Setup', 'dokan-lite' ) ); ?>
diff --git a/tests/Withdrawal/WithdrawChargeApi.php b/tests/Withdrawal/WithdrawChargeApi.php new file mode 100644 index 0000000000..f98dd7ae35 --- /dev/null +++ b/tests/Withdrawal/WithdrawChargeApi.php @@ -0,0 +1,234 @@ +server = $wp_rest_server; + do_action( 'rest_api_init' ); + } + + /** + * Saves dokan withdraw data in database. + * + * @return void + */ + public function save_withdraw_data_to_database() { + $data = [ + "withdraw_methods" => [ + "paypal" => "paypal", + "bank" => "bank", + "dokan_custom" => "dokan_custom", + "skrill" => "skrill", + ], + "withdraw_method_name" => "bKash", + "withdraw_method_type" => "Phone", + "withdraw_charges" => [ + "paypal" => [ + "fixed" => "5", + "percentage" => "10", + ], + "bank" => [ + "fixed" => "10", + "percentage" => "", + ], + "skrill" => [ + "fixed" => "", + "percentage" => "20", + ], + "dokan_custom" => [ + "fixed" => "", + "percentage" => "10", + ], + ], + "withdraw_limit" => "2", + "withdraw_order_status" => [ + "wc-completed" => "wc-completed", + ], + "exclude_cod_payment" => "off", + "withdraw_date_limit" => "0", + "hide_withdraw_option" => "off", + "disbursement_schedule_settings" => "", + "disbursement" => [ + "manual" => "manual", + ], + "disbursement_schedule" => [ + "quarterly" => "", + "monthly" => "", + "biweekly" => "", + "weekly" => "", + ], + "quarterly_schedule" => [ + "month" => "march", + "week" => "1", + "days" => "monday", + ], + "monthly_schedule" => [ + "week" => "1", + "days" => "monday", + ], + "biweekly_schedule" => [ + "week" => "1", + "days" => "monday", + ], + "weekly_schedule" => "monday", + "send_announcement_for_payment_change" => "false", + "send_announcement_for_disbursement_schedule_change" => "false", + ]; + + update_option( 'dokan_withdraw', $data, true ); + } + + /** + * Test that the endpoint exist. + * + */ + public function test_if_get_all_charges_api_exists() { + $this->save_withdraw_data_to_database(); + + $endpoint = '/' . $this->namespace . '/charges'; + + $request = new \WP_REST_Request( 'GET', $endpoint ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertNotEquals( 'rest_no_route', $data['code'] ); + } + + /** + * Test that the endpoint exist. + * + */ + public function test_if_get_method_withdraw_charge_api_exists() { + $this->save_withdraw_data_to_database(); + + $endpoint = '/' . $this->namespace . '/charge'; + + $request = new \WP_REST_Request( 'GET', $endpoint ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertNotEquals( 'rest_no_route', $data['code'] ); + } + + public function test_if_we_can_get_all_withdraw_charges() { + $this->save_withdraw_data_to_database(); + + $user = $this->factory()->user->create( + [ + 'role' => 'seller', + ] + ); + + wp_set_current_user( $user ); + + $endpoint = '/' . $this->namespace . '/charges'; + $request = new \WP_REST_Request( 'GET', $endpoint ); + + $response = $this->server->dispatch( $request ); + + $all_charges = dokan_withdraw_get_method_charges(); + $data = $response->get_data(); + + $this->assertArrayNotHasKey( 'code', $data ); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( $all_charges, $data ); + } + + /** + * Method charge data provider. + * + * @return array[] + */ + public function charge_data_provider() { + return [ + [ + [ + 'method' => 'paypal', + 'amount' => 100, + ], + [ + 'charge' => 15, + 'receivable' => 85, + 'charge_data' => ['fixed' => 5, 'percentage' => 10] + ], + ], + [ + [ + 'method' => 'bank', + 'amount' => 200, + ], + [ + 'charge' => 10, + 'receivable' => 190, + 'charge_data' => ['fixed' => 10, 'percentage' => ''] + ], + ], + ]; + } + + /** + * Testing we can get charge of payment method. + * + * @dataProvider charge_data_provider + * + * @return void + */ + public function test_if_we_can_get_withdraw_charge_of_a_method( $input, $expected ) { + $this->save_withdraw_data_to_database(); + + $user = $this->factory()->user->create( + [ + 'role' => 'seller', + ] + ); + + wp_set_current_user( $user ); + + $endpoint = '/' . $this->namespace . '/charge'; + $request = new \WP_REST_Request( 'GET', $endpoint ); + $request->set_query_params( + [ + 'method' => $input['method'], + 'amount' => $input['amount'], + ] + ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertArrayNotHasKey( 'code', $data ); + + $this->assertArrayHasKey( 'charge', $data ); + $this->assertEquals( $expected['charge'], $data['charge'] ); + + $this->assertArrayHasKey( 'receivable', $data ); + $this->assertEquals( $expected['receivable'], $data['receivable'] ); + + $this->assertArrayHasKey( 'charge_data', $data ); + $this->assertEquals( $expected['charge_data'], $data['charge_data'] ); + } +} diff --git a/tests/Withdrawal/WithdrawChargeTest.php b/tests/Withdrawal/WithdrawChargeTest.php new file mode 100644 index 0000000000..a404abd881 --- /dev/null +++ b/tests/Withdrawal/WithdrawChargeTest.php @@ -0,0 +1,146 @@ +assertIsCallable( 'dokan_withdraw_get_method_charges' ); + } + + /** + * Test we can get all withdraw charges data. + * + * @return void + */ + public function test_that_we_can_get_withdraw_charges() { + $all_methods = dokan_withdraw_get_methods(); + $charges = dokan_withdraw_get_method_charges(); + + $this->assertNotEmpty( $charges ); + $this->assertIsArray( $charges ); + + foreach ( array_keys( $all_methods ) as $method ) { + $this->assertArrayHasKey( $method, $charges ); + } + } + + /** + * Check all withdraw charge related methods exists in WeDevs\Dokan\Withdraw\Withdraw class + * + * @return void + */ + public function test_that_all_needed_method_exists_for_wothdraw_charge() { + $withdraw = new Withdraw(); + + $this->assertTrue( method_exists( $withdraw, 'set_method' ) ); + $this->assertTrue( method_exists( $withdraw, 'set_amount' ) ); + $this->assertTrue( method_exists( $withdraw, 'set_charge' ) ); + $this->assertTrue( method_exists( $withdraw, 'set_charge_data' ) ); + $this->assertTrue( method_exists( $withdraw, 'calculate_charge' ) ); + $this->assertTrue( method_exists( $withdraw, 'get_charge' ) ); + } + + /** + * Testing for set and get charge data in WeDevs\Dokan\Withdraw\Withdraw class. + * + * @return void + */ + public function test_that_we_can_set_and_get_charge_data() { + $withdraw = new Withdraw(); + + $charge_data = [ + 'fixed' => 0, + 'percentage' => 10, + ]; + + $withdraw->set_charge_data( $charge_data ); + + $this->assertEquals( $charge_data, $withdraw->get_charge_data() ); + } + + /** + * Withdraw charge data provider. + * + * @return array[] + */ + public function charge_data_provider() { + return [ + [ + [ + 'amount' => 100, + 'method' => 'paypal', + 'charge_data' => [ + 'fixed' => 10, + 'percentage' => 10, + ] + ], + 20, + ], + [ + [ + 'amount' => 200, + 'method' => 'skrill', + 'charge_data' => [ + 'fixed' => '', + 'percentage' => 20, + ] + ], + 40, + ], + [ + [ + 'amount' => 1250, + 'method' => 'dokan_custom', + 'charge_data' => [ + 'fixed' => 5, + 'percentage' => 32, + ] + ], + 405, + ], + [ + [ + 'amount' => 1250, + 'method' => 'stripe', + 'charge_data' => [ + 'fixed' => 50, + 'percentage' => '', + ] + ], + 50, + ], + ]; + } + + /** + * Test withdraw charge. + * + * @dataProvider charge_data_provider + */ + public function test_that_we_can_calculate_and_get_withdraw_charge_receivable( $input, $expected ) { + $withdraw = new Withdraw(); + $amount = $input['amount']; + $method = $input['method']; + $charge_data = $input['charge_data']; + + $withdraw->set_amount( $amount )->set_method( $method )->set_charge_data( $charge_data )->calculate_charge(); + + $this->assertEquals( $expected, $withdraw->get_charge(), 'Charge is' . $withdraw->get_charge() ); + $this->assertEquals( $amount - $expected, $amount - $withdraw->get_charge() ); + } +}