iOS小技能:封装银联接口协议(收银台的订单退款、查询机制)【下篇】

2,927 阅读9分钟

「这是我参与2022首次更文挑战的第27天,活动详情查看:2022首次更文挑战」。

引言

  1. 上篇:业务功能、退款接口的协议规则、请求 https://kunnan.blog.csdn.net/article/details/115084885
  2. 下篇:返回结果处理、测试技巧、常见问题处理方案

I 返回结果处理

1、申请退款成功,立马创建处理中的本地数据 2、退款查询,根据查询状态修改订单状态

  • 数据按XML的格式实时返回
字段名变量名必填类型说明
版本号versionString(8)版本号,version默认值是2.0。
字符集charsetString(8)可选值 UTF-8 ,默认为 UTF-8。
签名方式sign_typeString(8)签名类型,取值:MD5默认:MD5
授权交易机构sign_agentnoString(12)授权交易的服务商机构代码,商户授权给服务商交易的情况下返回,签名使用服务商的密钥
连锁商户号groupnoString(15)连锁商户为其下门店发交易的情况返回,签名使用连锁商户的密钥
返回状态码statusString(16)0表示成功,非0表示失败此字段是通信标识,非交易标识,交易是否成功需要查看 result_code 来判断
返回信息messageString(128)返回信息,如非空,为错误原因签名失败参数格式校验错误
网关返回码codeString(32)网关返回码
以下字段在 status 为 0的时候有返回
业务结果result_codeString(16)0表示成功,非0表示失败注:此处返回0表示退款申请接收成功,实际的退款结果根据退款查询接口查询
商户号mch_idString(15)商户号,由平台分配
设备号device_infoString(32)终端设备号
随机字符串nonce_strString(32)随机字符串,不长于 32 位
错误代码err_codeString(32)具体错误码请看文档最后错误码列表
签名signString(32)MD5签名结果,详见“安全规范”
以下字段在 status 和 result_code 都为 0的时候有返回
平台订单号transaction_idString(32)平台交易号
商户订单号out_trade_noString(32)商户系统内部的订单号
商户退款单号out_refund_noString(32)商户退款单号
平台退款单号refund_idString(32)平台退款单号
退款渠道refund_channelString(16)ORIGINAL—原路退款,默认
退款金额refund_feeInt退款总金额,单位为分,可以做部分退款
现金券退款金额coupon_refund_feeInt现金券退款金额 <= 退款金额, 退款金额-现金券退款金额为现金,单位为分
交易类型trade_typeString(32)pay.weixin.micropay——微信被扫支付pay.alipay.micropay——支付宝被扫支付pay.unionpay.micropay――银联被扫支付

1.1 状态判断逻辑

先判断协议字段返回,再判断业务返回,最后判断交易状态

1、返回状态码(status)参数:0表示调用成功;非0表示调用失败。 此字段是通信标识,非交易标识,交易是否成功需要查看 result_code 来判断 2、业务结果(result_code) : 0表示成功,非0表示失败注:此处返回0表示退款申请接收成功,实际的退款结果根据退款查询接口查询

协议级错误返回:
<xml>
<status>500</status>
<message><![CDATA[SYSERR]]></message>
</xml>
正确返回数据:
<xml>
<status>0</status>
<message><![CDATA[OK]]></message>
<result_code>0</result_code>

</xml>
业务级错误返回:
<xml>
<status>0</status>
<message><![CDATA[OK]]></message>
<result_code>1</result_code>
<err_code><![CDATA[AUTHCODE_EXPIRE]]></err_code>
<err_code_des><![CDATA[二维码已过期,请刷新再试]]></err_code_des>
</xml>

1.2 字段解析(XML解析)

blog.csdn.net/z929118967/…

  • 从Nsdata 转成CXMLDocument对象
 NSString * strResponse = [[NSString alloc] initWithData:response encoding:NSUTF8StringEncoding];
                CXMLDocument *xml= [[CXMLDocument alloc] initWithXMLString:strResponse options:0 error:nil];
                [weakSelf ProcessSucXML:xml];

  • 解析字段
    // 1、字符串类型的解析:银行卡名字
    [Session Instance].strBankNameReset = [[xml nodesForXPath:@"//ROOT/BODY/BNKNM" error:nil].lastObject stringValue];
 // 2、integerValue类型的解析:订单金额
        [Sessionsss sss].ssss = [[xml nodesForXPath:@"//ROOT/BODY/OsssMT" error:nil].lastObject stringValue].integerValue;
