微信支付
来源:互联网 发布:海岛奇兵医师数据 编辑:程序博客网 时间:2024/06/09 18:20
- 商户应用接入微信支付申请, 略, 具体见官网步骤(https://open.weixin.qq.com/cgi-bin/frame?t=resource/res_main_tmpl&verify=1&lang=zh_CN 移动应用开发->微信支付功能->微信APP支付接入商户服务中心, 里面包含了1. 申请流程指引 2. 开放类目及资费标准 3. 常见商户接入问题)
- 场景介绍
适用于商户在移动端APP中集成微信支付功能。商户APP调用微信提供的SDK调用微信支付模块,商户APP会跳转到微信中完成支付,支付完后跳回到商户APP内,最后展示支付结果。交互细节如下:步骤1:用户进入商户APP,选择商品下单、确认购买,进入支付环节。商户服务后台生成支付订单,签名后将数据传输到APP端步骤2:用户点击后发起支付操作,进入到微信界面,调起微信支付,出现确认支付界面步骤3:用户确认收款方和金额,点击立即支付后出现输入密码界面,可选择零钱或银行卡支付步骤4:输入正确密码后,支付完成,用户端微信出现支付详情页面步骤5:回跳到商户APP中,商户APP根据支付结果个性化展示订单处理结果
- 总的业务流程图(时间戳20150112),源图来自http://mch.weixin.qq.com/wiki/doc/api/index.php?chapter=8_3
- Android开发要点说明
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.apk2)注册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 //重点阅读
- 【微信APP支付】Sample_For_Android, (围绕支付,其他略。 sdk demo里面的功能包含有支付 分享 收藏等)
1. 依赖jar, libammsdk.jar2. 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 PayActivitypublic 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<NameValuePair> 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<Void, Void, GetAccessTokenResult> { 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<Void, Void, GetPrepayIdResult> { 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<NameValuePair> 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<NameValuePair> packageParams = new LinkedList<NameValuePair>(); 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<NameValuePair> signParams = new LinkedList<NameValuePair>(); 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<NameValuePair> signParams = new LinkedList<NameValuePair>(); 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.各步骤的输入参数说明见代码和文档
小结
- 对比支付宝的api, 微信支付这个版本的demo太粗糙了, 比如: 相关常量定义不集中; api不够简单; 先取得accessToken在取得prePayId完全可以封一个api的,然后用户传入一堆必须的参数并在api内部筛选判断组织,还不用暴露这么多api的url出来。(后记: 和上面的总的业务流程放在一起看,这个demo简直就在误导开发者。 总业务没access_token的获取,获取prepayId是在商户的后台的,但android的demo中有获取access_token的步骤且还用到了APP_SECRET,获取prepayId也是放在客户端;代码中一方面强调不能把重要信息hard code到代码,一方面关于这些重要信息又在到处用。。。)
- 支付过程三部走 1.获取accessTonken; 2.获取prePayId; 3.发起支付; (如同后记里说的,建议将1和2放在服务器端处理,客户端负责提供必要的参数)
- 跟支付宝一样得先注册,详细见上文商户应用接入微信支付申请
- 构建后台
- 构建移动客户端
整个过程中用到的信息
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,用于标识该应用。商户的微信支付审核通过邮件中也会包含该字段值。
后记
针对上面一些参数的来源不明的问题,在一顿google之后找到了暂时的答案,毕竟版本在更新
当你在微信开发平台(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”;
同时针对我小结里的第一点网友也有现成的建议或者方案(微信demo一开始就这样写就少让人走弯路了)
然后为了支付的安全起见,最好把支付的前两个步骤放在服务器端完成,客户端请求服务器拿到前两个步骤生成的一些参数,然后在客户端做第三步,调起微信支付。
1 0
- 微信、支付宝支付
- 支付宝,微信支付
- 微信支付【支付模式】
- 微信支付Jsapi支付
- 微信支付-刷卡支付
- 微信支付JSAPI支付
- 微信支付 h5 支付
- 支付--微信APP支付
- 微信支付:手机支付
- 支付宝 微信支付
- 支付宝-微信支付
- 微信支付与支付宝支付
- 支付宝支付与微信支付
- 微信支付和支付宝支付
- 微信支付与支付宝支付
- 支付宝支付和微信支付
- 微信支付和支付宝支付
- 支付宝支付接入+微信支付
- UVA - 10420 List of Conquests
- UVA - 340 Master-Mind Hints
- UVA - 10474 Where is the Marble?
- UVA - 152 Tree's a Crowd
- HEI
- 微信支付
- Strange Way to Express Integers--扩展欧几里得和中国剩余定理
- K-means聚类方法 - Andrew Ng笔记的翻译及理解
- 接入微信的流程,不官方,赞
- Android学习之AutoCompleteTextView和MultiAutoCompleteTextView
- URAL 1519 Formula 1 dp(插头)
- 文章标题
- CF 678A 暑假集训13
- HTML之表单初识