Skip to main content
On this page

Requirements#

Review the requirements for integrating DEUNA's iOS SDK:

  • Swift version: 5.8 or higher
  • Xcode version: 16.0 or higher
  • Deployment target: iOS 13.0 or higher for core modal experiences; iOS 14.0 or higher for embedded SwiftUI DeunaWidget
  • DEUNA credentials: Active account and valid credentials (contact support@deuna.com if you experience delays obtaining credentials)

Install#

There are two supported methods to install the DEUNA iOS SDK:

Swift Package Manager (Recommended)

  1. In Xcode, navigate to File → Add Package Dependencies...
  2. In the search bar, enter the repository URL:
    Plain text
    https://github.com/deuna-developers/deuna-sdk-ios
  3. Pin the package dependency rule to version 2.12.6 (or higher).
  4. When the Choose Package Products dialog appears:
    • Select DeunaSDK and link it to your application target.
    • Set all other products (DeunaSDKCybersource, DeunaSDKKount, DeunaSDKRiskified, DeunaSDKSift, DeunaSDKSignifyd, DeunaSDKMercadoPago, DeunaSDKAccertify) to None unless your integration uses that specific anti-fraud provider.

CocoaPods

If your iOS project uses CocoaPods:

  1. Specify the minimum deployment target and add DeunaSDK to your Podfile:
PodfileRuby
platform :ios, '14.0'

target 'YourApp' do
  use_frameworks!
  pod 'DeunaSDK', '~> 2.12.6'

  # Add ONLY the optional fraud provider subspecs you need:
  # pod 'DeunaSDK/MercadoPago'
  # pod 'DeunaSDK/Riskified'
  # pod 'DeunaSDK/Cybersource'
  # pod 'DeunaSDK/Signifyd'
  # pod 'DeunaSDK/Kount'
  # pod 'DeunaSDK/Sift'
  # pod 'DeunaSDK/Accertify'
end
  1. Run:
Shell
pod install
  1. Disable User Script Sandboxing: In Xcode, navigate to your target's Build Settings, search for User Script Sandboxing (ENABLE_USER_SCRIPT_SANDBOXING), and set it to No.

Required Permissions for Voucher Downloads

Initialize#

Swift
import DeunaSDK

You can initialize the SDK in two ways depending on your app architecture:

Global shared instance

Initialize once during app startup (e.g., in your AppDelegate or App initializer), and access anywhere via DeunaSDK.shared:

Swift
DeunaSDK.initialize(
  environment: .sandbox, // .sandbox or .production
  publicApiKey: "YOUR_PUBLIC_API_KEY"
)

let deunaSDK = DeunaSDK.shared

Coordinator or screen instance

Create and retain an instance for a specific screen or coordinator:

Swift
let deunaSDK = DeunaSDK(
  environment: .sandbox,
  publicApiKey: "YOUR_PUBLIC_API_KEY"
)

Open Payment Widget#

The Payment Widget offers multiple payment methods to customers:

  • Credit and debit cards: Visa, Mastercard, American Express, and co-branded cards.
  • Alternative payment methods (APMs): OXXO, KueskiPay, Aplazo, Daviplata, Nequi, and more.
  • Digital wallets: Apple Pay and PayPal.

Display in a modal

Call initPaymentWidget on your DeunaSDK instance:

Swift
deunaSDK.initPaymentWidget(
  orderToken: "YOUR_ORDER_TOKEN",
  callbacks: PaymentWidgetCallbacks(
    onSuccess: { order in
      deunaSDK.close() // Close the payment widget modal
      // YOUR CODE HERE: Handle successful payment (e.g. navigate to confirmation screen)
    },
    onError: { error in
      // Error handling
      if error.type == .initializationFailed {
        deunaSDK.close() // Close the widget if initialization fails
        // YOUR CODE HERE: Handle initialization failure (e.g. show alert or fallback)
        return
      }
      if error.type == .paymentError {
        // YOUR CODE HERE: Handle payment processing failure (e.g. prompt user to retry)
      }
    },
    onClosed: { action in
      // Widget closed
      // action == .userAction (dismissed manually by customer) or .systemAction (closed via code)
      // YOUR CODE HERE: Handle modal dismissal
    },
    onCardBinDetected: { metadata in
      guard metadata != nil else { return }
      // YOUR CODE HERE: Handle card brand or BIN detection (e.g. apply discounts)
    },
    onInstallmentSelected: { installment in
      // YOUR CODE HERE: Handle installment plan selection
    },
    onPaymentProcessing: {
      // YOUR CODE HERE: Payment processing started (e.g. disable external buttons)
    },
    onEventDispatch: { event, payload in
      // YOUR CODE HERE: Track analytics event
    }
  ),
  userToken: "YOUR_USER_TOKEN", // optional
  styleFile: "YOUR_STYLE_FILE_UUID", // optional
  language: "en" // optional: "es", "en", "pt"
)

