Your app
React Native and Expo
Keep react-native-iap (or expo-iap) for the payment sheet. Send what it hands you to Vend16, finish once we’ve answered, and unlock from our answer.
1Get your public key
Create a free account, verify your email, and the setup steps ask for your app’s name, iOS bundle ID and Android package name. You get a public key (pk_…, it goes in the app) and a secret key (sk_…, only for a server, if you have one). Both are shown once, so copy them.
2Add it to your app
Install react-native-iap (version 14 or later, which needs React Native 0.79 or later and gives you the StoreKit 2 JWS on iPhone as purchaseToken). It needs react-native-nitro-modules alongside it.
npm install react-native-iap react-native-nitro-modules
npx pod-installExpo: use expo-iap instead (npx expo install expo-iap), in a development build. Neither library runs in Expo Go. The code below works with either; change the import.
Report every purchase, finish only after Vend16 answers, and read access back:
import { useEffect } from 'react';
import { Platform } from 'react-native';
import {
initConnection, purchaseUpdatedListener, getAvailablePurchases, finishTransaction, type Purchase,
} from 'react-native-iap';
const KEY = 'pk_your_public_key'; // public key, safe in the app
const BASE = 'https://vend16.com';
const TYPES: Record<string, 'consumable' | 'non_consumable' | 'subscription'> = {
coins_100: 'consumable', pro_monthly: 'subscription',
};
async function report(p: Purchase, userId: string) {
const res = await fetch(`${BASE}/v1/receipts`, {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
platform: Platform.OS === 'ios' ? 'ios' : 'android',
token: p.purchaseToken, // iOS: the StoreKit 2 JWS. Android: the Play purchase token.
productId: p.productId,
productType: TYPES[p.productId],
appUserId: userId,
}),
});
if (res.status === 422) return 'finish-unlock-nothing';
if (!res.ok) return 'try-later'; // 502 or offline: the store hands it back later
const { status } = await res.json();
return ['recorded', 'duplicate', 'revoked'].includes(status) ? 'finish' : 'try-later';
}
async function handle(p: Purchase, userId: string) {
const outcome = await report(p, userId);
if (outcome === 'try-later') return; // left unfinished, so it comes back
try {
await finishTransaction({ purchase: p, isConsumable: TYPES[p.productId] === 'consumable' });
} catch { /* already finished: nothing to do */ }
}
export function useVend16(userId: string) {
useEffect(() => {
let sub: { remove(): void } | undefined;
initConnection().then(async () => {
// New purchases. On iPhone, ones left unfinished last time replay here too.
sub = purchaseUpdatedListener((p) => { void handle(p, userId); });
// Android doesn't replay them, so pick up anything paid for but not yet reported.
if (Platform.OS === 'android') {
for (const p of await getAvailablePurchases()) {
if (TYPES[p.productId] === 'consumable' || !('isAcknowledgedAndroid' in p && p.isAcknowledgedAndroid)) {
await handle(p, userId);
}
}
}
});
return () => sub?.remove();
}, [userId]);
}
export async function activeProducts(userId: string): Promise<string[]> {
const res = await fetch(`${BASE}/v1/subscribers/${encodeURIComponent(userId)}`, {
headers: { Authorization: `Bearer ${KEY}` },
});
return (await res.json()).active_product_ids;
}Start purchases with requestPurchase as the react-native-iap docs show; the listener above does the rest.
3Keep the money safe
- Tell the store a purchase is finished only after Vend16 answers
recorded,duplicateorrevoked. If the answer ispending, or Vend16 can’t be reached (a502or no network), leave it unfinished. The store hands it back on the next launch and you try again. - If Vend16 answers
422 invalid_receipt, finish it and unlock nothing. The store says it isn’t a real purchase. - Unlock from
active_product_idsinGET /v1/subscribers/:appUserId, never from a flag on the phone. Check it at launch and after every purchase.
Use a stable user ID from your own sign-in (or a UUID you keep on the device), never an email address.
4Connect the stores
Your code is done. The stores need to know about your products and about Vend16:
Stuck? Email hello@spruik.co and a real person replies.