---
title: "Refunds & chargebacks — Purchases for Laravel | Roundly"
description: "Apple, Google and Stripe refunds and disputes decode into PurchaseRefund records linked to the purchase they reverse."
url: https://roundly-consulting.com/open-source/docs/purchases-for-laravel/refunds
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Purchases for Laravel](https://roundly-consulting.com/open-source/docs/purchases-for-laravel.md)

# Refunds & chargebacks

Refund and dispute notifications from every store decode into a ResultType::Refund result and are recorded as a first-class PurchaseRefund:

| Store | Notification | Recorded as |
| --- | --- | --- |
| Apple | REFUND, REVOKE | One refund per refunded transaction (keyed on its transactionId) — refunding the subscription’s current period also flips the Subscription to Refunded |
| Google | Subscription-revoked RTDN, voided-purchase notification | Refund — a revoked subscription, or a void of its latest order, flips it to Refunded; a quantity-based partial void keeps the purchase Completed |
| Stripe | charge.refunded | One refund per charge (keyed on its PaymentIntent) whose price is the running total refunded — a charge not fully refunded keeps the purchase Completed |
| Stripe | charge.dispute.created | Chargeback, keyed on the dispute id |
| Stripe | charge.dispute.closed as won | The same chargeback row is reversed and the purchase it was linked to reinstated (PurchaseCompleted) — only a purchase the package recorded; none is ever created |
| Stripe | Inquiries (warning\_\*), charge.dispute.updated | Audited only — nothing changes |

handle() records the refund idempotently, links it to the originating purchase — matched on the provider plus the transaction id or provider id — flips that purchase to Status::Refunded, and dispatches PurchaseRefunded, or ChargebackReceived for disputes. A refund of a subscription’s current period — the Apple transaction it is in, or its latest Google order — and a revoked Google subscription also flip the Subscription to Refunded, so it stops being active; refunding an earlier period leaves it alone. Each refunded Apple transaction is its own PurchaseRefund, so refunds of two periods are two rows and two events. refunded\_at is Apple’s revocationDate, or — since Stripe and Google name no refund date — the time of the refund event. A partial refund (a Stripe charge not fully refunded, a Google quantity-based partial void) is recorded and fires PurchaseRefunded but leaves the purchase Completed:

```php
use RoundlyConsulting\Purchases\Models\PurchaseRefund;

$purchase->refunds;                    // HasMany<PurchaseRefund>
$purchase->status;                     // Status::Refunded once a refund is matched

PurchaseRefund::chargebacks()->get();  // disputes only

$refund->chargeback;                   // bool
$refund->reason;                       // ?string
$refund->price;                        // ?Money — the refunded amount
$refund->refunded_at;                  // ?Carbon
$refund->purchase;                     // the Purchase it reverses, when matched
```

reason holds what the store reports — Stripe’s reason field, Apple’s notification type, Google’s refund type or notification name. A prorated Apple refund records only the refunded share; a Family Sharing revocation records no amount.

## Stripe refunds are a running total

All refunds of one Stripe charge share one PurchaseRefund, keyed on its PaymentIntent, and its price is the charge’s cumulative amount\_refunded. Each further partial refund fires PurchaseRefunded again with the new total, not the amount of that refund — so never add up $event->refund->price across events; read it as “refunded so far”. An Apple refund row is one refunded transaction, a Google one one voided order.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/purchases-for-laravel.md)
