in_app_purchase_amazon

Design draft · Amazon Appstore implementation of the Flutter in_app_purchase federated plugin · 2 September 2026 · status: proposal, not yet implemented
Verdict: feasible. The platform-interface package was built for exactly this kind of third-party implementation, Flutter (since 3.27) lets an app swap out the endorsed in_app_purchase_android for a non-endorsed one, and the Amazon Appstore SDK is on Maven Central with a small, stable, well-documented API. It will not be endorsed upstream and there are a handful of semantic mismatches that need explicit decisions — so treat it as a community package with a clearly documented lossy edge or two, not as a drop-in Play Billing twin.
Contents
  1. What the review found
  2. Why it is not trivial
    1. Feature map: Play / App Store / Amazon
  3. Packaging and registration
  4. Native layer (Kotlin + Pigeon)
  5. Dart layer
  6. Semantic mapping
  7. Open decisions and known gaps
  8. Testing
  9. Milestones and effort
  10. Risks
  11. Sources

1. What the review found

1.1 The Flutter side

1.2 The Amazon side

1.3 Prior art

PackageWhat it isRelevance
amazon_iap, flutter_amazon_iapThin 1:1 wrappers over PurchasingService with response streamsUseful as reference for the native bridge; neither implements InAppPurchasePlatform, so apps need store-specific code paths.
in_app_purchase_amazon (predatorx7, GitHub)Early scaffold, "a new Flutter plugin project"Occupies the natural package name; check pub.dev availability before publishing (fallback: in_app_purchase_amazon_appstore).
purchases_flutter (RevenueCat)Hosted-backend SDK with Amazon supportProves the mapping is workable; not an option if you don't want a third-party backend.
flutter_inapp_purchase (OpenIAP)Alternative unified IAP plugin; Amazon sponsors the OpenIAP specWorth checking whether Amazon Appstore support has landed there — if it has, it is the only competitor for "one API, three stores" in Flutter.

2. Why it is not trivial

Request/response, not futures

The SDK is one static listener plus broadcast intents. The Dart side must correlate by RequestId and keep pending completers; the native side must register the listener with the application context once and survive activity recreation.

No numeric price, no currency

Product.getPrice() is a localized string ("$5.00"). ProductDetails.rawPrice and currencyCode are required fields with no exact source. Needs a documented best-effort strategy.

FAILED is ambiguous

Amazon's PurchaseResponse.FAILED "can simply mean the user canceled". The interface distinguishes canceled from error; Amazon does not.

Fulfillment ≠ acknowledge

notifyFulfillment with anything but FULFILLED refunds. completePurchase() maps cleanly to FULFILLED; the refund-path needs a platform-addition method.

Lifecycle expectations

Amazon expects getUserData() and getPurchaseUpdates(false) on every onResume. The interface has no lifecycle hook, so the plugin must do this itself and feed results into purchaseStream.

Subscription model differs

Parent SKU vs. term SKUs, receipts carry termSku; no general upgrade/downgrade API (modifySubscription is partner-gated). ChangeSubscriptionParam has no equivalent.

Duplicate receipts by design

Device-cache resets can redeliver receipts. The plugin must be idempotent and apps must dedupe on receiptId.

Build-time store choice

Override happens at pub level, not per Gradle flavor. One Flutter project produces either the Play or the Amazon binary unless you use the two-package layout below.

2.1 Feature map — what an app written against in_app_purchase gets on each store

Read this as "what does my existing in_app_purchase code do when the platform underneath is Amazon". The Play and App Store columns describe the endorsed implementations as of 3.3.0; the Amazon column describes the package proposed in this document, not the raw Amazon SDK.

