Sync WooCommerce Memberships with Discourse groups

This Plugin allows you to Sync WooCommerce Memberships with Discourse Groups.

Pre-requisites

For this to work you have to have DiscourseConnect enabled, with either Wordpress or Discourse as the DiscourseConnect provider.

Steps

  1. Install this Wordpress plugin GitHub - paviliondev/discourse-woocommerce · GitHub.

  2. Use the Wordpress theme editor to update these numbers to the WooCommerce plan_id and Discourse group id and group_name you want to sync

    $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');
    

    You can add as many entries as you like (or remove existing ones).

Optional Step

  1. Install WP Crontrol – WordPress plugin | WordPress.org and schedule run_full_wc_membership_sync to run every 24 hours. This will do a full sync to ensure consistency.

Technical Notes

The only reliable way to capture both membership creation and status change in WooCommerce is using the wc_memberships_user_membership_saved admin hook. You can use this in combination with the wc_memberships_user_membership_status_changed hook, but it should not be necessary.

This code uses the WooCommerce logger to log info to /wp-content/uploads/wc-logs/{log_file}. If it is not working check the output there. You will see a logs that look like this, showing you the stages of the logic:

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

Usage Notes

:point_right: The Sync will not work if you click “Delete User Membership” in the Wordpress admin panel. There is no hook in WooCommerce that fires when that is clicked. Change the status of the membership instead.

19개의 좋아요

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개의 좋아요

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개의 좋아요

In this case Discourse is the SSO Provider :slight_smile:

2개의 좋아요

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개의 좋아요

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

6개의 좋아요

Much appreciated! Thank you @DNSTARS

1개의 좋아요

제 상황은 조금 다르므로 어떤 수정이 필요할까요? 차이점은 다음과 같습니다:

  1. 저는 WordPress를 SSO 클라이언트로 사용하고 있으며, 사용자는 Discourse를 통해 로그인합니다.
  2. WP 플러그인 'WooCommerce Groups’를 설치했습니다: Groups WooCommerce Documentation - WooCommerce . WooCommerce Memberships와 유사하지만 기능이 좀 더 적은 편입니다. 핵심 기능은 모두 동일하다고 생각합니다.
  3. Discourse에 여러 그룹(3개 이상)이 있으므로 WP에는 3개의 접근 그룹이 있습니다.

이 코드에 대한 수정 사항이나 조언을 주실 수 있을까요? 감사합니다!
혹시 도움을 주실 수 있을까요? @pfaffman @simon

아, 거기에 질문을 쓰지 않았네요…
즉, WP와 Discourse 간의 이 통합을 활성화해야 합니다.

현재 작동하는 부분:

  1. WP가 SSO 클라이언트로 정상 작동합니다.
  2. 멤버십은 WP Groups 플러그인을 통해 판매되고 만료되면 => 사용자는 필요한 그룹에 추가됩니다.
  3. 사용자가 만료 시 그룹에서 삭제됩니다.

필요한 것:

  • 사용자가 WP에서 특정 그룹에 추가되면, 해당 사용자를 Discourse의 특정 그룹에 추가해야 합니다.
  • 사용자가 WP에서 해당 그룹에서 삭제되면, Discourse에서도 삭제해야 합니다.
    WP와 Discourse 모두에 여러 그룹이 있는 경우입니다.

update_discourse_group_access 함수가 URL에 API 인증 정보를 추가하고 있는 것을 발견했습니다. 이는 최신 버전의 Discourse에서는 작동하지 않습니다. 헤더 기반 인증을 사용하는 업데이트된 버전의 코드가 있으신가요? 없다면 제가 코드를 수정할 수 있지만, 수정 사항을 테스트할 수 없으므로 다소 위험합니다.

저는 깨끗한 Discourse와 WordPress 사이트를 가지고 있으므로, 어떤 위험도 없이 여러분의 코드를 테스트할 수 있습니다.

2개의 좋아요

