Skip to main content

Simulating Disputes

Overview

To simulate a dispute:

  1. Create a test dispute against a captured payment transaction.
  2. Find the dispute to read its status and stages.
  3. Respond to the dispute with evidence, or accept it.
  4. Simulate the issuer's full acceptance, then confirm the terminal state.

The simulateIssuer*CardPaymentDispute mutations are available in the Test environment only. They simulate the issuer's side of a dispute so you can drive a CardPaymentDispute through its lifecycle and exercise your webhook handling and response flow.

Contact your Highnote representative

The acquiring dispute surface is in preview. Access is provisioned by Highnote rather than through self-serve API keys, so contact your Highnote representative to enable it for your Test organization.

Error handling

The mutations below return a payload union of AccessDeniedError, UserError, and CardPaymentDispute. See API Error Handling for the general error-handling pattern.

Create a test dispute

Originate a dispute against a captured PaymentTransaction with simulateIssuerInitiatesCardPaymentDispute. This gives you a dispute to test without waiting for a network file.

mutation SimulateIssuerInitiatesCardPaymentDispute(
$input: SimulateIssuerInitiatesCardPaymentDisputeInput!
) {
simulateIssuerInitiatesCardPaymentDispute(input: $input) {
... on CardPaymentDispute {
id
cardPaymentDisputeStatus
category
networkReason {
code
}
stages {
__typename
status
responsibleParty
responseDueAt
}
}
... on UserError {
errors {
code
description
}
}
... on AccessDeniedError {
message
}
}
}
FieldTypeDescription
paymentTransactionIdID!The payment transaction to dispute. The transaction must have been captured.
networkReasonCodeSimulateCardPaymentDisputeNetworkReasonCode!The network reason for the dispute. Determines the dispute's category, and must belong to the transaction's card network.
disputedAmountAmountInput!The amount to dispute, in minor units. Must be positive, no greater than the captured amount, and in the transaction's currency.

An unknown transaction ID and a transaction that has not been captured both return ACQUIRING_PAYMENT_TRANSACTION_NOT_FOUND. A disputedAmount that is not positive, exceeds the captured amount, or uses a different currency than the transaction returns INVALID_DISPUTED_AMOUNT.

Three things about the dispute you get back are worth noting:

  • It opens at IN_REVIEW, not INITIATED. The dispute is live from the moment you create it.
  • Its chargeback stage is already IN_PROGRESS with responsibleParty: MERCHANT, so the dispute is waiting on you immediately. Read responseDueAt for your window to act in.
  • Its category is derived from the networkReasonCode you passed. You do not set it directly.

Find the dispute

Read the dispute back to check its status and stages before you respond. See Monitoring Disputes for the by-ID lookup, and List disputes for the cardPaymentDisputes connection and its filters.

The cardPaymentDisputes connection is backed by a search index, so a dispute you just created or changed can lag briefly in list results. A by-ID lookup with node is always current. Prefer it when a dispute you know exists is missing from a list.

Respond to the dispute

Respond as you would to a real dispute:

  • To contest it, upload evidence to the active stage and submit your response. See Challenging Disputes.
  • To concede it, accept the dispute for the full amount. See Accepting Disputes. This closes the dispute at MERCHANT_ACCEPTED, so the simulated issuer actions below no longer apply.

To continue this walkthrough, contest the dispute: the issuer needs a representment to accept.

Simulate issuer acceptance

Simulate the issuer accepting the full amount of the latest contested stage by passing the dispute ID to simulateIssuerAcceptsFullCardPaymentDispute.

This acts on a dispute that is waiting on the issuer, so submit your evidence first. Simulating acceptance before a representment exists is rejected.

mutation SimulateIssuerAcceptsFullCardPaymentDispute(
$input: SimulateIssuerAcceptsFullCardPaymentDisputeInput!
) {
simulateIssuerAcceptsFullCardPaymentDispute(input: $input) {
... on CardPaymentDispute {
id
cardPaymentDisputeStatus
}
... on UserError {
errors {
code
description
}
}
... on AccessDeniedError {
message
}
}
}
FieldTypeDescription
cardPaymentDisputeIdID!The CardPaymentDispute to drive to a full issuer acceptance.

Confirm the terminal state

The mutation response reports the dispute's status at the time of the call, which is still IN_REVIEW with disputeOutcome: null. The outcome applies a few seconds later, so read the dispute back to see its terminal state.

On read-back, the dispute is CLOSED with disputeOutcome: WON. This is a real write: the dispute closes and stays closed.

financialImpact populates on resolution. While a dispute is open it reads null, so use the active stage's amount for the contested value and financialImpact once the dispute reaches a terminal outcome. See Track financial impact.