✔ works 1:1 ◐ works with known limitation ✘ not available — not applicable / store has no such concept
Feature (interface / addition)Google PlayApple App StoreAmazon (new)Comment on the Amazon column
Core purchase flow
isAvailable()✔✔✔Backed by getUserData(); false when no Amazon account is signed in or the device has no Appstore.
queryProductDetails() — id, title, description, localized price string✔✔✔Chunked to the SDK's per-call SKU cap. notFoundIDs ← unavailableSkus.
ProductDetails.rawPrice + currencyCode (numeric, ISO 4217)✔✔◐Amazon only gives a localized string. Parsed + inferred from marketplace; flagged with priceIsEstimated. The one genuinely lossy field.
Consumables — buyConsumable() + completePurchase()✔✔✔purchase(sku) + notifyFulfillment(FULFILLED).
buyConsumable(autoConsume: true)✔—◐Implemented as auto-fulfil on success. Works, but forfeits Amazon's redelivery-until-fulfilled safety net; documented.
Non-consumables / entitlements — buyNonConsumable()✔✔✔Amazon ENTITLED type. ALREADY_PURCHASED surfaces as an error plus a restore.
Subscriptions — purchase, renewals visible via stream✔✔◐Works, but the app must query and buy the term SKU (parent SKU has no price). A different catalogue-ID convention, not a functional gap.
completePurchase() (acknowledge / finish)✔✔✔Idempotent; safe to call again on restored receipts.
restorePurchases() — entitlements & subscriptions✔✔✔getPurchaseUpdates(true), paginated to completion, emitted as restored.
restorePurchases() — consumables◐✘◐Only unfulfilled consumables, and only from the purchasing device. Same shape of limitation as Play.
Unfinished purchases re-emitted on launch/resume✔✔✔Plugin auto-syncs on onResume; unfulfilled receipts arrive as purchased.
countryCode()✔✔✔SDK ≥ 3.0.7 returns country of residence; falls back to marketplace.
Purchase status semantics
PurchaseStatus.pending (deferred / parental approval)✔✔◐Amazon Kids only, and only after enablePendingPurchases() via the addition. Approved result arrives through auto-sync.
Distinguish user cancel (canceled) from real failure (error)✔✔◐Amazon collapses both into FAILED. Mapped to canceled; raw status exposed on AmazonPurchaseDetails.
Refund / cancellation visible to the client◐◐✔Amazon receipts carry isCanceled/cancelDate and are redelivered; emitted as canceled. Arguably better than Play here.
Error delivery via PurchaseDetails.error (never stream error)✔✔✔Same contract as the Android implementation.
Subscription management
Upgrade / downgrade with proration (ChangeSubscriptionParam)✔—✘Amazon's modifySubscription() (tiered subs) is partner-gated. Param is ignored with a warning. Apple has no param either (subscription groups).
Free trial / introductory pricing info on the product✔✔✔Via AmazonProductDetails.product (freeTrialPeriod, promotions) — store-specific subtype on all three stores.
Price-change consent UI (showPriceConsentIfNeeded, queue delegate)✔✔—Amazon handles price changes by email/notification; there is no in-app hook.
Offer code / promo code redemption sheet✘✔✘No Amazon equivalent.
Signed promotional / win-back offers (Sk2PurchaseParam)✘✔—Amazon promotions are eligibility-based and applied automatically; nothing to pass at purchase time.
Obfuscated account id / applicationUserName✔✔✘Amazon has no purchase-time user tag; identity comes from getUserData().userId. Parameter accepted and ignored.
Verification & security
PurchaseVerificationData.serverVerificationData for backend checks✔✔◐Holds receiptId, but Amazon RVS also needs userId, which lives on AmazonPurchaseDetails. One extra field to forward.
Local / on-device signature check◐◐✘Amazon offers server-side RVS only. localVerificationData is the receipt JSON, not a signed blob.
Sandbox / test-environment detection✘✔✔Addition exposes isSandboxMode() (App Tester). Useful for pointing a backend at the RVS sandbox.
Server-to-server notifications (RTDN / ASSN / Amazon RTN)✔✔✔Outside the plugin on all three stores; noted for completeness.
Store extras (platform addition)
Refuse fulfilment / trigger refund from the client✘✘✔Amazon-only capability: notifyFulfillment(UNAVAILABLE | NOT_ELIGIBLE | EXISTING_PURCHASE) on the addition.
Product icon URL✘✘✔smallIconUrl on the wrapper.
Raw store objects reachable (billingClientPurchase / skPaymentTransaction / receipt)✔✔✔AmazonProductDetails.product, AmazonPurchaseDetails.receipt, plus rawJson passthrough.
Alternative billing / external links✔◐—No Amazon programme.
Integration & tooling
Zero-config registration (endorsed)✔✔◐One extra direct dependency (_default shim) and Flutter ≥ 3.27. No code changes.
App-side native config required✔ none✔ none◐PEM public key in assets/. Manifest entries are merged from the plugin.
Two stores in one binary with runtime switch——◐Mode B works technically; Amazon review may object to Play Billing classes in the APK.
Emulator / simulator testing without a real device◐✔✘Needs a Fire device (or any Android device) with Amazon App Tester installed. Dart mapping layer is unit-testable via Pigeon mocks.
Target devices going forward✔✔◐Fire tablets + Android-based Fire TV only; no non-Fire Android since Aug 2025, no Flutter on Vega OS.

