Ionic3微信支付流程
總結通俗來說微信支付一共兩步(最後附全部代碼):
1.統一下單(給微信獲得微信返回的支付訂單號(prepay_id))
解釋一下:通俗點講用戶支付錢的時候,需要跳到微信界面,根據一個微信支付訂單(微信界面肯定是微信的東西,和自己的項目沒有任何關係)來支付錢,這個微信支付訂單是微信生成的以下簡稱vx訂單,怎麼生成?項目後臺根據一系列參數生成一個map,利用map和商戶微信支付密鑰key根據規則生成簽名sign,將sign也放入到此前的map中,再將map轉換爲xml(格式參考微信支付文檔,微信只接受xml),並將此xml通過post請求發送給微信(發送url爲微信統一定死的地址,詳細接着看下面),不論成功與否都回返回一個xml,若成功xml轉map後會取得此次統一下單的終極目的參數prepay_id(預支付id),這個id是統一下單步驟的終極目標,獲得此id代表已經成功讓微信爲用戶之後的跳轉到微信支付界面付錢生成了一個訂單界面,等候用戶跳轉微信支付。若失敗則要查看返回的xml中,returnMsg會提示報的錯誤,根據錯誤排查(文章最後會有一系列坑及解決方案)。
注意:此步驟完成不會跳轉微信支付接口,僅僅是給微信支付下了個可以理解爲預付訂單,取得預付訂單號prepay_id。
統一下單xml示例:
參數舉例:商品訂單號out_trade_no(unique,並不是vx訂單)和錢數total_fee(any)已經用戶支付時的客戶端ip spbill_create_ip等等一系列參數,參考下圖。
以下是必不可少的參數小白解釋(具體可參考微信支付文檔):
appid(String 32):微信開發平臺創建app時取得
mch_id(String 32):商戶號id:微信開發平臺申請微信支付資格成功時取得。
body(String 32):自己定義對訂單商品的描述,比如body = 充值Q幣,用戶跳到微信支付界面時顯示出的充值界面內容即爲充值Q幣。
Out_trade_no(String 32):商品訂單號,此訂單號爲我們自己生成的訂單號,必須唯一。
Total_fee(Int):單位爲分! 用戶實際支付的金額
Spbill_create_ip(String 16):用戶支付時客戶端ip
Trader_type(String 16):APP(app支付時定死)
Nonce_str(String 32):隨機生成字符串
Notify_url(String 256):用戶支付完成後(完成統一下單不會走回調!),微信收到了錢或支付失敗都有可能返回一個xml,返回時需要異步的訪問我們服務器的一個url地址,此地址作爲支付的回調函數,在此地址中處理相應業務邏輯,返回時僅僅會有商品訂單號(項目生成),和實際支付金額,實際支付時間有用。
Sign(String 32):生成簽名,需要用到商戶支付密鑰(不是appSecret!)此出生成簽名必須與返回給TS的參數中生成簽名的方式一致。具體生成方式網上一大堆。
1.調起支付接口
將統一下單取得的prepay_id和其他參數如下等再次(第二次)生成簽名,與之前同意下單的簽名不相同!將這些參數發送回前臺準備進行微信窗口的跳轉(調起支付接口),具體操作見下節。
注意:timestamp必須爲String類型的時間戳!擴展字段爲定死。商戶訂單號outtradeno可不傳,爲前臺處理業務邏輯用。
2.App微信支付資格準備
https://open.weixin.qq.com/cgi-bin/frame?t=home/app_tmpl&lang=zh_CN
1.創建app,註冊微信平臺相關信息。取得appid,appSecret
2.通過後,開通微信支付資格(商戶號申請),具體材料有打包並且已經簽名成功的apk
3.取得資格後需要取得的參數爲商戶號,商戶支付密鑰。
最終上述步驟取得微信支付實際所需參數有:
APPID:申請的appid
MCH_ID:商戶號
KEY:商戶號的支付密鑰(不是appSecret!)
下面是後臺示例此步驟所獲得的參數一覽:
注意:apk包名和apk的簽名一旦綁定不可更改,必須與正式發版後的包名簽名一致,否則
會導致出現簽名錯誤(普通錯誤)。
微信支付插件安裝
Cmd: cordova plugin add [email protected] --variable wechatappid=xxxxxxxxxxxxxxx
注意:@2.0爲版本號可更換 wechatappid爲申請的appid(不是商戶號!),此處必須與申請微信支付的app的appid一致。
安裝完成後,打開confing.xml若如圖所示版本號的appid無誤則爲安裝成功。
請求後臺進行統一下單
安裝成功後,經大量測試,無需引入module等其他操作,直接在要使用微信支付的page.ts開搞。
1.在ts中組件聲明前聲明一個window對象。
2.直接請求(get/post都可)後臺(必須參數:支付金額(注意微信接口的單位爲分,此處若爲元需處理),用戶id(進行支付錢的業務處理需要),訂單號可後臺生成),後臺返回值重新組裝一個類似於post請求的對象參數,必須的參數如下圖(微信文檔有7個必須,經測試有下圖5個爲必須即可)
- 統一下單後臺代碼示例
注意:
- 發送的post請求編碼格式一定要統一(UTF-8),否則返回的xml會報body亂碼錯誤
- KEY一定要是商戶支付密鑰不是appSercet!!兩者不同。
- Total_fee在此處的單位一定是分,不論之前業務處理以元或其他單位,此處必須轉換成分。
- 訂單號一定是唯一的,此處建議取上一個訂單號+1,再拼接一個一定位數的隨機字符串,在回調函數中直接sub掉一定位數的字符串即可拿到唯一的訂單號。
- 回調函數不能帶參數!
- 統一下單的簽名方式一定要和調起支付接口參數的簽名方式一致!但並不是同一個簽名!
因爲簽名時所需參數多了一個prepay_id!
- Timestamp時間戳一定要是string!
- 注意發送參數的大小寫和非人的下劃線變化。Eg:mch_id =>partnerid,nonce_str =>noncestr
- 統一下單中返回的xml報錯商戶訂單號重複,解決方法檢查發送給微信的商戶訂單號是否與上次未完成的訂單重複,若未重複,等待一天即可(微信取消支付異常的預付訂單)。
調起支付接口,跳轉微信界面。
拿到通過第一步統一下單取得的參數後,將此對象參數通過下圖請求發送即可跳轉微信界面。
若失敗 reason會打印錯誤(eg:1.普通錯誤2.用戶取消支付返回等)。成功則爲完成支付。
注意:
- 注意es5和es6的坑,回調函數中的this指向問題,es5 爲function(data){}其中的this不是指ts的this而是回調函數中的this 。es6寫法爲(data)=> this指向爲ts中的this。\
- 若返回普通錯誤,說明這個方法本身沒問題,且插件安裝正確。在統一下單成功的情況下,檢查appid / 商戶號 /商戶支付密鑰(不是appSecert)是否和申請通過時的一致!
- 若一致可能是申請通過時間未到24小時(一般要在項目開始時申請,不要先做現申請)!
以下爲全部代碼:
ts:
//聲明window
declare var window;
@Component({
selector: 'page-paywaymodal',
templateUrl: 'paywaymodal.html',
})
/**
* 微信統一下單
*/
getWXPayParam(item) {
console.log(item.type);
let paymentObject = {
paymentFee: this.payFee,
payer: GlobalConfig.USERINFO.id,
paymentType: item.paymentConfigType,
paymentConfigName: item.paymentConfigName,
paymentConfigId: item.paymentConfigId,
paymentStatu: 0,
orderIdList: this.orderId
};
this.httpService.post(this.payUrl, paymentObject).then(data => {
if (!data.success) {
this.toastAlertService.errorToast(data.message, 1000, "top");
} else {
console.log(data);
let params = {
// appid:data.preOrderResult.appid,
partnerid: data.preOrderResult.partnerid, //商戶號
prepayid: data.preOrderResult.prepayid, //預支付id
//package: data.preOrderResult.package,
noncestr: data.preOrderResult.noncestr, //隨機數
timestamp: data.preOrderResult.timestamp, //時間戳
sign: data.preOrderResult.sign, //簽名
//appID
};
this.WXPay(params, data.preOrderResult.outtradeno, data.preOrderResult.paymentFee);
}
});
}
/**
* 調起微信支付接口
* @param params
*/
WXPay(params, paymentSn, paymentFee) {
window.Wechat.sendPaymentRequest(params, (data) => {
this.toastAlertService.successToast(data.message, 2000, "top");
this.viewCtrl.dismiss(true);
}, (reason) => {
this.toastAlertService.successToast("支付失敗" + reason, 2000, "middle");
this.viewCtrl.dismiss(false);
})
}
java(serviceImpl):
package com.xxx.common.business;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import com.xxx.common.api.WeChatConfig;
import com.xxx.utils.CommonUtil;
import com.xxxx.utils.HttpRequest;
import com.xxxx.utils.WXPayUtil;
import com.xxxx.weixin.api.WxOrderService;
/**
* 微信支付接口(仔細看註解)
* @author lu
*
*/
@Service
public class WxOrderServiceImpl implements WxOrderService {
/**
* ==========================================
* 參數:
* out_trade_no:訂單號 total_fee:支付錢數 body:所購買的物品簡介
* 注意:
* 1.微信預付單:指的是在自己的平臺需要和微信進行支付交易生成的一個微信訂單,稱之爲“預付單”
* 2.訂單:指的是自己的網站平臺與用戶之間交易生成的訂單或消費記錄
* 3.時間戳一定要轉成字符串類型,生成簽名方式一定要相同
* 微信支付過程:
* 1. 生成網站訂單或消費記錄(一定要有否則保存不了參數處理業務邏輯)
* 2. 用戶支付 --> 網站在微信平臺生成預付單(實際取得的是預支付訂單號)
* 3. 最終實際根據預付單的信息進行支付
* ==========================================
*/
@Override
public Map<String,String> placeOrder(String body,String out_trade_no,String total_fee,HttpServletRequest request) throws Exception {
System.out.println("進入產生預付訂單api");
try {
//獲取請求用戶端實際ip
String ip = request.getHeader("x-forwarded-for");
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("Proxy-Client-IP");
}
if(ip == null || ip.length()==0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("WL-Proxy-Client-IP");
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getRemoteAddr();
}
if(ip.indexOf(",")!=-1){
String[] ips = ip.split(",");
ip = ips[0].trim();
}
System.out.println("------請求用戶端ip----------"+ip);
Map<String, String> params = new HashMap<String, String>();
String total_yuan = "";// new BigDecimal(total_fee).movePointRight(2).toString();//分轉元(測試先設支付爲分)
// 訂單總金額,單位爲元(測試中先爲分)
if (StringUtils.isNotEmpty(total_fee)) {
total_yuan = new BigDecimal(total_fee).movePointRight(2).toString();//充值碳匯幣以元爲單位(分轉元)3-》300分;// new BigDecimal(recharge_fee).movePointRight(2).toString();//充值碳匯幣以元爲單位(分轉元)
}
// 用戶支付次數過於密集,會在極短的時間內生成累加訂單號和生成訂單之間會有可能出現重複訂單號的問題,故在發送給微信的訂單號後面追加隨機數,在回調函數中sub掉取出,會大大降低訂單的重複率
out_trade_no += CommonUtil.getUUID().substring(0, 3);//3位隨機數
params.put("appid",WeChatConfig.APPID);//APPID
params.put("mch_id",WeChatConfig.MCH_ID);//商戶號
params.put("body",body);//詳情描述
params.put("out_trade_no", out_trade_no);//訂單號
params.put("total_fee", total_yuan);//支付費用(分)
//params.put("total_fee", "1");測試用
params.put("spbill_create_ip", ip);//用戶端實際ip
params.put("trade_type",WeChatConfig.TRADE_TYPE);//交易類型
params.put("nonce_str", WXPayUtil.generateNonceStr());//隨機字符串
params.put("notify_url",WeChatConfig.NOTIFY_URL);//回調url
String sign = WXPayUtil.generateSignature(params,WeChatConfig.KEY);//生成簽名
params.put("sign", sign);
System.out.println("統一下單參數:"+params.toString());
String xml = WXPayUtil.mapToXml(params).replace("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>", "").trim();//將所有參數(map)轉xml格式,微信方接收方式爲xml
String xmlStr = HttpRequest.sendPost(WeChatConfig.PLACEANORDER_URL, xml);//發送post請求"統一下單接口"返回預支付id:prepay_id
System.out.println("xmlStr顯示:" + xmlStr);
String prepay_id = "";
if (xmlStr.indexOf("SUCCESS") != -1) {
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
prepay_id = (String) map.get("prepay_id");//預支付ID
System.out.println("取得預支付id:"+prepay_id);
}
Map<String, String> payMap = new HashMap<String, String>();
payMap.put("appid",WeChatConfig.APPID);
payMap.put("partnerid",WeChatConfig.MCH_ID);
payMap.put("prepayid",prepay_id);//預支付id(預支付提交後微信返回)
payMap.put("package",WeChatConfig.PACKAGE);//擴展字段
payMap.put("noncestr",WXPayUtil.generateNonceStr());//隨機字符串
payMap.put("timestamp",WXPayUtil.getCurrentTimestamp()+"");//時間戳
String paySign = WXPayUtil.generateSignature(payMap,WeChatConfig.KEY);
payMap.put("sign", paySign);//簽名(注意:簽名方式一定要與統一下單接口使用的一致)
payMap.put("outtradeno", out_trade_no.substring(0,out_trade_no.length() - 3));//訂單號
payMap.put("paymentFee",total_fee);//total_yuan 實際支付金額
System.out.println("吊起支付接口參數:"+payMap.toString());
return payMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
/**
* @Description: 微信支付的一些參數配置
* @author lu
*/
public class WeChatConfig {
public static final String APPID = "xxxxxxxx"; // 公衆賬號ID
public static final String MCH_ID = "xxxxxxxx"; // 商戶號
public static final String KEY = "xxxxxxxxxx"; // 商戶密鑰
public static final String PACKAGE = "Sign=WXPay";
/* // APP和網頁支付提交用戶端ip, Native支付填調用微信支付API的機器IP, 即:服務器ip地址
public static final String SPBILL_CREATE_IP = "127.0.0.1";*/
// 接收微信支付異步通知回調地址,通知url必須爲直接可訪問的url,不能攜帶參數。(需要配置)
public static final String NOTIFY_URL = "xxxxxxx/appPay/callBack";
// 支付方式,取值如下:JSAPI,NATIVE,APP
public static final String TRADE_TYPE = "APP";
// 微信支付 - 統一下單地址
public static final String PLACEANORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
}
在下辛苦填坑,轉載請註明出處! 有問題歡迎指出請教加VX kuaibaojing