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(...) ?: false → false.
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)
- 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.
- 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).
- 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!
Summary
When integrating Drop-In with
"fps"inSession.setPaymentMethods(...),the FPS tab does not appear in the picker, even though:
session.countryCode = "HK",session.currency = "HKD", oneoffpayment-redirectartifact is added andRedirectComponent.PROVIDERisregistered in
setSupportComponentProviders(...)"fps"in the examplesetPaymentMethods(listOf("paypal", "card", "googlepay", "fps", "alipayhk"))Other redirect-style methods (
alipaycn,alipayhk,wechatpay) rendercorrectly with the same setup. Only
fpsis silently filtered out.Environment
airwallex-payment-android6.8.0 (also verified on 6.9.0 — samecode path, no behavioral change)
payment,payment-card,payment-googlepay,payment-redirectapi.airwallex.com)padc=HKon the JWT client_secretRepro
1. SDK calls
config/payment_method_typesand FPS is returned withhas_schema=falseThe Android SDK calls the list endpoint with these query params (from
AirwallexApiRepository.retrieveAvailablePaymentMethodsUrlandAirwallex.retrieveAvailablePaymentMethodsPaged):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
fpsis internally inconsistent{ "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: falseis set despitefield_schemasbeing non-empty. Foralipaycnandwechatpay,has_schema: truematches a non-emptyfield_schemas. So at minimum the backend response is inconsistent forfps.3. SDK filters FPS out because of
has_schema=falsecomponents-core/.../AirwallexPlugins.ktL48-58(source):
ActionComponentProviderTypeonly containsCARD,REDIRECT,WECHATPAY,GOOGLEPAY, sovalueOf("FPS")throws and executionfalls into the
getOrElseblock. Because the backend returnshas_schema=false, the function returnsnull.Then in
components-core/.../Airwallex.ktL504-511(source):
For
fps:null?.canHandleSessionAndPaymentMethod(...) ?: false→false.fpsis dropped fromavailablePaymentMethodsbeforePaymentMethodsTabSectionever sees it, so the picker doesn't renderthe tab.
Side-by-side trace
valueOf(name.uppercase())ALIPAYCN)FPS)getOrElseresources.hasSchema == trueRedirectComponent.PROVIDERAirwallex.ktL504Two 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 detailendpoint already returns non-empty
field_schemas, so the list endpointflag is inconsistent with itself. Setting
has_schema=truewould letthe existing fallback path route
fpsthroughRedirectComponent.(B) Intentional — fps cannot work via Android Drop-In because it only
supports
flow=webqr(noinapp). If this is the case, the publisheddoc example
(screenshot)
listing
"fps"undersetPaymentMethods(...)for Android is misleadingand should be updated.
If (B), what is the recommended way to offer FPS in an Android app
today? Hosted Payment Page redirect? Native
confirmPaymentIntentwithpaymentMethod.type="fps"+ manualNextAction(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)
config/payment_method_typesso the list endpointreturns
resources.has_schema=trueforfps, matching the existingfield_schemasand matching the behavior ofalipaycn/alipayhk/wechatpay.FPStoActionComponentProviderTypeenum and have ittransparently dispatch through
RedirectComponentregardless of thehas_schemaflag (would also helppaypal,applepay, futuremethods).
supported value or remove it from the example.
Happy to share more diagnostic data (sanitized response payloads, app
logs) on request. Thank you!