v3 微信api 请求微信_微信支付V3接口开发流程

一、商户后台设置V3 Key密钥及下载V3 API证书(三个文件分别为apiclient_key.pem、apiclient_cert.pem、apiclient_cert.p12)

二、设计表结构实现管理功能

package com.budwk.nb.wx.models;

import com.budwk.nb.commons.base.model.BaseModel;

import lombok.Data;

import lombok.EqualsAndHashCode;

import org.nutz.dao.entity.annotation.*;

import org.nutz.dao.interceptor.annotation.PrevInsert;

import java.io.Serializable;

/**

* 微信支付配置表

* @author wizzer@qq.com

*/

@Data

@EqualsAndHashCode(callSuper = true)

@Table("wx_pay")

public class Wx_pay extends BaseModel implements Serializable {

private static final long serialVersionUID = 1L;

@Column

@Name

@Comment("ID")

@ColDefine(type = ColType.VARCHAR, width = 32)

@PrevInsert(els = {@EL("uuid()")})

private String id;

@Column

@ColDefine(type = ColType.VARCHAR, width = 32)

private String name;

@Column

@ColDefine(type = ColType.VARCHAR, width = 32)

private String mchid;

@Column

@ColDefine(type = ColType.VARCHAR, width = 50)

private String v3key;

/**

* apiclient_key.pem 物理路径

*/

@Column

@ColDefine(type = ColType.VARCHAR, width = 255)

private String v3keyPath;

/**

* apiclient_cert.pem 物理路径

*/

@Column

@ColDefine(type = ColType.VARCHAR, width = 255)

private String v3certPath;

/**

* apiclient_cert.p12 物理路径

*/

@Column

@ColDefine(type = ColType.VARCHAR, width = 255)

private String v3certP12Path;

/**

* 平台证书失效时间

*/

@Column

private Long expire_at;

}

package com.budwk.nb.wx.models;

import com.budwk.nb.commons.base.model.BaseModel;

import lombok.Data;

import lombok.EqualsAndHashCode;

import org.nutz.dao.entity.annotation.*;

import org.nutz.dao.interceptor.annotation.PrevInsert;

import java.io.Serializable;

/**

* 平台证书临存表

* @author wizzer@qq.com

*/

@Data

@EqualsAndHashCode(callSuper = true)

@Table("wx_pay_cert")

@TableIndexes({@Index(name = "INDEX_WX_PAY_CERT", fields = {"mchid", "serial_no"}, unique = true)})

public class Wx_pay_cert extends BaseModel implements Serializable {

private static final long serialVersionUID = 1L;

@Column

@Name

@Comment("ID")

@ColDefine(type = ColType.VARCHAR, width = 32)

@PrevInsert(els = {@EL("uuid()")})

private String id;

@Column

@ColDefine(type = ColType.VARCHAR, width = 32)

private String mchid;

@Column

@ColDefine(type = ColType.VARCHAR, width = 255)

private String serial_no;

@Column

@ColDefine(type = ColType.VARCHAR, width = 255)

private String effective_time;

@Column

private Long effective_at;

@Column

@ColDefine(type = ColType.VARCHAR, width = 255)

private String expire_time;

@Column

private Long expire_at;

@Column

@ColDefine(type = ColType.VARCHAR, width = 255)

private String algorithm;

@Column

@ColDefine(type = ColType.VARCHAR, width = 255)

private String nonce;

@Column

@ColDefine(type = ColType.VARCHAR, width = 255)

private String associated_data;

@Column

@ColDefine(type = ColType.TEXT)

private String ciphertext;

@Column

@ColDefine(type = ColType.TEXT)

private String certificate;

}

三、封装下订单/JSAPI/平台证书更新等功能服务类

package com.budwk.nb.web.commons.ext.wx;

import com.alibaba.dubbo.config.annotation.Reference;

import com.budwk.nb.web.commons.base.Globals;

