Sincronizar WooCommerce Memberships con grupos de Discourse

Este Plugin te permite sincronizar WooCommerce Memberships con Discourse Groups.

Prerrequisitos

Para que esto funcione, debes tener DiscourseConnect habilitado, con WordPress o Discourse como proveedor de DiscourseConnect.

Pasos

  1. Instala este plugin de WordPress GitHub - paviliondev/discourse-woocommerce · GitHub.

  2. Usa el editor de temas de WordPress para actualizar estos números con el plan_id de WooCommerce y el group_id y group_name de Discourse que deseas sincronizar.

    $member_group_map[] = (object) array('plan_id' => 51, 'group_id' => 43, 'group_name' => 'group1');
    $member_group_map[] = (object) array('plan_id' => 62, 'group_id' => 44, 'group_name' => 'group2');
    

    Puedes agregar tantas entradas como desees (o eliminar las existentes).

Paso Opcional

  1. Instala WP Crontrol – Plugin de WordPress | WordPress.org y programa run_full_wc_membership_sync para que se ejecute cada 24 horas. Esto realizará una sincronización completa para garantizar la consistencia.

Notas Técnicas

La única forma confiable de capturar tanto la creación de la membresía como el cambio de estado en WooCommerce es utilizando el hook de administrador wc_memberships_user_membership_saved. Puedes usarlo en combinación con el hook wc_memberships_user_membership_status_changed, pero no debería ser necesario.

Este código utiliza el registrador de WooCommerce para registrar información en /wp-content/uploads/wc-logs/{log_file}. Si no funciona, verifica la salida allí. Verás registros que se ven así, mostrando las etapas de la lógica:

2019-05-23T07:01:57+00:00 INFO Ejecutando handle_wc_membership_saved 1, 92, 1
2019-05-23T07:01:57+00:00 INFO La membresía de angus@email.com de VIP Membership cambió a wcm-active
2019-05-23T07:01:57+00:00 INFO Enviando solicitud PUT a http://localhost:3000/groups/41/members con angus@email.com
2019-05-23T07:01:57+00:00 INFO Respuesta de Discourse: 200 OK

Notas de Uso

:point_right: La sincronización no funcionará si haces clic en “Eliminar membresía de usuario” en el panel de administración de WordPress. No hay un hook en WooCommerce que se active al hacer clic en eso. Cambia el estado de la membresía en su lugar.

19 Me gusta

Thanks, @angus. Funny that it seems we were both doing this at the same time.

You managed to figure out what was in those $args. I couldn’t find the user_id in it, I think.

1 me gusta

It looks correct to me. Since the site is using WordPress as the SSO provider, you could use the static add_user_to_discourse_group and remove_user_from_discourse_group functions to handle changing the group status on Discourse. That would let you do something like:

	if ( in_array( $status, ACTIVE_STATUSES ) ) {
		DiscourseUtilities::add_user_to_discourse_group( $user_id, 'your_discourse_group_name' );
	} else {
		DiscourseUtilities::remove_user_from_discourse_group( $user_id, 'your_discourse_group_name' );
	}

Both of those functions take the user’s WordPress ID as the first argument, and a comma separated list of group names (with no spaces between the names) as the second argument. Those functions will take care of all the API credentials for you, so using them would simplify the code a bit. There are more details about those functions here: Manage group membership in Discourse with WP Discourse SSO.

4 Me gusta

In this case Discourse is the SSO Provider :slight_smile:

2 Me gusta

Just an update here.

The updated version of this integration has the following structure:

  1. Sync on every WooCommerce Membership status change

  2. Batch sync every relevant WooCommerce Member every 24 hours to ensure consistency.

  3. In terms of matching wordpress users with discourse users, the code first checks if the wordpress user has a discourse account associated with it. If so, it uses the discourse user id. If not it attempts the sync using the wordpress user’s email. The sync can fail if

    3.1 the user has never associated their wordpress and discourse accounts; and
    3.2 they have used different emails on wordpress and discourse.

We’re successfully running this in production on a client’s site. So far we’ve had only 2 users who have failed to sync, i.e. where both 3.1 and 3.2 were true.

Implementation