//3、数组的解析
    NSArray *arr = [xml nodesForXPath:@"//ROOT/BODY/REC" error:nil];



//采用遍历CXMLElement方法
    for (CXMLElement *element in arr)
    {
        NSString *strNumber = @"";
        NSArray *aNumber = [element elementsForName:@"NO"];
        if (aNumber != 0) {
            strNumber = [aNumber.firstObject stringValue];
        }
    }

//————————————————
//版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
//原文链接:https://blog.csdn.net/z929118967/article/details/74747249

II、解决商户平台交易流水的订单记录无法实时与银联同步的问题

目前平台和银联的订单对账间隔是1天,因此需要在app本地创建退款失败、退款中、退款成功的订单数据。

2.1 订单列表数据追加本地数据:数据去重和按照时间戳排序

iOS数据搜索技巧:1、 应用NSPredicate进行数据筛选:从数组搜索特定条件的元素2、利用正则表达式进行匹配查找数据3、使用系统特定API进行数据查找来避免循环遍历数组 ———————————————— 版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:blog.csdn.net/z929118967/…

/**
 数据去重:判断构造的数据,是否与接口返回的数据列表重复
 */
- (BOOL )iscontainsinarr:(NSArray*)arr;

- (BOOL )iscontainsinarr:(NSArray*)arr{
    
    
    

    NSPredicate* predicate = [NSPredicate predicateWithFormat:@"paymentNo == %@", self.paymentNo];
    
    
    

    NSArray *arFiltered = [  arr filteredArrayUsingPredicate:predicate];//以一定的条件(特定日期)过滤数组,即进行大数据搜索。
    
    
    if(arFiltered.count>0){
        
        return YES;
        

    }
    
    
    return NO;

}

  • 对象数组按照时间戳排序

iOS 排序指南:参数名ASCII码从小到大排序、数据按照日期进行分组、对象数组按照时间戳排序 kunnan.blog.csdn.net/article/det…


/**
        weakSelf.viewModel.listModels=   [QCT_Common sortedArrayUsingObjectKey:@"createTime" arr:weakSelf.viewModel.listModels];

 IOS中将对象数组按照时间戳排序
 */
- (NSMutableArray*)sortedArrayUsingObjectKey:(NSString*)key arr:(NSArray*)listModels{
    
    
    //1)取出日期分组
    
    NSString* valueForKeyPath = FMSTR(@"@distinctUnionOfObjects.%@",key);
    
    
    
    NSArray *arDistinct = [listModels valueForKeyPath:valueForKeyPath];
    
    
    //listModels是一些含有日期属性的对象集合
    //2)构建排序规则NSComparator
    NSComparator cmptr = ^(id obj1, id obj2){
        NSString *strData1 = obj1;
        NSString *strData2 = obj2;
        NSComparisonResult ret = [strData1 compare:strData2];
        return ret;
    };
//     3)按数字从小到大进行排序(将最新的数据显示在前面)
 
    NSArray  *arSorted = [arDistinct sortedArrayUsingComparator:cmptr];
    arSorted = arSorted.reverseObjectEnumerator.allObjects;//顺序取反
    NSMutableArray *newSorted_arr = [NSMutableArray array];
    
    //     4)按照日期进行分组
        for (NSString *strDateCreated in arSorted)
        {
     
            NSPredicate*   predicate = [NSPredicate predicateWithFormat:@"createTime == %@", strDateCreated];
            
            
     
            NSArray *arFiltered = [listModels filteredArrayUsingPredicate:predicate];//以一定的条件(特定日期)过滤maTemp数组,即进行大数据搜索。
            
            
            [newSorted_arr addObjectsFromArray:arFiltered];
            
            
            
     
        }
    
    return newSorted_arr;

//        ————————————————
//        版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
//        原文链接:https://blog.csdn.net/z929118967/article/details/115242819

//    ————————————————
//    版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
//    原文链接:https://blog.csdn.net/z929118967/article/details/113499172
    

}


  • 数据过滤: 获取最近7天的数据
        // 数据过滤:  获取最近7天的数据
        NSMutableString *str = [[NSMutableString alloc]initWithString: [QCT_Common get4TodayTimeWithDateFormat:@"yyyy-MM-dd HH:mm:ss"]];
        
        
        NSMutableArray *tmp = [NSMutableArray array];

        for (QCTReceiptDetailModel *obj in tmparr) {
            
            

            NSInteger day  = [QCT_Common contrastTimeBackDayWithStartDate:obj.completeTime endDate:str DateFormat:@"yyyy-MM-dd HH:mm:ss"];//  endDate 大
            
            
            
            
            NSLog(@"day: %ld",(long)day);

            if (day > 6) {
                                
            }else{
                [tmp addObject:obj];

                
            }

            
            

            
        }
        
