# Flutter SDK Integration

The xMoney Flutter SDK exposes native Payment Sheet, Payment Element, Apple Pay, and Google Pay controls through one Dart API. Card fields remain inside native SDK views; PAN and CVV do not cross the Dart platform channel.

Current release: **0.0.3**, built on the iOS and Android SDKs **1.1.0**.

## Requirements

- Flutter 3.44 or later
- Dart 3.10 or later
- iOS 15 or later
- Android `minSdk` 23
- A publishable xMoney key and a backend that signs orders


Do not ship a secret key
Store the xMoney private key on your backend. The app should receive only the publishable key, order payload, and checksum.

## 1. Install the SDK

Add the package to `pubspec.yaml`:

```yaml
dependencies:
  xmoney: ^0.0.3
```

Fetch packages and install iOS pods:

```bash
flutter pub get
cd ios
pod install
```

Native plugin changes need a full rebuild of the app. Hot reload and hot restart do not pick them up. If the plugin is missing from the native build, the SDK throws `XMoneyPaymentError` with code `NOT_LINKED`.

### Android configuration

Payment Sheet, Payment Element, and Google Pay present from a `FragmentActivity`.

1. Make `MainActivity` extend `FlutterFragmentActivity`. A plain `FlutterActivity` fails with `NO_PRESENTER`.

```kotlin
import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity : FlutterFragmentActivity()
```
2. Set the app `minSdk` to at least 23.
3. Set `android:windowSoftInputMode="adjustResize"` on that activity so card fields stay above the keyboard.
4. In `android/settings.gradle.kts`, declare the Android library plugin and the Compose compiler plugin. Use the same versions as your app's Android Gradle Plugin and Kotlin plugin:

```kotlin
plugins {
    id("com.android.library") version "9.1.0" apply false
    id("org.jetbrains.kotlin.plugin.compose") version "2.4.0" apply false
}
```
If `android/gradle.properties` sets `android.builtInKotlin=false`, remove that line.
5. Release builds that use R8 merge the plugin's consumer rules automatically. Add extra `-dontwarn` rules only if the build asks for them.


## 2. Create a PaymentIntent

Your backend creates and signs the order. Build an intent from the returned values:

```dart
final intent = PaymentIntent(
  orderPayload: payloadFromYourServer,
  orderChecksum: checksumFromYourServer,
);
```

