# Receipt Flow Overview ## How It Works Cheqi handles your **entire receipt delivery process** - from customer matching to final delivery. You send us the receipt data, and we take care of everything else: - 🔐 **End-to-end encrypted delivery** to customer devices - 📧 **Automatic PDF email fallback** for non-users - 📱 **Multi-device sync** across all customer devices - 🎨 **Professional formatting** - no design work needed - ✅ **Delivery confirmation** - know when receipts are delivered - 🌍 **Country compliance** - receipts automatically comply with local regulations based on where they're issued **You focus on your business, we handle the receipts.** ```mermaid sequenceDiagram participant POS as Your POS System participant Cheqi as Cheqi API participant Webhook as Webhook Recipient participant Customer as Customer's Device POS->>Cheqi: 1. Match customer (card/IBAN) Cheqi->>POS: 2. Return device public keys + webhook keys POS->>Cheqi: 3. Generate receipt template Cheqi->>POS: 4. Return UBL receipt JSON + template hash POS->>POS: 5. Encrypt receipt for devices & webhooks POS->>Cheqi: 6. Send encrypted receipts Cheqi->>Webhook: 7a. Webhook: receipt.created (encrypted data) Cheqi->>Customer: 7b. Push notification to devices Customer->>Customer: 8. Decrypt receipt data Customer->>Customer: 9. Merge customer details into UBL Customer->>Customer: 10. Calculate final hash (RFC8785 + SHA-256) Customer->>Cheqi: 11. Submit final hash Cheqi->>Webhook: 12. Webhook: receipt.finalized (final hash) Webhook->>Webhook: 13. Verify: decrypt → merge → hash → compare ``` ## The Complete Flow ### 1. Customer Identification When a customer makes a payment, identify them using payment details: #### Card Payment ```java Java IdentificationDetails customer = IdentificationDetails.builder() .paymentType(PaymentType.CARD_PAYMENT) .cardDetails(CardDetails.builder() .paymentAccountReference("PAR123456789") // From payment terminal .cardProvider(CardProvider.VISA) .build()) .build(); ``` ```csharp C# var customer = IdentificationDetails.Builder() .PaymentType(PaymentType.CARD_PAYMENT) .CardDetails(CardDetails.Builder() .Par("PAR123456789") // From payment terminal .CardProvider(CardProvider.VISA) .Build()) .Build(); ``` #### IBAN Payment ```java Java IdentificationDetails customer = IdentificationDetails.builder() .paymentType(PaymentType.IBAN_PAYMENT) .iban("NL91ABNA0417164300") .build(); ``` ```csharp C# var customer = IdentificationDetails.Builder() .PaymentType(PaymentType.IBAN_PAYMENT) .PaymentAccountDetails(PaymentAccountDetails.Builder() .Iban("NL91ABNA0417164300") .Build()) .Build(); ``` #### Email Fallback ```java Java IdentificationDetails customer = IdentificationDetails.builder() .paymentType(PaymentType.CARD_PAYMENT) .cardDetails(CardDetails.builder() .paymentAccountReference("PAR123456789") .cardProvider(CardProvider.VISA) .build()) .customerEmail("customer@example.com") // Fallback if not found .build(); ``` ```csharp C# var customer = IdentificationDetails.Builder() .PaymentType(PaymentType.CARD_PAYMENT) .CardDetails(CardDetails.Builder() .Par("PAR123456789") .CardProvider(CardProvider.VISA) .Build()) .RecipientEmail("customer@example.com") // Fallback if not found .Build(); ``` Payment Account Reference (PAR) **Payment Account Reference (PAR)** is a unique, tokenized identifier provided by payment terminals. It's more privacy-friendly than card numbers. ### 2. Receipt Template Generation Create a UBL-compliant receipt template from your transaction data: ```java Java ReceiptTemplateRequest receipt = ReceiptTemplateRequest.builder() .documentNumber("INV-2024-001") .issueDate(Instant.now()) .currency("EUR") .totalAmount(new BigDecimal("121.00")) .totalTaxAmount(new BigDecimal("21.00")) // Add products .addProduct(Product.builder() .name("Coffee") .quantity(2.0) .unitCode(UnitCode.ONE) .unitPrice("5.00") .subtotal("10.00") .total("12.10") .addTax(21.0, "VAT", "2.10") .build()) // Add tax breakdown .addTax(Tax.builder() .rate(21.0) .type("VAT") .taxableAmount("10.00") .amount("2.10") .label("VAT 21%") .build()) .build(); ``` ```csharp C# var receipt = ReceiptTemplateRequest.Builder() .DocumentNumber("INV-2024-001") .IssueDate(DateTimeOffset.UtcNow) .Currency("EUR") .InvoiceSubtotal(10.00m) .TotalBeforeTax(10.00m) .TotalTaxAmount(2.10m) .TotalAmount(12.10m) // Add products .AddProduct(Product.Builder() .Name("Coffee") .Quantity(2) .BaseQuantity(1.0) .UnitCode(UnitCode.ONE) .UnitPrice(5.00m) .Subtotal(10.00m) .Total(12.10m) .AddTax(21.0, "VAT", 2.10m) .Build()) // Add tax breakdown .AddTax(Tax.Builder() .Rate(21.0) .Type("VAT") .Amount(2.10m) .TaxableAmount(10.00m) .Label("VAT 21%") .Build()) .Build(); ``` ### 3. Hybrid Encryption Cheqi uses **hybrid encryption** for maximum security and performance: 1. **Generate AES Key** - A unique **AES-256** key is generated for each receipt 2. **Encrypt Receipt Data** - The receipt JSON is encrypted with **AES-256-GCM** (authenticated encryption) 3. **Encrypt AES Key** - The AES key is encrypted with each device's **RSA-2048 public key** 4. **Send to Backend** - Encrypted receipt + encrypted keys are sent to Cheqi **Why Hybrid Encryption?** - 🔐 **End-to-End Security** - Only customer devices can decrypt - ⚡ **Performance** - AES is fast for large data - 🔑 **Forward Secrecy** - Unique keys per receipt - 📱 **Multi-Device** - Encrypt once, deliver to all devices ### 4. Delivery & Decryption Once encrypted receipts reach Cheqi, a **two-phase delivery process** begins: #### Phase 1: Immediate Delivery (receipt.created) **For Customer Devices:** 1. **Push Notification** sent to customer's devices 2. **Device Downloads** encrypted receipt (2 parts: receipt content + customer details) 3. **RSA Decryption** of AES keys using device's private key 4. **AES Decryption** of both receipt content and customer details 5. **Merge** customer details into UBL receipt structure (`accountingCustomerParty`) 6. **Display** complete receipt in Cheqi app **For Webhook Recipients (Third-Party Apps):** 1. **Webhook Event** `receipt.created` sent immediately 2. Payload contains: - `encryptedReceipt` - Receipt content encrypted with webhook's public key - `encryptedCustomerDetails` - Customer data encrypted separately - `receiptId` - Unique receipt identifier - `createdAt` - Timestamp Privacy by Design **Privacy by Design:** Receipt content and customer details are encrypted separately. The POS system never sees customer details, and Cheqi servers cannot decrypt any data. Only authorized devices and webhook recipients can decrypt. #### Phase 2: Finalization (receipt.finalized) After the customer's device processes the receipt: 1. **Device merges** customer details into the UBL structure 2. **Canonicalization** - Receipt JSON is canonicalized using RFC8785 3. **Hash Calculation** - SHA-256 hash of canonical JSON 4. **Submit to Cheqi** - Device sends final hash to backend 5. **Webhook Event** `receipt.finalized` sent to all webhook recipients **Finalized Webhook Payload:** ```json { "event": "RECEIPT_FINALIZED", "receiptId": "abc123", "finalHash": "a3f5d8c2e1b4...", "finalizedAt": "2026-01-26T20:05:23Z", "deviceId": "device-uuid" } ``` **Webhook Recipients Can Now:** - **Verify** their hash matches `finalHash` (integrity proof) Important **Important:** Webhook recipients can **decrypt and use the receipt immediately** when they receive `receipt.created` in Phase 1. They don't need to wait for `receipt.finalized`. The finalized event is only needed for integrity verification - to confirm the receipt hash matches what the customer's device calculated. Why Two Phases? **Why Two Phases?** This ensures webhook recipients get data immediately while still being able to verify the receipt's integrity against the hash calculated by the customer's device. ## One-Method Integration The SDK provides a single method that handles the entire flow: ```java Java import com.cheqi.sdk.CheqiSDK; import com.cheqi.sdk.models.*; import com.cheqi.sdk.receipt.ReceiptResult; // Initialize SDK CheqiSDK sdk = CheqiSDK.builder() .apiEndpoint(Environment.PRODUCTION) .apiKey(System.getenv("CHEQI_API_KEY")) .build(); // Process complete receipt (one call does everything) ReceiptResult result = sdk.getReceiptService() .processCompleteReceipt( identificationDetails, // Customer identification receiptRequest // Receipt data ); // Check result if (result.isSuccess()) { System.out.println("✅ Receipt delivered to " + result.getReceiptCount() + " devices"); } else if (result.isCustomerNotFound()) { System.out.println("⚠️ Customer not found - prompt for email"); } else { System.out.println("❌ Failed: " + result.getMessage()); } ``` ```csharp C# using Cheqi.Sdk; using Cheqi.Sdk.Config; using Cheqi.Sdk.Models; // Initialize SDK var sdk = CheqiSdk.Builder() .ApiEndpoint(Environment.Production) .ApiKey(Environment.GetEnvironmentVariable("CHEQI_API_KEY")) .Build(); // Process complete receipt (one call does everything) var result = await sdk.ReceiptService.ProcessCompleteReceiptAsync( identificationDetails, // Customer identification receiptRequest // Receipt data ); // Check result if (result.IsSuccess) { Console.WriteLine($"✅ Receipt delivered to {result.ReceiptCount} devices"); } else if (!result.RecipientsFound) { Console.WriteLine("⚠️ Customer not found - prompt for email"); } else { Console.WriteLine($"❌ Failed: {result.ErrorMessage}"); } ``` **This single method:** - ✅ Matches the customer - ✅ Generates the receipt template - ✅ Encrypts for all devices - ✅ Sends to Cheqi backend - ✅ Returns delivery status ## Email Fallback - We Handle Everything When a customer isn't registered with Cheqi, **we automatically generate a professional PDF receipt and send it via email**. No additional work required from you: ✅ **Automatic PDF generation** - We format your receipt data into a beautiful PDF ✅ **Email delivery** - We handle the entire email sending process ✅ **Professional design** - Branded, clean, and professional-looking receipts ✅ **No extra code** - Just include the email address, we do the rest **You don't need to:** - ❌ Generate PDFs yourself - ❌ Set up email servers - ❌ Design email templates - ❌ Handle delivery failures ```java Java IdentificationDetails customer = IdentificationDetails.builder() .paymentType(PaymentType.CARD_PAYMENT) .cardDetails(CardDetails.builder() .paymentAccountReference("PAR123456789") .cardProvider(CardProvider.VISA) .build()) .customerEmail("customer@example.com") // Include email .build(); ReceiptResult result = sdk.getReceiptService() .processCompleteReceipt(customer, receiptRequest); if (result.isDeliveredViaEmail()) { System.out.println("📧 Receipt sent to: " + result.getEmailAddress()); } ``` ```csharp C# var customer = IdentificationDetails.Builder() .PaymentType(PaymentType.CARD_PAYMENT) .CardDetails(CardDetails.Builder() .Par("PAR123456789") .CardProvider(CardProvider.VISA) .Build()) .RecipientEmail("customer@example.com") // Include email .Build(); var result = await sdk.ReceiptService.ProcessCompleteReceiptAsync( customer, receiptRequest ); if (result.DeliveryMethod == DeliveryMethod.Email) { Console.WriteLine($"📧 Receipt sent to: {result.EmailAddress}"); } ``` **Automatic Delivery Logic:** - ✅ **Customer found** → Encrypted digital receipt to Cheqi app (email ignored) - ✅ **Customer not found + email provided** → Professional PDF receipt via email (we handle everything) - ❌ **Customer not found + no email** → `isCustomerNotFound() = true` (prompt customer for email) Complete Solution **Complete solution:** Whether your customer uses Cheqi or not, we ensure they receive their receipt. You send us the data once, and we handle all delivery scenarios. ## Webhook Integration for Third-Party Apps Third-party applications (accounting software, expense management tools, etc.) can receive real-time receipt notifications through webhooks. ### Subscribing to Webhooks **Step 1: OAuth Authorization** First, obtain OAuth permission from a merchant: ```java // Merchant authorizes your app via OAuth flow // You receive an access token with 'read_receipts' scope ``` **Step 2: Create Webhook Subscription** ```bash POST /webhook/subscription Authorization: Bearer {oauth_access_token} Content-Type: application/json { "name": "My Accounting System", "events": ["RECEIPT_CREATED", "RECEIPT_FINALIZED", "CREDIT_NOTE_CREATED"], "notificationUrl": "https://your-app.com/webhooks/cheqi" } ``` **Available Events:** - `RECEIPT_CREATED` - Immediate notification with encrypted receipt data - `RECEIPT_FINALIZED` - Sent when customer's device submits final hash - `CREDIT_NOTE_CREATED` - When a credit note is issued - `RETURN_REQUESTED` - When customer requests a return ### Receiving Webhooks #### Event 1: receipt.created Sent immediately when a receipt is generated: ```json { "event": "RECEIPT_CREATED", "companyId": "uuid", "receiptId": "abc123", "createdAt": "2026-01-26T20:00:00Z", "encryptedReceipt": { "encryptedReceipt": "base64...", "encryptedSymmetricKey": "base64...", "encryptedCustomerDetails": "base64...", "encryptedCustomerAesKey": "base64..." } } ``` **What to do:** 1. Decrypt the receipt content using your private key 2. Decrypt the customer details using your private key 3. Store both parts (don't merge yet - wait for finalization) 4. Mark receipt as "pending verification" #### Event 2: receipt.finalized Sent when the customer's device calculates the final hash: ```json { "event": "RECEIPT_FINALIZED", "receiptId": "abc123", "finalHash": "a3f5d8c2e1b4f7a9...", "finalizedAt": "2026-01-26T20:05:23Z", "deviceId": "device-uuid" } ``` **What to do:** 1. Retrieve your stored receipt parts 2. Merge customer details into UBL structure (`accountingCustomerParty` field) 3. Canonicalize the merged JSON using the SDK's `Canonicalizer` and RFC8785 4. Calculate SHA-256 hash 5. **Verify** your hash matches `finalHash` 6. Mark receipt as "verified" if hashes match ### Receipt Structure & Merging **Before Merge (Receipt Content):** ```json { "id": "INV-001", "issueDate": "2026-01-26", "accountingSupplierParty": { /* merchant info */ }, "accountingCustomerParty": null, // Empty initially "legalMonetaryTotal": { /* totals */ }, "purchaseReceiptLines": [ /* items */ ] } ``` **Customer Details (Separate):** ```json { "partyName": { "name": "John Doe" }, "postalAddress": { /* address */ }, "contact": { "electronicMail": "john@example.com" } } ``` **After Merge (Complete Receipt):** ```json { "id": "INV-001", "issueDate": "2026-01-26", "accountingSupplierParty": { /* merchant info */ }, "accountingCustomerParty": { // Merged here "partyName": { "name": "John Doe" }, "postalAddress": { /* address */ }, "contact": { "electronicMail": "john@example.com" } }, "legalMonetaryTotal": { /* totals */ }, "purchaseReceiptLines": [ /* items */ ] } ``` ### Hash Verification Example ```java Java // 1. Decrypt both parts String receiptJson = decryptReceipt(encryptedReceipt, yourPrivateKey); String customerJson = decryptCustomerDetails(encryptedCustomerDetails, yourPrivateKey); // 2. Parse JSON JSONObject receipt = new JSONObject(receiptJson); JSONObject customer = new JSONObject(customerJson); // 3. Merge customer into receipt receipt.put("accountingCustomerParty", customer); // 4. Canonicalize using SDK's RFC8785 Canonicalizer String canonicalJson = RFC8785Canonicalizer.canonicalize(receipt.toString()); // 5. Calculate SHA-256 hash String calculatedHash = SHA256.hash(canonicalJson); // 6. Verify against finalHash from webhook if (calculatedHash.equals(finalHash)) { System.out.println("✅ Receipt verified!"); } else { System.out.println("❌ Hash mismatch - receipt may be tampered"); } ``` ```csharp C# // 1. Decrypt both parts var receiptJson = DecryptReceipt(encryptedReceipt, yourPrivateKey); var customerJson = DecryptCustomerDetails(encryptedCustomerDetails, yourPrivateKey); // 2. Parse JSON var receipt = JsonSerializer.Deserialize(receiptJson); var customer = JsonSerializer.Deserialize(customerJson); // 3. Merge customer into receipt (create mutable dictionary) var receiptDict = JsonSerializer.Deserialize>(receiptJson); var customerDict = JsonSerializer.Deserialize>(customerJson); receiptDict["accountingCustomerParty"] = customerDict; // 4. Canonicalize using SDK's RFC8785 Canonicalizer var canonicalJson = Rfc8785Canonicalizer.Canonicalize( JsonSerializer.Serialize(receiptDict) ); // 5. Calculate SHA-256 hash var calculatedHash = Sha256Hasher.Hash(canonicalJson); // 6. Verify against finalHash from webhook if (calculatedHash == finalHash) { Console.WriteLine("✅ Receipt verified!"); } else { Console.WriteLine("❌ Hash mismatch - receipt may be tampered"); } ``` Security **Security:** Always verify the HMAC signature on webhook payloads using your webhook secret before processing. This ensures the webhook actually came from Cheqi. ### Webhook Security Each webhook includes an HMAC signature in the `X-Cheqi-Signature` header: ```java Java String signature = request.getHeader("X-Cheqi-Signature"); String payload = request.getBody(); // Verify signature Mac hmac = Mac.getInstance("HmacSHA256"); SecretKeySpec key = new SecretKeySpec(webhookSecret.getBytes(), "HmacSHA256"); hmac.init(key); String calculatedSignature = Base64.getEncoder().encodeToString( hmac.doFinal(payload.getBytes()) ); if (!signature.equals(calculatedSignature)) { throw new SecurityException("Invalid webhook signature"); } ``` ```csharp C# var signature = Request.Headers["X-Cheqi-Signature"]; var payload = await new StreamReader(Request.Body).ReadToEndAsync(); // Verify signature using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(webhookSecret)); var calculatedSignature = Convert.ToBase64String( hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)) ); if (signature != calculatedSignature) { throw new SecurityException("Invalid webhook signature"); } ``` ## Receipt Data Model Cheqi uses the **UBL (Universal Business Language)** standard for receipts: ### Required Fields | Field | Type | Description | | --- | --- | --- | | `documentNumber` | String | Invoice/receipt number (e.g., "INV-001") | | `issueDate` | Instant | When the receipt was issued | | `currency` | String | ISO 4217 code (e.g., "EUR", "USD") | | `totalAmount` | BigDecimal | Total including tax | | `totalTaxAmount` | BigDecimal | Total tax amount | ### Products Each product line item includes: ```java Java Product.builder() .name("Product Name") // Required .quantity(1.0) // Required .unitCode(UnitCode.ONE) // Required (see unit codes) .unitPrice("10.00") // Required .subtotal("10.00") // Required (before tax) .total("12.10") // Required (after tax) .sku("SKU-123") // Optional .brand("Brand Name") // Optional .addTax(21.0, "VAT", "2.10") // Tax breakdown .build() ``` ```csharp C# Product.Builder() .Name("Product Name") // Required .Quantity(1) // Required .BaseQuantity(1.0) // Required (1.0 for simple items) .UnitCode(UnitCode.ONE) // Required (see unit codes) .UnitPrice(10.00m) // Required .Subtotal(10.00m) // Required (before tax) .Total(12.10m) // Required (after tax) .Identifier("SKU-123") // Optional .Brand("Brand Name") // Optional .AddTax(21.0, "VAT", 2.10m) // Tax breakdown .Build() ``` ### Tax Breakdown Receipt-level tax summary: ```java Java Tax.builder() .rate(21.0) // Tax percentage .type("VAT") // Tax type .taxableAmount("10.00") // Amount before tax .amount("2.10") // Tax amount .label("VAT 21%") // Display label .build() ``` ```csharp C# Tax.Builder() .Rate(21.0) // Tax percentage .Type("VAT") // Tax type .TaxableAmount(10.00m) // Amount before tax .Amount(2.10m) // Tax amount .Label("VAT 21%") // Display label .Build() ``` ## Unit Codes Use standardized unit codes for products: | Category | Examples | | --- | --- | | **Count** | `ONE`, `EACH`, `PAIR`, `DOZEN` | | **Weight** | `KILOGRAM`, `GRAM`, `POUND` | | **Volume** | `LITER`, `MILLILITER`, `GALLON_US` | | **Length** | `METER`, `CENTIMETER`, `FOOT` | | **Time** | `HOUR`, `DAY`, `WEEK`, `MONTH` | | **Packaging** | `BOX`, `BOTTLE`, `CAN`, `BAG` | **Note:** The SDK provides all standard UBL unit codes as enums. ## Receipt States Track receipt delivery status: | State | Description | | --- | --- | | `SUCCESS` | Delivered to customer's app | | `EMAIL_SENT` | Sent via email (customer not found) | | `CUSTOMER_NOT_FOUND` | No match found, no email provided | | `FAILED` | Processing error occurred | ## Best Practices ### Always Include Email When Available Provide email as fallback to ensure receipt delivery: ```java // ✅ GOOD - Email fallback IdentificationDetails.builder() .paymentType(PaymentType.CARD_PAYMENT) .cardDetails(...) .customerEmail("customer@example.com") .build(); ``` ### Use Payment Account Reference (PAR) PAR is more privacy-friendly than card numbers: ```java // ✅ GOOD - Use PAR from terminal .paymentAccountReference("PAR123456789") // ❌ AVOID - Don't use card numbers .cardNumber("4111111111111111") // Not supported ``` ### Validate Amounts Before Sending Ensure totals match: ```java BigDecimal subtotal = products.stream() .map(Product::getSubtotal) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal taxTotal = products.stream() .map(Product::getTaxAmount) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal total = subtotal.add(taxTotal); assert total.equals(receiptRequest.getTotalAmount()); ``` ### Handle Errors Gracefully Implement retry logic for transient failures: ```java int maxRetries = 3; for (int i = 0; i < maxRetries; i++) { try { ReceiptResult result = sdk.getReceiptService() .processCompleteReceipt(customer, receipt); if (result.isSuccess()) { break; } } catch (CheqiSDKException e) { if (i == maxRetries - 1) { // Log error and notify support logger.error("Failed to send receipt after {} retries", maxRetries, e); } Thread.sleep(1000 * (i + 1)); // Exponential backoff } } ``` ## Next Steps - **[SDK Integration](/sdk/java)** - Integrate the Java SDK into your application - **[.NET SDK](/sdk/dotnet)** - Integrate the .NET SDK into your application - **[Authentication](/authentication/overview)** - API Keys and OAuth 2.0