【转】支付宝WAP支付接口开发

这篇具有很好参考价值的文章主要介绍了【转】支付宝WAP支付接口开发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

支付宝WAP支付接口开发

因项目需要,要增加支付宝手机网站支付功能,找了支付宝的样例代码和接口说明,折腾两天搞定,谨以此文作为这两天摸索的总结。由于公司有自己的支付接口,并不直接使用这个接口,所以晚些时候打算把测试代码整理好放到Github上。

1. 开发前准备

  1. 到官网了解此接口的信息,下载样例代码(只有ASP.NET和PHP)以便随时参考。
  2. 一个通过实名认证的企业支付宝账号,并申请开通手机WAP支付功能,我的测试账号是拿公司的,申请流程不清楚,官网有说怎么申请,各位各显神通吧。
  3. 公网域名和node.js环境。下面的代码大多用coffee来表达,不过本文不会贴太多代码,即使对coffee不熟悉也没什么关系。关于coffee可以参考这里。

github上有两个开源小项目(搜索 alipay ),但都没有WAP支付功能,可以拿来当参考,可以认为是示例代码的js移植版,结构很相像。我原打算在其中一个项目基础上继续开发,看了代码和接口文档后,还是决定从头开发一个。因为原有代码层次不够清晰,有点过度设计的感觉,而且支付宝的接口很简单,重写工作量不大。

吐槽下: 官网的示例代码真只是示例级(test)而已,跟产品级(production)还隔比较远,感觉还谈不上SDK。接口文档相当的坑爹,正因如此我才觉得有必要好好写篇文章总结。

2. 流程

接口开发最重要的应该是理解数据交互流程了,流程弄清了,并理解为何这么设计,开发起来也是事半功倍

首先,要准备下面几个参数:

  1. 企业支付宝账号的PID(也叫ParnerID)和KEY,如果使用RSA签名而不是MD5的话,还要把RSA私钥准备好
  2. 支付时用户看到的东西:商品名称(subject)、支付总额(total_fee)、购买数量(通常都是1吧)
  3. 交易后的跳转地址,交易成功后用户可以手工点击,或页面延迟自动跳转到这个地址(return_url)
  4. 交易状态异步通知地址,交易成功或交易关闭会把消息POST到这个地址(notify_url)

