本文讲的是微信支付中的扫码支付的模式二:该模式下,二维码链接由微信支付返回给商户,商户将得到的二维码链接转成二维码图片,用户通过扫码支付,此方式下生成的二维码2小时内有效。
微信支付官网地址:https://pay.weixin.qq.com/wiki/doc/api/index.html
网络相关
compile files('libs/org.apache.httpcomponents.httpclient_4.5.3.jar')
二维码相关:
compile 'com.google.zxing:core:3.2.1'
compile 'cn.bingoogolapple:bga-qrcodecore:1.1.7@aar'
compile 'cn.bingoogolapple:bga-zxing:1.1.7@aar'
退款接口需要用到双向证书https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3。因此在代码上需要有体现。在android中只用到4个中的apiclient_cert.p12,官网中说,【证书文件不能放在web服务器虚拟目录,应放在有访问权限控制的目录中,防止被他人下载。商户服务器要做好病毒和木马防护工作,不被非法侵入者窃取证书文件。】本文中,为了方便,我暂时将其放在assert文件夹中存放,代码中也有体现
String result = null;
// 证书密码(默认为商户ID)
String password = Constent.VALUE_MCH_ID;
// 实例化密钥库
KeyStore ks = KeyStore.getInstance("PKCS12");
// 获得密钥库文件流
AssetManager am = context.getResources().getAssets();
InputStream fis = am.open("apiclient_cert.p12");
// 加载密钥库
ks.load(fis, password.toCharArray());
// 关闭密钥库文件流
fis.close();
// 实例化密钥库
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 初始化密钥工厂
kmf.init(ks, password.toCharArray());
// 创建SSLContext
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(ks, Constent.VALUE_MCH_ID.toCharArray()) //加载证书密码,默认为商户ID
.build();
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
// 获取SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
4.还有很多坑,待补充….
//1 统一下单
public String unifiedOrder() {
SortedMap parameterMap = new TreeMap();
parameterMap.put(Constent.APPID, Constent.VALUE_APPID);//公众号ID
parameterMap.put(Constent.MCH_ID, Constent.VALUE_MCH_ID);//商户号
parameterMap.put(Constent.DEVICE_INFO, "");//设备号
parameterMap.put(Constent.NONCE_STR, PayCommonUtil.getRandomString(32));//随机字符串
parameterMap.put(Constent.BODY, "");//商品描述
parameterMap.put(Constent.SIGN_TYPE, Constent.MD);//签名类型
parameterMap.put(Constent.DETAIL, "");//商品详情
parameterMap.put(Constent.ATTACH, "");//附加数据
time = System.currentTimeMillis() + "";
parameterMap.put(Constent.OUT_TRADE_NO, time);//商户订单号
parameterMap.put(Constent.FEE_TYPE, Constent.CNY);//标价币种
parameterMap.put(Constent.TOTAL_FEE, "");//标价金额
parameterMap.put(Constent.SPBILL_CREATE_IP, "");//终端IP
parameterMap.put(Constent.TIME_START, System.currentTimeMillis() + "");//交易起始时间
//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的,不能携带参数。
parameterMap.put(Constent.NOTIFY_URL, "");//通知地址(支付结果通知)
parameterMap.put(Constent.TRADE_TYPE, Constent.NATIVE);//交易类型
parameterMap.put(Constent.PRODUCT_ID, "");//商品ID(和设备ID一起需知,扫码支付时必须传)
parameterMap.put(Constent.LIMIT_PAY, Constent.NO_CREDIT);//指定支付方式
parameterMap.put(Constent.SIGN, PayCommonUtil.createSign(Constent.UTF, parameterMap));//签名
String requestXML = PayCommonUtil.getRequestXml(parameterMap);//将请求组装成xml形式
String result = PayCommonUtil.httpsRequest(
Constent.URL_TONGYI_XIADAN, Constent.POST,
requestXML);//调用统一支付接口返回String类型字符串
Map map = null;
try {
map = PayCommonUtil.xmlToMap(result);//将返回的结果转为map形式
} catch (Exception e) {
e.printStackTrace();
}
return map.get(Constent.CODE_URL);//得到code_url用来生成二维码
}
6.2.终端使用code_url生成二维码
private void createQRCode() {
//匿名内部类导致Activity内存泄漏的问题待解决
new AsyncTask() {
@Override
protected Bitmap doInBackground(Void... params) {
System.out.println("ds>>> 生成二维码成功");
return QRCodeEncoder.syncEncodeQRCode(unifiedOrder(), BGAQRCodeUtil.dp2px(WechatPaymentActivity.this, 150));
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
iv.setImageBitmap(bitmap);
} else {
System.out.println("ds>>> 生成二维码失败");
}
}
}.execute();
}
6.3.用户打开微信支付扫一扫完成支付
无代码,不需终端操作
6.4.终端定时调用查询订单接口确认是否支付是否完成
注意:1.在主线程中不能进行网络操作 2.需要做定时操作
handler2.post(runnable2);//在main中调用
Handler handler2 = new Handler();
Runnable runnable2 = new Runnable() {
@Override
public void run() {
new Thread(new Runnable() {//每次都要开一个线程去查询订单情况,直到有用户支付成功的结果。
@Override
public void run() {
checkOrder();
}
}).start();
handler2.postDelayed(runnable2, 5000);
}
};
// 2 查询订单
public void checkOrder() {
SortedMap<String, Object> parameterMap = new TreeMap<String, Object>();
parameterMap.put(Constent.APPID, Constent.VALUE_APPID);//公众号ID
parameterMap.put(Constent.MCH_ID, Constent.VALUE_MCH_ID);//商户号
parameterMap.put(Constent.OUT_TRADE_NO, time);//商户订单号
parameterMap.put(Constent.NONCE_STR, PayCommonUtil.getRandomString(32));//随机字符串
parameterMap.put(Constent.SIGN, PayCommonUtil.createSign(Constent.UTF, parameterMap));//签名
String requestXML = PayCommonUtil.getRequestXml(parameterMap);//将请求组装成xml形式
String result = PayCommonUtil.httpsRequest(
Constent.URL_CHAXUN_DINGDAN, Constent.POST,
requestXML);//调用查询订单接口返回String类型字符串
Map<String, String> map = null;
try {
if (result != null)
map = PayCommonUtil.xmlToMap(result);//将返回的结果转为map形式
} catch (Exception e) {
e.printStackTrace();
}
if (map != null)
packageParams = PayCommonUtil.getSortedMap(map);//将map中空的过滤掉
if (packageParams != null && PayCommonUtil.isTenpaySign(Constent.UTF, packageParams, Constent.VALUE_API_KEY)) {
handler.post(runnable);//在新的线程去匹配返回的信息,做相应的UI变化
} else {
System.out.println("通知签名验证失败");
}
}
Handler handler = new Handler();
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
updateUI(packageParams);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
private void updateUI(SortedMap<Object, Object> packageParams) throws InterruptedException {
String result_code = (String) packageParams.get(Constent.RESULT_CODE);
String return_code = (String) packageParams.get(Constent.RETURN_CODE);
String trade_state = (String) packageParams.get(Constent.TRADE_STATE);
String trade_state_desc = (String) packageParams.get(Constent.TRADE_STATE_DESC);
String error_code = (String) packageParams.get(Constent.ERROR_CODE);
System.out.println("ds>>> result_code=" + result_code + ", return_code=" + return_code + ", trade_state=" + trade_state);
System.out.println("ds>>> trade_state_desc=" + trade_state_desc + ", error_code=" + error_code);
if (result_code.equals(Constent.SUCCESS) && return_code.equals(Constent.SUCCESS) && trade_state.equals(Constent.SUCCESS)) {
handler2.removeCallbacks(runnable2);
//支付成功,做相关逻辑。
} else if (Constent.PAYERROR.equals(packageParams.get(Constent.TRADE_STATE))) {
handler2.removeCallbacks(runnable2);
//支付失败,做相关逻辑。
}
}
6.5.退款接口
//5 如果验证失败,需申请退款
public void refundMoney() throws Exception {
SortedMapparameterMap=new TreeMap();
parameterMap.put(Constent.APPID, Constent.VALUE_APPID);//公众号ID
parameterMap.put(Constent.MCH_ID, Constent.VALUE_MCH_ID);//商户号
parameterMap.put(Constent.OUT_TRADE_NO, getIntent().getStringExtra(Constent.VALUE_OUT_TRADE_NO));//商户订单号
parameterMap.put(Constent.NONCE_STR, PayCommonUtil.getRandomString(32));//随机字符串
parameterMap.put(Constent.SIGN_TYPE, "MD5");
parameterMap.put(Constent.OUT_REFUND_NO, System.currentTimeMillis() + "");//商户退款单号
parameterMap.put(Constent.TOTAL_FEE, "1");//订单金额
parameterMap.put(Constent.REFUND_FEE, "1");//退款金额
parameterMap.put(Constent.REFUND_FEE_TYPE, "CNY");//货币种类
parameterMap.put(Constent.REFUND_DESC, "商品验证失败");//退款原因
parameterMap.put(Constent.REFUND_ACCOUNT, "refund reason");//退款资金来源
parameterMap.put(Constent.SIGN, PayCommonUtil.createSign("UTF-8", parameterMap));//签名
String requestXML = PayCommonUtil.getRequestXml(parameterMap);
System.out.println("ds>>> requestXML3 = " + requestXML);
String result = PayCommonUtil.httpsRequest(ValidateIdcardActivity.this, requestXML);
System.out.println("ds>>> result3 = " + result);
Map map = null;
try {
map = PayCommonUtil.xmlToMap(result);
} catch (Exception e) {
e.printStackTrace();
}
SortedMap
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
refundMoney();
} catch (Exception e) {
e.printStackTrace();
}
}
};
new Thread(runnable).start();
6.6 使用到的工具类
public class PayCommonUtil {
//判断签名是否正确
public static boolean isTenpaySign(String characterEncoding, SortedMap packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
//算出摘要
String mysign = MD5Encode(sb.toString(), characterEncoding).toLowerCase();
String tenpaySign = ((String) packageParams.get("sign")).toLowerCase();
return tenpaySign.equals(mysign);
}
//生成签名
public static String createSign(String characterEncoding, SortedMap parameters) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + Constent.VALUE_API_KEY);
String sign = MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
resultString = byteArrayToHexString(md.digest(resultString
.getBytes("UTF-8")));
} catch (Exception exception) {
}
return resultString;
}
//随机字符串生成
public static String getRandomString(int length) { //length表示生成字符串的长度
String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
//请求xml组装
public static String getRequestXml(SortedMap parameters) {
StringBuffer sb = new StringBuffer();
sb.append("" );
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
sb.append("<" + key + ">" + value + "" + key + ">");
}
sb.append("");
return sb.toString();
}
//请求方法
public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
return buffer.toString();
} catch (ConnectException ce) {
System.out.println("连接超时");
ce.printStackTrace();
} catch (Exception e) {
System.out.println("https请求异常");
e.printStackTrace();
}
return null;
}
public static String httpsRequest(Context context, String data) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException, CertificateException, NoSuchProviderException {
String result = null;
// 证书密码(默认为商户ID)
String password = Constent.VALUE_MCH_ID;
// 实例化密钥库
KeyStore ks = KeyStore.getInstance("PKCS12");
// 获得密钥库文件流
AssetManager am = context.getResources().getAssets();
InputStream fis = am.open("apiclient_cert.p12");
// 加载密钥库
ks.load(fis, password.toCharArray());
// 关闭密钥库文件流
fis.close();
// 实例化密钥库
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 初始化密钥工厂
kmf.init(ks, password.toCharArray());
// 创建SSLContext
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(ks, Constent.VALUE_MCH_ID.toCharArray()) //加载证书密码,默认为商户ID
.build();
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
// 获取SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(Constent.URL_TUIKUAN);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setRequestMethod("POST");
//设置当前实例使用的SSLSocketFactory
conn.setSSLSocketFactory(ssf);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
DataOutputStream out = new DataOutputStream(
conn.getOutputStream());
if (data != null)
out.writeBytes(data);
out.flush();
out.close();
//获取输入流
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
int code = conn.getResponseCode();
if (HttpsURLConnection.HTTP_OK == code) {
String temp = in.readLine();
while (temp != null) {
if (result != null)
result += temp;
else
result = temp;
temp = in.readLine();
}
}
return result;
}
public static Map xmlToMap(String strXML) throws Exception {
try {
Map data = new HashMap();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception e) {
e.printStackTrace();
}
return data;
} catch (Exception ex) {
ex.printStackTrace();
throw ex;
}
}
@NonNull
public static SortedMap getSortedMap(Map map) {
//过滤空 设置 TreeMap
SortedMap packageParams = new TreeMap();
Iterator it = map.keySet().iterator();
while (it.hasNext()) {
String parameter = (String) it.next();
String parameterValue = map.get(parameter);
String v = "";
if (null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
return packageParams;
}
}
后话:由于微信支付相关所有的代码和请求都是在android端做,因此有了这篇文章的存在,记录下来为了和我一样更多的人可以高效工作完成微信支付相关。有代码上的错误,欢迎大家及时指出。