微信jsapi支付获取code_【微信支付】微信JSAPI支付流程

前言

微信APP支付,微信授权,微信JSAPI支付,微信退款,支付宝APP支付,支付宝手机网站支付,支付宝退款。笔者都放到公众号: JAVA大贼船。微信搜一搜,方便以后开发用哦!

官方文档

JS-SDK说明文档

JSAPI支付文档

准备工作

公众号配置绑定域名(可在该域名下调用微信开放的JS接口,这里配置前端域名) 先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。

填写接口配置信息(用于配合微信服务器认证)

开发-基本配置,配置URL,Token和生成EncodingAESKey,配置完了这时候点击提交是不行的,需要后端写好接口与配合微信认证,如下代码。

@ApiOperation("微信认证地址")

@GetMapping("/baseCallback")

public String baseCallback(String signature,String timestamp,String nonce,String echostr) throws Exception {

return echostr;

}

配置ip白名单

获取普通accessToken时,请登录“微信公众平台-开发-基本配置”提前将服务器IP地址添加到IP白名单中,否则将无法调用成功

商户平台配置设置支付目录(配置了才能拉起微信收银台,配置前端域名,否则会报当前页面的Url未注册的错误)

登录微信支付商户平台(http://pay.weixin.qq.com)-->产品中心-->开发配置,设置后一般5分钟内生效。支付授权目录校验说明如下:

1、如果支付授权目录设置为顶级域名(例如:https://www.weixin.com/ ),那么只校验顶级域名,不校验后缀;

2、如果支付授权目录设置为多级目录,就会进行全匹配,例如设置支付授权目录为https://www.weixin.com/abc/123/,则实际请求页面目录不能为https://www.weixin.com/abc/,也不能为https://www.weixin.com/abc/123/pay/,必须为https://www.weixin.com/abc/123/

流程步骤

JSSDK使用步骤这个文档主要看 1概述和14微信支付,如下图;

主要步骤前端获取注入参数

前端支付前请求后台获取上图步骤三所需参数,并注入成功,前端最好配置全局,因为业务可能会扩展,其他接口会需要,如分享接口

前端获取唤起微信收银台所需参数

注入成功后->前端点击微信支付->后端向微信下单->下单成功后得到prepay_id->返回所需参数给前端->唤起收银台

后端代码实现

引入依赖

com.github.wxpay

wxpay-sdk

0.0.3

配置参数

application.yml

# 微信相关配置

wx:

#商户 ID(微信支付平台-账户中心-个人信息)

MCH_ID:

# APP_ID(微信开放平台或公众号查找)

H_APP_ID:

# 秘钥(微信开放平台或公众号查找)

H_APP_SECRET:

# 消息加解密所用到的解密串(微信公众号-基本配置查找)

H_ENCODINGAESKEY:

# token(微信公众号-基本配置查找)

H_TOKEN:

# 支付秘钥KEY(微信支付平台-账户中心-api安全-api秘钥)

H_KEY:

# 支付商户证书所载目录(微信支付平台-账户中心-api安全-API证书)

H_CERT_PATH:

#支付成功回调地址

WX_CALLBACK_URL:

YmlParament

@Component

@Data

public class YmlParament {

/*微信相关字段*/

@Value("${wx.H_APP_ID}")

private String h_app_id;

@Value("${wx.H_APP_SECRET}")

private String h_app_secret;

@Value("${wx.H_ENCODINGAESKEY}")

private String h_encodingaeskey;

@Value("${wx.H_TOKEN}")

private String h_token;

@Value("${wx.MCH_ID}")

private String mch_id;

@Value("${wx.H_KEY}")

private String h_key;

@Value("${wx.H_CERT_PATH}")

private String h_cert_path;

@Value("${wx.WX_CALLBACK_URL}")

private String wx_callback_url;

获取前端config接口配置参数(查看jssdk文档附录1)生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。Application 开启计划任务,用于定时刷新

@EnableScheduling//开启计划任务 表达式参考http://cron.qqe2.com/配置全局缓存

WxParament

/**

* 获取ACCESS_TOKEN

* 刷新规则:

* 1、启动设置该值

* 2、一小时刷新一次

*/

public static String ACCESS_TOKEN;

/**

* 获取JSAPI_TICKET

* 刷新规则:

* 1、启动设置该值

* 2、一小时刷新一次

*/

public static String JSAPI_TICKET;定时刷新

InitServiceImpl

@Autowired

private YmlParament ymlParament;

@PostConstruct

@Override

public void init() {

initAccessToken();

initJsapiTicket();

}

/**

* 初始化AccessToken 一小时执行一次

*cron表达式可在线生成:https://cron.qqe2.com/

*/

@Scheduled(cron = "0 0 0/1 * * ?")

@Override

public void initAccessToken() {

try {

WxParament.ACCESS_TOKEN = GetToken.getAccessToken(ymlParament.getH_app_id(), ymlParament.getH_app_secret());

} catch (Exception e) {

log.error("<====initAccessToken初始化失败!==>" + e);

e.printStackTrace();

}

log.info("<====初始化initAccessToken成功,值为==>" + WxParament.ACCESS_TOKEN);

}

/**

* 初始化JSAPI_TICKET 一小时执行一次

*/

@Scheduled(cron = "0 0 0/1 * * ?")

@Override

public void initJsapiTicket() {

try {

log.info("<====正在刷新 JSAPI_TICKET ==>");

WxParament.JSAPI_TICKET = GetToken.getTicket(ymlParament.getH_app_id(), ymlParament.getH_app_secret(),

WxParament.ACCESS_TOKEN);

} catch (Exception e) {

log.error("<====initJsapiTicket初始化失败!==>" + e);

e.printStackTrace();

}

log.info("<====刷新 JSAPI_TICKET 成功,值为 ==>" + WxParament.JSAPI_TICKET);

}

GetToken

/**

* ~~~~~~~~~ 第一步 ~~~~~~~~~

* 有效期两个小时,注意缓存 获取access_token,记得配置ip白名单

* 参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):

* https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

* @param appid

* @param secret

* @throws Exception

*/

public static String getAccessToken(String appid, String secret) throws Exception {

String res=HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token",

"grant_type=client_credential&appid=" + appid + "&secret=" + secret);

String access_token=JSON.parseObject(res).getString("access_token");

if(IsNull.isNull(access_token)) {

throw new Exception(res);

}

return access_token;

}

/**

* ~~~~~~~~~ 第二步 ~~~~~~~~~

* 有效期两个小时,注意缓存 用第一步拿到的access_token 采用http

* GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):

* https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

* @param appid

* @param secret

* @param accessToken 如果 accessToken 传null 则重新获取 access_token,如果accessToken有值,则直接获取Ticket

* @throws Exception

*/

public static String getTicket(String appid, String secret,String accessToken) throws Exception {

String token=IsNull.isNull(accessToken)?getAccessToken(appid, secret):accessToken;

String res=HttpUtil.get("https://api.weixin.qq.com/cgi-bin/ticket/getticket",

"access_token=" + token + "&type=jsapi");

String ticket=JSON.parseObject(res).getString("ticket");

if(IsNull.isNull(ticket)) {

throw new Exception(res);

}

return ticket;

}

/**

*

* jsapi_ticket是公众号用于调用微信JS接口的临时票据

* 前台统一js签名,参考说明如下

* 1、业务说明:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#3

* 2、签名算法:1-JS-SDK使用权限签名算法:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html#62

* @param jsapi_ticket,必须传入,每次有效时间两小时,如果频繁获取会报错

* @param url url(当前支付页面的URL,不包含#及其后面部分)

* @return

*/

public static Map getJsSignature(String jsapi_ticket, String url) {

return sign(jsapi_ticket, url);

}

/*签名算法*/

private static Map sign(String jsapi_ticket, String url) {

Map ret = new HashMap();

String nonce_str = UUID.randomUUID().toString();;

String timestamp = Long.toString(System.currentTimeMillis() / 1000);

String signature = null;

// 注意这里参数名必须全部小写,且必须有序

String string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "&timestamp=" + timestamp + "&url=" + url;

try {

MessageDigest crypt = MessageDigest.getInstance("SHA-1");

crypt.reset();

crypt.update(string1.getBytes("UTF-8"));

signature = byteToHex(crypt.digest());

} catch (NoSuchAlgorithmException e) {

e.printStackTrace();

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

}

ret.put("url", url);

ret.put("jsapi_ticket", jsapi_ticket);

ret.put("nonceStr", nonce_str);

ret.put("timestamp", timestamp);

ret.put("signature", signature);

return ret;

}

private static String byteToHex(final byte[] hash) {

Formatter formatter = new Formatter();

for (byte b : hash) {

formatter.format("%02x", b);

}

String result = formatter.toString();

formatter.close();

return result;

}

HttpUtil

public static String get(String urlStr, Map parameters) throws IOException {

URL url = new URL(urlStr);

HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();

httpURLConnection.setDoInput(true);

httpURLConnection.setDoOutput(true); // 设置该连接是可以输出的

httpURLConnection.setRequestMethod("GET"); // 设置请求方式

httpURLConnection.setRequestProperty("charset", "utf-8");

PrintWriter pw = new PrintWriter(new BufferedOutputStream(httpURLConnection.getOutputStream()));

StringBuffer parameter = new StringBuffer();

parameter.append("1=1");

for (Entry entry : parameters.entrySet()) {

parameter.append("&" + entry.getKey() + "=" + entry.getValue());

}

pw.write(parameter.toString());// 向连接中写数据(相当于发送数据给服务器)

pw.flush();

pw.close();

BufferedReader br = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(), "utf-8"));

String line = null;

StringBuilder sb = new StringBuilder();

while ((line = br.readLine()) != null) { // 读取数据

sb.append(line + "\n");

}

br.close();

return sb.toString();

}

