1.開發(fā)微信公眾號(hào)支付準(zhǔn)備資料
①APPID,這個(gè)數(shù)據(jù)我們可以從“申請(qǐng)微信支付成功”的郵件中獲取。
②AppSecret,這個(gè)數(shù)據(jù),大家可以看上圖中獲取。
③Mch_id,這是值是微信支付商戶號(hào),大家可以從郵件中獲取
④KEY,這個(gè)參數(shù)KEY是在商戶后臺(tái)配置的一個(gè)32位的key,微信商戶平臺(tái)-賬戶設(shè)置-安全設(shè)置-api安全,在這里設(shè)置。這個(gè)值是可以自行設(shè)置的。
2.設(shè)置支付目錄
支付授權(quán)目錄說明:
1、商戶最后請(qǐng)求拉起微信支付收銀臺(tái)的頁面地址我們稱之為“$\color{red}{支付目錄}$”,例如:https://www.weixin.com/pay.php。
2、商戶實(shí)際的支付目錄必須和在微信支付商戶平臺(tái)設(shè)置的一致,否則會(huì)報(bào)錯(cuò)“當(dāng)前頁面的URL未注冊(cè):”
支付授權(quán)目錄設(shè)置說明:
登錄微信支付商戶平臺(tái)(pay.weixin.qq.com)-->產(chǎn)品中心-->開發(fā)配置,設(shè)置后一般5分鐘內(nèi)生效。
支付授權(quán)目錄校驗(yàn)規(guī)則說明:
1、如果支付授權(quán)目錄設(shè)置為頂級(jí)域名(例如:https://www.weixin.com/ ),那么只校驗(yàn)頂級(jí)域名,不校驗(yàn)后綴;
2、如果支付授權(quán)目錄設(shè)置為多級(jí)目錄,就會(huì)進(jìn)行全匹配,例如設(shè)置支付授權(quán)目錄為https://www.weixin.com/abc/123/,則實(shí)際請(qǐng)求頁面目錄不能為https://www.weixin.com/abc/,也不能為https://www.weixin.com/abc/123/pay/,必須為https://www.weixin.com/abc/123/
具體看官方文檔:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3
3.首先要看微信支付的業(yè)務(wù)流程,官方文檔——>開發(fā)微信支付
微信支付的流程(主要步驟):
1、用戶訪問微信OAuth2.0網(wǎng)站,通過OAuth2.0的重定向獲得code
2、根據(jù)code獲得用戶的標(biāo)識(shí)符openid,這個(gè)參數(shù)在調(diào)用統(tǒng)一下單接口中會(huì)用到
3、調(diào)用統(tǒng)一下單API獲得prepay_id
4、獲得prepay_id后,接下來要調(diào)用通過“網(wǎng)頁端調(diào)起支付API”,這個(gè)調(diào)用的API需要一系列參數(shù),這些參數(shù)我們?cè)诤笈_(tái)進(jìn)行組裝,通過JSON傳到前臺(tái)去
5、獲得數(shù)據(jù)后通過調(diào)用JSPAI發(fā)起微信支付
6、用戶輸入支付密碼,支付完成
7、等待微信回調(diào),回調(diào)中處理業(yè)務(wù)
8、支付流程完畢
微信支付所需依賴:
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>commons-codec</groupId>
? ? ? ? ? ? <artifactId>commons-codec</artifactId>
? ? ? ? ? ? <version>1.6</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>commons-logging</groupId>
? ? ? ? ? ? <artifactId>commons-logging</artifactId>
? ? ? ? ? ? <version>1.1.3</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.apache.httpcomponents</groupId>
? ? ? ? ? ? <artifactId>fluent-hc</artifactId>
? ? ? ? ? ? <version>4.3.6</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.apache.httpcomponents</groupId>
? ? ? ? ? ? <artifactId>httpclient</artifactId>
? ? ? ? ? ? <version>4.3.6</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.apache.httpcomponents</groupId>
? ? ? ? ? ? <artifactId>httpclient-cache</artifactId>
? ? ? ? ? ? <version>4.3.6</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.apache.httpcomponents</groupId>
? ? ? ? ? ? <artifactId>httpcore</artifactId>
? ? ? ? ? ? <version>4.3.3</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.apache.httpcomponents</groupId>
? ? ? ? ? ? <artifactId>httpmime</artifactId>
? ? ? ? ? ? <version>4.3.6</version>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.jsoup</groupId>
? ? ? ? ? ? <artifactId>jsoup</artifactId>
? ? ? ? ? ? <version>1.7.2</version>
? ? ? ? </dependency>
? ? ? ? <!-- FastJson -->
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>com.alibaba</groupId>
? ? ? ? ? ? <artifactId>fastjson</artifactId>
? ? ? ? ? ? <version>1.2.1</version>
? ? ? ? </dependency>
? ? </dependencies>
4、微信支付整體流程實(shí)現(xiàn)
1.獲取code值,我這邊是通過$\color{red}{前端寫的js}$來獲取code值的
2.通過Code 獲取用戶 openid 和 access_token
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import ***********************.WeChatConst;//一些常量
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* Created by Arthur on 2015/11/10 16:45.
*/
public class WeChatUtil {
? ? /**
? ? * 通過Code 獲取用戶 openid 和 access_token
? ? * @param code
? ? * @return
? ? * @throws IOException
? ? */
? ? public static Map<String,Object> getOpenIdByCode(String code) throws IOException {
? ? ? ? /**
? ? ? ? * 設(shè)置訪問路徑
? ? ? ? */
? ? ? ? HttpPost httppost = new HttpPost("https://api.weixin.qq.com/sns/oauth2/access_token");
? ? ? ? /**
? ? ? ? * 組裝請(qǐng)求參數(shù)
? ? ? ? */
? ? ? ? String reqEntityStr = "appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
? ? ? ? reqEntityStr = reqEntityStr.replace("APPID", WeChatConst.APPID);
? ? ? ? reqEntityStr = reqEntityStr.replace("SECRET", WeChatConst.APP_SECRET);
? ? ? ? reqEntityStr = reqEntityStr.replace("CODE", code);
? ? ? ? StringEntity reqEntity = new StringEntity(reqEntityStr);
? ? ? ? /**
? ? ? ? * 設(shè)置瀏覽器
? ? ? ? */
? ? ? ? DefaultHttpClient httpclient = new DefaultHttpClient();
? ? ? ? /**
? ? ? ? * 設(shè)置參數(shù)
? ? ? ? */
? ? ? ? httppost.setEntity(reqEntity);
? ? ? ? /**
? ? ? ? * 發(fā)起請(qǐng)求
? ? ? ? */
? ? ? ? HttpResponse response = httpclient.execute(httppost);
? ? ? ? /**
? ? ? ? * 獲得請(qǐng)求內(nèi)容
? ? ? ? */
? ? ? ? String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));
? ? ? ? /**
? ? ? ? * 分析內(nèi)容
? ? ? ? */
? ? ? ? JSONObject jsonObject = (JSONObject) JSON.parse(strResult);
? ? ? ? Map<String,Object> map = new HashMap<>();
? ? ? ? map.put("openid",jsonObject.get("openid"));
? ? ? ? map.put("access_token",jsonObject.get("access_token"));
? ? ? ? map.put("refresh_token",jsonObject.get("refresh_token"));
? ? ? ? return map;
? ? }
? ? /**
? ? * 統(tǒng)一下單
? ? * 獲得PrePayId
? ? * @param body? 商品或支付單簡(jiǎn)要描述
? ? * @param out_trade_no 商戶系統(tǒng)內(nèi)部的訂單號(hào),32個(gè)字符內(nèi)、可包含字母
? ? * @param total_fee? 訂單總金額,單位為分
? ? * @param IP? ? APP和網(wǎng)頁支付提交用戶端ip
? ? * @param notify_url 接收微信支付異步通知回調(diào)地址
? ? * @param openid 用戶openId
? ? * @throws IOException
? ? */
? ? public static String unifiedorder(String body,String out_trade_no,Integer total_fee,String IP,String notify_url,String openid)throws IOException {
? ? ? ? /**
? ? ? ? * 設(shè)置訪問路徑
? ? ? ? */
? ? ? ? HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
? ? ? ? /**
? ? ? ? * 組裝請(qǐng)求參數(shù)
? ? ? ? * 按照ASCII排序
? ? ? ? */
? ? ? ? String reqEntityStr = "appid=APPID" +
? ? ? ? ? ? ? ? "&body=BODY" +
? ? ? ? ? ? ? ? "&mch_id=MCH_ID" +
? ? ? ? ? ? ? ? "&nonce_str=NONCE_STR" +
? ? ? ? ? ? ? ? "¬ify_url=NOTIFY_URL" +
? ? ? ? ? ? ? ? "&openid=OPENID" +
? ? ? ? ? ? ? ? "&out_trade_no=OUT_TRADE_NO" +
? ? ? ? ? ? ? ? "&spbill_create_ip=IP" +
? ? ? ? ? ? ? ? "&total_fee=TOTAL_FEE" +
? ? ? ? ? ? ? ? "&trade_type=TRADE_TYPE"
? ? ? ? ? ? ? ? ;//這個(gè)字段是用于之后MD5加密的,字段要按照ascii碼順序排序
? ? ? ? /**
? ? ? ? * 設(shè)置數(shù)據(jù)
? ? ? ? */
? ? ? ? String nonce_str = getNonceStr().toUpperCase();//隨機(jī)數(shù)據(jù)
? ? ? ? reqEntityStr = reqEntityStr.replace("APPID", WeChatConst.APPID);
? ? ? ? reqEntityStr = reqEntityStr.replace("MCH_ID", WeChatConst.MCH_ID);
? ? ? ? reqEntityStr = reqEntityStr.replace("NONCE_STR", nonce_str);
? ? ? ? reqEntityStr = reqEntityStr.replace("BODY", body);
? ? ? ? reqEntityStr = reqEntityStr.replace("OUT_TRADE_NO", out_trade_no);
? ? ? ? reqEntityStr = reqEntityStr.replace("TOTAL_FEE", total_fee.toString());
? ? ? ? reqEntityStr = reqEntityStr.replace("IP", IP);
? ? ? ? reqEntityStr = reqEntityStr.replace("NOTIFY_URL", notify_url);
? ? ? ? reqEntityStr = reqEntityStr.replace("TRADE_TYPE", WeChatConst.TRADE_TYPE);
? ? ? ? reqEntityStr = reqEntityStr.replace("OPENID", openid);
? ? ? ? /**
? ? ? ? * 生成sign的數(shù)據(jù)
? ? ? ? */
? ? ? ? String sign = reqEntityStr + "&key="+WeChatConst.KEY;
? ? ? ? sign = MD5Util.MD5(sign).toUpperCase();
? ? ? ? /**
? ? ? ? * 組裝XML
? ? ? ? */
? ? ? ? StringBuilder sb = new StringBuilder("");
? ? ? ? sb.append("<xml>");
? ? ? ? setXmlKV(sb,"appid",WeChatConst.APPID);
? ? ? ? setXmlKV(sb,"body",body);
? ? ? ? setXmlKV(sb,"mch_id",WeChatConst.MCH_ID);
? ? ? ? setXmlKV(sb,"nonce_str",nonce_str);
? ? ? ? setXmlKV(sb,"notify_url",notify_url);
? ? ? ? setXmlKV(sb,"openid",openid);
? ? ? ? setXmlKV(sb,"out_trade_no",out_trade_no);
? ? ? ? setXmlKV(sb,"spbill_create_ip",IP);
? ? ? ? setXmlKV(sb,"total_fee",total_fee.toString());
? ? ? ? setXmlKV(sb,"trade_type",WeChatConst.TRADE_TYPE);
? ? ? ? setXmlKV(sb,"sign",sign);
? ? ? ? sb.append("</xml>");
//? ? ? ? reqEntityStr = reqEntityStr.replace("sign", sign);
? ? ? ? StringEntity reqEntity = new StringEntity(new String (sb.toString().getBytes("UTF-8"),"ISO8859-1"));//這個(gè)處理是為了防止傳中文的時(shí)候出現(xiàn)簽名錯(cuò)誤
? ? ? ? httppost.setEntity(reqEntity);
? ? ? ? DefaultHttpClient httpclient = new DefaultHttpClient();
? ? ? ? HttpResponse response = httpclient.execute(httppost);
? ? ? ? String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));
//? ? ? ? System.out.println(strResult);
? ? ? ? return getPrePayId(strResult);
? ? }
? ? /**
? ? * 獲得32位隨機(jī)字符串
? ? * @return
? ? */
? ? public static String getNonceStr(){
? ? ? ? String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
? ? ? ? StringBuilder sb = new StringBuilder();
? ? ? ? Random rd = new Random();
? ? ? ? for(int i = 0 ; i < 32 ; i ++ ){
? ? ? ? ? ? sb.append(str.charAt(rd.nextInt(str.length())));
? ? ? ? }
? ? ? ? return sb.toString();
? ? }
? ? /**
? ? * 插入XML標(biāo)簽
? ? * @param sb
? ? * @param Key
? ? * @param value
? ? * @return
? ? */
? ? public static StringBuilder setXmlKV(StringBuilder sb,String Key,String value){
? ? ? ? sb.append("<");
? ? ? ? sb.append(Key);
? ? ? ? sb.append(">");
? ? ? ? sb.append(value);
? ? ? ? sb.append("</");
? ? ? ? sb.append(Key);
? ? ? ? sb.append(">");
? ? ? ? return sb;
? ? }
? ? /**
? ? * 解析XML? 獲得 PrePayId
? ? * @param xml
? ? * @return
? ? */
? ? public static String getPrePayId(String xml){
? ? ? ? int start = xml.indexOf("<prepay_id>");
? ? ? ? int end = xml.indexOf("</prepay_id>");
? ? ? ? if(start < 0 && end < 0){
? ? ? ? ? ? return null;
? ? ? ? }
? ? ? ? return xml.substring(start + "<prepay_id>".length(),end)
? ? ? ? ? ? ? ? .replace("<![CDATA[","").replace("]]>","");
? ? }
}
public interface WeChatConst {
? ? String APPID = "";//填寫APPID
? ? String APP_SECRET = "";//填寫AppSecret
? ? String MCH_ID = "";//Mch_id,這是值是微信支付商戶號(hào),大家可以從郵件中獲取
? ? String KEY = "";//這個(gè)參數(shù)KEY是在商戶后臺(tái)配置的一個(gè)32位的key,微信商戶平臺(tái)-賬戶設(shè)置-安全設(shè)置-api安全
? ? /**
? ? * 交易類型
? ? */
? ? String TRADE_TYPE = "JSAPI";
}
我們可以調(diào)用里面的“getOpenIdByCode”方法通過code 獲得openid,這個(gè)openid我們先記錄下來,稍后會(huì)用到的,接下來我們要經(jīng)行調(diào)用統(tǒng)一下單接口
3.然后通過可以調(diào)用里面的“unifiedorder”獲取prepay_id
后臺(tái)調(diào)傭統(tǒng)一下單的API,獲得prepay_id
**這步我們先要設(shè)置支付授權(quán)目錄**
微信公眾平臺(tái)→微信支付→開發(fā)配置→測(cè)試授權(quán)目錄和測(cè)試白名單。
直接配置支付授權(quán)目錄也行。
后臺(tái)獲得到需要用戶支付的金額,以及一些列的產(chǎn)品信息后,系統(tǒng)經(jīng)行預(yù)下單,也是就調(diào)用調(diào)用統(tǒng)一下單的API,生成一個(gè)需要支付的訂單,獲得prepay_id,該值用于在前端頁面,通過JSAPI中調(diào)用微信支付需要用到,代碼看下面的? "JSONObjectdoRecharge(HttpServletRequest request, String code)" 的方法,以及WeChatUtil。
統(tǒng)一下單大家可以調(diào)用我剛剛提供給大家的工具類中的WeChatUtil.unifiedorder這個(gè)方法,參數(shù)在方法上面的注釋有寫清楚。
這里如果你們想自行封裝需要注意一個(gè)編碼問題,這個(gè)會(huì)導(dǎo)致你回調(diào)產(chǎn)生簽名錯(cuò)誤。
獲得prepay_id后,我們需要在后臺(tái)組裝,調(diào)用微信JSAPI的接口數(shù)據(jù)。
以下是需要組裝起來的參數(shù),也是進(jìn)行微信支付的必須參數(shù)
"appId": "", //公眾號(hào)ID,由商戶傳入
"timeStamp": "", //時(shí)間戳?
"nonceStr": "", //隨機(jī)串? ?
"package": "", //獲得PrePayId
"signType": "MD5", //微信簽名方式:? ?
"paySign": "" //微信簽名
4.前端所需參數(shù),通過這些參數(shù)可以進(jìn)行支付
獲取這些參數(shù)來源是需要——>進(jìn)行預(yù)下單——>方法如下:
/**
* 微信支付——進(jìn)行預(yù)下單
*
* @param request
* @param code
* @return
* @throws IOException
*/
@RequestMapping(value ="/WxPay")
public static JSONObjectdoRecharge(HttpServletRequest request, String code)throws IOException {
//獲取openId的方法
? ? Map openIdByCode = WeChatUtil.getOpenIdByCode(code);
? ? System.out.println("openId:" + openIdByCode.get("openid"));
? ? String body = WeChatConst.BOBY;
? ? //商戶訂單號(hào)
? ? String out_trade_no = UUIDUtils.getUUID().toUpperCase();
? ? System.out.println("商戶訂單號(hào):" + out_trade_no);
? ? //支付金額
? ? int total_fee =1;
? ? //IP
? ? String IP = WeChatConst.IP;
? ? //支付成功后回調(diào)的地址
? ? String openid = (String) openIdByCode.get("openid");
? ? //在業(yè)務(wù)層調(diào)用 WeChatUtil.unifiedorder方法獲得prepay_id
? ? String prepay = WeChatUtil.unifiedorder(body, out_trade_no, total_fee, IP, WeChatConst.NOTIFY_URL, openid);
? ? System.out.println("prepay_id" + prepay);
? ? //將參數(shù)組裝起來
? ? String prepay_id = prepay;
? ? //1970年到現(xiàn)在的秒數(shù)
? ? String timeStamp = String.valueOf((System.currentTimeMillis() /1000));
? ? //數(shù)據(jù)字符串
? ? String nonceStr = WeChatUtil.getNonceStr().toUpperCase();
? ? //prepay_id
? ? String packageStr ="prepay_id=" + prepay_id;
? ? String signType ="MD5";
? ? //簽名
? ? String paySign =
"appId=" + WeChatConst.APPID +
"&nonceStr=" + nonceStr +
"&package=prepay_id=" + prepay_id +
"&signType=" + signType +
"&timeStamp=" + timeStamp +
"&key=" + WeChatConst.KEY;//注意這里的參數(shù)要根據(jù)ASCII碼 排序
? ? //將數(shù)據(jù)MD5加密
? ? paySign = Md5Utils.encrypt(paySign).toUpperCase();
? ? JSONObject object =new JSONObject();
? ? object.put("appId", WeChatConst.APPID);
? ? object.put("timeStamp", timeStamp);
? ? object.put("nonceStr", nonceStr);
? ? object.put("package", packageStr);
? ? object.put("signType", signType);
? ? object.put("paySign", paySign);
? ? return object;
}
通過該方法——>JSONObjectdoRecharge——>獲取進(jìn)行支付參數(shù)
5.將這些參數(shù)(appId、nonceStr、package、signType、timeStamp、key)填寫到如下頁面:
5、將該參數(shù)設(shè)置好的頁面HTML放到 "支付授權(quán)目錄"(授權(quán)目錄第3步驟有講到)
6、直接訪問該html文件,就可以進(jìn)行支付了
比如我存放到服務(wù)器當(dāng)中的https://xx.lxx-xx.com/h5/fjyc/wxPay.html 目錄
而這個(gè)項(xiàng)目當(dāng)中目錄是支付授權(quán)目錄,那我直接訪問該鏈接(https://xx.lxx-xx.com/h5/fjyc/wxPay.html )