然后,看这幅流程图(不错吧,推荐下这个网站:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Alipay WAP pay flow

Browzer->+Site: 1. HTTP GET                
note over Site: 2. create a new trade      
Site->+Alipay: 3. create redirect          
Alipay->-Site: 4. Token                    
note over Site: 5. build auth url          
Site->-Browzer: 6. redirect to auth url    
Browzer->Alipay: 7. redirect               
Alipay->Browzer: 8. trade info             
Browzer->Alipay: 9. auth and pay           
Alipay->+Site: 10. HTTP POST notify        
note over Site: 11. process trade          
Site->-Alipay: 12. reply "success"         
Alipay->Browzer: 13. pay success           
Browzer->Site: 14. goto return url

这个流程图基本囊括了整个交互过程,下面是说明:

  1. 用户点击购买按钮(或其他形式),向网站发起购买请求
  2. 网站创建订单,指派一个唯一订单号
  3. 网站把订单号、企业支付宝账号、交易金额、数量等信息,用私钥签名发送给支付宝
  4. 支付宝创建一个交易订单,返回一个交易令牌(token)
  5. 网站按照指定要求,用token和自己的私钥,构造一个重定向得到支付地址
  6. 网站把重定向地址返回给浏览器
  7. 浏览器自动重定向到该地址,即包含了token、网站签名的支付宝交易页面
  8. 支付宝显示当前交易金额、数量、卖家等信息
  9. 用户用自己的支付宝账号支付这笔金额
  10. 支付宝把用户支付成功(或失败)这个消息和订单号加上支付宝的签名,使用HTTP POST的方式通知网站(失败的话,会隔段时间重新发送)
  11. 网站处理交易后续逻辑(发货、订单状态存储之类的)
  12. 网站返回"success"字符串给支付宝,表示该通知已经处理,不用再重发
  13. 支付宝显示支付成功页面给用户(这一步和第10步是不分先后发生的)
  14. 支付成功页面延迟自动跳转,或用户点击“返回商户页面”,跳转到网站的支付结束页面(此时不一定成功处理支付宝发来的通知),但会在URL带上当前的订单号和状态。

可以发现,整个流程有点像OAuth(哎呀,之前那篇文章还没写完呢!),主要分三步:

一是申请支付宝交易号(获取token),这一步可以理解为,让支付宝验证网站的有效性、让网站指定该交易要支付多少钱 二是用户到支付宝页面付款,这一步可以理解为,让支付宝验证用户有效性,让用户在一个不受网站监视的环境下进行支付 三是用户付款后,处理结果页面告诉用户支付成功(同步通知),另外异步通知网站服务器该订单已支付。

支付宝的接口文档说只有两个步骤,感觉不是很好理解,三步还是比较准确的(收钱肯定要办事的嘛)。

好困,细节问题下期继续。。。


2013-07-24

3. 细节

3.1 网站向支付宝申请新订单

网站的订单系统先产生一个新订单,然后请求支付宝创建一个支付宝订单.

申请新订单的service是 alipay.wap.trade.create.direct 需要提交的关键参数包括:

3.1.1 用户在支付宝看到的订单信息:

subject: 商品名称

total_fee: 总金额

seller_account_name: 卖家支付宝账号(估计跟私钥绑定的)

merchant_url: 商品展示URL(似乎这个并非必要)

3.1.2 支付宝通知网站时将附带的信息:

out_trade_no: 该次交易对应网站的订单号(要求唯一)

call_back_url: 交易成功后,支付宝页面上“返回到商家页面”的地址(同步回调)

notify_url: 交易状态变更后,支付宝通知网站的回调地址(异步通知)

支付宝验证通过后,将返回新创建的支付宝订单号,网站可将该订单号与自己订单系统的的订单号绑定在一起。支付宝同时返回的还有该次交易的token,用于(3.2)用户支付。

3.2 用户在支付宝网站,查看订单消息,通过验证并支付

网站返回跳转到支付宝的地址,service是alipay.wap.auth.authAndExecute,包含(3.1)返回的token和网站对跳转地址的签名

这是个HTTPS页面,基本认为是安全的。当然前提是浏览器没被动手脚,安卓不少应用被捆绑广告那是常有的事,手机浏览器对HTTPS也不像PC那样有明显提示,这些也是我不怎么信任手机支付的原因。

用户跳转到支付宝页面后,可以在该页面里看到当前支付的订单的名称和金额,这些是3.1申请时由网站指定的,让用户在支付宝的页面确认一次再付款是合理的。

3.3 支付宝通知网站支付成功,网站收钱做事

这个过程是支付宝通知网站,网站处理后通知用户已到账,共包括两个并行部分:

3.3.1 异步通知

用户支付后,支付宝通过HTTP协议通知网站该订单交易结果。说白了就是支付宝悄悄地告诉网站“这个订单已经已经付款啦”

值得注意的是,异步通知有重发机制,支付宝需要得到响应为"success"才认为该通知成功被接收,否则会间隔一段时间重发,依次间隔2m,10m,10m,1h,2h,6h,15h,最多8次通知,由notify_id说明是同一个通知。8次通知都接收失败怎么办?额orz...文档没说,用那个支付宝订单号登录支付宝去查账吧。

3.3.2 同步通知

用户支付后,支付宝页面提示“支付成功”,可点击返回商家页面,也可等待一段时间自动跳回

个人认为,网站跳到这个页面后,如果仍未收到(3.3.1)异步通知,并且使用的是MD5签名,应该把状态从“待付款”调整为“等待对账”,而不应该贸然相信该通知的结果。原因是这个回调地址用户是可以知道的,MD5签名还是有被伪造的可能(4.3)。当然额外再做个token之类的理论上也行(需要放在urlpath而不是querystring)。

假如接口调用出错,通知是不会签名的。不签名的原因我怀疑是防止有人恶意收集请求-签名样本,见(4.3)。

4. 签名与加密

简单的说,签名防篡改,加密防窃听。上面的两种请求(3.1和3.2)和两个通知(3.3.1和3.3.2)都被要加签名,支付宝支持下面两类签名:

4.1 MD5: 业务数据不加密,防篡改

  • 优点: 相对较简单(当然是相对DSA/RSA来说),计算速度快,明文更直观

  • 缺点: 可抵赖,可能被窃听、安全性不如非对称加密

4.2 DSA/RSA: 业务数据加密,也防篡改

  • 优点:不可抵赖,安全性较高

  • 缺点:相对较复杂,解密速度慢

一开始我想不懂,支付宝既然支持RSA为何还要支持MD5,后来有人说RSA太慢,想想支付宝的业务量就释然了。由于每个商家的私钥都不同,并且跟商家的支付宝账号绑定,即使商家的私钥被破解了,用户支付时HTTPS协议基本可以保证用户支付的目标还是商家的账号。

4.3 使用MD5签名可能存在的风险

以下情景仅是我的推断,没有尝试过,所谓道高一尺魔高一丈,希望读者也别以身犯险。

在用户支付的步骤(3.2)和支付成功响应页面(3.3),用户可以得到一个明文请求内容和对应签名。由于网站和支付宝直接通信共用同一个密钥,一般长期不变,双方都可以对同一段数据产生签名,这就有可能抵赖的风险:

网站:“这个数据是你发过来的,上面有你的签名。”

支付宝:“不是我发的。这个数据是你伪造的,签名是你签的。”

另外,当攻击者收集到足够多的样本,是有可能破解出密钥的,继而可伪造网站或支付宝任意一方。

4.3.1 恶意消耗商家的订单号

攻击者伪造大量未使用的订单号(不少网站的订单号都是递增的纯数字,并公开给用户,且很容易推测到后面的数字),向支付宝请求订单,直到超时。由于商家对此并不知情(回调地址和通知地址均篡改掉),其他用户下单时假如商家用了被伪造过的订单号,就可能被支付宝认为提交了重复订单,结果支付失败。

4.3.2 欺骗商家已支付订单

由同步通知(3.3.2)返回的参数可以看到,网站订单标识和交易token和都是可以得到的。这样的话,关于步骤(3.1),用户不知道的参数包括notify_url和out_user_no,假如网站的用户id本身就是公开的,通知回调地址(3.3.1)被得知或同步通知(3.3.2)实现的不好,就可以通过伪造支付通知,欺骗商家订单已支付。

待续,下期补充实现代码


2013-07-28

5. 代码

5.1 签名

我只做了MD5签名,项目里没用到RSA签名,就没做那方面。按照文档说明和demo源代码,很容易就可以写出下面的签名代码:

1
2
3
4
5
6
7
getSign = (obj,key) ->
	return null unless obj
	arr = ([k,v] for k,v of obj when k isnt 'sign' and v? and v isnt '')
	arr.sort()
	src = ("#{i[0]}=#{i[1]}" for i in arr).join '&'
	src = "#{src}#{key}"
	crypto.createHash('md5').update(src,'utf8').digest 'hex'

支付宝发到网站的通知(3.3)的签名算法跟上面有点不一样,文档有这么段说明:

这里说要按通知的参数的原本顺序计算签名。所以我就把上面的arr.sort()去掉然后计算签名,结果发通知发来的签名和我自己计算的不一致,纠结半天后仔细看文档的样例说明,看到下面段:

仔细跟实际接收的数据比较之后发现,文档和样例都说发来的参数顺序是(service,v,sec_id,notify_data),但我实际收到的并不是按这个顺序,只要按照文档的参数顺序重新排列再计算签名就正确了,最终通知的签名算法如下( 真是个蛋疼的大坑orz):

1
2
3
4
5
getNotitySign = (obj,key) ->
	return null unless obj
	src = ("#{k}=#{obj[k]}" for k in ["service","v","sec_id","notify_data"]).join '&'
	src = "#{src}#{key}"
	crypto.createHash('md5').update(src,'utf8').digest 'hex'
  • 文档里有说到字符编码参数_input_charset,我用的是utf8编码,发现不用传这个参数也可以,看来支付宝默认的字符编码就是utf8了

  • 如果使用RSA签名,需要先解密再计算签名

5.2 辅助方法

为了代码层次更清晰,我把签名、url拼接等方法抽出到一个单独模块(alipay_api.coffee):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
api_url = "http://wappaygw.alipay.com/service/rest.htm"
regexTokenXml = /<request_token>(.*)<\/request_token>/

module.exports = api = 

	services:
		create: "alipay.wap.trade.create.direct"
		auth: "alipay.wap.auth.authAndExecute"

	toReqData: (name,obj) ->
		arr = ["<#{name}>"]
		arr.push "<#{k}>#{v}</#{k}>" for k,v of obj
		arr.push "</#{name}>"
		arr.join ''

	createReq: (service,partner,req_data) -> 
		service : service
		format  : 'xml'
		v       : '2.0'
		partner : partner
		sec_id  : 'MD5'
		sign    : null
		req_data: req_data

	parseTokenFromXml: (xml) ->
		return null unless xml
		m = regexTokenXml.exec xml
		m?[1]?.trim()

	getSign: (obj,key='') ->
		return null unless obj
		arr = ([k,v] for k,v of obj when k isnt 'sign' and v? and v isnt '')
		arr.sort()
		src = ("#{i[0]}=#{i[1]}" for i in arr).join '&'
		src = "#{src}#{key}"
		crypto.createHash('md5').update(src,'utf8').digest 'hex'

	getNotitySign: (obj,key='') ->
		return null unless obj
		src = ("#{k}=#{obj[k]}" for k in ["service","v","sec_id","notify_data"]).join '&'
		src = "#{src}#{key}"
		crypto.createHash('md5').update(src,'utf8').digest 'hex'

	sendCreate: (req,done) ->
		opt =
			url: createCreateUrl req
		request.get opt, (err,res,body) ->
			return done err if err
			body = "" unless body
			ret = querystring.parse body
			body = null
			done null,ret

	createAuthUrl: (token='',key='') ->
		req = api.createReq api.services.auth
		req.req_data = "<auth_and_execute_req><request_token>#{token}</request_token></auth_and_execute_req>"
		req.sign = api.getSign req, yes
		createAuthUrl req

createCreateUrl = (req) ->
	url = "#{api_url}?"
	url += "req_data=#{encodeURIComponent req.req_data}"
	url += "&service=#{encodeURIComponent req.service}"
	url += "&sec_id=#{encodeURIComponent req.sec_id}"
	url += "&partner=#{encodeURIComponent req.partner}"
	url += "&req_id=#{encodeURIComponent req.req_id}"
	url += "&sign=#{encodeURIComponent req.sign}"
	url += "&format=#{encodeURIComponent req.format}"
	url += "&v=#{encodeURIComponent req.v}"
	url

createAuthUrl = (req) ->
	url = "#{api_url}?"
	url += "req_data=#{encodeURIComponent req.req_data}"
	url += "&service=#{encodeURIComponent req.service}"
	url += "&sec_id=#{encodeURIComponent req.sec_id}"
	url += "&partner=#{encodeURIComponent req.partner}"
	url += "&sign=#{encodeURIComponent req.sign}"
	url += "&format=#{encodeURIComponent req.format}"
	url += "&v=#{encodeURIComponent req.v}"
	url

5.3 业务部分

5.3.1 购买(buy)

购买的逻辑对应于(2)流程图的(2,3,4,5),创建唯一请求ID,填充本次交易信息,发送到支付宝并获取token,然后拼接支付url并签名,然后重定向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
demo.buy = (info,done) ->
	return done 'bad user' unless info?.user_id?.length>10
	req = api.createReq api.services.create, info.partner
	ret = 
		redirect: ''
		token: null
	async.series [
		(cb) ->
			getRequestId req.service,(err,req_id) ->
				req.req_id = req_id
				cb err
		(cb) ->
			createTrade info,req.req_id,(err, tradeId) ->
				return cb err if err 
				req.req_data = 
					subject            : info.subject  # 商品名称
					out_trade_no       : tradeId.toString() # 网站订单号
					total_fee          : info.total_fee  # 价钱(number),单位元,例如 0.01 代表1分钱
					seller_account_name: info.seller_account_name # 支付宝账号
					call_back_url      : info.call_back_url # 支付成功后浏览器跳转地址
					notify_url         : info.notify_url # 支付成功支付宝的通知将异步发送到此地址
					out_user           : info.user_id # 网站的用户标识
					merchant_url       : info.merchant_url # 商品展示页面, 只是实际测试时(ios)发现支付时没地方可以跳到这个页面
				req.pay_expire = info.pay_expire if info.pay_expire? # 支付过期时间
				req.req_data = api.toReqData 'direct_trade_create_req',req.req_data
				req.sign = api.getSign req, info.key
				cb null
		(cb) ->
			api.sendCreate req, (err,res) ->
				return cb err if err 
				return cb 'bad sign from alipay server' unless req.sign is api.getSign req
				ret.token = api.parseTokenFromXml res.res_data
				ret.redirect = api.createAuthUrl ret.token
				storeTradeInfo req.out_trade_no, req.total_fee, ret.token, (err,success) ->
					return cb err if err
					cb if success then null else 'store trade info fail'
	],(err) ->
		done err, ret.redirect

其中用到的几个方法跟存储相关,我用的是MySQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
getRequestId = (service, done) ->
	sql = "insert into alipay_requests(service,create_time,state) values(?,now(),'CREATE')" 
	db.queryAll sql,[service],(err,result) ->
		done err, result?.insertId

createTrade = (info,req_id,callback) ->
	sql = "insert into alipay_trades(user,req_id,create_time) values(?,?,now())" 
	db.queryAll sql,[info.user_id,req_id],(err,result) ->
		callback err, result?.insertId
		
storeTradeInfo = (tradeId,rmb,token,callback) ->
	sql = "update alipay_trades set rmb=?,token=?,state='WAIT_PAY' where id = ?"
	args = [rmb,token,tradeId]
	db.queryAll sql,args,(err,updateResult) ->
		callback err,updateResult?.affectedRows is 1

5.3.2 通知(notify)

用户支付后就等着通知了,按道理应该在TRADE_SUCCESS时处理用户支付成功的逻辑,但我实际测试发现至发送了TRADE_FINISHED事件来,所以干脆两个一并处理了,反正只会有一次成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
xmlreader = require 'xmlreader'rr,updateResult?.affectedRows is 1
demo.onNotify = (req,callback) ->
	xmlreader.read req.notify_data,(err,xdoc) ->
		return done err if err
		notify = xdoc.notify
		notify_id = notify?.notify_id?.text()
		return done 'bad notify_data' unless notify_id
		done = (response) ->
			unless 'string' is typeof response
				console.error "response notify error: " + (response?.stack ? response ? '')
				response = 'server error'
			console.error "response notify: #{response}" unless response is 'success'
			storeNotifyResponse notify_id,response, (err) ->
				callback if response is 'success' then err else response
		storeNotifyDetails notify_id, notify, req, (err,success) ->
			return done err if err
			unless success
				return done "store detail error"
			trade_status = notify.trade_status.text()
			if trade_status is 'TRADE_FINISHED' or trade_status is 'TRADE_SUCCESS'
				unless req.sign is api.getNotitySign req
					return done 'bad sign'
				out_trade_no = notify.out_trade_no.text()
				getTradeUser out_trade_no,(err,user) ->
					return done err if err
					user_id = user?.user
					return done 'unknown user' unless user_id
					onPayed user_id,(err,success) ->
						return done err if err
						return done "onPayed error" unless success
						storeTradeFinalState out_trade_no, no, console.error
						done 'success'
			else # TRADE_PENDING, TRADE_CLOSED, WAIT_BUYER_PAY, etc 
				return done 'unknown trade status'

跟存储相关的几个方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
storeTradeFinalState = (tradeId,isError,callback) ->
	state = if isError then 'FAILURE' else 'SUCCESS'
	sql = "update alipay_trades set state=?,close_time=now() where id = ?"
	args = [state,tradeId]
	db.queryAll sql,args,(err,updateResult) ->
		callback err,updateResult?.affectedRows is 1

getTradeUser = (out_trade_no,callback) ->
	sql = "select id,user from alipay_trades where id=?"
	db.queryOne sql,[out_trade_no],callback

onPayed = (user_id,callback) ->
	sql = "update users set vip=1 where id=? and vip=0 limit 1"
	db.queryAll sql,[user_id],(err,updateResult) ->
		callback err, updateResult?.affectedRows is 1

storeNotifyDetails = (notify_id,notify,raw,callback) ->
	sql = "insert ignore into alipay_notifies(id,recv_time,subject,trade_no,gmt_create,
 		quantity,out_trade_no,notify_time,total_fee,buyer_email,trade_status, 
  		gmt_payment,gmt_close,raw) values(?,now(),?,?,?,?,?,?,?,?,?,?,?,? )"
	args = [
		notify_id,
		notify.subject?.text()
		notify.trade_no?.text()
		notify.gmt_create?.text()
		notify.quantity?.text()
		notify.out_trade_no?.text()
		notify.notify_time?.text()
		notify.total_fee?.text()
		notify.buyer_email?.text()
		notify.trade_status?.text()
		notify.gmt_payment?.text()
		notify.gmt_close?.text()
		JSON.stringify raw
	]
	db.queryAll sql,args,(err,updateResult) ->
		callback err,updateResult?.affectedRows is 1 

