首页 > 开源 > C++ Web框架Paozhu v1.17.0发布,添加MQTT,网络支付

C++ Web框架Paozhu v1.17.0发布,添加MQTT,网络支付

OSChina项目 2026-09-28 14:50 6 阅读 查看原文

Paozhu 重构了webpay模块 公网测试了支付宝支付 微信v2, v3版支付,小程序支付。网站使用 微信native支付 无需年费,应用于低频次支付场景。

Paozhu 已经是金丹修为的框架,提供了完善了web开发,而无需依赖第三方。

配置参考 conf/webpay.conf 配置文件

# 演示数据,需要你填入你真正的数据

# 微信支付 V2(jscode2session / access_token / JSAPI / Native XML 接口)

[wxpayv2]

appid = wx21222334444

mch_id = 11145678901

apikey = zI8wF8cI3k78uB8uZ2oB6lZ9gB2bL5bO

secret = 379c869abc23a35849663ea9d5bc7b69

cert_file = conf/apiclient_cert.pem

key_file = conf/apiclient_key.pem

notifyurl = https://www.xxx.net/ordernotify

# 微信支付 V3(Native / JSAPI 等 v3 接口)

# :warning:️ 换商户时 cert_file / key_file / v3_serial_no 三项必须全部替换

[wxpayv3]

appid = wx21222334444

mch_id = 11145678901

api_v3_key = 1122334455_need32char

cert_file = conf/apiclient_cert.pem

key_file = conf/apiclient_key.pem

notifyurl = https://www.xxx.net/ordernotify

v3_serial_no = 6F245450E065994D48A99A6987BE9B54286F2D1A

platform_cert_file =

填上自己的财付通信息

[sitea.alipay]

appid = xxx-sitea-alipay-appid

key_file = conf/sitea应用私钥RSA2048.txt

public_key_file = conf/sitea支付宝公钥RSA2.txt

notifyurl = https://www.site-a.com/alipaynotify

returnurl = https://www.site-a.com/alipayreturn

sandbox = 0

阿里支付宝支付配置信息,支持跳转阿里支付控制台或者本页扫描支付,微信只有本页扫描支付。


 

// 电脑网站支付的「前置模式」:告诉网关"这一版页面我要嵌在自己页里",让它吐二维码区而不是

// 整页收银台。模式编号语义官方文档页取不到 ⇒ 以网关行为为准,mode 原样透传不校验,

// 并把实际出网的串回显在页面上——"发没发这个键"不该靠翻代码确认。

// /testalipayfront 默认档(mode=4 & width 由网关自己定)

// /testalipayfront?mode=0|1|3|4 四档对照

// /testalipayfront?mode=4&width=300 模式 4 的宽度

// /testalipayfront?mode=none 对照档:一个键都不发

//@urlpath(null,testalipayfront)

asio::awaitable<std::string> test_alipay_front(std::shared_ptr<httppeer> peer)

{

httppeer &client = peer->get_peer();

client.type("text/html; charset=UTF-8");

#ifdef ENABLE_WEBPAY

std::string mode = client.get["mode"].to_string();

std::string width = client.get["width"].to_string();

if (mode.empty()) mode = "4";

const bool front = (mode != "none");

if (!front) width.clear();

std::string out_trade_no = get_date("%Y%m%d%H%M%S") + rand_string(4, 4);

pay::alipay apay;

std::string cfgmsg;

if (!webpay::alipay_fill(apay, kPayTag, cfgmsg))

{

client.output = cfgmsg;

co_return "";

}

apay.setOutTradeNo(out_trade_no);

apay.setSubject("测试商品");

apay.setBody("测试商品描述");

apay.setTotalAmount("0.01");

if (front)

{

apay.setQrPayMode(mode);

if (!width.empty()) apay.setQrcodeWidth(width);

}

std::string pay_url = apay.createTradePage();

// buildRequest() 把出网那一串(值已 percent 编码)留在 last_request_body_ 里;

// qr_pay_mode 这类键名本身是 ASCII,编码前后长得一样,所以直接按字面找就是"发没发"的读数。

const std::string sent = apay.lastRequestBody();

const bool sent_mode = (sent.find("qr_pay_mode") != std::string::npos);

const bool sent_width = (sent.find("qrcode_width") != std::string::npos);

std::string sent_note = "前置模式档 " + html_encode(mode)

+ " qrcode_width " + html_encode(width.empty() ? "(不发)" : width)

+ "<br>出网串里含 <b>qr_pay_mode</b>:" + (sent_mode ? "是" : "<b>否</b>")

+ " 含 <b>qrcode_width</b>:" + (sent_width ? "是" : "否")

+ " 串长 " + std::to_string(sent.size()) + " 字节"

+ "<details><summary>出网那一串原文(gateway.do? 之后,含 sign)</summary><pre>"

+ html_encode(sent) + "</pre></details>";

std::string insrep;

unsigned int oid = co_await tal_order_insert_async(out_trade_no, "0.01", "测试商品", "测试商品描述", client.client_ip, &insrep);

// 高度按档给:前置模式那一版只有二维码区,780px 会剩一大片空白,看着像"没渲染出来";

// none 档装的是整页收银台,得留够高度。

std::string frame_style = "width:100%;height:" + std::string(front ? "520px" : "780px")

+ ";border:1px solid #bbb";

if (oid == 0)

{

// 闸门:库里没这一行 ⇒ 不内嵌收银台(那一格里钱付出去就没账可对)。

// 出网串读数照打:它是本地签名的结果,跟库连不连得上无关。

client.output = "<!DOCTYPE html><html><head><meta charset=\"utf-8\">"

"<title>支付宝前置模式:未下单</title></head><body>"

"<p><b>存根写入失败</b>:订单没写进 cms.orderlist,这一页没有内嵌收银台。</p>"

"<p style=\"font-size:13px\">" + sent_note + "</p>"

"<p style=\"font-size:12px\">" + insrep + "</p></body></html>";

co_return "";

}

client.output = tal_paypage_embed(pay_url, out_trade_no, oid, insrep, sent_note, frame_style);

#else

client.output = "{\"code\":\"-1\",\"msg\":\"ENABLE_WEBPAY not enabled\"}";

#endif

co_return "";

}