JacksonUtil

public class JacksonUtil {

public static String parseString(String body, String field) {

ObjectMapper mapper = new ObjectMapper();

JsonNode node;

try {

node = mapper.readTree(body);

JsonNode leaf = node.get(field);

if (leaf != null) {

return leaf.asText();

}

} catch (IOException e) {

e.printStackTrace();

}

return null;

}

}

微信支付初始化参数

@ApiOperation("微信支付初始化参数")

@PostMapping("getJsSignature")

public R getJsSignature(@RequestBody String body){

//(当前支付页面的URL,不包含#及其后面部分)

String url = JacksonUtil.parseString(body, "url");

if(IsNull.isNull(url)) {

return R.error("参数不能为空!");

}

Map res = new HashMap<>();

Map jsSignature = GetToken.getJsSignature(WxParament.JSAPI_TICKET, url);

res.put("appId", ymlParament.getH_app_id());

res.put("timestamp",jsSignature.get("timestamp"));

res.put("nonceStr",jsSignature.get("nonceStr"));

res.put("signature",jsSignature.get("signature"));

return R.ok(res);

}

获取openid

微信统一下单初始化微信支付配置

@Component

public class WxConfig {

@Autowired

private YmlParament ymlParament;

/**

* 初始化微信支付配置

* @throws Exception

*/

@Bean(autowire = Autowire.BY_NAME,value = WxParament.H5_WX_PAY)

public WXPay setH5WXPay() throws Exception {

return new WXPay(new WxPayConfig(

ymlParament.getH_cert_path(),

ymlParament.getH_app_id(),

ymlParament.getMch_id(),

ymlParament.getH_key()));

}

}