The updated implementation is

  1. Functions.php methods (update MEMBERSHIP_PLAN_ID and DISCOURSE_GROUP_ID)
Functions.php

use WPDiscourse\Utilities\Utilities as DiscourseUtilities;

const MEMBERSHIP_PLAN_ID = 61128;
const DISCOURSE_GROUP_ID = 62;
const ACTIVE_STATUSES = array(‘wcm-active’);

function update_discourse_group_access($user_id, $membership_plan_id, $membership_plan_name, $status) {
$options = DiscourseUtilities::get_options();
$base_url = $options[‘url’];
$api_key = $options[‘api-key’];
$api_username = $options[‘publish-username’];

if ( empty( $base_url ) || empty( $api_key ) || empty( $api_username ) ) {
  return new \WP_Error( 'discourse_configuration_error', 'The WP Discourse plugin has not been properly configured.' );
}

$discourse_user_id = get_user_meta($user_id, 'discourse_sso_user_id', true);
$user_info = get_userdata($user_id);
$user_email = $user_info->user_email;
$logger = wc_get_logger();

$logger->info( sprintf('%s membership of %s is %s' , $user_email, $membership_plan_name, $status ) );

if (in_array($status, ACTIVE_STATUSES)) {
	$action = 'PUT';
} else {
	$action = 'DELETE';
}

$external_url = esc_url_raw( $base_url . "/groups/". DISCOURSE_GROUP_ID ."/members" );

$args = array(
‘api_key’ => $api_key,
‘api_username’ => $api_username
);

if ($discourse_user_id) {
$args[‘user_id’] = $discourse_user_id;
} else {
$args[‘user_emails’] = $user_email;
}

$logger->info( sprintf(‘Sending %s request to %s with %s’, $action, $external_url, http_build_query($args)) );

$external_url = add_query_arg($args, $external_url);

$response = wp_remote_request($external_url,
 array(
	 'method' => $action
 )
);

$logger->info( sprintf( 'Response from Discourse: %s %s' ,
    wp_remote_retrieve_response_code($response),
    wp_remote_retrieve_response_message($response) ) );

if ( ! DiscourseUtilities::validate( $response ) ) {

	return new \WP_Error( 'discourse_response_error', 'There has been an error in retrieving the user data from Discourse.' );
}

};

function handle_wc_membership_saved($membership_plan, $args) {
$logger = wc_get_logger();

$logger->info( sprintf('Running handle_wc_membership_saved %s, %s, %s', $args['user_id'], $args['user_membership_id'], $args['is_update'] ) );

$user_id = $args['user_id'];
$membership = wc_memberships_get_user_membership($args['user_membership_id']);
$membership_plan_id = $membership->plan->id;

if ($membership && $membership_plan_id == MEMBERSHIP_PLAN_ID) {
	$membership_plan_name = $membership_plan->name;
	$status = $membership->status;
	update_discourse_group_access($user_id, $membership_plan_id, $membership_plan_name, $status);
}

};

add_action(‘wc_memberships_user_membership_saved’, ‘handle_wc_membership_saved’, 10, 2);

function full_wc_membership_sync() {
$allusers = get_users();
$logger = wc_get_logger();

$logger->info( sprintf('Running full_wc_membership_sync') );

foreach ( $allusers as $user ) {
	$user_id = $user->id;
	$membership = wc_memberships_get_user_membership($user_id, MEMBERSHIP_PLAN_ID);
	$membership_plan_id = $membership->plan->id;

	$logger->info( sprintf('Checking membership of %s', $user->user_login) );

	if ($membership && $membership_plan_id === MEMBERSHIP_PLAN_ID) {
		$membership_plan_name = $membership->plan->name;
		$status = $membership->status;
		$logger->info( sprintf('Updating group access of %s', $user->user_login) );

		update_discourse_group_access($user_id, $membership_plan_id, $membership_plan_name, $status);

		$logger->info( sprintf('Sleeping for 5 seconds') );
		sleep(5);
	}
}

}

add_action(‘run_full_wc_membership_sync’, ‘full_wc_membership_sync’);

  1. Install WP Crontrol – WordPress plugin | WordPress.org and schedule “run_full_wc_membership_sync” to run every 24 hours.

