Skip to main content

Paper Receipt Android SDK 2.5.0

2.5.0 builds on 2.4.1 and pairs with Activation SDK 1.2.0, the same version as 2.4.x. Its headline changes:

  • UPC product lookup is public API again. Look up a product by UPC from Kotlin or Java, with no extra module.
  • Trip Resolution. When a US scan ends with no merchant identified, the SDK asks the Trip Resolution API as a last resort, so fewer receipts come back without a store.
  • Merchant-detection signals. ScanResults.merchantDetection() shows which detectors identified the store.
  • Account linking tells "sign in again" apart from a generic failure. A new REAUTHENTICATION_REQUIRED error code.
warning

This release contains a breaking change: com.microblink.ProductLookup is no longer public. See Breaking changes. It also ships with a known receipt-recognition regression — read Known issues before you upgrade.

Installation​

Bump the BOM to 2.5.0. If you also use the Activation SDK, keep its BOM at 1.2.0 (see the Android SDK Version Matrix).

build.gradle.kts
dependencies {
implementation(platform("com.microblink.blinkreceipt:blinkreceipt-bom:2.5.0"))
implementation("com.microblink.blinkreceipt:blinkreceipt-recognizer")
implementation("com.microblink.blinkreceipt:blinkreceipt-camera-ui")

// Only if you use Activation
implementation(platform("com.actualplatform:activation-bom:1.2.0"))
implementation("com.actualplatform:activation")
}

The build requirements are unchanged from 2.4.1: compileSdk 37 (or higher) and minSdk 24. Your app's targetSdk does not need to change. As with every release, recompile your app against the new version.

tip

The SDK ships its own consumer ProGuard/R8 rules and is safe under R8 full mode.

Breaking changes​

com.microblink.ProductLookup is no longer public​

com.microblink.ProductLookup was an internal product-intelligence lookup interface, unrelated to UPC lookup, and was never intended for client use. It is no longer public, and the name now belongs to the new com.microblink.core.ProductLookup model returned by UPC product lookup. Remove any references to the old interface.

What's new​

  • UPC product lookup. RecognizerClient().lookup(upc) returns a Task<ProductLookup>, from Kotlin or Java; pass an optional CancellationToken to cancel the request. See UPC product lookup.
  • Trip Resolution as a last-resort merchant lookup. See Trip Resolution.
  • ScanResults.merchantDetection(). The merchant-detection signals behind a scan's merchant — phone, logo, text, tax ID, CSV, product-search, long-tail, and Trip Resolution matches — so a misidentified store can be traced to the detector responsible. It is null when no detector produced a signal. These signals are not yet forwarded to the Activation SDK. See MerchantDetection.
  • Account linking: REAUTHENTICATION_REQUIRED error code (blinkreceipt-account-linking). When a retailer requires the user to sign in again during a background or order-fetch session, the error is now reported as REAUTHENTICATION_REQUIRED (code 1010, matching iOS) instead of INTERNAL_ERROR. Like INTERNAL_ERROR, it is non-recoverable and opens no WebView — prompt the user to relink the account.

UPC product lookup​

Look up a product by its UPC. The returned task never fails: a network or server error, including a timeout, is reported in ProductLookup.message, and an unknown UPC returns an empty ProductLookup. It is cancelled only if you cancel the CancellationToken you passed.

import com.google.android.gms.tasks.CancellationTokenSource
import com.microblink.RecognizerClient
import kotlinx.coroutines.tasks.await

// Suspends; call from a coroutine. Cancelling the coroutine cancels the request.
val cancellation = CancellationTokenSource()
val product = RecognizerClient().lookup("012345678905", cancellation.token).await(cancellation)

if (product.message == null) {
showProduct(product.name, product.brand, product.rewarded)
}

ProductLookup is Parcelable and carries the nullable upc, name, url, brand, and message, plus rewarded and brandParticipates (isRewarded() and isBrandParticipates() from Java), which default to false. lookup returns a Google Play services Task<ProductLookup>; the token is optional (lookup(upc) also works). blinkreceipt-recognizer exposes play-services-tasks, so Task and CancellationToken resolve without extra dependencies. To await(cancellation) from Kotlin, add org.jetbrains.kotlinx:kotlinx-coroutines-play-services to your app. This replaces com.microblink.barcode.RecognizerClient, which was removed with the barcode module. See ProductLookup.

Trip Resolution​

When a scan ends with no merchant — no detector settled on a winner and the receipt has no merchant name — the SDK calls the Trip Resolution API. It runs only after the other merchant lookups have come up empty, so it is used only when every other detector failed.

  • What you get. A merchant name found in time fills ScanResults.merchantName(), with merchantSource() set to "TRIP_RESOLUTION". Its store fills retailerId() only when no store was already detected and the SDK recognizes it; otherwise retailerId() stays UNKNOWN. Duplicate detection and promotion validation wait for the call, so they use the resolved merchant.
  • How long it takes. The request is abandoned 4 seconds after it is issued. With the bundled camera UI or a direct RecognizerView, it runs when you call finishedScanning(), adding at most 4 seconds there. A failed, empty, or late response leaves the merchant null, with no error reported.
  • Where it applies. US scans only, and subject to your license's networking restrictions, like the SDK's other ancillary lookups.
  • Network allow-lists. The call goes to https://api.actualplatform.com/trip-resolution/v1/api/trip_resolution/resolve, not to the licensing host. If your app restricts outbound traffic with an allow-list, allow api.actualplatform.com.

