# Sandbox Environment

The Cheqi sandbox is a fully functional test environment where you can build and test your integration without affecting production data. It mirrors the production API with pre-configured test customers so you can verify the complete receipt flow end-to-end.

## Sandbox Details

|  |  |
|  --- | --- |
| **API Base URL** | `https://sandbox.api.cheqi.io` |
| **Merchant Portal** | [sandbox.portal.cheqi.io](https://sandbox.portal.cheqi.io) |
| **Status** | Fully functional, mirrors production API |


Sandbox vs Production
The sandbox environment is functionally identical to production. The only differences are:

- Client applications are **auto-approved** (no review wait time)
- Pre-configured **test customers** with known card PARs and emails
- A **merchant portal** for managing companies, keys, and viewing receipts


## Getting Started

### 1. Create an Account

Go to [sandbox.portal.cheqi.io](https://sandbox.portal.cheqi.io) and register with your email address. You'll receive a verification code to complete sign-up.

### 2. Register a Company

Once logged in, create a company from the **Dashboard** (or click **Add Company**). This registers your merchant identity in the sandbox.

### 3. Get Your API Credentials

From the Dashboard, generate an **API Key** for direct API access.

Include the key as a Bearer token in the `Authorization` header for all API calls:

```bash
curl https://sandbox.api.cheqi.io/receipt/template \
  -H "Authorization: Bearer sk_your_sandbox_api_key" \
  -H "Content-Type: application/json" \
  -d '{...}'
```

Alternatively, create a **Client Application** for OAuth2 integrations. Client applications are auto-approved in the sandbox, so you can start testing immediately.

### 4. Generate and Upload a Public Key

Cheqi uses hybrid encryption (AES-256-GCM + RSA-OAEP with SHA-256). Upload a public key when you need to receive encrypted content from Cheqi. Keep the matching private key in your own infrastructure and use it to decrypt the wrapped AES key in each envelope — Cheqi never needs your private key.

You need a registered public key when you want to:

- **Receive encrypted receipt copies** — fetch issued receipts through the API or process receipt webhooks for your own company or client application.
- **Receive and process customer returns** — return requests are encrypted to your public key (your *issuer key*) and you decrypt them with the matching private key. **Without a registered key, customers cannot send you returns and you cannot decrypt them.**


If you only submit receipts for delivery to customer apps and do not handle returns, a public key is optional. Without one, Cheqi simply skips your company/client application as a recipient; customer-app delivery and PDF email fallback still work.

#### Generate an RSA Key Pair

```bash
# Generate a 2048-bit RSA private key
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private_key.pem

# Extract the public key
openssl rsa -in private_key.pem -pubout -out public_key.pem
```

This creates:

- `private_key.pem`: your PKCS#8 RSA private key (`-----BEGIN PRIVATE KEY-----`)
- `public_key.pem`: the X.509 SubjectPublicKeyInfo public key to upload to Cheqi (`-----BEGIN PUBLIC KEY-----`)


Upload only `public_key.pem`. Never upload `private_key.pem`.

Keep Your Private Key Safe
Store `private_key.pem` securely — you'll need it to decrypt receipt copies sent to your application. Never share it or commit it to version control.

#### Upload via the Merchant Portal

1. Go to the **Dashboard** on [sandbox.portal.cheqi.io](https://sandbox.portal.cheqi.io)
2. Find your client application
3. Paste the contents of `public_key.pem`, or select the `public_key.pem` file
4. Click **Upload Key**


The portal reads the public key PEM content, validates that it is a public key, and encodes it before sending it to the API. Uploading `public_key.pem` is equivalent to pasting the file contents. The portal rejects private-key files.

#### Upload via API

You can also upload the public key programmatically. The API expects the PEM content to be **Base64-encoded** (to avoid newline issues in JSON):

```bash
# Base64-encode the PEM file. This works on macOS and Linux.
PUBLIC_KEY_B64=$(openssl base64 -A -in public_key.pem)

# Upload to your client application
curl -X POST https://sandbox.api.cheqi.io/client-application/{clientApplicationId}/public-key \
  -H "Authorization: Bearer YOUR_COMPANY_API_KEY_OR_ADMIN_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"publicKey\": \"$PUBLIC_KEY_B64\",
    \"keyAlgorithm\": \"RSA_2048\"
  }"
```

The backend decodes the Base64-encoded PEM, strips the PEM headers and whitespace, validates the key, and stores the normalized public key material. If you later retrieve the key through the API, the returned `publicKey` is the normalized key material, not the original PEM file.

#### Company keys (API-key integrations)

If you integrate with a **company API key** rather than a client application, upload the company's public key instead. This is the key used as your *issuer key* when you receive customer returns issued under your company:

```bash
PUBLIC_KEY_B64=$(openssl base64 -A -in public_key.pem)

curl -X POST https://sandbox.api.cheqi.io/company/public-key \
  -H "Authorization: Bearer YOUR_COMPANY_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"publicKey\": \"$PUBLIC_KEY_B64\",
    \"keyAlgorithm\": \"RSA_2048\"
  }"
```

**Supported key algorithms:**

| Algorithm | Description |
|  --- | --- |
| `RSA_2048` | RSA 2048-bit with RSA-OAEP (SHA-256) |
| `RSA_4096` | RSA 4096-bit with RSA-OAEP (SHA-256) |


### 5. Submit a Receipt

Choose your integration method:

#### SDK (Recommended)

```java Java
CheqiSDK sdk = CheqiSDK.builder()
    .customApiEndpoint("https://sandbox.api.cheqi.io")
    .apiKey("sk_your_sandbox_api_key")
    .build();

IdentificationDetails id = IdentificationDetails.builder()
    .paymentType(PaymentType.CARD_PAYMENT)
    .cardDetails(CardDetails.builder()
        .paymentAccountReference("SANDBOX-PAR-001")
        .cardProvider(CardProvider.VISA)
        .build())
    .build();

ReceiptTemplateRequest receipt = ReceiptTemplateRequest.builder()
    .documentNumber("INV-001")
    .issueDate(Instant.now())
    .currency("EUR")
    .totalAmount(new BigDecimal("12.10"))
    .totalTaxAmount(new BigDecimal("2.10"))
    .totalBeforeTax(new BigDecimal("10.00"))
    .receiptSubtotal(new BigDecimal("10.00"))
    .products(List.of(
        Product.builder()
            .name("Cappuccino")
            .identifier("SKU-CAPPUCCINO-001")
            .quantity(1.0)
            .unitPrice(new BigDecimal("4.00"))
            .subtotal(new BigDecimal("4.00"))
            .total(new BigDecimal("4.84"))
            .addTax(21.0, "VAT", "4.00", "0.84")
            .build()
    ))
    .addTax(Tax.builder()
        .rate(21.0)
        .type("VAT")
        .taxableAmount("10.00")
        .amount("2.10")
        .label("VAT 21%")
        .build())
    .build();

ReceiptResult result = sdk.getReceiptService()
    .processCompleteReceipt(id, receipt);
```

```csharp C#
var sdk = CheqiSdk.Builder()
    .CustomApiEndpoint("https://sandbox.api.cheqi.io")
    .ApiKey("sk_your_sandbox_api_key")
    .Build();

var id = IdentificationDetails.Builder()
    .PaymentType(PaymentType.CARD_PAYMENT)
    .CardDetails(CardDetails.Builder()
        .PaymentAccountReference("SANDBOX-PAR-001")
        .CardProvider(CardProvider.VISA)
        .Build())
    .Build();

var receipt = ReceiptTemplateRequest.Builder()
    .DocumentNumber("INV-001")
    .IssueDate(DateTimeOffset.UtcNow)
    .Currency("EUR")
    .TotalAmount(12.10m)
    .TotalTaxAmount(2.10m)
    .TotalBeforeTax(10.00m)
    .ReceiptSubtotal(10.00m)
    .AddProduct(Product.Builder()
        .Name("Cappuccino")
        .Identifier("SKU-CAPPUCCINO-001")
        .Quantity(1)
        .UnitPrice(4.00m)
        .Subtotal(4.00m)
        .Total(4.84m)
        .AddTax(21.0, "VAT", 4.00m, 0.84m)
        .Build())
    .AddTax(Tax.Builder()
        .Rate(21.0)
        .Type("VAT")
        .TaxableAmount(10.00m)
        .Amount(2.10m)
        .Label("VAT 21%")
        .Build())
    .Build();

var result = await sdk.ReceiptService
    .ProcessCompleteReceiptAsync(id, receipt);
```

```javascript JavaScript
import { CheqiSDK, PaymentType } from "@cheqi/sdk";

const sdk = CheqiSDK.builder()
  .customApiEndpoint("https://sandbox.api.cheqi.io")
  .apiKey("sk_your_sandbox_api_key")
  .build();

const id = {
  paymentType: PaymentType.CARD_PAYMENT,
  cardDetails: {
    paymentAccountReference: "SANDBOX-PAR-001",
    cardProvider: "VISA",
  },
};

const receipt = {
  documentNumber: "INV-001",
  issueDate: new Date().toISOString(),
  currency: "EUR",
  totalAmount: 12.10,
  totalTaxAmount: 2.10,
  totalBeforeTax: 10.00,
  receiptSubtotal: 10.00,
  products: [
    {
      name: "Cappuccino",
      identifier: "SKU-CAPPUCCINO-001",
      quantity: 1,
      unitPrice: 4.00,
      subtotal: 4.00,
      total: 4.84,
      taxes: [
        {
          rate: 21.0,
          type: "VAT",
          taxableAmount: 4.00,
          amount: 0.84,
        },
      ],
    },
  ],
  taxes: [
    {
      rate: 21.0,
      type: "VAT",
      taxableAmount: 10.00,
      amount: 2.10,
      label: "VAT 21%",
    },
  ],
};

const result = await sdk.receiptService.processCompleteReceipt(id, receipt);
```

#### REST API

For manual integration, follow these steps:

1. **Resolve recipient:**

```bash
POST https://sandbox.api.cheqi.io/recipient/resolve
Authorization: Bearer sk_your_api_key
Content-Type: application/json

{
  "cardDetails": {
    "paymentAccountReference": "SANDBOX-PAR-001",
    "cardProvider": "VISA"
  }
}
```
2. **Create receipt template:**

```bash
POST https://sandbox.api.cheqi.io/receipt/template
```
3. **Encrypt** the receipt with the recipient's public key (AES-256-GCM + RSA-OAEP)
4. **Submit** the encrypted receipt:

```bash
POST https://sandbox.api.cheqi.io/receipt/encrypted
```


### 6. Verify Delivery

Go to the **Receipts** tab in the merchant portal to see your submitted receipt. Click on it to view delivery status and, when available, the **decrypted content**. To see decrypted content in the portal, issue the receipt against one of the sandbox test customer identifiers shown in the **Test Customers** section of the portal. This proves the full end-to-end flow works: customer matching, encryption, queuing, and decryption.

## Decrypted Receipt Preview

The sandbox merchant portal can show decrypted receipt content only for receipts delivered to Cheqi's pre-configured sandbox test customers. Use one of the test customer identifiers shown in the portal, such as a test PAR or email address, when resolving the recipient for the receipt.

This is a sandbox-only testing feature. The sandbox stores a private key for each seeded test customer, so when a receipt is delivered to one of those test-customer devices, the portal can decrypt that device copy and show:

- the decrypted receipt envelope
- decrypted customer context, when present
- recipient delivery status for the issued receipt


The portal does not decrypt arbitrary receipts. A receipt may have multiple encrypted copies, for example one for a customer device, one for a company, and one for a client application. The sandbox preview specifically looks for the copy encrypted to a sandbox test customer because that is the only customer-device private key Cheqi has in the sandbox.

Sandbox test customers are shared fixtures across the sandbox environment; they are not created separately for each merchant company. The receipt preview is still scoped to your company: the portal only lists and decrypts receipts that your company issued. Another sandbox company cannot open your receipts just because it can see the same test customer identifiers.

Production Privacy
This decrypted preview does not exist in production. In production, Cheqi does not store customer private keys and cannot read end-user receipt contents. Receipt payloads are decrypted by the receiving app, device, or client application that owns the matching private key.

If the portal shows that decrypted preview is unavailable, the receipt was not delivered to a sandbox test customer. Use one of the PARs or emails from the **Test Customers** section when you want to verify decrypted content in the portal.

## Test Customers

The sandbox has pre-configured test customers with known card PARs and emails. These test customers are shared across sandbox merchants. Go to the **Test Customers** section in the merchant portal to see the available identifiers you can use when resolving recipients. Receipts issued against these identifiers can be opened in the portal with decrypted receipt content, but only by the company that issued the receipt.

Use these test PARs in your `IdentificationDetails` to simulate real customer matching:

```java
IdentificationDetails customer = IdentificationDetails.builder()
    .paymentType(PaymentType.CARD_PAYMENT)
    .cardDetails(CardDetails.builder()
        .paymentAccountReference("SANDBOX-PAR-001")  // Test customer PAR
        .cardProvider(CardProvider.VISA)
        .build())
    .build();
```

## Pairing Codes

In production, customers can generate a temporary **pairing code** in the Cheqi app to identify themselves at checkout. This is especially useful for cash payments, gift cards, or any scenario where card data isn't available.

The customer shows an 8-digit code (or QR code) to the merchant, who includes it when submitting the receipt:

```java
IdentificationDetails customer = IdentificationDetails.builder()
    .paymentType(PaymentType.CARD_PAYMENT)
    .pairingCode("84739201")  // 8-digit code from customer's app
    .build();
```

Pairing codes are valid for **5 minutes** and can only be used once. See [Recipient Resolution](/receipts/recipient-resolution#identify-by-pairing-code) for full details.

## SDK Configuration for Sandbox

Point your SDK to the sandbox API:

```java Java
CheqiSDK sdk = CheqiSDK.builder()
    .customApiEndpoint("https://sandbox.api.cheqi.io")
    .apiKey(System.getenv("CHEQI_SANDBOX_API_KEY"))
    .build();
```

```csharp C#
var sdk = CheqiSdk.Builder()
    .CustomApiEndpoint("https://sandbox.api.cheqi.io")
    .ApiKey(System.Environment.GetEnvironmentVariable("CHEQI_SANDBOX_API_KEY"))
    .Build();
```

```javascript JavaScript
import { CheqiSDK } from "@cheqi/sdk";

const sdk = CheqiSDK.builder()
  .customApiEndpoint("https://sandbox.api.cheqi.io")
  .apiKey(process.env.CHEQI_SANDBOX_API_KEY)
  .build();
```

## Company Management

You can register multiple companies to test different merchant setups. Use the company switcher in the navigation bar to switch between them. Companies can be deactivated from the Dashboard when no longer needed — all API keys will be revoked but receipt data is retained for compliance.

## Next Steps

- **[Receipt Flow Overview](/receipts/overview)** - Understand the complete receipt delivery pipeline
- **[Authentication](/authentication/overview)** - API Keys vs OAuth 2.0
- **[Java SDK](/sdk/java)** - Full Java SDK documentation
- **[.NET SDK](/sdk/dotnet)** - Full .NET SDK documentation