回眸曾经的项目,与第三方支付相关,所带来的沟通问题
导读
笔者在校期间,通过自学java。学校里也开过这门课,但是,讲的都是一些基础,比如java的表达式、基本类型、自定义类型等等。也都是很基础的东西,就连lambda表达式都没有。然而,让我们交的作业,是用java-web开发出的网站。我当时做的是与图书共享相关的网站。当时满腔热血地想着去创业,但是,因为自身还没离开学校,社会经验不是特别足,于是,这件事就搁浅了。
去年六月份毕业,参加了班级的散伙饭后,大家也都各奔东西。但大部分从事软件开发的行业,有些人进入外包公司,有些人进入了游戏公司。不管进入到什么行业,在最初的一段时间中,遇到一个教你的人很重要。这样,你可以学校到很多东西。当然,你自己也得努力学习。
我依稀记得第一次做项目,那个项目做得真是一塌糊涂,权当我个人练手用了。对于个人来说,成长是非常快的,但是,对于企业来说,这是一种损失。真的是损失。因为,企业让你来做事,不是让你来试验的。自从第一个项目失败后,也不能说失败吧,至少做得不够完美。项目没有达到松散耦合的程度。我在开发的过程中,遇到了各种各样的问题,在他人的帮助下,慢慢地适应了开发强度。
当时,我给自己定义的是java后端开发工程师,因而,接口都是通过postman来测试。我们的持久层使用的hibernate框架,于是,仿照hibernate写个框架,参考我的博客:模仿hibernate框架,详解hibernate部分方法设计;同时使用spring容器集合该框架,于是,仿照了spring写个框架,可以参考我的博客:模拟spring框架,深入讲解spring的对象的创建;数据过滤的框架是Apache下的beanutils框架,于是,模拟beanutils写个框架,参看我的博客:只因数据过滤,方可模拟beanutils框架
框架能写的出来,而这只是java端的,我并不知道前端的一些情况,比如前端和后端是如何进行数据交互的,前端如何在页面展示数据等等。于是,下载了前端的页面。我们前端的页面放置在SVN上,后端代码放置在git上。自己慢慢地根据前端页面去摸索,神奇地是能够展示出数据。带我的人看我做的还不错,于是,教我前后端一起开发。
我们这个是前后端分离的,前端调用后端的接口。在学校里面也学过前端的一些知识,比如CSS3、HTML5,jQuery,JavaScript等等,那时并不是项目开发,有些东西只是自己弄着玩的。但公司项目的开发和自己开发完全不是一回事,需要掌握很多的前端知识。
不久,做了个完整的前端项目,因为有学校的经验,有些东西很快就掌握了,但是,其他很多东西还不怎么会,真的很脑大。但随着项目的深入,才发现自己在学校学的知识,还是远远地不够项目开发所用的。于是,不断地充电,不断地向前辈的学习,现在,前端的很多东西也知道了。
现在,回过头看我写的前端代码,再看前辈给我写的前端代码,我发现我那时写的真的垃圾,可以用不堪入目这个词来形容。但是,当时设计的页面还是蛮酷的,也得到了老板的认可。这是我值得骄傲的事。
我不断地尝试前后端的开发,也从中知道了vue.js,bootstrap,jQuery、echart等前端框架。也知道了本地ip和局域网ip的区别,以及如何用小米球做本地调试。从而更知道了,如何实现前后端分离。我越来越相信这句话,只要你努力,就会有人帮助你。
随着时间地推进,老板接了一个项目,就是图书共享的项目。我当时听到这个项目后,我就感觉到有点难过。这和我毕设所做的项目思想是一致的,如果,我当时能够勇敢一点,也许,我就推广我的这个项目了。但是,我没有。
世界就是这么残酷,不允许你有丝毫地犹豫。于是,老板再接下一个项目后,项目名为云码兑换平台。用户收到某家公司给的代币(福利),拿到代币到这个平台上兑换成钱。钱可以存储在自己的余额中,这就相当于微信钱包,余额可以提现到银行卡的中。也可以使用余额来购买商品。这分为企业钱包和个人钱包,企业钱包和个人钱包是不一样的。
我没做个类似项目,于是,就接过来做了。老板说这个项目对我来说,难度系数还是比较大的。我就下定决心把这个项目给做好,因为我知道可以从这个项目学到很多东西。我们的第三方支付平台是连连支付,杭州的一家公司。
支付
这个项目可以说没有任何人带我,完全由我自己参考连连给的文档,边学习边尝试着去开发。这样,也培养了我调用第三方接口,参读他们文档的能力。可以说,未尝不是一种收获呢?
我们老总说过年前夕,只做支付相关的业务,也就是,用户提交代币兑换金额的申请,后台管理员接收到这个申请后,从后台给用户打款。如图所示:
后台管理员点击批量打款时,服务端会做数据的筛选,晒选出状态为待转让的客户打款,其他情况下一律不打款。就这么一个小小的支付按钮,其内部涉及太多的知识点。
我之所以害怕,是因为对未知的恐惧。一开始做支付非常难,其实,做完之后,也没有我想像的那么难。但是,我还是需要做一些准备工作。比如,下载连连支付的SDK。但要考虑到SDK是什么SDK?SDK分为两种,一种是应用到java开发中,一种是应用到Android或iOS开发中。因为,连连那边没有将其区分开来,从而造成钱包项目的延误。这个会在下文提到。
下载好连连支付的SDK后,我们通过maven创建项目,使用的是阿里云的中央仓库,其并不存在连连SDK。因而,我们需要配置本地的Maven库。这个网上都有教程的,我在这里就不细说了。
配置好了SDK后,我们需要了解签名机制,连连使用的RSA签名机制,如图所示:
至于连连为什么使用RSA签名,可以参考我的这篇博客:支付与签名原串的那些事,但选择排序生成签名原串。
我们将生成的pkcs8格式的公钥上传到连连商户站,再从连连的商户站下载其连连公钥。私钥用以加签,公钥用以验签,这用以提高数据的安全性。
私钥怎么加签?每个公司的加签方式是不一样的,支付宝有支付宝的加签方式,微信有微信的加签方式。这里,我就说说连连支付的加签方式。我们在向连连发送支付请求前,需要封装我们的请求参数,将请求参数以一定格式的方式存储,这就是签名原串,如图所示:
将签名原串和我们的pkcs8格式的私钥共同加密,这就调用到jdk数字签名的这几个包:
import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
但是,加密时可能会报出Illegal Key Size的错误。当您使用Java版本为6, 7或8时, 由于连连提供的公钥超出了加密过程中使用到的AES加密方法的默认密钥长度限制, 需要替换您的开发环境中的local_policy.jar和US_export_policy.jar这两个jar包以去除密钥长度限制,即${java_home}/jre/lib/security/
否则会在加密时抛出异常 Illegal Key Size。
Jar包替换地址为:
http://www.oracle.com/technet...
http://www.oracle.com/technet...
http://www.oracle.com/technet...
得到加签后的签名字符串,再调用连连支付的LianLianPaySecurity.encrypt(JSON.toJSONString(preq), PaymentConstant.LIAN_PUBLIC_RSA_KEY);
加密算法,该算法有两个参数,一个是包括签名的请求参数,一个是下载下来的连连公钥。
如果请求连连支付成功的话,其会返回一段字符串,该字符串包含连连的签名原串、私钥签名串等其他信息。连连那边的签名串的加签方式,和我们这边的是一样的。我们从连连下载下来的连连公钥,其公钥也是其由私钥生成而来。其将请求参数封装成签名原串,将私钥和签名原串共同加签成签名串。
因而,我们这边拿到了连连的签名原串和签名串之后,就要进行验签了。怎么验签呢?我们这个时候,就用到了从连连的商户站下载的连连公钥了,如代码所示:
/** * Created By zby on 17:28 2018/12/18 * RSA签名验证 * <p> * * @param reqObj: The obtained asynchronous notification body * * @param rsa_public: The public key provided by LianLian */ public static boolean checkSignRSA(String response) { logger.info("【响应参数】申请付款的响应参数为:" + response); if (StringUtils.isBlank(response)) { return false; } JSONObject reqObj = JSONObject.parseObject(response); String lianPubkey = PaymentConstant.LIAN_PUBLIC_RSA_KEY; String lianSigned = reqObj.getString("sign"); String lianSignSrc = genSignData(reqObj); ifNullThrow(lianPubkey, ResultCodeEnum.NOT_CONFIG_LIAN_PUBLIC_KEY_VALUE); try { if (TraderRSAUtil.checksign(lianPubkey, lianSigned, lianSignSrc)) { return true; } else { return false; } } catch (Exception e) { return false; } }
底部调用的是TraderRSAUtil.checksign这个方法,其内部如代码所示:
/** * 签名验证 * * @param lianPubkey 下载好的连连公钥 * @param lianSignSrc 连连的签名原串,也就是将返回串封装成jsonObject * @param lianSigned 连连加密签名签名,包括签名原串+私钥 * @return */ public static boolean checksign(String lianPubkey, String lianSigned, String lianSignSrc) { try { //【1】获取公钥 //获取KeyFactory,指定RSA算法 KeyFactory keyFactory = KeyFactory.getInstance(PaymentConstant.SIGN_TYPE); //将BASE64编码的公钥字符串进行解码 BASE64Decoder decoder = new BASE64Decoder(); //将BASE64解码后的字节数组,构造成X509EncodedKeySpec对象,生成公钥对象 PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(decoder.decodeBuffer(lianPubkey))); byte[] signed = decoder.decodeBuffer(lianSigned); //【2】使用公钥,进行验签 //获取Signature实例,指定签名算法(与之前一致) Signature signature = Signature.getInstance(PaymentConstant.MD5_WITH_RSA); //加载公钥 signature.initVerify(publicKey); //更新原数据 signature.update(lianSignSrc.getBytes(BaseConstant.CHARSET)); //公钥验签(true-验签通过;false-验签失败) return signature.verify(signed); } catch (Exception e) { e.printStackTrace(); } return false; }
这就是一个支付的流程,不论是任何第三方支付,其都要有加签和验签,因为,这是最基本的安全机制。只不过,各个公司的加签和验签方式有所不同而已。
当然,有些东西就没必要使用RSA加签和验签了,比如连连那边的绑定银行卡的业务,其加签方式就是用MD5加签的。但是,支付涉及到了金额,这个,只要与钱相关的业务。都需要谨慎处理,也许,RSA不是最好的加密方式,但就目前而言,其还是比较安全的。
钱包
为什么要做钱包呢?做任何事都要有其目的。
我们做的是福利平台。届时,许多商户会入驻该平台,给其用户提供不同的优惠卡或者钱,比如流量充值卡、话费充值卡,中石油的加油卡等。不同商户提供不同的(虚拟)商品,不同的商品对应多少钱,比如话费卡30元,流量卡30元等。商户需要在钱包中充钱(充值),便于用户兑换。
用户拿到这些卡消费时,商户的钱包里的钱会减少。用户可以通过代币兑换这些充值卡,也可以通过代币兑换钱,商户的钱就会变少,而用户的钱包的钱增加,其可以提现到银行卡(提现),也可以购买其他产品(支付);在购买时,钱包余额不够,可以充值到连连钱包(充值)。如果已经购买了,也可以,申请退款(退款)。 同时,也可以查看提现记录(提现查询),支付记录(支付查询);
总的来说,电子钱包可以存储用户的金额,相当于微信支付中的余额,也相当于我们现实中的钱包。用户可以支配钱包金额(余额),但并非任意支配。因为用户支配的额度不能超过钱包的额度。
今年都到了三月二十四号了,我们的钱包已经做一个月了。但是,我们这边的产品经理可能和连连那边的业务没有沟通好,其所提供给我们的web组件全部是走他们的页面。我当时问他们的页面支不支持响应式布局,他们坚决地回答说不支持。如图所示:
这在PC端的浏览器中看,界面还是可以的,但是,如果用手机端看的话,其会非常的别扭,如图所示:
你会发现,这简直不能使用。我当时和对方说,这种效果的用户体验不好。他们说我们这边提供的了手机端的SDK组件,我看了他们给的SDK组件,其完全是Android开发和iOS开发的SDK组件。他们以为我们是Android开发,而我们这是微信公众号,是嵌入在微信中的h5页面,自然是不能使用的。
这就是产品经理和业务没有沟通好带来的误会,产品经理说他们那边的业务不懂技术。他们那边的业务以为SDK就是SDK,不区分是不是Android和iOS,还是服务端java的SDK。业务可能也没问他那边的技术。因而,浪费了这一个月的时间。我们这是从开年就开始做钱包了,做到现在就遇到了这个问题。所以,很多东西都要重新做。
对于企业来说,这是一个损失。但对于我个人来说,这是一种成长。不论是技术方面,还是与人沟通方面,都是一种成长。
我们后来和他们说,web组件走不通了,能不能通过API的方式自定义界面,他们说他们这边也是提供的,不过,是最近刚开放的API的接口。我们这边的产品经理非常生气,说你们那边没有的话,就早点和我们说吗,免得浪费我们的时间。
我在开发的起始阶段,看了他们的SDK,也有和对方沟通。我说我们这是公众号开发,你们这个SDK好像有问题,他们那边说你可以使用web组件,当时,也没有说到这个API的接口。
沟通问题
出现上述的问题,也不能说是谁的错。其实,谁都没有错,然而,谁貌似都有错。因为沟通的不及时,导致了上面的问题。
事情已经出了,生气也没用。因而,我就从这件事中吸取教训。凡是,都要先沟通好,比如,就这个SDK的问题。是什么样的SDK?嵌入在java中的SDK,还是纯安卓的SDK。这个,就需要考虑清楚。沟通是非常重要,如果沟通的不及时,既浪费了人力和物力,也让双方都不开心。
因而,以后为人处事,一定要学会沟通。不过,这对于我来说,也是一次小小的成长吧。在成长的过程中,就会摔跟头的。但站起来之后,回头看看绊倒自己的是什么?是石头,还是香蕉皮?以后,再遇到这样的事情,能不能及时的沟通。
祝自己在成长的过程,愈挫愈勇吧。