Parameter Reference

ParameterTypeModalEmbeddedDescription
orderTokenString✅✅Required. Unique token for the order created via DEUNA API.

Important: Do not define a redirect_url in order creation if you want onSuccess to run in-app.
callbacksPaymentWidgetCallbacks✅✅Required. Callbacks listening to widget events (onSuccess, onError, onClosed, onCardBinDetected, onInstallmentSelected, onPaymentProcessing, onEventDispatch).
userTokenString?✅✅Optional. Customer's DEUNA bearer token.

Important: For saved cards to appear, the email linked to userToken must match billing_address.email from order creation.
styleFileString?✅✅Optional. UUID provided by DEUNA to apply custom theme branding (colors, typography, logo).
paymentMethods[[String: Any]]?✅✅Optional. Array defining a single payment method for direct forms flow (e.g. [["paymentMethod": "bnpl", "processors": ["kueski"]]]).
languageString?✅✅Optional. Language code ("es", "en", "pt"). Overrides merchant default configuration.
behaviorWidgetBehavior?✅✅Optional. Flow customization, such as WidgetBehavior(paymentMethods: ["flowType": "twoStep"]) for PayPal, or Split Payments WidgetBehavior(paymentMethods: ["creditCard": ["splitPayments": ["maxCards": 2]]]).
fraudCredentials[String: Any]?✅✅Optional. Fraud provider credentials (e.g. ["RISKIFIED": ["storeDomain": "yourdomain.com"]]).
domainString?✅✅Optional. Overrides default base host (https://pay.deuna.io).
hidePayButtonBool❌✅Optional. Hides the built-in pay button in embedded mode when your application renders a native button.

Available experiences#

ExperienceMethodConfiguration typeImportant input
Payment WidgetinitPaymentWidget(...)PaymentWidgetConfigurationorderToken, PaymentWidgetCallbacks
Payment VaultinitElements(...)ElementsWidgetConfigurationElementsCallbacks; types may be omitted
Click to PayinitElements(...)ElementsWidgetConfigurationtypes: [["name": ElementsWidget.clickToPay]]
Next ActioninitNextAction(...)NextActionWidgetConfigurationToken for the order requiring another action
VoucherinitVoucher(...)VoucherWidgetConfigurationToken for the voucher order

Embed a widget with SwiftUI#

On iOS 14 or later, use DeunaWidget to render the payment experience embedded in your SwiftUI hierarchy.

Swift
import DeunaSDK
import SwiftUI

struct EmbeddedCheckoutScreen: View {
  let deunaSDK: DeunaSDK
  let orderToken: String

  var body: some View {
    VStack {
      DeunaWidget(
        deunaSDK: deunaSDK,
        configuration: PaymentWidgetConfiguration(
          orderToken: orderToken,
          callbacks: PaymentWidgetCallbacks(
            onSuccess: { order in
              // Explicitly release widget resources to prevent memory leaks
              deunaSDK.dispose()
              // YOUR CODE HERE: Handle successful payment
            },
            onError: { error in
              if error.type == .paymentError {
                // YOUR CODE HERE: Handle payment error
              }
            },
            onEventDispatch: { event, payload in
              // YOUR CODE HERE: Track analytics event
            }
          ),
          userToken: nil,
          styleFile: nil
        )
      )
    }
  }
}

Embedding with auto-resize and keyboard handling

By default, DeunaWidget fills its container at a fixed height. When passing autoResizeConfig: AutoResizeConfig(initialHeight: 100), the widget switches to auto-resize mode: it grows and shrinks dynamically to match the WebView content height.

When embedded inside a ScrollView, use ScrollViewFinder to capture the underlying UIScrollView and wire onScrollNeeded so focused fields automatically scroll above the keyboard:

Swift
import DeunaSDK
import SwiftUI
import UIKit

struct MyCheckoutView: View {
  let deunaSDK: DeunaSDK
  let widgetConfig: DeunaWidgetConfiguration

  @State private var outerScrollView: UIScrollView?

  var body: some View {
    ScrollView {
      VStack(spacing: 0) {
        // Invisible helper capturing the underlying UIScrollView
        Color.clear.frame(height: 0)
          .background(ScrollViewFinder { sv in outerScrollView = sv })

        // Your content above the widget

        DeunaWidget(deunaSDK: deunaSDK, configuration: widgetConfig)
          .onScrollNeeded { overlap in
            guard let sv = outerScrollView else { return }
            let newOffset = CGPoint(x: 0, y: sv.contentOffset.y + overlap)
            UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) {
              sv.setContentOffset(newOffset, animated: false)
            }
          }

        // Your content below the widget
      }
    }
  }
}

