Full Custom UI Integration
To replace the SDK’s default UI with a custom UI (e.g., using your own XML layout, colors, styles, or error screens), follow these steps:
The SDK currently provides two types of payment. We can use any of them by first preparing Moyasar's PaymentRequest
object as follows:
val paymentRequest = PaymentRequest(
apiKey = "pk_test_vcFUHJDBwiyRu4Bd3hFuPpTnRPY4gp2ssYdNJMY3",
amount = 1000, // Amount in the smallest currency unit For example: 10 SAR = 10 * 100 Halalas
currency = "SAR",
description = "Sample Android SDK Payment",
manual = false,
metadata = mapOf(
"order_id" to "order_123"
),
saveCard = false,
buttonType = MoyasarButtonType.PAY, // [determine button title: Optional]; by default, it's set to `MoyasarButtonType.PAY`.
allowedNetworks = listOf(
CreditCardNetwork.Visa,
CreditCardNetwork.Mastercard,
CreditCardNetwork.Mada
) // [set your supported networks: Optional]
)
- In our demo, you will find a complete example of Custom UI (Credit Card or STC Pay).
- An error will be thrown if the API key format is incorrect.
Credit Card Payments (Custom UI)
We need to create a new fragment called CustomUIPaymentFragment
that encapsulates the Moyasar logic and displays it as illustrated in the snippet below.
After that, you will need to observe two LiveData (inputFieldsValidatorLiveData
, creditCardStatus
) to handle manage error handling and UI logic.
class PayWithMoyasarActivity : AppCompatActivity() {
companion object {
fun startPaymentWithMoyasar(
context: Context,
getResult: ActivityResultLauncher<Intent>,
) {
val intent = Intent(context, PayWithMoyasarActivity::class.java)
getResult.launch(intent)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
// Other setup code
val paymentFragment = CustomUIPaymentFragment.newInstance(this.application, paymentConfig) { this.handlePaymentResult(it) }
this.supportFragmentManager.beginTransaction().apply {
// Id for the payment container view
replace(R.id.paymentSheetFragment, paymentFragment)
commit()
}
}
fun handlePaymentResult(result: PaymentResult) {
// ...
}
fun handleCompletedPayment(payment: Payment) {
// ..
}
}
class CustomUIPaymentFragment : Fragment() {
private lateinit var parentActivity: FragmentActivity
private lateinit var binding: FragmentCustomUIPaymentBinding
companion object {
fun newInstance(
application: Application,
paymentRequest: PaymentRequest,
callback: (PaymentResult) -> Unit,
): CustomUIPaymentFragment {
val configError = paymentRequest.validate()
if (configError.any()) {
throw InvalidConfigException(configError)
}
MoyasarAppContainer.initialize(application, paymentRequest, callback)
return CustomUIPaymentFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
super.onCreateView(inflater, container, savedInstanceState)
parentActivity = requireActivity()
binding = FragmentCustomUIPaymentBinding.inflate(inflater, container, false)
setupObservers()
binding.setupListeners()
return binding.root
}
private fun setupObservers() {
viewModel.creditCardStatus.observe(viewLifecycleOwner, ::handleOnStatusChanged)
viewModel.inputFieldsValidatorLiveData.observe(viewLifecycleOwner) { inputFieldUIModel ->
showInvalidNameErrorMsg(inputFieldUIModel.errorMessage?.nameErrorMsg)
showInvalidCardNumberErrorMsg(inputFieldUIModel.errorMessage?.numberErrorMsg)
showInvalidExpiryErrorMsg(inputFieldUIModel.errorMessage?.expiryDateErrorMsg)
showInvalidCVVErrorMsg(inputFieldUIModel.errorMessage?.cvcErrorMsg)
handleFormValidationState(inputFieldUIModel.isFormValid)
handleShowAllowedCardTypesIcons(inputFieldUIModel.cardNumber)
}
}
}
CustomUIPaymentFragment
is a newly created fragment built from scratch to encapsulate Moyasar's payment logic.
STC Pay Payments integration (Custom UI)
We need to implement a custom fragment EnterMobileNumberCustomUIFragment
, which encapsulates Moyasar’s logic and handles its display.
Upon successful completion of this screen, we should navigate to another custom screen EnterOTPCustomUIFragment
.
Thus, we need to observe two LiveData (inputFieldsValidatorLiveData
, stcPayStatus
) to manage error handling and UI updates.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Other setup code
val enterMobileNumberFragment = EnterMobileNumberCustomUIFragment.newInstance(this.application, paymentRequest) { this.handlePaymentResult(it) }
this.supportFragmentManager.beginTransaction().apply {
//ID for the payment container view
replace(R.id.paymentSheetFragment, enterMobileNumberFragment)
commit()
}
}
fun handlePaymentResult(result: PaymentResult) {
// ...
}
fun handleCompletedPayment(payment: Payment) {
// ..
}
}
class EnterMobileNumberCustomUIFragment : Fragment() {
private lateinit var binding: FragmentEnterMobileNumberCustomUIBinding
private lateinit var parentActivity: FragmentActivity
companion object {
fun newInstance(
application: Application,
paymentRequest: PaymentRequest,
callback: (PaymentResult) -> Unit,
): EnterMobileNumberCustomUIFragment {
val configError = paymentRequest.validate()
if (configError.any()) {
throw InvalidConfigException(configError)
}
MoyasarAppContainer.initialize(application, paymentRequest, callback)
return EnterMobileNumberCustomUIFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
super.onCreateView(inflater, container, savedInstanceState)
parentActivity = requireActivity()
binding = FragmentEnterMobileNumberCustomUIBinding.inflate(inflater, container, false)
initView()
setupObservers()
binding.setupListeners()
return binding.root
}
private fun setupObservers() {
viewModel.stcPayStatus.observe(
viewLifecycleOwner,
::handleOnStatusChanged
)
viewModel.inputFieldsValidatorLiveData.observe(viewLifecycleOwner) { inputFieldUIModel ->
showInvalidPhoneErrorMsg(inputFieldUIModel?.stcPayUIModel?.mobileNumberErrorMsg)
handleFormValidationState(viewModel.inputFieldsValidatorLiveData.value?.stcPayUIModel?.isMobileValid)
}
}
}
class EnterOTPCustomUIFragment : Fragment() {
private lateinit var binding: FragmentEnterOTPCustomUIBinding
private lateinit var parentActivity: FragmentActivity
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
super.onCreateView(inflater, container, savedInstanceState)
parentActivity = requireActivity()
binding = FragmentEnterOTPCustomUIBinding.inflate(inflater, container, false)
initView()
return binding.root
}
private fun initView() {
binding.progressBar.isVisible = false
setupListeners()
setupObservers()
}
private fun setupListeners() {
binding.payButton.setOnClickListener {
val transactionURL = arguments?.getString(TRANSACTION_URL).orEmpty()
viewModel.submitSTCPayOTP(
transactionURL = transactionURL,
otp = binding.otpEt.text.toString()
)
}
binding.otpEt.doAfterTextChanged { text ->
viewModel.stcPayOTPChanged(text)
}
}
private fun setupObservers() {
viewModel.stcPayStatus.observe(viewLifecycleOwner, ::handleOnStatusChanged)
viewModel.inputFieldsValidatorLiveData.observe(viewLifecycleOwner) { inputFieldUIModel ->
showInvalidOTPErrorMsg(inputFieldUIModel?.stcPayUIModel?.otpErrorMsg)
handleFormValidationState(viewModel.inputFieldsValidatorLiveData.value?.stcPayUIModel?.isOTPValid)
}
}
}
Handling Payment Result for Credit Card / STC Pay
Now, we can handle the Credit Card payment result as follows:
fun handlePaymentResult(result: PaymentResult) {
when (result) {
is PaymentResult.Completed -> {
handleCompletedPayment(result.payment);
}
is PaymentResult.Failed -> {
// Handle error
val error = result.error;
}
PaymentResult.Canceled -> {
// User has canceled the payment
}
else -> { /* Handle other statuses */ }
}
}
fun handleCompletedPayment(payment: Payment) {
when (payment.status) {
"paid" -> { /* Handle successful payment */ }
"failed" -> {
val errorMessage = payment.source["message"]
/* Handle failed payment */
}
else -> { /* Handle other statuses */ }
}
}
- A Completed payment does not guarantee success; it indicates that the payment process has been finalized successfully.
- You need to check the payment status to ensure the payment is successful.
Ensure that the payment view is removed after obtaining the result.
Java interoperability
The SDK is developed in Kotlin and supports interoperability with Java.
Demo Example
You can explore the SDK driver demo in the moyasar-android-sdk repository.