优秀的编程知识分享平台

网站首页 > 技术文章 正文

java微信支付在服务器使用spring mvc实现订单生成

nanyue 2024-08-03 18:12:25 技术文章 8 ℃

关于微信应用的支付这个东西,因为官方没有服务器的sample,所以对于我们开发者开发感觉有点困难,不过其实认真的看一下文档感觉也是挺容易的,其实我感觉整个过程中,就是微信支付返回的数据有点不符合人性的阅读而已,使用了XML任何还加上类似以前弄论坛的discuz里面的布局文件的格式,不过怎么说都好,这样设计肯定也有它的原理的。如果对我的文章感兴趣,欢迎订阅我的头条号,一点热,yeehot.com.

我这里就以微信应用支付发起订单生产为例子,关于公众号支付后面再说,不过服务器的订单生成也是使用统一的接口的。

下面我们开始我们支付的学习,我这里直接以Android手机客户端发起调用为例,因为我的IOS客户端没有接入微信支付,只是接入了苹果的内支付,所以就不做介绍了,其实和Android的都一样,都是发送支付的响应然后调用微信客户端,发起调用支付的功能。

更多相关的文档大家可以移步到微信支付文档

https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1

关于微信支付的开通这些我就不做介绍了,就是微信认证的步骤。

关于微信的支付步骤就是,生产订单后,然后在客户端根据交易回话标识正确的信息,发起微信支付的调用。

微信订单生产的入口是

URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder

我这里就介绍介必填的参数,因为涉及到太多的参数了,其他参数大家可以看文档。

appid:应用ID ,在微信开发平台可以获取

mch_id:商户号 ,开通微信支付会收到的商家ID

nonce_str:随机字符串,这个随机数我们自己生产

sign:签名,这个我们在代码介绍如何生产

body:商品描述

detail:商品详情

out_trade_no:商户订单号,这个订单号我们需要保持到数据库,用来查询是否成功。

total_fee:交易金额

spbill_create_ip:客户端IP

notify_url:异步返回的接口。这里微信服务器会按照一定的频率向这个地址发送交易订单的信息。所以这个一定是外网的地址。

trade_type:交易类型,这里是APP就填写“APP”

首先我们在服务器端根据用于产品信息然后生产订单。

我们在spring MVC服务器端设计几个参数。分别是我们app的用户名,商品描述,商品详情和金钱。

这几个参数,是我用来记录客户端的用户点击支付的频率,以及等下需要用到的订单生成保存在数据库需要用到的,当然你也可以添加更多的参数,我这里就不做介绍,看各人的需要。

假设我们有一个订单是yeehot.com的会员充值,然后是用Y币作为单位,1个Y币是1元,Y币的作用是可以和站内的其他会员交流,送礼物的花费。我的用户名是一点热。那么我们可以在springMVC服务器这样写接受数据的入口

public final static String WEIXIN_ZHIFU_SECRET = "a15";//替换成你的秘钥

public final static String WEIXIN_APPID = "wx1113";//替换成你的应用ID

public final static String WEIXIN_MID = "12111";//替换你的商家ID

@ResponseBody

@RequestMapping(value = "/wx/payinfo", method = RequestMethod.GET)

public String payOrder(String username, String body, String subject, String money){

try {

if (subject != null)

subject = URLDecoder.decode(subject, "utf-8");

if (body != null)

body = URLDecoder.decode(body, "utf-8");

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

}

}

接着,我们还需要到微信商家平台,取回我们的商家ID和设置秘钥。取回后,还有在我们的微信开放平台取到我们的应用ID,因为我这里是以移动应用为例子,我们把这些参数放到map里面。

Map<String, String> map = new HashMap<String, String>();

map.put("appid", WEIXIN_APPID);

map.put("mch_id", WEIXIN_MID);


完成了上一步操作后,我们需要产生一个随机数。那么我这里直接就以MD5在加上uuid来生成吧

map.put("nonce_str",MD5(UUID.randomUUID().toString()));

其中MD5生成的方法如下:

/**

* generate MD5

*

* @param src

* @return

* @throws Exception

*/

public static String MD5(String src) {

try {

if (src == null) {

return "";

}

byte[] result = null;

MessageDigest alg = MessageDigest.getInstance("MD5");

result = alg.digest(src.getBytes("utf-8"));

return byte2hex(result);

} catch (Exception e) {

throw new RuntimeException();

}

}

