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).
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
- The
woocommerce_order_refundedhook fires with the order ID and refund ID. - LicenceForge retrieves the order and calculates the total refunded amount.
- If the refunded amount is greater than or equal to the order total, the associated license is suspended.
- 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:
- The
charge.refundedwebhook arrives with the charge object. - LicenceForge compares the
amount_refundedto the original chargeamount. - Full refund (refunded amount >= charge amount): the license is suspended.
- 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 );
}
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) |
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.