See [Create the order on your backend](/guides/checkout/inline-checkout/web-integration#3-create-the-order-on-your-backend) for the shared server responsibilities.

## 3. Choose a payment surface

### Payment Sheet

Payment Sheet owns the complete checkout UI and Pay button.

```dart
import 'package:xmoney/xmoney.dart';

final sheet = PaymentSheetController();

Future<void> pay() async {
  await sheet.init(PaymentConfig(
    publicKey: 'pk_test_...',
    paymentMethods: PaymentMethodsConfig(
      applePay: ApplePayConfig(enabled: true),
      googlePay: GooglePayConfig(enabled: true),
    ),
    card: CardConfig(
      savedCards: SavedCardsConfig(enabled: true),
    ),
  ));

  final result = await sheet.present(
    intent,
    onEvent: (event) {
      if (event is PaymentSheetReadyEvent) {
        hideCheckoutLoader();
      }
    },
  );

  handlePaymentResult(result);
}
```

`sheet.loading` is `true` while `init` is in flight. If you do not need a controller, call `PaymentSheet.init` and `PaymentSheet.present` directly.

Idle close, drag, back, or scrim dismissal returns `PaymentResultCanceled` without consuming the order. Once payment or 3DS starts, terminal results consume the signed order.

### Payment Element

Payment Element places the native payment form inside your Flutter layout.

```dart
import 'package:xmoney/xmoney.dart';

PaymentElement(
  configuration: PaymentConfig(
    publicKey: 'pk_test_...',
    paymentMethods: PaymentMethodsConfig(
      applePay: ApplePayConfig(enabled: true),
      googlePay: GooglePayConfig(enabled: true),
    ),
  ),
  intent: intent,
  onEvent: (event) {
    if (event is PaymentElementReadyEvent) {
      hideCheckoutLoader();
    }
  },
  onResult: (result) {
    hideCheckoutLoader();
    handlePaymentResult(result);
  },
);
```

Keep merchant loading until `PaymentElementReadyEvent`, or until `onResult` delivers `PaymentResultFailed` when the first load fails. Payment Element adds no outer page padding or background. Keep it mounted at its real size while it binds an order, and provide the surrounding layout in your app.

To use your own Pay button, hide the SDK button and call `confirm()` after `PaymentElementReadyEvent`:

```dart
final controller = PaymentElementController();

PaymentElement(
  controller: controller,
  configuration: PaymentConfig(
    publicKey: 'pk_test_...',
    card: CardConfig(
      submitButton: SubmitButtonConfig(visible: false),
    ),
  ),
  intent: intent,
  onResult: handlePaymentResult,
);

FilledButton(
  onPressed: () => controller.confirm(),
  child: const Text('Pay'),
);
```

Use `isInteractionEnabled` from `PaymentElementAvailabilityEvent` to disable the merchant button while the order updates or payment is processing.

### Standalone wallets

Apple Pay is available only on iOS; Google Pay is available only on Android. A wallet button on an unsupported platform renders nothing.

```dart
import 'package:xmoney/xmoney.dart';

ApplePayButton(
  configuration: PaymentConfig(publicKey: 'pk_test_...'),
  intent: intent,
  appearance: WalletAppearance(
    color: WalletButtonColor.black,
    type: WalletButtonType.buy,
  ),
  onResult: handlePaymentResult,
);

GooglePayButton(
  configuration: PaymentConfig(publicKey: 'pk_test_...'),
  intent: intent,
  appearance: WalletAppearance(
    color: WalletButtonColor.black,
    type: WalletButtonType.buy,
  ),
  onResult: handlePaymentResult,
);
```

For imperative flows, use `ApplePay.init`, `getState`, and `present`, or the corresponding `GooglePay` methods. Check `isAvailable` and `isReady` before showing custom wallet UI.

`WalletPayReadyEvent` fires once the mount order is bound and the button has drawn. A failed mount bind sends `PaymentResultFailed` and no ready event, so end a merchant loader on both. A wallet dismissal before authorization returns `PaymentResultCanceled` and does not consume the intent.

See [Apple Pay](/guides/payments/payment-methods/apple-pay) and [Google Pay](/guides/payments/payment-methods/google-pay) for activation requirements.

## Handle PaymentResult

```dart
void handlePaymentResult(PaymentResult result) {
  switch (result) {
    case PaymentResultComplete(:final transaction):
      confirmOrderOnBackend(transaction);
    case PaymentResultFailed(:final error):
      showPaymentError(error.message);
    case PaymentResultCanceled():
      showCheckout();
  }
}
```

Use the result to update the app, then confirm final payment status on your backend before fulfillment.

## Update a mounted Payment Element

When the amount, currency, or cart changes, request a new signed order and rebind the mounted element:

```dart
await controller.updateOrder(nextIntent);
```

Changing the `intent` passed to `PaymentElement` also updates the order. Keep the widget mounted; use `isInteractionEnabled` for loading and button state.

`updateOrder` does not lock Pay while your server creates `nextIntent`. When the shopper changes the cart, pass the server call to `updateOrderWith` instead. Pay locks immediately and stays locked until the returned order is bound:

```dart
try {
  final next = await controller.updateOrderWith(() async {
    await Future<void>.delayed(const Duration(milliseconds: 300));
    if (!controller.isUpdateCurrent) throw StateError('superseded');
    return createOrderOnYourServer(cartTotal);
  });
} on XMoneyPaymentError catch (error) {
  if (error.code == 'SUPERSEDED_UPDATE_ORDER') return; // a newer update owns the result
  showPaymentError(error.message);
}
```

- A newer update throws `SUPERSEDED_UPDATE_ORDER` from the older call. Check `isUpdateCurrent` after waiting so the older callback does not call your server.
- During a charge, a different order throws `PAYMENT_IN_PROGRESS`.
- If your callback throws, the previous order stays payable.
- If binding fails after an order was already bound, `PaymentElementUpdateFailedEvent` is emitted and Pay stays locked on the previous order until a later update succeeds. `onResult` is not called.


`WalletPayButtonController` has the same `updateOrder`, `updateOrderWith`, and `isUpdateCurrent` for `ApplePayButton` and `GooglePayButton`. See [Loading, order updates, and failures](/guides/checkout/native-sdks#loading-order-updates-and-failures) for the shared contract.

For live styling changes, call `updateAppearance`, `updateStyle`, `updateLocale`, or `updateWalletAppearance` on `PaymentElementController`.

## Configuration

`PaymentConfig` controls saved cards, card validation, field grouping, submit-button intent, locale, light/dark style, colors, and radii.

```dart
final configuration = PaymentConfig(
  publicKey: 'pk_test_...',
  card: CardConfig(
    savedCards: SavedCardsConfig(enabled: true, optInVisible: true),
    validationMode: ValidationMode.onTouched,
    inputs: CardInputsConfig(grouping: CardGrouping.condensed),
  ),
  options: OptionsConfig(
    locale: 'en-US',
    style: UserInterfaceStyle.automatic,
  ),
);
```

Supported UI languages are English (`en`), Greek (`el`), Romanian (`ro`), Bulgarian (`bg`), Hungarian (`hu`), and Polish (`pl`). Region tags such as `pl-PL` are supported.

For cardholder name verification, provide `CardHolderVerification` in the card configuration and return `true` to continue:

```dart
card: CardConfig(
  cardHolderVerification: CardHolderVerification(
    name: CardHolderName(firstName: 'John', lastName: 'Doe'),
    onCardHolderVerification: (result) =>
        result.status == CardHolderMatchStatus.matched,
  ),
),
```

The callback must return immediately, without network or other async work. If it does not answer within 2 seconds, the SDK declines the check. The site must have cardholder name verification enabled.

## Next steps

- Review the [native SDK shared model](/guides/checkout/native-sdks).
- Use [test cards](/guides/payments/testing) in the test environment.
- Explore the complete [Flutter SDK samples](https://github.com/xMoney-Payments/xmoney-flutter/tree/main/example).