WordPress+WooCommerce 注文確認ダイアログを表示する

WordPress+WooCommerce でネットショップ構築すると、「注文する」で確認ダイアログも注文確認画面も出ずに、速攻受注処理が走って完了画面になる。
日本以外ではそれが当たり前らしく、本体にそのような機能は取り入れられなかったらしい。

なので、確認ダイアログを出すことにトライ。数日がかりでようやく動いた。
オリジナルのソースに手を入れることなく、ググった英語のページを参考に、PHP・jsそれぞれのトリガー・フックでのカスタマイズ記述のみで対応した。

  • 動作
    ・WooCommerceのエラーがあれば、そのエラー表示
    ・WooCommerceのエラーがなければ、確認ダイアログ表示 → OKで受注処理、完了画面。キャンセルで元の画面。
     エラーチェックの後に確認ダイアログを出すことにこだわった。
  • 方針
    ・submitされた時に、confirm用のjsで拾う
    ・php側、class-wc-checkout.php のvalidateの後、受注処理が走る前に抜けてjsに戻る
    ・戻ってきたjs側でダイアログを表示する
  • 修正
    (1)/(子テーマ)/woocommerce/checkout/payment.php
    ※Woocommerceの該当フォルダから、子テーマにコピーし、submitボタン周りを修正する。
     【参考】Woocommerceの該当フォルダについて
        WooCommerceのカスタマイズ方法
    ※今回は「storefront」を親テーマとした。

    <input type="hidden" value="0" name="chkcfm"><!-- 追記。これがconfirmであることのフラグとなる -->
    
    <?php echo apply_filters( 
     'woocommerce_order_button_html',
     '<button type="submit" onclick="javascript:document.checkout.chkcfm.value=1;"
    class="button alt" name="woocommerce_confirm_place_order" id="place_order3" value="' . esc_attr( $order_button_text ) . '" data-value="' . esc_attr( $order_button_text ) . '">' . esc_html( $order_button_text ) . '[確認]</button>' ); // @codingStandardsIgnoreLine ?>	
    <!-- onclick()を追記する -->
    

    (2)/(子テーマ)/function.php
    以下、追記する。

    <?php
    //使用するスクリプト・CSSを登録する
    function __wc_add_scripts_checkout() {
        wp_enqueue_script(
            'checkout-script',
            get_stylesheet_directory_uri() . '/assets/js/woocommerce/confirm_checkout.js',
            array( 'jquery' )
        );
        wp_enqueue_script(
            'checkout-confirm-dlg-script',
            get_stylesheet_directory_uri() . '/assets/jquery.confirm/jquery.confirm.js',
            array( 'jquery' )
        );
        wp_enqueue_style( 
    	'checkout-confirm-dlg-css', get_stylesheet_directory_uri() . '/assets/jquery.confirm/jquery.confirm.css', ""
        );
    }
    add_action( 'woocommerce_before_checkout_form', '__wc_add_scripts_checkout');
    
    //WooCommerceのsubmit時のvalidate後に実行する。
    // /woocommerce/includes/class-wc-checkout.php validate_checkout()
    //エラーなしなら、result='confirm_checkout'を返す
    function __wc_comfirm_checkout($data, $errors) { 
        if ( $_POST['chkcfm'] == 1 ) {
        	if (count($errors->errors) <= 0) {
                $response = array(
    		'result'   => 'confirm_checkout',
    	    );
    	    unset( WC()->session->refresh_totals, WC()->session->reload_checkout );
    
    	    wp_send_json( $response );
    	    exit;
    	}
        }
    } 
    add_action('woocommerce_after_checkout_validation', '__wc_comfirm_checkout', 10, 2); 
    

    (3)/(子テーマ)/assets/js/woocommerce/confirm_checkout.js
    以下、新規作成する。

    jQuery( function( $ ) {
        var checkout_form = $( 'form.checkout' );
    
        //checkout.js の submitで、受注処理実行前に実行されるハンドラ
        checkout_form.on( 'checkout_place_order', function() {
    	var $form = $( this );
    	if ($form.context.elements.chkcfm.value == 0) {
    	    return true;
    	}
    
    	$form.addClass( 'processing' );
    
    	var form_data = $form.data();
    	if ( 1 !== form_data['blockUI.isBlocked'] ) {
    	    $form.block({
    		message: null,
    		overlayCSS: {
        		    background: '#fff',
    	    	    opacity: 0.6
    		}
    	    });
    	}
    
    	$.ajax({
    	    type:   'POST',
    	    url:    wc_checkout_params.checkout_url,
    	    data:   $form.serialize(),
    	    dataType:   'json',
    	    success:	function( result ) {
    		try {
    		    if ( 'confirm_checkout' === result.result ) {
    			show_confirm_checkout_dlg($form);
    			return false;
    		    } else if ( 'failure' === result.result ) {
    			throw 'Result failure';
    		    } else {
    			throw 'Invalid response';
    		    }
    		} catch( err ) {
    		    // Reload page
    		    if ( true === result.reload ) {
    			    window.location.reload();
    			    return;
    		    }
    
    		    // Trigger update in case we need a fresh nonce
    		    if ( true === result.refresh ) {
    			$( document.body ).trigger( 'update_checkout' );
    		    }
    
    		    // Add new errors
    		    if ( result.messages ) {
    			confirm_checkout_error( result.messages );
    		    } else {
    			confirm_checkout_error( '<div class="woocommerce-error">' + wc_checkout_params.i18n_checkout_error + '</div>' );
    		    }
    		}
    	    },
    	    error:	function( jqXHR, textStatus, errorThrown ) {
    		confirm_checkout_error( '<div class="woocommerce-error">' + errorThrown + '</div>' );
    	    }
    	});
    
    	checkout_form.removeClass( 'processing' ).unblock();
    	return false;
        });
    
        //ダイアログを表示し、OKならsubumitし、受注処理を実行する
        function show_confirm_checkout_dlg($obj) {
    	$.confirm({
    	  'title'     : '注文確認',
    	  'message'   : 'この内容で注文処理を行います',
    	  'buttons'   : {
    		   'OK' : {
    		      'action': function(){
    			//「confirmでない」フラグをたてる
    			$obj.context.elements.chkcfm.value = 0;
    			//submitする
    			$obj.submit();
    			return true;
    	      	     }
    		   },
    		   'キャンセル': {
    			'action': function(){
    			     $obj.removeClass( 'processing' ).unblock();
    			     return false;
    			}
                       }
    		}
    	});
        }
    
        //エラー処理。
        // 本来は、wc_checkout_form.submit_error()、wc_checkout_form.scroll_to_notices()を呼びたいが…
        function confirm_checkout_error( error_message ) {
    	$form = checkout_form;
    	$( '.woocommerce-NoticeGroup-checkout, .woocommerce-error, .woocommerce-message' ).remove();
    	$form.prepend( '<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout">' + error_message + '</div>' );
    	$form.removeClass( 'processing' ).unblock();
    	$form.find( '.input-text, select, input:checkbox' ).trigger( 'validate' ).blur();
    
    	var scrollElement = $( '.woocommerce-NoticeGroup-updateOrderReview, .woocommerce-NoticeGroup-checkout' );
    
    	if ( ! scrollElement.length ) {
    		scrollElement = $( '.form.checkout' );
    	}
    	$.scroll_to_notices( scrollElement );
    		$( document.body ).trigger( 'checkout_error' );
    	}
    });
    
    

    (4)ダイアログ。jQueryプラグインのConfirmを利用している。
    置き場所はどこでもよい。以下は例。
    /(子テーマ)/assets/jquery.confirm/jquery.confirm.js
    /(子テーマ)/assets/jquery.confirm/jquery.confirm.css

    ※DL元
    How to Create a jQuery Confirm Dialog Replacement

    cssのみ、レスポンシブ対応と、ボタン表示不具合を修正した。以下。

    モバイル用
    該当箇所を修正。
    ※ダイアログが中央に来ません。すみません。

    #confirmBox .button{
    	display:inline-block;
    	background:url('buttons.png') no-repeat;
    	color:white;
    	position:relative;
    	height: 33px;
    	
    	font:17px/33px 'Cuprum','Lucida Sans Unicode', 'Lucida Grande', sans-serif;
    	
    	margin-right: 15px;
    	padding: 0 35px 0 40px;
    	text-decoration:none;
    	border:none;
    }
    

    PC用
    追記。

    @media (min-width: 768px) {
    #confirmBox{
    	background:url('body_bg.jpg') repeat-x left bottom #e5e5e5;
    	width:460px;
    	position:fixed;
    	left:50%;
    	top:50%;
    	margin:-130px 0 0 -230px;
    	border: 1px solid rgba(33, 33, 33, 0.6);
    	
    	-moz-box-shadow: 0 0 2px rgba(255, 255, 255, 0.6) inset;
    	-webkit-box-shadow: 0 0 2px rgba(255, 255, 255, 0.6) inset;
    	box-shadow: 0 0 2px rgba(255, 255, 255, 0.6) inset;
    }
    

    ボタン表示不具合対応。最後の1行「transform: scaleX(-1);」を追記。

    #confirmBox .button span{
    	position:absolute;
    	top:0;
    	right:-5px;
    	background:url('buttons.png') no-repeat;
    	width:5px;
    	height:33px;
    	transform: scaleX(-1);
    }
    

    以上で、動くはず。
    Google Chrome最新バージョンでのみ動作確認済み。その他は不明です。
    修正前にはバックアップを取るなど、自己責任でどうぞ。。。

  • 【参考】
    Hooking into WooCommerce’s checkout JS events
    ※checkout.jsでのイベントフックにものすごく役立った。

    Adding custom functionality to a WooCommerce checkout

    アラート画面のデザインを自由にカスタマイズ!警告表示のダイアログのUI機能をjQueryプラグインConfirmで印象をガラッと変更する方法!