Reading the map. Everything in the "core purchase flow" group is green or nearly so — an app that only queries products, buys, completes and restores will run unchanged on Amazon. The amber cells cluster in three places: price fidelity (one lossy field), status nuance (cancel vs. error, pending only for Kids), and the subscription-management extras that Play and Apple expose but Amazon either doesn't have or gates behind partner programmes. The red cells are almost all features that are store-specific on every store already (offer codes, proration, obfuscated ids), so an app written portably against the interface is unlikely to depend on them. The two places where Amazon is actually ahead — client-side refund path and cancellation visibility — go into the platform addition rather than being hidden.

3. Packaging and registration

Two packages, both in one repo. The split costs almost nothing and unlocks both deployment styles.

PackageContainsDeclares implements?
in_app_purchase_amazonKotlin plugin, Pigeon bridge, InAppPurchaseAmazonPlatform, wrappers, addition, example appNo. Native pluginClass only. Dart registration is explicit via InAppPurchaseAmazonPlatform.registerPlatform().
in_app_purchase_amazon_default~10 lines: a dartPluginClass whose registerWith() calls registerPlatform()Yes: implements: in_app_purchase, platforms: android. Adding it as a direct dependency triggers the Flutter-tool override and unregisters in_app_purchase_android for that build.

Mode A — dedicated Amazon build (recommended for v1)

# pubspec.yaml of the Amazon-flavoured app
dependencies:
  in_app_purchase: ^3.3.0
  in_app_purchase_amazon_default: ^0.1.0   # overrides in_app_purchase_android on Android

Requires Flutter ≥ 3.27. App code is unchanged: InAppPurchase.instance now talks to Amazon. This matches how Amazon submissions already work (separate PEM key asset, separate signing, usually a separate product flavor for Fire OS form factors).

Mode B — single binary, runtime switch

dependencies:
  in_app_purchase: ^3.3.0
  in_app_purchase_amazon: ^0.1.0   # no override; Play Billing stays registered

// main.dart
WidgetsFlutterBinding.ensureInitialized();
if (await InAppPurchaseAmazonPlatform.isAmazonInstall()) {   // installer == com.amazon.venezia, or --dart-define=STORE=amazon
  InAppPurchaseAmazonPlatform.registerPlatform();             // replaces the Play instance before first use
}

Works because InAppPurchasePlatform.instance is a plain setter and InAppPurchase.instance delegates lazily. Caveat: both SDKs ship in the APK. Google tolerates that; Amazon review historically frowns on Play dependencies in Fire OS submissions. Document it, don't default to it.

Plugin pubspec (core)

name: in_app_purchase_amazon
flutter:
  plugin:
    platforms:
      android:
        package: dev.yourorg.inapppurchaseamazon
        pluginClass: InAppPurchaseAmazonPlugin
dependencies:
  in_app_purchase_platform_interface: ^1.4.0
  plugin_platform_interface: ^2.1.0
dev_dependencies:
  pigeon: ^26.0.0

Android build contributions

4. Native layer (Kotlin + Pigeon)

Mirror in_app_purchase_android: one Pigeon file, one Kotlin plugin class, one listener class. No method channels by hand.

// pigeons/messages.dart
enum AmazonRequestStatus { successful, failed, notSupported, alreadyPurchased, invalidSku, pending }
enum AmazonProductType   { consumable, entitled, subscription }
enum AmazonFulfillmentResult { fulfilled, existingPurchase, notEligible, unavailable }