You can review the logs of the sync in WooCommerce > Status > Logs.

@DNSTARS @pfaffman

8 Me gusta

What a baller, sending some magical internet money your way in thanks. :+1:

6 Me gusta

Much appreciated! Thank you @DNSTARS

1 me gusta

¿Qué cambios debería realizar en mi caso? Tengo una situación ligeramente diferente. Aquí están las diferencias:

  1. Tengo WordPress como cliente SSO; los usuarios inician sesión a través de Discourse.
  2. He instalado el plugin de WP ‘WooCommerce Groups’: Groups WooCommerce Documentation - WooCommerce. Es similar a WooCommerce Memberships pero con menos funciones. Creo que todas las funciones principales son las mismas.
  3. Tengo múltiples grupos en Discourse (más de 3), y por eso necesito 3 grupos de acceso en WP.

¿Puedes proporcionarme correcciones para este código o algún consejo? ¡Gracias!
¿Quizás puedas ayudarme? @pfaffman @simon

Ah, no escribí una pregunta allí…
Así que necesito activar esta integración entre WP y Discourse.

Lo que está funcionando:

  1. WP funciona como cliente SSO.
  2. Las membresías se venden a través del plugin WP Groups y expiran ⇒ los usuarios se agregan a los grupos necesarios.
  3. Los usuarios se eliminan de los grupos al expirar.

Necesito:

  • Cuando un usuario se agrega a un grupo en WP, necesito agregar a ese usuario al grupo específico en Discourse.
  • Cuando se elimina de ese grupo en WP, eliminarlo en Discourse.
    Múltiples grupos en WP y Discourse

Noté que la función update_discourse_group_access está agregando las credenciales de autenticación de la API a la URL. Esto no funcionará en ninguna versión actualizada de Discourse. ¿Tienes una versión actualizada del código que utilice autenticación basada en encabezados? Si no es así, puedo editar el código, pero no podré probar mis cambios, por lo que es un poco arriesgado.

Tengo sitios limpios de Discourse y WordPress, puedo probar tu código sin ningún riesgo.

2 Me gusta

Aquí está el código de Angus, actualizado para usar autenticación basada en encabezados. No he realizado ningún otro cambio en él. Tampoco he probado el código. No actualizaré la publicación original hasta que alguien tenga la oportunidad de probarlo.

use WPDiscourse\Utilities\Utilities as DiscourseUtilities;

const MEMBERSHIP_PLAN_ID = 35;
const DISCOURSE_GROUP_ID = 41;
const ACTIVE_STATUSES    = array( 'wcm-active' );

function update_discourse_group_access( $user_id, $membership_plan_id, $membership_plan_name, $status ) {
	$options      = DiscourseUtilities::get_options();
	$base_url     = $options['url'];
	$api_key      = $options['api-key'];
	$api_username = $options['publish-username'];

	if ( empty( $base_url ) || empty( $api_key ) || empty( $api_username ) ) {
		return new \WP_Error( 'discourse_configuration_error', 'El plugin WP Discourse no se ha configurado correctamente.' );
	}

	$user_info         = get_userdata( $user_id );
	$user_email        = $user_info->user_email;
	$logger            = wc_get_logger();

	$logger->info( sprintf( 'La membresía de %s de %s ha cambiado a %s', $user_email, $membership_plan_name, $status ) );

	if ( in_array( $status, ACTIVE_STATUSES ) ) {
		$action = 'PUT';
	} else {
		$action = 'DELETE';
	}

	$external_url = esc_url_raw( $base_url . "/groups/" . DISCOURSE_GROUP_ID . "/members" );

	$logger->info( sprintf( 'Enviando solicitud %s a %s con %s', $action, $external_url, $user_email ) );

	$response = wp_remote_request( $external_url,
		array(
			'method'  => $action,
			'headers' => array(
				'Api-Key'      => sanitize_key( $api_key ),
				'Api-Username' => sanitize_text_field( $api_username ),
			),
			'body'    => array( 'user_emails' => $user_email ),
		)
	);

	$logger->info( sprintf( 'Respuesta de Discourse: %s %s',
		wp_remote_retrieve_response_code( $response ),
		wp_remote_retrieve_response_message( $response ) ) );

	if ( ! DiscourseUtilities::validate( $response ) ) {

		return new \WP_Error( 'discourse_response_error', 'Ha ocurrido un error al recuperar los datos del usuario de Discourse.' );
	}
}

