跳轉到主要內容
本頁說明商戶 App 或 H5 如何透過 QFPay API 與 Intent / Universal Link 等方式喚起銀行支付應用,完成 FPS App-to-App 的支付流程。支援 Android 與 iOS,並提供代碼範例與 Demo 下載連結。

1. 取得支付參數

API 端點/trade/v1/payment
請求方法POST
支付編碼(pay_type)802010

請求參數

參數名稱是否必填類型描述
通用請求參數依照平台設定詳見 通用請求參數
僅適用於 HSBC FPS 商戶如你的 FPS 為 HSBC 直連模式,商戶必須申請一張 獨立的 SSL 憑證(證書),且該憑證之 網域名稱必須與商戶主體一致此設定為以下用途所必須:
  • iOS Universal Link 回調驗證
  • Android HTTPS 回調/重導驗證
  • HSBC 生產環境上線及安全審核
詳細申請流程、CSR 產生方式及所需文件,請參閱:
FPS e-Cert 申請說明文件

回應參數

參數名稱類型描述
通用回應參數依照平台設定詳見 通用回應參數
pay_paramsString(128)用於拉起銀行 FPS 支付應用的 URL,例如:https://fps.xxx/xxx

2. Android FPS 支付流程

2.1 原生 App-to-App 流程

  1. 商戶先透過 API 取得 pay_params(URL)
  2. 透過 Android Intent 啟動 FPS 支付 App
  3. 設定 Action:hk.com.hkicl,並以 key url 傳入支付 URL
  4. 使用 startActivityForResult 連動支付 App
  5. onActivityResult 接收支付結果
  6. 商戶 App 應以自身訂單狀態(建議查詢 API / 後端訂單狀態)確認最終結果
Fps App Call App Chin
Android Java 啟動範例
Java
/// 支付發起請求代碼
int payRequestCode = 100;

// 支付鏈接參數
String payUrl = "https://fps.qfpay.global/trade/v1/urltranslate/PAYCORE_SHORT_URL_202511075370911194";

// 封裝支付選擇應用(銀行 app)Intent
Intent intent = new Intent("hk.com.hkicl");

// Intent 添加參數 “url”,值為支付鏈接參數
intent.putExtra("url", payUrl);

// 啟動 app 選擇器,選擇支付銀行
startActivityForResult(intent, payRequestCode);

// 獲取支付返回結果
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data, @NonNull ComponentCaller caller) {
    super.onActivityResult(requestCode, resultCode, data, caller);
    if (requestCode == payRequestCode) {
        if (resultCode == RESULT_OK) { // 支付成功
        } else if (resultCode == RESULT_CANCELED) { // 支付失敗
        }
    }
}

2.2 Android H5-to-App 支付流程

H5 頁面可透過 WebView 調用 FPS App 完成支付,需實作以下幾步:
  1. WebView 設定
    • 啟用 JavaScript:webView.getSettings().setJavaScriptEnabled(true)
    • 綁定 JS 與 Android 溝通橋接:addJavascriptInterface(new JsBridge(), "AndroidBridge")
  2. H5 觸發支付
    • 用戶點擊付款後,H5 以 JS 呼叫 Android:
      AndroidBridge.handleMessage(JSON.stringify({ url: 'https://fps.qfapi.com/xxx' }))
  3. 原生 App 接收參數並拉起支付 App
    • 組裝 Intent 發送支付請求
  4. 接收結果並回傳給 H5
    • onActivityResult() 內使用 JS 回傳 WebView
  5. 商戶應以自身訂單查詢為準確認最終支付狀態
Android WebView H5-to-App 範例
Java
public class Web2AppCallPayActivity extends Activity {

    /**
     * 商户 App 加載的 H5 連結
     */
    private static final String WEB_PAY_LINK = "https://img-int.qfapi.com/upstatic/20251119/fpsH5CallApp/index.html";

    /**
     * 商户 App 內部 Web 組件
     */
    private WebView webView;

    /**
     * Web 端發起支付時傳入的 callBackId
     */
    private String callBackId;

