#l_ios-applepay
1 messages · Page 1 of 1 (latest)
đź‘‹ Welcome to your new thread!
⏲️ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.
⏱️ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.
đź”— This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1275860476282011749
📝 Have more to share? Add more details, code, screenshots, videos, etc. below.
Here is some further context.
Overview:
Our app is a white-labeled bike-sharing platform, and we manage payments using a combination of Flutter, Node.js, and Swift. We have one main Stripe account where we store all customer data and payment methods, and then we use connected Stripe accounts for each individual app instance.
Payment Method Storage:
- Setup Intents: When a user adds a payment method (either manually or via Apple Pay), we create a Setup Intent on our main Stripe account via our Node.js backend.
- PaymentSheet in Swift: We use the setupIntentClientSecret to initiate the PaymentSheet in Swift, allowing the user to add their card. After the payment method is added, we pass the Setup Intent ID back to the server, where we retrieve the payment method and attach it to the Stripe Customer in our main account using Stripe.PaymentMethodsResource.attach.
Trip Flow:
- Creating Payment Intents: When a user starts a trip, we generate a Payment Intent on the connected account linked to that specific app. This Payment Intent is created with capture_method: 'manual' to place a hold on the user's card for the trip. The amount is based on an estimated hold, which we later capture or cancel depending on the final trip cost.
- Cloning Payment Methods: We clone the stored payment method from the main account to the connected account by creating a new Payment Method linked to the main account’s customer ID and setting it as the payment_method for the Payment Intent.
Apple Pay Flow:
- Initial Issue: Initially, we allowed users to store their Apple Pay cards and used them directly for trip payments without showing the Apple Pay sheet again. This led to app store rejections, as Apple requires each new payment to be authorized by the user.
- Updated Implementation: To comply, I’m now developing a solution where, after creating the Payment Intent, we generate a PKPaymentRequest in Swift with a PKPaymentSummaryItem for the trip hold amount. We set the applicationData to the Payment Intent client secret and present the Apple Pay sheet to the user with the “Confirm with Side Button” or “Double Click to Pay” prompt. The Payment Intent status is requires_capture at this point, as all other statuses are handled when the Payment Intent is created.
Current Considerations:
- The Apple Pay sheet is shown primarily to meet Apple’s guidelines, ensuring that the user actively authorizes each payment. However, it doesn’t actually change the Payment Intent status, as it’s already set to requires_capture.
- I’m looking to confirm if this approach is fully compliant with Stripe and Apple Pay guidelines or if there’s anything more we should be doing to ensure everything is handled correctly.
Let me see if I can snag a mobile dev for you. Will circle back in a few
đź‘‹ Hey @glossy bloom I'll do my best to help but I'm not great at iOS development.
l_ios-applepay
That's a lot of words so give me a few minutes to read everything and try to understand the real questions
Since these new payments used the stored Apple Pay card and we didn't re-auth on each trip we had app store rejections.
Yes, this can be really tricky/confusing at first for developers. Apple requires you to basically collect "new card details" for each payment even though it's the same underlying card. They want you to show their "payment sheet" each time. It's what Uber, Starbucks and many other apps have to do too.
My updated flow to uses a PKPaymentRequest using the Payment Intent to show the Apple Pay sheet for each trip. The PaymentIntent is requires_capture when the sheet is shown. Does this approach meet Stripe and Apple Pay guidelines?
yes it should. Basically someone wants to ride, you show them a price and you have them "approve" the payment. Under the hood we get card details and a cryptogram to prove the end customer approved the payment. You can then confirm the PaymentIntent and hold the funds (since you use manual capture) until you're ready to capture those at the end of the trip.
Setup Intents: When a user adds a payment method (either manually or via Apple Pay)
yeah you definitely don't want ApplePay in that flow if it's for rides. You can save a card via ApplePay but it's more for subscriptions like a $100/month gym membership. It can also work for "incidentals" like if someone takes a drink or food in a hotel room. But it doesn't work as a "saved card" for future trips.
Okay I wanted to get this out first to confirm that your understanding is correct on the Apple rules and that it requires some special set up for your flow.
Okay re-read everything. Yes what you are doing looks correct and maps what we document in https://docs.stripe.com/apple-pay?platform=ios for example.
The "main issue" you will have is for a case where the trip's amount is higher than what you originally quoted. That's why I think Lyft and Lime and other apps like that authorize more than needed to cover for that case or cover tips.
Does that make sense overall?
Yes, this can be really tricky/confusing at first for developers. Apple requires you to basically collect "new card details" for each payment even though it's the same underlying card. They want you to show their "payment sheet" each time. It's what Uber, Starbucks and many other apps have to do too.
So I know most apps like Uber don't store my Apple Pay card (or allow you to add a card via Apple Pay), but in my Bird scooter app they do. So in the payment methods it shows the card I added via Apple Pay with the Apple Pay logo and the last 4 digits of the card. So I'm wondering how this is compliant?
yes it should. Basically someone wants to ride, you show them a price and you have them "approve" the payment. Under the hood we get card details and a cryptogram to prove the end customer approved the payment. You can then confirm the PaymentIntent and hold the funds (since you use manual capture) until you're ready to capture those at the end of the trip.
Okay so on our payment methods screen, instead of having an option to add a card via Apple Pay we should just have an option where the user can select Apple Pay, and we would just store this as a preference (rather than an actual card) so when they start a trip, if this preference is selected, what would we present to them to authorize the payment? Because to create a payment intent we need to have a payment method. Can you just walk me through the flow that we would need to follow in this case?
yeah you definitely don't want ApplePay in that flow if it's for rides. You can save a card via ApplePay but it's more for subscriptions like a $100/month gym membership. It can also work for "incidentals" like if someone takes a drink or food in a hotel room. But it doesn't work as a "saved card" for future trips.
Okay this makes sense, thanks for clarifying.
Oh I got confused at first because you use the "quote" feature to put your reply
I can't speak to what Bird is doing but that doesn't sound compliant to me from my understanding at least
Sorry I'm new to discord, let me know the best way to reply to individual parts of your reply
all good, was just surprised. Be careful I'll do the opposite and quote your text and then put my response below
so when they start a trip, if this preference is selected, what would we present to them to authorize the payment
Yes that's what the Starbucks app does for example for me. I have an Android so it's GooglePay but they have the same rules
Because to create a payment intent we need to have a payment method. Can you just walk me through the flow that we would need to follow in this case?
Hum there are many ways to integrate so it depends a lot on your situation.
The most common integration path is to first create the PaymentIntent server-side with the amount/currency and then confirm it client-side in your iOS app with the chosen payment method
Hum there are many ways to integrate so it depends a lot on your situation.
The most common integration path is to first create the PaymentIntent server-side with the amount/currency and then confirm it client-side in your iOS app with the chosen payment method
Okay I see. So right now in our app for trip holds, we don't actually confirm the payment intent (via Stripe.PaymentIntentsResource.confirm). The only time we call Stripe.PaymentIntentsResource.confirm is if for example, a user is purchasing something like a pass/membership, then we create a payment intent with confirm set to true and confirmation_method set to 'manual'. If the PI status is something like requires_action, then we handle that action natively, and call the purchase API again with the created PI id, which we then use to retrieve the payment intent and call Stripe.PaymentIntentsResource.confirm with the payment intent id. For trips though, we don't use Stripe.PaymentIntentsResource.confirm. We create a payment intent with the trip hold amount at the start of the trip with capture_method set to 'manual', then at the end of a trip if the trip total is <= hold amount, we capture the amount from the payment intent. Or if the total is higher, we create a new payment intent with the trip total and do not set capture_method, so the payment intent is processed immediately.
So based on your answer, you're saying for Apple Pay cases, we can create a payment intent for the trip hold amount without payment_method set, then use the payment intent client secret to confirm it in swift. What would we use to do this? PKPaymentRequest, PaymentSheet, or something else? Also I believe I tried to do this before (I will test to confirm) but got an error.
I believe the error was related to having to provide either a customer or payment_method when creating the payment intent and the way you're suggesting (adding the payment method after) would cause this error. Also I believe our main/connected account set up adds to this problem, since we store our customers and payment methods on our main acct and create payment intents on our connected accts by creating a new payment method, which is cloned from the main acct payment method.
Sorry for the long replies, will try to be more concise.
Yes it's the most canonical flow
1/ Create a PaymentIntent server-side
2/ Confirm it client-side
It's been "the way" to integrate since it launched in 2018 and has never changed. We have added new features recently but this really is the best way.
You mentioned Flutter in your original message which means you're likely using a third-party SDK we don't own or maintain. But overall you would use PaymentSheet which is the easiest, see https://docs.stripe.com/payments/accept-a-payment?platform=ios
We do all of our stripe operations via node backend or natively. But the app is developed in Flutter, so we use method channels to pass server data to iOS/Android to collect payment method details. Also, regarding the PaymentSheet, in my updated implementation I couldn't find a way to bypass the payment method form and go straight to the Apple Pay "Confirm with Side Button"/"Double Click to Pay" prompt so that's why I added the PKPaymentRequest. Do you know how to do this using PaymentSheet? In Uber, for example, if I have Apple Pay as my preferred payment, when I request a ride it goes directly to the Apple Pay prompt.
Okay so my read is you want none of our UIs in any way and you integrate straight with Apple's own APIs? If so that works fine, just not something I can help with
We do use stripe UI when adding a payment method (via setup intent), but since you're saying we can't store the Apple Pay card and should treat it as a new method each time then we wouldn't want to show the PaymentSheet where it shows the manual card form, since the user selected Apple Pay. What is your recommendation? You said apps like Uber, Lime, etc. do this same flow so just wondering what they do
you can't use SetupIntents at all here. It won't work. Your app will be rejected again.
Those apps I mentioned they don't use a SetupIntent (or the equivalent at that processor) They accept a real payment upfront.
Yes I understand. I'm just referring to setup intents in the context of using it to manually add a payment method (not with an Apple Pay card).
So would the apps you mentioned be set up so that if you have Apple Pay as a default payment selection, when you start a trip, the Apple Pay authorization sheet displayed is from an integration with Apple's own APIs? Is there no way to directly show this Apple Pay auth sheet using stripe and skip showing the manual card form where the user would have to press the Apple Pay button to get to the auth sheet?
There are again many ways to integrate. Like I can think of literally 10 ways to collect card details just with iOS. I think you want this specific flow that is ApplePay only: https://docs.stripe.com/apple-pay?platform=ios#present-payment-sheet
Okay great, thanks!
Also you said earlier: "The 'main issue' you will have is for a case where the trip's amount is higher than what you originally quoted. That's why I think Lyft and Lime and other apps like that authorize more than needed to cover for that case or cover tips."
So I have a follow up question to this: would you then suggest we charge a higher amount for a trip hold? And what happens if the trip ends up being more than the hold? Since you're saying we shouldn't store the Apple Pay card how can we charge for the trip without user authorization? I know apps like Uber are able to do things like charge you if you cause damage to the car (e.g., puking) so how does this work?
Yeah I would save the card, you're allowed. What you're not allowed is to re-use that saved card for what we call "on session payments" where the customer is in your app actively ready to pay.
So you basically have to save the card and remember why it was saved (a certain trip) so that you're allowed to do an extra "off session payment" on that card for that specific trip.
Ah I see. So how would we store the payment method? In our original set up, the setup intent is created on our main acct and so we can retrieve the payment method and attach it to the customer object (all on the main acct). But with the payment intent, this is created on the connected acct, so how we then create the payment method for the card in the main acct and attach it to the customer in the main acct?
That's completely impossible. So you can't
You would save the card on the connected account
You can set setup_future_usage: 'off_session' on the PaymentIntent and attach it to a Customer (a specific one created on the connected account) so that you can save that PaymentMethod and use it in the future for incidentals (or delete it once you don't need it anymore)
Got it. So for Apple Pay, we would not be able to store any payment method on the main account at all? Only on the connected?
correct
Is there no way to clone the PM from the connected to the main? Or is that only possible to clone from the main to the connected?
impossible!
Sorry, I get what you are trying to do, it really is just impossible for ApplePay and you have to do a custom flow really
Okay, this helps a lot thank you!
sure thing!