# 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 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",
            "status": "EXPIRED",
            "createTime": 1675392982792,
            "expireTime": 1675396480000,
            "transactTime": 0,
            "order_name": "MiniApp-Payment#118223456798",
            "pay_currency": "",
            "pay_amount": "0",
            "expectCurrency": "BTC",
            "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 订单金额
    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后台实际结算到商户账户的币种,注:仅在订单Gate结算给商户后才在订单详情中返回
    actualAmount string 订单支付完成后,对应Gate后台实际结算到商户账户的币种的金额,注:仅在订单Gate结算给商户后才在订单详情中返回
    rate 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_CLOSED"
    data string 订单数据类型对象的 JSON 格式字符串

    order data 类型

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

    请求示例:

    {
        "bizType": "PAY",
        "data": "{\"merchantTradeNo\":\"56236\", \"terminalType\":\"MINIAPP\", \"currency\":\"BTC\", \"orderAmount\":\"1.91\", \"payerId\":513301}",
        "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查看完整接入流程。