四个演示和简单订单管理再controller/src目录,订单管理文件在libs/webpay目录

controller/src/test_alipay.cpp

controller/src/test_wxpay.cpp

自己写业务代码时候可以按需剪裁移植。

这次狂添加了 MQTT 模块

方便物联网场景使用,其它也可以使用MQTT。

mqtt目录上服务器端程序 ,客户端在 controller/src目录 test_mqtt_client.cpp 文件

 

//@urlpath(null, test_mqtt_client)

asio::awaitable<std::string> test_mqtt_client(std::shared_ptr<httppeer> peer)

{

httppeer &cp = peer->get_peer();

mqtt_client_config_t cfg1;

cfg1.client_id = "mydevice@@@group-device";

cfg1.clean_start = true;

cfg1.keepalive = 60;

cfg1.receive_maximum = 65535;

auto r1 = co_await run_one_case("Case1_B4_no_local", cfg1);

mqtt_client_config_t cfg2 = cfg1;

cfg2.client_id = "mydevice@@@"; // reg_key=mydevice group=empty device=empty

auto r2 = co_await run_one_case("Case2_parse_client_id_reg_only", cfg2);

mqtt_client_config_t cfg3 = cfg1;

cfg3.client_id = "mydevice@@@rm-client";

cfg3.receive_maximum = 8; // less than server policy 16 → server still advertises its own policy 16 (no longer min with client value)

auto r3 = co_await run_one_case("Case3_S1_rm8", cfg3);

auto dump = [](const case_result &r) -> std::string

{

std::string s = std::string("=== ") + r.name + " ===\n";

s += " connected_=" + std::to_string((int)r.connected_) +

" rc=" + std::to_string(static_cast<int>(r.connack_rc)) +

" err=" + std::to_string((int)r.iserror);

if (!r.error_msg.empty())

s += " (" + r.error_msg + ")";

s += "\n session_present=" + std::to_string((int)r.session_present);

s += " server_receive_maximum=" + std::to_string(r.server_receive_maximum);

s += "\n run_loop_fun=" + std::to_string(r.recv.size());

if (!r.recv.empty())

for (auto &p : r.recv) s += " [" + p.topic + "]";

s += "\n received_window_empty=" + std::to_string((int)r.received_window_empty) +

" (hook installed, should be 1)";

s += "\n";

return s;

};

std::string summary = dump(r1) + dump(r2) + dump(r3);

summary += "\n=== Verification Summary ===\n";

summary += "B3 empty Client ID: " + std::string((int)r2.connack_rc == 0 ? "PASS" : "FAIL") +

" (rc=" + std::to_string(static_cast<int>(r2.connack_rc)) + ")\n";

summary += "S1 receive_max: " + std::string(r3.server_receive_maximum == 16 ? "PASS (server advertises its own policy 16)" : "FAIL") +

" (server_rm=" + std::to_string(r3.server_receive_maximum) + ", expect 16)\n";

summary += "B4 No Local: " + std::string(r1.recv.empty() ? "PASS" : "FAIL (loopbacks received)") +

" (" + std::to_string(r1.recv.size()) + " loopbacks)\n";

// All three cases install run_loop_fun and the "no message should land"

// semantics of no_local makes the empty window check toothless here.

// For real evidence see /test_mqtt_tick_push, which installs the same hook

// but actually receives 5 messages and checks received in the callback.

summary += "hook installed, no window writes: — (this group produces zero delivered messages, reading is meaningless; see /test_mqtt_tick_push)\n";

cp.output = make_html("MQTT 5 Client Test — B3 + S1 + B4", summary);

co_return "";

}


目前MQTT支持V5版本

http2流量控制目前已经是第四次重构了

ORM添加原生rawsql语句查询支持。

其它可以参考官方地址

https://github.com/hggq/paozhu/