    /**
     * 支付發起請求代碼
     */
    int payRequestCode = 100;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);
        setContentView(R.layout.more_view);

        webView = findViewById(R.id.web_pay);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.addJavascriptInterface(this, "AndroidInterface");
        webView.loadUrl(WEB_PAY_LINK);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data, @NonNull ComponentCaller caller) {
        super.onActivityResult(requestCode, resultCode, data, caller);
        if (requestCode == payRequestCode) {
            EvaluateBean evaluateBean = new EvaluateBean();
            evaluateBean.setCode(resultCode);
            if (resultCode == RESULT_OK) {
                evaluateBean.setRespmsg("Pay Success");
            } else if (resultCode == RESULT_CANCELED) {
                evaluateBean.setRespmsg("Pay Cancel");
            }
            String evaluateJson = new Gson().toJson(evaluateBean);

            webView.evaluateJavascript(
                "javascript:window.handleNativeCallback('" + callBackId + ")" + ",(" + evaluateJson + "')",
                null
            );
        }
    }

    @JavascriptInterface
    public void handleMessage(String paramFromWebPay) {
        if (TextUtils.isEmpty(paramFromWebPay)) return;
        WebParamsBean webParamsBean = new Gson().fromJson(paramFromWebPay, WebParamsBean.class);

        callBackId = webParamsBean.getCallbackId();
        String paymentRequestURL = webParamsBean.getParams().getPaymentRequestURL();
        launchBankPay(paymentRequestURL);
    }

    private void launchBankPay(String paymentRequestURL) {
        Intent intent = new Intent("hk.com.hkicl");
        intent.putExtra("url", paymentRequestURL);
        startActivityForResult(intent, payRequestCode);
    }
}

3. iOS App-to-App 支付流程

3.1 原生 App 啟動 FPS 支付 App

  1. 商戶先調用支付參數接口,獲取支付參數 URL(pay_params
  2. 商戶 App 使用 iOS App Extension 框架 + UIActivityViewController 調起支援的支付 App(如銀行 App)
  3. 消費者在支付 App 完成付款後,透過回調參數 callback(即商戶 App 的 Universal Link)跳轉回商戶 App
  4. 商戶 App 必須根據自身訂單狀態確認最終支付結果
參考:
#import "FPSAppCallAppTool.h"
#import <UIKit/UIKit.h>
#import "define.h"

@implementation FPSAppCallAppTool

+ (FPSAppCallAppTool *)shareInstance {
    static FPSAppCallAppTool *model = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!model) {
            model = [[FPSAppCallAppTool alloc] init];
        }
    });
    return model;
}

- (void)fpsPaymentResult:(NSDictionary *) result {
    NSLog(@"%@", result);
    [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationNameFPSPaymentH5CallAppResult object:result];
}

- (UIViewController *)getCurrentWindowRootVC {
    for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) {
        if ([scene isKindOfClass:[UIWindowScene class]]) {
            UIWindowScene *windowScene = (UIWindowScene *)scene;
            for (UIWindow *window in windowScene.windows) {
                if (window.isKeyWindow) {
                    return window.rootViewController;
                }
            }
        }
    }
    return nil;
}

- (void)invokePaymentExtension:(NSString *)paymentRequestURL {
    @try {
        if (!paymentRequestURL || paymentRequestURL.length <= 0) {
            [self showAlert];
            return;
        }
    } @catch (NSException *exception) {
        return;
    }

    NSString *callbackURL = @"https://img-int.qfapi.com/trade/123"; //

    NSDictionary *paymentPayload = @{
        @"URL": paymentRequestURL,
        @"callback": callbackURL
    };

    NSItemProvider *itemProvider = [[NSItemProvider alloc]
        initWithItem:paymentPayload
        typeIdentifier:@"hk.com.hkicl"];

    NSExtensionItem *extensionItem = [[NSExtensionItem alloc] init];
    extensionItem.attachments = @[itemProvider];

    UIActivityViewController *activityVC = [[UIActivityViewController alloc]
        initWithActivityItems:@[extensionItem]
        applicationActivities:nil];

    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        activityVC.popoverPresentationController.sourceView = [self getCurrentWindowRootVC].view;
        activityVC.popoverPresentationController.sourceRect = [self getCurrentWindowRootVC].view.frame;
        activityVC.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionUp;
    }

    activityVC.completionWithItemsHandler = ^(UIActivityType  _Nullable activityType,
                                            BOOL completed,
                                            NSArray * _Nullable returnedItems,
                                            NSError * _Nullable error) {
        if (completed) {
            NSLog(@"用户选择了擴展:%@,處理完成", activityType);
        } else if (error) {
            NSLog(@"擴展調用失敗:%@", error.localizedDescription);
        } else {
            NSLog(@"用户取消了操作");
        }
    };

    [[self getCurrentWindowRootVC] presentViewController:activityVC animated:YES completion:nil];
}

