.NetCore对接各大财务软件凭证API——用友系列(1)

.NetCore对接各大财务软件凭证API——用友系列(1)

一.前言

今天,我们转战用友系列的第一个产品---T+/Tplus。前两篇文章讲解分享的都是金蝶的产品,因为本身公司牵涉的业务有限,后续有金蝶其他产品的API对接业务时,会继续来分享经验。

T+的API接口,哎,想起来都是心酸泪。关于该接口的对接开发经验,我之前也简单记录了一些,传送门 记录用友T+接口对接的心酸历程。今天我们就来详细解析下这令人头大的财务API接口。

二.API接口详解

2.1接口定义和入参

根据开发者社区API文档的描述我们可以看到,T+版本为12.3以上的API对接,都必须使用V2版本,那v2与v1版本的区别有哪些呢?主要有两点:1. 请求认证方式,增加云企业ID认证方式 ;2.v2版本支持异步请求。OK,因为我们对接的客户财务环境为12.3,那么我们就来处理v2版本的OpenAPI,该版本的API引入了鉴权机制,简单来说就是在请求头增加了授权 Authorization 参数.

2.1.1 Authorization参数以及签名处理

那么 Authorization 参数如何才能生成呢?可以看官网首页的描述是 需要appkey、appsecert、私钥的文件全路径。那这三个参数又如何才能获取呢?必须申请ISV认证,即注册ISV,提交开发申请通过审核后,总部会将这三个参数一并发到注册时预留的邮箱里。

2.1.2 OrgId方式的签名算法处理

这里,我们在上一步已经拿到签名所需的三个必要参数了,官网给了两种请求Head的处理方式,一种使用OrgId,一种使用用户名、密码、账套号,这两种方式我们都会讲到。先看第一种方式OrgId访问。

OrgId的获取方式,官网描述的也有,


1590027798731.png

即必须开通云企业才能看到


1590027984042.png

这样,我们第一种使用OrgId认证方式的所需参数就已全部准备完毕了,接着往下看,首先需要对appKey、orgid、appsecret、私钥全路径做一个签名1的算法加密,这个算法官网给我们提供的也有,这里仅提供C#版本的签名算法1接着做一个Base64位的加密即可得到Authorization参数。

          if (!APIConfig.AuthorizeParameters.ContainsKey("appkey")
                || !APIConfig.AuthorizeParameters.ContainsKey("orgid")
                || !APIConfig.AuthorizeParameters.ContainsKey("appsecret")
                || !APIConfig.AuthorizeParameters.ContainsKey("secerturl"))
            {
                throw new Exception("鉴权参数不完整");
            }
            var request = new AccessTokenRequest();
            Dictionary<string, object> parm = new Dictionary<string, object>();
            string appkey = APIConfig.AuthorizeParameters["appkey"];
            string orgid = APIConfig.AuthorizeParameters["orgid"];
            string appsecret = APIConfig.AuthorizeParameters["appsecret"];
            string secetrurl = APIConfig.AuthorizeParameters["secerturl"];

            parm.Add("appkey", appkey);
            parm.Add("orgid", orgid);
            parm.Add("appsecret", appsecret);

            JsonSerializer jsonSerializer = new JsonSerializer();
            string datas = jsonSerializer.Serialize(parm);
            try
            {
                var signClass = new TokenManage();
                string signvalue = signClass.CreateSignedToken(datas, secetrurl);
                string authStr = @"{""appKey"":""" + appkey + @""",""authInfo"":""" + signvalue + @""",""orgId"":" + orgid + @"}";
                string encode = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(authStr));
                Dictionary<string, string> parms = new Dictionary<string, string>();
                parms.Add("Authorization", encode);
                request.SetHeaderParameters(parms);
                var response = Excute(request);
                return response;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }

当你处理完第一步,调试接口正常返回

{"result":true,"access_token":"03e74889-1457-48cd-970a-ba3742ffcdea","sid":""}  

先不要高兴的太早了,我们还要根据这一步获取到的Token做业务调用。如图所示


1590028694104.png

官网也给的有测试的Demo供我们调用调试,这比较方便我们对问题作出反馈。 T+OpenAPI测试工具-包含v2版本-C# 然后大坑就来了...demo中的jose-jwt.dll是 .NET Framework的版本,但是我们的开发环境是.netCore2.2,很遗憾的是该dll在.netCore环境下不支持.详细的解决过程很心酸,就不再多叙述,我在之前的文章里已详细描述,这里我们只说最后的结果就是解决了不支持的问题。

2.1.3 用户名密码方式的签名算法处理

这种方式的登录相比第一种使用OrgId认证的方式有什么好处呢?我在实际的开发中得到了验证,OrgId方式的认证在对那些没有开通云企业的客户来说这种方式是行不通的,所以相比较来说,还是用户名密码的认证比较通用,只要客户能提供给我们一个正常可登录财务系统的用户名和密码,我们就可以使用该方式来进行接口的开发对接。下面具体来说一下,如何正确得到该方式的Authoirzation参数。


1590029362390.png

这是第一步得到Token的方法,可以看到签名方式和加密方式不变,变得是签名方式的参数不同,orgId为空,在PostBody里要传入用户名、密码和账套号。


1590029470127.png

账套号为中括号里的,比如上图的021809... 不要传名称!不要传名称!不要传名称!接着获取到Token后,我们就可以调用业务接口了,这里还是使用用户名密码的方式来调用。


1590029588592.png

增加了上一步获取到的Token,详细代码如下

            var signClass = new TokenManage();
            var request = new GetTokenByPwdRequest();
            string appkey = APIConfig.AuthorizeParameters["appkey"];
            string appsecret = APIConfig.AuthorizeParameters["appsecret"];
            string secetrurl = APIConfig.AuthorizeParameters["secerturl"];
            string userName = APIConfig.AuthorizeParameters["userName"];
            string password = APIConfig.AuthorizeParameters["password"];
            string EncodePassword = signClass.GetMd5(password);
            string accNum = APIConfig.AuthorizeParameters["accNum"];

            Dictionary<string, object> parm = new Dictionary<string, object>();
            parm.Add("appkey", appkey);
            parm.Add("orgid", "");
            parm.Add("appsecret", appsecret);

            JsonSerializer jsonSerializer = new JsonSerializer();
            string datas = jsonSerializer.Serialize(parm);
            try
            {
                string signvalue = signClass.CreateSignedToken(datas, secetrurl);
                string authStr = @"{""appKey"":""" + appkey + @""",""authInfo"":""" + signvalue + @""",""orgId"":""""}";
                string encode = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(authStr));
                Dictionary<string, string> parms = new Dictionary<string, string>();
                parms.Add("Authorization", encode);
                request.SetHeaderParameters(parms);

                Dictionary<string, object> postParms = new Dictionary<string, object>();
                var args = new PwdEntity() { userName = userName, password = EncodePassword, accNum = accNum };
                var argsJson = jsonSerializer.Serialize(args);
                postParms.Add("_args", argsJson);
                request.SetPostParameters(postParms);

                var response = Excute(request);
                return response;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }

两种方式都处理完毕了,我们就可以愉快的使用API来做业务的处理啦。

2.2 业务接口调用

2.2.1 会计科目查询
1590029854357.png

URL的话,v2版本的调用,我们只需将v1改为v2即可,若想一次性获取到所有的会计科目,查询条件传空值即可,即如下:

{ 
  dto:{ 
  } 
}
        public QueryAccountResponse QueryAccountRequest()
        {
            var request = new QueryAccountRequest();
            var parmsDic = new Dictionary<string, object>();
            var parms = new QuertyEntity() { dto = new BasicDto { } };
            parmsDic.Add("_args", JsonConvert.SerializeObject(parms));
            request.SetPostParameters(parmsDic);
            return _Client.Excute(request);
        }

该方法可返回所有会计科目用作基础档案或者前端页面展示使用。

2.2.2 凭证相关操作

凭证创建请求实体:

{
    "dto": {
        "ExternalCode": "1", //外部编码-唯一码,长度小于50
        "DocType": {
            "Code": "记" // 凭证类别编码
        },
        "AttachedVoucherNum": "2",// 附单据数
        "Memo": "无", // 备注 长度小于50
        "VoucherDate": "2014-09-30",// 凭证日期
        "Entrys": [{
                "Summary": "提现", // 凭证摘要 
                "Account": {
                    "Code": "1001" // 科目档案
                },
                "Currency": {
                    "Code": "RMB" // 币别
                },
                "AmountCr": "100" //贷方金额
            },
            {
                "Summary": "提现",
                "Account": {
                    "Code": "1002"
                },
                "Currency": {
                    "Code": "RMB"
                },
                "AmountDr": "100", // 借方金额
                "AuxInfos": [{ // 辅助核算信息
                    "AuxAccDepartment": { // 部门核算
                        "Code": "001"
                    },
                    "AuxAccInventory": { // 存货核算
                        "Code": "001"
                    },
                    "AuxAccProject": { // 项目核算
                        "Code": "001"
                    },
                    "AuxAccPerson": { // 人员核算
                        "Code": "001"
                    },
                    "AuxAccCustomer": { //往来单位核算
                        "Code": "001"
                    }
                }]
            }
        ]
    }
}

URL: TPlus/api/v1/doc/Create || TPlus/api/v2/doc/Create

构造好对应的凭证实体即可正常传输凭证了。

凭证查询:官网给的示例是传入一个dtos的数组,但是在实际的开发过程中,真正传入一个起始期间,一个截止时间时,并没有正确的返回对应的数据,所以我到现在也没搞明白这个起始和截止时间该咋用,如果有知道的小伙伴,还请帮忙解答。


1590030669375.png

三.结束语

其实,真正对于T+的业务调用并没有花费很多时间,因为前面的坑已经踩完了,后面基本上也没啥了,就是我很纳闷的是,每个接口的返回值格式是不固定的,这个就很令人费解啊。咱也搞不懂到底为啥这样定义。倒是前面处理签名算法和dll不兼容的问题前前后后大约搞了一个星期才完整解决,这个很是让人头大。

曾经在T+的开发群了看到这样一句话,每个开发都是一个实施。也确实是这样一种情况,你对接的每个API接口,不可能总会有人给你解答问题,这时候你只能靠自己去摸索,去猜,当然了大部分的开发文档还是很规范的。其实做对接的做多了,你会遇到不同形式的API接口,每家厂商都有自己独特的开发标准,我们能做的就是遵循这套标准,不然如何对接,如何正确处理我们的工作。

最后的最后,希望我们做API对接或者说是做开发的,要保持一颗良好的心态去面对问题,要相信问题总是会被解决的,只是时间早晚。而且要找对方法,比如社区,或者对应的交流群等等,会有很多大佬帮你解答疑惑,祝你在开发的道路上勇往直前的。

我是程序猿贝塔,一个分享自己对接过财务系统API经历和生活感悟的程序员。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容