Skip to main content

Custom Payment UI Implementation

info

Unified config for Moyasar API

  final paymentConfig = PaymentConfig(
publishableApiKey: 'YOUR_API_KEY',
amount: 25758, // SAR 257.58
description: 'order #1324',
metadata: {'size': '250g'},
creditCard: CreditCardConfig(saveCard: true, manual: false),
applePay: ApplePayConfig(
merchantId: 'YOUR_MERCHANT_ID',
label: 'YOUR_STORE_NAME',
manual: false,
saveCard: false),
samsungPay: SamsungPayConfig(
serviceId: 'YOUR_SAMSUNG_SERVICE_ID',
merchantName: 'YOUR_STORE_NAME'),
);

1. Custom applePay UI

  • Callback once the user clicks on the custom Apple Pay widget
  void onSubmitApplePay(applePay) async {
final source = ApplePayPaymentRequestSource(
applePay['token'],
paymentConfig.applePay!.manual,
paymentConfig.applePay!.saveCard);
final paymentRequest = PaymentRequest(paymentConfig, source);

final result = await Moyasar.pay(
apiKey: paymentConfig.publishableApiKey,
paymentRequest: paymentRequest);

onPaymentResult(result);
}

2. Custom Credit Card UI

  • Callback once the user fills & submit their CC information using the custom form widget
  void onSubmitCcForm() async {
final source = CardPaymentRequestSource(
creditCardData: CardFormModel(
name: 'John Doe',
number: '4111111111111111',
cvc: '123',
month: '05',
year: '2027'),
tokenizeCard: paymentConfig.creditCard!.saveCard,
manualPayment: paymentConfig.creditCard!.manual);

final paymentRequest = PaymentRequest(paymentConfig, source);

final result = await Moyasar.pay(
apiKey: paymentConfig.publishableApiKey,
paymentRequest: paymentRequest);

onPaymentResult(result);
}
note

With a custom Credit Card UI, handling the 3DS step is your responsibility. When status is initiated, open (result.source as CardPaymentResponseSource).transactionUrl in a web view and watch for the redirect to PaymentConfig.callbackUrl.

3. Custom Samsung Pay UI

  • Callback once the Samsung Pay SDK returns a payment token
  void onSubmitSamsungPay(String token, String orderNumber) async {
final source = SamsungPayPaymentRequestSource(
samsungPayToken: token,
manualPayment: paymentConfig.samsungPay!.manual,
);

final paymentRequest = PaymentRequest(
paymentConfig,
source,
additionalMetadata: {'samsungpay_order_id': orderNumber},
);

final result = await Moyasar.pay(
apiKey: paymentConfig.publishableApiKey,
paymentRequest: paymentRequest);

onPaymentResult(result);
}

4. Custom STC UI

  // STC Pay Implementation
String? transactionUrl; // Store this as a class variable
final phoneController = TextEditingController();
final otpController = TextEditingController();

// Callback when user submits their mobile number for STC Pay
void onSubmitStcPay() async {
final source = StcRequestSource(
mobile: phoneController.text,
);

final paymentRequest = PaymentRequest(paymentConfig, source);

final result = await Moyasar.pay(
apiKey: paymentConfig.publishableApiKey,
paymentRequest: paymentRequest,
);

if (result is PaymentResponse && result.status == PaymentStatus.initiated) {
final stcResponse = result.source as StcResponseSource;
setState(() {
transactionUrl = stcResponse.transactionUrl;
});
} else {
// Handle error
}
}

// Callback when user submits OTP for STC Pay
void onSubmitStcOtp() async {
if (transactionUrl == null) return;

final otpRequest = OtpRequestSource(
otpValue: otpController.text,
);

final result = await Moyasar.verifyOTP(
transactionURL: transactionUrl!,
otpRequest: otpRequest,
);

onPaymentResult(result);
}

Handling onPaymentResult

  • Unified payment result processor
  void onPaymentResult(result) {
if (result is PaymentResponse) {
switch (result.status) {
case PaymentStatus.initiated:
// handle 3DS redirection / awaiting OTP.
break;
case PaymentStatus.paid:
// handle success.
break;
case PaymentStatus.authorized:
case PaymentStatus.captured:
// manual capture flow.
break;
case PaymentStatus.failed:
// handle failure.
break;
}
return;
}

// Otherwise the result is an error.
if (result is PaymentCanceledError) {
// The user dismissed the payment sheet.
} else if (result is ValidationError) {
// Invalid request data — inspect result.errors.
} else if (result is NetworkError || result is TimeoutError) {
// Connectivity problem; the user can retry.
}
}

Error types

TypeMeaning
PaymentCanceledErrorThe user dismissed the payment sheet.
UnprocessableTokenErrorThe wallet returned an empty or unusable token.
ValidationErrorInvalid request data. Has message and errors.
AuthErrorThe publishable API key was rejected.
ApiErrorMoyasar returned a server-side error.
NetworkErrorThe request never reached the API.
TimeoutErrorThe request timed out.
UnspecifiedErrorAnything else.