Angus의 코드를 헤더 기반 인증을 사용하도록 업데이트했습니다. 다른 부분은 변경하지 않았습니다. 또한 코드를 테스트하지도 않았습니다. 누군가 테스트할 때까지 OP를 업데이트하지 않겠습니다.

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', 'The WP Discourse plugin has not been properly configured.' );
	}

	$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/" . DISCOURSE_GROUP_ID . "/members" );

	$logger->info( sprintf( 'Sending %s request to %s with %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( '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 );

이 코드는 $discourse_user_id 변수를 설정하고 있지만, 이 변수는 어디에서도 사용되지 않습니다. 따라서 코드에서 제거할 수 있을 것입니다.

그 부분에 대해서는 확신이 서지 않습니다. 이 코드는 wc_memberships_user_membership_saved 액션 후크에 연결되어 있습니다. 이 후크는 WooCommerce Memberships 플러그인에 의해 추가된 것으로 보입니다. WooCommerce Groups 플러그인에도 유사한 후크가 제공될 가능성이 높지만, 이름과 매개변수가 동일할 가능성은 낮습니다.

3개의 좋아요

‘Groups for Woocommerce’ 플러그인은 그룹 기능을 수행하기 위해 ‘WP Group’ 플러그인을 사용하며, 해당 API는 다음과 같습니다: Actions | Itthinx Documentation

Groups WooCommerce의 경우,

액션

현재 이 확장 프로그램은 특정 액션을 호출하지 않습니다.

간소화를 위해 WooCommerce Memberships로 전환하고 @simon이 수정한 원래 코드를 사용하고 있습니다.

ID를 제 데이터에 맞게 변경했습니다:

const MEMBERSHIP_PLAN_ID = 347;
const DISCOURSE_GROUP_ID = 42;

현재 이 시점에서 동기화(sync)는 되지 않지만 로그에 일부 정보가 표시됩니다:

> 2020-05-14T13:12:34+00:00 INFO test@gmail.com membership of PAID1 changed to wcm-active
> 2020-05-14T13:12:34+00:00 INFO Sending PUT request to https://site/groups/42/members with test@gmail.com
> 2020-05-14T13:12:34+00:00 INFO Response from Discourse: 400 Bad Request

Discourse 그룹 ID는 여기서 찾았습니다.
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개의 좋아요

따라서 2가지 질문이 남았습니다:

  1. 오류 없이 동기화를 어떻게 수행합니까?
  2. WP에 멤버십 유형이 3개 이상이고 Discourse에 그룹이 있는 경우 어떻게 해야 합니까? 이 코드는 1개 유형에만 적용됩니다.

코드 업데이트 과정에서 발생한 버그로 보입니다. 오늘 늦게 확인해 보겠습니다.

2개의 좋아요

여기 코드 예시를 업데이트했습니다: Sync WooCommerce Memberships with Discourse groups - #11 by simon. 어제 게시한 코드에 인코딩 오류가 있었습니다. 코드 내 API 호출이 정상적으로 작동하는지 테스트했지만, Woocommerce Memberships 플러그인과 함께 코드를 테스트하지는 않았습니다.

그렇게 하는 것이 가능합니다. 누군가가 여기서 도움을 줄 수 있을 수도 있지만, 누군가를 고용하여 작업을 맡기는 것이 더 좋은 결과를 얻을 수 있습니다. 작업을 수행해 줄 개발자를 찾으려면 Marketplace 카테고리에 주제를 만들어 보는 것을 추천합니다.

2개의 좋아요

@simon, 작동하게 만들었어요! 팀 여러분, 감사합니다! :vulcan_salute:

1개의 좋아요

@simon 님, @angus 님이 사용한 배치 동기화에 사용되는 부분을 님의 코드에서 찾지 못했습니다.

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

매일 동기화를 활성화하려면 이 코드를 님의 코드 끝에 추가해야 하나요?

다음과 같은 훅 이름으로 WP Crontrol 플러그인을 통해 WordPress에 이 크론 이벤트를 추가하려고 하면:

run_full_wc_membership_sync

오류가 발생합니다:

원인은 무엇일 수 있을까요?

해결했습니다. 누군가에게 도움이 될 수 있기를 바랍니다. ‘다음 실행(Next Run)’ 필드에서 'At’을 선택하고 2020-05-10 10:00:00과 같은 날짜를 입력해야 했습니다.

동기화를 테스트해 보았는데 이제 작동합니다.

Discourse에서 여러 WooCommerce 멤버십 플랜과 그룹을 사용하는 경우 작동하는 솔루션입니다. 멤버십 ID와 Discourse 그룹 ID를 본인의 것으로 변경해야 합니다.

  1. WooCommerce 멤버십 플랜 ID는 Wordpress에서 플랜을 편집할 때 URL에서 찾을 수 있습니다: https://site/wp-admin/post.php?post=**441**&action=edit
  2. Discourse 그룹 ID는 여기에서 찾을 수 있습니다: https://site/groups/group_name.json

functions.php에 포함할 작동 코드:

요약

//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', 'The WP Discourse plugin has not been properly configured.' );
}

$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( 'Sending %s request to %s with %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( '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 && 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('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;

   $logger->info( sprintf('Checking membership of %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('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’);

첫 번째 열 - 멤버십 플랜 ID, 두 번째 열 - Discourse 그룹 ID를 변경해야 합니다.

요약
                                    "347"  => "42",
                                    "357"  => "43",
                                    "441"  => "44"
7개의 좋아요

@Ed_Bobkov 님, 이 부분에 대해 도움을 주실 수 있을지 기대하고 있습니다. 현재 제 상황을 설명드리면 다음과 같습니다…

  1. Wordpress를 SSO 클라이언트로 사용하고 있으며, 사용자는 Discourse 또는 Wordpress를 통해 로그인합니다.
  2. 현재 Wordpress에서 사용하고 있는 “memberships” 플러그인은 없습니다.
  3. Discourse에는 회원 유형별로 수십 개의 그룹이 있을 것입니다.
  4. 사용자가 사용할 수 있는 새로운 멤버십 그룹이 있을 때마다 WP에서 새로운 멤버십 그룹을, Discourse에서 해당하는 그룹을 수동으로 생성할 계획이었습니다.
  5. 말씀하신 대로 “WP와 Discourse 간의 이 통합을 활성화해야 합니다.”
  6. “Woocommerce Memberships” 플러그인을 설치해야 하는 것 같습니다.

다음 단계는 무엇인가요?