Products & Offerings
Configure what you sell and how you display it
Overview
CroissantPay uses three concepts to organize your in-app purchases:
Products
Products are the actual items you sell—subscriptions, one-time purchases, or consumables. Each product maps to a product in the App Store and/or Google Play.
Offerings
Offerings are curated collections of products to display on your paywall. You can have multiple offerings for different scenarios (default, sale, experiment).
Packages
Packages are products within an offering, with a specific type (monthly, annual, lifetime, etc.) that helps you identify them in your UI.
Hierarchy
Offerings
├── "default" (current offering)
│ ├── Package: MONTHLY
│ │ └── Product: Premium Monthly ($9.99/mo)
│ ├── Package: ANNUAL
│ │ └── Product: Premium Annual ($79.99/yr)
│ └── Package: LIFETIME
│ └── Product: Premium Lifetime ($199.99)
│
└── "sale_offering"
├── Package: MONTHLY
│ └── Product: Premium Monthly ($7.99/mo) [sale price]
└── Package: ANNUAL
└── Product: Premium Annual ($59.99/yr) [sale price]Product Types
Auto-Renewable Subscriptions
Automatically renew until cancelled. Best for ongoing access to features or content.
- • Weekly, monthly, quarterly, annual, etc.
- • Support free trials and introductory pricing
- • Grace periods for failed payments
Non-Consumable (Lifetime)
One-time purchase that doesn't expire. Best for permanent unlocks.
- • Permanent access to features
- • Restored on reinstall/new devices
- • No renewal or expiration
Consumables
Can be purchased multiple times and used up. Best for virtual currency or credits.
- • Coins, gems, credits, etc.
- • Not restored on reinstall
- • Track balance in your backend
Creating Products
Step 1: Create in App Stores
First, create your products in App Store Connect and Google Play Console. See our iOS Setup and Android Setup guides.
Step 2: Add to CroissantPay
- Go to Products in the dashboard
- Click "Create Product"
- Enter product details:
- • Identifier (e.g., "premium_monthly")
- • Display name
- • Product type
- • App Store product ID
- • Google Play product ID
- Link entitlements this product grants
POST /api/v1/products
{
"identifier": "premium_monthly",
"displayName": "Premium Monthly",
"description": "Full premium access, billed monthly",
"type": "subscription",
"storeProductIdApple": "com.myapp.premium_monthly",
"storeProductIdGoogle": "premium_monthly",
"entitlementIds": ["premium"]
}Creating Offerings
Offerings let you control which products appear on your paywall without app updates.
Default Offering
Your app always has a "current" offering that's returned by default. This is typically your main paywall configuration.
Multiple Offerings
Create additional offerings for:
- Sales: Special pricing during promotions
- A/B Tests: Different product combinations to test
- User Segments: Different offerings for different users
- Win-back: Special offers for churned users
POST /api/v1/offerings
{
"identifier": "default",
"displayName": "Default Offering",
"isCurrent": true,
"packages": [
{
"productId": "prod_monthly",
"packageType": "MONTHLY"
},
{
"productId": "prod_annual",
"packageType": "ANNUAL"
}
]
}Package Types
Package types help you identify products in your UI without hardcoding identifiers:
| Type | Description |
|---|---|
WEEKLY | Weekly subscription |
MONTHLY | Monthly subscription |
TWO_MONTH | Every 2 months |
THREE_MONTH | Quarterly subscription |
SIX_MONTH | Semi-annual subscription |
ANNUAL | Yearly subscription |
LIFETIME | One-time lifetime purchase |
CUSTOM | Custom/other package type |
Using Offerings in Your App
import { usePurchases } from '@croissantpay/react-native';
function Paywall() {
const { offerings, purchaseProduct, isLoading } = usePurchases();
if (isLoading) {
return <LoadingSpinner />;
}
const currentOffering = offerings?.current;
if (!currentOffering) {
return <Text>No products available</Text>;
}
return (
<View>
<Text>Choose your plan</Text>
{currentOffering.packages.map(pkg => (
<TouchableOpacity
key={pkg.id}
onPress={() => purchaseProduct(pkg.product)}
>
<View style={styles.planCard}>
<Text style={styles.planName}>
{pkg.product.title}
</Text>
<Text style={styles.price}>
{pkg.product.priceString}
{pkg.packageType === 'ANNUAL' && '/year'}
{pkg.packageType === 'MONTHLY' && '/month'}
</Text>
{pkg.packageType === 'ANNUAL' && (
<Text style={styles.savings}>
Save 33%
</Text>
)}
</View>
</TouchableOpacity>
))}
</View>
);
}Accessing Specific Packages
const currentOffering = offerings?.current;
// Access packages by type
const monthlyPkg = currentOffering?.monthly;
const annualPkg = currentOffering?.annual;
const lifetimePkg = currentOffering?.lifetime;
// Or filter from packages array
const monthly = currentOffering?.packages.find(
pkg => pkg.packageType === 'MONTHLY'
);Best Practices
Match product IDs between stores
Use similar identifiers for the same product across platforms to make management easier.
Include annual options
Annual subscriptions typically have higher LTV and lower churn. Always offer an annual option with a discount.
Use offerings for remote control
Instead of hardcoding products, use offerings to control your paywall remotely. This lets you run sales or tests without app updates.
Handle missing products gracefully
Always check if offerings and products exist before rendering. Products might not be available in all regions.