Swift 4 iOS应用结合Firebase,如何用Stripe实现订阅制内容访问?
Hey there! Let’s break down exactly how to implement monthly subscriptions with Stripe in your Swift 4 iOS app, using Firebase for auth and data storage. I’ve built similar flows before, so I’ll walk you through each step clearly.
First, get your services lined up:
- Stripe Dashboard: Create a monthly subscription product and note its
priceId(you’ll need this later). Enable test mode to experiment without real payments. - Firebase: Set up Firebase Auth (for user login) and Firestore (to store user subscription data). Also, enable Firebase Cloud Functions—we’ll use these for secure server-side Stripe interactions (never expose your Stripe secret key in the iOS app!).
- Dependencies: Add the Stripe SDK to your project via CocoaPods:
pod 'Stripe'
Every subscriber needs a Stripe Customer ID tied to their Firebase user. We’ll use a Cloud Function to create this safely:
Cloud Function (Node.js)
const functions = require("firebase-functions"); const admin = require("firebase-admin"); const stripe = require("stripe")(functions.config().stripe.secret_key); admin.initializeApp(); exports.createStripeCustomer = functions.https.onCall(async (data, context) => { if (!context.auth) { throw new functions.https.HttpsError("unauthenticated", "You must be logged in to create a customer."); } const user = context.auth; const customer = await stripe.customers.create({ email: user.token.email, metadata: { firebaseUID: user.uid } }); // Save the Stripe Customer ID to Firestore await admin.firestore().collection("users").doc(user.uid).set( { stripeCustomerId: customer.id }, { merge: true } ); return { customerId: customer.id }; });
Swift 4 Client Call
Invoke this function right after a user logs in:
import FirebaseAuth import FirebaseFunctions func linkUserToStripe() { guard Auth.auth().currentUser != nil else { return } let functions = Functions.functions() functions.httpsCallable("createStripeCustomer").call { result, error in if let error = error as NSError? { print("Failed to create Stripe customer: \(error.localizedDescription)") return } // Customer ID is now saved to Firestore automatically via the function print("Stripe customer linked successfully!") } }
We’ll use Stripe’s Payment Sheet for a secure, pre-built checkout experience. First, create a Cloud Function to generate a subscription, then trigger the Payment Sheet from your app.
Cloud Function for Subscription Creation
exports.createSubscription = functions.https.onCall(async (data, context) => { if (!context.auth) { throw new functions.https.HttpsError("unauthenticated", "You must be logged in to subscribe."); } const { customerId, priceId } = data; const subscription = await stripe.subscriptions.create({ customer: customerId, items: [{ price: priceId }], payment_behavior: "default_incomplete", expand: ["latest_invoice.payment_intent"] }); return { subscriptionId: subscription.id, clientSecret: subscription.latest_invoice.payment_intent.client_secret }; });
Swift 4 Payment Sheet Integration
import Stripe import FirebaseFirestore func startSubscriptionFlow() { guard let user = Auth.auth().currentUser else { return } // Fetch the user's Stripe Customer ID from Firestore let db = Firestore.firestore() db.collection("users").document(user.uid).getDocument { document, error in guard let document = document, document.exists, let customerId = document["stripeCustomerId"] as? String else { print("No Stripe customer found for user") return } // Call the subscription creation function let functions = Functions.functions() let payload = [ "customerId": customerId, "priceId": "YOUR_MONTHLY_PRICE_ID" // Replace with your Stripe price ID ] functions.httpsCallable("createSubscription").call(payload) { result, error in if let error = error as NSError? { print("Subscription creation failed: \(error.localizedDescription)") return } guard let response = result?.data as? [String: String], let clientSecret = response["clientSecret"] else { return } // Initialize and present the Payment Sheet var config = STPPaymentSheetConfiguration() config.merchantDisplayName = "Your App Name" let paymentSheet = STPPaymentSheet( paymentIntentClientSecret: clientSecret, configuration: config ) paymentSheet.present(from: self) { result in switch result { case .completed: DispatchQueue.main.async { print("Subscription successful!") // Update your UI to show premium content } case .canceled: print("User canceled payment") case .failed(let error): print("Payment failed: \(error.localizedDescription)") } } } } }
Stripe sends webhook events when subscriptions change (e.g., canceled, expired, payment failed). Use a Cloud Function to update Firestore with these changes:
Webhook Handler Cloud Function
exports.handleStripeWebhook = functions.https.onRequest(async (req, res) => { const sig = req.headers["stripe-signature"]; let event; try { event = stripe.webhooks.constructEvent( req.rawBody, sig, functions.config().stripe.webhook_secret ); } catch (err) { res.status(400).send(`Webhook Error: ${err.message}`); return; } switch (event.type) { case "customer.subscription.updated": const subscription = event.data.object; const firebaseUID = subscription.metadata.firebaseUID; if (!firebaseUID) break; await admin.firestore().collection("users").doc(firebaseUID).set({ subscriptionStatus: subscription.status, subscriptionId: subscription.id, currentPeriodEnd: subscription.current_period_end }, { merge: true }); break; case "customer.subscription.deleted": const deletedSub = event.data.object; const deletedUID = deletedSub.metadata.firebaseUID; if (!deletedUID) break; await admin.firestore().collection("users").doc(deletedUID).set({ subscriptionStatus: "canceled", subscriptionId: null }, { merge: true }); break; default: console.log(`Unhandled event type: ${event.type}`); } res.status(200).end(); });
Pro Tip: In the Stripe Dashboard, set up your webhook endpoint to point to this Cloud Function’s URL, and select the events customer.subscription.updated, customer.subscription.deleted, and invoice.payment_failed.
Check the user’s subscription status from Firestore before granting access to premium features:
func checkSubscriptionAccess(completion: @escaping (Bool) -> Void) { guard let user = Auth.auth().currentUser else { completion(false) return } let db = Firestore.firestore() db.collection("users").document(user.uid).addSnapshotListener { snapshot, error in guard let document = snapshot, document.exists else { completion(false) return } let status = document["subscriptionStatus"] as? String ?? "inactive" // Allow access if status is active or in a trial period let hasAccess = status == "active" || status == "trialing" completion(hasAccess) } } // Usage example when accessing premium content checkSubscriptionAccess { hasAccess in DispatchQueue.main.async { if hasAccess { self.showPremiumContent() } else { self.showSubscriptionPrompt() } } }
Add a cancel button that triggers a Cloud Function to end the subscription:
Cancel Subscription Cloud Function
exports.cancelSubscription = functions.https.onCall(async (data, context) => { if (!context.auth) { throw new functions.https.HttpsError("unauthenticated", "You must be logged in to cancel."); } const { subscriptionId } = data; await stripe.subscriptions.del(subscriptionId); return { success: true }; });
Swift 4 Client Call
func cancelSubscription() { guard let user = Auth.auth().currentUser else { return } let db = Firestore.firestore() db.collection("users").document(user.uid).getDocument { document, error in guard let document = document, document.exists, let subscriptionId = document["subscriptionId"] as? String else { print("No active subscription found") return } let functions = Functions.functions() functions.httpsCallable("cancelSubscription").call(["subscriptionId": subscriptionId]) { result, error in if let error = error as NSError? { print("Cancel failed: \(error.localizedDescription)") return } print("Subscription canceled successfully") // Update UI to reflect canceled status } } }
内容的提问来源于stack exchange,提问作者Manley