/**
     计算两个时间的间隔(天)
     
     @param start 开始时间
     @param end 结束时间
     @return 间隔时间
     */
    + (NSInteger)contrastTimeBackDayWithStartDate:(NSString *)start endDate:(NSString *)end DateFormat:(NSString*)Format
    {
        
        NSTimeInterval time = [self contrastTimeWithStartDate:start endDate:end dateFormatter:Format];
        
        
        
        NSInteger minute,second,hour,day;
        second=(NSInteger)time%1000;
        minute = (NSInteger)(time/60)%60;
        hour = (NSInteger)(time/3600)%24;
        day = (time/3600/24);
        return day;
    }

    /**
     计算两个时间的间隔(毫秒)
     
     @param start 开始时间
     @param end 结束时间
     @return 间隔时间
     */
    + (NSTimeInterval)contrastTimeWithStartDate:(NSString *)start endDate:(NSString *)end dateFormatter:(NSString*)Formatter
    {
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    //    [dateFormatter setDateFormat:@"yyyy-MM-dd"];
        
        [dateFormatter setDateFormat:Formatter];
        
        
        
        NSDate *startDate = [dateFormatter dateFromString:start];
        NSDate *endDate = [dateFormatter dateFromString:end];
        NSTimeInterval time = [endDate timeIntervalSinceDate:startDate];
        return time;
    }


2.2 退款状态查询处理

up.95516.com/open/openap…

  • 退款状态

