Skip to content

Commit

Permalink
Address sheet (#2020)
Browse files Browse the repository at this point in the history
* feat: create android binding for addresssheet

* feat: create addressheet model

* feat: add correct mapping for addressheet

* feat: create ios addressheet implementation

* fix auto close ios addressheet

* fix: give widget small height to fix flutter requirements

* chore: add documentation

---------

Co-authored-by: Remon <[email protected]>
  • Loading branch information
remonh87 and Remon authored Jan 13, 2025
1 parent b76793a commit 3ad42b9
Show file tree
Hide file tree
Showing 16 changed files with 1,768 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
]
],
["Mobile elements",
["Address Sheet", "/address_sheet"],
["Customer Sheet", "/customer_sheet"]
],
["Regional payments", [
Expand Down
167 changes: 167 additions & 0 deletions docs/address-sheet.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Address Sheet Implementation Guide

The Address Sheet is a pre-built UI component that allows you to collect shipping or billing addresses from your users in a Flutter application using Stripe's native UI components.

## Installation

Make sure you have the `flutter_stripe` package installed in your `pubspec.yaml`:

```yaml
dependencies:
flutter_stripe: ^latest_version
```
### 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`

Check warning on line 27 in docs/address-sheet.mdx

View workflow job for this annotation

GitHub Actions / Typo CI

commmand

"commmand" is a typo. Did you mean "command"?
- 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.


### Basic Implementation

Here's a basic example of how to implement the Address Sheet:

```dart
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
AddressSheet(
onSubmit: (details) {
// Handle the submitted address details
print(details.toJson());
},
onError: (error) {
// Handle any errors that occur
print(error.error.localizedMessage);
},
params: AddressSheetParams(),
)
```

## Parameters

### Required Parameters

- `onSubmit`: Callback function that is called when the user successfully submits their address information. Receives a `CollectAddressResult` object containing:
- `address`: The collected address details
- `name`: The customer's name
- `phoneNumber`: The customer's phone number (optional)

- `onError`: Callback function that is called when an error occurs or when the user closes the sheet before submitting. Receives a `StripeException` object.

- `params`: An `AddressSheetParams` object that configures the address sheet behavior and appearance.

### Address Result Structure

The `CollectAddressResult` object contains the following information:

```dart
class CollectAddressResult {
final Address address;
final String name;
final String? phoneNumber;
}
```

The `Address` object contains standard address fields like street, city, state, postal code, and country.

## Example Implementation

Here's a complete example showing how to implement the Address Sheet in a Flutter screen:

```dart
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
class AddressSheetExample extends StatefulWidget {
const AddressSheetExample({super.key});
@override
State<AddressSheetExample> createState() => _AddressSheetExampleState();
}
class _AddressSheetExampleState extends State<AddressSheetExample> {
String? result;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Address Sheet Example'),
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
AddressSheet(
onError: (error) {
setState(() {
result = error.error.localizedMessage;
});
},
onSubmit: (details) {
setState(() {
result = details.toJson().toString();
});
},
params: AddressSheetParams(),
),
SizedBox(height: 20),
Text(result ?? ''),
],
),
),
);
}
}
```

### Customization

You can customize the Address Sheet behavior by configuring the `AddressSheetParams`. This allows you to:
- Set default values
- Configure which fields are required
- Customize the appearance
- Set specific country restrictions

## Platform Support

The Address Sheet is supported on both iOS and Android platforms, providing a native UI experience on each platform.

## Best Practices

1. Always handle both the `onSubmit` and `onError` callbacks to ensure a good user experience.
2. Validate the collected address information before using it in your application.
3. Consider implementing proper error handling and display appropriate error messages to users.
4. Store the collected address information securely if you need to reuse it later.

## Related Resources

- [Stripe Documentation](https://stripe.com/docs)
- [Flutter Stripe Package](https://pub.dev/packages/flutter_stripe)
57 changes: 57 additions & 0 deletions example/lib/screens/address_sheet/address_sheet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:stripe_example/utils.dart';
import 'package:stripe_example/widgets/example_scaffold.dart';
import 'package:stripe_example/widgets/response_card.dart';

class AddressSheetExample extends StatefulWidget {
const AddressSheetExample({super.key});

@override
State<AddressSheetExample> createState() => _AddressSheetExampleState();
}

class _AddressSheetExampleState extends State<AddressSheetExample> {
bool isCompleted = false;

String? result;

@override
void initState() {
super.initState();
}

@override
void dispose() {
super.dispose();
}

@override
Widget build(BuildContext context) {
return ExampleScaffold(
title: 'Addresssheet',
tags: ['Address sheet'],
padding: EdgeInsets.symmetric(horizontal: 16),
children: [
AddressSheet(
onError: (error) {
setState(() {
result = error.error.localizedMessage;
});
},
onSubmit: (details) {
setState(() {
result = details.toJson().toPrettyString();
});
},
params: AddressSheetParams(),
),
Divider(),
SizedBox(height: 20),
ResponseCard(
response: result ?? '',
),
],
);
}
}
8 changes: 8 additions & 0 deletions example/lib/screens/screens.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:stripe_example/screens/address_sheet/address_sheet.dart';
import 'package:stripe_example/screens/customer_sheet/customer_sheet_screen.dart';
import 'package:stripe_example/screens/others/can_add_to_wallet_screen.dart';
import 'package:stripe_example/screens/payment_sheet/express_checkout/express_checkout_element.dart';
Expand Down Expand Up @@ -146,6 +147,13 @@ class Example extends StatelessWidget {
)
],
),
ExampleSection(title: 'Address sheet', children: [
Example(
title: 'Address sheet',
builder: (context) => AddressSheetExample(),
platformsSupported: [DevicePlatform.android, DevicePlatform.ios],
),
]),
ExampleSection(title: 'Customer sheet', children: [
Example(
title: 'Customer sheet',
Expand Down
1 change: 1 addition & 0 deletions packages/stripe/lib/flutter_stripe.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export 'package:stripe_platform_interface/stripe_platform_interface.dart';

export 'src/model/apple_pay_button.dart';
export 'src/stripe.dart';
export 'src/widgets/adress_sheet.dart';
// export 'src/widgets/apple_pay_button.dart';
export 'src/widgets/aubecs_debit_form.dart';
export 'src/widgets/card_field.dart';
Expand Down
115 changes: 115 additions & 0 deletions packages/stripe/lib/src/widgets/adress_sheet.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import 'package:flutter/foundation.dart';

Check warning on line 1 in packages/stripe/lib/src/widgets/adress_sheet.dart

View workflow job for this annotation

GitHub Actions / Typo CI

Filename: packages/stripe/lib/src/widgets/adress_sheet.dart

"adress" in the filename is a typo. Did you mean "dress"?
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_stripe/flutter_stripe.dart';

class AddressSheet extends StatelessWidget {
const AddressSheet({
required this.onSubmit,
required this.onError,
required this.params,
super.key,
});

/// Called when the user submits their information
final OnAddressSheetSubmit onSubmit;

/// Called when the user taps the button to close the sheet before submitting their information, or when an error occurs
final OnAddressSheetError onError;

/// The parameters for the address sheet
final AddressSheetParams params;

@override
Widget build(BuildContext context) {
return _AddressSheet(
onSubmit: onSubmit,
onError: onError,
addressSheetParams: params,
);
}
}

class _AddressSheet extends StatefulWidget {
const _AddressSheet({
required this.onSubmit,
required this.onError,
required this.addressSheetParams,
});

final AddressSheetParams addressSheetParams;
final OnAddressSheetSubmit onSubmit;
final OnAddressSheetError onError;

@override
State<_AddressSheet> createState() => _AddressSheetState();
}

class _AddressSheetState extends State<_AddressSheet> {
static const _viewType = 'flutter.stripe/address_sheet';
MethodChannel? _methodChannel;

void onPlatformViewCreated(int viewId) {
_methodChannel = MethodChannel('flutter.stripe/address_sheet/$viewId');
_methodChannel?.setMethodCallHandler((call) async {
if (call.method == 'onSubmitAction') {
final tmp = Map<String, dynamic>.from(call.arguments as Map);
final tmpAdress = Map<String, dynamic>.from(tmp['address'] as Map);

Check warning on line 59 in packages/stripe/lib/src/widgets/adress_sheet.dart

View workflow job for this annotation

GitHub Actions / Typo CI

tmpAdress

"tmpAdress" is a typo. Did you mean "tmpDress"?

widget.onSubmit(
CollectAddressResult(
address: Address.fromJson(tmpAdress),

Check warning on line 63 in packages/stripe/lib/src/widgets/adress_sheet.dart

View workflow job for this annotation

GitHub Actions / Typo CI

tmpAdress

"tmpAdress" is a typo. Did you mean "tmpDress"?
name: tmp['name'] as String,
phoneNumber: tmp['phone'] as String?,
),
);
} else if (call.method == 'onErrorAction') {
final tmp = Map<String, dynamic>.from(call.arguments as Map);
final foo = Map<String, dynamic>.from(tmp['error'] as Map);

widget.onError(
StripeException(error: LocalizedErrorMessage.fromJson(foo)));
}
});
}

@override
Widget build(BuildContext context) {
return SizedBox(
height: 10,
child: defaultTargetPlatform == TargetPlatform.iOS
? UiKitView(
viewType: _viewType,
creationParamsCodec: const StandardMessageCodec(),
creationParams: widget.addressSheetParams.toJson(),
onPlatformViewCreated: onPlatformViewCreated,
)
: PlatformViewLink(
surfaceFactory: (context, controller) {
return AndroidViewSurface(
controller: controller as AndroidViewController,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
gestureRecognizers: const <Factory<
OneSequenceGestureRecognizer>>{},
);
},
onCreatePlatformView: (params) {
onPlatformViewCreated(params.id);
return PlatformViewsService.initExpensiveAndroidView(
id: params.id,
viewType: _viewType,
layoutDirection: TextDirection.ltr,
creationParams: widget.addressSheetParams.toJson(),
creationParamsCodec: const StandardMessageCodec(),
)
..addOnPlatformViewCreatedListener(
params.onPlatformViewCreated)
..create();
},
viewType: _viewType,
),
);
}
}
Loading

0 comments on commit 3ad42b9

Please sign in to comment.