# Cancel a sale

Cancel an in-progress sale with the Tap to Pay on iPhone SDK, and handle the cancellation status that the SDK returns.

**Prerequisites:** [Authentication](/api/authentication) · [Run a sale](/guides/payments/tap-to-pay-on-iphone/run-a-sale)

If a merchant starts a sale by mistake, or a customer changes their mind before they tap their card, you can cancel the sale while it's still in progress. To cancel a sale, call `cancelTransaction` on `WTPSTerminal`.

You can't cancel a sale after our gateway starts to authorize the sale. To return funds after a sale, go to [Run a refund](/guides/payments/tap-to-pay-on-iphone/run-a-refund).

<a id="integration-steps"></a>
## Integration steps

1. [Cancel the sale](#step-1-cancel-the-sale).
2. [Handle the cancellation status](#step-2-handle-the-cancellation-status).

<a id="step-1-cancel-the-sale"></a>
## Step 1. Cancel the sale

- Call `cancelTransaction` while the sale is in progress.

**`Swift`**

```swift
terminal.cancelTransaction()
```

**`Objective-C`**

```objectivec
[[WTPSTerminal singleton] cancelTransaction];
```

The SDK returns the outcome to `onCancelTransactionAttempt:withError:` on your `CoreAPISaleListener`. You registered this listener in [Run a sale](/guides/payments/tap-to-pay-on-iphone/run-a-sale), so you don't need to register another listener.

<a id="step-2-handle-the-cancellation-status"></a>
## Step 2. Handle the cancellation status

The SDK sends a `TransactionCancellationStatus` value to `onCancelTransactionAttempt:withError:`. The value indicates whether the SDK canceled the sale:

| **Value** | **Description** |
| --- | --- |
| `TRANSACTION_CANCELLATION_STATUS_CANCELLING` | The SDK is canceling the transaction. Wait for the SDK to confirm the outcome. |
| `TRANSACTION_CANCELLATION_STATUS_CANCELLED` | The SDK canceled the transaction. |
| `TRANSACTION_CANCELLATION_STATUS_NOT_ALLOWED` | The SDK can't cancel the transaction, for example, because our gateway is authorizing the sale. |
| `TRANSACTION_CANCELLATION_STATUS_NOTHING_TO_CANCEL` | There was no transaction to cancel. |

**Important:** `TRANSACTION_CANCELLATION_STATUS_CANCELLING` means that the SDK accepted your request, not that it canceled the sale. Wait for `TRANSACTION_CANCELLATION_STATUS_CANCELLED` before your app tells the merchant that the SDK canceled the sale.

The following example handles each cancellation status and enables or disables a payment button:

**`Swift`**

```swift
func onCancelTransactionAttempt(_ cancellationStatus: TransactionCancellationStatus,
                                withError sdkError: CoreSdkErrorWrapper?) {
    switch cancellationStatus {
    case TRANSACTION_CANCELLATION_STATUS_CANCELLING:
        // The SDK is canceling the transaction. Wait for the outcome.
        confirmButton.isEnabled = false
    case TRANSACTION_CANCELLATION_STATUS_CANCELLED:
        // The SDK canceled the transaction. You can start another sale.
        confirmButton.isEnabled = true
    case TRANSACTION_CANCELLATION_STATUS_NOT_ALLOWED:
        // It's too late to cancel. Wait for onSaleResponse:.
        confirmButton.isEnabled = true
    case TRANSACTION_CANCELLATION_STATUS_NOTHING_TO_CANCEL:
        // No transaction was in progress.
        confirmButton.isEnabled = true
    default:
        confirmButton.isEnabled = true
    }
}
```

**`Objective-C`**

```objectivec
- (void)onCancelTransactionAttempt:(TransactionCancellationStatus)cancellationStatus
                         withError:(CoreSdkErrorWrapper *)sdkError {
    switch (cancellationStatus) {
        case TRANSACTION_CANCELLATION_STATUS_CANCELLING:
            // The SDK is canceling the transaction. Wait for the outcome.
            _confirmButton.enabled = NO;
            break;
        case TRANSACTION_CANCELLATION_STATUS_CANCELLED:
            // The SDK canceled the transaction. You can start another sale.
            _confirmButton.enabled = YES;
            break;
        case TRANSACTION_CANCELLATION_STATUS_NOT_ALLOWED:
            // It's too late to cancel. Wait for onSaleResponse:.
            _confirmButton.enabled = YES;
            break;
        case TRANSACTION_CANCELLATION_STATUS_NOTHING_TO_CANCEL:
            // No transaction was in progress.
            _confirmButton.enabled = YES;
            break;
    }
}
```

<a id="find-out-why-the-sdk-didnt-cancel-the-sale"></a>
### Find out why the SDK didn't cancel the sale

If the SDK returns `TRANSACTION_CANCELLATION_STATUS_NOT_ALLOWED`, it also sends a `CoreSdkErrorWrapper` that explains why. The wrapper has the following properties:

| **Property** | **Description** |
| --- | --- |
| `type` | Type of error that the wrapper holds. The SDK returns one of the following error types:  - `SDK_CORE_DEVICE_ERROR` indicates a `CoreDeviceError` value.  - `SDK_CORE_ERROR` indicates a `CoreError` value. |
| `value` | Value of the error as an `NSNumber`. Cast the value to the type that `type` indicates. |

If the SDK returns a `CoreDeviceError`, the value can be one of the following:

| **Value** | **Description** |
| --- | --- |
| `CANCELLATION_NOT_ALLOWED_TRANSACTION_STARTING` | The sale is starting. |
| `CANCELLATION_NOT_ALLOWED_TRANSACTION_GOING_ONLINE` | The sale is going to our gateway. |

The following example reads the error from the wrapper:

**`Swift`**

```swift
if let sdkError = sdkError {
    switch sdkError.type {
    case .SDK_CORE_DEVICE_ERROR:
        let deviceError = CoreDeviceError(rawValue: sdkError.value.uint32Value)
        // For example, CANCELLATION_NOT_ALLOWED_TRANSACTION_GOING_ONLINE
    case .SDK_CORE_ERROR:
        let coreError = CoreError(rawValue: sdkError.value.uint32Value)
    }
}
```

**`Objective-C`**

```objectivec
if (sdkError != nil) {
    if ([sdkError type] == SDK_CORE_DEVICE_ERROR) {
        CoreDeviceError deviceError = (CoreDeviceError)[[sdkError value] integerValue];
        // For example, CANCELLATION_NOT_ALLOWED_TRANSACTION_GOING_ONLINE
    } else {
        CoreError coreError = (CoreError)[[sdkError value] integerValue];
    }
}
```
