Crear un plugin de una pasarela de pago con WooCommerce

Empecemos el tutorial de pasarela de pago.

Paso 1: Comenzamos creando un plugin

En caso de que no lo sepas, los métodos de pago personalizados son simplemente plugins. Por lo tanto, tenemos que crear uno.

Para crear un plugin solo necesitas crear un archivo y agregar un par de líneas de código dentro de él.

Así que, en la carpeta /plugins, creé un archivo llamado misha-gateway.php y agregué el siguiente código allí. En caso de que tu plugin tenga más de un archivo, colócalo en una carpeta con el mismo nombre, por ejemplo: misha-gateway/misha-gateway.php.

<?php
/*
* Plugin Name: WooCommerce Custom Payment Gateway
* Plugin URI: https://rudrastyh.com/woocommerce/payment-gateway-plugin.html
* Description: Acepta pagos con tarjeta de crédito en tu tienda.
* Author: Misha Rudrastyh
* Author URI: http://rudrastyh.com
* Version: 1.0.1
*/

Una vez que lo hayas hecho, ¡el plugin aparecerá en tu área de administración! E incluso puedes activarlo.

Paso 2: Las pasarelas de pago son clases de PHP. Aquí está el esqueleto de la clase.

Así que tenemos que crear una clase PHP personalizada para extender la clase WC_Payment_Gateway de WooCommerce.

Cada método de clase se describe a continuación. Puedes comenzar copiando y pegando el siguiente código en tu archivo principal del plugin.

/*
 * Este gancho de acción registra nuestra clase PHP como una pasarela de pago de WooCommerce
 */
add_filter( 'woocommerce_payment_gateways', 'misha_add_gateway_class' );
function misha_add_gateway_class( $gateways ) {
	$gateways[] = 'WC_Misha_Gateway'; // aquí va el nombre de tu clase
	return $gateways;
}

/*
 * La propia clase, ten en cuenta que está dentro del gancho de acción plugins_loaded
 */
add_action( 'plugins_loaded', 'misha_init_gateway_class' );
function misha_init_gateway_class() {

	class WC_Misha_Gateway extends WC_Payment_Gateway {

		/**
		 * Constructor de la clase, más sobre esto en el Paso 3
		 */
		public function __construct() {

		...

		}

		/**
		 * Opciones del plugin, las veremos en el Paso 3 también
		 */
		public function init_form_fields(){

		...
	
		}

		/**
		 * Lo necesitarás si deseas tu propio formulario personalizado para la tarjeta de crédito, el Paso 4 trata sobre esto
		 */
		public function payment_fields() {

		...
				 
		}

		/*
		 * CSS y JS personalizados, generalmente solo se requieren cuando decides tener un formulario personalizado para la tarjeta de crédito
		 */
		public function payment_scripts() {

		...
	
		}

		/*
		 * Validación de campos, más en el Paso 5
		 */
		public function validate_fields() {

		...

		}

		/*
		 * Aquí procesamos los pagos, todo sobre esto en el Paso 5
		 */
		public function process_payment( $order_id ) {

		...
					
		}

		/*
		 * En caso de que necesites un webhook, como PayPal IPN, etc.
		 */
		public function webhook() {

		...
					
		}
	}
}

Si insertas el código anterior “tal cual” en tu archivo del plugin, obtendrás un error 500, porque este código simplemente muestra la estructura de la clase del plugin, donde cada método debería estar.

Paso 3: Opciones del Plugin de la Pasarela de Pago

En el constructor de la clase:

  • Definimos las propiedades de la clase, como el ID y el nombre de la pasarela, líneas 23-33.
  • Inicializamos la configuración, líneas 35-39.
  • Agregamos opciones a las propiedades de la clase, líneas 40-45.
  • Guardamos las opciones, línea 48.
  • Agregamos JavaScript y CSS personalizados si es necesario, línea 51.