import com.budwk.nb.wx.models.Wx_pay;

import com.budwk.nb.wx.models.Wx_pay_cert;

import com.budwk.nb.wx.services.WxPayCertService;

import com.budwk.nb.wx.services.WxPayService;

import org.nutz.dao.Chain;

import org.nutz.dao.Cnd;

import org.nutz.ioc.loader.annotation.Inject;

import org.nutz.ioc.loader.annotation.IocBean;

import org.nutz.json.Json;

import org.nutz.lang.Strings;

import org.nutz.lang.Times;

import org.nutz.lang.util.NutMap;

import org.nutz.log.Log;

import org.nutz.log.Logs;

import org.nutz.weixin.bean.WxPay3Response;

import org.nutz.weixin.util.WxPay3Api;

import org.nutz.weixin.util.WxPay3Util;

import java.nio.charset.StandardCharsets;

import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.List;

/**

* @author wizzer@qq.com

*/

@IocBean

public class WxPay3Service {

private static final Log log = Logs.get();

private static final SimpleDateFormat DATE_TIME_ZONE = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");

@Inject

@Reference(check = false)

private WxPayCertService wxPayCertService;

@Inject

@Reference(check = false)

private WxPayService wxPayService;

// 通过商户号获取 wx_pay 对象

public synchronized Wx_pay getWxPay(String mchid) {

Wx_pay wxPay = Globals.WxPay3Map.getAs(mchid, Wx_pay.class);

if (wxPay == null) {

wxPay = wxPayService.fetch(Cnd.where("mchid", "=", mchid));

Globals.WxPay3Map.put(wxPay.getMchid(), wxPay);

}

checkPlatfromCerts(wxPay);

return wxPay;

}

// 检查及更新平台证书机制

public void checkPlatfromCerts(Wx_pay wxPay) {

if (wxPay == null)

throw new IllegalStateException("Wx_pay is null");

if (wxPay.getExpire_at() == null || wxPay.getExpire_at() == 0 || wxPay.getExpire_at() < 8 * 3600 * 1000 + System.currentTimeMillis()) {

getPlatfromCerts(wxPay.getMchid(), wxPay.getV3key(), wxPay.getV3keyPath(), wxPay.getV3certPath());

wxPay = wxPayService.fetch(Cnd.where("mchid", "=", wxPay.getMchid()));

Globals.WxPay3Map.put(wxPay.getMchid(), wxPay);

}

}

// jsapi 订单下单

public WxPay3Response v3_order_jsapi(String mchid, String body) throws Exception {

log.debug("v3_order_jsapi body::" + body);

String serialNo = WxPay3Util.getCertSerialNo(getWxPay(mchid).getV3certPath());

return WxPay3Api.v3_order_jsapi(mchid, serialNo, getWxPay(mchid).getV3keyPath(), body);

}

// 通过jsapi 订单号生成js参数

public NutMap v3_call_jsapi(String mchid, String appid, String prepay_id) throws Exception {

return WxPay3Util.getJsapiSignMessage(appid, prepay_id, getWxPay(mchid).getV3keyPath());

}

// 验证http响应签名结果

public boolean verifySignature(WxPay3Response wxPay3Response, String mchid) throws Exception {

Wx_pay_cert wxPayCert = wxPayCertService.fetch(Cnd.where("mchid", "=", mchid).and("serial_no", "=", wxPay3Response.getHeader().get("Wechatpay-Serial")));

return WxPay3Util.verifySignature(wxPay3Response, wxPayCert.getCertificate());

}

// 验证回调通知签名及内容

public String verifyNotify(String mchid, String serialNo, String body, String signature, String nonce, String timestamp) throws Exception {

Wx_pay_cert wxPayCert = wxPayCertService.fetch(Cnd.where("mchid", "=", mchid).and("serial_no", "=", serialNo));

return WxPay3Util.verifyNotify(serialNo, body, signature, nonce, timestamp,

getWxPay(mchid).getV3key(), wxPayCert.getCertificate());

}

/**

* 请求并保存新证书

*

* @param mchid

* @return

*/

public void getPlatfromCerts(String mchid, String v3Key, String v3KeyPatch, String v3CertPath) {

try {

wxPayCertService.clear(Cnd.where("mchid", "=", mchid).and("expire_at", "

String serialNo = WxPay3Util.getCertSerialNo(v3CertPath);

WxPay3Response wxPay3Response = WxPay3Api.v3_certificates(mchid, serialNo, v3KeyPatch);

if (wxPay3Response.getStatus() == 200) {

NutMap nutMap = Json.fromJson(NutMap.class, wxPay3Response.getBody());

List list = nutMap.getList("data", NutMap.class);

for (NutMap cert : list) {

Wx_pay_cert wxPayCert = new Wx_pay_cert();

wxPayCert.setMchid(mchid);

wxPayCert.setEffective_time(cert.getString("effective_time"));

wxPayCert.setExpire_time(cert.getString("expire_time"));

long expire_at = 0;

try {

expire_at = Times.parse(DATE_TIME_ZONE, cert.getString("expire_time")).getTime();

wxPayCert.setEffective_at(Times.parse(DATE_TIME_ZONE, cert.getString("effective_time")).getTime());

wxPayCert.setExpire_at(expire_at);

} catch (ParseException e) {

e.printStackTrace();

}

wxPayCert.setSerial_no(cert.getString("serial_no"));

NutMap encrypt_certificate = cert.getAs("encrypt_certificate", NutMap.class);

wxPayCert.setAlgorithm(encrypt_certificate.getString("algorithm"));

wxPayCert.setAssociated_data(encrypt_certificate.getString("associated_data"));

wxPayCert.setCiphertext(encrypt_certificate.getString("ciphertext"));

wxPayCert.setNonce(encrypt_certificate.getString("nonce"));

String platformCertificate = WxPay3Util.decryptToString(v3Key.getBytes(StandardCharsets.UTF_8),

encrypt_certificate.getString("associated_data").getBytes(StandardCharsets.UTF_8),

encrypt_certificate.getString("nonce").getBytes(StandardCharsets.UTF_8),

encrypt_certificate.getString("ciphertext")

);

wxPayCert.setCertificate(platformCertificate);

try {

wxPayCertService.insert(wxPayCert);

} catch (Exception e) {

//重复的插入会报错,不管它

}

}

Wx_pay_cert wxPayCert = wxPayCertService.fetch(Cnd.where("mchid", "=", mchid).orderBy("effective_at", "desc"));

if (wxPayCert != null) {

wxPayService.update(Chain.make("expire_at", wxPayCert.getExpire_at()), Cnd.where("mchid", "=", mchid));

}

}

} catch (Exception e) {

log.errorf("获取平台证书失败,mchid=%s", mchid, e);

}

}

}

四、小程序支付业务代码

@Test

public void test_v3_order() throws Exception {

String orderPayNo = R.UU32();

String orderId = R.UU32();

NutMap wxPayUnifiedOrder = NutMap.NEW();

wxPayUnifiedOrder.addv("appid", appid);

wxPayUnifiedOrder.addv("mchid", mchid);

wxPayUnifiedOrder.addv("description", new String(("LaiShop-order-" + orderId).getBytes(), StandardCharsets.UTF_8));

wxPayUnifiedOrder.addv("out_trade_no", orderPayNo);

Date now = new Date();

wxPayUnifiedOrder.addv("time_expire", DateUtil.getDateAfterMinute(now, 30));

// 回调通知URL传递mchid商户号,便于系统支持接入N个小程序及支付商户账号

wxPayUnifiedOrder.addv("notify_url", Globals.AppDomain + "/shop/open/wxpay/" + mchid + "/notify");

wxPayUnifiedOrder.addv("amount", NutMap.NEW().addv("total", 1).addv("currency", "CNY"));

wxPayUnifiedOrder.addv("payer", NutMap.NEW().addv("openid", "o9Bnd4lXKfNsOci-6H98zCMWyBps"));

String body = Json.toJson(wxPayUnifiedOrder);

System.out.println("body::" + body);

WxPay3Response wxPay3Response = wxPay3Service.v3_order_jsapi(mchid, body);

System.out.println("wxPay3Response::" + Json.toJson(wxPay3Response));

boolean verifySignature = wxPay3Service.verifySignature(wxPay3Response, mchid);

System.out.println("verifySignature::" + verifySignature);

NutMap v3order = Json.fromJson(NutMap.class, wxPay3Response.getBody());

NutMap resp = wxPay3Service.v3_call_jsapi(mchid, appid, v3order.getString("prepay_id"));

System.out.println("resp::" + Json.toJson(resp));

}

五、回调通知业务代码

package com.budwk.nb.web.controllers.open.pay;

import com.alibaba.dubbo.config.annotation.Reference;

import com.budwk.nb.web.commons.ext.wx.WxPay3Service;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;

import io.swagger.v3.oas.annotations.servers.Server;

import io.swagger.v3.oas.annotations.tags.Tag;

import org.nutz.ioc.loader.annotation.Inject;

import org.nutz.ioc.loader.annotation.IocBean;

import org.nutz.json.Json;

import org.nutz.lang.Streams;

import org.nutz.lang.util.NutMap;

import org.nutz.log.Log;

import org.nutz.log.Logs;

import org.nutz.mvc.adaptor.VoidAdaptor;

import org.nutz.mvc.annotation.AdaptBy;

import org.nutz.mvc.annotation.At;

import org.nutz.mvc.annotation.Ok;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import java.io.Reader;

import java.nio.charset.StandardCharsets;

/**

* @author wizzer@qq.com

*/

@IocBean

@At("/shop/open/wxpay")

@Ok("json")

@OpenAPIDefinition(tags = {@Tag(name = "商城_微信支付回调")}, servers = @Server(url = "/"))

public class WxPay3NotifyController {

private static final Log log = Logs.get();

@Inject

private WxPay3Service wxPay3Service;

@At("/{mchid}/notify")

@Ok("raw")

@AdaptBy(type = VoidAdaptor.class)

public void notify(String mchid, Reader reader, HttpServletRequest req, HttpServletResponse resp) throws IOException {

try {

NutMap map = NutMap.NEW();

String timestamp = req.getHeader("Wechatpay-Timestamp");

String nonce = req.getHeader("Wechatpay-Nonce");

String serialNo = req.getHeader("Wechatpay-Serial");

String signature = req.getHeader("Wechatpay-Signature");

log.debugf("timestamp=%s,nonce=%s,serialNo=%s,signature=%s", timestamp, nonce, serialNo, signature);

String body = Streams.readAndClose(reader);

// 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号

String plainText = wxPay3Service.verifyNotify(mchid, serialNo, body, signature, nonce, timestamp);

log.debugf("支付通知明文=%s", plainText);

NutMap res = Json.fromJson(NutMap.class, plainText);

NutMap payer = res.getAs("payer", NutMap.class);

String trade_state = res.getString("trade_state");

String out_trade_no = res.getString("out_trade_no");

String openid = payer.getString("openid");

boolean ok = true;//业务代码入库

if ("SUCCESS".equals(trade_state) && ok) {

resp.setStatus(200);

map.put("code", "SUCCESS");

map.put("message", "SUCCESS");

} else {

resp.setStatus(500);

map.put("code", "ERROR");

map.put("message", "签名错误");

}

resp.setHeader("Content-type", "application/json");

resp.getOutputStream().write(Json.toJson(map).getBytes(StandardCharsets.UTF_8));

resp.flushBuffer();

} catch (Exception e) {

e.printStackTrace();

}

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值