Files
2026-04-19 20:41:37 +02:00

363 lines
12 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Plugin Name: AZA License Bridge
* Description: Verknüpft WooCommerce-Abo-Käufe mit dem AZA Hetzner-Lizenz-Backend.
* Version: 1.0.1
* Author: AZA MedWork
* Requires PHP: 7.4
* Requires at least: 5.8
*
* Nach erfolgreicher Aktivierung eines Abonnements wird automatisch ein
* Lizenzschlüssel auf dem Hetzner-Backend erzeugt und per E-Mail an den
* Kunden versendet.
*
* Konfiguration: WordPress Admin → Einstellungen → AZA License Bridge
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// ── Settings Page ─────────────────────────────────────────────────────────────
add_action( 'admin_menu', function () {
add_options_page(
'AZA License Bridge',
'AZA License Bridge',
'manage_options',
'aza-license-bridge',
'aza_lb_settings_page'
);
} );
add_action( 'admin_init', function () {
register_setting( 'aza_lb_settings', 'aza_lb_api_url' );
register_setting( 'aza_lb_settings', 'aza_lb_wc_secret' );
register_setting( 'aza_lb_settings', 'aza_lb_lookup_key' );
} );
function aza_lb_settings_page() {
$api_url = get_option( 'aza_lb_api_url', 'https://api.aza-medwork.ch' );
$wc_secret = get_option( 'aza_lb_wc_secret', '' );
$lookup_key = get_option( 'aza_lb_lookup_key', 'aza_basic_monthly' );
?>
<div class="wrap">
<h1>AZA License Bridge</h1>
<form method="post" action="options.php">
<?php settings_fields( 'aza_lb_settings' ); ?>
<table class="form-table">
<tr>
<th scope="row">Hetzner API URL</th>
<td>
<input type="url" name="aza_lb_api_url" value="<?php echo esc_attr( $api_url ); ?>" class="regular-text" />
</td>
</tr>
<tr>
<th scope="row">WC Provision Secret</th>
<td>
<input type="text" name="aza_lb_wc_secret" value="<?php echo esc_attr( $wc_secret ); ?>" class="regular-text" />
<p class="description">Muss identisch sein mit WC_PROVISION_SECRET auf Hetzner.</p>
</td>
</tr>
<tr>
<th scope="row">Standard lookup_key</th>
<td>
<input type="text" name="aza_lb_lookup_key" value="<?php echo esc_attr( $lookup_key ); ?>" class="regular-text" />
<p class="description">z. B. aza_basic_monthly, aza_basic_yearly</p>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
<hr>
<h2>Status</h2>
<p><strong>API URL:</strong> <?php echo esc_html( trailingslashit( $api_url ) . 'wc/provision' ); ?></p>
<p><strong>Secret gesetzt:</strong> <?php echo $wc_secret ? '✅ Ja' : '❌ Nein'; ?></p>
</div>
<?php
}
// ── Helpers ───────────────────────────────────────────────────────────────────
function aza_lb_log( $message ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( '[AZA-License-Bridge] ' . $message );
}
$upload = wp_upload_dir();
$base = isset( $upload['basedir'] ) ? $upload['basedir'] : '';
if ( empty( $base ) ) {
return;
}
$log_dir = trailingslashit( $base ) . 'aza-logs';
if ( ! is_dir( $log_dir ) ) {
wp_mkdir_p( $log_dir );
}
$log_file = trailingslashit( $log_dir ) . 'license-bridge.log';
$timestamp = current_time( 'Y-m-d H:i:s' );
@file_put_contents( $log_file, '[' . $timestamp . '] ' . $message . PHP_EOL, FILE_APPEND | LOCK_EX );
}
function aza_lb_get_lookup_key_from_subscription( $subscription ) {
if ( ! is_object( $subscription ) || ! method_exists( $subscription, 'get_items' ) ) {
return '';
}
$items = $subscription->get_items();
if ( empty( $items ) || ! is_array( $items ) ) {
return '';
}
foreach ( $items as $item ) {
if ( ! is_object( $item ) || ! method_exists( $item, 'get_product' ) ) {
continue;
}
$product = $item->get_product();
if ( ! $product || ! is_object( $product ) || ! method_exists( $product, 'get_meta' ) ) {
continue;
}
$lookup_key = trim( (string) $product->get_meta( '_aza_lookup_key' ) );
if ( $lookup_key !== '' ) {
return $lookup_key;
}
}
return '';
}
function aza_lb_get_parent_order_id_from_subscription( $subscription ) {
if ( ! is_object( $subscription ) ) {
return 0;
}
if ( method_exists( $subscription, 'get_parent_id' ) ) {
return (int) $subscription->get_parent_id();
}
return 0;
}
function aza_lb_get_billing_email_from_subscription( $subscription ) {
if ( ! is_object( $subscription ) ) {
return '';
}
if ( method_exists( $subscription, 'get_billing_email' ) ) {
$email = trim( (string) $subscription->get_billing_email() );
if ( $email !== '' ) {
return $email;
}
}
if ( method_exists( $subscription, 'get_parent' ) ) {
$parent_order = $subscription->get_parent();
if ( $parent_order && is_object( $parent_order ) && method_exists( $parent_order, 'get_billing_email' ) ) {
$email = trim( (string) $parent_order->get_billing_email() );
if ( $email !== '' ) {
return $email;
}
}
}
return '';
}
// ── Provisioning Request ──────────────────────────────────────────────────────
/**
* Sendet den Lizenz-Provisioning-Request an das Hetzner-Backend.
*
* @param int $subscription_id WooCommerce Subscription ID
* @param int $order_id WooCommerce Order ID
* @param string $email Kunden-E-Mail
* @param string $lookup_key Produkt-/Planschlüssel
* @return array|WP_Error
*/
function aza_lb_provision_license( $subscription_id, $order_id, $email, $lookup_key = '' ) {
$api_url = rtrim( (string) get_option( 'aza_lb_api_url', 'https://api.aza-medwork.ch' ), '/' );
$wc_secret = (string) get_option( 'aza_lb_wc_secret', '' );
if ( $wc_secret === '' ) {
aza_lb_log( "SKIP: WC_PROVISION_SECRET nicht konfiguriert (sub={$subscription_id})" );
return new WP_Error( 'aza_lb_no_secret', 'WC Provision Secret nicht konfiguriert.' );
}
if ( $lookup_key === '' ) {
$lookup_key = (string) get_option( 'aza_lb_lookup_key', 'aza_basic_monthly' );
}
$url = $api_url . '/wc/provision';
$payload = array(
'customer_email' => (string) $email,
'wc_order_id' => (int) $order_id,
'wc_subscription_id' => (int) $subscription_id,
'lookup_key' => (string) $lookup_key,
'allowed_users' => 1,
'devices_per_user' => 2,
);
$body = wp_json_encode( $payload );
aza_lb_log(
sprintf(
'POST %s (sub=%d, order=%d, email=%s, lookup_key=%s)',
$url,
(int) $subscription_id,
(int) $order_id,
(string) $email,
(string) $lookup_key
)
);
$response = wp_remote_post(
$url,
array(
'timeout' => 20,
'headers' => array(
'Content-Type' => 'application/json',
'X-WC-Secret' => $wc_secret,
'User-Agent' => 'AZA-License-Bridge/1.0.1',
),
'body' => $body,
)
);
if ( is_wp_error( $response ) ) {
aza_lb_log( 'FEHLER: ' . $response->get_error_message() );
return $response;
}
$code = (int) wp_remote_retrieve_response_code( $response );
$resp_body = (string) wp_remote_retrieve_body( $response );
aza_lb_log( "Response HTTP {$code}: {$resp_body}" );
if ( $code >= 200 && $code < 300 ) {
$data = json_decode( $resp_body, true );
return is_array( $data ) ? $data : array( 'raw' => $resp_body );
}
return new WP_Error( 'aza_lb_provision_failed', "HTTP {$code}: {$resp_body}" );
}
// ── WooCommerce Hook ──────────────────────────────────────────────────────────
/**
* Hook: Subscription wird aktiv.
*
* Ziel:
* - Neukauf sauber provisionieren
* - Mehrfachauslösung durch Meta-Flag verhindern
*/
add_action( 'woocommerce_subscription_status_active', 'aza_lb_on_subscription_active', 10, 1 );
function aza_lb_on_subscription_active( $subscription ) {
if ( ! is_object( $subscription ) ) {
aza_lb_log( 'SKIP: Hook erhielt kein Objekt.' );
return;
}
if ( ! method_exists( $subscription, 'get_id' ) ) {
aza_lb_log( 'SKIP: Hook-Objekt ohne get_id().' );
return;
}
$sub_id = (int) $subscription->get_id();
if ( $sub_id <= 0 ) {
aza_lb_log( 'SKIP: ungültige Subscription-ID.' );
return;
}
$already = (string) get_post_meta( $sub_id, '_aza_license_provisioned', true );
if ( $already === 'yes' ) {
aza_lb_log( "SKIP: sub={$sub_id} bereits provisioniert" );
return;
}
$email = aza_lb_get_billing_email_from_subscription( $subscription );
if ( $email === '' ) {
aza_lb_log( "SKIP: sub={$sub_id} keine E-Mail gefunden" );
return;
}
$order_id = aza_lb_get_parent_order_id_from_subscription( $subscription );
$lookup_key = aza_lb_get_lookup_key_from_subscription( $subscription );
if ( $lookup_key === '' ) {
$lookup_key = (string) get_option( 'aza_lb_lookup_key', 'aza_basic_monthly' );
}
$result = aza_lb_provision_license( $sub_id, $order_id, $email, $lookup_key );
if ( is_wp_error( $result ) ) {
aza_lb_log( "PROVISION FEHLGESCHLAGEN: sub={$sub_id} " . $result->get_error_message() );
return;
}
$license_key = '';
if ( isset( $result['license_key'] ) ) {
$license_key = trim( (string) $result['license_key'] );
}
update_post_meta( $sub_id, '_aza_license_provisioned', 'yes' );
update_post_meta( $sub_id, '_aza_license_provisioned_at', current_time( 'mysql' ) );
update_post_meta( $sub_id, '_aza_license_lookup_key', $lookup_key );
if ( $license_key !== '' ) {
update_post_meta( $sub_id, '_aza_license_key', $license_key );
}
if ( $order_id > 0 && $license_key !== '' ) {
update_post_meta( $order_id, '_aza_license_key', $license_key );
}
$status = isset( $result['status'] ) ? (string) $result['status'] : 'unknown';
aza_lb_log( "PROVISION OK: sub={$sub_id}, order={$order_id}, key={$license_key}, status={$status}" );
}
// ── Admin-Anzeige ─────────────────────────────────────────────────────────────
/**
* Zeigt den Lizenzschlüssel in der Admin-Bestellung an.
* Primär aus der Order, sekundär aus verknüpften Subscriptions.
*/
add_action( 'woocommerce_admin_order_data_after_billing_address', function ( $order ) {
if ( ! $order || ! is_object( $order ) || ! method_exists( $order, 'get_id' ) ) {
return;
}
$order_id = (int) $order->get_id();
$key = (string) get_post_meta( $order_id, '_aza_license_key', true );
if ( $key === '' && function_exists( 'wcs_get_subscriptions_for_order' ) ) {
$subscriptions = wcs_get_subscriptions_for_order( $order_id );
if ( is_array( $subscriptions ) ) {
foreach ( $subscriptions as $subscription ) {
if ( ! is_object( $subscription ) || ! method_exists( $subscription, 'get_id' ) ) {
continue;
}
$sub_key = (string) get_post_meta( (int) $subscription->get_id(), '_aza_license_key', true );
if ( $sub_key !== '' ) {
$key = $sub_key;
break;
}
}
}
}
if ( $key !== '' ) {
echo '<p><strong>AZA Lizenzschlüssel:</strong> <code>' . esc_html( $key ) . '</code></p>';
}
}, 10, 1 );