Skip to main content
Because HitPay processes the actual payment, refunds must be initiated through HitPay first — then reported back to Stripe. The bridge between the two systems is the Stripe Payment Record, which stores the HitPay payment ID at the time the payment is confirmed.

How It Works

When a payment completes, your backend calls paymentRecords.reportPayment() and embeds the HitPay payment ID inside the Payment Record — both in processor_details and in metadata. This creates a persistent, queryable link between the Stripe record and the HitPay payment that you can use to issue refunds later.

Key Concepts

API References

HitPay Refund API

Initiate a refund by payment_id and amount.

Stripe reportRefund

Report a completed refund back to a Stripe Payment Record.

Stripe Payment Records

Retrieve Payment Records and their metadata.

How the HitPay Payment ID is Stored

At payment confirmation time, your backend calls paymentRecords.reportPayment() with the HitPay payment ID embedded in two fields:
To look up the HitPay payment ID later, retrieve the Payment Record and read from either field:
The Payment Record ID itself is typically stored on the PaymentIntent metadata as stripe_payment_record_id at the time of payment confirmation, so you can always trace: PaymentIntent → Payment Record → HitPay Payment.

Refund Flow

The HitPay refund call is the only step that aborts the flow on failure. The Stripe reportRefund call is best-effort — if it fails, the customer has still been refunded by HitPay.

API Calls Summary


Implementation

1

Retrieve the Payment Record

Server-sideLook up the Stripe Payment Record to retrieve the HitPay payment ID. The Payment Record ID should have been stored on the PaymentIntent metadata as stripe_payment_record_id at payment confirmation time.
2

Call the HitPay Refund API

Server-side · Critical pathConvert the amount from cents to a decimal string and call HitPay’s refund endpoint. If this fails, the entire refund is aborted.
HitPay accepts amount as a decimal string (e.g. "50.00"). Stripe works in integer cents (e.g. 5000). Always convert at system boundaries.
3

Report the refund to Stripe

Server-side · Non-blockingReport the completed refund back to the Stripe Payment Record. Pass the HitPay refund ID as refund_reference to cross-link the records.

Subscription Invoices

For subscriptions, payments are tied to Stripe invoices rather than PaymentIntents directly. The same linkage applies — when the HitPay payment is confirmed for a subscription invoice, store both IDs in the invoice metadata:
At refund time, retrieve the invoice to get both IDs, then follow the same refund steps above.
See Auto-Charge and Out-of-Band for how subscription payments are confirmed and how these metadata values are written.

Error Handling

The refund flow uses a critical path / best-effort pattern:

Testing

  1. Complete a test payment using a sandbox HitPay payment method
  2. Confirm the Payment Record has hitpay_payment_id in its metadata
  3. Initiate a refund with an amount ≤ the original payment
  4. Verify in the HitPay dashboard: refund appears under the original payment
  5. Verify in the Stripe dashboard: Payment Record shows the refund report
Sandbox credentials:

FAQ

The hitpay_payment_id was not written to the Payment Record’s metadata when the original payment was confirmed. Check your paymentRecords.reportPayment() call and ensure the metadata object includes hitpay_payment_id. See One-Time Payments.
Common causes:
  • Invalid payment_id: Ensure you’re passing the HitPay payment ID, not a Stripe ID.
  • Amount exceeds original payment: HitPay rejects amounts greater than what was paid.
  • Already refunded: HitPay does not allow a second refund on a fully refunded payment.
  • Wrong API key or environment: Confirm your HITPAY_API_KEY matches the environment (sandbox vs. production).
The Payment Record ID may be from a different Stripe account or environment. Confirm the STRIPE_SECRET_KEY matches the account where the Payment Record was originally created.
Last modified on March 30, 2026