class PlatformUserData { late String userId; late String marketplace; String? countryCode; }
class PlatformPromotionPlan { late String promotionPrice; late String promotionPricePeriod; late int promotionPriceCycles; }
class PlatformPromotion { late String promotionType; late List<PlatformPromotionPlan> plans; }
class PlatformProduct {
  late String sku; late AmazonProductType productType;
  late String title; late String description; late String price; late String smallIconUrl;
  String? subscriptionPeriod; String? freeTrialPeriod;
  late List<PlatformPromotion> promotions;
  late String rawJson;
}
class PlatformReceipt {
  late String receiptId; late String sku; late AmazonProductType productType;
  late int purchaseDateMillis; int? cancelDateMillis; late bool isCanceled;
  String? termSku; int? deferredDateMillis; String? deferredSku;
  late String rawJson;
}
class PlatformUserDataResponse        { late String requestId; late AmazonRequestStatus status; PlatformUserData? userData; }
class PlatformProductDataResponse     { late String requestId; late AmazonRequestStatus status; late List<PlatformProduct> products; late List<String> unavailableSkus; }
class PlatformPurchaseResponse        { late String requestId; late AmazonRequestStatus status; PlatformUserData? userData; PlatformReceipt? receipt; }
class PlatformPurchaseUpdatesResponse { late String requestId; late AmazonRequestStatus status; PlatformUserData? userData; late List<PlatformReceipt> receipts; late bool hasMore; }

@HostApi()
abstract class AmazonBillingApi {
  void registerListener();                       // idempotent; app context
  String getUserData();                          // returns requestId
  String getProductData(List<String> skus);
  String getPurchaseUpdates(bool reset);
  String purchase(String sku);
  void notifyFulfillment(String receiptId, AmazonFulfillmentResult result);
  void enablePendingPurchases();
  bool isSandboxMode();                          // LicensingService.getAppstoreSDKMode() == SANDBOX
  String? installerPackageName();                // for Mode B detection
  void setAutoSyncOnResume(bool enabled);
}

@FlutterApi()
abstract class AmazonBillingCallbackApi {
  void onUserDataResponse(PlatformUserDataResponse r);
  void onProductDataResponse(PlatformProductDataResponse r);
  void onPurchaseUpdatesResponse(PlatformPurchaseUpdatesResponse r);
  void onPurchaseResponse(PlatformPurchaseResponse r);
}

Kotlin plugin responsibilities

5. Dart layer

lib/
  in_app_purchase_amazon.dart          // exports platform, addition, param, details types
  amazon_appstore_wrappers.dart        // typed wrappers (mirrors billing_client_wrappers)
  src/
    in_app_purchase_amazon_platform.dart
    in_app_purchase_amazon_platform_addition.dart
    amazon_product_details.dart        // AmazonProductDetails extends ProductDetails
    amazon_purchase_details.dart       // AmazonPurchaseDetails extends PurchaseDetails
    amazon_purchase_param.dart         // AmazonPurchaseParam extends PurchaseParam
    request_router.dart                // RequestId -> Completer / stream routing
    price_parser.dart                  // best-effort rawPrice + marketplace->currency
    messages.g.dart                    // pigeon output
class InAppPurchaseAmazonPlatform extends InAppPurchasePlatform {
  static void registerPlatform() {
    InAppPurchasePlatform.instance = InAppPurchaseAmazonPlatform();
    InAppPurchasePlatformAddition.instance = InAppPurchaseAmazonPlatformAddition(...);
  }
  static Future<bool> isAmazonInstall();   // installer == com.amazon.venezia || dart-define

  @override Stream<List<PurchaseDetails>> get purchaseStream;   // broadcast
  @override Future<bool> isAvailable();
  @override Future<ProductDetailsResponse> queryProductDetails(Set<String> identifiers);
  @override Future<bool> buyNonConsumable({required PurchaseParam purchaseParam});
  @override Future<bool> buyConsumable({required PurchaseParam purchaseParam, bool autoConsume = true});
  @override Future<void> completePurchase(PurchaseDetails purchase);
  @override Future<void> restorePurchases({String? applicationUserName});
  @override Future<String> countryCode();
}

class InAppPurchaseAmazonPlatformAddition extends InAppPurchasePlatformAddition {
  Future<AmazonUserData> getUserData();
  Future<void> notifyFulfillment(String receiptId, AmazonFulfillmentResult result); // the refund path
  Future<void> enablePendingPurchases();
  Future<bool> isSandboxMode();
  Future<List<AmazonReceipt>> getPurchaseUpdates({required bool reset}); // raw, paginated-to-completion
  void setAutoSyncOnResume(bool enabled);
}

