# Download links

Cheqi download links give a customer a hosted receipt URL when no wallet recipient is available. The same client-encrypted mechanism is used for an online download fallback and for a URL issued while the Cheqi API is temporarily unavailable.

```text
https://receipt.cheqi.io/{downloadId}#{contentKey}
```

The SDK generates the download ID and AES-256-GCM content key locally. It builds
the canonical download envelope locally, encrypts it, and uploads only
ciphertext through `POST /receipt/download`. The fragment after `#` is not sent
to Cheqi by the browser, so Cheqi cannot decrypt the receipt. Decryption happens
in the customer's browser.

Use `https://receipt.cheqi.io` in production and `https://sandbox.receipt.cheqi.io` in sandbox.

## Online fallback

Download fallback is enabled for the company or client application. When recipient resolution cannot find a wallet recipient and selects `DOWNLOAD_FALLBACK`, the SDK:

1. generates the download URL and fragment key locally
2. builds the canonical download envelope from the definitive receipt input
3. encrypts that envelope locally
4. uploads the ciphertext through `POST /receipt/download`
5. returns the download URL to your integration


Your integration can render the URL as a QR code, print it, include it in an order confirmation, or show it on an order-status page.

When the API is reachable and recipient resolution returns `routeFound: false`, stop with customer-not-found. Do not call the low-level upload endpoint to bypass a disabled download fallback.

## Service outage

The URL can also be generated without a network call. This lets a POS display or print the QR code while Cheqi is unavailable. The receipt page tells the customer that the receipt is not ready yet and asks them to return later.

Before exposing the URL, your integration must durably store:

- the download URL or its download ID and content key
- the original definitive receipt input and requested formats
- issuer and jurisdiction context required for local generation
- a stable merchant transaction identifier
- the current processing state and operational timestamps


When Cheqi is reachable again, your processing builds and encrypts the canonical
download envelope with the SDK, persists the ciphertext, and uploads it. Do not
implement a separate offline receipt renderer or envelope format.

An already issued outage URL may be completed even if a later recipient resolution produces no route. `POST /receipt/download` deliberately takes no `matchId` and does not re-evaluate the fallback setting, because the customer already possesses the URL.

## Retry contract

Your integration owns storage, retry scheduling, compute allocation, monitoring, retention, and manual recovery. Cheqi SDKs provide stateless primitives and return values; they do not include a database, outbox, background worker, or extensive retry policy.

Use these states:

| State | Stored data | Next action |
|  --- | --- | --- |
| `PENDING_DOWNLOAD_TEMPLATE` | URL credentials and original receipt input | Retry local envelope generation |
| `PENDING_DOWNLOAD_UPLOAD` | URL credentials, template hash, and exact ciphertext | Retry the exact stored upload bytes |
| `DELIVERED_DOWNLOAD` | Delivery result and URL reference | No further submission |


Persist ciphertext before attempting upload. If the upload result is ambiguous, retry the exact bytes. AES-GCM uses a random IV, so encrypting again produces different ciphertext.

The first successful write wins permanently. Replaying identical bytes as the same principal is idempotent. Different bytes or a different principal for the same download ID return HTTP 409 and never replace the stored receipt.

Never log the complete URL or fragment key. Anyone with the fragment key can decrypt the receipt.

## Java SDK

For a custom local environment:

```java
CheqiSDK sdk = CheqiSDK.builder()
    .customApiEndpoint("http://localhost:8080")
    .receiptDownloadBaseUrl("http://localhost:5190")
    .apiKey(System.getenv("CHEQI_API_KEY"))
    .maxRetries(0)
    .build();
```

The high-level flow completes a client-encrypted download immediately when the
identification details provide enough local payment context:

```java
ReceiptResult result = sdk.getReceiptService()
    .issueReceipt(identificationDetails, receiptPayload);

if (result.isAccepted() && result.getDownloadUrl() != null) {
    presentToCustomer(result.getDownloadUrl());
} else if (result.isDownloadEnvelopeRequired()) {
    ReceiptResult completed = sdk.getReceiptService().completeDownloadFallback(
        result,
        receiptEnvelope,
        templateHash,
        accessToken
    );
    presentToCustomer(completed.getDownloadUrl());
}
```

For an explicit cash or anonymous download flow, use `issueDownloadReceipt(...)`
instead of recipient matching. The content key remains only in the returned URL
fragment.