SUCCESS—退款成功 FAIL—退款失败 PROCESSING—退款处理中 NOTSURE—未确定, 需要商户原退款单号重新发起 CHANGE—转入代发,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,资金回流到商户的现金帐号,需要商户人工干预,通过线下或者平台转账的方式进行退款。

  • 处理查询结果
    
    [NetworkHelper4XML postWithURL:url params:params successBlock:^(CXMLDocument * _Nonnull xml) {
        //$n 表示记录的序号,取值为 0~($ refund_count -1),例如 refund_count 指示返回的退款记录有 2 条。第一条序号为“0”,第二条序号为“1”。

        
//        退款笔数refund_count Int 退款记录数
        NSString *refund_count = [[xml nodesForXPath:@"//xml/refund_count" error:nil].lastObject stringValue];
        
        
        
        refund_count = [NSNumber numberWithInteger:(refund_count.integerValue-1)].description;
        
        
        NSString* refund_status =  [[xml nodesForXPath:FMSTR(@"%@%@",@"//xml/refund_status_",refund_count) error:nil].lastObject stringValue];
        
        if([refund_status isEqualToString:@"SUCCESS"]){
                                    
            
//            refund_time_$n 退款时间 yyyyMMddHHmmss
            
            NSString* refund_time_ = [[xml nodesForXPath:FMSTR(@"%@%@",@"//xml/refund_time_",refund_count) error:nil].lastObject stringValue];
            
            //商户退款单号
            

            NSString* out_refund_no_ = [[xml nodesForXPath:FMSTR(@"%@%@",@"//xml/out_refund_no_",refund_count) error:nil].lastObject stringValue];

            
//            退款金额,单位为分,可以做部分退款
            NSString*   refund_fee_ =[[xml nodesForXPath:FMSTR(@"%@%@",@"//xml/refund_fee_",refund_count) error:nil].lastObject stringValue];
#pragma mark - ********退款成功界面: 退款编号、退款时间、退款人、退款金额、打印小票
            
            payinfomodel.orderNo = out_refund_no_;
            
            payinfomodel.paymentNo = out_refund_no_;
            
            
            payinfomodel.createTime  = [QCT_Common strdatedateFormat:@"yyyy-MM-dd HH:mm:ss" fromDateFormat:@"yyyyMMddHHmmss" objstr:refund_time_];// 格式化时间
            
            
            payinfomodel.completeTime = payinfomodel.createTime;
            
            model4list.completeTime =payinfomodel.createTime;
            model4list.createTime =payinfomodel.createTime;
            

            
            payinfomodel.paymentAmount = [NSNumber numberWithDouble: refund_fee_.doubleValue/100.00].description;
            
            payinfomodel.stateName = @"退款成功";
                        
            payinfomodel.state = [NSNumber numberWithInt:k_PAY_STATE_Enum4PAY_STATE_SUCCESS].description;
            model4list.state = payinfomodel.state;
            model4list.stateName =payinfomodel.stateName;
            
            [model4list  bg_updatetoDB];
            
        [[NSNotificationCenter defaultCenter] postNotificationName:QCTupdateInffo4ReceiptListNotification object: [NSNumber numberWithInteger:1] userInfo:@{QCTchangeViewNotificationuserInfo:NSClassFromString(@"QCTNewslist4ReceiptViewControllerNewViewController")}];// 只要门店需要筛选

            

            QCTRefundSUCCModel *model4SUCCModel = payinfomodel.RefundSUCCModel;


            QCTrefund_successfulViewController *vc = [QCTrefund_successfulViewController new];
            
            vc.viewModel.model = model4SUCCModel;
            
            
            [weakSelf.navigationController pushViewController:vc  animated:YES];
            
            NSLog(@"跳到成功页");
            
            
            
        }else if([refund_status isEqualToString:@"CHANGE"]){
            
            
            payinfomodel.stateName = @"其他错误";
            
                        
            payinfomodel.state = [NSNumber numberWithInt:k_PAY_STATE_Enum4PAY_STATE_PAYMENT_FAILURE].description;
            
            payinfomodel.remark = @"转入代发,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,资金回流到商户的现金帐号,需要商户人工干预,通过线下或者平台转账的方式进行退款";
            
            
            model4list.state = payinfomodel.state;
            
            model4list.stateName =payinfomodel.stateName;
            

            [LBAlertController showAlertTitle:nil content:@"CHANGE:转入代发" cancelString:@"确定" cancleBlock:^{
                [self pop];
                
            } sureString:nil sureBlock:nil
                            currentController:[QCT_Common getCurrentVC]];

        
    }else if([refund_status isEqualToString:@"FAIL"]){//退款失败
        
        
        payinfomodel.stateName = @"退款失败";
        
                    
        payinfomodel.state = [NSNumber numberWithInt:k_PAY_STATE_Enum4PAY_STATE_PAYMENT_FAILURE].description;
        
        model4list.state = payinfomodel.state;
        model4list.stateName =payinfomodel.stateName;

        [LBAlertController showAlertTitle:nil content:@"退款失败" cancelString:@"确定" cancleBlock:^{
            [self pop];
            
        } sureString:nil sureBlock:nil
                        currentController:[QCT_Common getCurrentVC]];

        
    }else if([refund_status isEqualToString:@"PROCESSING"]){//退款处理中
        [self.view showHUDMessage:@"PROCESSING:退款处理中" ];
        
        
                    

                    NSString* out_refund_no_ = [[xml nodesForXPath:FMSTR(@"%@%@",@"//xml/out_refund_no_",refund_count) error:nil].lastObject stringValue];

                    
        //            退款金额,单位为分,可以做部分退款
                    NSString*   refund_fee_ =[[xml nodesForXPath:FMSTR(@"%@%@",@"//xml/refund_fee_",refund_count) error:nil].lastObject stringValue];
                    
        payinfomodel.paymentAmount = [NSNumber numberWithDouble: refund_fee_.doubleValue/100.00].description;

        //        NSLog(@"跳到退款中界面");
                
        payinfomodel.orderNo = out_refund_no_;
        
        payinfomodel.paymentNo = out_refund_no_;
        
        
        model4list.paymentAmount = payinfomodel.paymentAmount ;
        
        payinfomodel.createTime = weakSelf.viewModel.payinfomodel.createTime4out_refund_no;
        
//        [QCT_Common strdate4YYMMDDWithyyyyMMddHHmmssstr:refund_time_];// 格式化
        payinfomodel.completeTime = payinfomodel.createTime;
        
        model4list.completeTime =payinfomodel.createTime;
        model4list.createTime =payinfomodel.createTime;

                
                QCTRefundSUCCModel *model = payinfomodel.RefundSUCCModel;
                
            model.isFromFretless = YES;
                
                
                
                QCTrefund_ingViewController *vc =                     [QCTrefund_ingViewController new];

                
                vc.viewModel.model = model;
                
                
                [weakSelf.navigationController pushViewController:vc  animated:YES];
                
                


    
    }else if([refund_status isEqualToString:@"NOTSURE"]){//退款处理中
        // 新增备注信息: 转入代发,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,资金回流到商户的现金帐号,需要商户人工干预,通过线下或者平台转账的方式进行退款
        payinfomodel.stateName = @"其他错误";
        
                    
        payinfomodel.state = [NSNumber numberWithInt:k_PAY_STATE_Enum4PAY_STATE_PAYMENT_FAILURE].description;
        
        
        
        
        model4list.state = payinfomodel.state;
        model4list.stateName =payinfomodel.stateName;
        
        payinfomodel.remark =@"未确定,请重新发起退款";        // 新增备注信息:未确定,请重新发起退款
    

        [LBAlertController showAlertTitle:nil content:@"NOTSURE:未确定,请重新发起退款" cancelString:@"确定" cancleBlock:^{
            [self pop];
            
        } sureString:nil sureBlock:nil
                        currentController:[QCT_Common getCurrentVC]];

        

    }
        
#pragma mark - ********  更新账单流水列表数据
        [model4list  bg_updatetoDB];
        
    [[NSNotificationCenter defaultCenter] postNotificationName:QCTupdateInffo4ReceiptListNotification object: [NSNumber numberWithInteger:1] userInfo:@{QCTchangeViewNotificationuserInfo:NSClassFromString(@"QCTNewslist4ReceiptViewControllerNewViewController")}];// 只要门店需要筛选

        
            
    } RspCDFailed:nil failure:nil isShowLoadingDataGif:YES];
    



