id = 'arsenalpay';
$this->icon = apply_filters( 'woocommerce_arsenalpay_icon', '' . plugin_dir_url( __FILE__ ) . 'wc-arsenalpay.png' );
$this->method_title = __( 'ArsenalPay', 'woocommerce' );
$this->method_description = __( 'Allows payments with ArsenalPay gateway', 'woocommerce' );
$this->has_fields = false;
// Load settings fields.
$this->init_form_fields();
$this->init_settings();
// Get the settings and load them into variables
$this->title = $this->get_option('title');
$this->description = $this->get_option('description');
$this->debug = 'yes' === $this->get_option('debug', 'no');
$this->arsenalpay_callback_key = $this->get_option('arsenalpay_callback_key');
$this->arsenalpay_widget_key = $this->get_option('arsenalpay_widget_key');
$this->arsenalpay_widget_id = $this->get_option('arsenalpay_widget_id');
$this->arsenalpay_ip = $this->get_option('arsenalpay_ip');
// Logs
if ( $this->debug ) {
$this->loger = new WC_Logger();
}
// Add display hook of receipt and save hook for settings:
add_action( 'woocommerce_receipt_arsenalpay', array( $this, 'receipt_page' ) );
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array(
$this,
'process_admin_options'
) );
// ArsenalPay callback hook:
add_action( 'woocommerce_api_wc_gw_arsenalpay', array( $this, 'callback_listener' ) );
if ( ! $this->is_valid_for_use() ) {
$this->enabled = false;
}
}
/**
* Check if this gateway is enabled and available in the user's country
*
* @access public
* @return bool
*/
function is_valid_for_use() {
if ( ! in_array( get_woocommerce_currency(), apply_filters( 'woocommerce_arsenalpay_supported_currencies', array( 'RUB' ) ) ) ) {
return false;
}
return true;
}
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable ArsenalPay', 'wc-arsenalpay' ),
'default' => 'yes'
),
'title' => array(
'title' => __( 'Title', 'woocommerce' ),
'type' => 'text',
'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ),
'default' => __( 'ArsenalPay', 'woocommerce' ),
'desc_tip' => true,
),
'description' => array(
'title' => __( 'Description', 'woocommerce' ),
'type' => 'text',
'default' => __( 'Pay with ArsenalPay.', 'wc-arsenalpay' ),
'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce' ),
'desc_tip' => true,
),
'debug' => array(
'title' => __( 'Debug Log', 'woocommerce' ),
'type' => 'checkbox',
'label' => __( 'Enable logging', 'woocommerce' ),
'default' => 'no',
'description' => sprintf( __( 'Log ArsenalPay events, such as callback requests', 'wc-arsenalpay' ), wc_get_log_file_path( 'arsenalpay' ) )
),
'arsenalpay_widget_id' => array(
'title' => __( 'Widget ID', 'wc-arsenalpay' ),
'type' => 'text',
'description' => __( 'Assigned to merchant for the access to ArsenalPay payment widget. Required.', 'wc-arsenalpay' ),
'desc_tip' => true,
),
'arsenalpay_widget_key' => array(
'title' => __( 'Widget key', 'wc-arsenalpay' ),
'type' => 'text',
'description' => __( 'Assigned to merchant for the access to ArsenalPay payment widget. Required.', 'wc-arsenalpay' ),
'desc_tip' => true,
),
'arsenalpay_callback_key' => array(
'title' => __( 'Callback key', 'wc-arsenalpay' ),
'type' => 'text',
'description' => __( 'With this key you check a validity of sign that comes with callback payment data. Required.', 'wc-arsenalpay' ),
'desc_tip' => true,
),
'arsenalpay_ip' => array(
'title' => __( 'Allowed IP-address', 'wc-arsenalpay' ),
'type' => 'text',
'description' => __( 'It can be allowed to receive ArsenalPay payment confirmation callback requests only from the ip address pointed out here. Optional.', 'wc-arsenalpay' ),
'desc_tip' => true,
),
);
}
public function admin_options() {
echo "
Redirect URL
- " . __( 'Callback URL' ) . ":
" . get_bloginfo( "url" ) . "/?wc-api=wc_gw_arsenalpay&arsenalpay=callback
";
if ( $this->is_valid_for_use() ) {
?>
get_user_id();
$total = number_format( $order->get_total(), 2, '.', '' );
$nonce = md5( microtime( true ) . mt_rand( 100000, 999999 ) );
$sign_data = "$user_id;$order_id;$total;$this->arsenalpay_widget_id;$nonce";
$widget_sign = hash_hmac( 'sha256', $sign_data, $this->arsenalpay_widget_key );
$html = "
";
echo $html;
}
public function process_payment( $order_id ) {
$order = wc_get_order( $order_id );
return array(
'result' => 'success',
'redirect' => $order->get_checkout_payment_url( true )
);
}
function callback_listener() {
global $woocommerce;
@ob_clean();
$callback_params = stripslashes_deep( $_POST );
$REMOTE_ADDR = $_SERVER["REMOTE_ADDR"];
$this->log( 'Remote IP: ' . $REMOTE_ADDR );
$IP_ALLOW = trim($this->arsenalpay_ip);
if (strlen($IP_ALLOW) > 0 && $IP_ALLOW != $REMOTE_ADDR) {
$this->log('IP '. $REMOTE_ADDR . ' is not allowed.');
$this->exitf('ERR');
}
if ( ! $this->check_params( $callback_params ) ) {
$this->exitf( 'ERR' );
}
$order = wc_get_order( $callback_params['ACCOUNT'] );
$function = $callback_params['FUNCTION'];
if ( ! $order || empty( $order ) || ! ( $order instanceof WC_Order ) ) {
if ( $function == "check" ) {
$this->log( " Order %s doesn't exist. ", $callback_params['ACCOUNT'] );
$this->exitf( 'NO' );
}
$this->exitf( "ERR" );
}
if ( ! ( $this->check_sign( $callback_params, $this->arsenalpay_callback_key ) ) ) {
$this->log( 'Error in callback parameters ERR_INVALID_SIGN' );
$this->exitf( 'ERR' );
}
switch ( $function ) {
case 'check':
$this->callback_check( $callback_params, $order, $woocommerce );
break;
case 'payment':
$this->callback_payment( $callback_params, $order, $woocommerce );
break;
case 'cancel':
$this->callback_cancel( $callback_params, $order, $woocommerce );
break;
case 'cancelinit':
$this->callback_cancel( $callback_params, $order, $woocommerce );
break;
case 'refund':
$this->callback_refund( $callback_params, $order, $woocommerce );
break;
case 'reverse':
$this->callback_reverse( $callback_params, $order, $woocommerce );
break;
case 'reversal':
$this->callback_reverse( $callback_params, $order, $woocommerce );
break;
case 'hold':
$this->callback_hold( $callback_params, $order, $woocommerce );
break;
default: {
$order->update_status( 'failed', sprintf( __( 'Payment failed via callback.', 'woocommerce' ) ) );
$this->log( 'Error in callback' );
$this->exitf( 'ERR' );
}
}
}
function callback_check( $callback_params, $order, $woocommerce ) {
if ( $order->is_paid() || $order->has_status( 'cancelled' ) || $order->has_status( 'refunded' ) ) {
$this->log( 'Aborting, Order #' . $order->get_id() . ' is ' . $order->get_status() );
$this->exitf( 'ERR' );
}
$isCorrectAmount = ( $order->get_total() == $callback_params['AMOUNT'] && $callback_params['MERCH_TYPE'] == '0' ) ||
( $order->get_total() > $callback_params['AMOUNT'] && $callback_params['MERCH_TYPE'] == '1' && $order->get_total() == $callback_params['AMOUNT_FULL'] );
if ( ! $isCorrectAmount ) {
$this->log( 'Payment error: Amounts do not match (amount ' . $callback_params['AMOUNT'] . ')' );
// Put this order on-hold for manual checking
$order->update_status( 'on-hold', sprintf( __( 'Validation error: ArsenalPay amounts do not match (amount %s).', 'woocommerce' ), $callback_params['AMOUNT'] ) );
$this->exitf( 'ERR' );
}
$order->update_status( 'pending', sprintf( __( 'Order number has been checked.', 'wc-arsenalpay' ) ) );
$order->add_order_note( __( 'Waiting for payment confirmation after checking.', 'wc-arsenalpay' ) );
$this->exitf( 'YES' );
}
function callback_payment( $callback_params, $order, $woocommerce ) {
if ( $order->is_paid() || $order->has_status( 'cancelled' ) || $order->has_status( 'refunded' ) ) {
$this->log( 'Aborting, Order #' . $order->get_id() . ' is ' . $order->get_status() );
$this->exitf( 'ERR' );
}
if ( $order->get_total() == $callback_params['AMOUNT'] && $callback_params['MERCH_TYPE'] == '0' ) {
$order->add_order_note( __( 'Payment completed.', 'wc-arsenalpay' ) );
} elseif ( $order->get_total() > $callback_params['AMOUNT'] && $callback_params['MERCH_TYPE'] == '1' && $order->get_total() == $callback_params['AMOUNT_FULL'] ) {
$order->add_order_note( sprintf( __( "Payment received with less amount equal to %s.", 'wc-arsenalpay' ), $callback_params['AMOUNT'] ) );
} else {
$this->log( 'Payment error: Amounts do not match (amount ' . $callback_params['AMOUNT'] . ')' );
// Put this order on-hold for manual checking
$order->update_status( 'on-hold', sprintf( __( 'Validation error: ArsenalPay amounts do not match (amount %s).', 'woocommerce' ), $callback_params['AMOUNT'] ) );
$this->exitf( 'ERR' );
}
$order->payment_complete();
$woocommerce->cart->empty_cart();
$this->exitf( 'OK' );
}
function callback_hold( $callback_params, $order, $woocommerce ) {
if ( ! $order->has_status( 'on-hold' ) && ! $order->has_status( 'pending' ) ) {
$this->log( 'Aborting, Order #' . $order->get_id() . ' has not been checked.' );
$this->exitf( 'ERR' );
}
$isCorrectAmount = ( $order->get_total() == $callback_params['AMOUNT'] && $callback_params['MERCH_TYPE'] == '0' ) ||
( $order->get_total() > $callback_params['AMOUNT'] && $callback_params['MERCH_TYPE'] == '1' && $order->get_total() == $callback_params['AMOUNT_FULL'] );
if ( ! $isCorrectAmount ) {
$this->log( 'Payment error: Amounts do not match (amount ' . $callback_params['AMOUNT'] . ')' );
// Put this order on-hold for manual checking
$order->update_status( 'on-hold', sprintf( __( 'Validation error: ArsenalPay amounts do not match (amount %s).', 'woocommerce' ), $callback_params['AMOUNT'] ) );
$this->exitf( 'ERR' );
}
$order->update_status( 'on-hold', sprintf( __( 'Order number has been holden.', 'wc-arsenalpay' ) ) );
$order->add_order_note( __( 'Waiting for payment or cancel confirmation after hold request.', 'wc-arsenalpay' ) );
$this->exitf( 'OK' );
}
function callback_cancel( $callback_params, $order, $woocommerce ) {
if ( ! $order->has_status( 'on-hold' ) && ! $order->has_status( 'pending' ) ) {
$this->log( 'Aborting, Order #' . $order->get_id() . ' has not been checked status.' );
$this->exitf( 'ERR' );
}
$order->update_status( 'cancelled', sprintf( __( 'Order has been cancelled', 'wc-arsenalpay' ) ) );
$this->exitf( 'OK' );
}
function callback_refund( $callback_params, $order, $woocommerce ) {
if ( ! $order->is_paid() && ! $order->has_status( 'refunded' ) ) {
$this->log( 'Aborting, Order #' . $order->get_id() . ' is not paid.' );
$this->exitf( 'ERR' );
}
$arsenalPaidSum = $order->get_total() - $order->get_total_refunded();
$isCorrectAmount = ( $callback_params['MERCH_TYPE'] == 0 && $arsenalPaidSum >= $callback_params['AMOUNT'] ) ||
( $callback_params['MERCH_TYPE'] == 1 && $arsenalPaidSum >= $callback_params['AMOUNT'] && $arsenalPaidSum >= $callback_params['AMOUNT_FULL'] );
if ( ! $isCorrectAmount ) {
$this->log( "Refund error: Paid amount({$arsenalPaidSum}) < refund amount({$callback_params['AMOUNT']})" );
$this->exitf( 'ERR' );
}
$res = wc_create_refund( array(
'amount' => $callback_params['AMOUNT'],
'reason' => 'Partition refund via Arsenalpay',
'order_id' => $order->get_id(),
'refund_payment' => false,
) );
if ( $res instanceof WP_Error ) {
$this->log( 'Error during refund: ' . $res->get_error_message() );
$this->exitf( 'ERR' );
}
$this->exitf( 'OK' );
}
function callback_reverse( $callback_params, $order, $woocommerce ) {
if ( ! $order->is_paid() ) {
$this->log( 'Aborting, Order #' . $order->get_id() . ' is not complete.' );
$this->exitf( 'ERR' );
}
$arsenalPaidSum = $order->get_total() - $order->get_total_refunded();
$isCorrectAmount = ( $callback_params['MERCH_TYPE'] == 0 && $arsenalPaidSum == $callback_params['AMOUNT'] ) ||
( $callback_params['MERCH_TYPE'] == 1 && $arsenalPaidSum >= $callback_params['AMOUNT'] && $arsenalPaidSum == $callback_params['AMOUNT_FULL'] );
if ( ! $isCorrectAmount ) {
$this->log( 'Reverse error: Amounts do not match (amount ' . $callback_params['AMOUNT'] . ' and ' . $arsenalPaidSum . ')' );
$this->exitf( 'ERR' );
}
$order->update_status( 'refunded', sprintf( __( 'Order has been reversed', 'wc-arsenalpay' ) ) );
$this->exitf( 'OK' );
}
private function check_sign( $callback_params, $pass ) {
$validSign = ( $callback_params['SIGN'] === md5( md5( $callback_params['ID'] ) .
md5( $callback_params['FUNCTION'] ) . md5( $callback_params['RRN'] ) .
md5( $callback_params['PAYER'] ) . md5( $callback_params['AMOUNT'] ) . md5( $callback_params['ACCOUNT'] ) .
md5( $callback_params['STATUS'] ) . md5( $pass ) ) ) ? true : false;
return $validSign;
}
private function check_params( $callback_params ) {
$required_keys = array
(
'ID', /* Merchant identifier */
'FUNCTION', /* Type of request to which the response is received*/
'RRN', /* Transaction identifier */
'PAYER', /* Payer(customer) identifier */
'AMOUNT', /* Payment amount */
'ACCOUNT', /* Order number */
'STATUS', /* When /check/ - response for the order number checking, when
// payment/ - response for status change.*/
'DATETIME', /* Date and time in ISO-8601 format, urlencoded.*/
'SIGN', /* Response sign = md5(md5(ID).md(FUNCTION).md5(RRN).md5(PAYER).md5(AMOUNT).
// md5(ACCOUNT).md(STATUS).md5(PASSWORD)) */
);
/**
* Checking the absence of each parameter in the post request.
*/
foreach ( $required_keys as $key ) {
if ( empty( $callback_params[ $key ] ) || ! array_key_exists( $key, $callback_params ) ) {
$this->log( 'Error in callback parameters ERR' . $key );
return false;
} else {
$this->log( " $key=$callback_params[$key]" );
}
}
if ( $callback_params['FUNCTION'] != $callback_params['STATUS'] ) {
$this->log( "Error: FUNCTION ({$callback_params['FUNCTION']} not equal STATUS ({$callback_params['STATUS']})" );
return false;
}
return true;
}
/**
* Log file path: /wp-content/uploads/wc-logs/log-[a-z0-9]+.log
*
* @param $msg
*/
private function log( $msg ) {
if ( $this->debug ) {
$this->loger->log( 'debug', "Arsenalpay: " . $msg );
}
}
public function exitf( $msg ) {
$this->log( " $msg " );
echo $msg;
die;
}
}
/**
* Add the ArsenalPay Gateway to WooCommerce
**/
function add_wc_arsenalpay( $methods ) {
$methods[] = 'WC_GW_ArsenalPay';
return $methods;
}
add_filter( 'woocommerce_payment_gateways', 'add_wc_arsenalpay' );
}