/// Traverses UIKit view hierarchy to capture the parent UIScrollView
struct ScrollViewFinder: UIViewRepresentable {
  let onFound: (UIScrollView) -> Void

  func makeUIView(context: Context) -> UIView {
    let view = UIView()
    view.isHidden = true
    DispatchQueue.main.async {
      var parent = view.superview
      while let p = parent {
        if let sv = p as? UIScrollView { onFound(sv); return }
        parent = p.superview
      }
    }
    return view
  }

  func updateUIView(_ uiView: UIView, context: Context) {}
}

Custom pay button (hidePayButton)

If your application renders its own native button outside the widget, pass hidePayButton: true and execute payments manually using isValid and submit:

Swift
let configuration = PaymentWidgetConfiguration(
  orderToken: orderToken,
  callbacks: callbacks,
  hidePayButton: true
)

// When the user taps your native pay button:
deunaSDK.isValid { isValid in
  guard isValid else {
    showValidationWarning()
    return
  }

  deunaSDK.submit { result in
    if result["status"] as? String == "error" {
      print("Submit failed:", result["message"] ?? "")
    }
  }
}
MethodDescriptionReturn Value
deunaSDK.isValid { valid in ... }Validates user input in active payment form.Bool
deunaSDK.submit { result in ... }Submits payment equivalent to clicking the widget button.[String: Any] (status: "success" or status: "error")

Filter and configure payment methods#

Direct forms flow (paymentMethods)

The DEUNA checkout architectures support launching directly into an individual payment form—a pattern known as the forms flow.

While paymentMethods is an array type, the checkout engines currently support only one element in this list. Supplying a single payment method opens the widget directly into that method's form (e.g. Kueski Pay BNPL), bypassing the method selector.

Swift
deunaSDK.initPaymentWidget(
  orderToken: orderToken,
  callbacks: callbacks,
  paymentMethods: [
    [
      "paymentMethod": "bnpl",
      "processors": ["kueski"]
    ]
  ]
)

Configuration priority resolution

PrioritySourceBehavior
1 (Highest)paymentMethods in .initPaymentWidget()Direct forms flow: Opens immediately into the single specified payment method form (e.g., Kueski), bypassing the method selector.
2include_payment_options in order creationMulti-method selection: Displays only the payment methods specified during order creation that are active on your account.
3 (Fallback)Merchant-level configuration (/payment-methods)Full selector: If omitted in both SDK call and order payload, displays all active payment methods configured for your merchant account.

PayPal configurations

Flow Type (flowType)

Swift
deunaSDK.initPaymentWidget(
  orderToken: orderToken,
  behavior: WidgetBehavior(
    paymentMethods: [
      "flowType": "twoStep" // "twoStep" (summary first) or "singleStep" (direct)
    ]
  )
)

Auto Purchase (express)

When customers have previously authorized quick payments with PayPal, enable immediate processing:

Swift
deunaSDK.initPaymentWidget(
  orderToken: orderToken,
  callbacks: callbacks,
  paymentMethods: [
    [
      "paymentMethod": "wallet",
      "processors": ["paypal_wallet"],
      "configuration": ["express": true]
    ]
  ]
)

Dynamic styling and order refresh#

Dynamically customize appearance (setCustomStyle)

