iap-apple

Lightweight Apple App Store receipt validation for Node.js - Zero dependencies, TypeScript-first, blazing fast.
Why iap-apple? 🤔
| Runtime Dependencies | 0 | 5-10+ |
| TypeScript | Native | Partial/None |
| Bundle Size | ~15KB | 100KB+ |
| Node.js Fetch | Native | axios/request |
| Maintained | 2024+ | Often stale |
- Zero Dependencies - Uses Node.js native
fetch, no bloat
- TypeScript-First - Full type definitions, great IDE support
- Production Ready - 93%+ test coverage, battle-tested
- Simple API - One function to validate, intuitive helpers
Installation 📦
pnpm add iap-apple
npm install iap-apple
yarn add iap-apple
Requirements: Node.js 22+
Quick Start 🚀
import { verify, getPurchasedItems, isPurchasedItemExpired } from 'iap-apple';
const response = await verify(receiptData, {
appSharedSecret: 'your-shared-secret',
});
const items = getPurchasedItems(response);
const latestPurchase = items[0];
if (!isPurchasedItemExpired(latestPurchase)) {
console.log('Subscription is active!');
}
API Reference 📚
verify(receipt, config)
Validates a receipt against Apple's verifyReceipt endpoint. Automatically handles production/sandbox fallback.
import { verify, IAPAppleError } from 'iap-apple';
try {
const response = await verify(receiptData, {
appSharedSecret: 'your-shared-secret',
appleExcludeOldTransactions: true,
test: false,
logger: console,
});
console.log('Receipt validated:', response.status === 0);
} catch (error) {
const { rejectionMessage, data } = error as IAPAppleError;
console.error('Validation failed:', rejectionMessage);
}
isVerifiedReceipt(response)
Check if a receipt validation was successful.
import { verify, isVerifiedReceipt } from 'iap-apple';
const response = await verify(receipt, config);
if (isVerifiedReceipt(response)) {
}
getPurchasedItems(response)
Extract purchased items from the response. Returns items sorted by purchase date (newest first), deduplicated by original_transaction_id.
import { verify, getPurchasedItems } from 'iap-apple';
const response = await verify(receipt, config);
const items = getPurchasedItems(response);
for (const item of items) {
console.log(`Product: ${item.productId}`);
console.log(`Purchased: ${new Date(item.purchaseDateMS)}`);
console.log(`Expires: ${item.expirationDateMS ? new Date(item.expirationDateMS) : 'Never'}`);
}
isPurchasedItemExpired(item)
Check if a subscription has expired or been cancelled.
import { getPurchasedItems, isPurchasedItemExpired } from 'iap-apple';
const items = getPurchasedItems(response);
const subscription = items[0];
if (isPurchasedItemExpired(subscription)) {
console.log('Subscription expired or cancelled');
} else {
console.log('Subscription is active');
}
isPurchasedItemCanceled(item)
Check if a purchase was cancelled (refunded).
import { getPurchasedItems, isPurchasedItemCanceled } from 'iap-apple';
const items = getPurchasedItems(response);
if (isPurchasedItemCanceled(items[0])) {
console.log('User received a refund');
}
Types 📝
PurchasedItem
interface PurchasedItem {
bundleId: string;
appItemId: string;
transactionId: string;
originalTransactionId?: string;
productId: string;
purchaseDateMS: number;
originalPurchaseDateMS?: number;
expirationDateMS?: number;
cancellationDateMS?: number;
isTrialPeriod: boolean;
quantity: number;
}
IIAPAppleConfig
interface IIAPAppleConfig {
appSharedSecret: string;
appleExcludeOldTransactions?: boolean;
test?: boolean;
logger?: ILogger | null;
}
Error Handling
All errors are thrown as IAPAppleError:
interface IAPAppleError {
rejectionMessage: string;
data?: IVerifyReceiptResponseBody | null;
}
Common status codes:
21002 - Malformed receipt data
21003 - Receipt authentication failed
21004 - Shared secret mismatch
21005 - Apple server unavailable
21006 - Subscription expired
21007 - Sandbox receipt sent to production
21008 - Production receipt sent to sandbox
StoreKit 2 / App Store Server API 🆕
This library uses Apple's legacy verifyReceipt endpoint, which still works but is deprecated for new apps.
For new projects using StoreKit 2, consider Apple's official library:
npm install @apple/app-store-server-library
When to use iap-apple:
- Existing apps using StoreKit 1
- Need zero dependencies
- Want a simpler API
- Validating receipts from older iOS versions
When to use Apple's library:
- New apps with StoreKit 2
- Need App Store Server Notifications V2
- Need subscription offer signing
Contributing 🤝
Contributions are welcome! Please open an issue or submit a PR.
License 📄
ISC