private static String byte2hex(byte[] b) {

if (b == null) {

return "";

}

StringBuffer hs = new StringBuffer();

String stmp = null;

for (int n = 0; n < b.length; n++) {

stmp = Integer.toHexString(b[n] & 0XFF);

if (stmp.length() == 1) {

hs.append("0");

}

hs.append(stmp);

}

return hs.toString();

}

到了这一步,就可以进行订单号的生成了。我们以时间来生成out_trade_no

String tradeNo = getOutTradeNo();

map.put("out_trade_no", tradeNo);

这里的 getOutTradeNo()的方法代码如下:

public static String getOutTradeNo() {

SimpleDateFormat format = new SimpleDateFormat("YYYYMMddHHmmss");

Date date = new Date();

String key = format.format(date);

java.util.Random r = new java.util.Random();

key += r.nextInt();

key = key.substring(0, 15);

return key;

}

完成订单号的生成,我们可以进行到金额、IP,异步回调URL等其他信息的输入

map.put("total_fee", money);

map.put("spbill_create_ip", "192.168.1.1");

map.put("notify_url",

"http://pay.yeehot.com/YeehotProgram/zhifu/wxnotify");

map.put("trade_type", "APP");

到了这一步需要注意的就是签名的方法了。

因为签名是根据我们有多少个参数数据,就需要将多少个参数进行拼接,当然是要除了sign本身

所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA,接着在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。具体生成请看官方文档

https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3

我这里就继续用java如何来实现签名的算法:

String signString = creatWXsign(map,WEIXIN_ZHIFU_SECRET);

System.out.println("yeehotsign==" + signString);

map.put("sign", signString);


这里的WEIXIN_ZHIFU_SECRET,是我们刚刚说到的在支付商家平台自定义的一个秘钥,大家可以登录到商家平台自行设置一个秘钥。其中map是我们上面几个步骤已经包含的参数。

public static String creatWXsign(Map<String, String> params, String secret) {

StringBuilder result = new StringBuilder();

List<String> keys = new ArrayList<String>(params.keySet());

Collections.sort(keys);

for (Iterator<String> iterator = keys.iterator(); iterator.hasNext(); ) {

String key = iterator.next();

if (key!=null&&key.length()>0&&!"key".equalsIgnoreCase(key)&&!"sign".equalsIgnoreCase(key)) {

result.append(key).append("=").append(params.get(key));

if (iterator.hasNext()) {

result.append("&");

}

}

}

result.append("&key="+secret);

System.out.println("md5 sign="+result.toString());

return MD5(result.toString());

}

在提交到微信服务器之前,我们还需要将数据转换成XML格式的string类型。这个步骤我就觉得有点让人摸不着头脑。哈哈,实现代码如下:

public String RequestXml2String(Map<String, String> params) {

StringBuffer sb = new StringBuffer();

sb.append("<xml>");

Set es = params.entrySet();

Iterator it = es.iterator();

while(it.hasNext()) {

Map.Entry entry = (Map.Entry)it.next();

String k = (String)entry.getKey();

String v = (String)entry.getValue();

if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {

sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");

}else {

sb.append("<"+k+">"+v+"</"+k+">");

}

}

sb.append("</xml>");

return sb.toString();

}

接着我们可以写一个POST的方法,我这里是直接使用CloseableHttpClient来实现。我这里同时也把MAP键值对转换层XML格式的字符串类型了。然后提交到服务器。

