支付宝开放平台C++方式接入

链接:https://github.com/ArthasModern/AlipayOpenapiCpp

如果你想以C\C++方式接入支付宝开放平台调用支付宝的各种开放接口(比如收款等),那么你可以参考这个项目;

这个项目里帮你解决了参数组装、排序、OpenSSL加签验签、网络请求等问题,能帮助你轻松接入支付宝;

并且该项目依赖C\C++标准库STL以及几个开源的第三方库,代码精简(只有几个class),非常易于集成;


使用起来很简便:

#include <iostream>
#include "stdlib.h"

#define _DEBUG

#include "openapi/openapi_client.h"

using namespace std;

/** ++++++++++++++++++++++++++++++++++++++++++++++++ **/
/** 此处替换为开发者在支付宝开放平台申请的应用ID **/
string appId = "2016000066668888";

/** 此处替换为开发者使用openssl生成的rsa私钥 **/
string pKey = "-----BEGIN RSA PRIVATE KEY-----\n"
		"MIICXQIBAAKBgQDTI0/RaV/YKWDGbKkQGYpD9I/UljBCCf3rWm09sXiif8MN5rLA\n"
		"3TjC4gZ478n6Dys5yO23h1HGVTWu+mQ8071+pwLHGQ+dyDNrGWR89VLb9yanOeRf\n"
		"efOcN19ATZgGAzheM28E/iqaYkh8F2NlCjOiZAsBG6eVvxachwVAQUIWwwIDAQAB\n"
		"AoGAdVr8Q46JenHNW50L/2niw1DNHUF5g0tgeo+hhpf9UH0pIrHnC3Iq2Y+eP1ww\n"
		"7K+/u/elwcwSNOYp159PVcvvV9LwPwH29DdH6KEWIDiyFpjbXPcMMFwgakyLnFTL\n"
		"sxxa6DYznFokT+IPkF6esoypa7VQFU1RIal5Sgphq7CGCDECQQDqyL3QjYT6ffLd\n"
		"NRiMBB13+eIxvXGy5AEQcH4pNt6kYHWONCWeZ34miNp2UliIBvBHZ1uuGoO4F/Jx\n"
		"2sWwWlSpAkEA5jeQGFx/RDzzi0qPMpSOR50d2IC4NbbresY+hgJEBbI6n5hPR1ts\n"
		"MUuO1e3L5I5rzRKNzD1um1DdSgmqaqmHiwJBANLnRpNsPRMjRqHtS0Kjg7E9mDIk\n"
		"Qll3NXmGA96T+oXgXFlEgLJ9tzV4Y/471GlFClyp/RI1oTMi19fstP7I9hkCQBjr\n"
		"bseUS5phVqN/QJzjA7uwwChNVqNJ15eEmgP7fs13C213GS3KMZ3sZdu2T9m/qN+b\n"
		"4Il5JN3fFPUMssu06h0CQQCGBdmtRLi+9ws57qTPHR/BdHGUxdBRWllc9sGVVaRw\n"
		"+EOMGXus6/BssTRjwplx7w8uUR0U3s1KYDJMHMCjW25x\n"
		"-----END RSA PRIVATE KEY-----";

/** 支付宝公钥,用来验证支付宝返回请求的合法性 **/
string aliPubKey = "-----BEGIN PUBLIC KEY-----\n"
        "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDI6d306Q8fIfCOaTXyiUeJHkr\n"
        "IvYISRcc73s3vF1ZT7XN8RNPwJxo8pWaJMmvyTn9N4HQ632qJBVHf8sxHi/fEsra\n"
        "prwCtzvzQETrNRwVxLO5jVmRGi60j8Ue1efIlzPXV9je9mkjzOmdssymZkh2QhUr\n"
        "CmZYI/FCEa3/cNMW0QIDAQAB\n"
        "-----END PUBLIC KEY-----";

/** 注:appid,私钥,支付宝公钥等信息建议不要写死在代码中 **/
/** 这些信息应以配置等方式保存,此处写在代码中只是为了示例的简便 **/
/** ++++++++++++++++++++++++++++++++++++++++++++++++ **/


