In-App Purchases
Sell digital items in your app — premium skins, extra levels, coins, ad removal, and anything else. No payment integration, no backend setup. Supports Stripe (card) and Alipay.
Download for Your AI Tool
Download this guide and paste it into Cursor, Claude, ChatGPT, Lovable, Google AI Studio, or any AI coding tool to let it build apps with in-app purchases.
Download Guide (.md)Quick Start
The SDK is automatically injected as window.gapp.iap once you add items to your app in the Dashboard.
// 1. List items (e.g. on a shop page)
const items = await gapp.iap.getItems()
// 2. Check ownership before showing buy button
const owned = await gapp.iap.isOwned('premium_skin')
if (owned) {
showUnlockedUI()
} else {
showBuyButton()
}
// 3. Trigger purchase (from a click handler)
async function onBuyClick() {
const result = await gapp.iap.purchase('premium_skin')
if (result.success) {
unlockItem('premium_skin')
} else if (result.error === 'cancelled') {
// user closed the popup — do nothing
} else {
alert('Purchase failed: ' + result.error)
}
}Setup
1. Connect Stripe (for USD payments)
Go to Dashboard → Settings → Payouts and complete Stripe Connect onboarding. This lets gapp.so send your earnings to your bank account. Alipay is also available for creators in China.
2. Create items in the Dashboard
Open your app in Dashboard → Edit → In-App Purchases tab. Add each item with an item key (lowercase, underscore-separated), name, description, and price (USD).
3. Call the SDK from your app
Use gapp.iap.purchase(itemKey) from a button click handler. The SDK handles the whole payment flow — login, checkout, verification.
4. Unlock content based on ownership
Call gapp.iap.isOwned(itemKey) on page load or listen for the gapp:iap:purchased event to grant access immediately after purchase.
API Reference
gapp.iap.getItems()List all active items configured for this app
Returns: Promise<Array<{ itemKey, name, description, priceCents, imageUrl }>>
const items = await gapp.iap.getItems()
// [{ itemKey: 'premium_skin', name: 'Premium Skin', priceCents: 499, ... }]gapp.iap.getItem(itemKey)Get a single item by its key
Returns: Promise<Item | null>
const item = await gapp.iap.getItem('premium_skin')gapp.iap.isOwned(itemKey)Check if the current user owns an item
Returns: Promise<boolean>
if (await gapp.iap.isOwned('premium_skin')) {
unlockPremiumContent()
}gapp.iap.getPurchases()List the current user's completed purchases for this app
Returns: Promise<Array<{ itemKey, purchasedAt }>>
const purchases = await gapp.iap.getPurchases()
gapp.iap.purchase(itemKey)Start a purchase flow. Opens a payment popup. Resolves after the popup closes or payment is verified.
Returns: Promise<{ success: boolean, error?: string, purchaseId?: string }>
const result = await gapp.iap.purchase('premium_skin')
if (result.success) {
grantItem('premium_skin')
}Purchase Event
The SDK fires gapp:iap:purchased when a purchase is verified. Useful when the purchase button lives in a different component from the unlock logic.
window.addEventListener('gapp:iap:purchased', (e) => {
const { itemKey, purchaseId } = e.detail
unlockItem(itemKey)
})Error Codes
Returned in result.error from gapp.iap.purchase().
| Code | Meaning |
|---|---|
unauthenticated | User is not logged in. A login popup is opened automatically — the caller may retry afterwards. |
already_owned | User already owns this item (one-time purchase). |
item_not_found | The item key does not exist or the item is inactive. |
popup_blocked | The browser blocked the payment popup. |
cancelled | User closed the popup before completing payment. |
failed | Payment verification did not complete in time. |
Full Example
A complete shop page with buy buttons, ownership checks, and event listening.
window.addEventListener('gapp:ready', async () => {
const items = await gapp.iap.getItems()
const purchases = await gapp.iap.getPurchases()
const ownedKeys = new Set(purchases.map(p => p.itemKey))
for (const item of items) {
renderItem(item, ownedKeys.has(item.itemKey))
}
})
function renderItem(item, owned) {
const el = document.createElement('div')
el.innerHTML = `
<h3>${item.name}</h3>
<p>${item.description}</p>
<button data-key="${item.itemKey}" ${owned ? 'disabled' : ''}>
${owned ? 'Owned' : '$' + (item.priceCents / 100).toFixed(2)}
</button>
`
el.querySelector('button').addEventListener('click', async (e) => {
const key = e.target.dataset.key
const result = await gapp.iap.purchase(key)
if (!result.success && result.error !== 'cancelled') {
alert('Purchase failed: ' + result.error)
}
})
document.getElementById('shop').appendChild(el)
}
// Unlock immediately on purchase, anywhere in the app
window.addEventListener('gapp:iap:purchased', (e) => {
grantItem(e.detail.itemKey)
})Local Development
Test IAP flows locally without real money. Add the dev-proxy script to your HTML and pass mock items via the data-iap-items attribute:
<script
src="https://gapp.so/dev-proxy.js"
data-token="YOUR_TOKEN"
data-iap-items='[
{"itemKey":"premium_skin","name":"Premium Skin","priceCents":499},
{"itemKey":"pro_pack","name":"Pro Pack","priceCents":999}
]'
></script>Purchases are stored in localStorage with the prefix gapp_dev_iap_. A browser confirm() dialog simulates the payment. When you publish, the script tag is stripped automatically — no changes needed.
Pricing & Fees
| Price range | $0.49 – $99.99 USD |
| Platform fee | 20% |
| Creator share | 80% |
| Payment methods | Stripe (card), Alipay |
| Item key format | [a-z0-9_]{1,50} |
| Payment expiry | 30 minutes (pending payment auto-expires if not completed) |
FAQ
Q: How much does gapp.so take?
A: The platform fee is 20%. You receive 80% of each sale, paid out via Stripe Connect (USD) or Alipay (CNY).
Q: Can I change prices after launch?
A: Yes. Update the price in the Dashboard and it applies to new purchases immediately. Existing purchases are not affected.
Q: Can users buy the same item twice?
A: No. Each item is a one-time purchase per user. purchase() returns error: "already_owned" if the user already bought it. Use isOwned() before showing a buy button.
Q: How do I handle payment failures?
A: Check result.success from purchase(). The result.error field tells you why (cancelled, popup_blocked, failed, etc.). Your app should fail gracefully — no content should be granted unless success is true.
Q: Are prices set in USD or CNY?
A: Prices are always set in USD in the Dashboard. For Alipay (CNY) payments, gapp.so converts at checkout using a fixed USD→CNY exchange rate (updated periodically by the platform). Your analytics show USD and CNY earnings separately.
Q: Can I test purchases without real money?
A: Yes. Use the dev-proxy.js script to test locally — it mocks gapp.iap with localStorage and a confirmation dialog. See the Local Development section below.