Skip to content

FPS not shown in Drop-In picker (Android 6.8.0) #340

Description

@ilikekobe0502

Summary

When integrating Drop-In with "fps" in Session.setPaymentMethods(...),
the FPS tab does not appear in the picker, even though:

  • the Airwallex dashboard shows FPS as Activated for our HK-registered merchant
  • session.countryCode = "HK", session.currency = "HKD", oneoff
  • the payment-redirect artifact is added and RedirectComponent.PROVIDER is
    registered in setSupportComponentProviders(...)
  • the published Android Drop-In docs include "fps" in the example
    setPaymentMethods(listOf("paypal", "card", "googlepay", "fps", "alipayhk"))

Other redirect-style methods (alipaycn, alipayhk, wechatpay) render
correctly with the same setup. Only fps is silently filtered out.

Environment

  • SDK: airwallex-payment-android 6.8.0 (also verified on 6.9.0 — same
    code path, no behavioral change)
  • Modules used: payment, payment-card, payment-googlepay, payment-redirect
  • Environment: production (api.airwallex.com)
  • Merchant: HK-registered entity, padc=HK on the JWT client_secret
  • Drop-In layout: PaymentSheet (Tab)

Repro

1. SDK calls config/payment_method_types and FPS is returned with has_schema=false

The Android SDK calls the list endpoint with these query params (from
AirwallexApiRepository.retrieveAvailablePaymentMethodsUrl and
Airwallex.retrieveAvailablePaymentMethodsPaged):

GET /api/v1/pa/config/payment_method_types
  ?__resources=true
  &os_type=android
  &lang=<device locale>
  &page_num=0
  &active=true
  &transaction_currency=HKD
  &country_code=HK

Among the returned items:

{
  "name": "fps",
  "display_name": "FPS",
  "transaction_mode": "oneoff",
  "flows": [],
  "transaction_currencies": ["CNY", "HKD"],
  "country_codes": ["*"],
  "active": true,
  "resources": {
    "has_schema": false,
    "logos": { "...": "..." }
  }
}

For comparison, alipaycn (renders fine) is returned with
"has_schema": true.

2. The detail endpoint for fps is internally inconsistent

GET /api/v1/pa/config/payment_method_types/fps?transaction_currency=HKD&country_code=HK&...
{
  "name": "fps",
  "has_schema": false,
  "field_schemas": [
    {
      "transaction_mode": "oneoff",
      "flow": "webqr",
      "fields": [
        { "name": "flow", "ui_type": "list", "type": "enum", "hidden": true,
          "candidates": [ { "display_name": "webqr", "value": "webqr" } ] }
      ]
    }
  ]
}

has_schema: false is set despite field_schemas being non-empty. For
alipaycn and wechatpay, has_schema: true matches a non-empty
field_schemas. So at minimum the backend response is inconsistent for
fps.

3. SDK filters FPS out because of has_schema=false

components-core/.../AirwallexPlugins.kt L48-58
(source):

fun getProvider(paymentMethodType: AvailablePaymentMethodType): ActionComponentProvider<out ActionComponent>? {
    return runCatching {
        getProvider(ActionComponentProviderType.valueOf(paymentMethodType.name.uppercase()))
    }.getOrElse {
        if (paymentMethodType.resources?.hasSchema == true) {
            getProvider(ActionComponentProviderType.REDIRECT)
        } else {
            null
        }
    }
}

ActionComponentProviderType only contains CARD, REDIRECT,
WECHATPAY, GOOGLEPAY, so valueOf("FPS") throws and execution
falls into the getOrElse block. Because the backend returns
has_schema=false, the function returns null.

Then in components-core/.../Airwallex.kt L504-511
(source):

response.items = response.items.filter { paymentMethod ->
    paymentMethod.transactionMode == transactionMode &&
        AirwallexPlugins.getProvider(paymentMethod)
            ?.canHandleSessionAndPaymentMethod(session, paymentMethod, activity)
            ?: false
}

For fps: null?.canHandleSessionAndPaymentMethod(...) ?: falsefalse.
fps is dropped from availablePaymentMethods before
PaymentMethodsTabSection ever sees it, so the picker doesn't render
the tab.

Side-by-side trace

Step alipaycn (works) fps (filtered out)
valueOf(name.uppercase()) throws (no ALIPAYCN) throws (no FPS)
Enters getOrElse yes yes
resources.hasSchema == true true false
Returns RedirectComponent.PROVIDER null
Filter at Airwallex.kt L504 passes dropped
Visible in picker

Two possible interpretations

There are two reasonable readings of this — would appreciate confirmation
on which one is correct:

(A) Backend bug — fps should expose has_schema=true. The detail
endpoint already returns non-empty field_schemas, so the list endpoint
flag is inconsistent with itself. Setting has_schema=true would let
the existing fallback path route fps through RedirectComponent.

(B) Intentional — fps cannot work via Android Drop-In because it only
supports flow=webqr (no inapp). If this is the case, the published
doc example
(screenshot)
listing "fps" under setPaymentMethods(...) for Android is misleading
and should be updated.

If (B), what is the recommended way to offer FPS in an Android app
today? Hosted Payment Page redirect? Native confirmPaymentIntent with
paymentMethod.type="fps" + manual NextAction(type=REDIRECT)
handling? A future SDK revision that adds FPS as a first-class
ActionComponentProviderType?

Impact

We cannot ship FPS to HK users via the Android Drop-In flow until this
is resolved (or until we move to a workaround). FPS is a top requested
payment method for HK conversion, so this is blocking.

Suggested fixes (one or more of)

  1. Update backend config/payment_method_types so the list endpoint
    returns resources.has_schema=true for fps, matching the existing
    field_schemas and matching the behavior of alipaycn /
    alipayhk / wechatpay.
  2. Add FPS to ActionComponentProviderType enum and have it
    transparently dispatch through RedirectComponent regardless of the
    has_schema flag (would also help paypal, applepay, future
    methods).
  3. Update the Android Drop-In documentation to either confirm FPS as a
    supported value or remove it from the example.

Happy to share more diagnostic data (sanitized response payloads, app
logs) on request. Thank you!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions