看官方文檔還是會(huì)暈畸肆,這里就直接寫一下如何實(shí)現(xiàn)吧僚匆。
需要在小程序端實(shí)現(xiàn)的內(nèi)容
第一步:生成商戶訂單
// 生成商戶訂單即發(fā)起預(yù)支付
/**生成商戶訂單 */
generateOrder: function (openid) {
var that = this
//統(tǒng)一支付
wx.request({
url: 'http://localhost:8070/RMS/pay_pay.action', // 自己的后臺(tái)支付接口
method: 'GET',
data: {
total_fee: '1', // 以分為單位突梦,'1'即 -> 1分錢敏弃, 必填6⑺铩B成!
body: '支付測(cè)試', // 商品描述振惰,即商品名稱歌溉,注意:此參數(shù)必填!!痛垛!
},
success: function (res) {
var pay = res.data
//發(fā)起支付
var timeStamp = pay[0].timeStamp;
var packages = pay[0].package;
var paySign = pay[0].paySign;
var nonceStr = pay[0].nonceStr;
var param = { "timeStamp": timeStamp, "package": packages, "paySign": paySign, "signType": "MD5", "nonceStr": nonceStr };
that.pay(param)
},
})
},
第二步:發(fā)起支付
/* 支付 */
payMoney: function (param) {
console.log("開(kāi)始支付");
var that = this;
wx.requestPayment({
'timeStamp': param.timeStamp,
'nonceStr': param.nonceStr,
'package': param.package,
'signType': param.signType,
'paySign': param.paySign,
'success': function (res) {
wx.showToast({
title: '支付成功',
icon: 'success',
duration: 3000
})
},
'fail': function (res) {
console.log("支付失敗");
api.showModal("支付失敗草慧,請(qǐng)重試");
}
})
},
其實(shí)支付功能在小程序前端就這么點(diǎn)代碼,主要還是后臺(tái)的一些操作匙头。
為了安全考慮漫谷,需要用到的,小程序appid和秘鑰蹂析,商戶的商戶號(hào)和支付秘鑰全都放在后臺(tái)舔示。
哦,看到這里你可能問(wèn)我电抚,什么是商戶號(hào)和商戶秘鑰惕稻,那你首先要做的就不是寫代碼了,而是先去看一下官方文檔蝙叛,起碼簡(jiǎn)單了解一下支付的業(yè)務(wù)流程缩宜,這里你需要注意微信官方文檔頁(yè)面下邊的五個(gè)步驟,看看它跳轉(zhuǎn)之后的內(nèi)容甥温。
然后先去申請(qǐng)開(kāi)通一下小程序的支付功能
再去微信商戶的平臺(tái)設(shè)置并獲取一下你的商戶號(hào)和支付秘鑰,具體方法在這也給你:微信支付商戶平臺(tái)-配置密鑰/API安全
之后就基本都是后臺(tái)的任務(wù)了妓布。
調(diào)用支付統(tǒng)一下單API來(lái)獲取prepay_id姻蚓,并將小程序調(diào)起支付數(shù)據(jù)需要簽名的字段appId,timeStamp匣沼,nonceStr狰挡,package再次簽名——小程序調(diào)起支付API
后臺(tái)代碼
/**
* @author
* @version 創(chuàng)建時(shí)間:2017年1月21日 下午4:59:03
* 小程序端請(qǐng)求的后臺(tái)action,返回簽名后的數(shù)據(jù)傳到前臺(tái)
*/
public class PayAction {
private String total_fee;//總金額
private String body;//商品描述
private String detail;//商品詳情
private String attach;//附加數(shù)據(jù)
private String time_start;//交易起始時(shí)間
private String time_expire;//交易結(jié)束時(shí)間
private String openid;//用戶標(biāo)識(shí)
private JSONArray jsonArray=new JSONArray();
public String pay() throws UnsupportedEncodingException, DocumentException{
body = new String(body.getBytes("UTF-8"),"ISO-8859-1");
String appid = "替換為自己的小程序ID";//小程序ID
String mch_id = "替換為自己的商戶號(hào)";//商戶號(hào)
String nonce_str = UUIDHexGenerator.generate();//隨機(jī)字符串
String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String code = PayUtil.createCode(8);
String out_trade_no = mch_id+today+code;//商戶訂單號(hào)
String spbill_create_ip = "替換為自己的終端IP";//終端IP
String notify_url = "http://www.weixin.qq.com/wxpay/pay.php";//通知地址
String trade_type = "JSAPI";//交易類型
String openid="替換為用戶的openid";//用戶標(biāo)識(shí)
//封裝支付參數(shù)
PaymentPo paymentPo = new PaymentPo();
paymentPo.setAppid(appid);
paymentPo.setMch_id(mch_id);
paymentPo.setNonce_str(nonce_str);
String newbody=new String(body.getBytes("ISO-8859-1"),"UTF-8");//以u(píng)tf-8編碼放入paymentPo释涛,微信支付要求字符編碼統(tǒng)一采用UTF-8字符編碼
paymentPo.setBody(newbody);
paymentPo.setOut_trade_no(out_trade_no);
paymentPo.setTotal_fee(total_fee);
paymentPo.setSpbill_create_ip(spbill_create_ip);
paymentPo.setNotify_url(notify_url);
paymentPo.setTrade_type(trade_type);
paymentPo.setOpenid(openid);
// 把請(qǐng)求參數(shù)打包成Map
Map<String, String> sParaTemp = new HashMap<String, String>();
sParaTemp.put("appid", paymentPo.getAppid());
sParaTemp.put("mch_id", paymentPo.getMch_id());
sParaTemp.put("nonce_str", paymentPo.getNonce_str());
sParaTemp.put("body", paymentPo.getBody());
sParaTemp.put("out_trade_no", paymentPo.getOut_trade_no());
sParaTemp.put("total_fee",paymentPo.getTotal_fee());
sParaTemp.put("spbill_create_ip", paymentPo.getSpbill_create_ip());
sParaTemp.put("notify_url",paymentPo.getNotify_url());
sParaTemp.put("trade_type", paymentPo.getTrade_type());
sParaTemp.put("openid", paymentPo.getOpenid());
// 除去Map中的空值和簽名參數(shù)
Map<String, String> sPara = PayUtil.paraFilter(sParaTemp);
String prestr = PayUtil.createLinkString(sPara); // 把Map所有元素加叁,按照“參數(shù)=參數(shù)值”的模式用“&”字符拼接成字符串
String key = "&key=替換為商戶支付密鑰"; // 商戶支付密鑰
//MD5運(yùn)算生成簽名
String mysign = PayUtil.sign(prestr, key, "utf-8").toUpperCase();
paymentPo.setSign(mysign);
//打包要發(fā)送的xml
String respXml = MessageUtil.messageToXML(paymentPo);
// 打印respXml發(fā)現(xiàn),得到的xml中有“__”不對(duì)唇撬,應(yīng)該替換成“_”
respXml = respXml.replace("__", "_");
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//統(tǒng)一下單API接口鏈接
String param = respXml;
String result =PayUtil.httpRequest(url, "POST", param);
// 將解析結(jié)果存儲(chǔ)在HashMap中
Map<String, String> map = new HashMap<String, String>();
InputStream in=new ByteArrayInputStream(result.getBytes());
// 讀取輸入流
SAXReader reader = new SAXReader();
Document document = reader.read(in);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子節(jié)點(diǎn)
@SuppressWarnings("unchecked")
List<Element> elementList = root.elements();
for (Element element : elementList) {
map.put(element.getName(), element.getText());
}
// 返回信息
String return_code = map.get("return_code");//返回狀態(tài)碼
String return_msg = map.get("return_msg");//返回信息
JSONObject JsonObject=new JSONObject() ;
//請(qǐng)求成功
if(return_code=="SUCCESS"||return_code.equals(return_code)){
// 業(yè)務(wù)結(jié)果
String prepay_id = map.get("prepay_id");//返回的預(yù)付單信息
String nonceStr=UUIDHexGenerator.generate();
JsonObject.put("nonceStr", nonceStr);
JsonObject.put("package", "prepay_id="+prepay_id);
Long timeStamp= System.currentTimeMillis()/1000;
JsonObject.put("timeStamp", timeStamp+"");
String stringSignTemp = "appId="+appid+"&nonceStr=" + nonceStr + "&package=prepay_id=" + prepay_id+ "&signType=MD5&timeStamp=" + timeStamp;
//再次簽名
String paySign=PayUtil.sign(stringSignTemp, "&key=替換為自己的商戶密鑰", "utf-8").toUpperCase();
JsonObject.put("paySign", paySign);
jsonArray.add(JsonObject);
}
return "pay";
}
//set get方法略
}
這里邊開(kāi)頭獲取的那一段數(shù)據(jù)(總金額/商品描述/商品詳情/附加數(shù)據(jù)/...)它匕,里邊只有總金額和商品描述是必須的,其他的你酌情處理窖认。
不過(guò)基本都是傳遞一個(gè)商品id豫柬,然后后臺(tái)自己去數(shù)據(jù)庫(kù)中查找對(duì)應(yīng)的商品信息∑私看你跟后臺(tái)如何溝通處理了烧给。
-
后臺(tái)業(yè)務(wù)邏輯涉及到的工具類及參數(shù)封裝類
MessageUtil
public class MessageUtil {
public static HashMap<String,String> parseXML(HttpServletRequest request) throws Exception, IOException{
HashMap<String,String> map=new HashMap<String,String>();
// 通過(guò)IO獲得Document
SAXReader reader = new SAXReader();
Document doc = reader.read(request.getInputStream());
//得到xml的根節(jié)點(diǎn)
Element root=doc.getRootElement();
recursiveParseXML(root,map);
return map;
}
private static void recursiveParseXML(Element root,HashMap<String,String> map){
//得到根節(jié)點(diǎn)的子節(jié)點(diǎn)列表
List<Element> elementList=root.elements();
//判斷有沒(méi)有子元素列表
if(elementList.size()==0){
map.put(root.getName(), root.getTextTrim());
}
else{
//遍歷
for(Element e:elementList){
recursiveParseXML(e,map);
}
}
}
private static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 對(duì)所有xml節(jié)點(diǎn)都增加CDATA標(biāo)記
boolean cdata = true;
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
public static String messageToXML(PaymentPo paymentPo){
xstream.alias("xml",PaymentPo.class);
return xstream.toXML(paymentPo);
}
}
PaymentPo//封裝支付參數(shù)實(shí)體
package cn.it.shop.util;
/**
* @author
* @version 創(chuàng)建時(shí)間:2017年1月21日 下午4:20:52
* 類說(shuō)明
*/
public class PaymentPo {
private String appid;//小程序ID
private String mch_id;//商戶號(hào)
private String device_info;//設(shè)備號(hào)
private String nonce_str;//隨機(jī)字符串
private String sign;//簽名
private String body;//商品描述
private String detail;//商品詳情
private String attach;//附加數(shù)據(jù)
private String out_trade_no;//商戶訂單號(hào)
private String fee_type;//貨幣類型
private String spbill_create_ip;//終端IP
private String time_start;//交易起始時(shí)間
private String time_expire;//交易結(jié)束時(shí)間
private String goods_tag;//商品標(biāo)記
private String total_fee;//總金額
private String notify_url;//通知地址
private String trade_type;//交易類型
private String limit_pay;//指定支付方式
private String openid;//用戶標(biāo)識(shí)
//set get方法略
}
PayUtil//工具類
/**
* @author
* @version 創(chuàng)建時(shí)間:2017年1月17日 下午7:46:29 類說(shuō)明
*/
public class PayUtil {
/**
* 簽名字符串
* @param text需要簽名的字符串
* @param key 密鑰
* @param input_charset編碼格式
* @return 簽名結(jié)果
*/
public static String sign(String text, String key, String input_charset) {
text = text + key;
return DigestUtils.md5Hex(getContentBytes(text, input_charset));
}
/**
* 簽名字符串
* @param text需要簽名的字符串
* @param sign 簽名結(jié)果
* @param key密鑰
* @param input_charset 編碼格式
* @return 簽名結(jié)果
*/
public static boolean verify(String text, String sign, String key, String input_charset) {
text = text + key;
String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset));
if (mysign.equals(sign)) {
return true;
} else {
return false;
}
}
/**
* @param content
* @param charset
* @return
* @throws SignatureException
* @throws UnsupportedEncodingException
*/
public static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5簽名過(guò)程中出現(xiàn)錯(cuò)誤,指定的編碼集不對(duì),您目前指定的編碼集是:" + charset);
}
}
/**
* 生成6位或10位隨機(jī)數(shù) param codeLength(多少位)
* @return
*/
public static String createCode(int codeLength) {
String code = "";
for (int i = 0; i < codeLength; i++) {
code += (int) (Math.random() * 9);
}
return code;
}
private static boolean isValidChar(char ch) {
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
return true;
if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f))
return true;// 簡(jiǎn)體中文漢字編碼
return false;
}
/**
* 除去數(shù)組中的空值和簽名參數(shù)
* @param sArray 簽名參數(shù)組
* @return 去掉空值與簽名參數(shù)后的新簽名參數(shù)組
*/
public static Map<String, String> paraFilter(Map<String, String> sArray) {
Map<String, String> result = new HashMap<String, String>();
if (sArray == null || sArray.size() <= 0) {
return result;
}
for (String key : sArray.keySet()) {
String value = sArray.get(key);
if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
|| key.equalsIgnoreCase("sign_type")) {
continue;
}
result.put(key, value);
}
return result;
}
/**
* 把數(shù)組所有元素排序,并按照“參數(shù)=參數(shù)值”的模式用“&”字符拼接成字符串
* @param params 需要排序并參與字符拼接的參數(shù)組
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params) {
List<String> keys = new ArrayList<String>(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i == keys.size() - 1) {// 拼接時(shí)喝噪,不包括最后一個(gè)&字符
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
return prestr;
}
/**
*
* @param requestUrl請(qǐng)求地址
* @param requestMethod請(qǐng)求方法
* @param outputStr參數(shù)
*/
public static String httpRequest(String requestUrl,String requestMethod,String outputStr){
// 創(chuàng)建SSLContext
StringBuffer buffer=null;
try{
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(requestMethod);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
//往服務(wù)器端寫內(nèi)容
if(null !=outputStr){
OutputStream os=conn.getOutputStream();
os.write(outputStr.getBytes("utf-8"));
os.close();
}
// 讀取服務(wù)器端返回的內(nèi)容
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
buffer = new StringBuffer();
String line = null;
while ((line = br.readLine()) != null) {
buffer.append(line);
}
}catch(Exception e){
e.printStackTrace();
}
return buffer.toString();
}
public static String urlEncodeUTF8(String source){
String result=source;
try {
result=java.net.URLEncoder.encode(source, "UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
}
UUIDHexGenerator
package cn.it.shop.util;
import java.net.InetAddress;
/**
* @author
* @version 創(chuàng)建時(shí)間:2017年1月17日 下午7:45:06 類說(shuō)明
*/
public class UUIDHexGenerator {
private static String sep = "";
private static final int IP;
private static short counter = (short) 0;
private static final int JVM = (int) (System.currentTimeMillis() >>> 8);
private static UUIDHexGenerator uuidgen = new UUIDHexGenerator();
static {
int ipadd;
try {
ipadd = toInt(InetAddress.getLocalHost().getAddress());
} catch (Exception e) {
ipadd = 0;
}
IP = ipadd;
}
public static UUIDHexGenerator getInstance() {
return uuidgen;
}
public static int toInt(byte[] bytes) {
int result = 0;
for (int i = 0; i < 4; i++) {
result = (result << 8) - Byte.MIN_VALUE + (int) bytes[i];
}
return result;
}
protected static String format(int intval) {
String formatted = Integer.toHexString(intval);
StringBuffer buf = new StringBuffer("00000000");
buf.replace(8 - formatted.length(), 8, formatted);
return buf.toString();
}
protected static String format(short shortval) {
String formatted = Integer.toHexString(shortval);
StringBuffer buf = new StringBuffer("0000");
buf.replace(4 - formatted.length(), 4, formatted);
return buf.toString();
}
protected static int getJVM() {
return JVM;
}
protected synchronized static short getCount() {
if (counter < 0) {
counter = 0;
}
return counter++;
}
protected static int getIP() {
return IP;
}
protected static short getHiTime() {
return (short) (System.currentTimeMillis() >>> 32);
}
protected static int getLoTime() {
return (int) System.currentTimeMillis();
}
public static String generate() {
return new StringBuffer(36).append(format(getIP())).append(sep).append(format(getJVM())).append(sep)
.append(format(getHiTime())).append(sep).append(format(getLoTime())).append(sep)
.append(format(getCount())).toString();
}
}
這個(gè)后臺(tái)代碼具體有沒(méi)有什么問(wèn)題呢础嫡,我也不清楚,反正我這邊是可以支付成功的酝惧。
這代碼不用想榴鼎,肯定是我拷貝的伯诬。
參考文檔: 微信小程序支付
這里最后還有個(gè)需要注意的東西:你支付成功后需要給微信反饋!C史 姑廉!
具體如何反饋,讓你們后臺(tái)參考前文業(yè)務(wù)流程中的第四條:支付結(jié)果通用通知