简介这份代码包面向对移动支付技术感兴趣的开发者聚焦某宝支付SDK转H5及APP支付方法的实现可用于移动端支付集成、H5支付实现以及支付调试与测试等场景。资源共6个文件压缩包约11KB包含Python脚本、HTML页面、Markdown说明文档、txt依赖清单及gitignore等配置项其中Python脚本承载核心支付逻辑HTML页面用于H5支付效果验证文档则辅助理解整体结构。内容围绕支付SDK参数结构展开涉及alipay_sdk、app_id、biz_content、sign等参数的URL编码处理并讲解RSA加密与3DES算法加密的流程基于Flask框架实现参数解析、加密处理、支付链接生成与错误处理最终产出H5支付链接和原生APP跳转链接。目前已有439人学习适合作为研究支付流程与加密思路的参考代码帮助开发者理解移动支付环节的技术实现与调试方法。1. 某宝支付 SDK 转 H5 及 APP 支付从原生桥接到跨端收银台的那条暗线很多团队第一次接某宝支付 SDK 时都是在原生工程里跑通的——Android 拿AlipayClient拼orderInfoiOS 走AliPaySDK的payOrder回调里判断resultStatus是不是9000。这套流程在纯原生 App 里稳如老狗可一旦业务要往 H5 页面、跨端框架或者内嵌 WebView 里搬问题就来了H5 没有原生桥拿不到orderInfo的签名串调不起客户端支付结果也回不到页面。标题里说的「某宝支付 SDK 转 H5 及 APP 支付方法」本质就是解决这条链路的断裂——把原本只在原生层跑的支付能力拆成「服务端签名 前端唤起 结果回传」三段让 H5 和 APP 都能复用同一套订单逻辑。适合正在做多端收银台、或者被跨端框架支付回调折磨过的开发者。2. 拆解某宝支付 SDK 的调用链为什么 H5 不能直接复用原生代码2.1 原生 SDK 到底封装了什么原生某宝支付 SDK 的核心动作只有三步接收一个已经签好名的订单字符串orderInfo拉起某宝客户端把支付结果通过onActivityResult或URL Scheme回调给调用方。它不负责生成订单也不负责签名——签名必须在服务端用商户私钥完成客户端只做「传参 唤起 收结果」。这意味着 H5 想调起支付缺的不是 SDK 本身而是「怎么把服务端签好的orderInfo递到某宝客户端手里」。常见做法是H5 页面从服务端拿到orderInfo后通过location.href跳转到一个特定 scheme或者用 form 表单提交到某宝的网关地址。APP 内嵌 WebView 的场景更麻烦因为 WebView 默认拦截非 http/https 的 scheme 跳转需要原生层做shouldOverrideUrlLoading的白名单放行。2.2 H5 支付与 APP 支付的边界在哪H5 支付也就是常说的 wap 支付和 APP 支付最大的区别在于唤起方式。H5 支付走的是某宝的网页收银台用户在浏览器里完成登录和付款回跳地址由商户在服务端下单时指定APP 支付走的是客户端 SDK需要原生代码参与。两者共用同一套服务端签名逻辑但前端拿到的orderInfo格式和唤起方式不同。对比项H5 支付APP 支付唤起方式跳转网关 URL 或 scheme原生 SDK 方法签名位置服务端服务端结果回传同步跳转 异步通知客户端回调 异步通知依赖原生否是适用场景浏览器、内嵌 WebView原生 App、跨端框架注意无论 H5 还是 APP异步通知notify_url才是最终可信的支付结果前端回调只用来做页面跳转不能作为发货依据。2.3 跨端框架里的支付适配层怎么设计用跨端框架做 APP 时支付通常要写一个原生插件或模块暴露一个pay(orderInfo)方法给 JS 层调用。JS 层负责从服务端拉订单原生层负责调 SDK 并回传结果。这个适配层的接口设计要统一H5 和 APP 共用同一套订单参数只是唤起实现不同。我一般会定义一个PaymentRequest结构包含orderInfo、scheme、callbackId原生层根据scheme决定走 H5 还是 APP 通道。3. 服务端签名与订单生成H5 和 APP 共用的那一段3.1 签名参数怎么拼才不会被拒某宝支付的签名逻辑是把所有请求参数按 key 字典序排序用拼接成keyvalue形式再用商户私钥做 RSA2 签名。参数里不能包含sign本身空值参数不参与签名。常见翻车点是参数编码——orderInfo里的和必须正确 URL 编码否则某宝网关解析时会截断。# 服务端生成 orderInfo 的核心逻辑Python 示例 import base64 from urllib.parse import quote_plus from Crypto.PublicKey import RSA from Crypto.Signature import pkcs1_15 from Crypto.Hash import SHA256 def build_order_info(params: dict, private_key_str: str) - str: # 1. 过滤空值并按 key 排序 filtered {k: v for k, v in params.items() if v not in (None, )} sorted_keys sorted(filtered.keys()) # 2. 拼接待签名字符串 unsigned .join(f{k}{filtered[k]} for k in sorted_keys) # 3. RSA2 签名 private_key RSA.import_key(private_key_str) digest SHA256.new(unsigned.encode(utf-8)) signature pkcs1_15.new(private_key).sign(digest) sign_b64 base64.b64encode(signature).decode() # 4. 把 sign 拼回参数并 URL 编码 filtered[sign] sign_b64 order_info .join(f{k}{quote_plus(str(filtered[k]))} for k in sorted(filtered.keys())) return order_info这段代码的关键点有三个filtered去掉了空值避免签名串里出现key这种空参数sorted_keys保证字典序某宝网关校验时顺序不对会直接报签名错误最后quote_plus对每个 value 做 URL 编码尤其是sign里的和/不编码会在传输中被转义。参数说明params至少包含app_id、method、charset、sign_type、timestamp、version、notify_url、biz_content其中biz_content是 JSON 字符串里面放out_trade_no、total_amount、subject等业务字段。3.2 H5 唤起链接的拼装与回跳控制H5 支付的唤起方式有两种一种是直接跳转某宝网关的https://openapi.alipay.com/gateway.do加上参数另一种是拼一个alipays://开头的 scheme。前者在手机浏览器里会自动唤起某宝客户端或网页收银台后者更适合内嵌 WebView 里由原生层拦截。回跳地址通过return_url指定用户支付完成后会跳回这个地址但要注意return_url只代表用户操作完成不代表支付成功。// H5 页面发起支付的简化逻辑 function requestPayment(orderInfo) { // orderInfo 由服务端接口返回已包含 sign const gateway https://openapi.alipay.com/gateway.do; const url ${gateway}?${orderInfo}; // 方式一直接跳转 window.location.href url; // 方式二内嵌 WebView 里通知原生层拦截 if (window.webkit window.webkit.messageHandlers window.webkit.messageHandlers.pay) { window.webkit.messageHandlers.pay.postMessage({ orderInfo, scheme: alipays }); } }逻辑说明orderInfo已经是服务端拼好的完整查询串前端只负责拼接网关地址并跳转。如果是内嵌 WebView优先走原生桥因为部分 WebView 会拦截alipays://导致页面白屏。参数说明window.webkit.messageHandlers.pay是 iOS WKWebView 的原生桥Android 侧对应addJavascriptInterface注入的对象方法名要两端对齐。3.3 异步通知的验签与幂等处理异步通知是某宝服务器主动 POST 到notify_url的内容包含trade_status、out_trade_no、trade_no等。收到通知后必须先验签再判断trade_status是否为TRADE_SUCCESS或TRADE_FINISHED最后做幂等——同一个out_trade_no可能收到多次通知数据库里要有唯一索引或状态机来防止重复发货。# 异步通知验签与幂等处理骨架 def handle_notify(request): params request.POST.dict() sign params.pop(sign, None) sign_type params.pop(sign_type, None) # 1. 验签 if not verify_sign(params, sign, ALIPAY_PUBLIC_KEY): return failure # 2. 判断交易状态 if params.get(trade_status) not in (TRADE_SUCCESS, TRADE_FINISHED): return success # 非成功状态也返回 success避免重复通知 # 3. 幂等用 out_trade_no 做唯一键 order Order.query.filter_by(out_trade_noparams[out_trade_no]).first() if order and order.status paid: return success # 4. 更新订单并触发发货 mark_order_paid(params[out_trade_no], params[trade_no]) return success参数说明verify_sign用某宝公钥对去掉sign和sign_type后的参数做验签trade_status只有两个值代表可发货返回字符串success告诉某宝不要再重发通知。幂等这一步是血泪经验早期没做唯一索引结果用户付一次款发了两次货。4. APP 支付与 H5 支付的桥接WebView 里怎么把支付串递出去4.1 Android 侧拦截 scheme 的正确姿势Android WebView 里H5 跳转alipays://时默认会报ERR_UNKNOWN_URL_SCHEME。需要在shouldOverrideUrlLoading里判断 scheme如果是alipays就手动构造 Intent 拉起某宝客户端。注意 Android 11 以上要在AndroidManifest.xml里声明queries包可见性否则resolveActivity返回 null。// Android WebView 拦截 alipays scheme webView.setWebViewClient(new WebViewClient() { Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { String url request.getUrl().toString(); if (url.startsWith(alipays://) || url.startsWith(alipayqr://)) { try { Intent intent new Intent(Intent.ACTION_VIEW, Uri.parse(url)); view.getContext().startActivity(intent); return true; // 拦截不让 WebView 自己处理 } catch (ActivityNotFoundException e) { // 某宝客户端未安装降级到 H5 收银台 view.loadUrl(https://openapi.alipay.com/gateway.do? buildH5OrderInfo()); return true; } } return false; } });逻辑说明shouldOverrideUrlLoading返回true表示自己处理返回false交给 WebView。ActivityNotFoundException是必须捕获的用户没装某宝客户端时要有降级方案。参数说明alipayqr://是扫码支付的 scheme一并放行可以覆盖更多场景。4.2 iOS 侧 WKWebView 的白名单与回调iOS 的 WKWebView 不会自动拦截 scheme需要在decidePolicyForNavigationAction里判断或者用WKNavigationDelegate的decidePolicyFor方法。iOS 9 以后还要在Info.plist里把alipays加入LSApplicationQueriesSchemes否则canOpenURL永远返回 false。// iOS WKWebView 拦截 alipays scheme func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: escaping (WKNavigationActionPolicy) - Void) { guard let url navigationAction.request.url else { decisionHandler(.allow) return } if url.scheme alipays || url.scheme alipayqr { UIApplication.shared.open(url, options: [:]) { success in if !success { // 唤起失败降级到 H5 webView.load(URLRequest(url: URL(string: https://openapi.alipay.com/gateway.do?\(self.h5OrderInfo))!)) } } decisionHandler(.cancel) return } decisionHandler(.allow) }逻辑说明decisionHandler(.cancel)阻止 WebView 自己加载 schemeUIApplication.shared.open负责唤起客户端。success为 false 时降级到 H5 收银台。参数说明LSApplicationQueriesSchemes里要加alipays和alipayqr否则open会直接失败。4.3 支付结果怎么从原生回传到 H5原生唤起某宝客户端后支付结果通过onActivityResultAndroid或application(_:open:options:)iOS回到原生层。原生层拿到resultStatus后通过evaluateJavascript或WKScriptMessageHandler把结果回传给 H5。H5 收到结果后不要直接发货而是轮询服务端订单状态或等待异步通知。// H5 侧接收原生回传的支付结果 window.onNativePayResult function(result) { // result: { resultStatus: 9000, memo: , result: } if (result.resultStatus 9000) { // 支付成功轮询服务端确认 pollOrderStatus(orderId); } else if (result.resultStatus 6001) { // 用户取消 showToast(支付已取消); } else { showToast(支付失败请重试); } };逻辑说明resultStatus为9000只代表客户端支付成功最终状态以服务端异步通知为准。pollOrderStatus每隔 2 秒查一次订单接口最多查 5 次。参数说明6001是用户主动取消8000是正在处理中4000是系统异常这些状态码要区分处理不能统一提示失败。5. 避坑与排查支付串递出去却没反应的那些原因5.1 现象H5 跳转后白屏控制台报 ERR_UNKNOWN_URL_SCHEME原因WebView 没有拦截alipays://浏览器内核不认识这个 scheme。解决在shouldOverrideUrlLoading里加白名单判断手动构造 Intent 拉起。如果是 iOS检查Info.plist的LSApplicationQueriesSchemes是否包含alipays。5.2 现象APP 支付回调 resultStatus 一直是 6001原因orderInfo里的app_id和客户端绑定的商户不一致或者签名用的私钥和某宝后台配置的公钥不匹配。解决先用某宝提供的签名校验工具验证orderInfo确认app_id、pid、sign三者对应同一套商户信息。另一个常见原因是orderInfo被 URL 编码了两次客户端解析时拿到的是编码后的字符串。5.3 现象异步通知收不到或者收到后验签失败原因notify_url必须是公网可访问的地址不能带内网 IP 或 localhost。验签失败通常是参数里混入了sign和sign_type之外的多余字段或者某宝公钥配置错了。解决在验签前把sign和sign_type从参数字典里剔除用某宝后台下载的公钥文件而不是应用公钥。5.4 现象H5 支付完成后 return_url 没跳回来原因return_url里带了查询参数某宝回跳时把参数拼在了?后面导致商户自己的参数被截断。解决return_url尽量只写路径参数通过服务端 session 或本地存储传递。另外部分浏览器在唤起客户端后不会回到原页面需要引导用户手动切回。5.5 现象跨端框架里 JS 调不到原生支付模块原因原生模块没有注册到框架的桥接层或者方法名大小写不一致。解决检查框架的原生插件注册文件确认pay方法已经暴露。Android 侧注意JavascriptInterface注解不能漏iOS 侧注意WKScriptMessageHandler的add时机要在页面加载前。6. 进阶把支付串做成可复用的收银台中间层走到这里H5 和 APP 的支付链路基本能跑通了。但真正让团队少踩坑的做法是把「订单生成 签名 唤起 结果回传」抽成一个独立的收银台中间层。服务端只暴露一个/api/pay/create接口入参是业务订单号和金额出参是orderInfo和payType前端根据payType决定走 H5 跳转还是原生桥。这样新增一个端时只需要实现唤起和回传两个方法签名和订单逻辑完全复用。我一般会在中间层里加一个「支付结果查询」接口前端在收到原生回调后调一次服务端返回订单的真实状态。这个接口内部先查本地订单表如果状态未更新再主动调某宝的订单查询接口避免异步通知延迟导致用户等太久。查询接口要做频率限制同一个订单号 5 秒内只允许查一次防止前端轮询打爆服务端。验证方法上我习惯用某宝沙箱环境先跑通全链路沙箱的app_id和密钥是独立的不会影响生产。沙箱里可以模拟支付成功、支付失败、用户取消三种状态把前端的分支逻辑都覆盖一遍。上线前再把app_id和密钥换成生产的notify_url和return_url也要同步改。最后一个习惯每次接完支付我都会在日志里把out_trade_no、trade_no、resultStatus、trade_status四个字段打一条结构化日志。出问题时直接按out_trade_no搜从下单到回调的完整链路一目了然。这个习惯帮我省了无数次对账的功夫希望帮到你。本文还有配套的精品资源点击获取