function handle_wc_membership_saved( $membership_plan, $args ) {
	$logger = wc_get_logger();

	$logger->info( sprintf( 'Ejecutando handle_wc_membership_saved %s, %s, %s', $args['user_id'], $args['user_membership_id'], $args['is_update'] ) );

	$user_id            = $args['user_id'];
	$membership         = wc_memberships_get_user_membership( $args['user_membership_id'] );
	$membership_plan_id = $membership->plan->id;

	if ( $membership && $membership_plan_id == MEMBERSHIP_PLAN_ID ) {
		$membership_plan_name = $membership_plan->name;
		$status               = $membership->status;
		update_discourse_group_access( $user_id, $membership_plan_id, $membership_plan_name, $status );
	}
}
add_action( 'wc_memberships_user_membership_saved', 'handle_wc_membership_saved', 10, 2 );

El código está definiendo una variable $discourse_user_id, pero esa variable no se utiliza en ningún lugar. Probablemente podría eliminarse del código.

No estoy tan seguro de eso. El código se conecta al hook de acción wc_memberships_user_membership_saved. Parece probable que ese hook sea añadido por el plugin WooCommerce Memberships. Probablemente haya un hook similar disponible en el plugin WooCommerce Groups, pero es poco probable que tenga el mismo nombre y parámetros.

3 Me gusta

El plugin ‘Groups for Woocommerce’ utiliza el plugin ‘WP Group’ para operar las funciones de grupos, aquí está su API: Actions | Itthinx Documentation

En cuanto a Groups WooCommerce

Acciones

La extensión no invoca ninguna acción específica en la actualidad.

Para simplificar, cambié a WooCommerce Memberships y utilicé el código original junto con los cambios realizados por @simon.

Cambié los IDs por mis datos:

const MEMBERSHIP_PLAN_ID = 347;
const DISCOURSE_GROUP_ID = 42;

En este punto no puedo sincronizar, pero obtengo información en el registro:

> 2020-05-14T13:12:34+00:00 INFO test@gmail.com: la membresía de PAID1 cambió a wcm-active
> 2020-05-14T13:12:34+00:00 INFO Enviando solicitud PUT a https://site/groups/42/members con test@gmail.com
> 2020-05-14T13:12:34+00:00 INFO Respuesta de Discourse: 400 Solicitud incorrecta

El ID del grupo de Discourse lo encontré aquí:
site/groups/PAID1.json

> {"group":{"id":42,"automatic":false,"name":"PAID1","user_count":0,"mentionable_level":0,"messageable_level":0,"visibility_level":0,"automatic_membership_email_domains":"","primary_group":false,"title":"","grant_trust_level":null,"incoming_email":null,"has_messages":false,"flair_url":"","flair_bg_color":"","flair_color":"","bio_raw":"","bio_cooked":null,"bio_excerpt":null,"public_admission":false,"public_exit":false,"allow_membership_requests":false,"full_name":"France-PAID1","default_notification_level":3,"membership_request_template":"","is_group_user":false,"is_group_owner":true,"members_visibility_level":0,"can_see_members":true,"publish_read_state":false,"is_group_owner_display":false,"mentionable":true,"messageable":false},"extras":{"visible_group_names":["admins","moderators","PAID1","PAID2","PAID3","staff","trust_level_0","trust_level_1","trust_level_2","trust_level_3","trust_level_4"]}}
2 Me gusta

Así que quedan dos preguntas abiertas:

  1. ¿Cómo sincronizar sin errores?
  2. ¿Qué pasa si tengo más de 3 tipos de membresía en WP y grupos en Discourse? Este código es solo para 1 tipo.

Parece que esto proviene de un error en mi actualización del código. Lo revisaré más tarde hoy.

2 Me gusta