private String PostWX(String url, Map<String ,String> mapinfo){

String postinfo=RequestXml2String(mapinfo);

String result="";

X509TrustManager tm = new X509TrustManager() {

public void checkClientTrusted(X509Certificate[] xcs,

String string) throws CertificateException {

}

public void checkServerTrusted(X509Certificate[] xcs,

String string) throws CertificateException {

}

public X509Certificate[] getAcceptedIssuers() {

return null;

}

};

SSLContext sslcontext;

try {

sslcontext = SSLContext.getInstance("TLS");

sslcontext.init(null, new TrustManager[]{tm}, null);

} catch (NoSuchAlgorithmException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (KeyManagementException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

ConnectionSocketFactory plainsf = PlainConnectionSocketFactory

.getSocketFactory();

LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory

.getSocketFactory();

Registry<ConnectionSocketFactory> registry = RegistryBuilder

.<ConnectionSocketFactory> create().register("http", plainsf)

.register("https", sslsf).build();

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(

registry);

// 将最大连接数增加

cm.setMaxTotal(200);

CloseableHttpClient httpClient = HttpClients.custom()

.setConnectionManager(cm)

.build();

HttpPost post = new HttpPost(url);

post.setEntity(new StringEntity(postinfo, "UTF-8"));

try {

CloseableHttpResponse response = httpClient.execute(post);

try {

StringBuilder sb2 = new StringBuilder();

BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));

String line;

while ((line = br.readLine()) != null) {

sb2.append(line);

}

result= sb2.toString();

System.out.println("------------------------------------");

} finally {

response.close();

}

}

catch (ClientProtocolException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

finally {

// 关闭连接,释放资源

try {

httpClient.close();

} catch (IOException e) {

e.printStackTrace();

}

}

return result;

}

当提交成功后会返回一串XML格式的数据,如下,我们需要对这些数据进行转换

我们继续

String infoString =PostWX(

"https://api.mch.weixin.qq.com/pay/unifiedorder", map);

Map<String, String> appinfo = new HashMap<String, String>();

Map<String, String> info = new HashMap<String, String>();

JSONObject jsonObject = new JSONObject();

info = doXMLParse(infoString);

这里XML解析的代码如下:

import org.jdom.*;

public static Map<String,String> doXMLParse(String inputinfo) throws JDOMException, IOException {

if(null == inputinfo || "".equals(inputinfo)) {

return null;

}

Map<String,String> m = new HashMap<String,String>();

InputStream in =new ByteArrayInputStream(inputinfo.getBytes("utf-8"));

SAXBuilder builder = new SAXBuilder();

Document doc = builder.build(in);

Element root = doc.getRootElement();

List list = root.getChildren();

Iterator it = list.iterator();

while(it.hasNext()) {

Element e = (Element) it.next();

String k = e.getName();

String v = "";

List children = e.getChildren();

if(children.isEmpty()) {

v = e.getTextNormalize();

} else {

v = getChildrenText(children);

}

m.put(k, v);

}

in.close();

return m;

}

public static String getChildrenText(List children) {

StringBuffer sb = new StringBuffer();

if(!children.isEmpty()) {

Iterator it = children.iterator();

while(it.hasNext()) {

Element e = (Element) it.next();

String name = e.getName();

String value = e.getTextNormalize();

List list = e.getChildren();

sb.append("<" + name + ">");

if(!list.isEmpty()) {

sb.append(getChildrenText(list));

}

sb.append(value);

sb.append("</" + name + ">");

}

}

return sb.toString();

}

获取转换的数据后,我们还需要对数据进行签名返回给客户端,这个一定要注意的,不是原来的签名。

try {

info = doXMLParse(infoString);

jsonObject.put("appid", info.get("appid"));

jsonObject.put("prepayid", info.get("prepay_id"));

jsonObject.put("timestamp", String.valueOf(new java.sql.Timestamp(System.currentTimeMillis())));

jsonObject.put("noncestr",

MD5(UUID.randomUUID().toString()));

jsonObject.put("partnerid", info.get("mch_id"));

jsonObject.put("package", "Sign=WXPay");

String signString2 = creatWXsign(appinfo,

WEIXIN_ZHIFU_SECRET);

//这里的签名是新的签名信息,用于客户端调用的。

//这里需要将订单信息保存到数据库,我这里就不写出来,下一节再写

jsonObject.put("sign", signString2);

jsonObject.put("result_code", info.get("result_code"));

} catch (JDOMException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

catch (JSONException e) {

e.printStackTrace();

}

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

return jsonObject.toString();

我这里设计返回给客户端的格式是json

{

"result_code": "SUCCESS",

"sign": "121266a0dd51f8981",

"timestamp": "2016-07-29 11:05:58.917",

"partnerid": "121212121",

"noncestr": "bd90122321356a1fdd75f185c4",

"prepayid": "wx20160729195123213213122112",

"package": "Sign=WXPay",

"appid": "wx2412112123233"

}

客户端调用这些参数就可以实现回调了,我以安卓的为例:这些参数我是修改过的,真实的数据就看自己返回的数据。

PayReq req = new PayReq();

req.appId = "wx2412112123233";

req.partnerId = "121212121";

req.prepayId = "wx20160729195123213213122112";

req.nonceStr = "bd90122321356a1fdd75f185c4";

req.timeStamp = "2016-07-29 11:05:58.917";

req.packageValue = "Sign=WXPay";

req.sign = "121266a0dd51f8981";

req.extData = "app data"; // optional

// 在支付之前,如果应用没有注册到微信,应该先调用IWXMsg.registerApp将应用注册到微信

api.sendReq(req);

整个controller的代码如下:

/**

* Created by yeehot on 16/7/29.

*/

@Controller

public class WeiXinPayController {

public final static String WEIXIN_ZHIFU_SECRET = "a15";//替换成你的秘钥

public final static String WEIXIN_APPID = "wx1113";//替换成你的应用ID

public final static String WEIXIN_MID = "12111";//替换你的商家ID

@ResponseBody

@RequestMapping(value = "/wx/payinfo", method = RequestMethod.GET)

public String payOrder(String username, String body, String subject, String money){

{

try {

if (subject != null)

subject = URLDecoder.decode(subject, "utf-8");

if (body != null)

body = URLDecoder.decode(body, "utf-8");

} catch (UnsupportedEncodingException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

Map<String, String> map = new HashMap<String, String>();

map.put("appid", WEIXIN_APPID);

map.put("mch_id", WEIXIN_MID);

map.put("nonce_str",MD5(UUID.randomUUID().toString()));

map.put("device_info", "WEB");

map.put("body", body);

map.put("detail", subject);

String tradeNo = getOutTradeNo();

map.put("out_trade_no", tradeNo);

map.put("total_fee", money);

map.put("spbill_create_ip", "192.168.1.1");

map.put("notify_url",

"http://pay.yeehot.com/YeehotProgram/zhifu/wxnotify");

map.put("trade_type", "APP");

String signString = creatWXsign(map,

WEIXIN_ZHIFU_SECRET);

System.out.println("yeehotsign==" + signString);

map.put("sign", signString);

String infoString =PostWX(

"https://api.mch.weixin.qq.com/pay/unifiedorder", map);

//

Map<String, String> appinfo = new HashMap<String, String>();

Map<String, String> info = new HashMap<String, String>();

JSONObject jsonObject = new JSONObject();

try {

info = doXMLParse(infoString);

jsonObject.put("appid", info.get("appid"));

jsonObject.put("prepayid", info.get("prepay_id"));

jsonObject.put("timestamp", String.valueOf(new java.sql.Timestamp(System.currentTimeMillis())));

jsonObject.put("noncestr",

MD5(UUID.randomUUID().toString()));

jsonObject.put("partnerid", info.get("mch_id"));

jsonObject.put("package", "Sign=WXPay");

String signString2 = creatWXsign(appinfo,

WEIXIN_ZHIFU_SECRET);

//这里需要将订单信息保存到数据库,我这里就不写出来,下一节再写

jsonObject.put("sign", signString2);

jsonObject.put("result_code", info.get("result_code"));

} catch (JDOMException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

catch (JSONException e) {

e.printStackTrace();

}

catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

return jsonObject.toString();

}

private String PostWX(String url, Map<String ,String> mapinfo){

String postinfo=RequestXml2String(mapinfo);

String result="";

X509TrustManager tm = new X509TrustManager() {

public void checkClientTrusted(X509Certificate[] xcs,

String string) throws CertificateException {

}

public void checkServerTrusted(X509Certificate[] xcs,

String string) throws CertificateException {

}

public X509Certificate[] getAcceptedIssuers() {

return null;

}

};

SSLContext sslcontext;

try {

sslcontext = SSLContext.getInstance("TLS");

sslcontext.init(null, new TrustManager[]{tm}, null);

} catch (NoSuchAlgorithmException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (KeyManagementException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

ConnectionSocketFactory plainsf = PlainConnectionSocketFactory

.getSocketFactory();

LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory

.getSocketFactory();

Registry<ConnectionSocketFactory> registry = RegistryBuilder

.<ConnectionSocketFactory> create().register("http", plainsf)

.register("https", sslsf).build();

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(

registry);

// 将最大连接数增加

cm.setMaxTotal(200);

CloseableHttpClient httpClient = HttpClients.custom()

.setConnectionManager(cm)

.build();

HttpPost post = new HttpPost(url);

post.setEntity(new StringEntity(postinfo, "UTF-8"));

try {

CloseableHttpResponse response = httpClient.execute(post);

try {

StringBuilder sb2 = new StringBuilder();

BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));

String line;

while ((line = br.readLine()) != null) {

sb2.append(line);

}

result= sb2.toString();

System.out.println("------------------------------------");

} finally {

response.close();

}

}

catch (ClientProtocolException e) {

// TODO Auto-generated catch block

e.printStackTrace();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

finally {

// 关闭连接,释放资源

try {

httpClient.close();

} catch (IOException e) {

e.printStackTrace();

}

}

return result;

}

/**

* generate MD5

*

* @param src

* @return

* @throws Exception

*/

public static String MD5(String src) {

try {

if (src == null) {

return "";

}

byte[] result = null;

MessageDigest alg = MessageDigest.getInstance("MD5");

result = alg.digest(src.getBytes("utf-8"));

return byte2hex(result);

} catch (Exception e) {

throw new RuntimeException();

}

}

private static String byte2hex(byte[] b) {

if (b == null) {

return "";

}

StringBuffer hs = new StringBuffer();

String stmp = null;

for (int n = 0; n < b.length; n++) {

stmp = Integer.toHexString(b[n] & 0XFF);

if (stmp.length() == 1) {

hs.append("0");

}

hs.append(stmp);

}

return hs.toString();

}

public static String getOutTradeNo() {

SimpleDateFormat format = new SimpleDateFormat("YYYYMMddHHmmss");

Date date = new Date();

String key = format.format(date);

java.util.Random r = new java.util.Random();

key += r.nextInt();

key = key.substring(0, 15);

return key;

}

public static String creatWXsign(Map<String, String> params, String secret) {

StringBuilder result = new StringBuilder();

List<String> keys = new ArrayList<String>(params.keySet());

Collections.sort(keys);

for (Iterator<String> iterator = keys.iterator(); iterator.hasNext(); ) {

String key = iterator.next();

if (key!=null&&key.length()>0&&!"key".equalsIgnoreCase(key)&&!"sign".equalsIgnoreCase(key)) {

result.append(key).append("=").append(params.get(key));

if (iterator.hasNext()) {

result.append("&");

}

}

// System.out.println("md5 sign="+result.toString());

}

result.append("&key="+secret);

System.out.println("md5 sign="+result.toString());

return MD5(result.toString());

}

public String RequestXml2String(Map<String, String> params) {

StringBuffer sb = new StringBuffer();

sb.append("<xml>");

Set es = params.entrySet();

Iterator it = es.iterator();

while(it.hasNext()) {

Map.Entry entry = (Map.Entry)it.next();

String k = (String)entry.getKey();

String v = (String)entry.getValue();

if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {

sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");

}else {

sb.append("<"+k+">"+v+"</"+k+">");

}

}

sb.append("</xml>");

return sb.toString();

}

public static Map<String,String> doXMLParse(String inputinfo) throws JDOMException, IOException {

if(null == inputinfo || "".equals(inputinfo)) {

return null;

}

Map<String,String> m = new HashMap<String,String>();

InputStream in =new ByteArrayInputStream(inputinfo.getBytes("utf-8"));

SAXBuilder builder = new SAXBuilder();

Document doc = builder.build(in);

Element root = doc.getRootElement();

List list = root.getChildren();

Iterator it = list.iterator();

while(it.hasNext()) {

Element e = (Element) it.next();

String k = e.getName();

String v = "";

List children = e.getChildren();

if(children.isEmpty()) {

v = e.getTextNormalize();

} else {

v = getChildrenText(children);

}

m.put(k, v);

}

in.close();

return m;

}

public static String getChildrenText(List children) {

StringBuffer sb = new StringBuffer();

if(!children.isEmpty()) {

Iterator it = children.iterator();

while(it.hasNext()) {

Element e = (Element) it.next();

String name = e.getName();

String value = e.getTextNormalize();

List list = e.getChildren();

sb.append("<" + name + ">");

if(!list.isEmpty()) {

sb.append(getChildrenText(list));

}

sb.append(value);

sb.append("</" + name + ">");

}

}

return sb.toString();

}

}

我们还可以到微信平台验证签名的正确性,因为这个关乎是否能够成功调用支付

https://pay.weixin.qq.com/wiki/tools/signverify/

这里就讲到这里为止,如果存在疑问的话,欢迎留言咨询,此外还有一些回调的数据还没有实现,我们留到下一节再讲,欢迎继续关注我的头条号:一点热,yeehot.com,如果有什么问题,欢迎留言咨询,我看到之后会第一时间回复大家的。如果需要转载到其他网站,请与我联系。

最近发表
标签列表