/** some examples **/
JsonMap getPrecreateContent();

int main(int argc, char *argv[])
{

    /** 实例化OpenapiClient工具类 **/
    OpenapiClient openapiClient(appId,
                                pKey,
                                OpenapiClient::default_url,
                                OpenapiClient::default_charset,
                                aliPubKey);

    /** ++++++++++++++++++++++++++++++++++++++++++++++++ **/
    /** 各个具体业务接口参数组装模式具体参看Openapi官方文档 **/
    /** https://doc.open.alipay.com/ **/
    // demo1:当面付预下单示例
    string method = "alipay.trade.precreate";
    JsonMap contentMap = getPrecreateContent();

    /** ++++++++++++++++++++++++++++++++++++++++++++++++ **/

    /** ++++++++++++++++++++++++++++++++++++++++++++++++ **/
    /** 网关扩展参数,例如商户需要回传notify_url等,可以在extendParamMap中传入 **/
    /** 这是一个可选项,如不需要,可不传 **/
    /* StringMap extendParamMap;
    extendParamMap.insert(StringMap::value_type("notify_url", "http://api.test.alipay.net/atinterface/receive_notify.htm"));
    */

    /** ++++++++++++++++++++++++++++++++++++++++++++++++ **/

    /** 调用Openapi网关 **/
    JsonMap respMap;
    respMap = openapiClient.invoke(method, contentMap);
    /* 如果有扩展参数,则按如下方式传入
    respMap = openapiClient.invoke(method, contentMap, extendParamMap);
    */

    /** 解析支付宝返回报文 **/
    JsonMap::const_iterator iter = respMap.find("code");
    if (iter != respMap.end()) {
        string respCode = iter->second.toString();
        DebugLog("code:%s", respCode.c_str());
    } else {
        DebugLog("cannot get code from response");
    }

    iter = respMap.find("msg");
    if (iter != respMap.end()) {
        string respMsg = iter->second.toString();
        DebugLog("msg:%s", respMsg.c_str());
    } else {
        DebugLog("cannot get msg from response");
    }

    system("pause");
    return 0;
}

/**
 * 组装支付宝预下单业务请求
 */
JsonMap getPrecreateContent() {

    JsonMap contentMap;
    contentMap.insert(JsonMap::value_type(JsonType("out_trade_no"), JsonType("20160606121212")));
    contentMap.insert(JsonMap::value_type(JsonType("total_amount"), JsonType(0.01)));
    contentMap.insert(JsonMap::value_type(JsonType("subject"), JsonType("好东西")));

    return contentMap;
}


Readme.txt:

该项目为C++项目,包含访问支付宝开放平台(Openapi)网关的源码;

/** ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ **/

工程中有两个项目文件:
Openapi.pro为QtCreater项目文件,可用QtCreater打开;
Openapi.sln为VS(2008版)项目文件,可用VS打开;

注:该项目源码依赖C\C++标准库(STL)以及几个开源的第三方库(cJSON,libcurl,openssl);
开发者可以在其它支持C\C++的平台(linux等)编译适配;

/** ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ **/

项目主要源码包含在目录“/openapi”中;

1)主工具类 -- ./openapi/openapi_client 
---- 访问支付宝开放平台(openapi)网关的工具类;
---- 外部主要依赖该类访问支付宝网关;
---- 具体使用示例可参见main.cpp中的源码及注释;

2)其它依赖的工具类:
HttpClient -- ./openapi/http/http_client
---- 该工具类提供Http(Https)网络通信的功能;
---- 该工具类依赖第三方库libcurl

JsonUtil -- ./openapi/json/json_util
---- 该工具类提供Json串与C++对象之间的转换功能;
---- 该工具类依赖第三方库cJSON;

openssl & libcurl -- ./libs/
---- 这两个第三方库已经编译成静态库(windows平台);
---- 其它平台的静态库,开发者可自行下载源码进行编译;

/** ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ **/

集成/调试之前的准备工作:

1)入住支付宝开放平台,申请应用并获得应用ID(appId);