WxPayConfig

public class WxPayConfig implements WXPayConfig {

private byte[] certData;

private String appID;

private String mchID;

private String key;

public WxPayConfig(String certPath, String appID,String mchID,String key) throws Exception {

File file = new File(certPath);

InputStream certStream = new FileInputStream(file);

this.certData = new byte[(int) file.length()];

certStream.read(this.certData);

certStream.close();

this.appID = appID;

this.mchID = mchID;

this.key = key;

}

}微信下单接口,关键代码(服务层)

@Resource(name = WxParament.H5_WX_PAY)

private WXPay wxH5Pay;

/* 微信统一下单 */

private Map wxUnifiedOrder(String orderNo, String orderFee, String requestIp, String openid) throws RuntimeException {

Map data = new HashMap();

data.put("nonce_str", WXPayUtil.generateNonceStr());

data.put("body", "我来下单啦");

data.put("out_trade_no", orderNo);

data.put("sign_type", "MD5");

data.put("total_fee", orderFee);

data.put("spbill_create_ip", requestIp);

data.put("openid", openid);

data.put("notify_url",ymlParament.getWx_callback_url());

data.put("trade_type", "JSAPI"); // 此处指定为JSAPI支付

Map wxOrderResult = WxPay.unifiedorder(data,wxH5Pay);

if("FAIL".equals(wxOrderResult.get("return_code"))){

throw new RuntimeException(wxOrderResult.get("return_msg"));

}

/* IsNull自定义,主要判断非空 */

if (IsNull.isNull(wxOrderResult.get("prepay_id"))) {

throw new RuntimeException("微信支付下单成功后,返回的prepay_id为空");

}

return wxOrderResult;

}

