Software doesn't have to be transactional. It can be purposeful. With Snooze If You Can, I wanted to prove that a free, open-source app could also be a vehicle for doing good - one snooze at a time.
The Concept
Every time you snooze your alarm in Snooze If You Can, you have the option to make a small donation to Darüşşafaka, a Turkish educational foundation that provides free education to children who have lost one or both parents. The idea is simple: if you're going to lose five more minutes of your morning, at least make those five minutes count for someone.
It's opt-in, configurable, and completely transparent. Users choose their donation amount per snooze (from ₺1 to ₺10), and every transaction is visible in-app.
Why Darüşşafaka
Darüşşafaka has been providing free education since 1863 - over 160 years. It's one of Turkey's oldest and most respected educational institutions. Children who can't afford education get a full scholarship including boarding, meals, and educational materials.
As someone who's benefited enormously from access to education and technology, directing micro-donations to educational access felt right. The foundation is reputable, transparent with its finances, and the impact is direct - your donation supports a specific child's education.
StoreKit 2 Integration
Apple's StoreKit 2 framework handles in-app purchases and subscriptions. For charitable donations through the App Store, the technical implementation uses consumable in-app purchases.
import StoreKit
class DonationManager: ObservableObject {
@Published var products: [Product] = []
func loadProducts() async {
do {
products = try await Product.products(for: [
"donation_tier_1", // ₺1
"donation_tier_2", // ₺3
"donation_tier_5", // ₺5
"donation_tier_10" // ₺10
])
} catch {
print("Failed to load products: \(error)")
}
}
func purchase(_ product: Product) async throws -> Transaction? {
let result = try await product.purchase()
switch result {
case .success(let verification):
let transaction = try checkVerified(verification)
await transaction.finish()
return transaction
case .userCancelled, .pending:
return nil
@unknown default:
return nil
}
}
}StoreKit 2's async/await API is a significant improvement over the original StoreKit. Purchase flows are straightforward: load products, present them, process the result.
The Apple Tax Question
Apple takes a 30% cut (15% for small businesses) of every in-app purchase, including charitable donations. This is the most common criticism of the approach.
I considered alternatives:
- Direct payment processing - bypasses Apple's cut but violates App Store guidelines for digital goods
- Web-based donations - allowed, but adds friction (users leave the app to donate)
- External link - Apple now permits links to external payment methods in some regions, but the UX is clunky
The pragmatic choice: use StoreKit and accept the platform fee. The alternative - not having donations at all because the fee isn't ideal - helps nobody. 70-85% of the donation reaching the foundation is better than 0%.
I'm transparent about this in the app. The donation screen shows exactly what percentage reaches Darüşşafaka after Apple's fee.
Tracking and Transparency
Every donation is tracked locally and users can view their donation history:
struct DonationRecord: Codable, Identifiable {
let id: UUID
let date: Date
let amount: Decimal
let productId: String
let transactionId: UInt64
let netAmount: Decimal // after Apple's cut
}The app shows:
- Total donated this month
- Total donated all-time
- Breakdown by donation tier
- Estimated net amount reaching the foundation
No data is sent to external servers. All donation records are stored locally in the user's device using SwiftData. Privacy is non-negotiable.
The UI: Making Giving Feel Good
The donation flow is integrated into the alarm experience without being pushy:
- Alarm goes off → Complete challenge → Alarm dismissed
- Post-dismissal screen shows a gentle prompt: "Make your morning count?"
- One tap to donate at the user's pre-configured tier
- Confirmation with a simple animation and the running total
The key UX principle: charitable giving should feel rewarding, not guilting. The prompt appears after the alarm is dismissed (not during, when the user is frustrated). The language is positive. The default is to skip - donations are never the default action.
Server-Side Verification
For donation integrity, every transaction is verified server-side using Apple's App Store Server API:
func verifyTransaction(_ transaction: Transaction) async -> Bool {
// Verify the JWS (JSON Web Signature) signed by Apple
guard let appTransaction = try? await AppTransaction.shared else {
return false
}
// Verify the transaction belongs to this app
// and hasn't been previously consumed
return transaction.productType == .consumable
&& transaction.environment == .production
}This prevents receipt fraud - someone couldn't forge a donation record to inflate their dashboard numbers. Every donation displayed in the app corresponds to a verified Apple transaction.
Impact So Far
Since launching the donation feature:
- Hundreds of micro-donations processed
- Users who enable donations snooze less frequently (an unexpected behavioral side effect - perhaps knowing a snooze costs something, even voluntarily, makes you think twice)
- Multiple users requested additional foundation options - planning to add more in a future update
Lessons for Purpose-Driven Apps
- Make it opt-in. Forced charitable features breed resentment. Optional ones build loyalty.
- Be transparent about fees. Users respect honesty about platform costs.
- Privacy first. Donation data is personal. Keep it on-device.
- Don't over-complicate. One tap to donate. One screen to review. That's it.
Building software with purpose doesn't require a nonprofit structure or a complex social impact framework. It requires a simple question: "Can this app do something good while doing what it already does?"
For Snooze If You Can, the answer was yes.
Building an app? Consider how it could make a small positive impact. Micro-donations, awareness features, or educational components - purpose doesn't have to be the product. It can be a feature.