Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public class AirwallexPaymentFlutterPlugin: NSObject, FlutterPlugin {
case "initialize":
sdk = AirwallexSdk()
let arguments = call.arguments as! NSDictionary
sdk?.initialize(environment: arguments["environment"] as! String)
let saveLogToLocal = (arguments["saveLogToLocal"] as? Bool) ?? false
sdk?.initialize(environment: arguments["environment"] as! String, saveLogToLocal: saveLogToLocal)
result(nil)
case "presentEntirePaymentFlow":
let arguments = call.arguments as! NSDictionary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ class AirwallexSdk: NSObject {
private var paymentConsentID: String?
private var paymentSessionHandler: PaymentSessionHandler?

func initialize(environment: String) {
func initialize(environment: String, saveLogToLocal: Bool) {
let mode = AirwallexSDKMode.from(environment)
Airwallex.setMode(mode)
AWXAPIClientConfiguration.shared()
if saveLogToLocal {
Airwallex.enableLocalLogFile()
} else {
Airwallex.disableLocalLogFile()
}
AnalyticsLogger.shared().bindExtraCommonData(["framework": "flutter", "frameworkVersion": "0.3.0"])
}

Expand Down
41 changes: 41 additions & 0 deletions lib/airwallex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import '/types/payment_sheet_configuration.dart';
import 'airwallex_payment_flutter_platform_interface.dart';

class Airwallex {
/// Initializes the Airwallex SDK. Call this once at app startup before invoking any other payment method.
///
/// @param environment - The Airwallex environment to connect to. Defaults to `Environment.production`.
/// @param enableLogging - When `true`, the SDK emits logs to the console. Android only. Defaults to `true`.
/// @param saveLogToLocal - When `true`, logs are also persisted to a local file for debugging. Defaults to `false`.
static void initialize({
Environment environment = Environment.production, bool enableLogging = true, bool saveLogToLocal = false}) {
if (enableLogging && kDebugMode) {
Expand All @@ -21,6 +26,12 @@ class Airwallex {
AirwallexPaymentFlutterPlatform.instance.initialize(environment, enableLogging, saveLogToLocal);
}

/// Presents the full Airwallex payment sheet, letting the customer pick any supported payment method
/// (cards, wallets, redirect methods) and complete the payment.
///
/// @param session - The payment session describing the intent, amount, currency, and customer.
/// @param configuration - Optional UI configuration for the payment sheet (e.g. layout).
/// @returns The result of the payment attempt.
Future<PaymentResult> presentEntirePaymentFlow(
BaseSession session, {
PaymentSheetConfiguration? configuration,
Expand All @@ -29,6 +40,12 @@ class Airwallex {
.presentEntirePaymentFlow(session, configuration: configuration);
}

/// Presents the card-only payment sheet. Use this when the merchant wants to restrict checkout
/// to card payments and skip the payment-method selection screen.
///
/// @param session - The payment session describing the intent, amount, currency, and customer.
/// @param supportedBrands - Optional list of card brands to accept. When `null`, all supported brands are accepted.
/// @returns The result of the payment attempt.
Future<PaymentResult> presentCardPaymentFlow(
BaseSession session, {
List<CardBrand>? supportedBrands,
Expand All @@ -37,24 +54,48 @@ class Airwallex {
.presentCardPaymentFlow(session, supportedBrands: supportedBrands);
}

/// Pays with raw card details collected by the merchant's own UI. The merchant is responsible
/// for PCI compliance when using this method.
///
/// @param session - The payment session describing the intent, amount, currency, and customer.
/// @param card - The card details to charge.
/// @param saveCard - When `true`, the card is saved as a payment consent for future use.
/// @returns The result of the payment attempt.
Future<PaymentResult> payWithCardDetails(BaseSession session, Card card, bool saveCard) {
return AirwallexPaymentFlutterPlatform.instance
.payWithCardDetails(session, card, saveCard);
}

/// Pays using an existing payment consent (a previously saved card).
///
/// @param session - The payment session describing the intent, amount, currency, and customer.
/// @param consent - The payment consent to charge.
/// @returns The result of the payment attempt.
Future<PaymentResult> payWithConsent(BaseSession session, PaymentConsent consent) {
return AirwallexPaymentFlutterPlatform.instance
.payWithConsent(session, consent);
}

/// Starts a Google Pay payment flow. Android only.
///
/// @param session - The payment session. Must include `googlePayOptions` for the merchant configuration.
/// @returns The result of the payment attempt.
Future<PaymentResult> startGooglePay(BaseSession session) {
return AirwallexPaymentFlutterPlatform.instance.startGooglePay(session);
}

/// Starts an Apple Pay payment flow. iOS only.
///
/// @param session - The payment session. Must include `applePayOptions` for the merchant configuration.
/// @returns The result of the payment attempt.
Future<PaymentResult> startApplePay(BaseSession session) {
return AirwallexPaymentFlutterPlatform.instance.startApplePay(session);
}

/// Sets the tint color used by the native payment UI on iOS.
/// On Android, override the `airwallex_tint_color` resource instead.
///
/// @param color - The tint color to apply.
static void setTintColor(Color color) {
AirwallexPaymentFlutterPlatform.instance.setTintColor(color);
}
Expand Down
11 changes: 11 additions & 0 deletions lib/types/apple_pay_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import 'package:json_annotation/json_annotation.dart';

part 'apple_pay_options.g.dart';

/// Apple Pay configuration. Required on a `PaymentSession` when invoking
/// `startApplePay`, or when offering Apple Pay through `presentEntirePaymentFlow` on iOS.
///
/// `merchantIdentifier` must match the Apple Pay merchant ID configured in the app's entitlements.
@JsonSerializable(explicitToJson: true)
class ApplePayOptions {
String merchantIdentifier;
Expand All @@ -27,6 +31,7 @@ class ApplePayOptions {
Map<String, dynamic> toJson() => _$ApplePayOptionsToJson(this);
}

/// Card networks the merchant is willing to accept through Apple Pay.
enum ApplePaySupportedNetwork {
visa,
masterCard,
Expand All @@ -37,13 +42,15 @@ enum ApplePaySupportedNetwork {
maestro
}

/// Payment-processing capabilities the merchant supports. `supports3DS` is required by Apple Pay.
enum ApplePayMerchantCapability {
supports3DS,
supportsCredit,
supportsDebit,
supportsEMV,
}

/// Contact fields the merchant requires from the customer during Apple Pay checkout.
enum ContactField {
emailAddress,
name,
Expand All @@ -52,6 +59,8 @@ enum ContactField {
postalAddress,
}

/// An additional line item displayed in the Apple Pay summary
/// (e.g. tax, shipping, discount) on top of the session amount.
@JsonSerializable()
class CartSummaryItem {
String label;
Expand All @@ -69,6 +78,8 @@ class CartSummaryItem {
Map<String, dynamic> toJson() => _$CartSummaryItemToJson(this);
}

/// Whether a `CartSummaryItem` amount is final or still pending
/// (e.g. shipping not yet calculated).
enum CartSummaryItemType {
finalType,
pendingType,
Expand Down
6 changes: 6 additions & 0 deletions lib/types/card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import 'package:json_annotation/json_annotation.dart';

part 'card.g.dart';

/// Card details. When used as input to `payWithCardDetails`, the merchant
/// supplies `number`, `expiryMonth`, `expiryYear`, `cvc`, and optionally `name`.
///
/// The remaining fields (`bin`, `last4`, `brand`, `fingerprint`, `cvcCheck`,
/// `avsCheck`, etc.) are populated by Airwallex when a Card is returned as part of
/// a saved payment method on a `PaymentConsent`.
@JsonSerializable(fieldRename: FieldRename.snake, includeIfNull: false)
class Card {
String? cvc;
Expand Down
10 changes: 10 additions & 0 deletions lib/types/google_pay_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'package:json_annotation/json_annotation.dart';

part 'google_pay_options.g.dart';

/// Google Pay configuration. Required on a `PaymentSession` when invoking
/// `startGooglePay`, or when offering Google Pay through `presentEntirePaymentFlow` on Android.
@JsonSerializable(explicitToJson: true)
class GooglePayOptions {
List<String>? allowedCardAuthMethods;
Expand Down Expand Up @@ -44,6 +46,7 @@ class GooglePayOptions {
Map<String, dynamic> toJson() => _$GooglePayOptionsToJson(this);
}

/// Controls how the customer's billing address is collected via Google Pay.
@JsonSerializable()
class BillingAddressParameters {
Format? format;
Expand All @@ -59,11 +62,16 @@ class BillingAddressParameters {
Map<String, dynamic> toJson() => _$BillingAddressParametersToJson(this);
}

/// How much of the billing address Google Pay should return.
///
/// - `min` — postal code and country only (sufficient for most card validation).
/// - `full` — full address.
enum Format {
min,
full,
}

/// Controls how the customer's shipping address is collected via Google Pay.
@JsonSerializable()
class ShippingAddressParameters {
List<String>? allowedCountryCodes;
Expand All @@ -79,6 +87,8 @@ class ShippingAddressParameters {
Map<String, dynamic> toJson() => _$ShippingAddressParametersToJson(this);
}

/// The set of card networks Airwallex supports through Google Pay. Useful as a default value
/// for `GooglePayOptions.allowedCardNetworks` when the merchant has no preference.
List<String> googlePaySupportedNetworks() {
return [
"AMEX",
Expand Down
5 changes: 5 additions & 0 deletions lib/types/merchant_trigger_reason.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/// Why the merchant is triggering a charge against a saved payment consent.
/// Only meaningful when `NextTriggeredBy` is `merchant`.
///
/// - `scheduled` — fixed-interval billing the customer has agreed to (e.g. monthly subscription).
/// - `unscheduled` — merchant-initiated charge at an unpredictable time (e.g. account top-up, usage overage).
enum MerchantTriggerReason {
unscheduled,
scheduled,
Expand Down
1 change: 1 addition & 0 deletions lib/types/next_triggered_by.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// Who initiates the next charge against a saved payment consent.
enum NextTriggeredBy {
merchant,
customer,
Expand Down
5 changes: 5 additions & 0 deletions lib/types/payment_consent.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import 'package:airwallex_payment_flutter/types/shipping.dart';

part 'payment_consent.g.dart';

/// A reusable payment authorization tied to a customer and a payment method.
/// Created during a `RecurringSession` / `RecurringWithIntentSession` flow,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we mention the non deprecated Session instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We’re still using OneOffSession, RecurringSession, and RecurringWithIntentSession in Dart, so I think it’s okay for now. We can update the documentation after we deprecate these types in Dart.

/// and later passed to `payWithConsent` to charge without re-prompting the customer.
@JsonSerializable(explicitToJson: true, fieldRename: FieldRename.snake, includeIfNull: false)
class PaymentConsent {
String? id;
Expand Down Expand Up @@ -45,6 +48,8 @@ class PaymentConsent {
Map<String, dynamic> toJson() => _$PaymentConsentToJson(this);
}

/// The payment instrument attached to a `PaymentConsent` — typically a saved card,
/// along with its billing details.
@JsonSerializable(explicitToJson: true, fieldRename: FieldRename.snake, includeIfNull: false)
class PaymentMethod {
String? id;
Expand Down
9 changes: 9 additions & 0 deletions lib/types/payment_result.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
/// The outcome of a payment flow. Discriminated by `status`:
/// `'success'`, `'inProgress'`, or `'cancelled'`.
///
/// Errors are not represented here — they are surfaced by the returned `Future` failing.
abstract class PaymentResult {
final String status;
PaymentResult(this.status);
}

/// The payment completed successfully. If a payment consent was created during the
/// flow, its id is returned in `paymentConsentId`.
class PaymentSuccessResult extends PaymentResult {
final String? paymentConsentId;

PaymentSuccessResult({this.paymentConsentId})
: super('success');
}

/// The payment has been submitted but its final outcome is not yet known.
/// The merchant should poll the payment intent or wait for a webhook to confirm the result.
class PaymentInProgressResult extends PaymentResult {
PaymentInProgressResult() : super('inProgress');
}

/// The customer cancelled the payment flow before it completed.
class PaymentCancelledResult extends PaymentResult {
PaymentCancelledResult() : super('cancelled');
}
13 changes: 13 additions & 0 deletions lib/types/payment_session.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import 'merchant_trigger_reason.dart';

part 'payment_session.g.dart';

/// Base class for every payment session. A session bundles the authentication
/// (`clientSecret`), amount and currency, customer details, and optional wallet
/// configuration into the single value passed to every payment flow method.
///
/// Pick a concrete subclass based on what the merchant wants to do:
/// - `OneOffSession` — charge the customer once.
/// - `RecurringSession` — save a payment method for future recurring charges, without charging now.
/// - `RecurringWithIntentSession` — charge now AND save the payment method for future recurring charges.
abstract class BaseSession {
String type;
String? customerId;
Expand Down Expand Up @@ -42,6 +50,7 @@ abstract class BaseSession {
Map<String, dynamic> toJson();
}

/// A one-off payment against a payment intent.
@JsonSerializable(explicitToJson: true)
class OneOffSession extends BaseSession {
String paymentIntentId;
Expand Down Expand Up @@ -70,6 +79,8 @@ class OneOffSession extends BaseSession {
Map<String, dynamic> toJson() => _$OneOffSessionToJson(this);
}

/// A session that sets up a payment consent for future recurring charges
/// without charging the customer right now.
@JsonSerializable(explicitToJson: true)
class RecurringSession extends BaseSession {
NextTriggeredBy nextTriggeredBy;
Expand All @@ -96,6 +107,8 @@ class RecurringSession extends BaseSession {
Map<String, dynamic> toJson() => _$RecurringSessionToJson(this);
}

/// A session that charges a payment intent now and sets up a payment consent
/// for future recurring charges in a single step.
@JsonSerializable(explicitToJson: true)
class RecurringWithIntentSession extends BaseSession {
String paymentIntentId;
Expand Down
5 changes: 5 additions & 0 deletions lib/types/payment_sheet_configuration.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
/// How payment methods are arranged on the payment sheet.
///
/// - `tab` — payment methods shown as horizontally-scrollable tabs.
/// - `accordion` — payment methods stacked vertically, expanding on tap.
enum PaymentLayout { tab, accordion }

/// Optional UI configuration for the payment sheet shown by `presentEntirePaymentFlow`.
class PaymentSheetConfiguration {
final PaymentLayout layout;

Expand Down
2 changes: 2 additions & 0 deletions lib/types/shipping.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'package:json_annotation/json_annotation.dart';

part 'shipping.g.dart';

/// Customer shipping (and billing, when reused via `PaymentMethod.billing`) details
/// pre-filled into the payment sheet.
@JsonSerializable(explicitToJson: true)
class Shipping {
String? firstName;
Expand Down
Loading