storeNotifyResponse = (notify_id,response,done) ->
	sql = "update alipay_notifies set response=? where id=?"
	db.queryAll sql,[response,notify_id],done

6. 后记

有些公司有自己的支付平台,封装了一层,结果调用流程变成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Alipay WAP using platform pay flow

Browzer->+Site: 1. HTTP GET
note over Site: 2. create a new trade
Site->-Browzer: 3. trade info
Browzer->+Platform: 4. redirect
note over Platform: 5. create a new trade
Platform->+Alipay: 6. create redirect
Alipay->-Platform: 7. Token
note over Platform: 8. build auth url
Platform->-Browzer: 9. redirect to auth url
Browzer->Alipay: 10. redirect
Alipay->Browzer: 11. trade info
Browzer->Alipay: 12. auth and pay
Alipay->+Platform: 13. HTTP POST notify
Platform->Site: 14. proxy
note over Site: 15. process trade
Site->Platform: 16. reply "success"
Platform->-Alipay: 17. reply "success"
Alipay->Browzer: 18. pay success
Browzer->Site: 19. goto return url

咋一看,挺方便的,Site只需要做个跳转即可,细节都被Platform隐藏起来了,切换不同的支付方式也变得很方便。

可是,这个平台的接口并不完善,这些数据都是明文并且没有要求Site做签名,于是就有一个风险。跟(2)的流程图对比,可以发现这个流程多了(3,4,14,16)四个步骤。后面两步只是个代理包装,没什么问题,问题在于步骤3和4。

