Skip to content

Misleading return type for initCustomerSheet #2283

@naipaka

Description

@naipaka

Describe the bug

The initCustomerSheet method has a misleading return type (Future<CustomerSheetResult?>) that doesn't match its actual behavior or the React Native SDK implementation.

Current behavior:

  • Type definition: Future<CustomerSheetResult?> (includes paymentOption, paymentMethod, error fields)
  • Actual return value: Always null on success, throws exception on error
  • React Native SDK: Promise<{ error?: StripeError }> (error only)

The CustomerSheetResult type suggests payment option information will be returned, but it never is. This creates confusion for developers who expect to receive payment selection data from initialization.

To Reproduce

Steps to reproduce the behavior:

  1. Call initCustomerSheet and check the return value:
final result = await Stripe.instance.initCustomerSheet(
  customerSheetInitParams: CustomerSheetInitParams(
    customerId: customerId,
    customerEphemeralKeySecret: ephemeralKey,
    merchantDisplayName: 'Test',
  ),
);
print(result);  // Always null
  1. Call retrieveCustomerSheetPaymentOptionSelection immediately after:
final selection = await Stripe.instance.retrieveCustomerSheetPaymentOptionSelection();
print(selection);  // Can contain payment option data
  1. Observe that initCustomerSheet returns null even when payment options exist, while retrieveCustomerSheetPaymentOptionSelection can retrieve them right after.

Expected behavior

The return type should match the actual behavior and align with React Native SDK:

Future<void> initCustomerSheet(...)  // Throws exception on error

Smartphone / tablet

  • Device: N/A (API design issue)
  • OS: iOS, Android (both platforms affected)
  • Package version: Current main branch
  • Flutter version: All versions

Additional context

Root cause analysis:

The Flutter implementation has a misleading type definition and unnecessary complexity:

// Current implementation
Future<CustomerSheetResult?> initCustomerSheet(...) async {
  final result = await _methodChannel.invokeMethod(...);

  if (result is List) {
    return null;  // iOS case
  } else {
    return _parseCustomerSheetResult(result);  // Android case
  }
}

Issues:

  1. Misleading type: CustomerSheetResult? suggests payment data might be returned, but it never is
  2. Unnecessary parsing: Calls _parseCustomerSheetResult even though result is always empty on success
  3. Platform-specific logic: Special handling for iOS (array) vs Android (map)

Native implementations return empty values on success:

  • iOS: Empty array []
  • Android: Empty map WritableNativeMap()

Both indicate success, but the Flutter layer treats them differently and returns null in both cases anyway.

React Native SDK comparison:

React Native correctly returns only error information:

const { error } = await CustomerSheet.initialize({...});

Source: https://stripe.dev/stripe-react-native/api-reference/variables/CustomerSheet.html

Evidence that return value is not used:

Even the official example ignores the return value:

// example/lib/screens/customer_sheet/customer_sheet_screen.dart:90
await Stripe.instance.initCustomerSheet(
  customerSheetInitParams: CustomerSheetInitParams(/* ... */),
);
// No variable assignment

Proposed fix:

Future<void> initCustomerSheet(
  CustomerSheetInitParams params,
) async {
  final result = await _methodChannel.invokeMethod('initCustomerSheet', {
    'params': params.toJson(),
    'customerAdapterOverrides': {},
  });

  // Check for errors only
  // iOS returns empty array, Android returns empty map on success - both are fine
  if (result is Map<String, dynamic> && result['error'] != null) {
    result['runtimeType'] = 'failed';
    throw StripeException.fromJson(result);
  }
}

Changes:

  1. Return type: Future<CustomerSheetResult?>Future<void>
  2. Simplified to error-checking only (no unnecessary parsing)
  3. Works with both iOS (array) and Android (map) seamlessly
  4. Update documentation if needed

Note: Native implementations (iOS/Android) don't need to be changed. The fix is purely on the Flutter side.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions