Payment widget - iOS

The Payment widget is an integrated tool that allows you to offer multiple payment methods to end customers.

The Payment widget supports:

  • Alternative payment methods (APMs): such as OXXO, KueskiPay, Aplazo, among others.
  • Credit and debit cards: MasterCard, Visa, American Express, and so on.

Initialize the widget

Before integrating the Payment widget, complete the First steps - iOS.

1. Show the Widget

Show in a Modal

To show the Payment Widget in a modal (pageSheet), call the initPaymentWidget function passing the following data:

deunaSDK.initPaymentWidget(
  orderToken: "<DEUNA order token>",
  userToken: "<DEUNA user token>", // optional
  styleFile: "<DEUNA theme ID to customize the look and feel of the widget>", // optional
  callbacks: PaymentWidgetCallbacks(
    onSuccess: { order in
      self.deunaSDK.close() // Closes the payment widget
    },
    onError: { error in
      // Error handling                
      if(error.type == .paymentError){
         // YOUR CODE HERE
      }
    },
    onClosed: { action in
      // Widget closed
    },
    onCardBinDetected: { metadata in
      guard metadata != nil else {
        // Your code here
      }
    }
  ),
)

Show the embedded widget (SwiftUI)

Use the DeunaWidget view to show the payment widget embedded in your app with SwiftUI.

import DeunaSDK
import SwiftUI

struct YourView: View {
    let deunaSDK: DeunaSDK
    var body: some View {
        VStack {
            DeunaWidget(
                deunaSDK: deunaSDK,
                configuration: PaymentWidgetConfiguration(
                    orderToken: "YOUR_ORDER_TOKEN",
                    callbacks: PaymentWidgetCallbacks(
                        onSuccess: { order in
                           // NOTE: Explicitly release widget resources
                           // when no longer needed
                           // to prevent memory leaks and ensure proper cleanup.
                            deunaSDK.dispose()
                        },
                        onError: { error in
                          if(error.type == .paymentError){
         											// YOUR CODE HERE
      										}        
                        },
                        onEventDispatch: { event, data in
                            
                        }
                    )
                )
            )
        }
    }
}

Parameters

AttributesModalEmbeddedDescription
orderTokenThe orderToken is a unique token generated for the payment order. This token is generated through DEUNA's API and you must implement the corresponding endpoint in your backend to get this information.

When creating the order with DEUNA's API, a redirect URL (redirect_url) should not be defined so that the onSuccess callback runscorrectly.
userToken (optional)The bearer token of the DEUNA user. When this is sent, all actions within the widget will be performed on this DEUNA user.

For this userToken to be used and for saved cards to be shown to the customer, the email associated with that userToken must be the same as the one sent when creating the order in billing_address.email. If both emails do not match, then the flow without showing cards is used for security.
callbacksCallbacks are callback functions that are responsible for listening to and handling payment widget events. These events allow managing specific actions based on the payment status. The main callbacks include: onSuccess, onError, onClosed, onCanceled, onCardBinDetected, onInstallmentSelected
styleFile
(Ooptional)
UUID provided by DEUNA. This applies if you want to configure a custom custom styles file (change colors, texts, logo, and so on). If a valid value is provided for styleFile, the Payment widget uses the UI configuration provided by the theme configuration that matches the provided UUID.
paymentMethods (optional)A list of allowed payment methods. This parameter determines which type of widget should be rendered.
language(optional)This parameter allows you to specify the language in which the widget interface is displayed. It should be provided as a valid language code (for example, "es" for Spanish, "en" for English, "pt" for Portuguese).

Behavior:

- If provided,then the widget uses the language specified in this parameter, regardless of the merchant's configuration.
- If not provided, then the widget uses the language configured by the merchant
behavior (optional)Use this parameter to configure the widget's behavior.

Configuration for voucher downloads in iOS

In iOS, for payment methods that generate the download of a voucher or receipt, additional configuration is required in your application.

Add the NSPhotoLibraryUsageDescription key to the Info.plist file.

This allows your app to have the necessary permissions to download and store receipts on the user's device.

Example configuration in Info.plist:

<key>NSPhotoLibraryUsageDescription</key>
<string>The application needs access to the gallery to download and store payment receipts.</string>

behavior parameter (optional)

The initPaymentWidget and buildPaymentWidgetUrl methods accept the behavior parameter, which must be an instance of the WidgetBehavior class that allows you to customize the behavior of the Payment widget, including:

  • Enable payments with multiple cards.
  • Among other configuration options.

Note: These configurations apply to all payment methods enabled in the widget.

Payment methods behavior paymentMethods

The paymentMethods parameter within behavior allows you to configure global behaviors for all payment methods enabled in the widget.

  • flowType (Flow Type)
  • Values (string): twoStep or singleStep
  • Current compatibility: Exclusive to PayPal.

Controls the display flow for specific payment methods that require showing prior information before the payment form.

/// DIALOG
deunaSDK.initPaymentWidget(
  orderToken: "<DEUNA order token>",
  behavior: WidgetBehavior(
    paymentMethods: [
      "flowType" : "twoStep"
    ]
  )
)

/// EMBEDDED
DeunaWidget(
  deunaSDK: deunaSDK,
  configuration: PaymentWidgetConfiguration(
    orderToken: "YOUR_ORDER_TOKEN",
    behavior: WidgetBehavior(
      paymentMethods: [
        "flowType" : "twoStep"
      ]
    )
  )
)

Split payment across multiple cards (split payments)

The Split Payments feature allows customers to split a purchase payment across multiple credit/debit cards.

Requirements

  • The option must be enabled in the merchant configuration.
  • Currently only supports splitting between a maximum of two cards.
/// MODAL
deunaSDK.initPaymentWidget(
  orderToken: "<DEUNA order token>",
  behavior: WidgetBehavior(
    paymentMethods: [
      "creditCard": [
        "splitPayments": [
          "maxCards": 2
        ]
      ]
    ]
  )
)

// EMBEDDED
DeunaWidget(
  deunaSDK: deunaSDK,
  configuration: PaymentWidgetConfiguration(
    orderToken: "YOUR_ORDER_TOKEN",
    behavior: WidgetBehavior(
      paymentMethods: [
        "creditCard": [
          "splitPayments": [
            "maxCards": 2
          ]
        ]
      ]
    )
  )
)

Auto purchase configuration for PayPal

The auto purchase feature for PayPal allows processing payments instantly when:

  • The customer has previously linked their PayPal account.
  • They have authorized quick payments in their account.
/// MODAL
deunaSDK.initPaymentWidget(
 orderToken: "<DEUNA order token>",
 userToken: ..., // optional
 styleFile: ..., // optional
 callbacks: ...,
 paymentMethods: [
   [ 
     "paymentMethod": "wallet",
     "processors": ["paypal_wallet"],
     "configuration": [ "express": true  ]
   ]
 ]
)

Configuration parameters

ParameterTypeDefault valueDescription
expressBooleantrueWhen true, processes payment automatically if the customer has PayPal linked. When false, allows selecting/confirming the account.

The express configuration will only work if in the paymentMethods parameter only the PayPal configuration is passed.

2. Show or hide payment methods

The Payment widget can display various payment methods available for an order without the merchant having to individually add each APM button in their frontend.

When invoking the **itPaymentWidget function, the paymentMethods parameter defines the payment methods that the widget shows:

  • If only one method is passed in paymentMethods, then the widget automatically opens the payment method form without showing buttons for other AMPs.

// MODAL
deunaSDK.initPaymentWidget(
 orderToken: "<DEUNA order token>",
 userToken: ..., // optional
 styleFile: ..., // optional
 callbacks: ...,
 paymentMethods: [
   [ 
     "paymentMethod": "voucher",
     "processors": ["payu_oxxo_cash"]
   ]
 ]
)

// EMBEDDED
DeunaWidget(
  deunaSDK: deunaSDK,
  configuration: PaymentWidgetConfiguration(
    orderToken: "YOUR_ORDER_TOKEN",
    callbacks: PaymentWidgetCallbacks(
      onSuccess: { order in },
      onError: { error in },
      onEventDispatch: { event, data in }
    ),
     paymentMethods: [
       [ 
         "paymentMethod": "voucher",
         "processors": ["payu_oxxo_cash"]
       ]
     ]
  )
)
// MODAL
deunaSDK.initPaymentWidget(
 orderToken: "<DEUNA order token>",
 userToken: ..., // optional
 styleFile: ..., // optional
 callbacks: ...,
 paymentMethods: [
   [ 
     "paymentMethod": "voucher",
     "processors": ["daviplata"]
   ]
 ]
)

// EMBEDDED
DeunaWidget(
  deunaSDK: deunaSDK,
  configuration: PaymentWidgetConfiguration(
    orderToken: "YOUR_ORDER_TOKEN",
    callbacks: PaymentWidgetCallbacks(
      onSuccess: { order in },
      onError: { error in },
      onEventDispatch: { event, data in }
    ),
     paymentMethods: [
       [ 
         "paymentMethod": "voucher",
         "processors": ["daviplata"]
       ]
     ]
  )
)
// MODAL
deunaSDK.initPaymentWidget(
 orderToken: "<DEUNA order token>",
 userToken: ..., // optional
 styleFile: ..., // optional
 callbacks: ...,
 paymentMethods: [
   [ 
     "paymentMethod": "voucher",
     "processors": ["nequi_push_voucher"]
   ]
 ]
)

// EMBEDDED
DeunaWidget(
  deunaSDK: deunaSDK,
  configuration: PaymentWidgetConfiguration(
    orderToken: "YOUR_ORDER_TOKEN",
    callbacks: PaymentWidgetCallbacks(
      onSuccess: { order in },
      onError: { error in },
      onEventDispatch: { event, data in }
    ),
     paymentMethods: [
       [ 
         "paymentMethod": "voucher",
         "processors": ["nequi_push_voucher"]
       ]
     ]
  )
)

If more than one payment method is specified in the paymentMethods parameter, then the widget only shows the enabled methods, which must be configured at the merchant level.

📘

For credit card processing, there is no need to pass the processors's array since the merchant can have multiple configured for the use of routing.

// MODAL
deunaSDK.initPaymentWidget(
 orderToken: "<DEUNA order token>",
 userToken: ..., // optional
 styleFile: ..., // optional
 callbacks: ...,
 paymentMethods: [
   [
     "paymentMethod": "credit_card"
   ],
   [ 
     "paymentMethod": "bnpl",
     "processors": ["kueski"]
   ],
   [
     "paymentMethod": "voucher",
     "processors": ["payu_oxxo_cash"]
   ]
 ]
)

// EMBEDDED
DeunaWidget(
  deunaSDK: deunaSDK,
  configuration: PaymentWidgetConfiguration(
    orderToken: "YOUR_ORDER_TOKEN",
    callbacks: PaymentWidgetCallbacks(
      onSuccess: { order in },
      onError: { error in },
      onEventDispatch: { event, data in }
    ),
     paymentMethods: [
       [
         "paymentMethod": "credit_card"
       ],
       [ 
         "paymentMethod": "bnpl",
         "processors": ["kueski"]
       ],
       [
         "paymentMethod": "voucher",
         "processors": ["payu_oxxo_cash"]
       ]
     ]
  )
)

If no list of methods is specified in paymentMethods and the include_payment_options parameter was not used when creating the order, then the widget shows all payment methods configured for the merchant.

// MODAL
deunaSDK.initPaymentWidget(
 orderToken: "<DEUNA order token>",
 userToken: ..., // optional
 styleFile: ..., // optional
 callbacks: ...
)

// EMBEDDED
DeunaWidget(
  deunaSDK: deunaSDK,
  configuration: PaymentWidgetConfiguration(
    orderToken: "YOUR_ORDER_TOKEN",
    callbacks: PaymentWidgetCallbacks(
      onSuccess: { order in },
      onError: { error in },
      onEventDispatch: { event, data in }
    )
  )
)

Payment methods configuration priority table

The following table shows how the Payment widget decides which payment methods to show when the paymentMethods parameter is not passed.

PriorityConfiguration SourceDescriptionBehavior in case of a single payment method
1Payment methods passed when executing the .initPaymentWidget function or when showing the widget embedded with DeunaWidgetPayment methods are shown according to those passed when starting the widget, as long as they are activated and configured at the merchant level.If only one payment method is passed, then the widget automatically opens the payment method form without showing buttons.
2Order and include_payment_optionsThe order is checked to verify if include_payment_options has payment methods that are configured and activated at the merchant level. Unconfigured methods are not shown.If only one payment method is passed, then the widget automatically opens the payment method form without showing buttons.
3Methods configured at merchant level
(API /payment-methods)
If no payment method is passed either in the .initPaymentWidget function or in include_payment_options when creating the order, then the methods configured at the merchant level are taken.If only one payment method is passed, then the widget automatically opens the payment method form without showing buttons.

3. Listen to Payment widget events

When a transaction is successful or fails, you must update your interface to notify users about the transaction result. You can do this by listening to Payment widget events through callbacks.

The instance of the PaymentWidgetCallbacks class passed to the initPaymentWidget function allows you to listen to widget events through callbacks. Define the respective callbacks to update your app interface.

Callbacks

CallbackModalEmbeddedWhen is it triggered?
onSuccessRuns when the payment is completed. This callback contains a parameter of type [String:Any] with the order information.
onErrorRuns when an error occurs. This callback contains a parameter of type PaymentsError which identifies the type of error produced.
onClosed (Optional)Executes when the payment widget modal is closed.

This callback contains a parameter of enum type CloseAction with the following values:

- .userAction: When the widget was manually closed by the user (pressing the close button X or swiping the modal down) without the operation being completed.

- .systemAction: When the widget closes due to the execution of the close function.
onCardBinDetected (Optional)Runs when the Payment widget detects the BIN of an entered credit or debit card, or when the user deletes the entered card number.

This callback contains a parameter of type [String:Any] with the BIN information and the brand of the entered card.
NOTE: The CardBinMetadata type parameter will be null when the user removes the text entered in the card number field.
onInstallmentSelected (optional)If the order can be deferred, then this callback runs when the user selects the months to defer.

This callback contains a parameter of type [String:Any] with the information of the deferral months selected by the user.
onPaymentProcessing (Optional)This callback when the user presses the pay button and the payment is being processed.

NOTE: If there is any incorrect field in the payment form, then this event will not run.
onEventDispatch (Optional)Runs on all events that the widget can produce.
This callback contains a parameter of type CheckoutEvent and the data associated with that event.

NOTE: Use the onError callback to identify if the widget could not be shown or if an error occurred when processing the payment.

onError: { error in
    // The widget could not be loaded
    if error.type == .initializationFailed {
      self.deunaSDK.close() // close the widget
      return
    }
                    
    // The payment was failed
    if(error.type == .paymentError){
       // YOUR CODE HERE
    }
}

Optional functionalities

In addition to the mandatory steps to operate the widget, you have the following customization options:

Close the widget

When the widget is shown in a modal, the payment widget only closes when the user presses the widget's close button or when they press the back button on iOS.

To close the modal when a payment is successful or when an error occurs, you must call the close function.

Example

deunaSDK.initPaymentWidget(
  orderToken: "<DEUNA order token>",
  userToken: "<DEUNA user token>", // optional
  callbacks: PaymentWidgetCallbacks(
     onSuccess: { order in
         self.deunaSDK.close() // Closes the Payment widget
         // Your additional code
     },
     onError: nil,
  	...
  )
)

Customize widget appearance