class AmazonProductDetails extends ProductDetails {
  final AmazonProduct product;          // full wrapper incl. promotions, periods, icon
  final bool priceIsEstimated;          // true when rawPrice/currencyCode were inferred
}
class AmazonPurchaseDetails extends PurchaseDetails {
  final AmazonReceipt receipt;
  final String userId;                  // needed for RVS
  final String marketplace;
  final AmazonRequestStatus rawStatus;  // so apps can see FAILED vs. anything else
}

Request router

One Map<String, Completer> per response type keyed by requestId. If a response arrives with no matching completer (plugin-initiated auto-sync, or a response that outlived an engine restart) it is routed to purchaseStream when it carries receipts, otherwise dropped with a debug log. getPurchaseUpdates pagination (hasMore) is handled inside the router: it re-issues the call and accumulates until hasMore == false, then completes. Timeouts: none by default (Amazon can legitimately take a while while the parent approves a pending purchase); an optional per-call timeout is exposed on the addition.

6. Semantic mapping

Interface callAmazon call(s)Notes
isAvailable()registerListener() then getUserData()SUCCESSFUL → true. FAILED (not signed in / no Appstore) and NOT_SUPPORTED → false. Cache the user data for later use.
queryProductDetails(ids)getProductData(ids), chunkedAmazon caps the SKU set per call (historically 100; verify against 3.0.9 Javadoc). unavailableSkus → notFoundIDs. Subscriptions: callers must pass term SKUs, since only term SKUs carry prices — document loudly.
buyNonConsumable(param)purchase(sku)Entitlements and subscription term SKUs. AmazonPurchaseParam adds nothing in v1; exists for forward-compat.
buyConsumable(param, autoConsume)purchase(sku); if autoConsume, notifyFulfillment(FULFILLED) on SUCCESSFULParity with Android's auto-consume. Warn in docs: auto-fulfilling before delivery removes Amazon's retry safety net; default true only for parity.
completePurchase(p)notifyFulfillment(p.purchaseID, FULFILLED)Re-fulfilling an already fulfilled receipt is explicitly fine per Amazon docs, so this is safe on restored purchases too.
restorePurchases()getPurchaseUpdates(true) to exhaustionNon-canceled receipts → PurchaseStatus.restored, pendingCompletePurchase = true. Canceled receipts → PurchaseStatus.canceled so apps revoke. Consumables only come back if unfulfilled or bought on this device — same limitation as Play, document it.
countryCode()getUserData().countryCodeAvailable since SDK 3.0.7. Fallback to marketplace.
(auto, on resume)getUserData(), getPurchaseUpdates(false)Unfulfilled receipts → PurchaseStatus.purchased on the stream, exactly like Android re-emitting unacknowledged purchases on connect. Also catches Kids pending purchases that got approved while the app was away.
purchaseStream error path—Never addError; emit PurchaseDetails with status: error and an IAPError(source: 'amazon_appstore', code, message), matching the Android implementation's behaviour.

Status mapping

AmazonPurchaseStatusExtra
PurchaseResponse.SUCCESSFULpurchasedpendingCompletePurchase = true
PurchaseResponse.PENDINGpendingRequires enablePendingPurchases() to occur at all; final receipt arrives via purchase updates.
PurchaseResponse.FAILEDcanceledAmazon documents FAILED as "customer exited before completing". Expose rawStatus for the rare genuine error.
PurchaseResponse.ALREADY_PURCHASEDerror (code: already_purchased)Plus trigger getPurchaseUpdates(true) so the owned item is re-emitted as restored, as Amazon recommends.
PurchaseResponse.INVALID_SKU / NOT_SUPPORTEDerrorcode: invalid_sku / not_supported
Receipt with isCanceled == truecanceledFrom purchase updates; cancelDate in wrapper.

Field mapping

ProductDetailsSource
idsku
title, descriptiontitle, description (already localized)
priceprice string verbatim
rawPriceParsed from price using the marketplace locale's decimal/grouping separators; 0.0 if unparseable, priceIsEstimated = true
currencyCodeLookup table keyed by UserData.marketplace (US→USD, UK→GBP, DE/FR/IT/ES/NL→EUR, JP→JPY, CA→CAD, AU→AUD, BR→BRL, MX→MXN, IN→INR, …); empty string when unknown
currencySymbolLeading/trailing non-numeric run of price
PurchaseDetailsSource
purchaseIDreceiptId
productIDsku (for subscriptions: the term SKU; parent SKU is on the wrapper)
transactionDatepurchaseDate millis as string
verificationData.localVerificationDatareceipt.toJSON()
verificationData.serverVerificationDatareceiptId — the token RVS wants; userId is on AmazonPurchaseDetails because RVS needs both
verificationData.source'amazon_appstore'