2)本地生成rsa密钥对,并上传公钥到支付宝开放平台相应的应用下,
   并保存好自己的私钥(privateKey -- 严格保密);
   rsa密钥生成方式参看支付宝开放平台官方文档;
   注:C++使用的是标准格式的rsa私钥,不是pkcs8格式的;
	
3)各个具体业务接口参数组装模式具体参看Openapi官方文档;

4)支付宝开放平台文档中心:https://doc.open.alipay.com/


其中关键工具类的构建如下:

#ifndef OPENAPICLIENT_H
#define OPENAPICLIENT_H

#include "http/http_client.h"
#include "json/json_util.h"
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <time.h>

#ifndef XRSA_KEY_BITS
#define XRSA_KEY_BITS (1024)
#endif

/** STL map default sort order by key **/
typedef map<string, string> StringMap;

/**
 * @brief The OpenapiClient class
 */
class OpenapiClient {
public:
    OpenapiClient(const string &appId,
                  const string &privateKey,
                  const string &url = default_url,
                  const string &charset = default_charset,
                  const string &alipayPublicKey = string());

public:
    static const string default_charset;
    static const string default_url;
    static const string default_sign_type;
    static const string default_version;

    static const string KEY_APP_ID;
    static const string KEY_METHOD;
    static const string KEY_CHARSET;
    static const string KEY_SIGN_TYPE;
    static const string KEY_SIGN;
    static const string KEY_TIMESTAMP;
    static const string KEY_VERSION;
    static const string KEY_BIZ_CONTENT;

private:
    string appId;
    string privateKey;
    string signType;
    string version;
    string charset;
    string url;

    string alipayPublicKey;

public:
    string invoke(const string &method, const string &content, const StringMap &extendParamMap = StringMap());
    JsonMap invoke(const string &method, const JsonMap &contentMap, const StringMap &extendParamMap = StringMap());


private:
    /**
     *
     * STL map default sort order by key
     *
     * STL map 默认按照key升序排列
     * 这里要注意如果使用的map必须按key升序排列
     *
     */
    string buildContent(const StringMap &contentPairs);
    string analyzeResponse(const string &responseStr);

public:
    static string base64Encode(const unsigned char *bytes, int len);
    static bool base64Decode(const string &str, unsigned char *bytes, int &len);

    static string rsaSign(const string &content, const string &key);
    static bool rsaVerify(const string &content, const string &sign, const string &key);

public:
    string getAppId();

    string getSignType();

    string getVersion();

    string getCharset();

    string getUrl();

    string getAlipayPublicKey();

};

#endif // OPENAPICLIENT_H

源文件:

#include "openapi_client.h"


const string OpenapiClient::default_charset      = "utf-8";
const string OpenapiClient::default_url          = "https://openapi.alipay.com/gateway.do";
const string OpenapiClient::default_sign_type    = "RSA";
const string OpenapiClient::default_version      = "2.0";

const string OpenapiClient::KEY_APP_ID           = "app_id";
const string OpenapiClient::KEY_METHOD           = "method";
const string OpenapiClient::KEY_CHARSET          = "charset";
const string OpenapiClient::KEY_SIGN_TYPE        = "sign_type";
const string OpenapiClient::KEY_SIGN             = "sign";
const string OpenapiClient::KEY_TIMESTAMP        = "timestamp";
const string OpenapiClient::KEY_VERSION          = "version";
const string OpenapiClient::KEY_BIZ_CONTENT      = "biz_content";


OpenapiClient::OpenapiClient(const string &appId,
                             const string &privateKey,
                             const string &url,
                             const string &charset,
                             const string &alipayPublicKey)
    : appId(appId),
      privateKey(privateKey),
      signType(default_sign_type),
      version(default_version),
      url(url),
      charset(charset),
      alipayPublicKey(alipayPublicKey) {

}

JsonMap OpenapiClient::invoke(const string &method, const JsonMap &contentMap, const StringMap &extendParamMap) {

    string content = JsonUtil::objectToString(JsonType(contentMap));
    string responseContent = invoke(method, content, extendParamMap);
    JsonType jsonObj = JsonUtil::stringToObject(responseContent);
    return jsonObj.toMap();
}