Use the setCustomStyle function to customize the widget appearance.

await DeunaSDK.setCustomStyle({...});

📘

For more information, go to Style customization.

Example

// Extension to convert a String to a Dictionary(JSON)
extension String {
    func toDictionary() -> [String: Any]? {
        guard let data = data(using: .utf8) else {
            return nil
        }
        do {
            let dictionary = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
            return dictionary
        } catch {
            print("Error: \(error.localizedDescription)")
            return nil
        }
    }
}

.
.
.


deunaSDK.initPaymentWidget(
  orderToken: "<DEUNA order token>",
  userToken: "<DEUNA user token>", // optional
  callbacks: PaymentWidgetCallbacks(
     onCardBinDetected: { metadata  in
       self.deunaSDK.setCustomStyle(data: """
                        {
                          "theme": {
                            "colors": {
                              "primaryTextColor": "#023047",
                              "backgroundSecondary": "#8ECAE6",
                              "backgroundPrimary": "#8ECAE6",
                              "buttonPrimaryFill": "#FFB703",
                              "buttonPrimaryHover": "#FFB703",
                              "buttonPrimaryText": "#000000",
                              "buttonPrimaryActive": "#FFB703"
                            }
                          },
                          "HeaderPattern": {
                            "overrides": {
                              "Logo": {
                                "props": {
                                  "url": "https://images-staging.getduna.com/ema/fc78ef09-ffc7-4d04-aec3-4c2a2023b336/test2.png"
                                }
                              }
                            }
                          }
                        }
                        """.toDictionary() ?? [:]
        )     
     }
  	...
  )
)

Refresh the Payment widget

The refetchOrder function updates the Payment widget and returns the updated order data.

This function is useful for offering promotions.

A common case is a "20% promotion when paying with Mastercard.”

In this scenario, the merchant listens to the onCardBinDetected event to identify the card franchise. Then the merchant updates the order in DEUNA (being responsible for calculating promotions) and, through this function, notifies the widget to update, as the order amount might have changed.

Example usage of the refetchOrder function

deunaSDK.initPaymentWidget(
  orderToken: "<DEUNA order token>",
  userToken: "<DEUNA user token>", // optional
  callbacks: PaymentWidgetCallbacks(
     onCardBinDetected: { metadata  in           
         if let cardBrand = metadata["cardBrand"] as? String, cardBrand == "Mastercard" {
           self.deunaSDK.refetchOrder { order in
             print("ORDER: \(order)")
           }
         }
     }
  	...
  )
)

Check demo app example

To better understand the Payment widget integration, review the example project provided by DEUNA.

This example will help you better understand how to implement the widget in your iOS application.

📘

To access the example project and get more information, Check the iOS Project Example documentation.

Hide the pay button (embedded widget)

When the widget is shown embedded with the DeunaWidget view, you can hide the widget's pay button using the parameter hidePayButton: true.

DeunaWidget(
  deunaSDK: deunaSDK,
  configuration: PaymentWidgetConfiguration(
    orderToken: "YOUR_ORDER_TOKEN",
    hidePayButton: true // Set true to hide the pay button
  )
)

If the DEUNA widget pay button is hidden, then you must use the .isValid {} and .submit{} functions to complete the payment flow.

MethodDescriptionResponse
deunaSdk.isValid{}Validates if the entered information is correct and if the payment can be processed.true if the information is valid, false otherwise.
deunaSdk.submit{}Executes the payment process, equivalent to pressing the pay button. Performs the same internal validations.{ status: "success", message: "Payment processed successfully" } or { status: "error", message: "The submit flow is not available" }

Process considerations:

  • If hidePayButton isfalse or not defined, then the pay button is visible, and the payment flow works automatically.
  • If hidePayButton is true, then the pay button is not displayed, and the payment must be managed with submit{}.
  • Use isValid{} before calling submit{} to avoid errors in the payment process.
  • If the payment flow is not yet available, then submit{} will always return an error with the message "The submit flow is not available."