7. Open decisions and known gaps

  1. Price fidelity. The inferred rawPrice/currencyCode are the one place this implementation is lossy versus Play/StoreKit. Decision: ship the heuristic, flag it with priceIsEstimated, and tell apps that need exact amounts to read them from their own catalogue or from RVS. Do not silently return plausible-looking wrong numbers without the flag.
  2. FAILED → canceled. Chosen because that is what it means 99% of the time and because canceled is what UIs should do with it. The raw status is preserved. Alternative (map to error) would make every user back-out show an error dialog in apps written against Play semantics.
  3. Subscription upgrades. No equivalent of ChangeSubscriptionParam. A GooglePlayPurchaseParam passed to this platform is ignored with a debug warning; modifySubscription() can be added to the addition later for partners who have tiered subscriptions enabled.
  4. Auto-sync on resume default. On by default because Amazon calls it mandatory and apps written against the interface would otherwise silently miss approved pending purchases. Can be disabled via the addition for apps that call restorePurchases() themselves.
  5. Idempotency. The plugin dedupes receipts within one process lifetime by receiptId before emitting. Cross-launch dedupe is the app's job (as on every store) and the README says so.
  6. Receipt verification. Out of scope client-side (Amazon says server-side RVS only). README includes the RVS request shape and links the sandbox endpoint. Expose isSandboxMode() so backends can be pointed at the RVS sandbox during App Tester runs.
  7. Endorsement. Assume none. flutter/packages hosts only first-party store implementations; design and document as a non-endorsed override, which is precisely what #137040 was built for.
  8. Package name. Confirm in_app_purchase_amazon is free on pub.dev; otherwise in_app_purchase_amazon_appstore. Publish under a verified publisher so it can be found via "packages that implement in_app_purchase".

8. Testing

9. Milestones and effort

MilestoneDeliverableEstimate
M0 — spikeBare plugin, getUserData round-trip through Pigeon on a Fire tablet with App Tester; confirms manifest merge, PEM handling, override registration on Flutter 3.27+2–3 days
M1 — coreFull Pigeon API, router, InAppPurchaseAmazonPlatform with all interface methods, wrappers, unit tests1.5–2 weeks
M2 — parity polishAddition API, auto-sync, pending purchases, price parser + marketplace table, dedupe, example app mirroring the upstream example1 week
M3 — release_default shim package, README/migration guide, LAT run, publish 0.1.03–5 days

Roughly 4–5 weeks for one engineer familiar with Flutter plugins and Android, most of it in M1 and in device testing. The native bridge itself is a few hundred lines; the time goes into the semantic edges and the test matrix.

10. Risks

RiskLevelMitigation
Shrinking target: no non-Fire Android since Aug 2025; new Fire TV hardware on Vega OS (no Flutter)mediumBe explicit that the package serves Fire tablets + Android-based Fire TV. Don't over-invest in TV-specific work.
Amazon changes the SDK (they did five point releases 2022–2026, all additive)lowPin the SDK version; raw JSON passthrough means new fields are reachable before the wrapper is updated.
Interface additions in in_app_purchase_platform_interfacelowBase class ships default implementations; track the changelog, implement when relevant.
Override requires Flutter ≥ 3.27; older toolchains register both plugins in alphabetical order (Amazon would lose)lowSet the SDK constraint; Mode B works on older Flutter anyway.
Amazon review objecting to Play Billing classes in a single binary (Mode B)mediumRecommend Mode A; document Mode B as at-your-own-risk.
Price heuristic wrong for an unlisted marketplacemediumpriceIsEstimated flag; table extendable at runtime via the addition; never crash on parse failure.
Local testing friction (App Tester, no emulator sandbox)mediumShip the sdktester JSON with the example; keep the pure-Dart mapping layer fully unit-testable without a device.
Bus factorhighSmall surface, heavy tests, and a design doc (this one) so the package can be handed over.

11. Sources