IIII 测试技巧

3.1 产生订单数据

  • 由于商户平台侧的订单数据和银联的是一天同步一次,因此可以提前一天使用支付宝多交易产生订单,以便第二天测试退款。

当天的退款金额<=收款金额

原理文章: blog.csdn.net/z929118967/…

3.2 修改返回报文

  • 去掉空格之后,再使用。否则会导致节点的值会包含空格 在这里插入图片描述
  • 正确的格式
<xml><appid><![CDATA[]]></appid> <charset><![CDATA[UTF-8]]></charset> <coupon_refund_fee_0><![CDATA[0]]></coupon_refund_fee_0> <mch_id><![CDATA[]]></mch_id> <mdiscount_0><![CDATA[0]]></mdiscount_0> <nonce_str><![CDATA[]]></nonce_str> <out_refund_no_0><![CDATA[]]></out_refund_no_0> <out_trade_no><![CDATA[]]></out_trade_no> <out_transaction_id><![CDATA[]]></out_transaction_id> <refund_channel_0><![CDATA[ORIGINAL]]></refund_channel_0> <refund_count><![CDATA[1]]></refund_count> <refund_fee_0><![CDATA[1]]></refund_fee_0> <refund_id_0><![CDATA[]]></refund_id_0> <refund_status_0><![CDATA[SUCCESS]]></refund_status_0> <refund_time_0><![CDATA[]]></refund_time_0> <result_code><![CDATA[0]]></result_code> <sign><![CDATA[]]></sign> <sign_agentno><![CDATA[]]></sign_agentno> <sign_type><![CDATA[MD5]]></sign_type> <status><![CDATA[0]]></status> <total_fee><![CDATA[1]]></total_fee> <trade_type><![CDATA[pay.alipay.micropay]]></trade_type> <transaction_id><![CDATA[]]></transaction_id> <version><![CDATA[2.0]]></version> 
</xml>

see also

openapi

java demo

github.com/zhangkn/web… 中国银联云闪付合作伙伴开放平台 partner.95516.com/portal/docu…

iOS网络请求指南: 请求参数的拼接(签名)、返回参数解析(JSON/XML解析)

kunnan.blog.csdn.net/article/det…

🍅 联系作者: iOS逆向(公号:iosrev)


🍅 作者简介:CSDN 博客专家认证🏆丨全站 Top 50、华为云云享专家认证🏆、iOS逆向公号号主


🍅 简历模板、技术互助。关注我,都给你。