Update widget styles dynamically (e.g. adapting branding upon card BIN detection):

Swift
deunaSDK.setCustomStyle(data: [
  "theme": [
    "colors": [
      "primaryTextColor": "#023047",
      "buttonPrimaryFill": "#FFB703",
      "buttonPrimaryText": "#000000"
    ]
  ]
])

Refresh order totals (refetchOrder)

When offering franchise-specific promotions (e.g., "20% off with Mastercard"), listen to onCardBinDetected, update the order on your backend, and notify the widget:

Swift
callbacks: PaymentWidgetCallbacks(
  onCardBinDetected: { metadata in
    guard let brand = metadata?["cardBrand"] as? String, brand == "Mastercard" else { return }

    Task {
      await applyDiscountOnBackend(orderId, promoCode: "MASTERCARD_20")
      deunaSDK.refetchOrder { updatedOrder in
        print("Order refreshed with discount:", updatedOrder)
      }
    }
  }
)

Payment Vault and Click to Pay#

Payment Vault is a component of DEUNA Payment solutions that allows saving cards securely and paying via Click to Pay without processing regular checkouts.

Display in a modal

Swift
deunaSDK.initElements(
  userToken: "<USER_TOKEN>", // optional if userInfo is provided
  userInfo: DeunaSDK.UserInfo(
    firstName: "Esteban", // optional if userToken is provided
    lastName: "Posada", // optional if userToken is provided
    email: "esteban@deuna.com" // required
  ),
  callbacks: ElementsCallbacks(
    onSuccess: { response in
      deunaSDK.close() // Close the Vault widget
      // YOUR CODE HERE: Handle card saved successfully
    },
    onError: { error in
      deunaSDK.close() // Close the Vault widget on error
      // YOUR CODE HERE: Handle error
    },
    onClosed: { action in
      // Widget closed: action == .userAction or .systemAction
      // YOUR CODE HERE: Handle widget dismissal
    },
    onEventDispatch: { type, response in
      // YOUR CODE HERE: Listen to widget events
    }
  ),
  orderToken: "YOUR_ORDER_TOKEN", // optional
  widgetExperience: ElementsWidgetExperience( // optional
    userExperience: ElementsWidgetExperience.UserExperience(
      showSavedCardFlow: true, // optional: show card saving toggle
      defaultCardFlow: true // optional: show toggle to save as default
    )
  )
)

Display embedded (SwiftUI)

Swift
DeunaWidget(
  deunaSDK: deunaSDK,
  configuration: ElementsWidgetConfiguration(
    callbacks: ElementsCallbacks(
      onSuccess: { response in
        deunaSDK.dispose() // Explicitly release widget resources
        // YOUR CODE HERE: Handle success
      },
      onError: { error in
        // YOUR CODE HERE: Handle error
      },
      onEventDispatch: { type, response in
        // YOUR CODE HERE: Listen to widget events
      }
    ),
    userInfo: DeunaSDK.UserInfo(
      firstName: "Esteban",
      lastName: "Posada",
      email: "esteban@deuna.com"
    ),
    orderToken: "YOUR_ORDER_TOKEN",
    autoResizeConfig: AutoResizeConfig(initialHeight: 150)
  )
)

Click to Pay

Swift
deunaSDK.initElements(
  userInfo: DeunaSDK.UserInfo( // mandatory for Click To Pay
    firstName: "Esteban",
    lastName: "Posada",
    email: "esteban@deuna.com"
  ),
  callbacks: ElementsCallbacks(
    onSuccess: { data in
      deunaSDK.close() // Close the widget
      // YOUR CODE HERE: Handle Click to Pay success
    },
    onError: { error in
      deunaSDK.close() // Close the widget
      // YOUR CODE HERE: Handle error
    },
    onClosed: { action in
      // YOUR CODE HERE: Handle widget dismissal
    },
    onEventDispatch: { type, data in
      // YOUR CODE HERE: Listen to widget events
    }
  ),
  types: [
    [
      "name": ElementsWidget.clickToPay // Pass this to render Click to Pay
    ]
  ]
)

Parameter Reference

