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().

CodeMeaning
unauthenticatedUser is not logged in. A login popup is opened automatically — the caller may retry afterwards.
already_ownedUser already owns this item (one-time purchase).
item_not_foundThe item key does not exist or the item is inactive.
popup_blockedThe browser blocked the payment popup.
cancelledUser closed the popup before completing payment.
failedPayment 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 fee20%
Creator share80%
Payment methodsStripe (card), Alipay
Item key format[a-z0-9_]{1,50}
Payment expiry30 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.