可以看到,交易细节被放到了重定向url中了,即用户可以得知交易内容并篡改里面的数据。举个例子,假设重定向地址类似这样

http://payment.mysite.com/api/pay?tradeno=10000&rmb=100.00&siteid=1

由于该地址没有验签机制,所以攻击者很容易就可以发现这里面可以随意篡改数据。

  • 方法1: 将rmb=100.00改成0.01,即将100元的支付变成1分钱,然后支付。结果Site得到通知的时候,就需要额外处理这种支付款项和要求款项不一致的情况。当然最简单就是金额不足就不退款并且支付失败了。假如没做这种判断,那就相当把100元的商品以1分钱卖出去了。

  • 方法2: 伪造大量tradeno,然后请求,并且不支付款项。由于tradeno要求唯一,并只能使用一次,这样就相当于消耗掉了site的交易ID,并且site对此毫不知情。结果正常用户要购买时,创建的订单号对于site来说是未使用的,但对platform来说已经是使用过了,会返回支付失败。

要避免这个问题,需要在重定向的地址增加签名,签名异常的请求都抛弃掉。文章来源地址https://www.toymoban.com/news/detail-711199.html

到了这里,关于【转】支付宝WAP支付接口开发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Servlet+JDBC实战开发书店项目讲解第七篇:模拟支付

    在本篇文章中,我们将使用Servlet和JDBC来完成书店项目中的模拟支付功能。我们将介绍实现思路、后端代码设计以及前端代码设计,并通过代码实现订单支付成功,并实现查看未付款订单和完成订单支付等操作。 为了模拟支付功能,我们需要在书店项目中添加支付的相关逻辑

    2024年02月12日
    浏览(40)
  • FineBI实战项目一(20):不同支付方式订单笔数分析开发

    点击新建组件,创建不同支付方式订单笔数组件。 选择线图,拖拽total_cnt到纵轴,拖拽pay_type到横轴,修改连线样式为雷达图。 修改横轴和纵轴的显示文字 添加组件到仪表板。 效果如下:

    2024年01月15日
    浏览(40)
  • uniapp前端+python后端=微信小程序支付到底怎么开发???国内的资料为什么没一篇能讲清楚,简简单单的只需要3步就可以了-V2版本

    真的,在接到这个任务的时候,本以为很简单,不就是普通的浏览器复制粘贴,最不济找下gpt给生成一下,但是到实际开发就不同了,不是后端出问题就是前端,搜资料,上百度上google,基本每一个人讲的都不一样,不是这问题就是那问题,特别是微信官方,自己接口的逻辑

    2024年01月22日
    浏览(52)
  • 网址打包微信小程序源码 wap转微信小程序 网站转小程序源码 网址转小程序开发

    我们都知道微信小程序是无法直接打开网址的。 这个小程序源码提供了一种将网址直接打包成微信小程序的方法, 使得用户可以在微信小程序中直接访问这些网址内容。 这个源码没有进行加密,可以直接查看和修改。 将下面代码中的网站改成你的就行了,简单易用 蓝奏云

    2024年04月10日
    浏览(49)
  • 微信支付, 小程序,公众号, 商户号 需要进行的配置

    要想绑定商户号对接支付功能的必须是服务号,不能是订阅号. 服务号的申请创建这里不多说.按照微信提示步骤走即可 1.1.1点击左侧菜单 基本配置 1.1.2点击启用开发者密码(AppSecret) 根据提示验证 1.1.3保存记录AppID、AppSecret。 白名单内只填写ip地址即可 点击左侧菜单 开发者工

    2024年02月16日
    浏览(33)
  • 支付-支付宝接口全流程

    支付宝支付产品如下:文档: https://b.alipay.com/signing/productSetV2.htm 如何开通支付宝手机网站支付接口? 进入网址: https://b.alipay.com/signing/productDetailV2.htm?productId=I1011000290000001001 点击:立即开通 上传营业执照等资料,提交审核,根据提示进行开通。 第三方支付接口流程大同小

    2024年02月06日
    浏览(48)
  • 西米支付:如何选择自己需求的接口(传奇游戏支付接口)

    传奇游戏是中国网游无法绕过的一座碑,也是千万初代网游玩家的游戏启蒙,2001年一款游戏横空出世 靠着超爽的打击感,和多人同屏战斗迅速在网游火了起来,它就是传奇。随着《传奇》盛大的成长、兴盛与衰弱,一路走来,已经在14年。游戏的充值模式也由以前的点卡充值

    2024年02月15日
    浏览(34)
  • AI问答:前端需要掌握的设计模式/vue项目使用了哪些设计模式/vue项目开发可以使用哪些设计模式

    一、理解什么是设计模式 设计模式是对软件设计开发过程中反复出现的某类问题的通用解决方案。 设计模式是一个在软件设计领域中被广泛应用的概念,它指的是一套被公认为有效的解决特定问题的设计思路和方法。 设计模式更多的是指导思想和方法论,而不是现成的代码

    2024年02月09日
    浏览(63)
  • 关于支付通道,支付接口,支付对接的100个名词解释

    一份简明易懂的支付术语解释清单,帮助你更好地理解支付通道、支付接口和支付对接等相关概念。 100个名词的简要解释: 在线支付:通过互联网实现的支付方式,包括网银支付、第三方支付等。 支付网关:连接商户和支付机构的中间件,实现支付流程的安全处理和支付数

    2024年02月04日
    浏览(43)
  • 苍穹外卖项目开发指南:项目概述、环境搭建、Swagger接口文档生成

    详细介绍苍穹外卖项目的开发流程,包括软件开发整体介绍、项目概述、环境搭建、Swagger接口文档生成、Nginx反向代理和负载均衡配置等内容,帮助开发人员快速上手项目开发。

    2024年02月10日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包