string OpenapiClient::invoke(const string &method, const string &content, const StringMap &extendParamMap) {

    time_t t = time(0);
    char tmp[64];
    strftime(tmp, sizeof(tmp), "%Y-%m-%d %X", localtime(&t));

    StringMap requestPairs;
    requestPairs.insert(StringMap::value_type(OpenapiClient::KEY_APP_ID, appId));
    requestPairs.insert(StringMap::value_type(OpenapiClient::KEY_BIZ_CONTENT, content));
    requestPairs.insert(StringMap::value_type(OpenapiClient::KEY_CHARSET, charset));
    requestPairs.insert(StringMap::value_type(OpenapiClient::KEY_METHOD, method));
    requestPairs.insert(StringMap::value_type(OpenapiClient::KEY_SIGN_TYPE, signType));
    requestPairs.insert(StringMap::value_type(OpenapiClient::KEY_TIMESTAMP, tmp));
    requestPairs.insert(StringMap::value_type(OpenapiClient::KEY_VERSION, version));

    /** 追加外部传入的网关的补充参数,如notify_url等 **/
    for (StringMap::const_iterator iter = extendParamMap.begin(); iter != extendParamMap.end(); ++iter) {
        requestPairs.insert(StringMap::value_type(iter->first, iter->second));
    }

    string wholeContent = buildContent(requestPairs);
    string sign = OpenapiClient::rsaSign(wholeContent, privateKey);
    requestPairs.insert(StringMap::value_type(OpenapiClient::KEY_SIGN, sign));

    wholeContent = buildContent(requestPairs);
    DebugLog("Request:%s", wholeContent.c_str());

    HttpClient httpClient;
    string responseStr = httpClient.sendSyncRequest(url, requestPairs);

    DebugLog("Response:%s", responseStr.c_str());

    string responseContent = analyzeResponse(responseStr);
    return responseContent;
}

/**
 *
 * STL map default sort order by key
 *
 * STL map 默认按照key升序排列
 * 这里要注意如果使用的map必须按key升序排列
 *
 */
string OpenapiClient::buildContent(const StringMap &contentPairs) {

    string content;
    for (StringMap::const_iterator iter = contentPairs.begin();
         iter != contentPairs.end(); ++iter) {
        if (!content.empty()) {
            content.push_back('&');
        }
        content.append(iter->first);
        content.push_back('=');
        content.append(iter->second);
    }
    return content;
}

string OpenapiClient::analyzeResponse(const string &responseStr) {

    JsonType responseObj = JsonUtil::stringToObject(responseStr);
    JsonMap responseMap = responseObj.toMap();
    //获取返回报文中的alipay_xxx_xxx_response的内容;
    int beg = responseStr.find("_response\"");
    int end = responseStr.rfind("\"sign\"");
    if (beg < 0 || end < 0) {
        return string();
    }
    beg = responseStr.find('{', beg);
    end = responseStr.rfind('}', end);
    //注意此处将map转为json之后的结果需要与支付宝返回报文中原格式与排序一致;
    //排序规则是节点中的各个json节点key首字母做字典排序;
    //Response的Json值内容需要包含首尾的“{”和“}”两个尖括号,双引号也需要参与验签;
    //如果字符串中包含“http://”的正斜杠,需要先将正斜杠做转义,默认打印出来的字符串是已经做过转义的;
    //此处转换之后的json字符串默认为"Compact"模式,即紧凑模式,不要有空格与换行;
    string responseContent = responseStr.substr(beg, end - beg + 1);

    DebugLog("ResponseContent:%s", responseContent.c_str());

    //此处为校验支付宝返回报文中的签名;
    //如果支付宝公钥为空,则默认跳过该步骤,不校验签名;
    //如果支付宝公钥不为空,则认为需要校验签名;
    if (!alipayPublicKey.empty()) {

        DebugLog("AlipayPublicKey:%s", alipayPublicKey.c_str());

        JsonMap::const_iterator iter = responseMap.find(OpenapiClient::KEY_SIGN);
        if (iter == responseMap.end()) {
            DebugLog("Cannot get Sign from response, Verify Failed");
            return string();
        }
        //获取返回报文中的sign;
        string responseSign = iter->second.toString();

        DebugLog("ResponseSign:%s", responseSign.c_str());

        //调用验签方法;
        bool verifyResult = OpenapiClient::rsaVerify(responseContent, responseSign, alipayPublicKey);

        if (!verifyResult) {
            DebugLog("Verify Failed");
            return string();
        }
        DebugLog("Verify Success");
    } else {
        DebugLog("AlipayPublicKey is empty, Skip the Verify");
    }

    return responseContent;
}

