无服务器后台下进行微信支付集成
本篇博客主要介绍在没有后台的情况下,进行微信支付开发,涉及微信的统一下单、签名以及调取支付,文末附有demo
主要内容
- 一键下单
- 签名及二次签名
- 调取支付
- 其它设置
一键下单
参数说明,可以参考:[微信支付一键下单](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1)这里面的坑虽多,比如金额的单位是分,必须为整数,但是都比较容易解决,如果有错误,在下单时也会有返回的错误信息。如果参数没错,可以进行以下步骤:
1.设置下单数据
这里需要从商户管理这,拿到三个数据:应用id、商户号、API密钥
其它数据,可以自己产生,我将这些数据放入一个map数组,参考下面代码:
public Map<String, String> requestArgs() {
String ip = NetUtil.getWifiIp(mInstance);
if (TextUtils.isEmpty(ip)) {
ip = NetUtil.getLocalIpAddress();
}
try {
Map<String, String> requestMap = new HashMap<>();
requestMap.put("appid", WXConfig.APP_ID); //应用ID
requestMap.put("mch_id", WXConfig.MCH_ID); //商户号
//requestMap.put("device_info", "013467007045764"); //设备号
requestMap.put("nonce_str", WXConfig.genNonceStr()); //随机字符串
//requestMap.put("sign_type", WXConfig.SIGN_TYPE);//签名类型
requestMap.put("body", getString(R.string.app_name) + "-" + payBody);//商品描述
//requestMap.put("detail","");//商品详情
//requestMap.put("attach","测试");//附加数据
requestMap.put("out_trade_no", WXConfig.genOutTradNo());//商户订单号
//requestMap.put("fee_type","CNY");//货币类型
requestMap.put("total_fee", payMoney);//总金额
requestMap.put("spbill_create_ip", ip);//终端IP
//requestMap.put("time_start","");//交易起始时间
//requestMap.put("time_expire","");//交易结束时间
//requestMap.put("goods_tag","");//商品标记
requestMap.put("notify_url", WXConfig.notify_url);//通知地址
requestMap.put("trade_type", WXConfig.trade_type);//交易类型
//requestMap.put("limit_pay","no_credit");//指定支付方式
return requestMap;
} catch (Exception e) {
Log.e("TAG", "fail, ex = " + e.getMessage());
return null;
}
上述代码中,如果被我注释了,说明这个参考可以为空,其中,需要自己得到的参数,我一一做出解释: 1.body-商品描述:遵循微信支付的要求为:应用名+说明 2.商户订单号:可以自行随机生成,保证唯一即可,我的生成规则如下:
public static String genOutTradNo() {
long currentTime = System.currentTimeMillis();
Random random = new Random();
return currentTime + random.nextInt(1000) + "";
}
3.total_fee-总金额:即调用支付时应支付的价格,单位分,例100为1元 4.ip-终端IP:即下单终端的ip地址,可以通过以下方式获得(记得加入权限,可以参考demo中的权限):
// 获取WIFI的ip地址
public static String getWifiIp(Context context) {
//获取wifi服务
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
//判断wifi是否开启
if (!wifiManager.isWifiEnabled()) {
wifiManager.setWifiEnabled(true);
}
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
int ipAddress = wifiInfo.getIpAddress();
String ip = intToIp(ipAddress);
return ip;
}
private static String intToIp(int i) {
return (i & 0xFF) + "." +
((i >> 8) & 0xFF) + "." +
((i >> 16) & 0xFF) + "." +
(i >> 24 & 0xFF);
}
public static String getLocalIpAddress() {
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
return inetAddress.getHostAddress().toString();
}
}
}
} catch (SocketException ex) {
}
return null;
}
5.notify_url-通知地址:有便填上,没有随意填写正确网址 6.trade_type-交易类型:此处为APP,注意大写
2.提交下单
下单请求应在异步中进行,参考下面代码,发送post请求,即可
private class GetPrepayIdTask extends AsyncTask<Void, Void, Map<String, String>> {
@Override
protected void onPreExecute() {
}
@Override
protected void onPostExecute(Map<String, String> result) {
// 调取支付
payWx(result.get("prepay_id"));
}
@Override
protected Map<String, String> doInBackground(Void... params) {
// 统一下单的需要的数据
Map<String, String> dataMap = requestArgs();
//计算签名
String sign = WXConfig.getSign(dataMap);
dataMap.put("sign", sign);
// 将map转为xml
String entity = StringUtil.map2xml(dataMap);
// 提交数据,获取下单信息
byte[] buf = WXNetUtil.httpPost(WXConfig.unifiedorder, entity);
assert buf != null;
String content = new String(buf);
LogUtil.d("content=" + content);
// 将xml解析成map
return StringUtil.xml2Map(content);
}
}
此处容易造成大部分困扰就是签名,关于签名,其实本质并不复杂,主要问题在于,一定要保证appid,商户号和key正确,key不正确微信是没有提示的,一定注意是32为的数字字母(含大小写),如果下单一直提示签名错误,请检查这三个参数。参数签名,可以参考以下代码:
// 生成签名
public static String getSign(Map<String, String> dataMap) {
// 按字典排序
Collection<String> keySet = dataMap.keySet();
List<String> list = new ArrayList<>(keySet);
Collections.sort(list);
// 获取签名
StringBuilder sb = new StringBuilder();
for (String s : list) {
if (sb.length() != 0) {
sb.append("&").append(s).append("=").append(dataMap.get(s));
} else {
sb.append(s).append("=").append(dataMap.get(s));
}
}
sb.append("&key=").append(WXConfig.API_KEY);
LogUtil.d("nweS=" + sb.toString());
// 最终发送的数据
String sign = MD5.getMessageDigest(sb.toString()).toUpperCase();
LogUtil.d("sign=" + sign);
return sign;
}
签名后,还要将map转为xml,得到结果后还要xml转为map,参考下面代码:
/**
* 将map转为xml
* @param map 需要转换的map集合
* @return
*/
public static String map2xml(Map<String, String> map) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
for (Map.Entry<String, String> entry : map.entrySet()) {
sb.append("<" + entry.getKey() + ">" + entry.getValue() + "</" + entry.getKey() + ">");
}
sb.append("</xml>");
return sb.toString();
}
/**
* 将xml转为map
* @param content 需要转换的xml数据
* @return
*/
public static Map<String, String> xml2Map(String content) {
try {
Map<String, String> xml = new HashMap<>();
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new StringReader(content));
int event = parser.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
String nodeName = parser.getName();
switch (event) {
case XmlPullParser.START_DOCUMENT:
break;
case XmlPullParser.START_TAG:
if ("xml".equals(nodeName) == false) {
//实例化student对象
xml.put(nodeName, parser.nextText());
}
break;
case XmlPullParser.END_TAG:
break;
}
event = parser.next();
}
return xml;
} catch (Exception e) {
Log.e("orion", e.toString());
}
return null;
}
经过上列步骤,便能得到最重要的数据:prepay_id,也就是说,我们已经将后台的工作几乎完成了,那么下面就要涉及到安卓的工作了。
2.调用支付
1.导入微信支付包,可以在官网下载:libs下载 2.设置支付参数,参数参考官网说明:支付参数说明 其主要代码,见下:
private void payWx(String prepayId) {
PayReq payReq = new PayReq();
payReq.appId = WXConfig.APP_ID;
payReq.partnerId = WXConfig.MCH_ID;
payReq.prepayId = prepayId;
payReq.packageValue = "Sign=WXPay";
payReq.nonceStr = WXConfig.genNonceStr();
payReq.timeStamp = String.valueOf(TimeUtil.getTimestamp() / 1000);
// 获取签名
Map<String, String> signMap = new HashMap<>();
signMap.put("appid", payReq.appId);
signMap.put("partnerid", payReq.partnerId);
signMap.put("prepayid", payReq.prepayId);
signMap.put("package", payReq.packageValue);
signMap.put("noncestr", payReq.nonceStr);
signMap.put("timestamp", payReq.timeStamp);
payReq.sign = WXConfig.getSign(signMap);
msgApi.sendReq(payReq);
}
msgApi一定要在调用前初始化:
private void initWx() {
msgApi = WXAPIFactory.createWXAPI(mInstance, null);
// 将该app注册到微信
msgApi.registerApp(WXConfig.APP_ID);
}
在这个过程中,还要做的是,在AndroidManifest的主Activity中加入:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="@string/wx_app_id" />
</intent-filter>
加入receiver
<receiver android:name=".AppRegister">
<intent-filter>
<action android:name="com.tencent.mm.plugin.openapi.Intent.ACTION_REFRESH_WXAPP" />
</intent-filter>
</receiver>
3.设置回调类:wxapi.WXPayEntryActivity
private IWXAPI iwxapi;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
iwxapi = WXAPIFactory.createWXAPI(this, WXConfig.APP_ID);
iwxapi.handleIntent(getIntent(), this);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
iwxapi.handleIntent(intent, this);
}
@Override
public void onReq(BaseReq req) {
}
@Override
public void onResp(final BaseResp resp) {
LogUtil.i(TAG, resp.errCode + "--" + resp.errStr);
}
}
在onResp中进行判断和逻辑操作即可。 demo地址 说明:1.HttpPost要用到org.apache.http.legacy包,demo中也含该包 2.替换wxconfig中的appid,mch_id和api_key及value/string资源中的appid后,该项目可以直接运行(保证wxapi所在目录在项目报名下)