Bidirectional sync between Eventbrite and The Events Calendar, with WooCommerce ticket purchasing that bypasses Eventbrite's processing fees by registering buyers as free attendees via API. Includes venue/ organizer sync, QR code ticket generation, attendee management with CSV export, scheduled sync via WP-Cron, and real-time Eventbrite webhooks.
149 lines
3.9 KiB
PHP
149 lines
3.9 KiB
PHP
<?php
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
final class EB4TEC_Webhook {
|
|
|
|
public function __construct(
|
|
private readonly EB4TEC_API_Client $api,
|
|
private readonly EB4TEC_Event_Sync $event_sync,
|
|
) {
|
|
add_filter( 'query_vars', [ $this, 'add_query_var' ] );
|
|
add_action( 'template_redirect', [ $this, 'handle_webhook' ] );
|
|
}
|
|
|
|
public function add_query_var( array $vars ): array {
|
|
$vars[] = 'eb4tec_webhook';
|
|
return $vars;
|
|
}
|
|
|
|
public function handle_webhook(): void {
|
|
if ( ! get_query_var( 'eb4tec_webhook' ) ) {
|
|
return;
|
|
}
|
|
|
|
$payload = file_get_contents( 'php://input' );
|
|
$signature = $_SERVER['HTTP_X_EVENTBRITE_SIGNATURE'] ?? '';
|
|
|
|
if ( ! $this->verify_signature( $payload, $signature ) ) {
|
|
wp_die( 'Forbidden', 'Forbidden', [ 'response' => 403 ] );
|
|
}
|
|
|
|
$data = json_decode( $payload, true );
|
|
if ( ! is_array( $data ) ) {
|
|
wp_send_json( [ 'status' => 'error', 'message' => 'Invalid JSON' ], 400 );
|
|
}
|
|
|
|
$action = $data['config']['action'] ?? '';
|
|
$api_url = $data['api_url'] ?? '';
|
|
|
|
// Extract EB event ID from the API URL (e.g. .../events/123456789/).
|
|
$eb_event_id = $this->extract_event_id_from_url( $api_url );
|
|
|
|
switch ( $action ) {
|
|
case 'event.published':
|
|
case 'event.updated':
|
|
if ( $eb_event_id ) {
|
|
$this->event_sync->pull_event( $eb_event_id );
|
|
}
|
|
break;
|
|
|
|
case 'event.unpublished':
|
|
if ( $eb_event_id ) {
|
|
$this->handle_event_unpublished( $eb_event_id );
|
|
}
|
|
break;
|
|
|
|
case 'attendee.updated':
|
|
$this->handle_attendee_updated( $data );
|
|
break;
|
|
|
|
default:
|
|
// Unknown action — acknowledge without processing.
|
|
break;
|
|
}
|
|
|
|
wp_send_json( [ 'status' => 'ok' ], 200 );
|
|
}
|
|
|
|
private function verify_signature( string $payload, string $signature ): bool {
|
|
$secret = (string) get_option( 'eb4tec_webhook_secret', '' );
|
|
|
|
// If no secret is configured, skip verification (allows initial setup).
|
|
if ( empty( $secret ) ) {
|
|
return true;
|
|
}
|
|
|
|
if ( empty( $signature ) ) {
|
|
return false;
|
|
}
|
|
|
|
$expected = hash_hmac( 'sha256', $payload, $secret );
|
|
return hash_equals( $expected, $signature );
|
|
}
|
|
|
|
private function handle_event_unpublished( string $eb_event_id ): void {
|
|
$query = new WP_Query( [
|
|
'post_type' => 'tribe_events',
|
|
'post_status' => [ 'publish', 'draft' ],
|
|
'posts_per_page' => 1,
|
|
'fields' => 'ids',
|
|
'meta_query' => [ [
|
|
'key' => '_eb4tec_event_id',
|
|
'value' => $eb_event_id,
|
|
] ],
|
|
] );
|
|
|
|
if ( ! empty( $query->posts ) ) {
|
|
$post_id = $query->posts[0];
|
|
wp_update_post( [
|
|
'ID' => $post_id,
|
|
'post_status' => 'draft',
|
|
] );
|
|
update_post_meta( $post_id, '_eb4tec_eb_status', 'unpublished' );
|
|
}
|
|
}
|
|
|
|
private function handle_attendee_updated( array $data ): void {
|
|
// Extract attendee ID from the API URL if present.
|
|
$api_url = $data['api_url'] ?? '';
|
|
$attendee_id = $this->extract_id_from_url( $api_url, 'attendees' );
|
|
|
|
if ( ! $attendee_id ) {
|
|
return;
|
|
}
|
|
|
|
// Find any WC order that has this attendee and update its note.
|
|
global $wpdb;
|
|
$order_ids = $wpdb->get_col(
|
|
$wpdb->prepare(
|
|
"SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_eb4tec_attendee_ids' AND meta_value LIKE %s",
|
|
'%' . $wpdb->esc_like( $attendee_id ) . '%'
|
|
)
|
|
);
|
|
|
|
foreach ( $order_ids as $order_id ) {
|
|
$order = wc_get_order( $order_id );
|
|
if ( $order ) {
|
|
$order->add_order_note(
|
|
sprintf( __( 'EB4TEC: Eventbrite attendee %s was updated.', 'eb4tec' ), esc_html( $attendee_id ) ),
|
|
false,
|
|
false
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function extract_event_id_from_url( string $url ): string {
|
|
return $this->extract_id_from_url( $url, 'events' );
|
|
}
|
|
|
|
private function extract_id_from_url( string $url, string $resource ): string {
|
|
// URLs look like: https://www.eventbriteapi.com/v3/events/123456789/
|
|
if ( preg_match( '#/' . preg_quote( $resource, '#' ) . '/(\d+)/#', $url, $matches ) ) {
|
|
return $matches[1];
|
|
}
|
|
return '';
|
|
}
|
|
}
|