Java微信支付全教程demo【公眾號支付】
直入主題:
- 註冊微信公眾號、微信支付商戶號,並做好基礎配置(不解釋配置詳情,無非是獲取 appid,商戶號等)
- 微信支付介面程式碼
- 微信支付回撥介面程式碼
- 微信h5支付頁面喚起字元密碼介面完成支付
1,寫程式碼之前準備工作
(1):利用開原始碼 weixin-java-tools來開發效率很高,免去了很多繁瑣的程式碼開發量;
搭建maven工程,引入:
-
<!-- 微信支付 開始-->
-
<dependency>
-
<groupId>com.github.binarywang</groupId>
-
<artifactId>weixin-java-pay</artifactId>
-
<version>2.8.0</version>
-
</dependency>
-
<dependency>
-
<groupId>com.github.binarywang</groupId>
-
<artifactId>weixin-java-pay</artifactId>
-
<version>2.8.0</version>
-
<classifier>sources</classifier>
-
</dependency>
-
<dependency>
-
<groupId>com.github.binarywang</groupId>
-
<artifactId>weixin-java-mp</artifactId>
-
<version>2.8.0</version>
-
</dependency>
-
<dependency>
-
<groupId>com.github.binarywang</groupId>
-
<artifactId>weixin-java-mp</artifactId>
-
<version>2.8.0</version>
-
<classifier>sources</classifier>
-
</dependency>
-
<dependency>
-
<groupId>com.github.binarywang</groupId>
-
<artifactId>weixin-java-common</artifactId>
-
<version>2.8.0</version>
-
</dependency>
-
<dependency>
-
<groupId>com.github.binarywang</groupId>
-
<artifactId>weixin-java-common</artifactId>
-
<version>2.8.0</version>
-
<classifier>sources</classifier>
-
</dependency>
-
<!-- 微信支付 結束 -->
(2):微信支付開發介面需要 用到使用者openId引數,至於微信授權獲取使用者openId這裡不做解釋;
(3):獲得微信支付所要的配置檔案,
這裡我配置的有引數的都是必須要填寫的,其他的可以不寫,這裡WX_WEB_URL這個是你網站的網址在回撥的時候需要用到,我把這個地址配置到了配置檔案裡了。
-
#微信對接配置
-
WX_APPID=wx11231231231
-
WX_APPSECRET=321321321321321321
-
WX_TOKEN=
-
WX_AESKEY=
-
#微信支付商戶號
-
WX_mchId=432432432
-
#微信支付平臺商戶API金鑰
-
WX_mchKey=fgfdfdewrewrwer432432
-
#服務商模式下的子商戶公眾賬號ID
-
WX_subAppId=
-
#服務商模式下的子商戶號
-
WX_subMchId=
-
WX_keyPath=
-
WX_WEB_URL=http://7cvyn3.natappfree.cc/zc
(4):這個是我們從微信平臺上獲取的配置檔案,還有兩個重要的授權地址是我們要在微信平臺上配置的,這個也是微信極為坑的一點
1:在微信公眾平臺——》許可權介面——》網頁授權獲取使用者基本資訊
必須填入外網域名並且要下載提示裡的.txt檔案,放到你網站的跟目錄下,可以通過網站直接訪問的路徑
這一步配置好後,還有一步就是微信支付的路徑配置,這裡最坑,我已在崩潰的邊緣,這就是支付授權目錄,注意這裡是目錄,不是僅僅是域名,後面要加你要掉起支付的html頁面的最後一個路徑要加/。當初我只寫了域名,然後怎麼都掉用起不來支付介面。坑了我好幾天,
到這一步,如果都沒有問,你基礎的配置就完成了,下面就到了擼程式碼的步驟了,由於我們用的是開源工具,程式碼量其實非常簡單,只需要一點配置就好。
2:擼程式碼,Java搞起來
2.1:先把微信配置檔案 通過spring寫到bean中;
-
<!-- 微信基礎配置 -->
-
<bean name="wxConfig" class="me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage">
-
<property name="appId" value="${WX_APPID}" />
-
<property name="secret" value="${WX_APPSECRET}" />
-
</bean>
-
<!-- 微信核心service注入 -->
-
<bean id="wxMpService" class="me.chanjar.weixin.mp.api.impl.WxMpServiceImpl">
-
<property name="wxMpConfigStorage" ref="wxConfig" />
-
</bean>
-
<!-- 微信支付配置 -->
-
<bean name="wxPayConfig" class="com.github.binarywang.wxpay.config.WxPayConfig">
-
<property name="appId" value="${WX_APPID}" />
-
<property name="mchId" value="${WX_mchId}" />
-
<property name="mchKey" value="${WX_mchKey}" />
-
<property name="subAppId" value="${WX_subAppId}" />
-
<property name="subMchId" value="${WX_subMchId}" />
-
<property name="keyPath" value="${WX_keyPath}" />
-
</bean>
-
<!-- 微信支付service注入 -->
-
<bean id="wxPayService" class="com.github.binarywang.wxpay.service.impl.WxPayServiceImpl">
-
<property name="config" ref="wxPayConfig" />
-
</bean>
2.2:直接進入重點,微信支付控制器,微信支付欲支付介面和回撥介面:相關工具類封裝在下面。
支付那我已經把業務和微信支付做了分離。
-
/**
-
* 微信對接控制器,微信支付
-
* Project Name:zc_app_api
-
* File Name:WxInitController.java
-
* Package Name:com.zc.app.api.controller.weixin
-
* Date:2017年9月30日下午3:10:19
-
* @author 吉文劍
-
*/
-
@Controller
-
@RequestMapping("/wxPay/")
-
public class WxPayController extends BaseController {
-
private final Logger logger = LoggerFactory.getLogger("WxPayController");
-
@Autowired
-
private WxPayConfig payConfig;
-
@Autowired
-
private WxPayService payService;
-
/**
-
*微信公眾號支付,業務方法
-
* @param response
-
* @param request
-
*/
-
@RequestMapping(value = "toPayInfo")
-
public void getJSSDKPayInfo(HttpServletResponse response,
-
HttpServletRequest request) {
-
DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
-
String orderSubject = "停車繳費0.01元";//商品描述
-
String merchantTradeNo = df.format(new Date());//商戶訂單號
-
Integer totalAmount = 1;//訂單總金額,單位為分
-
String goodsDesc = "萬達停車場下午1點-9點";
-
String gooodsCode = "code"+merchantTradeNo;
-
Map<String, Object> payData = getPayData( merchantTradeNo,
-
orderSubject,
-
totalAmount,
-
goodsDesc,
-
gooodsCode);
-
resultPayData(response,payData);
-
return;
-
}
-
/** 微信公眾號支付介面,通過引數生成網頁微信js支付引數,掉起支付介面必須引數
-
* @param merchantTradeNo 商戶訂單號(必填)
-
* @param orderSubject 訂單名稱(必填)
-
* @param totalAmount 訂單金額,單位分(必填)
-
* @param goodsDesc 商品描述(可空)
-
* @param gooodsCode 商品編碼(可空)
-
* Date:2017年12月4日上午11:04:04
-
* @author 吉文劍
-
*/
-
private Map<String, Object> getPayData(String merchantTradeNo,
-
String orderSubject,
-
Integer totalAmount,
-
String goodsDesc,
-
String gooodsCode){
-
Map<String, Object> map = new HashMap<String, Object>();
-
ConstantUtils instance = ConstantUtils.getInstance();
-
ZuUser sessUser = (ZuUser) session.getAttribute(BaseController.SESSION_USER);
-
if(null == sessUser || !StringUtils.isValid(sessUser.getOpenId())){
-
map.put("result", false);
-
return map;
-
}
-
WxPayUnifiedOrderRequest prepayInfo = WxPayUnifiedOrderRequest.newBuilder()
-
.openid(sessUser.getOpenId())
-
.outTradeNo(merchantTradeNo)
-
.totalFee(totalAmount)
-
.body(orderSubject)
-
.tradeType(WeixinUtils.TRADE_TYPE)
-
.spbillCreateIp(request.getRemoteAddr())
-
.notifyURL(instance.getPropertyValue("WEB_URL")+WeixinUtils.NOTIFY_URL)
-
.nonceStr(WeixinUtils.getNonceStr())
-
.detail(goodsDesc)
-
.productId(gooodsCode)
-
.build();
-
try {
-
Map<String, String> payInfo = this.payService.getPayInfo(prepayInfo);
-
map.put("result", true);
-
map.put("data", payInfo);
-
} catch (WxPayException e) {
-
map.put("result", false);
-
map.put("data", e.getErrCodeDes());
-
this.logger.error(e.getErrCodeDes());
-
e.printStackTrace();
-
}
-
return map;
-
}
-
/**
-
* 微信通知支付結果的回撥地址,notifyCallback
-
*
-
* @param request
-
* @param response
-
*/
-
@RequestMapping(value = "notifyCallback")
-
public void notifyCallback(HttpServletRequest request, HttpServletResponse response) {
-
try {
-
synchronized (this) {
-
Map<String, String> kvm = XMLUtil.parseRequestXmlToMap(request);
-
String orderCode = null;//回撥 支付訂單號
-
String resultCode = null;//回撥支付是否成功狀態嗎
-
String totalFee = null;//支付金額
-
System.out.println("微信支付回撥引數:");
-
System.out.println(kvm);
-
if (SignUtils.checkSign(kvm, this.payConfig.getMchKey())) {
-
orderCode = kvm.get("out_trade_no");
-
resultCode = kvm.get("result_code");
-
totalFee = kvm.get("total_fee");
-
if ("SUCCESS".equals(resultCode)) {
-
//TODO(user) 微信伺服器通知此回撥介面支付成功後,通知給業務系統做處理
-
logger.info("out_trade_no: " + orderCode + " pay SUCCESS!");
-
response.getWriter().write(WeixinUtils.WX_PAY_SUCCESS);
-
} else {
-
this.logger.error("out_trade_no: " + orderCode + " result_code is FAIL");
-
response.getWriter().write(WeixinUtils.WX_PAY_FAIL);
-
}
-
} else {
-
this.logger.error("out_trade_no: " + orderCode + " check signature FAIL");
-
response.getWriter().write(WeixinUtils.WX_PAY_SIGN_FAIL);
-
}
-
if("SUCCESS".equals(resultCode)){
-
//支付成功的業務邏輯
-
//totalFee 要判斷支付金額是否等於訂單金額!!!
-
System.out.println("支付成功:訂單號:"+orderCode+",支付金額:"+totalFee);
-
}else{
-
//支付失敗的業務邏輯
-
System.out.println("微信支付 回撥 :*-************支付失敗");
-
}
-
}
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
/**
-
* 客戶端返回JSON字串
-
* @param response
-
* @param object
-
* @return
-
*/
-
protected String resultPayData(HttpServletResponse response, Object object) {
-
try {
-
response.reset();
-
response.setContentType("application/json");
-
response.setCharacterEncoding("utf-8");
-
//解決跨域問題
-
response.setHeader("Access-Control-Allow-Origin", "*");
-
response.getWriter().print(new Gson().toJson(object));
-
return null;
-
} catch (IOException e) {
-
return null;
-
}
-
}
-
}
2.3:自己整理了一個工具類,你們後面會用的到的,基本都是寫配置項,不會變的:
-
/**
-
* 微信通用工具類
-
* Project Name:zc_app_api
-
* File Name:WeixinUtils.java
-
* Package Name:com.zc.app.api.utils
-
* Date:2017年10月18日下午1:45:01
-
* @author 吉文劍
-
*/
-
public class WeixinUtils {
-
/** 微信支付回撥支付成功,返回成功結果 */
-
public static final String WX_PAY_SUCCESS = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[ok]]></return_msg></xml>";
-
/** 微信支付回撥支付失敗,返回失敗結果 */
-
public static final String WX_PAY_FAIL = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[result_code is FAIL]]></return_msg></xml>";
-
/** 微信支付回撥支付sign驗證失敗,返回sign驗證失敗結果 */
-
public static final String WX_PAY_SIGN_FAIL = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[check signature FAIL]]></return_msg></xml>";
-
/** 微信支付回撥地址路徑 */
-
public static final String NOTIFY_URL = "/wxPay/notifyCallback.do";
-
/** 微信獲取微信使用者授權後用戶資訊 地址路徑 */
-
public static final String OAUTH2_USERINFO_URL = "/wx/getOAuth2UserInfo.do";
-
/** 微信官方api介面 */
-
public static final String URL_OAUTH2 = "https://open.weixin.qq.com/connect/oauth2/authorize?";
-
/** 獲取微信使用者資訊 */
-
public static final String SCOPE = "snsapi_userinfo";
-
/** 交易型別 :jsapi代表微信公眾號支付 */
-
public static final String TRADE_TYPE = "JSAPI";
-
/** 獲取微信openId URL */
-
public String getWxOpenIdUrl(String toUrl){
-
ConstantUtils instance = ConstantUtils.getInstance();
-
StringBuffer url = new StringBuffer();
-
url.append(URL_OAUTH2)
-
.append("appid=").append(instance.getPropertyValue("WX_APPID"))
-
.append("&redirect_uri=").append(instance.getPropertyValue("WX_WEB_URL") + OAUTH2_USERINFO_URL)
-
.append("&response_type=code")
-
.append("&scope=").append(SCOPE)
-
.append("&state=").append(toUrl)
-
.append("#wechat_redirect");
-
return url.toString();
-
}
-
/**
-
* 獲得微信支付隨機碼
-
* @return
-
* Date:2017年12月4日上午9:50:48
-
* @author 吉文劍
-
*/
-
public static String getNonceStr(){
-
return UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
-
}
-
}
2.4:其他相關工具類:
-
/**
-
* @ClassName: ConstantUtils
-
* @Description: (讀取配置檔案的資訊,一些公共的屬性,引數配置在)
-
* @author JiWenJian
-
*/
-
public class ConstantUtils {
-
private static final String FILEPATH = "/zc_app_api.properties";
-
private static ConstantUtils instance;
-
private ConstantUtils(){
-
}
-
public static ConstantUtils getInstance(){
-
if(null == instance ){
-
instance = new ConstantUtils();
-
}
-
return instance;
-
}
-
/**
-
* @Description: (讀取檔案資訊)
-
* @author JiWenJian
-
* @date 2012-11-22 下午01:42:08
-
* @param key
-
* @return
-
*/
-
public String getPropertyValue(String key) {
-
Properties props = new Properties();
-
try {
-
InputStream in = getClass().getResourceAsStream(FILEPATH);
-
props.load(in);
-
return props.getProperty(key);
-
} catch (Exception e) {
-
e.printStackTrace();
-
return null;
-
}
-
}
-
/**
-
* @Description: (讀取配置Integer數值資訊)
-
* @author JiWenJian
-
* @date 2012-11-22 下午01:42:08
-
* @param key
-
* @return
-
*/
-
public Integer getPropertyIntegerValue(String key) {
-
Properties props = new Properties();
-
try {
-
InputStream in = getClass().getResourceAsStream(FILEPATH);
-
props.load(in);
-
return Integer.parseInt(props.getProperty(key));
-
} catch (Exception e) {
-
e.printStackTrace();
-
return null;
-
}
-
}
-
}
-
/**
-
* xml解析成map物件
-
*/
-
public class XMLUtil {
-
/**
-
* 將微信伺服器傳送的Request請求中Body的XML解析為Map
-
*
-
* @param request
-
* @return
-
* @throws Exception
-
*/
-
public static Map<String, String> parseRequestXmlToMap(HttpServletRequest request) throws Exception {
-
// 解析結果儲存在HashMap中
-
Map<String, String> resultMap;
-
InputStream inputStream = request.getInputStream();
-
resultMap = parseInputStreamToMap(inputStream);
-
return resultMap;
-
}
-
/**
-
* 將輸入流中的XML解析為Map
-
*
-
* @param inputStream
-
* @return
-
* @throws DocumentException
-
* @throws IOException
-
*/
-
public static Map<String, String> parseInputStreamToMap(InputStream inputStream) throws DocumentException, IOException {
-
// 解析結果儲存在HashMap中
-
Map<String, String> map = new HashMap<String, String>();
-
// 讀取輸入流
-
SAXReader reader = new SAXReader();
-
Document document = reader.read(inputStream);
-
//得到xml根元素
-
Element root = document.getRootElement();
-
// 得到根元素的所有子節點
-
List<Element> elementList = root.elements();
-
//遍歷所有子節點
-
for (Element e : elementList) {
-
map.put(e.getName(), e.getText());
-
}
-
//釋放資源
-
inputStream.close();
-
return map;
-
}
-
/**
-
* 將String型別的XML解析為Map
-
*
-
* @param str
-
* @return
-
* @throws Exception
-
*/
-
public static Map<String, String> parseXmlStringToMap(String str) throws Exception {
-
Map<String, String> resultMap;
-
InputStream inputStream = new ByteArrayInputStream(str.getBytes("UTF-8"));
-
resultMap = parseInputStreamToMap(inputStream);
-
return resultMap;
-
}
-
}
3:下面到了前臺html頁面,只剩下最後一小步了,只要掉起微信支付密碼介面就大功告成了;
-
<head>
-
<script type="text/javascript" src="http://g.alicdn.com/sj/lib/jquery/dist/jquery.min.js"></script>
-
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
-
<script src="../js/common.js"></script>
-
<script type="text/javascript">
-
function toPay(){
-
if (typeof WeixinJSBridge == "undefined"){
-
if( document.addEventListener ){
-
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
-
}else if (document.attachEvent){
-
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
-
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
-
}
-
}else{
-
onBridgeReady();
-
}
-
}
-
function onBridgeReady(){
-
$.ajax({
-
type: "POST",
-
url: DEF_GLOBAL_DOMAIN+"/wxPay/toPayInfo.do",
-
success: function(data){
-
console.log(data);
-
WeixinJSBridge.invoke(
-
'getBrandWCPayRequest', {
-
"appId" : data.data.appId,
-
"timeStamp": data.data.timeStamp,
-
"nonceStr" : data.data.nonceStr,
-
"package" : data.data.package,
-
"signType" : data.data.signType,
-
"paySign" : data.data.paySign
-
},function(res){
-
$("#msgId").html(res.err_msg);
-
if(res.err_msg == "get_brand_wcpay_request:ok"){
-
$("#resId").html("支付成功");
-
// location.href="weixinPayResult.html";//支付成功跳轉到指定頁面
-
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
-
$("#resId").html("支付取消");
-
}else{
-
$("#resId").html("支付失敗");
-
}
-
});
-
}
-
});
-
}
-
</script>
-
</head>
-
<body>
-
<div class="content">
-
<div class="form-area">
-
<div class="inp">
-
支付0.01元
-
</div>
-
<button class="em-submit-st2" onclick="toPay()" >
-
確定支付
-
</button>
-
</div>
-
結果:
-
<p/>
-
<div id="resId"></div>
-
引數:
-
<p/>
-
<div id="invokeId"></div>
-
<br/><p/>
-
返回:
-
<p/>
-
<div id="msgId"></div>
-
</div>
-
</body>
-
</html>
到這裡整個微信支付就完成了,總結:難點在於微信平臺繁瑣的配置,很繁瑣,錯一個整個都跑不通,還以為是自己程式碼有問題,坑爹。程式碼層,感覺沒有什麼複雜量,就做一些配置就好了,至於微信老是喜歡用各種 簽名 sign
這個工具已經給我們解決了,只要各個地方的配置沒有問題,其實就很簡單了。
整個執行流程 是 :
微信點選支付按鈕——》
傳送ajax到支付請求控制器——》
返回支付引數——》
用支付引數,呼叫微信內嵌的掉起支付js方法,發起支付——》
支付結果同步返回結果——》
支付結果非同步傳送到後臺回撥控制器做結果處理
感謝同是天涯程式碼人,花這麼長時間來參讀本文章,加油把。。。