Simulating Disputes
Overview
To simulate a dispute:
- Create a test dispute against a captured payment transaction.
- Find the dispute to read its status and stages.
- Respond to the dispute with evidence, or accept it.
- 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.
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
}
}
}
| Field | Type | Description |
|---|---|---|
paymentTransactionId | ID! | The payment transaction to dispute. The transaction must have been captured. |
networkReasonCode | SimulateCardPaymentDisputeNetworkReasonCode! | The network reason for the dispute. Determines the dispute's category, and must belong to the transaction's card network. |
disputedAmount | AmountInput! | 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, notINITIATED. The dispute is live from the moment you create it. - Its chargeback stage is already
IN_PROGRESSwithresponsibleParty: MERCHANT, so the dispute is waiting on you immediately. ReadresponseDueAtfor your window to act in. - Its
categoryis derived from thenetworkReasonCodeyou 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
}
}
}
| Field | Type | Description |
|---|---|---|
cardPaymentDisputeId | ID! | 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.