ParameterTypeModalEmbeddedDescription
callbacksElementsCallbacks✅✅Required. Event listeners (onSuccess, onError, onClosed, onEventDispatch).
userInfoDeunaSDK.UserInfo?✅✅Customer's firstName, lastName, and email. Required for Click to Pay.
userTokenString?✅✅Optional. DEUNA bearer token. If passed, omit userInfo.
types[[String: Any]]?✅✅Optional. Omit to render the default Card Vault experience. Pass [["name": ElementsWidget.clickToPay]] to render Click to Pay.
orderTokenString?✅✅Optional. Unique order token. Customer details can be extracted from billing_address.
styleFileString?✅✅Optional. Theme customization UUID.
widgetExperienceElementsWidgetExperience?✅✅Optional. showSavedCardFlow (card saving toggle) and defaultCardFlow (save as default toggle).
domainString?✅✅Optional. Overrides host (https://elements.deuna.com).
hidePayButtonBool❌✅Optional. Hides button when embedded. Use isValid and submit for custom buttons.

Native Apple Pay#

Check merchant configuration and device capability before rendering an Apple Pay button.

Swift
let userInfo = DeunaSDK.UserInfo(email: "ada@example.com")
let params = GetWalletsAvailableParams(
  orderToken: "YOUR_ORDER_TOKEN",
  userInfo: userInfo
)

deunaSDK.getWalletsAvailable(params: params) { wallets, error in
  if let error = error {
    // YOUR CODE HERE: Handle wallet availability check error
    return
  }

  // Check if Apple Pay is available on this device and merchant account
  let isApplePayAvailable = wallets.contains(.applePay)
  // YOUR CODE HERE: Update Apple Pay button visibility in UI
}

After availability succeeds, launch the native Apple Pay sheet using initElements:

Swift
deunaSDK.initElements(
  userInfo: userInfo,
  callbacks: ElementsCallbacks(
    onSuccess: { data in
      // YOUR CODE HERE: Handle Apple Pay authorization success
    },
    onError: { error in
      // YOUR CODE HERE: Handle Apple Pay error
    },
    onClosed: { action in
      // YOUR CODE HERE: Handle Apple Pay sheet dismissal
    }
  ),
  types: [
    ["name": "APPLE_PAY"]
  ],
  orderToken: "YOUR_ORDER_TOKEN"
)

The application must include the Apple Pay entitlement and a provisioning profile containing the merchant identifier. See Apple Pay for the full setup.

Continue a pending action#

Use the same order token when DEUNA reports that the payment requires a supported 3DS challenge or redirect.

Swift
deunaSDK.initNextAction(
  orderToken: "YOUR_ORDER_TOKEN",
  callbacks: NextActionCallbacks(
    onSuccess: { order in
      deunaSDK.close()
      // YOUR CODE HERE: Handle 3DS or action completion
    },
    onError: { error in
      deunaSDK.close()
      // YOUR CODE HERE: Handle error
    },
    onClosed: { action in
      // YOUR CODE HERE: Handle modal dismissal
    }
  )
)

The SDK presents supported external URLs and returns control to the widget. Use callback state for the interface and the authoritative DEUNA payment state for fulfillment.

Open a voucher#

Swift
deunaSDK.initVoucher(
  orderToken: "YOUR_ORDER_TOKEN",
  callbacks: VoucherCallbacks(
    onSuccess: { order in
      // YOUR CODE HERE: Handle voucher display
    },
    onError: { error in
      // YOUR CODE HERE: Handle error
    },
    onClosed: { action in
      // YOUR CODE HERE: Handle modal dismissal
    }
  )
)

Voucher callbacks follow the same success, error, close, and event-dispatch lifecycle as the other payment experiences.

Fraud-device data and fingerprinting#

The DEUNA iOS SDK uses a modular architecture: it does not bundle third-party fraud SDKs by default. Only add the specific provider(s) you plan to use. If you don't use a provider, you don't pay any app size penalty.

Supported Providers

ProviderSPM ProductCocoaPods SubspecRequired Parameters
MERCADOPAGODeunaSDKMercadoPagopod 'DeunaSDK/MercadoPago'(None required - pass [:])
RISKIFIEDDeunaSDKRiskifiedpod 'DeunaSDK/Riskified'storeDomain
CYBERSOURCEDeunaSDKCybersourcepod 'DeunaSDK/Cybersource'orgId, merchantId (Optional: fpServer)
SIGNIFYDDeunaSDKSignifydpod 'DeunaSDK/Signifyd'orgId (Optional: fpServer)
KOUNTDeunaSDKKountpod 'DeunaSDK/Kount'merchantId, clientId, dataCollectorUrl, environment
SIFTDeunaSDKSiftpod 'DeunaSDK/Sift'accountId, beaconKey
ACCERTIFYDeunaSDKAccertifypod 'DeunaSDK/Accertify'Config files in Xcode bundle resources

Accertify bundle assets setup

The Accertify integration requires two configuration files inside your Xcode project. Ensure both files are added to your main target and included under Copy Bundle Resources:

  1. server_keys_message_hosted.json: The public encryption keys file provided by Accertify.
  2. InMobile.plist: Property list file containing your merchant details:
    • accountID (String): Your account GUID
    • registrationURL (String): InMobile registration endpoint
    • logURL (String): InMobile log endpoint
    • serverKeyData (String): server_keys_message_hosted.json

Direct call via generateFraudId

Call deunaSDK.generateFraudId with the parameters for your contracted providers:

Swift
deunaSDK.generateFraudId(params: [
  "RISKIFIED": [
    "storeDomain": "yourdomain.com"
  ],
  "CYBERSOURCE": [
    "orgId": "your_org_id",
    "merchantId": "your_merchant_id"
  ],
  "MERCADOPAGO": [:],
  "ACCERTIFY": [:]
]) { fraudId in
  // fraudId is a Base64-encoded JSON string, or nil if providers failed or none configured
  guard let fraudId = fraudId else { return }
  print("Base64 Fraud ID:", fraudId)
}

Pass fraudCredentials directly to widgets

When launching widgets (initPaymentWidget, PaymentWidgetConfiguration, etc.), you can pass your configuration directly in fraudCredentials. The SDK generates the fraud ID internally at purchase time:

Swift
deunaSDK.initPaymentWidget(
  orderToken: orderToken,
  callbacks: callbacks,
  fraudCredentials: [
    "RISKIFIED": [
      "storeDomain": "yourdomain.com"
    ],
    "CYBERSOURCE": [
      "orgId": "your_org_id",
      "merchantId": "your_merchant_id"
    ],
    "MERCADOPAGO": [:]
  ]
)

Behavior notes

  • Zero-bloat & Crash-safe: If a provider is included in the dictionary request but its framework or subspec was not linked, the SDK safely ignores it without throwing an exception or crashing.
  • Parallel execution: Requested providers profile concurrently on background threads.
  • Fallback behavior: If no valid providers are configured or available on the device, the completion handler receives nil.

Callback and lifecycle reference#

Callback / MethodModalEmbeddedDescription
onSuccess✅✅Payment or card vault operation completed successfully. Receives order or credential payload. Call deunaSDK.close() (modal) or deunaSDK.dispose() (embedded).
onError✅✅Error occurred during initialization or payment processing. Receives typed PaymentsError or ElementsError.
onClosed✅❌Modal closed. Receives CloseAction.userAction (dismissed manually) or CloseAction.systemAction (closed via close()).
onPaymentProcessing✅✅Triggered when customer taps pay and payment processing begins. Use to disable buttons and show progress spinners.
onCardBinDetected✅✅Triggered when a card BIN is detected or cleared (nil when user deletes the number). Use for franchise discounts.
onInstallmentSelected✅✅Triggered when customer selects deferred installments (nil on full one-time payment).
onEventDispatch✅✅Emits all underlying CheckoutEvent or ElementsEvent occurrences with data payloads.
close()✅❌Dismisses the active modal presentation while retaining the SDK instance.
dispose()❌✅Releases the embedded controller and WebView when the view hierarchy unmounts.

Handling errors (onError)

Inspect error.type to distinguish initialization failures from processing declines:

Swift
onError: { error in
  switch error.type {
  case .initializationFailed:
    // Widget failed to load; close presentation and notify user
    deunaSDK.close()
    showToast("Payment service temporarily unavailable")
  case .paymentError:
    // Transaction declined or processor error
    showRetry(error)
  @unknown default:
    showGenericError(error)
  }
}

Official examples and source#

See WalletsScreen.swift for native Apple Pay and EmbeddedScreen.swift for the SwiftUI lifecycle.

In this section