步骤5:回跳到商户APP中,商户APP根据支付结果个性化展示订单处理结果
1)后台设置
商户在微信开放平台申请开发应用后,微信开放平台会生成APP的唯一标识APPID。
由于需要保证支付安全,需要在开放平台绑定商户应用包名和应用签名,设置好后才能正常发起支付。
设置界面在【开放平台】中的栏目【管理中心 / 修改应用 / 修改开发信息】里。
应用包名:是在APP项目配置文件AndroidManifest.xml中声明的package值,
例如DEMO中的package="net.sourceforge.simcpux"。
应用签名:根据项目的应用包名和编译使用的keystore,可由签名工具生成一个32位的md5串,
在调试的手机上安装签名工具后,运行可生成应用签名串。签名工具下载地址
https://open.weixin.qq.com/zh_CN/htmledition/res/dev/download/sdk/Gen_Signature_Android.apk
2)注册APPID
商户APP工程中引入微信JAR包,调用API前,需要先向微信注册您的APPID,代码如下:
API调用前,需要先向微信注册您的APP,代码如下:
final IWXAPI msgApi = WXAPIFactory.createWXAPI(context, null);
// 将该app注册到微信
msgApi.registerApp("wxd930ea5d5a258f4f");
3)调起支付
商户服务器生成支付订单,先调用统一下单API(详见第7节)生成预付单,
获取到prepay_id后将参数再次签名传输给APP发起支付。以下是调起微信支付的关键代码:
IWXAPI api;
PayReq request = new PayReq();
request. appId = "wxd930ea5d5a258f4f";
request. partnerId = "1900000109";
request.prepayId= "1101000000140415649af9fc314aa427",;
request. packageValue = "prepay_id=1101000000140415649af9fc314aa427";
request.nonceStr= "1101000000140429eb40476f8896f4c9";
request.timeStamp= "1398746574";
request.sign= "7ffecb600d7157c5aa49810d2d8f28bc2811827b";
api.sendReq(req);
4)支付结果回调
参照微信SDK Sample,在WXPayEntryActivity类中实现onResp函数,支付完成后,
微信APP会返回到商户APP并回调onResp函数,开发者需要在该函数中接收通知,
判断返回错误码,如果支付成功则去后台查询支付结果再展示用户实际支付结果。
注意一定不能以客户端返回作为用户支付的结果,应以服务器端的接收的支付通知或查询API返回的结果为准
从微信资源中心->资源下载->android资源下载 https://res.wx.qq.com/open/zh_CN/htmledition/res/dev/download/sdk/Android2_SDK221cbf.zip
解压后得
【微信APP支付】Sample_For_Android //demo,稍候重点分析
【微信APP支付】SDK_For_Android //jar包和api文档
【微信APP支付】服务端demo //暂略
【微信支付】退款及对账demo //暂略
【微信APP支付】接口文档V1.2_For_Android.pdf //重点阅读
【微信支付】退款及对账开发指南.pdf //重点阅读
1. 依赖jar, libammsdk.jar
2. AndroidManifest.xml 一些基本设置
3. 继续挖支付相关多的代码
3.1 主activity WXEntryActivity
payBtn = (Button) findViewById(R.id.goto_pay_btn);
payBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//直接跳转到PayActivity并finish
startActivity(new Intent(WXEntryActivity.this, PayActivity.class));
finish();
}
});
3.2 PayActivity
public class PayActivity extends Activity {
private static final String TAG = "MicroMsg.SDKSample.PayActivity";
//key api
private IWXAPI api;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pay);
//通过工厂创建,传入APP_ID(从官方网站申请到的合法appId)
api = WXAPIFactory.createWXAPI(this, Constants.APP_ID);
Button payBtn = (Button) findViewById(R.id.pay_btn);
payBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//1. 获取access token
new GetAccessTokenTask().execute();
}
});
Button checkPayBtn = (Button) findViewById(R.id.check_pay_btn);
checkPayBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//判断微信版本是否支持支付
boolean isPaySupported = api.getWXAppSupportAPI() >= Build.PAY_SUPPORTED_SDK_INT;
Toast.makeText(PayActivity.this, String.valueOf(isPaySupported), Toast.LENGTH_SHORT).show();
}
});
}
/**
* 微信公众平台商户模块和商户约定的密钥
*
* 注意:不能hardcode在客户端,建议genPackage这个过程由服务器端完成
*/
private static final String PARTNER_KEY = "8934e7d15453e97507ef794cf7b0519d";
private String genPackage(List params) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < params.size(); i++) {
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
sb.append('&');
}
sb.append("key=");
sb.append(PARTNER_KEY); // 注意:不能hardcode在客户端,建议genPackage这个过程都由服务器端完成
// 进行md5摘要前,params内容为原始内容,未经过url encode处理
String packageSign = MD5.getMessageDigest(sb.toString().getBytes()).toUpperCase();
return URLEncodedUtils.format(params, "utf-8") + "&sign=" + packageSign;
}
/**
* 微信开放平台和商户约定的密钥
*
* 注意:不能hardcode在客户端,建议genSign这个过程由服务器端完成
*/
private static final String APP_SECRET = "db426a9829e4b49a0dcac7b4162da6b6"; // wxd930ea5d5a258f4f 对应的密钥
/**
* 微信开放平台和商户约定的支付密钥
*
* 注意:不能hardcode在客户端,建议genSign这个过程由服务器端完成
*/
private static final String APP_KEY = "L8LrMqqeGRxST5reouB0K66CaYAWpqhAVsq7ggKkxHCOastWksvuX1uvmvQclxaHoYd3ElNBrNO2DHnnzgfVG9Qs473M3DTOZug5er46FhuGofumV8H2FVR9qkjSlC5K"; // wxd930ea5d5a258f4f 对应的支付密钥
private class GetAccessTokenTask extends AsyncTask {
private ProgressDialog dialog;
@Override
protected void onPreExecute() {
//执行前 弹框提示
dialog = ProgressDialog.show(PayActivity.this, getString(R.string.app_tip), getString(R.string.getting_access_token));
}
@Override
protected void onPostExecute(GetAccessTokenResult result) {
if (dialog != null) {
dialog.dismiss();
}
if (result.localRetCode == LocalRetCode.ERR_OK) {
Toast.makeText(PayActivity.this, R.string.get_access_token_succ, Toast.LENGTH_LONG).show();
Log.d(TAG, "onPostExecute, accessToken = " + result.accessToken);
//3.获取到access token后,继续获取prePayId
GetPrepayIdTask getPrepayId = new GetPrepayIdTask(result.accessToken);
getPrepayId.execute();
} else {
Toast.makeText(PayActivity.this, getString(R.string.get_access_token_fail, result.localRetCode.name()), Toast.LENGTH_LONG).show();
}
}
@Override
protected GetAccessTokenResult doInBackground(Void... params) {
GetAccessTokenResult result = new GetAccessTokenResult();
//2.http get得到access token
String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
Constants.APP_ID, APP_SECRET);
Log.d(TAG, "get access token, url = " + url);
byte[] buf = Util.httpGet(url);
if (buf == null || buf.length == 0) {
result.localRetCode = LocalRetCode.ERR_HTTP;
return result;
}
String content = new String(buf);
result.parseFrom(content);
return result;
}
}
private class GetPrepayIdTask extends AsyncTask {
private ProgressDialog dialog;
private String accessToken;
public GetPrepayIdTask(String accessToken) {
this.accessToken = accessToken;
}
@Override
protected void onPreExecute() {
//获得prepay id 前弹框
dialog = ProgressDialog.show(PayActivity.this, getString(R.string.app_tip), getString(R.string.getting_prepayid));
}
@Override
protected void onPostExecute(GetPrepayIdResult result) {
if (dialog != null) {
dialog.dismiss();
}
if (result.localRetCode == LocalRetCode.ERR_OK) {
Toast.makeText(PayActivity.this, R.string.get_prepayid_succ, Toast.LENGTH_LONG).show();
//5. 得到prepay id后发起支付请求
sendPayReq(result);
} else {
Toast.makeText(PayActivity.this, getString(R.string.get_prepayid_fail, result.localRetCode.name()), Toast.LENGTH_LONG).show();
}
}
@Override
protected void onCancelled() {
super.onCancelled();
}
@Override
protected GetPrepayIdResult doInBackground(Void... params) {
//4. http post得到prepay id
String url = String.format("https://api.weixin.qq.com/pay/genprepay?access_token=%s", accessToken);
String entity = genProductArgs();
Log.d(TAG, "doInBackground, url = " + url);
Log.d(TAG, "doInBackground, entity = " + entity);
GetPrepayIdResult result = new GetPrepayIdResult();
byte[] buf = Util.httpPost(url, entity);
if (buf == null || buf.length == 0) {
result.localRetCode = LocalRetCode.ERR_HTTP;
return result;
}
String content = new String(buf);
Log.d(TAG, "doInBackground, content = " + content);
result.parseFrom(content);
return result;
}
}
private static enum LocalRetCode {
ERR_OK, ERR_HTTP, ERR_JSON, ERR_OTHER
}
// 结果解析, 辅助类
private static class GetAccessTokenResult {
private static final String TAG = "MicroMsg.SDKSample.PayActivity.GetAccessTokenResult";
public LocalRetCode localRetCode = LocalRetCode.ERR_OTHER;
public String accessToken;
public int expiresIn;
public int errCode;
public String errMsg;
public void parseFrom(String content) {
if (content == null || content.length() <= 0) {
Log.e(TAG, "parseFrom fail, content is null");
localRetCode = LocalRetCode.ERR_JSON;
return;
}
try {
JSONObject json = new JSONObject(content);
if (json.has("access_token")) { // success case
accessToken = json.getString("access_token");
expiresIn = json.getInt("expires_in");
localRetCode = LocalRetCode.ERR_OK;
} else {
errCode = json.getInt("errcode");
errMsg = json.getString("errmsg");
localRetCode = LocalRetCode.ERR_JSON;
}
} catch (Exception e) {
localRetCode = LocalRetCode.ERR_JSON;
}
}
}
// 结果解析, 辅助类
private static class GetPrepayIdResult {
private static final String TAG = "MicroMsg.SDKSample.PayActivity.GetPrepayIdResult";
public LocalRetCode localRetCode = LocalRetCode.ERR_OTHER;
public String prepayId;
public int errCode;
public String errMsg;
public void parseFrom(String content) {
if (content == null || content.length() <= 0) {
Log.e(TAG, "parseFrom fail, content is null");
localRetCode = LocalRetCode.ERR_JSON;
return;
}
try {
JSONObject json = new JSONObject(content);
if (json.has("prepayid")) { // success case
prepayId = json.getString("prepayid");
localRetCode = LocalRetCode.ERR_OK;
} else {
localRetCode = LocalRetCode.ERR_JSON;
}
errCode = json.getInt("errcode");
errMsg = json.getString("errmsg");
} catch (Exception e) {
localRetCode = LocalRetCode.ERR_JSON;
}
}
}
private String genNonceStr() {
Random random = new Random();
return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
}
private long genTimeStamp() {
return System.currentTimeMillis() / 1000;
}
/**
* 建议 traceid 字段包含用户信息及订单信息,方便后续对订单状态的查询和跟踪
*/
private String getTraceId() {
return "crestxu_" + genTimeStamp();
}
/**
* 注意:商户系统内部的订单号,32个字符内、可包含字母,确保在商户系统唯一
*/
private String genOutTradNo() {
Random random = new Random();
return MD5.getMessageDigest(String.valueOf(random.nextInt(10000)).getBytes());
}
private long timeStamp;
private String nonceStr, packageValue;
private String genSign(List params) {
StringBuilder sb = new StringBuilder();
int i = 0;
for (; i < params.size() - 1; i++) {
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
sb.append('&');
}
sb.append(params.get(i).getName());
sb.append('=');
sb.append(params.get(i).getValue());
String sha1 = Util.sha1(sb.toString());
Log.d(TAG, "genSign, sha1 = " + sha1);
return sha1;
}
private String genProductArgs() {
JSONObject json = new JSONObject();
try {
json.put("appid", Constants.APP_ID);
String traceId = getTraceId(); // traceId 由开发者自定义,可用于订单的查询与跟踪,建议根据支付用户信息生成此id
json.put("traceid", traceId);
nonceStr = genNonceStr();
json.put("noncestr", nonceStr);
List packageParams = new LinkedList();
packageParams.add(new BasicNameValuePair("bank_type", "WX"));
packageParams.add(new BasicNameValuePair("body", "千足金箍棒"));
packageParams.add(new BasicNameValuePair("fee_type", "1"));
packageParams.add(new BasicNameValuePair("input_charset", "UTF-8"));
packageParams.add(new BasicNameValuePair("notify_url", "http://weixin.qq.com"));
packageParams.add(new BasicNameValuePair("out_trade_no", genOutTradNo()));
packageParams.add(new BasicNameValuePair("partner", "1900000109"));
packageParams.add(new BasicNameValuePair("spbill_create_ip", "196.168.1.1"));
packageParams.add(new BasicNameValuePair("total_fee", "1"));
packageValue = genPackage(packageParams);
json.put("package", packageValue);
timeStamp = genTimeStamp();
json.put("timestamp", timeStamp);
List signParams = new LinkedList();
signParams.add(new BasicNameValuePair("appid", Constants.APP_ID));
signParams.add(new BasicNameValuePair("appkey", APP_KEY));
signParams.add(new BasicNameValuePair("noncestr", nonceStr));
signParams.add(new BasicNameValuePair("package", packageValue));
signParams.add(new BasicNameValuePair("timestamp", String.valueOf(timeStamp)));
signParams.add(new BasicNameValuePair("traceid", traceId));
json.put("app_signature", genSign(signParams));
json.put("sign_method", "sha1");
} catch (Exception e) {
Log.e(TAG, "genProductArgs fail, ex = " + e.getMessage());
return null;
}
return json.toString();
}
private void sendPayReq(GetPrepayIdResult result) {
PayReq req = new PayReq();
req.appId = Constants.APP_ID;
req.partnerId = Constants.PARTNER_ID;
req.prepayId = result.prepayId;
req.nonceStr = nonceStr;
req.timeStamp = String.valueOf(timeStamp);
req.packageValue = "Sign=" + packageValue;
List signParams = new LinkedList();
signParams.add(new BasicNameValuePair("appid", req.appId));
signParams.add(new BasicNameValuePair("appkey", APP_KEY));
signParams.add(new BasicNameValuePair("noncestr", req.nonceStr));
signParams.add(new BasicNameValuePair("package", req.packageValue));
signParams.add(new BasicNameValuePair("partnerid", req.partnerId));
signParams.add(new BasicNameValuePair("prepayid", req.prepayId));
signParams.add(new BasicNameValuePair("timestamp", req.timeStamp));
req.sign = genSign(signParams);
// 在支付之前,如果应用没有注册到微信,应该先调用IWXMsg.registerApp将应用注册到微信
api.sendReq(req);
}
}
PayActivity小结, 1.获取accessTonken; 2.获取prePayId; 3.发起支付; 4.各步骤的输入参数说明见代码和文档
整个过程中用到的信息
PARTNER_ID,
用途:在demo方法sendPayReq中用到
来源:暂未在文档中找到,见后记
PARTNER_KEY,
用途:在demo方法genPackage中用到。
官方建议不能hardcode在客户端
来源:见后记
APP_KEY,
用途:在demo获取prepayID的过程中用到,具体方法genProductArgs() + 在sendPayReq过程中用到。
官方建议不能hardcode在客户端
来源:见后记
APP_SECRET,
用途:在demo的内部类GetAccessTokenTask的doInBackground用到。
官方建议不能hardcode在客户端
来源:http://mch.weixin.qq.com/wiki/doc/api/index.php?chapter=3_1
AppSecret是APPID对应的接口密码,用于获取接口调用凭证access_token时使用。
在微信支付中,先通过OAuth2.0接口获取用户openid,此openid用于微信内网页支付模式下单接口使用。
在开发模式中获取AppSecret(成为开发者且帐号没有异常状态)。
APP_ID,
用途:每个环节都用到了:构建IWXAPI,获取accessToken,获取prepayID,sendPayReq都用到了
来源:http://mch.weixin.qq.com/wiki/doc/api/index.php?chapter=3_1
appid是微信公众账号或开放平台APP的唯一标识,在公众平台申请公众账号或者在开放平台申请APP账号后,
微信会自动分配对应的appid,用于标识该应用。商户的微信支付审核通过邮件中也会包含该字段值。
当你在微信开发平台(open.weixin.qq.com)申请一个普通应用时,你会得到:
public static final String APP_ID = “wxd930ea5d5a258f4f”;
private static final String APP_SECRET = “db426a9829e4b49a0dcac7b4162da6b6″;
当你的应用涉及到微信支付功能是,你需要申请微信支付,申请会有些合同啊什么的,审核通过你会收到邮件,邮件中会有
private static final String APP_KEY(PaySignKey) = “L8LrMqqeGRxST5reouB0K66CaYAWpqhAVsq7ggKkxHCOastWksvuX1uvmvQclxaHoYd3ElNBrNO2DHnnzgfVG9Qs473M3DTOZug5er46FhuGofumV8H2FVR9qkjSlC5K”; // wxd930ea5d5a258f4f 对应的支付密钥
除此之外,还有个微信支付商户申请,申请通过后邮件中会回复:
public static final String PARTNER_ID = “1900000109″;
private static final String PARTNER_KEY = “8934e7d15453e97507ef794cf7b0519d”;
然后为了支付的安全起见,最好把支付的前两个步骤放在服务器端完成,
客户端请求服务器拿到前两个步骤生成的一些参数,
然后在客户端做第三步,调起微信支付。