Sincronize WooCommerce Memberships com grupos Discourse

Este Plugin permite sincronizar as WooCommerce Memberships com os Grupos do Discourse.

Pré-requisitos

Para que isso funcione, você precisa ter o DiscourseConnect ativado, com o WordPress ou o Discourse como provedor do DiscourseConnect.

Passos

  1. Instale este plugin do WordPress: GitHub - paviliondev/discourse-woocommerce · GitHub.

  2. Use o editor de temas do WordPress para atualizar esses números com o plan_id da WooCommerce e o group_id e group_name do Discourse que deseja 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');
    

    Você pode adicionar tantas entradas quanto desejar (ou remover as existentes).

Passo Opcional

  1. Instale o WP Crontrol – Plugin do WordPress | WordPress.org e agende run_full_wc_membership_sync para executar a cada 24 horas. Isso realizará uma sincronização completa para garantir a consistência.

Notas Técnicas

A única maneira confiável de capturar tanto a criação da associação quanto a mudança de status na WooCommerce é usando o hook de administrador wc_memberships_user_membership_saved. Você pode usá-lo em combinação com o hook wc_memberships_user_membership_status_changed, mas isso não deve ser necessário.

Este código usa o logger da WooCommerce para registrar informações em /wp-content/uploads/wc-logs/{log_file}. Se não estiver funcionando, verifique a saída lá. Você verá logs assim, mostrando as etapas da lógica:

2019-05-23T07:01:57+00:00 INFO Running handle_wc_membership_saved 1, 92, 1
2019-05-23T07:01:57+00:00 INFO angus@email.com membership of VIP Membership changed to wcm-active
2019-05-23T07:01:57+00:00 INFO Sending PUT request to http://localhost:3000/groups/41/members with angus@email.com
2019-05-23T07:01:57+00:00 INFO Response from Discourse: 200 OK

Notas de Uso

:point_right: A sincronização não funcionará se você clicar em “Delete User Membership” (Excluir Associação do Usuário) no painel administrativo do WordPress. Não há nenhum hook na WooCommerce que seja acionado ao clicar nisso. Altere o status da associação em vez disso.

19 curtidas

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 curtida

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 curtidas

In this case Discourse is the SSO Provider :slight_smile:

2 curtidas

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 curtidas

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

6 curtidas

Much appreciated! Thank you @DNSTARS

1 curtida

Que alterações devo fazer no meu caso? Minha situação é um pouco diferente. Aqui estão as diferenças:

  1. Tenho o WordPress como cliente SSO, e os usuários fazem login via Discourse.
  2. Instalei o plugin do WP ‘WooCommerce Groups’: Groups WooCommerce Documentation - WooCommerce. É semelhante ao WooCommerce Memberships, mas com menos funcionalidades. Acredito que todas as funções principais sejam as mesmas.
  3. Tenho vários grupos no Discourse (mais de 3), e por isso há 3 grupos de acesso no WP.

Você pode me fornecer correções para este código ou algum conselho? Obrigado!
Talvez você possa ajudar? @pfaffman @simon

Ah, eu não escrevi uma pergunta lá…
Então, preciso ativar essa integração entre WP e Discourse.

O que está funcionando:

  1. O WP está funcionando como cliente SSO.
  2. As memberships são vendidas por meio do plugin WP Groups e expiram => os usuários são adicionados aos grupos necessários.
  3. Os usuários são removidos dos grupos ao expirarem.

Preciso:

  • Quando um usuário for adicionado a um grupo no WP, preciso adicioná-lo ao grupo específico no Discourse.
  • Quando for removido desse grupo no WP, removê-lo também no Discourse.
    Vários grupos no WP e no Discourse.

Percebi que a função update_discourse_group_access está adicionando as credenciais de autenticação da API à URL. Isso não funcionará em nenhuma versão atualizada do Discourse. Você tem uma versão atualizada do código que usa autenticação baseada em cabeçalhos? Se não, posso editar o código, mas não poderei testar minhas edições, então é um pouco arriscado.

Tenho sites Discourse e WordPress limpos, posso testar seu código sem nenhum risco.

2 curtidas

