# 1. 场景介绍

    适用于商户在移动端APP中集成Gate Pay支付功能

    商户APP调用Gate Pay提供的SDK调用Gate Pay支付模块,商户APP会跳转到 Gate APP中完成支付,支付完后跳回到商户APP内,最后展示支付结果。

    目前支持手机系统有:IOS(苹果)、Android(安卓)。

    交互细节如下:

    步骤1:用户进入商户APP,选择商品下单、确认购买,进入支付环节。商户服务后台生成支付订单,签名后将数据传输到APP端。

    步骤2:用户点击后发起支付操作,进入到Gate 界面,调起Gate Pay支付,出现确认支付界面。

    步骤3:用户确认收款方和金额,点击立即支付后出现输入密码界面。

    步骤4:输入正确密码后,支付完成,现支付结果页面。

    步骤5:回跳到商户APP中,商户APP根据支付结果个性化展示订单处理结果。

    # 2. 开发指引

    以下是交互时序图,下单API、支付结果通知API和查询订单API等都涉及签名过程,调用都必须在商户服务器端完成。如下图所示

    调起支付时序图

    调起支付主要流程:

    步骤1-6:

    用户发起支付,商户端调用GatePay下单(/v1/pay/order)API创建预支付订单。

    商户调用下单API后,分正常返回和异常返回情况:

    正常返回:预付单创建成功返回prepay_id。

    异常返回:返回http code或错误码,商户可根据错误码说明来排查原因并执行下一步操作。

    步骤6-14:

    情况一:第六步如果商户在成功调用下单接口获取预订单ID,并且同时调起支付组件接口需求的参数和签名返回给商户APP端,商户APP端可直接调用调起支付组件接口。

    情况二:第六步如果商户在返回预订单ID没有返回调起参数和签名,商户服务端侧需要提供获取签名接口,商户客户端需要主动请求商户后端获取。

    当调起支付组件签名验证返回成功,SDK弹出支付组件并调用查询订单信息。

    步骤15-17:

    用户支付成功后,支付结果会同步通知给客户端。商户可通过以下两种方式获取订单状态:

    方法一:支付结果通知。用户支付成功后,GatePay会将支付成功的结果以回调通知的形式同步给商户,商户的回调地址需要在调用APP下单API时传入notify_url参数。

    方法二:当因网络抖动或本身notify_url存在问题等原因,导致无法接收到回调通知时,商户也可主动调用查询订单API(/v1/pay/order/query) 来查询订单状态 。

    # 3. 相关接口列表

    # 3.1 调起支付组件验证接口

    作用: 调起支付组件签名验证接口。

    • 数据类型:JSON (content-type: application/json)
    • 请求方式:POST
    • 验证方式:签名验证
    • Path: /v1/pay/open/sdk

    接口定义:

    参数名 变量 类型 必填 描述
    应用ID clientid string 是 Gate开放平台审核通过的移动应用appid
    商户号 merchantid string 是 商户号
    预订单ID prepayid string 是 预订单ID
    订单详情扩展字符串 package_ext string 是 暂填写固定值GatePay

    请求示例:

    curl --location --request POST 'https://openplatform.gateapi.io/v1/pay/open/sdk' \
    --header 'Content-Type: application/json' \
    --header 'X-GatePay-Certificate-ClientId: zb6WUrBDZlRAT7qz' \
    --header 'X-GatePay-Timestamp: 1674117221032' \
    --header 'x-GatePay-Nonce: 7436636664' \
    --header 'x-GatePay-Signature: 11e658cf6b09d917caf9d4bb6ec4493431c55f066a694e58a5571a4a1a114ebc2011d346f340e54a5a2e2edaa172e907742fbdc99b94d009dade4b551daabd07' \
    --data-raw '{"prepayid":"50620368071692288","package_ext":"ext"}'
    

    成功响应:

    {
        "status":"SUCCESS",
        "code":"000000",
        "errorMessage":"",
        "data":{
            "prepayid":"50620368071692288"
        }
    }
    

    失败响应:

    {
        "status":"FAIL",
        "code":"10002",
        "errorMessage":"",
        "data":{}
    }
    

    # 3.2 订单状态查询接口

    为方便商户同步订单信息,GatePay提供了查询接口用于查询预付单与退款单信息

    • 请求类型:JSON (content-type: json)
    • 请求方法:POST
    • 验证方式:签名验证
    • Path:/v1/pay/order/query

    接口定义:

    属性名 类型 是否必须 说明
    prepayId string 否 预付单信息
    merchantTradeNo string 否 商户订单id。预付单id与此id提供一个即可

    请求示例:

    curl --location '127.0.0.1:8201/v1/pay/order/query' \
    --header 'Content-Type: application/json' \
    --header 'X-GatePay-Certificate-ClientId: zb6WUrBDZlRAT7qz' \
    --header 'X-GatePay-Timestamp: 1678442053228' \
    --header 'x-GatePay-Nonce: 2002257450' \
    --header 'x-GatePay-Signature: ce9ed61041a3a76d292c7475e3957d2e788dd21e7e8b6aa523867a047892e4395a0dfd0e851e9a357a3582babe6a02e40ccff8111ee86d317cf56838f0b5568c' \
    --data '{  
        "prepayId": "56416503889661952"
    }'
    

    响应示例:

    {
        "status": "SUCCESS",
        "code": "000000",
        "errorMessage": "",
        "data": {
            "prepayId": "56335302571069440",
            "merchantId": 10002,
            "merchantTradeNo": "118223456798",
            "transactionId": "",
            "goodsName": "NF2T",
            "currency": "USDT",
            "orderAmount": "1.9",
            "surchargeAmount": "0",
            "fiatCurrency": "",
            "fiatAmount": "",
            "fiatRate": "",
            "status": "EXPIRED",
            "createTime": 1675392982792,
            "expireTime": 1675396480000,
            "transactTime": 0,
            "order_name": "MiniApp-Payment#118223456798",
            "pay_currency": "",
            "pay_amount": "0",
            "expectCurrency": "BTC",
            "channelId": "",
            "rate": "0"
        }
    }
    

    请求回应类型:

    属性名 类型 是否必须 说明
    status string 是 SUCCESS 或者 FAIL
    code string 是 出错代码
    data query order return type 否 支付订单信息
    errorMessage string 否 错误信息

    query order return type:

    属性名 类型 是否必须 说明
    prepayId string 是 预付单id
    merchantId int64 是 用于申请商户账号的Gate UID
    merchantTradeNo string 是 商户订单
    transactionId string 是 交易id
    goodsName string 是 商品名称
    currency string 是 订单币种
    orderAmount string 是 订单金额
    surchargeAmount string 否 附加费,指由消费者承担的费用
    toleranceAmount string 否 容差金额:当订单剩余未付金额 ≤ 设置的容差金额时,系统将自动视为支付成功,无需用户额外补款。
    underpaidAmount string 否 支付完成后,未支付金额
    fiatCurrency string 否 法币订单币种
    fiatAmount string 否 法币订单金额
    fiatRate string 否 法币-加密货币汇率
    status string 是 订单状态 PENDING-订单待支付,PROCESS-地址支付处理中状态,PAID-支付成功,EXPIRED-订单超时,CANCELLED- 订单被商户主动取消,ERROR-订单支付失败错误
    createTime int64 是 预付单创建时间,单位毫秒
    expireTime int64 是 预付单过期时间,单位毫秒
    transactTime int64 是 支付完成时间,单位毫秒
    order_name string 是 订单名称
    pay_currency string 是 用户实际支付币种
    pay_amount string 是 用户实际支付金额
    expectCurrency string 否 商家创建订单时,指定营收币种,注:仅在商户指定结算币种的订单详情中返回
    actualCurrency string 否 订单支付完成后,Gate 后台实际结算到商户账户的币种。注:仅在订单结算给商户后才在订单详情中返回
    actualAmount string 否 订单支付完成后,对应 Gate 后台实际结算到商户账户的币种金额。注:仅在订单结算给商户后才在订单详情中返回
    rate string 是 闪兑支付时的汇率
    channelId string 否 客户名称

    # 3.3 订单状态改变回调通知

    在预付单状态发生改变时,比如超出支付时间而导致的过期或者支付成功,GatePay后台会向商户发送通知。通知地址是商户注册时提供的callback url。

    如果由于网络或者其他未知原因导致通知失败,GatePay后台会每隔3秒重试10次。如果重试也失败,商户可以通过查询API查询预付单最新状态。

    • 请求类型: JSON (content-type: application/json)
    • 请求方法:POST
    • 请求体内容:
    属性名 类型 是否必须 描述
    bizType string 是 业务类型,必须为 "PAY"
    bizId string 是 预付单的 ID
    bizStatus string 是 业务状态,包括 “PAY_SUCCESS” 支付成功,“PAY_CLOSE” 订单关闭,“PAY_ERROR” 异常场景回调
    data string 是 订单数据类型对象的 JSON 格式字符串

    order data 类型

    属性名 类型 是否必须 说明
    merchantTradeNo string 是 商户订单 ID
    goodsName string 是 商品名称
    terminalType string 是 交易来源,例如 "MINIAPP"
    currency string 是 订单币种
    orderAmount string 是 交易金额
    payerId int64 否 付款用户 ID

    请求示例:

    {
        "bizType": "PAY",
        "data": "{\"merchantTradeNo\":\"gateio_withdraw6331782520222\",\"productType\":\"NFT\",\"productName\":\"ka\",\"tradeType\":\"APP\",\"goodsName\":\"ka\",\"terminalType\":\"APP\",\"currency\":\"USDT\",\"totalFee\":\"1.2\",\"orderAmount\":\"1.2\",\"createTime\":1664123708000,\"transactionId\":\"24344545\",\"channelId\":\"123456\"}",
        "bizId": "1647557960944",
        "bizStatus": "PAY_SUCCESS"
    }
    

    返回示例:

    {
        "returnCode": "SUCCESS",
        "returnMessage": null
    }
    

    # 4. iOS集成SDK

    # 4.1版本

    • http://gate.com iOS App: 3.11.1+
    • iOS系统:iOS11.0+

    # 4.2 集成

    Cocoapods: pod 'GateOpen'

    # 4.3 手动

    1. 下载GateOpenSDK.framework动态库文件
    2. 将GateOpenSDK.framework添加到你的工程文件内
    3. 选择Build Phases选项,将GateOpenSDK.framework添加到Embed Frameworks

    # 4.4 使用

    Info.plist

    配置Info.plist必要的参数:

    在LSApplicationQueriesSchemes内添加参数: gateio、gatepay

    # 4.5 发起支付

    GOPayRequest
    GOPayRequest用于封装支付请求必需的参数的类。
    @interface GOPayRequest : GORequest
    ///Gate 提供的api_key
    @property (nonatomic, copy) NSString *api_key;
    ///请求生成时的UTC时间戳
    @property (nonatomic, copy) NSString *timestamp;
    ///随机字符串,建议长度在32个字符以内,字符串组成为数字和字母。
    @property (nonatomic, copy) NSString *nonce;
    ///请求签名
    @property (nonatomic, copy) NSString *sign;
    /// 预订单ID
    @property (nonatomic, copy) NSString *prepayid;
    /// 回跳scheme 如:scheme://xxxx
    /// 如果是深度连接:<https://xxxx>
    @property (nonatomic, copy) NSString *redirectUri;
    
    @end
    GateOpenManager
    GateOpenManager用于发起和接收支付请求回调的类。
    @interface GateOpenManager : NSObject
    
    + (instancetype)shared;
    
    /// 调起支付方法
    /// @param payItem 支付所需参数
    /// @param handle 支付完成回调
    - (void)payment:(GOPayRequest *)payItem result:(PayResultHandle)handle;
    
    /// appdelegate 跳转解析
    /// @param url delegate OpenURL
    - (void)handle:(NSURL *)url;
    @end
    GOPayResponse
    GOPayResponse用于接收支付返回结果的类。
    /// 支付完成回跳返回模型
    @interface GOPayResponse : GOResponse
    
    /// 是否支付成功
    @property (nonatomic, assign) BOOL isSuccess;
    
    /// 带的消息
    @property (nonatomic, strong) NSString * _Nullable message;
    
    - (instancetype _Nonnull)initWith:(BOOL)isSuccess
                              message:(NSString * _Nullable)message;
    
    @end
    

    # 4.6 代码调用

    # 4.6.1 发起支付

    第三方应用需要从自己服务器获取支付请求参数,创建GOPayRequest对象后, 再向支付SDK发起支付请求

    /// 创建发起支付请求参数 GOPayRequest

    GOPayRequest *payRequest = [[GOPayRequest alloc] init];
            payRequest.prepayid = @"prepayId";
            payRequest.timestamp = @"timestamp"   
            payRequest.nonce = @"signature";
            payRequest.sign = @"signature";
            payRequest.api_key = @"api_key";
            payRequest.redirectUri = @"schemes://";
            
            /// 发起支付请求
            [[GateOpenManager shared] payment:payRequest result:^(GOPayResponse * _Nullable response) {
                wSelf.payResultInfo.text = response.isSuccess ? @"支付成功" : @"支付失败";
                wSelf.payResultInfo.textColor = response.isSuccess ? UIColor.greenColor : UIColor.redColor;
           }];
    
    

    # 4.6.2 回调配置

    - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
        [[GateOpenManager shared] handle:url];
        return YES;
    }
    

    # 5. Android集成SDK

    # 5.1 获取SDK包,引入依赖

    1. 将Gate Pay提供<repos>文件夹集成到项目根目录,或集成到本地maven仓库。
    2. 在project级别的build.gradle添加本地maven地址。
    3. 在app module的build.gradle文件的dependencies中添加依赖

    在project级别的build.gradle添加本地maven地址

    allprojects {
       repositories {
            maven {
               url uri("${rootProject.projectDir}/repos")
           }
         }
       }
    

    在app module的build.gradle文件的dependencies中添加依赖

    implementation 'com.gateio.sdk:gatepay-sdk:1.0.0'
    

    # 5.2 初始化SDK

    在Application 中 onCreate 中初始化:

    GatePaySDK.init(boolean isDebug, final Context context, String clientId, String redirectUri)
    

    参数说明:

    参数名 类型 是否必须 描述
    isDebug boolean 否 是否调试,开启调试模式(线上版本需要关闭!!!)(Log 日志筛选 "gate_pay_sdk" 可获得对应异常信息提示)
    context Context 是 建议传递 Application Context。
    clientId String 是 GatePay 平台获取的 clientId (即 Gate 提供的 api_key)。
    redirectUri String 是 当前应用需要回调 scheme uri(如项目未实现 scheme 跳转,可尝试传入 "gate_default_scheme",gatepay-sdk 自动获取当前 Activity 实现跳转)。

    注:参数都不能为null,否则会造成初始化失败

    # 5.3 调起支付组件

    在当前页面获取预支付订单和签名信息后,通过GatePaySDK.startGatePay来调起Gate支付组件。

    GatePaySDK.startGatePay(Activity activity, String signature, String timesTamp, String nonce,
       String prepayId, String packageExt, NavigationGatePayListener navigationGatePayListener)
    

    参数说明:

    属性名 类型 是否必须 说明
    activity Activity 是 当前页面activity
    signature String 是 请求签名,Gate Pay通过此签名来确定此请求是否合法
    timesTamp String 是 请求⽣成时的UTC时间戳,milliseconds。请注意,Gate Pay不处理收到请求时间与这个时间戳差距⼤于5秒钟的请求
    nonce String 是 随机字符串,字符符合HTTP Header 头部的规范,建议⻓度在32个字符以内,字符串组成为数字和字⺟
    prepayId String 是 获取的预支付订单ID
    packageExt String 是 扩展字段,取固定值"GatePay"
    navigationGatePayListener NavigationGatePayListener 是 调起组件成功或失败监听,可处理调起逻辑

    错误码code说明:

    onGateOpenFailed(int code, String errorMessage)

    1. code:10021 errorMessage:openPackage failed 请检查App是否有调起其他应用权限。

    2. code:10022 errorMessage:intentData error 请检查PrepayId和redirectUri是否正确。

    3. code:10023 errorMessage:response data is null 请检查验签的参数是否正确。

    4. code:10023 errorMessage:动态获取throwable.getMessage() 请检查onGateOpenFailed异常信息提示,做对应排查。

    5. code:10024 errorMessage:params error 请检查初始化或者调起支付传参是否正确。

    注:非以上code,可直接查看errorMessage错误信息,或对应查看Gate Pay服务端对接错误码。

    # 5.4 支付结果回调

    1. 在步骤三中的当前页面Activity的onResume()中,通过获取boolean GatePaySDK.gateResumePayResult() 来判断是否GatePay,如是true即可处理GatePay回调逻辑。

    注:GatePaySDK.gateResumePayResult()方法避免重复被调用,内部已实现清除逻辑,只能获取一次,如需Log打印或作其它判断,可获取后暂存该值。

    1. 获取Gate App的支付状态: Code: GatePaySDK.gateResumePayState().getCode() State: GatePaySDK.gateResumePayState().getState()

    说明:

    回调中Code值

    Code:10005---failed 支付失败(常量值:GatePayConstant.MESSENGER_STATE_CODE_FAILED)

    Code:10006---cancel 取消支付(常量值:GatePayConstant.MESSENGER_STATE_CODE_FAILED_CANCEL_PAY)

    Code:10010---success 支付成功(常量值:GatePayConstant.MESSENGER_STATE_CODE_SUCCESS)

    注: 如为10010--支付成功,则去后台查询支付结果再展示用户实际支付结果。注意一定不能以客户端返回作为用户支付的结果,应以服务器端的接收的支付通知或查询API返回的结果为准。

    # 5.5 Android常见问题

    1. 填写了redirectUri仍无法回到当前App? 查看AndroidManifest,当前Activity是否已设置android:exported="true" (建议启动模式为android:launchMode="singleTask")

    2. 调起startGatePay方法没有响应? 在初始化的时候将isDebug设置为true,通过Logcat筛选"gate_pay_sdk"关键字可查看对应异常提示。 包括但不限于以下情况:

    • SDK未初始化
    • 没有填写clientId
    • 没有填写redirectUri
    • 暂未安装Gate App (SDK已实现跳转下载)
    • 暂未升级最新版Gate App[3.11.1+] (SDK已实现弹框提醒更新下载)
    1. 支付结果回调状态无法获取? 查看当前App是否限制与其他App交互,如确认参数以及权限没问题的情况下,仍无法获取,可尝试redirectUri传入"gate_default_scheme"(可参考步骤二说明), 在当前页面Activity从onNewIntent中获取Intent值(仅在"gate_default_scheme"生效)。

    获取GatePayConstant.INTENT_GATE_B_CLASS_PARAM,参数为Json格式,如:{"code":10006,"state":"cancel"}

    方式1):直接onNewIntent中获取
       @Override
       protected void onNewIntent(Intent intent) {
           super.onNewIntent(intent);
           if (intent != null) {
               String intentData = intent.getStringExtra(GatePayConstant.INTENT_GATE_B_CLASS_PARAM);
           }
         }
    
       方式2):onResume()中获取
       @Override
       protected void onNewIntent(Intent intent) {
           super.onNewIntent(intent);
            setIntent(intent);
       }
    
       @Override
       protected void onResume() {
           super.onResume();
           Intent intent = getIntent();
           if (intent != null) {
               String intentData = intent.getStringExtra(GatePayConstant.INTENT_GATE_B_CLASS_PARAM);
           }
       }
    
    1. 为什么调起支付组件需要通过服务端获取签名信息?

    提供平台信息接入后,Gate Pay收到这些信息后会给第三⽅提供api_key和api_secret,其中api_key作为身份标识,api_secret⽤于请求签名,其中api_secret是⼀定要妥善保存防⽌泄漏的,只能通过服务端生成签名信息。

    注:可参考GatePayDemo查看完整接入流程。