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/