[原]支付宝无线支付服务端接入C++

来源:互联网 发布:2017淘宝补单平台 编辑:程序博客网 时间:2024/05/16 06:13
类似第三方接入这种事情,所能够参考的比较有价值的资料就是文档和demo,如果它们描述地足够清晰,那接入起来当然是件非常容易的事情。相对来说,阿里提供的文档和demo都比较清晰。在这之前我也做过其他的第三方接入,一次是官方文档和给的demo对不上,对接的时候还得找官方的接入人员咨询,非常蛋疼,对接成功后上线了一段时间又发现了问题,原来对方又把接口给改了,返回数据都跟以前的不一致,我艹~~

官方提供的服务端demo有三种语言实现:C#、Java和PHP,这基本能够满足大多数开发的需求,当然语言都是相通的,用其它语言也可以实现相同的功能,比如用C/C++来完成,本篇博文重点总结下用C/C++做支付宝无线支付后台接入时所遇到的问题和解决方法。


交互流程

数据交互的流程就不赘述了,文档上描述地很清楚,借用一下文档上的交互时序图:

这样一看其实服务端所要完成的事情还是比较简单的,即处理支付宝的异步回调,异步回调默认当交易成功或支付成功时触发。

异步回调逻辑

异步回调究竟要完成什么内容呢?查看官方的demo就大致明白了,简单来说可以分为三个部分:数据校验、业务逻辑和回调返回。

数据校验

数据校验具体又可以分为两部分,校验notify_idsign。校验notify_id的目的在于鉴别请求是否由支付宝发起,而校验sign的目的在于检验数据是否被篡改。

校验notify_id的方法比较容易实现,需要你构造一个URL(支付宝校验地址+partner id+notify_id),并向这个URL发起HTTP GET请求,如果返回的数据是true,则校验成功,否则失败,具体信息可以参考文档10 如何验证是否支付宝请求。HTTP请求的实现可以使用libcurl,参考官方提供的这个demo。

校验sign有三步,请求参数过滤(空值、sign与sign_type)、提取待签名字符串和签名校验,具体可以参考官方提供的demo,如Java demo中的AlipayNotify.java中getSignVeryfy方法,也可以参考文档9 签名机制。查看阿里商户后台,猜测它应该支持两种加密算法:RSA和DSA,我采用的是RSA,有关RSA的细节可以参考wiki和阮一峰关于RSA算法原理的介绍。
校验sign的前两步用STL就能够实现,至于签名校验的部分我用的OpenSSL库来完成这部分的内容。虽然这货前段时间曝漏洞很频繁,但鉴于大家对它的关注度比较高,相信未来它会发展的越来越好。目前OpenSSL提供的文档比较稀缺,代码看起来也比较费劲,当然是我水平比较有限。这部分需要关注的有以下几步:

(1)公钥编码,读取之前要对公钥进行Base64解码,有开源的Base64编解码实现;

(2)RSA公钥读取(从内存),用到了BIO_new_mem_buf和PEM_read_bio_RSA_PUBKEY;

(3)RSA验签,重点要用到的API有EVP_VerifyInit_ex、EVP_VerifyUpdate和EVP_VerifyFinal,附下文档链接。

需要注意的是RSA公钥字符串的格式,以“-----BEGIN PUBLIC KEY-----\n”开头,以“-----END PUBLIC KEY-----”结束,中间每隔64个字符要加上换行符,多出的24个字符最后也要加上换行,否则会在读取时报错。

URL encode可以使用libcurl提供的curl_easy_escape函数。

业务逻辑和回调返回的部分就不赘述了,按照demo的格式来就行。

订单数据构造

如果考虑安全性,我们还能够做更多的事情。比如订单数据构造的部分完全由后台来完成。这部分内容要参考客户端demo的代码,读取私钥可以使用PEM_read_bio_RSAPrivatekey直接从内存中读取(注意读取前也要对私钥做Base64解码,格式与生成的PKCS8格式私钥保持一致就行);用到的加密函数有EVP_SignInit、EVP_SignUpdate和EVP_SignFinal,文档上有相关函数的说明;加密完成后需要对密文做Base64编码。

还有一点需要强调的是程序中所用到的公钥是阿里后台生成的公钥,在你把公钥上传上去后它会生成一个新的公钥。阿里返回的数据是经过它的私钥加密的,所以在验签阿里的数据是需要使用它提供的公钥,而不是你用openssl生成的公钥。