- (void)showAlert{
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"没有輸入參數"
                                                                   message:@"請先輸入FPS支付參數"
                                                            preferredStyle:UIAlertControllerStyleAlert];

    [alert addAction:[UIAlertAction actionWithTitle:@"确定"
                                               style:UIAlertActionStyleCancel
                                             handler:nil]];
    [[self getCurrentWindowRootVC] presentViewController:alert animated:YES completion:nil];
}

#pragma mark - 解析連結中的查詢參數
- (void)parseQueryParamsFromCallbackURL:(NSURL *)url {
    NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
    NSArray<NSURLQueryItem *> *queryItems = components.queryItems;

    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    for (NSURLQueryItem *item in queryItems) {
        if (item.value) {
            params[item.name] = item.value;
        }
    }
    [self fpsPaymentResult: [params copy]];
}

@end

3.2 iOS H5-to-App 雙向通信支付流程

H5 調用原生 App 的核心在於建立雙向通信機制,完成支付參數的傳遞與結果回傳:
  1. H5 調用後端接口獲得支付參數
  2. 使用 JsBridge 將支付參數傳遞給原生 App
  3. App 接收到後呼叫 FPS App 進行支付(同 3.1 流程)
  4. 支付完成後 App 將結果透過 evaluateJavaScript 回傳給 H5 顯示
  5. 使用 WKWebView + JSBridge 實現
#import "FPSWKWebView.h"
#import <WebKit/WebKit.h>
#import "FPSAppCallAppTool.h"
#import "define.h"

@interface FPSWKWebView ()<WKScriptMessageHandler, WKNavigationDelegate, WKUIDelegate>
@property(copy, nonatomic) NSString *callbackId;
@end

@implementation FPSWKWebView

- (instancetype)initWithFrame:(CGRect)frame{
    WKUserContentController *userContentController = [[WKUserContentController alloc] init];
    [userContentController addScriptMessageHandler:self name:@"NativeBridge"];

    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    config.processPool = [[WKProcessPool alloc] init];
    config.userContentController = userContentController;
    config.preferences.javaScriptCanOpenWindowsAutomatically = YES;
    [config.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];

    self = [super initWithFrame:frame configuration:config];
    if (self) {
        self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        self.navigationDelegate = self;
        self.UIDelegate = self;
        if (@available(iOS 16.4, *)) {
            self.inspectable = YES;
        }
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(fpsPaymentResult:)
                                                     name:kNotificationNameFPSPaymentH5CallAppResult
                                                   object:nil];
    }
    return self;
}

- (void)fpsPaymentResult:(NSNotification *)notification {
    NSDictionary *params = nil;
    NSString *ret = notification.object[@"is_successful"];
    if ([ret isEqualToString: @"0"]) {
        params = @{@"code": @"3000", @"respmsg": @"Failed"};
    } else {
        params = @{@"code": @"0000", @"respmsg": @"Success"};
    }

    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:params options:0 error:&error];
    if (error) return;

    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    NSString *jsCode = [NSString stringWithFormat:@"window.handleNativeCallback('%@', '%@');",
                        self.callbackId, jsonString];

    [self evaluateJavaScript:jsCode completionHandler:nil];
}

- (void)userContentController:(WKUserContentController *)userContentController
      didReceiveScriptMessage:(WKScriptMessage *)message {

    if (![message.name isEqualToString:@"NativeBridge"]) return;

    NSDictionary *body = message.body;
    NSString *action = body[@"action"];
    NSString *callbackId = body[@"callbackId"];

    if ([action isEqualToString:@"FPSH5CallApp"]) {
        NSString *paymentRequestURL = body[@"params"][@"paymentRequestURL"];
        self.callbackId = callbackId;
        [[FPSAppCallAppTool shareInstance] invokePaymentExtension:paymentRequestURL];
    }
}

- (void)dealloc {
    [self.configuration.userContentController removeScriptMessageHandlerForName:@"NativeBridge"];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kNotificationNameFPSPaymentH5CallAppResult object:nil];
}

@end

下載範例 Demo