-
-
Notifications
You must be signed in to change notification settings - Fork 534
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Sync with React Native 0.29.0 (#1335)
* handle Android React Native glue changes * update iOS glue code * BREAKING: remove deprecated apple pay methods * feat: add possibility to create deffered payment for payment sheet * sync Android with 0.30.0 * sync iOS with 0.30.0 * FIX: #1376 focus issue on iOS * feat: finish deffered callback flow * feat: add handlenextaction for setupinten * fix deferred configuration, implement missing methods on iOS * workaround for missing confirmHandler object on iOS * DOC: create docs for defered payment * fixed yarn file --------- Co-authored-by: Remon <>
- Loading branch information
Showing
49 changed files
with
4,187 additions
and
3,215 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
--- | ||
title: Payment Sheet deferred payment | ||
description: Accept a payment with Payment Sheet attaching the payment intent after collecting payment details. | ||
--- | ||
|
||
# Accept a deferred payment - Payment Sheet deferred | ||
|
||
### Securely accept payments online. | ||
|
||
<img src="https://b.stripecdn.com/docs-statics-srv/assets/bd5ffc39f955d37bb53ef7d854ae4474.png" height="36" /> | ||
|
||
Integrate Stripe’s prebuilt payment UI into the checkout of your app with the PaymentSheet class. | ||
|
||
This integration combines all of the steps required to pay—collecting payment details and confirming the payment—into a single sheet that displays on top of your app. | ||
|
||
|
||
## 1. Set up Stripe [Server Side] [Client Side] | ||
|
||
First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register). | ||
|
||
#### Server-side | ||
|
||
This integration requires endpoints on your server that talk to the Stripe API. Use one official libraries for access to the Stripe API from your server. [Follow the steps here](https://stripe.com/docs/payments/accept-a-payment?platform=ios&ui=payment-sheet#setup-server-side) | ||
|
||
#### Client-side | ||
|
||
The Flutter SDK is open source, fully documented. | ||
|
||
To install the SDK, follow these steps: | ||
- Run the commmand `flutter pub add flutter_stripe` | ||
- This will add a line like this to your project's pubspec.yaml with the latest package version | ||
|
||
|
||
For details on the latest SDK release and past versions, see the [Releases](https://github.com/flutter-stripe/flutter_stripe/releases) page on GitHub. To receive notifications when a new release is published, [watch releases for the repository](https://docs.github.com/en/github/managing-subscriptions-and-notifications-on-github/managing-subscriptions-for-activity-on-github/viewing-your-subscriptions#watching-releases-for-a-repository). | ||
|
||
|
||
When your app starts, configure the SDK with your Stripe [publishable key](https://dashboard.stripe.com/) so that it can make requests to the Stripe API. | ||
|
||
```dart | ||
void main() async { | ||
Stripe.publishableKey = stripePublishableKey; | ||
runApp(const App()); | ||
} | ||
``` | ||
|
||
Use your [test mode](https://stripe.com/docs/keys#obtain-api-keys) keys while you test and develop, and your [live mode](https://stripe.com/docs/keys#test-live-modes) keys when you publish your app. | ||
|
||
## 2. Add an enpoint [Server Side] | ||
|
||
First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register). | ||
|
||
#### Server-side | ||
|
||
This integration uses three Stripe API objects: | ||
|
||
1. A [PaymentIntent](https://stripe.com/docs/api/payment_intents). Stripe uses this to represent your intent to collect payment from a customer, tracking your charge attempts and payment state changes throughout the process. | ||
|
||
2. A [Customer](https://stripe.com/docs/api/customers) (optional). To set up a card for future payments, it must be attached to a Customer. Create a Customer object when your customer creates an account with your business. If your customer is making a payment as a guest, you can create a Customer object before payment and associate it with your own internal representation of the customer’s account later. | ||
|
||
3. A Customer Ephemeral Key (optional). Information on the Customer object is sensitive, and can’t be retrieved directly from an app. An Ephemeral Key grants the SDK temporary access to the Customer. | ||
|
||
>> If you never save cards to a Customer and don’t allow returning Customers to reuse saved cards, you can omit the Customer and Customer Ephemeral Key objects from your integration. | ||
For security reasons, your app can’t create these objects. Instead, add an endpoint on your server that: | ||
|
||
1. Retrieves the Customer, or creates a new one. | ||
2. Creates an Ephemeral Key for the Customer. | ||
3. Creates a PaymentIntent, passing the Customer id. | ||
4. Returns the Payment Intent’s [client secret](https://stripe.com/docs/api/payment_intents/object#payment_intent_object-client_secret), the Ephemeral Key’s secret, and the Customer’s id to your app. | ||
|
||
Check examples implementations for your server [here](https://stripe.com/docs/payments/accept-a-payment?platform=ios#add-server-endpoint) | ||
|
||
## 3. Integrate the payment sheet [Client Side] | ||
|
||
Before displaying the payment sheet, your checkout page should: | ||
|
||
- Show the products being purchased and the total amount | ||
- Collect any required shipping information | ||
- Include a checkout button to present Stripe’s UI | ||
|
||
Next, integrate Stripe’s prebuilt payment UI into your app’s checkout. Unlike the standard payment flow where you supply a `setupIntentClientSecret` or an `paymentIntentClientSecret` you provide an intent configuration. | ||
|
||
First you need to inititalize the payment sheet | ||
|
||
```dart | ||
Future<void> initPaymentSheet() async { | ||
try { | ||
// 1. create payment intent on the server | ||
final data = await _createTestPaymentSheet(); | ||
// 2. initialize the payment sheet | ||
await Stripe.instance.initPaymentSheet( | ||
paymentSheetParameters: SetupPaymentSheetParameters( | ||
// Enable custom flow | ||
customFlow: false, | ||
// Main params | ||
merchantDisplayName: 'Flutter Stripe Store Demo', | ||
intentConfiguration: IntentConfiguration( | ||
mode: IntentMode( | ||
currencyCode: 'USD', | ||
amount: 1500, | ||
), | ||
confirmHandler: (method, saveFuture) { | ||
// This is the method where you call the server to create a paymentintent for the payment method id supplied in the callback. | ||
_createIntentAndConfirmToUser(method.id); | ||
}), | ||
// Customer keys | ||
customerEphemeralKeySecret: data['ephemeralKey'], | ||
customerId: data['customer'], | ||
// Extra options | ||
testEnv: true, | ||
applePay: true, | ||
googlePay: true, | ||
style: ThemeMode.dark, | ||
merchantCountryCode: 'DE', | ||
), | ||
); | ||
setState(() { | ||
_ready = true; | ||
}); | ||
} catch (e) { | ||
ScaffoldMessenger.of(context).showSnackBar( | ||
SnackBar(content: Text('Error: $e')), | ||
); | ||
rethrow; | ||
} | ||
} | ||
``` | ||
|
||
When the customer taps a **Checkout** button, call present to present the payment sheet. After the customer completes the payment, the sheet is dismissed. | ||
```dart | ||
await Stripe.instance.presentPaymentSheet(); | ||
``` | ||
|
||
After collecting the payment details the callback provided in the `intentConfiguration` is called you need to make sure to do two things: | ||
1. Create a payment intent on the server with the payment method id retrieved from the callback. | ||
2. Call `intentCreationCallback` in the sdk with the client secret of the payment intent created on the server. | ||
|
||
```dart | ||
Future<void> _createIntentAndConfirmToUser(String paymentMethodId) async { | ||
final url = Uri.parse('$kApiUrl/payment-intent-for-payment-sheet'); | ||
final response = await http.post( | ||
url, | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: json.encode({ | ||
'paymentMethodId': paymentMethodId, | ||
}), | ||
); | ||
final body = json.decode(response.body); | ||
if (body['error'] != null) { | ||
throw Exception(body['error']); | ||
} | ||
await Stripe.instance.intentCreationCallback( | ||
IntentCreationCallbackParams(clientSecret: body['clientSecret'])); | ||
} | ||
``` | ||
|
||
Unless your business model requires immediate payment (e.g., an on-demand service), don’t assume you have received payment at this point. Instead, inform the customer that you confirmed their order and notify them by email when you fulfill their order in the next step. | ||
|
||
>> ADD MORE PAYMENT METHODS | ||
>> While cards finish payment by this point, other payment methods can take time to process before payment is complete. By not assuming you’ve received payment on the client, you can add additional payment methods in the future without updating your app. | ||
|
||
## 4. Handle post-payment events | ||
|
||
Stripe sends a [`payment_intent.succeeded`](https://stripe.com/docs/api/events/types#event_types-payment_intent.succeeded) event when the payment completes. Use the Dashboard, a custom webhook, or a partner solution to receive these events and run actions, like sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow. | ||
|
||
Listen for these events rather than waiting on a callback from the client. On the client, the customer could close the browser window or quit the app before the callback executes. Setting up your integration to listen for asynchronous events also makes it easier to accept more payment methods in the future. Check out our [guide to payment methods](https://stripe.com/payments/payment-methods-guide) to see the differences between all supported payment methods. | ||
|
||
|
||
## 5. Test the integration | ||
|
||
Several test cards are available for you to use in test mode to make sure this integration is ready. Use them with any CVC, postal code, and future expiration date. | ||
|
||
| NUMBER | DESCRIPTION | | ||
| ------ | ------- | | ||
| 4242424242424242 | Succeeds and immediately processes the payment. | | ||
| 4000002500003155 | Requires authentication. Stripe will trigger a modal asking for the customer to authenticate. | | ||
| 4000000000009995 | Always fails with a decline code of insufficient_funds. | | ||
|
||
For the full list of test cards see the guide on [testing](https://stripe.com/docs/testing). |
180 changes: 180 additions & 0 deletions
180
example/lib/screens/payment_sheet/payment_sheet_deffered_screen.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import 'dart:convert'; | ||
Check warning on line 1 in example/lib/screens/payment_sheet/payment_sheet_deffered_screen.dart GitHub Actions / Typo CIFilename: example/lib/screens/payment_sheet/payment_sheet_deffered_screen.dart
|
||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_stripe/flutter_stripe.dart'; | ||
import 'package:http/http.dart' as http; | ||
import 'package:stripe_example/config.dart'; | ||
import 'package:stripe_example/screens/payment_sheet/payment_sheet_screen_custom_flow.dart'; | ||
import 'package:stripe_example/widgets/example_scaffold.dart'; | ||
import 'package:stripe_example/widgets/loading_button.dart'; | ||
|
||
class PaymentSheetDefferedScreen extends StatefulWidget { | ||
@override | ||
_PaymentSheetScreenState createState() => _PaymentSheetScreenState(); | ||
} | ||
|
||
class _PaymentSheetScreenState extends State<PaymentSheetDefferedScreen> { | ||
int step = 0; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return ExampleScaffold( | ||
title: 'Payment Sheet', | ||
tags: ['Single Step'], | ||
children: [ | ||
Stepper( | ||
controlsBuilder: emptyControlBuilder, | ||
currentStep: step, | ||
steps: [ | ||
Step( | ||
title: Text('Init payment'), | ||
content: LoadingButton( | ||
onPressed: initPaymentSheet, | ||
text: 'Init payment sheet', | ||
), | ||
), | ||
Step( | ||
title: Text('Confirm payment'), | ||
content: LoadingButton( | ||
onPressed: confirmPayment, | ||
text: 'Pay now', | ||
), | ||
), | ||
], | ||
), | ||
], | ||
); | ||
} | ||
|
||
Future<void> _createIntentAndConfirmToUser(String paymentMethodId) async { | ||
final url = Uri.parse('$kApiUrl/payment-intent-for-payment-sheet'); | ||
final response = await http.post( | ||
url, | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
body: json.encode({ | ||
'paymentMethodId': paymentMethodId, | ||
}), | ||
); | ||
final body = json.decode(response.body); | ||
if (body['error'] != null) { | ||
throw Exception(body['error']); | ||
} | ||
|
||
|
||
await Stripe.instance.intentCreationCallback( | ||
IntentCreationCallbackParams(clientSecret: body['clientSecret'])); | ||
} | ||
|
||
Future<void> initPaymentSheet() async { | ||
try { | ||
// // 1. create payment intent on the server | ||
// final data = await _createTestPaymentSheet(); | ||
|
||
// create some billingdetails | ||
final billingDetails = BillingDetails( | ||
name: 'Flutter Stripe', | ||
email: '[email protected]', | ||
phone: '+48888000888', | ||
address: Address( | ||
city: 'Houston', | ||
country: 'US', | ||
line1: '1459 Circle Drive', | ||
line2: '', | ||
state: 'Texas', | ||
postalCode: '77063', | ||
), | ||
); // mocked data for tests | ||
|
||
// 2. initialize the payment sheet | ||
await Stripe.instance.initPaymentSheet( | ||
paymentSheetParameters: SetupPaymentSheetParameters( | ||
// Main params | ||
merchantDisplayName: 'Flutter Stripe Store Demo', | ||
intentConfiguration: IntentConfiguration( | ||
mode: IntentMode( | ||
currencyCode: 'USD', | ||
amount: 1500, | ||
), | ||
confirmHandler: (method, saveFuture) { | ||
_createIntentAndConfirmToUser(method.id); | ||
}), | ||
|
||
// Extra params | ||
primaryButtonLabel: 'Pay now', | ||
applePay: PaymentSheetApplePay( | ||
merchantCountryCode: 'DE', | ||
), | ||
googlePay: PaymentSheetGooglePay( | ||
merchantCountryCode: 'DE', | ||
testEnv: true, | ||
), | ||
|
||
style: ThemeMode.dark, | ||
appearance: PaymentSheetAppearance( | ||
colors: PaymentSheetAppearanceColors( | ||
background: Colors.lightBlue, | ||
primary: Colors.blue, | ||
componentBorder: Colors.red, | ||
), | ||
shapes: PaymentSheetShape( | ||
borderWidth: 4, | ||
shadow: PaymentSheetShadowParams(color: Colors.red), | ||
), | ||
primaryButton: PaymentSheetPrimaryButtonAppearance( | ||
shapes: PaymentSheetPrimaryButtonShape(blurRadius: 8), | ||
colors: PaymentSheetPrimaryButtonTheme( | ||
light: PaymentSheetPrimaryButtonThemeColors( | ||
background: Color.fromARGB(255, 231, 235, 30), | ||
text: Color.fromARGB(255, 235, 92, 30), | ||
border: Color.fromARGB(255, 235, 92, 30), | ||
), | ||
), | ||
), | ||
), | ||
billingDetails: billingDetails, | ||
), | ||
); | ||
setState(() { | ||
step = 1; | ||
}); | ||
} catch (e) { | ||
ScaffoldMessenger.of(context).showSnackBar( | ||
SnackBar(content: Text('Error: $e')), | ||
); | ||
rethrow; | ||
} | ||
} | ||
|
||
Future<void> confirmPayment() async { | ||
try { | ||
// 3. display the payment sheet. | ||
await Stripe.instance.presentPaymentSheet(); | ||
|
||
setState(() { | ||
step = 0; | ||
}); | ||
|
||
ScaffoldMessenger.of(context).showSnackBar( | ||
SnackBar( | ||
content: Text('Payment succesfully completed'), | ||
), | ||
); | ||
} on Exception catch (e) { | ||
if (e is StripeException) { | ||
ScaffoldMessenger.of(context).showSnackBar( | ||
SnackBar( | ||
content: Text('Error from Stripe: ${e.error.localizedMessage}'), | ||
), | ||
); | ||
} else { | ||
ScaffoldMessenger.of(context).showSnackBar( | ||
SnackBar( | ||
content: Text('Unforeseen error: ${e}'), | ||
), | ||
); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.