También podemos registrar webhooks de la pasarela de pago en el constructor de la clase.

public function __construct() {

	$this->id = 'misha'; // ID del plugin de la pasarela de pago
	$this->icon = ''; // URL del icono que se mostrará en la página de pago junto al nombre de tu pasarela
	$this->has_fields = true; // en caso de que necesites un formulario personalizado para la tarjeta de crédito
	$this->method_title = 'Misha Gateway';
	$this->method_description = 'Descripción de la pasarela de pago Misha'; // se mostrará en la página de opciones

	// las pasarelas pueden admitir suscripciones, reembolsos, métodos de pago guardados,
	// pero en este tutorial comenzaremos con pagos simples
	$this->supports = array(
		'products'
	);

	// Método con todos los campos de opciones
	$this->init_form_fields();

	// Cargar las configuraciones.
	$this->init_settings();
	$this->title = $this->get_option( 'title' );
	$this->description = $this->get_option( 'description' );
	$this->enabled = $this->get_option( 'enabled' );
	$this->testmode = 'yes' === $this->get_option( 'testmode' );
	$this->private_key = $this->testmode ? $this->get_option( 'test_private_key' ) : $this->get_option( 'private_key' );
	$this->publishable_key = $this->testmode ? $this->get_option( 'test_publishable_key' ) : $this->get_option( 'publishable_key' );

	// Este gancho de acción guarda las configuraciones
	add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );

	// Necesitamos JavaScript personalizado para obtener un token
	add_action( 'wp_enqueue_scripts', array( $this, 'payment_scripts' ) );
	
	// También puedes registrar un webhook aquí
	// add_action( 'woocommerce_api_{nombre_del_webhook}', array( $this, 'webhook' ) );
}

public function init_form_fields(){

	$this->form_fields = array(
		'enabled' => array(
			'title'       => 'Habilitar/Deshabilitar',
			'label'       => 'Habilitar Pasarela Misha',
			'type'        => 'checkbox',
			'description' => '',
			'default'     => 'no'
		),
		'title' => array(
			'title'       => 'Título',
			'type'        => 'text',
			'description' => 'Esto controla el título que el usuario ve durante el proceso de pago.',
			'default'     => 'Tarjeta de crédito',
			'desc_tip'    => true,
		),
		'description' => array(
			'title'       => 'Descripción',
			'type'        => 'textarea',
			'description' => 'Esto controla la descripción que el usuario ve durante el proceso de pago.',
			'default'     => 'Paga con tu tarjeta de crédito a través de nuestra súper genial pasarela de pago.',
		),
		'testmode' => array(
			'title'       => 'Modo de prueba',
			'label'       => 'Habilitar modo de prueba',
			'type'        => 'checkbox',
			'description' => 'Activar el modo de prueba usando claves API de prueba.',
			'default'     => 'yes',
			'desc_tip'    => true,
		),
		'test_publishable_key' => array(
			'title'       => 'Clave pública de prueba',
			'type'        => 'text'
		),
		'test_private_key' => array(
			'title'       => 'Clave privada de prueba',
			'type'        => 'password',
		),
		'publishable_key' => array(
			'title'       => 'Clave pública en vivo',
			'type'        => 'text'
		),
		'private_key' => array(
			'title'       => 'Clave privada en vivo',
			'type'        => 'password'
		)
	);
}

Paso 4: Formulario de Pago Directo