He actualizado el ejemplo de código aquí: Sync WooCommerce Memberships with Discourse groups - #11 by simon. Hubo un error de codificación en el código que publiqué ayer. He probado que la llamada a la API en el código funciona correctamente, pero no he probado el código con el plugin Woocommerce Memberships.

Debería ser posible hacer eso. Es posible que alguien pueda ayudarte aquí, pero quizás tengas más suerte contratando a alguien para que realice el trabajo. Podrías intentar crear un tema en nuestra categoría Marketplace para encontrar un desarrollador que haga el trabajo.

2 Me gusta

@simon, ¡ya lo tengo funcionando! ¡Gracias, equipo! :vulcan_salute:

1 me gusta

@simon, en tu código no encontré la parte utilizada para la sincronización por lotes que @angus utilizó.

/*run_full_wc_membership_sync*/
function full_wc_membership_sync() {
$allusers = get_users();
$logger = wc_get_logger();

$logger->info( sprintf('Ejecutando run_full_wc_membership_sync') );

foreach ( $allusers as $user ) {
	$user_id = $user->id;
	$membership = wc_memberships_get_user_membership($user_id, MEMBERSHIP_PLAN_ID);
	$membership_plan_id = $membership->plan->id;

	$logger->info( sprintf('Verificando la membresía de %s', $user->user_login) );

	if ($membership && $membership_plan_id === MEMBERSHIP_PLAN_ID) {
		$membership_plan_name = $membership->plan->name;
		$status = $membership->status;
		$logger->info( sprintf('Actualizando el acceso al grupo de %s', $user->user_login) );

		update_discourse_group_access($user_id, $membership_plan_id, $membership_plan_name, $status);

		$logger->info( sprintf('Durmiendo durante 5 segundos') );
		sleep(5);
	}
}

}

add_action('run_full_wc_membership_sync', 'full_wc_membership_sync');  

¿Debería agregar este código al final del tuyo si quiero activar una sincronización diaria?

Cuando intento agregar este evento cron en WP con el plugin WordPress Crontrol usando un nombre de gancho:

run_full_wc_membership_sync

obtuve un error:

¿Qué podría causarlo?

Resuelto. Puede ayudar a alguien. Necesitaba seleccionar ‘En’ en el campo ‘Siguiente ejecución’ e introducir una fecha como 2020-05-10 10:00:00.

Probé la sincronización y ahora funciona.

Aquí tienes una solución funcional si tienes múltiples planes de membresía de WooCommerce y grupos en Discourse. Debes cambiar los IDs de tus planes de membresía y los IDs de los grupos de Discourse por los tuyos.

  1. Los IDs de los planes de membresía de WooCommerce se pueden encontrar en la URL mientras editas el plan en WordPress: https://site/wp-admin/post.php?post=**441**&action=edit
  2. Los IDs de los grupos de Discourse se pueden encontrar aquí: https://site/groups/group_name.json

Código funcional para incluir en functions.php:

Resumen

//wp+discourse
use WPDiscourse\Utilities\Utilities as DiscourseUtilities;

const MEMBERSHIP_PLAN_DISCOURSE_GROUP = [
“347” => “42”,
“357” => “43”,
“441” => “44”
];

//const ACTIVE_STATUSES = array( ‘wcm-active’ );
const ACTIVE_STATUSES = array( ‘wcm-active’, ‘wcm-free_trial’ );

