Refunds and Disputes

LicenceForge handles refunds and payment disputes consistently across both WooCommerce and Stripe, automatically adjusting license status based on refund amount and dispute outcome.

Refund Behaviour

The core refund logic is the same regardless of payment platform:

  • Full refund — the license is suspended immediately.
  • Partial refund — the event is logged but the license status is not changed.

A refund is considered "full" when the refunded amount is greater than or equal to the original order total (for WooCommerce) or the original charge amount (for Stripe).

Suspended, not cancelled

Full refunds result in a suspended status rather than cancelled. This allows the license to be reactivated if needed (e.g., if the refund was issued in error). To permanently revoke access, cancel the license manually after the refund.

WooCommerce Refunds

LicenceForge listens to the woocommerce_order_refunded hook, which fires whenever a refund is processed for a WooCommerce order.

Processing flow

  1. The woocommerce_order_refunded hook fires with the order ID and refund ID.
  2. LicenceForge retrieves the order and calculates the total refunded amount.
  3. If the refunded amount is greater than or equal to the order total, the associated license is suspended.
  4. If the refund is partial (less than the order total), the refund is logged in the license audit trail but no status change occurs.
// WooCommerce refund handler (simplified)
add_action( 'woocommerce_order_refunded', function ( $order_id, $refund_id ) {
    $order  = wc_get_order( $order_id );
    $license = WPLF_License::find_by_order_id( $order_id );

    if ( ! $license ) {
        return;
    }

    $refunded = (float) $order->get_total_refunded();
    $total    = (float) $order->get_total();

    if ( $refunded >= $total ) {
        // Full refund — suspend the license
        $license->suspend( 'Full refund processed' );
    } else {
        // Partial refund — log only
        $license->add_audit_log( 'Partial refund of ' . wc_price( $refunded ) );
    }
}, 10, 2 );

Summary

Refund type Condition License action
Full refund Refunded amount >= order total License suspended
Partial refund Refunded amount < order total Logged only (no status change)

Stripe Refunds

Stripe refunds are handled via the charge.refunded webhook event. The logic mirrors WooCommerce refund handling:

  1. The charge.refunded webhook arrives with the charge object.
  2. LicenceForge compares the amount_refunded to the original charge amount.
  3. Full refund (refunded amount >= charge amount): the license is suspended.
  4. Partial refund: the event is logged without changing the license status.
// Stripe charge.refunded handler (simplified)
$charge = $event->data->object;

if ( $charge->amount_refunded >= $charge->amount ) {
    // Full refund
    $license->suspend( 'Stripe full refund: ' . $charge->id );
} else {
    // Partial refund
    $license->add_audit_log( 'Stripe partial refund: ' . $charge->amount_refunded );
}

Stripe Disputes

Payment disputes (chargebacks) are handled separately from refunds and follow a more aggressive approach: the license is suspended immediately when a dispute is opened, before the dispute is resolved.

Dispute created

When a charge.dispute.created webhook event is received, LicenceForge immediately suspends the associated license. This prevents the customer from continuing to use the software while the dispute is under review.

// charge.dispute.created handler
$dispute = $event->data->object;
$license = WPLF_License::find_by_stripe_charge( $dispute->charge );

if ( $license ) {
    $license->suspend( 'Payment dispute opened: ' . $dispute->id );
}
Immediate suspension

Unlike refunds, disputes always suspend the license immediately regardless of the disputed amount. This is a protective measure while the dispute is pending resolution.

Dispute closed

When a charge.dispute.closed webhook event is received, the outcome determines what happens to the license:

Dispute outcome License action
Merchant won License is reactivated
Merchant lost Outcome is logged (license remains suspended)
// charge.dispute.closed handler
$dispute = $event->data->object;
$license = WPLF_License::find_by_stripe_charge( $dispute->charge );

if ( ! $license ) {
    return;
}

if ( $dispute->status === 'won' ) {
    $license->reactivate( 'Dispute resolved in merchant favour: ' . $dispute->id );
} else {
    $license->add_audit_log( 'Dispute lost: ' . $dispute->id );
    // License remains suspended
}

Complete Reference

The following table summarises all refund and dispute scenarios across both payment platforms:

Platform Event Condition License action
WooCommerce woocommerce_order_refunded Full refund (>= order total) Suspended
WooCommerce woocommerce_order_refunded Partial refund Logged only
Stripe charge.refunded Full refund (>= charge amount) Suspended
Stripe charge.refunded Partial refund Logged only
Stripe charge.dispute.created Always Suspended immediately
Stripe charge.dispute.closed Merchant won Reactivated
Stripe charge.dispute.closed Merchant lost Logged only (remains suspended)
Audit trail

All refund and dispute events are recorded in the license's audit log, regardless of whether the license status changes. This provides a complete history for support and compliance purposes.