@Override

public Map insertWxH5Pay(String orderNo,String orderFee, String requestIp, String openid) {

try {

/* 微信下单 */

Map wxOrderResult = wxUnifiedOrder(orderNo, orderFee, requestIp, openid);

Map chooseWXPay = new HashMap<>();

/* 这里我遇到一个大坑,这里的key值是要驼峰式写法,而APP支付却不用!!! */

chooseWXPay.put("appId", ymlParament.getH_app_id());

chooseWXPay.put("timeStamp", WxUtils.create_timestamp());

chooseWXPay.put("nonceStr", wxOrderResult.get("nonce_str"));

chooseWXPay.put("package", "prepay_id=" + wxOrderResult.get("prepay_id"));

chooseWXPay.put("signType", "MD5");

String signature = WXPayUtil.generateSignature(chooseWXPay, ymlParament.getH_key());

chooseWXPay.put("paySign", signature);

return chooseWXPay;

} catch (Exception e) {

e.printStackTrace();

throw new RuntimeException(e.getMessage());

}

}controller层(略)

微信支付成功回调关键代码

@Resource(name = WxParament.H5_WX_PAY)

private WXPay wxH5Pay;

@ApiOperation("微信支付回调")

@PostMapping("callback")

public String callback(HttpServletRequest request) throws Exception {

try {

// 1、获取参数

Map params = getMapByRequest(request);

log.info("微信回调回来啦!!!!" + params);

// 2、校验

checkCallbackWxPay(params);

//业务逻辑

return wxPaySuccess();

} catch (Exception e) {

e.printStackTrace();

return wxPayFail(e.getMessage());

}

}

/**

* 获取微信过来的请求参数

* @param request

* @return

* @throws Exception

*/

private static Map getMapByRequest(HttpServletRequest request) throws Exception{

InputStream inStream = request.getInputStream();

ByteArrayOutputStream outSteam = new ByteArrayOutputStream();

byte[] buffer = new byte[1024];

int len = 0;

while ((len = inStream.read(buffer)) != -1) {

outSteam.write(buffer, 0, len);

}

Map ret= WXPayUtil.xmlToMap(new String(outSteam.toByteArray(), "utf-8"));

outSteam.close();

inStream.close();

return ret;

}

/*校验 */

private void checkCallbackWxPayAircraft(Map params)

throws Exception {

if ("FAIL".equals(params.get("result_code"))) {

throw new Exception("微信支付失败");

}

if (!checkWxCallbackSing(params, wxH5Pay)) {

throw new Exception("微信支付回调签名认证失败");

}

//校验金额

//判断订单是否重复

//....业务逻辑

}

/**

* 检查微信回调签名 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7

*

* @param wp 传入:@Autowired WXPay

*/

private static boolean checkWxCallbackSing(Map notifyMap, WXPay wp) throws Exception {

return wp.isPayResultNotifySignatureValid(notifyMap);

}

/**

* 微信支付返回参数结果封装

*/

private static String wxPaySuccess() throws Exception {

Map succResult = new HashMap();

succResult.put("return_code", "SUCCESS");

succResult.put("return_msg", "OK");

return WXPayUtil.mapToXml(succResult);

}

/**

* @param mess 错误消息提示

* @return 微信返回错我的提示

* @throws Exception

*/

private static String wxPayFail(String mess) throws Exception {

Map succResult = new HashMap();

succResult.put("return_code", "FAIL");

succResult.put("return_msg", IsNull.isNull(mess)?"自定义异常错误!":mess);

return WXPayUtil.mapToXml(succResult);

}

补充如果不想验证签名,还有一种方式判断是否支付成功,就是调用微信查询订单接口查看是否支付成功关键代码

/*调用微信查询订单接口*/

Map orderqueryRes = orderquery(wXH5Pay,params.get("out_trade_no"));

/*交易成功*/

if (!"SUCCESS".equals(orderqueryRes.get("trade_state"))){

throw new Exception("<===微信支付失败====>订单号为【"+ params.get("out_trade_no")+ "】的订单");

}

/**

* 查询支付结果 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2

*/

private static Map orderquery(WXPay wxpay, String outTradeNo) throws Exception {

Map parament = new HashMap();

parament.put("out_trade_no", outTradeNo);

return wxpay.orderQuery(parament);

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值