function update_discourse_group_access( $user_id, $membership_plan_id, $membership_plan_name, $status ) {
$options = DiscourseUtilities::get_options();
$base_url = $options[‘url’];
$api_key = $options[‘api-key’];
$api_username = $options[‘publish-username’];

if ( empty( $base_url ) || empty( $api_key ) || empty( $api_username ) ) {
	return new \WP_Error( 'discourse_configuration_error', 'El plugin WP Discourse no se ha configurado correctamente.' );
}

$user_info         = get_userdata( $user_id );
$user_email        = $user_info->user_email;
$logger            = wc_get_logger();

$logger->info( sprintf( '%s membership of %s changed to %s', $user_email, $membership_plan_name, $status ) );

if ( in_array( $status, ACTIVE_STATUSES ) ) {
	$action = 'PUT';
} else {
	$action = 'DELETE';
}

$external_url = esc_url_raw( $base_url . "/groups/" . MEMBERSHIP_PLAN_DISCOURSE_GROUP[$membership_plan_id] . "/members" );

$logger->info( sprintf( 'Enviando solicitud %s a %s con %s', $action, $external_url, $user_email ) );

$response = wp_remote_request( $external_url,
	array(
		'method'  => $action,
		'headers' => array(
			'Api-Key'      => sanitize_key( $api_key ),
			'Api-Username' => sanitize_text_field( $api_username ),
		),
		'body'    => array( 'user_emails' => $user_email ),
	)
);

$logger->info( sprintf( 'Respuesta de Discourse: %s %s',
	wp_remote_retrieve_response_code( $response ),
	wp_remote_retrieve_response_message( $response ) ) );

if ( ! DiscourseUtilities::validate( $response ) ) {

	return new \WP_Error( 'discourse_response_error', 'Ha ocurrido un error al recuperar los datos del usuario de Discourse.' );
}

}

function handle_wc_membership_saved( $membership_plan, $args ) {
$logger = wc_get_logger();

$logger->info( sprintf( 'Ejecutando handle_wc_membership_saved %s, %s, %s', $args['user_id'], $args['user_membership_id'], $args['is_update'] ) );

$user_id            = $args['user_id'];

$membership         = wc_memberships_get_user_membership( $args['user_membership_id'] );

$membership_plan_id = $membership->plan->id;

if ( $membership && isset(MEMBERSHIP_PLAN_DISCOURSE_GROUP[$membership_plan_id])) {
	$membership_plan_name = $membership_plan->name;
	$status               = $membership->status;
	update_discourse_group_access( $user_id, $membership_plan_id, $membership_plan_name, $status );
}

}
add_action( ‘wc_memberships_user_membership_saved’, ‘handle_wc_membership_saved’, 10, 2 );

/run_full_wc_membership_sync/
function full_wc_membership_sync() {
$allusers = get_users();
$logger = wc_get_logger();

$logger->info( sprintf('Ejecutando full_wc_membership_sync') );

foreach ( $allusers as $user ) {

   $user_id = $user->id;

   $membership = wc_memberships_get_user_membership($user_id);

   $membership_plan_id = $membership->plan->id;

   $logger->info( sprintf('Verificando membresía de %s', $user->user_login) );

   if ($membership  && isset(MEMBERSHIP_PLAN_DISCOURSE_GROUP[$membership_plan_id])) {
       
	  $membership_plan_name = $membership->plan->name;
      
	  $status = $membership->status;
      
	  $logger->info( sprintf('Actualizando acceso al grupo de %s', $user->user_login) );

	  update_discourse_group_access($user_id, $membership_plan_id, $membership_plan_name, $status);

	  $logger->info( sprintf('Durmiendo durante 5 segundos') );
      
	  sleep(5);
   }
}

}

add_action(‘run_full_wc_membership_sync’, ‘full_wc_membership_sync’);

Debes cambiar en la primera columna: IDs de planes de membresía, y en la segunda columna: IDs de grupos de Discourse.

Resumen
                                    "347"  => "42",
                                    "357"  => "43",
                                    "441"  => "44"
7 Me gusta

Hola @Ed_Bobkov, esperaba que pudieras ayudarme con esto. Aquí tienes algunas afirmaciones sobre mi situación actual…

  1. Tengo WordPress como cliente SSO; los usuarios inician sesión a través de Discourse o WordPress.
  2. Actualmente no tengo un plugin de “membresías” que esté usando en WordPress.
  3. Tendré docenas de grupos en Discourse para cada tipo diferente de miembro.
  4. Tenía planeado crear manualmente un nuevo grupo de membresía en WP y un grupo correspondiente en Discourse cada vez que tengamos un nuevo grupo de membresía disponible para nuestros usuarios.
  5. Como mencionaste: “Necesito activar esta integración entre WP y Discourse”.
  6. Supongo que necesito obtener el plugin “Woocommerce Memberships”.

¿Cuáles son mis siguientes pasos?