To run it on a preliminary snapshot instead — for the snapshot you will treat as final — call the new RecognizerView.preliminaryResults(true). The snapshot reports PreliminaryResult.tripResolutionRequested() == true; the no-argument preliminaryResults() is unchanged. Request it once: each request can make a network call and wait up to 4 seconds for it. If the pass can't run, or Trip Resolution doesn't apply (a non-US scan, or networking disabled by your license), you receive an empty snapshot still flagged tripResolutionRequested() — keep the snapshot you already have.

Activation scans in the camera UI​

The Activation SDK submits the settled preliminary snapshot rather than the final pass, so for a scan that still has no merchant, the camera UI requests one extra preliminary pass with Trip Resolution before handing the snapshot over. In testing this added about 3 seconds to the hand-off of a merchant-less scan; it is bounded at 8 seconds, after which the snapshot is handed over without Trip Resolution. It is skipped when the snapshot already has a merchant or the scan fails the receipt check, and non-US scans are handed over with no added wait.

Known issues​

Receipt-recognition regression in 2.5.0

The receipt data extraction update in this release undoes, fully or partly, several receipt-recognition fixes from earlier releases. It was shipped knowingly, and a fix is pending.

  • Native crash. A receipt whose subtotal plus taxes rounds to a non-zero amount under $0.10 crashes the scan while its total is checked.
  • Extraction accuracy may regress for tax and subtotal, return-receipt detection (ScanResults.isReturn()), longer store-and-register transaction IDs, auth codes, Kroger and Barnes & Noble receipts, Walmart weighted items with left-indented prices, German discount formats, and a Canadian date format.

If these receipts matter to your app, stay on 2.4.1 until the fix ships.

Changed​

More accurate receipt data extraction​

Merchant, date, total, and product details are recognized more reliably. No API changes. See also Known issues.

  • Dates. Three-letter months may start lowercase (15-jun-2026). Full-line date-times may omit separators or the year, have a one-digit hour, seconds, or a leading "at", and end in A, P, or pM — for example Jul6202611:46AM, Jul 23 1:50pM, and Jul 17 2026 at 7:26:12 AM.
  • Transaction IDs. CHK values (for example on Starbucks receipts), Ticket:, and Order Id: are now recognized as transaction IDs, and validation is tighter to cut false positives.
  • Duplicate detection. The auth code used for duplicate detection is now the one attached to the last payment method, not the last value on the receipt that looks like an auth code.
  • Line items. Once a receipt's subtotal equals the sum of its products and discounts, later frames that add products are ignored, so an already-balanced receipt no longer gains duplicate line items.
  • Performance. Merging frames into a scan's results is faster on long receipts.

Fixed​

  • Native crash while exporting scan results with certain coupon layouts. A receipt where a coupon's linked description line held no product or coupon description no longer crashes.
  • The camera opens on Android 7.0–9 when only CAMERA is granted. CameraView also required WRITE_EXTERNAL_STORAGE on API 28 and below, so apps that requested only CAMERA got onPermissionDenied() and never saw a preview. CAMERA is now the only permission checked on every supported API level; if you added the storage permission as a workaround, you can drop it.
  • Merchant-detection data sent with the receipt. The receipt's own phone number is no longer overwritten by the phone-match source ("Yelp" / "DB"), which now has its own phone_match_source field, and long-tail merchant matches are now included.
  • Account linking: script errors 400 and 1005 are reported as PARSING_FAILURE again (blinkreceipt-account-linking). Since 2022 they had been reported as INTERNAL_ERROR. If your app special-cases INTERNAL_ERROR, these sessions now arrive as PARSING_FAILURE; both are non-recoverable and open no WebView.

Upgrade checklist​

  • Read Known issues and decide whether to upgrade now or stay on 2.4.1.
  • Bump the Paper Receipt BOM to 2.5.0. If you use Activation, keep the Activation BOM at 1.2.0.
  • Remove any references to com.microblink.ProductLookup. For UPC lookups, use RecognizerClient().lookup(upc, token) (from Kotlin, await(cancellation) it via kotlinx-coroutines-play-services).
  • If your app allow-lists outbound hosts, allow api.actualplatform.com.
  • If you use account linking, handle REAUTHENTICATION_REQUIRED by prompting the user to relink, and review any handling of INTERNAL_ERROR versus PARSING_FAILURE.
  • If you added WRITE_EXTERNAL_STORAGE only for the camera, remove it.