Closed unxavi closed 1 year ago
Hi: @dackers86, Do you know when is it going to be develop? . I think that it's a very interesting feature
HI @jesus-mg-ios @unxavi.
I added the enhancement
tag as I initially thought could require some development. However, could you provide a use case for this scenario?
The extension itself is simply use the Stripe Api
at it's core. If this technique can be used through Stripe, I cannot a reason as to why we would limited through the extension?
Could this merely be a documentation update, or has anyone encountered a scenario where this type of request has not been possible?
@dackers86 thanks for getting back to me.
I want a user to be able to subscribe to a delivery service, the user pays a subscription fee and can order anything they want to be delivery, the service is consumed outside of the app, so on the AppStore guidelines you are asked to implement this with a payment system different than In App Purchases, if you do with IAP you are in violation of the guidelines, so Stripe mobile subscription is perfect for this use case.
3.1.3(e) Goods and Services Outside of the App: If your app enables people to purchase physical goods or services that will be consumed outside of the app, you must use purchase methods other than in-app purchase to collect those payments, such as Apple Pay or traditional credit card entry.
Another user case, you are music teacher which you have students that subscribe to your music classes through the application on the store, the classes are done through a video conference on a one to one class. One teacher - One student.
By the AppStore guidelines you could use other method different from In App Purchases, getting around the 30% fee of the AppStore even if you make the video conference inside the app.
3.1.3(d) Person-to-Person Services: If your app enables the purchase of real-time person-to-person services between two individuals (for example tutoring students, medical consultations, real estate tours, or fitness training), you may use purchase methods other than in-app purchase to collect those payments.
In this use case if you decide not to deliver the lesson inside the app, but you use something like a link to zoom, then you are affected by the guideline 3.1.3(e) Goods and Services Outside of the App and you should use another payment different of IAP to comply with the store rules.
As you say, this extension uses the Stripe API
and this extension docs says the following:
Subscription payments (web only)
The issue is that for web payments with the extension, you create a document on Firestore and the extension will give you back a URL
where then you redirect a user to pay on the browser. This is good for a desktop browser experience and this API you can give to the extension the price
id for the Stripe subscription.
On the other side, this extension for mobile, lets you make only one time payment, you write to Firestore the document with the following
And then the extension give you back the paymentIntentClientSecret
and ephemeralKeySecret
which then you can use inside the mobile app with the SwiftUI PaymentSheet
https://stripe.com/docs/payments/accept-a-payment?platform=ios&uikit-swiftui=swiftui
The mode
subscription
can't be use with the mobile in the extension, this will raise an error
This extension to work with subscription on a mobile app is incomplete and non functional. We need to be able to use the price
id param like for the web, but instead of getting back an URL, we need to get back the ephemeralKeySecret
to be able to use it with the SwiftUI object PaymentSheet
from stripe.
To be able to accomplish this, we should implement the correct Stripe API
to handle this use case, which is possible on this extension.
Example Stripe API Call.
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ plan: priceId],
payment_behavior: "default_incomplete",
expand: ["latest_invoice.payment_intent"],
payment_settings: {
save_default_payment_method: "on_subscription",
},
metadata: {
firebaseUserUID: userUid,
},
});
@dackers86 I think this post is already long enough, if you have any doubts or comments, could you get back? At least to clear any doubt.
Do you think we could implement this? Stripe is great for some use cases on mobile where we can't use IAP or have the option of not using it, and yet this extension does not cover this uses cases.
@dackers86 any doubt?
Hi @unxavi Thanks for the awesome feedback.
We will look at producing an investigative PR based on the above!
Waiting for update.
Waiting. @dackers86 Please merge your PR
app.post('/payment-sheet-subscription', async (_, res) => {
const { secret_key } = getKeys();
const stripe = new Stripe(secret_key as string, {
apiVersion: '2020-08-27',
typescript: true,
});
const customers = await stripe.customers.list();
// Here, we're getting latest customer only for example purposes.
const customer = customers.data[0];
if (!customer) {
return res.send({
error: 'You have no customer created',
});
}
const ephemeralKey = await stripe.ephemeralKeys.create(
{ customer: customer.id },
{ apiVersion: '2020-08-27' }
);
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: 'price_1L3hcFLu5o3P18Zp9GDQEnqe' }],
trial_period_days: 3,
});
if (typeof subscription.pending_setup_intent === 'string') {
const setupIntent = await stripe.setupIntents.retrieve(
subscription.pending_setup_intent
);
return res.json({
setupIntent: setupIntent.client_secret,
ephemeralKey: ephemeralKey.secret,
customer: customer.id,
});
} else {
throw new Error(
'Expected response type string, but received: ' +
typeof subscription.pending_setup_intent
);
}
});
router.post("/create-customer-and-subscription", async (req, res) => {
try {
let customer;
let paymentMethod = req.body.paymentMethodId;
let stripeEmail = req.body.stripeEmail;
let customerId = req.body.customerId;
if (!paymentMethod) {
paymentMethod = "pm_card_visa";
}
try {
customer = await stripe.customers.retrieve(customerId);
// Retrieve new payment method
let newPaymentMethod = await stripe.paymentMethods.retrieve(paymentMethod);
// Retrieve current default payment method
let currentPaymentMethod = null;
if (customer.invoice_settings.default_payment_method) {
currentPaymentMethod = await stripe.paymentMethods.retrieve(customer.invoice_settings.default_payment_method);
}
// Compare new payment method and current payment method
if (!currentPaymentMethod || (newPaymentMethod.card.last4 !== currentPaymentMethod.card.last4 ||
newPaymentMethod.card.exp_month !== currentPaymentMethod.card.exp_month ||
newPaymentMethod.card.exp_year !== currentPaymentMethod.card.exp_year)) {
// Attach payment method to the customer
await stripe.paymentMethods.attach(
paymentMethod,
{ customer: customer.id }
);
// Update customer's default payment method
await stripe.customers.update(customer.id, {
invoice_settings: {
default_payment_method: paymentMethod
},
});
} else {
paymentMethod = currentPaymentMethod.id;
}
} catch (err) {
console.log("The customer doesn't exist. Creating one...");
customer = await stripe.customers.create({
email: stripeEmail,
name: "Customer Name",
address: {
line1: "Address Line 1",
postal_code: "110092",
city: "New Delhi",
state: "Delhi",
country: "India",
},
payment_method: paymentMethod,
invoice_settings: {
default_payment_method: paymentMethod
},
});
}
const ephemeralKey = await stripe.ephemeralKeys.create(
{ customer: customer.id },
{ apiVersion: "2022-11-15" }
);
const setupIntent = await stripe.setupIntents.create({
customer: customer.id,
payment_method_types: ['card'],
usage: 'off_session',
});
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [
{
price: 'price_1NGcpvSDKjg4kQFbTszkhOy4',
},
],
default_payment_method: paymentMethod,
payment_behavior: 'allow_incomplete',
payment_settings: { save_default_payment_method: 'on_subscription' },
expand: ['latest_invoice.payment_intent'],
});
res.status(200).json({
customer: customer.id,
publishableKey: Publishable_Key,
setupIntent: setupIntent.client_secret,
ephemeralKey: ephemeralKey.secret,
subscription: subscription.id,
paymentIntent: subscription.latest_invoice.payment_intent.client_secret,
});
} catch (err) {
console.error(`Error: ${err}`);
res.status(err.statusCode).send(err.raw.message);
}
});
PR merged, now waiting for official integration docs
is official docs now updated, for how to use mobile subscription?
Feature request
Is your feature request related to a problem? Please describe.
When integrating this extension, the docs for the setup says
Subscription payments (web only)
why is that? what makes it hard to also integrate subscriptions for mobile devices? Could it be possible to work around this limitation?On the recommended usage for the extension at the URL https://github.com/stripe/stripe-firebase-extensions/tree/next/firestore-stripe-payments
It stays:
If you're developing native mobile applications and you're selling digital products or services within your app, (e.g. subscriptions, in-game currencies, game levels, access to premium content, or unlocking a full version), you must use the app store's in-app purchase APIs. See [Apple's](https://developer.apple.com/app-store/review/guidelines/#payments) and [Google's](https://support.google.com/googleplay/android-developer/answer/9858738?hl=en&ref_topic=9857752) guidelines for more information.
Which is not totally true
Apple Guidelines for example: https://developer.apple.com/app-store/review/guidelines/#payments
3.1.3(d) Person-to-Person Services: If your app enables the purchase of real-time person-to-person services between two individuals (for example tutoring students, medical consultations, real estate tours, or fitness training), you may use purchase methods other than in-app purchase to collect those payments.
3.1.3(e) Goods and Services Outside of the App: If your app enables people to purchase physical goods or services that will be consumed outside of the app, you must use purchase methods other than in-app purchase to collect those payments, such as Apple Pay or traditional credit card entry.
My current use case is a subscription Person-to-Person that will be consumed outside of the app. So I'm affected by both rules and I must use a different purchase methods other than in-app purchase to be compliant.
So I don't understand why the extension limit this use cases.
Describe the solution you'd like
I would like to use the extension to subscribe the users of a mobile app to stripe, since we are selling services outside of the app.