可以参考如下代码,也可以直接在gist上下载(需翻墙)
#ifndef __ALIPAY_H__#define __ALIPAY_H__ #include "base64/base64.h"#include <curl/curl.h>#include <map>#include <openssl/bio.h>#include <openssl/rsa.h>#include <openssl/pem.h>#include <openssl/err.h> class Rsa {public:static bool verify(const char *public_key, const string &content, const string &sign) {BIO *bufio = NULL;RSA *rsa = NULL;EVP_PKEY *evpKey = NULL;bool verify = false;EVP_MD_CTX ctx;int result = 0;string decodedSign = base64_decode(sign);char *chDecodedSign = const_cast<char*>(decodedSign.c_str()); bufio = BIO_new_mem_buf((void*)public_key, -1);if (bufio == NULL) {ERR("BIO_new_mem_buf failed");goto safe_exit;} rsa = PEM_read_bio_RSA_PUBKEY(bufio, NULL, NULL, NULL);if (rsa == NULL) {ERR("PEM_read_bio_RSA_PUBKEY failed");goto safe_exit;} evpKey = EVP_PKEY_new();if (evpKey == NULL) {ERR("EVP_PKEY_new failed");goto safe_exit;} if ((result = EVP_PKEY_set1_RSA(evpKey, rsa)) != 1) {ERR("EVP_PKEY_set1_RSA failed");goto safe_exit;} EVP_MD_CTX_init(&ctx); if (result == 1 && (result = EVP_VerifyInit_ex(&ctx, EVP_sha1(), NULL)) != 1) {ERR("EVP_VerifyInit_ex failed");} if (result == 1 && (result = EVP_VerifyUpdate(&ctx, content.c_str(), content.size())) != 1) {ERR("EVP_VerifyUpdate failed");} if (result == 1 && (result = EVP_VerifyFinal(&ctx, (unsigned char*)chDecodedSign,decodedSign.size(), evpKey)) != 1) {ERR("EVP_VerifyFinal failed");} if (result == 1) {verify = true;} else {ERR("verify failed");} EVP_MD_CTX_cleanup(&ctx); safe_exit:if (rsa != NULL) {RSA_free(rsa);rsa = NULL;} if (evpKey != NULL) {EVP_PKEY_free(evpKey);evpKey = NULL;} if (bufio != NULL) {BIO_free_all(bufio);bufio = NULL;} return verify;} static string sign(const char *private_key, const string &content) {BIO *bufio = NULL;RSA *rsa = NULL;EVP_PKEY *evpKey = NULL;bool verify = false;EVP_MD_CTX ctx;int result = 0;unsigned int size = 0;char *sign = NULL;string signStr = ""; bufio = BIO_new_mem_buf((void*)private_key, -1);if (bufio == NULL) {ERR("BIO_new_mem_buf failed");goto safe_exit;} rsa = PEM_read_bio_RSAPrivateKey(bufio, NULL, NULL, NULL);if (rsa == NULL) {ERR("PEM_read_bio_RSAPrivateKey failed");goto safe_exit;} evpKey = EVP_PKEY_new();if (evpKey == NULL) {ERR("EVP_PKEY_new failed");goto safe_exit;} if ((result = EVP_PKEY_set1_RSA(evpKey, rsa)) != 1) {ERR("EVP_PKEY_set1_RSA failed");goto safe_exit;} EVP_MD_CTX_init(&ctx); if (result == 1 && (result = EVP_SignInit_ex(&ctx, EVP_sha1(), NULL)) != 1) {ERR("EVP_SignInit_ex failed");} if (result == 1 && (result = EVP_SignUpdate(&ctx, content.c_str(), content.size())) != 1) {ERR("EVP_SignUpdate failed");} size = EVP_PKEY_size(evpKey);sign = (char*)malloc(size+1);memset(sign, 0, size+1);if (result == 1 && (result = EVP_SignFinal(&ctx, (unsigned char*)sign,&size, evpKey)) != 1) {ERR("EVP_SignFinal failed");} if (result == 1) {verify = true;} else {ERR("verify failed");} signStr = base64_encode((const unsigned char*)sign, size);EVP_MD_CTX_cleanup(&ctx);free(sign); safe_exit:if (rsa != NULL) {RSA_free(rsa);rsa = NULL;} if (evpKey != NULL) {EVP_PKEY_free(evpKey);evpKey = NULL;} if (bufio != NULL) {BIO_free_all(bufio);bufio = NULL;} return signStr;} private:static void ERR(const string &pre) {ERR_load_crypto_strings();char buf[512];ERR_error_string_n(ERR_get_error(), buf, sizeof buf);// log error here}}; // partner number, start with 2088#define PARTNER "" // alipay verify url, used to check notify_id// to confirm the data is sent by alibaba#define HTTPS_VERIFY_URL "https://mapi.alipay.com/gateway.do?service=notify_verify" // our private key, PKCS#8 format#define PRIVATE_KEY "" // alipay public key, used to check data from alibaba#define PUBLIC_KEY "" #define SUBJECT ""#define SELLER_ID ""#define BODY "" class Alipay {public:/* verify callback data */static bool verify(CgiUtil &cgi) {string responseTxt = "true";string notifyId = cgi.get_str("notify_id", "");if (!notifyId.empty()) {responseTxt = verify_response(notifyId);}string sign = cgi.get_str("sign", "");bool isSign = get_sign_verify(cgi, sign);if (isSign && responseTxt == "true") {return true;}return false;} static string get_order(const string &oid, unsigned int price, const string &cb_url) {string orderInfo = get_order_info(oid, price, cb_url);// we just support RSA right now, don't botherstring sign = Rsa::sign(PRIVATE_KEY, orderInfo);stringstream ss;ss << orderInfo   << "&sign=\""   << url_encode(sign)   << "\"&"   << "sign_type=\"RSA\"";return ss.str();} private: static string get_order_info(const string &oid, unsigned int price, const string &cb_url) {float payMoney = (float)price / 100.0;string encode_url = url_encode(cb_url); stringstream ss;ss << "service=\"mobile.securitypay.pay\"&"   << "partner=\""   << PARTNER   << "\"&"   << "_input_charset=\"utf-8\"&"   << "notify_url=\""   << encode_url   << "\"&"   << "out_trade_no=\""   << oid   << "\"&"   << "subject=\""   << SUBJECT   << "\"&"   << "payment_type=\"1\"&"   << "seller_id=\""   << SELLER_ID   << "\"&"   << "total_fee=\""   << payMoney   << "\"&"   << "body=\""   << BODY   << "\""; return ss.str();} static string verify_response(const string ¬ifyId) {stringstream ss;ss << HTTPS_VERIFY_URL   << "&partner="   << PARTNER   << "&notify_id="   << notifyId;                // do it yourself using curl return check_alipay_cb_url(ss.str().c_str());} static bool get_sign_verify(CgiUtil &cgi, string sign) {std::map<string, string> map; para_filter(cgi, map); string preSignStr = create_link_string(map);bool isSign = false;// RSA verifyif (Rsa::verify(PUBLIC_KEY, preSignStr, sign)) {isSign = true;}return isSign;} static void para_filter(CgiUtil &cgi, std::map<string, string> &hmap) {vector<FormEntry> entry = cgi.getElements();vector<FormEntry>::iterator it = entry.begin();for (; it != entry.end(); it++) {FormEntry fe = *it;string name = fe.getName();string value = fe.getValue(); if (value == "" || iequal(name, "sign") || iequal(name, "sign_type")) {continue;}hmap.insert(pair<string, string>(name, value));}} static string create_link_string(std::map<string, string> &map) {std::map<string, string>::iterator it = map.begin();unsigned int size = map.size();stringstream ss; for (unsigned int i = 0; it != map.end() && i < size; it ++, i ++) {if (i == size - 1) {ss << it->first << "=" << it->second;} else {ss << it->first << "=" << it->second << "&";}} return ss.str();} static string url_encode(const string &url) {string encode = "";char *data_encode = curl_easy_escape(NULL, url.c_str(), 0); if (data_encode) {encode = data_encode;curl_free(data_encode);} return encode;} }; #endif /* __ALIPAY_H__ */
需要注意的是:代码中使用的<span style="background-color: rgb(240, 240, 240);">CgiUtil类这里没有实现,可以参考支付宝给的C#或者其它语言版本的demo模仿实现!!</span>
<span style="background-color: rgb(240, 240, 240);">demo在:http://doc.open.alipay.com/doc2/detail?treeId=54&articleId=103419&docType=1</span>
<span style="background-color: rgb(240, 240, 240);"></span>

0 0
原创粉丝点击