---
title: "Refunds for custom Jovvie Checkout integrations"
description: "Where merchants refund payments made through your integration, and how your integration finds out about a refund."
url: https://docs.jovvie.com/developers/refunds/
section: "Developers"
last_updated: 2026-10-26
---

# Refunds for custom Jovvie Checkout integrations

Section: Developers

> Where merchants refund payments made through your integration, and how your integration finds out about a refund.

This page explains how refunds work for payments your integration creates, and how your integration can keep its records up to date when a merchant refunds a payment.

The Jovvie SDKs cannot create refunds yet. SDK support for refunds is planned. Until then, the merchant refunds the payment with their payment processor. Your integration can update its records when a refund event arrives, and the Stripe Dashboard or the NMI portal is the record to check against.

## Where merchants refund

| Processor | Where to refund                 |
| --------- | ------------------------------- |
| Stripe    | The merchant’s Stripe Dashboard |
| NMI       | The NMI portal                  |

Do not build a refund button in your app that looks like it refunds the card. If your app has a refund screen, tell staff to make the refund in the Stripe Dashboard or the NMI portal.

Before making or retrying a refund, or returning money another way, check the payment and its refunds in the Stripe Dashboard or the NMI portal. Confirm that the amount being returned has not already been refunded and no refund for that amount is still processing. If you are unsure, contact [Jovvie support](https://jovvie.com/support/).

## Help the merchant find the payment

When you create a payment, save the IDs the merchant will need to find it with their processor:

- **Stripe:** `$payment->generatedData->paymentIntentId`. This is the payment’s ID in Stripe.
- **NMI:** the NMI transaction ID. Get it with `getTransactionId()` from the verified `jovvie-gateway.payment.completed` webhook event. See [Handle webhooks](https://docs.jovvie.com/developers/webhooks/#nmi-sdk-events).

Show these IDs on the order in your app. With Stripe, the description you pass when you create the payment also appears on the payment in the Stripe Dashboard.

## How your integration hears about a refund

Jovvie can send a refund event to your application’s **Webhook URL**. When one arrives, verify it with your webhook key before you use it, then update your records. See [Handle webhooks](https://docs.jovvie.com/developers/webhooks/).

Do not count on a refund event for every refund. The Stripe Dashboard or the NMI portal is the record of what was refunded, so reconcile your records against it. See [Keep your records in step](https://docs.jovvie.com/developers/refunds/#keep-your-records-in-step).

|                   | Stripe                                                | NMI                                                          |
| ----------------- | ----------------------------------------------------- | ------------------------------------------------------------ |
| Event type        | `jovvie.payment.refunded`                             | `jovvie-gateway.payment.refunded`                            |
| Event class       | `JovviePayments\WebhookEvents\Events\PaymentRefunded` | `JovviePaymentsGateway\WebhookEvents\Events\PaymentRefunded` |
| Jovvie payment ID | `getPaymentId()`                                      | `getPaymentId()`                                             |
| Processor ID      | `getPaymentIntentId()`                                | `getTransactionId()`                                         |
| Amount refunded   | `getInvoiceRefund()`: the total refunded so far       | `getTransactionRefund()`: the amount of this refund          |
| Other amount      | `getInvoiceAmount()`: the amount left after refunds   | `getTransactionAmount()`: the original payment amount        |
| Currency          | `getInvoiceCurrency()`                                | `getTransactionCurrency()`                                   |
| Mode              | `getPaymentMode()`: `live` or `test`                  | Not included                                                 |

All amounts are in the smallest currency unit, such as cents.

A `refunded` status can mean a full or a partial refund. To tell them apart, compare the amounts in the event. Do not rely on the payment’s status alone to know whether, or how much, a payment was refunded. Check the Stripe Dashboard or the NMI portal.

### Example: handle a Stripe refund event

This example runs after you have verified the webhook and created `$event` with `WebhookEvent::constructEvent()`. The same event can arrive more than once, so it saves the event ID and the refund in one database transaction. Your events table has a unique constraint on the event ID, so a second copy of the event cannot be saved. See [Process each event once](https://docs.jovvie.com/developers/webhooks/#process-each-event-once).

```php
use JovviePayments\WebhookEvents\Events\PaymentRefunded;

if ($event instanceof PaymentRefunded) {
    // Ignore events from the other mode, so a test refund never changes a live order.
    $appMode = 'YOUR_APP_MODE'; // 'live' or 'test'. Load this from your server configuration.
    if ($event->getPaymentMode() !== $appMode) {
        http_response_code(200);
        exit;
    }

    yourApp_beginTransaction();

    try {
        // Fails if this event ID was already saved.
        if (!yourApp_insertEventId($event->getId())) {
            yourApp_rollBack();
            http_response_code(200);
            exit;
        }

        $order = yourApp_findOrderByJovviePaymentId($event->getPaymentId());

        if (!$order) {
            // Keep the event so you can match it to an order later.
            yourApp_storeEventForReconciliation($event);
        } else {
            // Lock the order row so two refund events cannot update it at once.
            yourApp_lockOrder($order);

            $refunded  = $event->getInvoiceRefund();  // total refunded so far
            $remaining = $event->getInvoiceAmount();  // amount left

            // Events can arrive out of order. Only move forward: never replace
            // a larger refunded total with a smaller one.
            if ($refunded > yourApp_getRefundedTotal($order)) {
                if ($remaining === 0) {
                    yourApp_markFullyRefunded($order, $refunded);
                } else {
                    yourApp_markPartlyRefunded($order, $refunded);
                }
            }
        }

        yourApp_commit();
    } catch (\Throwable $e) {
        // Nothing was saved. Answer with an error so Jovvie sends the event again.
        yourApp_rollBack();
        http_response_code(500);
        exit;
    }

    http_response_code(200);
    exit;
}
```

The `yourApp_` functions stand for your own code.

With NMI, each refund event carries the amount of that refund only. If the merchant makes more than one partial refund, add the amounts from each event to get the total.

## Keep your records in step

- The refund is made by the processor, not by your app. Treat the Stripe Dashboard or the NMI portal as the record of what was refunded.
- If your app does not show a refund the merchant made, check the payment in the Stripe Dashboard or the NMI portal and update your records to match.
- Update your records after the refund is made, not before. If your app shows a refund that was never made, staff and customers can be misled.

## Related pages

- [Handle webhooks](https://docs.jovvie.com/developers/webhooks/)
- [Check a payment’s status](https://docs.jovvie.com/developers/check-payment-status/)
- [Where to see your payouts](https://docs.jovvie.com/payments/payouts/)

## Need help?

Contact [Jovvie support](https://jovvie.com/support/).