Aqui está o código de Angus, atualizado para usar autenticação baseada em cabeçalho. Não fiz nenhuma outra alteração nele. Também não testei o código. Não atualizarei a publicação original até que alguém tenha a chance de testá-lo.

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', 'O plugin WP Discourse não foi configurado corretamente.' );
	}

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

	$logger->info( sprintf( 'A filiação de %s ao plano %s foi alterada para %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 solicitação %s para %s com %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( 'Resposta do 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', 'Ocorreu um erro ao recuperar os dados do usuário no Discourse.' );
	}
}

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

	$logger->info( sprintf( 'Executando 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 );

O código está definindo uma variável $discourse_user_id, mas essa variável não está sendo utilizada em nenhum lugar. Provavelmente, ela pode ser removida do código.

Não tenho tanta certeza disso. O código se conecta ao hook de ação wc_memberships_user_membership_saved. Parece provável que esse hook seja adicionado pelo plugin WooCommerce Memberships. Provavelmente, há um hook semelhante disponível no plugin WooCommerce Groups, mas é improvável que tenha o mesmo nome e parâmetros.

3 curtidas

O plugin ‘Groups for Woocommerce’ utiliza o plugin ‘WP Group’ para operar as funções de grupos. Aqui está a API dele: Actions | Itthinx Documentation

Quanto ao Groups WooCommerce

Ações

A extensão não invoca nenhuma ação específica no momento.

Para simplificar, mudei para o WooCommerce Memberships e usei o código original + as alterações feitas por @simon.

Mudei os IDs para os meus dados:

const MEMBERSHIP_PLAN_ID = 347;
const DISCOURSE_GROUP_ID = 42;

Neste ponto, não consigo sincronizar, mas obtenho algumas informações no log:

> 2020-05-14T13:12:34+00:00 INFO test@gmail.com associação de PAID1 alterada para wcm-active
> 2020-05-14T13:12:34+00:00 INFO Enviando requisição PUT para https://site/groups/42/members com test@gmail.com
> 2020-05-14T13:12:34+00:00 INFO Resposta do Discourse: 400 Bad Request

O ID do grupo do Discourse foi encontrado aqui:
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 curtidas

Então, duas questões permanecem em aberto:

  1. Como sincronizar sem erros?
  2. E se eu tiver mais de 3 tipos de associação no WordPress e grupos no Discourse? Este código é para apenas 1 tipo.

Isso parece vir de um bug na minha atualização do código. Vou dar uma olhada nisso mais tarde hoje.

2 curtidas

Atualizei o exemplo de código aqui: Sync WooCommerce Memberships with Discourse groups - #11 by simon. Houve um erro de codificação no código que postei ontem. Testei e a chamada à API no código está funcionando corretamente, mas não testei o código com o plugin Woocommerce Memberships.

Deveria ser possível fazer isso. É provável que alguém possa te ajudar aqui, mas você pode ter mais sorte contratando alguém para realizar o trabalho. Você pode tentar criar um tópico na nossa categoria Marketplace para encontrar um desenvolvedor para fazer o trabalho.

2 curtidas

@simon, consegui fazer funcionar! Obrigado, equipe! :vulcan_salute:

1 curtida

@simon, no seu código não encontrei a parte usada para sincronização em lote que o @angus utilizou.

/*run_full_wc_membership_sync*/
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');  

Devo adicionar esse código ao final do seu código se quiser ativar uma sincronização diária?

Quando tento adicionar esse evento cron no WP com o plugin WordPress Crontrol, usando o nome do hook:

run_full_wc_membership_sync

Obtenho um erro:

O que pode estar causando isso?

Resolvido. Pode ajudar alguém. Eu precisei escolher ‘Em’ no campo ‘Próxima Execução’ e colocar uma data como 2020-05-10 10:00:00.

Testei a sincronização - agora está funcionando.

Aqui está uma solução funcional se você tiver múltiplos planos de associação WooCommerce e grupos no Discourse. Você precisa alterar seus IDs de associação e IDs de grupo do Discourse para os seus.

  1. Os IDs dos planos de associação WooCommerce podem ser encontrados na URL ao editar o plano no WordPress: https://site/wp-admin/post.php?post=**441**&action=edit
  2. Os IDs dos grupos do Discourse podem ser encontrados aqui: https://site/groups/group_name.json

Código funcional para incluir no functions.php:

Resumo

//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', 'O plugin WP Discourse não foi configurado corretamente.' );
}

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

$logger->info( sprintf( 'Associação de %s de %s alterada para %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 solicitação %s para %s com %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( 'Resposta do 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', 'Ocorreu um erro ao recuperar os dados do usuário do Discourse.' );
}

}

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

$logger->info( sprintf( 'Executando 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('Executando 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 associação 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('Atualizando acesso ao grupo de %s', $user->user_login) );

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

	  $logger->info( sprintf('Dormindo por 5 segundos') );
      
	  sleep(5);
   }
}

}

add_action(‘run_full_wc_membership_sync’, ‘full_wc_membership_sync’);

Você precisa alterar na primeira coluna - IDs dos planos de associação, na segunda coluna - IDs dos grupos do Discourse.

Resumo
                                    "347"  => "42",
                                    "357"  => "43",
                                    "441"  => "44"
7 curtidas

Olá @Ed_Bobkov, esperava que você pudesse me ajudar com isso. Aqui estão algumas informações sobre onde estou agora…

  1. Tenho o Wordpress como um cliente SSO; os usuários fazem login via Discourse ou Wordpress.
  2. Atualmente, não tenho um plugin de “assinaturas” sendo usado no Wordpress.
  3. Teremos dezenas de grupos no Discourse para cada tipo diferente de membro.
  4. Planejo criar manualmente um novo grupo de assinatura no WP e um grupo correspondente no Discourse sempre que tivermos um novo grupo de assinatura disponível para nossos usuários.
  5. Como você mencionou: “Preciso ativar essa integração entre WP e Discourse.”
  6. Estou supondo que precise obter o plugin “Woocommerce Memberships”?

Quais são meus próximos passos?