Antes de implementar el código a continuación, por favor lee estos puntos clave:

  • Si estás creando una pasarela de pago como PayPal, donde todas las acciones del usuario ocurren en el sitio web de la pasarela de pago, puedes omitir este paso, simplemente no agregues los métodos payment_fields() y validate_fields() y continúa con el Paso 5.
  • En este tutorial, asumimos que estás utilizando un procesador de pagos que envía los datos de la tarjeta con su propia solicitud AJAX y te proporciona un token que puedes usar en PHP, así que ¡no! agregues los atributos “name” a los campos del formulario de la tarjeta. Así es como funciona el proceso paso a paso:
    1. El cliente completa los datos de la tarjeta y hace clic en “Realizar Pedido”.
    2. Retrasamos el envío del formulario utilizando el evento checkout_place_order en WooCommerce y enviamos una solicitud AJAX con los datos de la tarjeta directamente a nuestro procesador de pagos.
    3. Si los detalles del cliente son correctos, el procesador devuelve un token y lo agregamos a nuestro formulario.
    4. Ahora podemos enviar el formulario (¡en JS, por supuesto!).
    5. Usamos el token en PHP para capturar un pago a través de la API del procesador de pagos.

4.1. Agregar scripts en cola

En el Paso 2 ya hemos agregado el gancho de acción wp_enqueue_scripts y conectado el método payment_scripts() a él.

public function payment_scripts() {

	// solo necesitamos JavaScript para procesar un token en las páginas de carrito y pago, ¿verdad?
	if ( ! is_cart() && ! is_checkout() && ! isset( $_GET['pay_for_order'] ) ) {
		return;
	}

	// si nuestra pasarela de pago está deshabilitada, tampoco tenemos que encolar JS
	if ( 'no' === $this->enabled ) {
		return;
	}

	// no es necesario trabajar con los detalles de la tarjeta sin SSL a menos que tu sitio web esté en modo de prueba
	if ( ! $this->testmode && ! is_ssl() ) {
		return;
	}

	// supongamos que es nuestro JavaScript del procesador de pagos que permite obtener un token
	wp_enqueue_script( 'misha_js', 'https://www.mishapayments.com/api/token.js' );

	// y este es nuestro JS personalizado en el directorio de tu plugin que funciona con token.js
	wp_register_script( 'woocommerce_misha', plugins_url( 'misha.js', __FILE__ ), array( 'jquery', 'misha_js' ) );

	// en la mayoría de los procesadores de pagos, tienes que usar la CLAVE PÚBLICA para obtener un token
	wp_localize_script( 'woocommerce_misha', 'misha_params', array(
		'publishableKey' => $this->publishable_key
	) );

	wp_enqueue_script( 'woocommerce_misha' );

}

4.2. Obtener un token en JavaScript

Antes que nada, quiero decir que para cada procesador de pagos, este código podría ser diferente, pero la idea principal es la misma. Aquí está el contenido de tu archivo misha.js:

var successCallback = function(data) {

	var checkout_form = $( 'form.woocommerce-checkout' );

	// agregar un token a nuestro campo de entrada oculto
	// console.log(data) para encontrar el token
	checkout_form.find('#misha_token').val(data.token);

	// desactivar el evento checkout_place_order
	checkout_form.off( 'checkout_place_order', tokenRequest );

	// enviar el formulario ahora
	checkout_form.submit();

};

var errorCallback = function(data) {
    console.log(data);
};

var tokenRequest = function() {

	// aquí estará la función de la pasarela de pago que procesa todos los datos de la tarjeta desde tu formulario,
	// tal vez necesite tu Clave API pública que está en misha_params.publishableKey
	// y activa successCallback() en caso de éxito y errorCallback en caso de error
	return false;
		
};

jQuery(function($){

	var checkout_form = $( 'form.woocommerce-checkout' );
	checkout_form.on( 'checkout_place_order', tokenRequest );

});

4.3. Formulario con datos de la tarjeta

Con el método payment_fields() de la clase, puedes crear un formulario de pago con los campos de la tarjeta de crédito de esta manera:

public function payment_fields() {
 
	// bien, vamos a mostrar una descripción antes del formulario de pago
	if ( $this->description ) {
		// puedes agregar instrucciones para el modo de prueba, es decir, números de tarjetas de prueba, etc.
		if ( $this->testmode ) {
			$this->description .= ' MODO DE PRUEBA HABILITADO. En el modo de prueba, puedes usar los números de tarjeta enumerados en la <a href="#">documentación</a>.';
			$this->description  = trim( $this->description );
		}
		// mostrar la descripción con etiquetas <p> etc.
		echo wpautop( wp_kses_post( $this->description ) );
	}
 
	// Voy a usar echo() para mostrar el formulario, pero puedes cerrar las etiquetas PHP y mostrarlo directamente en HTML
	echo '<fieldset id="wc-' . esc_attr( $this->id ) . '-cc-form" class="wc-credit-card-form wc-payment-form" style="background:transparent;">';
 
	// Agregar este gancho de acción si deseas que tu pasarela de pago personalizada lo admita
	do_action( 'woocommerce_credit_card_form_start', $this->id );
 
	// Recomiendo usar IDs únicos, porque otras pasarelas podrían estar usando #ccNo, #expdate, #cvc
	echo '<div class="form-row form-row-wide"><label>Número de tarjeta <span class="required">*</span></label>
		<input id="misha_ccNo" type="text" autocomplete="off">
		</div>
		<div class="form-row form-row-first">
			<label>Fecha de vencimiento <span class="required">*</span></label>
			<input id="misha_expdate" type="text" autocomplete="off" placeholder="MM / AA">
		</div>
		<div class="form-row form-row-last">
			<label>Código de tarjeta (CVC) <span class="required">*</span></label>
			<input id="misha_cvv" type="password" autocomplete="off" placeholder="CVC">
		</div>
		<div class="clear"></div>';
 
	do_action( 'woocommerce_credit_card_form_end', $this->id );
 
	echo '<div class="clear"></div></fieldset>';
 
}

Paso 5: Procesar pagos

5.1. Validar campos

Sé que los campos de la página de pago, como el nombre, deben validarse antes, pero esto es solo un ejemplo:

public function validate_fields(){
 
	if( empty( $_POST[ 'billing_first_name' ]) ) {
		wc_add_notice(  '¡Se requiere el nombre!', 'error' );
		return false;
	}
	return true;
 
}

5.2. Capturar pagos con la API y establecer el estado del pedido

Prepárate para mucho texto 🙃

  • Una vez que obtengas el objeto de pedido con la función wc_get_order(), puedes usar sus métodos como get_billing_first_name(), get_billing_country(), get_billing_address_1(), etc., para obtener los detalles de facturación y envío del cliente (por cierto, puedes encontrar todos los métodos en includes/class-wc-order.php, que está en la carpeta del plugin WooCommerce). También puedes obtener los detalles de facturación desde la matriz $_POST; en el momento de escribir este tutorial, no estoy seguro de cuál es mejor.
  • Puedes agregar notas al pedido con el método $order->add_order_note(), puede ser notas para el cliente (se mostrarán en el área de miembros) y notas privadas (solo en las páginas de edición de pedidos).
  • Notas del pedido en las páginas de edición de pedidos.
  • En este tutorial, consideramos el uso de pagos directos sin ir a los sitios web de las pasarelas de pago. Pero si, para tus propósitos, los clientes deben ir a un sitio web de pasarela de pago para completar su pago, debes omitir el Paso 4 y, en este paso, en lugar de capturar los pagos con wp_remote_post(), utiliza add_query_arg() para construir una URL de redireccionamiento correcta a la página de pago de la pasarela de pago.
  • Usa $order->get_total() para obtener el monto del pedido.
  • No olvides cambiar $this->testmode en la url a 0 o 1 según corresponda.
  • Después de enviar una solicitud a la pasarela de pago, verifica la respuesta para ver si el pago se realizó correctamente. En mi caso, puedo verificarlo de esta manera: $response[‘response’][‘code’] == 200 && $response[‘response’][‘message’] == ‘OK’ (si uso wp_remote_post() para enviar una solicitud a la pasarela de pago, esta es la única forma de verificar si la pasarela respondió correctamente). Si usas la API de WooCommerce REST, revisa la respuesta y toma la decisión adecuada.
  • Si la respuesta de la pasarela de pago es correcta, actualiza el estado del pedido a “completado” y no lo redirijas a ninguna parte.
  • En caso de que la respuesta de la pasarela de pago no sea la esperada, muestra un mensaje de error al cliente y no actualices el estado del pedido.
