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

类似第三方接入这种事情,所能够参考的比较有价值的资料就是文档和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_bufPEM_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 bother
		string 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 verify
		if (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>

2分钟快速集成支付宝快捷支付,帮助没有做过支付宝,又需要快速集成支付宝支付的用户。配置完成之后,只需要一行代码即可代用支付宝支付。 1. 将本工程中的IntegratedAlipay文件夹导入工程中,记得选copy 2.点击项目名称,点击“Build Settings”选项卡,在搜索框中,以关键字“search” 搜索,对“Header Search Paths”增加头文件路径:“$(SRCROOT)/项目名称/IntegratedAlipay/AlipayFiles”(注意:不包括引号,如果不是放到项目根目录下,请在项目名称后面加上相应的目录名)。 3. 点击项目名称,点击“Build Phases”选项卡,在“Link Binary with Librarles” 选项中,新增“AlipaySDK.framework”、“UIKit.framework”和“SystemConfiguration.framework” 两个系统库文件。如果项目中已有这两个库文件,可不必再增加。 4. 在“AlipayHeader.h”头文件中设置kPartnerID、kSellerAccount、kAppScheme、kPrivateKey的值(注意,建议除appScheme以外的字段都从服务器请求) 5. 在需要用的地方导入“AlipayHeader.h”,并使用“[AlipayRequestConfig alipayWithPartner:...”方法进行支付 6. 在AppDelegate中处理事件回调(可直接复制下面内容): - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { //如果极简 SDK 不可用,会跳转支付宝钱包进行支付,需要将支付宝钱包的支付结果回传给 SDK if ([url.host isEqualToString:@"safepay"]) { [[AlipaySDK defaultService] processOrderWithPaymentResult:url standbyCallback:^(NSDictionary *resultDic) { NSLog(@"result = %@",resultDic); }]; if ([url.host isEqualToString:@"platformapi"]){//支付宝钱包快登授权返回 authCode [[AlipaySDK defaultService] processAuthResult:url standbyCallback:^(NSDictionary *resultDic) { NSLog(@"result = %@",resultDic); }]; } return YES; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值