string OpenapiClient::rsaSign(const string &content, const string &key) {

    string signed_str;
    const char *key_cstr = key.c_str();
    int key_len = strlen(key_cstr);
    BIO *p_key_bio = BIO_new_mem_buf((void *)key_cstr, key_len);
    RSA *p_rsa = PEM_read_bio_RSAPrivateKey(p_key_bio, NULL, NULL, NULL);

    if (p_rsa != NULL) {

        const char *cstr = content.c_str();
        unsigned char hash[SHA_DIGEST_LENGTH] = {0};
        SHA1((unsigned char *)cstr, strlen(cstr), hash);
        unsigned char sign[XRSA_KEY_BITS / 8] = {0};
        unsigned int sign_len = sizeof(sign);
        int r = RSA_sign(NID_sha1, hash, SHA_DIGEST_LENGTH, sign, &sign_len, p_rsa);

        if (0 != r && sizeof(sign) == sign_len) {
            signed_str = base64Encode(sign, sign_len);
        }
    }

    RSA_free(p_rsa);
    BIO_free(p_key_bio);
    return signed_str;
}

bool OpenapiClient::rsaVerify(const string &content, const string &sign, const string &key) {

    bool result = false;
    const char *key_cstr = key.c_str();
    int key_len = strlen(key_cstr);
    BIO *p_key_bio = BIO_new_mem_buf((void *)key_cstr, key_len);
    RSA *p_rsa = PEM_read_bio_RSA_PUBKEY(p_key_bio, NULL, NULL, NULL);

    if (p_rsa != NULL) {
        const char *cstr = content.c_str();
        unsigned char hash[SHA_DIGEST_LENGTH] = {0};
        SHA1((unsigned char *)cstr, strlen(cstr), hash);
        unsigned char sign_cstr[XRSA_KEY_BITS / 8] = {0};
        int len = XRSA_KEY_BITS / 8;
        base64Decode(sign, sign_cstr, len);
        unsigned int sign_len = XRSA_KEY_BITS / 8;
        int r = RSA_verify(NID_sha1, hash, SHA_DIGEST_LENGTH, (unsigned char *)sign_cstr, sign_len, p_rsa);

        if (r > 0) {
            result = true;
        }
    }

    RSA_free(p_rsa);
    BIO_free(p_key_bio);
    return result;
}

string OpenapiClient::base64Encode(const unsigned char *bytes, int len) {

    BIO *bmem = NULL;
    BIO *b64 = NULL;
    BUF_MEM *bptr = NULL;

    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    bmem = BIO_new(BIO_s_mem());
    b64 = BIO_push(b64, bmem);
    BIO_write(b64, bytes, len);
    BIO_flush(b64);
    BIO_get_mem_ptr(b64, &bptr);

    string str = string(bptr->data, bptr->length);
    BIO_free_all(b64);
    return str;
}

bool OpenapiClient::base64Decode(const string &str, unsigned char *bytes, int &len) {

    const char *cstr = str.c_str();
    BIO *bmem = NULL;
    BIO *b64 = NULL;

    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    bmem = BIO_new_mem_buf((void *)cstr, strlen(cstr));
    b64 = BIO_push(b64, bmem);
    len = BIO_read(b64, bytes, len);

    BIO_free_all(b64);
    return len > 0;
}


string OpenapiClient::getAppId() {
    return appId;
}

string OpenapiClient::getSignType() {
    return signType;
}

string OpenapiClient::getVersion() {
    return version;
}

string OpenapiClient::getCharset() {
    return charset;
}

string OpenapiClient::getUrl() {
    return url;
}

string OpenapiClient::getAlipayPublicKey() {
    return alipayPublicKey;
}

版权声明:本文为sowhat_Ah原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。