public function process_payment( $order_id ) {

	global $woocommerce;

	$order = wc_get_order( $order_id );

	// no dudes en agregar cualquier tipo de datos personalizados aquí
	// no estoy seguro de cuál es mejor - obtener el nombre desde el objeto de pedido o desde la matriz $_POST
	$customer_first_name = $_POST[ 'billing_first_name' ];
	$customer_country = $order->get_billing_country();
	$customer_address = $order->get_billing_address_1();

	// URL de la pasarela de pago (ejemplo)
	$url = 'https://example.com/api/charge';

	// Datos que enviaremos a la pasarela de pago
	$data = array(
		'customer_name'    => $customer_first_name,
		'customer_country' => $customer_country,
		'customer_address' => $customer_address,
		'amount'           => $order->get_total(),
		'token'            => $_POST[ 'misha_token' ]
	);

	// Aquí enviamos una solicitud a la pasarela de pago
	$response = wp_remote_post( $url, array(
		'method'    => 'POST',
		'timeout'   => 90,
		'sslverify' => true,
		'headers'   => array(
			'Authorization' => 'Basic ' . base64_encode( $this->private_key . ':' ) // Otra opción: 'api_key' => $this->private_key,
		),
		'body'      => $data
	) );

	if ( is_wp_error( $response ) ) {
		throw new Exception( __( 'Hubo un error al procesar su pago. Inténtelo de nuevo.', 'misha' ) );
	}

	if ( 200 != wp_remote_retrieve_response_code( $response ) ) {
		throw new Exception( __( 'Hubo un error al procesar su pago. Inténtelo de nuevo.', 'misha' ) );
	}

	$response = json_decode( wp_remote_retrieve_body( $response ) );

	// Si la respuesta de la pasarela de pago es correcta, actualizamos el estado del pedido a "completado"
	if ( 'succeeded' === $response->status ) {

		// pagar el pedido (aquí y después de actualizar el estado del pedido)
		$order->payment_complete();

		// añadir nota
		$order->add_order_note(
			'¡Gracias por su pago! Recibimos ' . $response->amount . ' ' . $response->currency . '. ID de transacción: ' . $response->id
		);

		// quitar carrito
		$woocommerce->cart->empty_cart();

		// redirigir a la página de confirmación del pedido
		return array(
			'result'   => 'success',
			'redirect' => $this->get_return_url( $order )
		);

	} else {

		// agregar una nota de error, esto no se muestra al cliente
		$order->add_order_note(
			'ERROR: transacción fallida con ID de transacción ' . $response->id
		);

		// devuelve un error si el pago no se pudo procesar
		throw new Exception( __( 'Hubo un error al procesar su pago. Inténtelo de nuevo.', 'misha' ) );

	}

}

Eso es! Si sigues este tutorial paso a paso, deberías obtener tu pasarela de pago personalizada y funcional. Toma en cuenta que este es solo un ejemplo y, en realidad, puede haber más desafíos durante el proceso de desarrollo. También recuerda que el código del procesador de pagos real puede ser diferente, y necesitarás adaptarlo en consecuencia.

Espero que esto te ayude a crear tu propia pasarela de pago personalizada para WooCommerce. ¡Buena suerte en tu proyecto! Si tienes alguna pregunta o necesitas más ayuda, no dudes en preguntar. ¡Feliz desarrollo!

Tomado en parte de: https://rudrastyh.com/woocommerce/payment-gateway-plugin.html


Posted

in

by

Tags:

Comments

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *