歡迎留言、轉(zhuǎn)發(fā)
微信極速開發(fā)系列文章(微信支付、授權(quán)獲取用戶信息等):點(diǎn)擊這里
目錄
1、注冊賬號、開發(fā)者認(rèn)證
2、添加應(yīng)用
3、申請微信支付
4、技術(shù)開發(fā)功能實(shí)現(xiàn)步驟介紹
5、代碼實(shí)例
服務(wù)端源碼地址:http://git.oschina.net/javen205/weixin_guide
客戶端源碼地址:https://github.com/Javen205/JPay
微信APP支付接入商戶服務(wù)中心 官方介紹文檔
1、注冊賬號、開發(fā)者認(rèn)證
在開放平臺直接注冊,注冊郵箱不能與微信其他的產(chǎn)品同號。
比較坑的是微信公眾號中的支付(微信買單、刷卡、公眾號支付、wap支付)以及微信app支付都需要進(jìn)行微信認(rèn)證而不是公用一個微信商戶平臺(需要交兩次認(rèn)證的費(fèi)用)。

微信認(rèn)證這個時間比較短(畢竟交了300大洋)一般一個工作日就會有人聯(lián)系你核查公司的資料。
微信認(rèn)證(開發(fā)者資質(zhì)認(rèn)證)通過之后就可以在開放平臺添加應(yīng)用了(這個需要審核),應(yīng)用通過之后就可以申請微信支付了(也需要審核)
2、添加應(yīng)用
這個比較簡單,按照提示操作就行 上圖




應(yīng)用包名只定義,應(yīng)用簽名可以使用資源下載中心的簽名生成工具。務(wù)必記住包名以及簽名keystore文件的密碼,如果包名或者簽名文件不對打包是喚不起微信支付的。



3、申請微信支付
如果添加的應(yīng)用審核通過了(一個工作日),就可以直接申請微信支付了(7個工作日之內(nèi))。

審核通過之后將會收到審核通過的郵件,里面有登錄商戶平臺的登錄賬戶、密碼、商戶號以及一些操作指引的說明。服務(wù)端生成預(yù)付訂單的簽名需要密鑰 設(shè)置方法可以參考這里
4、技術(shù)開發(fā)功能實(shí)現(xiàn)
這里主要聊聊Android微信支付,主要包括以下幾個步驟
1、商戶服務(wù)端生成訂單并在微信平臺生成預(yù)付訂單
2、客戶端調(diào)起微信支付進(jìn)行支付
3、客戶端回調(diào)支付結(jié)果
4、服務(wù)端接收支付通知
1、商戶服務(wù)端生成訂單并在微信平臺生成預(yù)付訂單
調(diào)起微信支付前需要服務(wù)器生成支付訂單再調(diào)用【統(tǒng)一下單API】生成預(yù)付訂單prepayId,再生成簽名sign【調(diào)起支付API】
以上兩個步驟建議都在服務(wù)端完成,客戶端(Android)通過接口獲取對應(yīng)的參數(shù)即可
2、客戶端調(diào)起微信支付進(jìn)行支付
通過微信提供的jar 喚起微信支付

3、客戶端回調(diào)支付結(jié)果
參照微信SDK Sample,在net.sourceforge.simcpux.wxapi包路徑中實(shí)現(xiàn)WXPayEntryActivity類【包名或類名不一致會造成無法回調(diào)】
栗子說明:認(rèn)真反復(fù)讀了幾遍,感覺這句話有歧義是一個坑,測試的時候一直不回調(diào)。這里他想說的意識如下:
比如你申請應(yīng)用包名為:javen.com那么回調(diào)的WXPayEntryActivity類必須放到javen.com.wxapi的包下面

4、服務(wù)端接收支付通知
支付結(jié)果通知【官方文檔】
代碼實(shí)現(xiàn)參考開源項(xiàng)目 【點(diǎn)擊這里】
5、代碼實(shí)例
服務(wù)端代碼:根據(jù)商戶訂單生成微信預(yù)付訂單并返回喚起微信支付需要的參數(shù)。Demo中參數(shù)寫成固定了僅供參考
此項(xiàng)目已開源 【點(diǎn)擊這里】 如果對你有幫助請點(diǎn)擊
Start告訴我 hahaha 。以下代碼對應(yīng)的目錄在com.javen.weixin.controller.WeixinPayController.java中
/**
* 微信APP支付
*/
public void appPay(){
//不用設(shè)置授權(quán)目錄域名
//統(tǒng)一下單地址 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1#
Map<String, String> params = new HashMap<String, String>();
params.put("appid", appid);
params.put("mch_id", partner);
params.put("nonce_str", System.currentTimeMillis() / 1000 + "");
params.put("body", "Javen微信公眾號極速開發(fā)");
String out_trade_no=System.currentTimeMillis()+"";
params.put("attach", "custom json");
params.put("out_trade_no", out_trade_no);
int price=10000;
params.put("total_fee", price+"");
String ip = IpKit.getRealIp(getRequest());
if (StrKit.isBlank(ip)) {
ip = "127.0.0.1";
}
params.put("spbill_create_ip", ip);
params.put("notify_url", notify_url);
params.put("trade_type", "APP");
String sign = PaymentKit.createSign(params, paternerKey);
params.put("sign", sign);
String xmlResult = PaymentApi.pushOrder(params);
System.out.println(xmlResult);
Map<String, String> result = PaymentKit.xmlToMap(xmlResult);
String return_code = result.get("return_code");
String return_msg = result.get("return_msg");
if (StrKit.isBlank(return_code) || !"SUCCESS".equals(return_code)) {
ajax.addError(return_msg);
renderJson(ajax);
return;
}
String result_code = result.get("result_code");
if (StrKit.isBlank(result_code) || !"SUCCESS".equals(result_code)) {
ajax.addError(return_msg);
renderJson(ajax);
return;
}
// 以下字段在return_code 和result_code都為SUCCESS的時候有返回
String prepay_id = result.get("prepay_id");
//封裝調(diào)起微信支付的參數(shù) https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12
Map<String, String> packageParams = new HashMap<String, String>();
packageParams.put("appid", appid);
packageParams.put("partnerid", partner);
packageParams.put("prepayid", prepay_id);
packageParams.put("package", "Sign=WXPay");
packageParams.put("noncestr", System.currentTimeMillis() + "");
packageParams.put("timestamp", System.currentTimeMillis() / 1000 + "");
String packageSign = PaymentKit.createSign(packageParams, paternerKey);
packageParams.put("sign", packageSign);
String jsonStr = JsonUtils.toJson(packageParams);
System.out.println("最新返回apk的參數(shù):"+jsonStr);
renderJson(jsonStr);
}
客戶端代碼實(shí)現(xiàn)
使用單例模式統(tǒng)一入口,首先判斷微信客戶端是否安裝,如果有安裝再從商戶服務(wù)器獲取調(diào)起支付的參數(shù)
public class IPay {
private static IPay mIPay;
private Context mContext;
private IPay(Context context) {
mContext = context;
}
public static IPay getIntance(Context context){
if (mIPay == null) {
synchronized(IPay.class){
if (mIPay == null) {
mIPay = new IPay(context);
}
}
}
return mIPay;
}
//支付結(jié)果回調(diào)
public interface IPayListener{
void onPay(int code);
}
public void toTestPay(Order order,IPayListener listener){
if (order != null) {
if (IPayLogic.getIntance(mContext.getApplicationContext()).isWeixinAvilible()) {
Constants.payListener = listener;
new TestPayPrepay(mContext).execute();
}else {
Toast.makeText(mContext, "未安裝微信", Toast.LENGTH_LONG).show();
}
}else {
Toast.makeText(mContext, "參數(shù)異常 order is null", Toast.LENGTH_LONG).show();
}
}
}
調(diào)起微信支付、獲取調(diào)取微信支付參數(shù)、判斷微信是否安裝邏輯實(shí)現(xiàn)
public class IPayLogic {
private static IPayLogic mIPayLogic;
private Context mContext;
private IPayLogic(Context context) {
mContext = context;
}
public static IPayLogic getIntance(Context context){
if (mIPayLogic == null) {
synchronized(IPayLogic.class){
if (mIPayLogic == null) {
mIPayLogic = new IPayLogic(context);
}
}
}
return mIPayLogic;
}
//測試
public String testPay(){
return HttpKit.get(Constants.TESTPAY_URL);
}
/**
* 調(diào)起支付
* @param appId
* @param partnerId
* @param prepayId
* @param nonceStr
* @param timeStamp
* @param sign
*/
public void startWXPay(String appId,String partnerId,String prepayId,
String nonceStr,String timeStamp,String sign){
IWXAPI api= WXAPIFactory.createWXAPI(mContext, null);
api.registerApp(appId);
boolean isPaySupported = api.getWXAppSupportAPI() >= Build.PAY_SUPPORTED_SDK_INT;
if (!isPaySupported) {
Toast.makeText(mContext, "請更新微信客戶端", Toast.LENGTH_SHORT).show();
return;
}
PayReq request = new PayReq();
request.appId = appId;
request.partnerId = partnerId;
request.prepayId= prepayId;
request.packageValue = "Sign=WXPay";
request.nonceStr=nonceStr;
request.timeStamp= timeStamp;
request.sign= sign;
api.sendReq(request);
}
/**
* 判斷微信是否安裝
* @param context
* @return
*/
public boolean isWeixinAvilible() {
final PackageManager packageManager = mContext.getPackageManager();// 獲取packagemanager
List<PackageInfo> pinfo = packageManager.getInstalledPackages(0);// 獲取所有已安裝程序的包信息
if (pinfo != null) {
for (int i = 0; i < pinfo.size(); i++) {
String pn = pinfo.get(i).packageName;
if (pn.equals("com.tencent.mm")) {
return true;
}
}
}
return false;
}
}
HttpKit MD5 工具類
/**
* HttpKit
*/
public class HttpKit {
private HttpKit() {}
/**
* https 域名校驗(yàn)
*/
private class TrustAnyHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
/**
* https 證書管理
*/
private class TrustAnyTrustManager implements X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
}
private static final String GET = "GET";
private static final String POST = "POST";
private static String CHARSET = "UTF-8";
private static final SSLSocketFactory sslSocketFactory = initSSLSocketFactory();
private static final TrustAnyHostnameVerifier trustAnyHostnameVerifier = new HttpKit().new TrustAnyHostnameVerifier();
private static SSLSocketFactory initSSLSocketFactory() {
try {
TrustManager[] tm = {new HttpKit().new TrustAnyTrustManager() };
SSLContext sslContext = SSLContext.getInstance("TLS"); // ("TLS", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
return sslContext.getSocketFactory();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void setCharSet(String charSet) {
if (charSet.isEmpty()) {
throw new IllegalArgumentException("charSet can not be blank.");
}
HttpKit.CHARSET = charSet;
}
private static HttpURLConnection getHttpConnection(String url, String method, Map<String, String> headers) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyManagementException {
URL _url = new URL(url);
HttpURLConnection conn = (HttpURLConnection)_url.openConnection();
if (conn instanceof HttpsURLConnection) {
((HttpsURLConnection)conn).setSSLSocketFactory(sslSocketFactory);
((HttpsURLConnection)conn).setHostnameVerifier(trustAnyHostnameVerifier);
}
conn.setRequestMethod(method);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(19000);
conn.setReadTimeout(19000);
conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36");
if (headers != null && !headers.isEmpty())
for (Entry<String, String> entry : headers.entrySet())
conn.setRequestProperty(entry.getKey(), entry.getValue());
return conn;
}
/**
* Send GET request
*/
public static String get(String url, Map<String, String> queryParas, Map<String, String> headers) {
HttpURLConnection conn = null;
try {
conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), GET, headers);
conn.connect();
return readResponseString(conn);
}
catch (Exception e) {
throw new RuntimeException(e);
}
finally {
if (conn != null) {
conn.disconnect();
}
}
}
public static String get(String url, Map<String, String> queryParas) {
return get(url, queryParas, null);
}
public static String get(String url) {
return get(url, null, null);
}
/**
* Send POST request
*/
public static String post(String url, Map<String, String> queryParas, String data, Map<String, String> headers) {
HttpURLConnection conn = null;
try {
conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), POST, headers);
conn.connect();
OutputStream out = conn.getOutputStream();
out.write(data.getBytes(CHARSET));
out.flush();
out.close();
return readResponseString(conn);
}
catch (Exception e) {
throw new RuntimeException(e);
}
finally {
if (conn != null) {
conn.disconnect();
}
}
}
public static String post(String url, Map<String, String> queryParas, String data) {
return post(url, queryParas, data, null);
}
public static String post(String url, String data, Map<String, String> headers) {
return post(url, null, data, headers);
}
public static String post(String url, String data) {
return post(url, null, data, null);
}
private static String readResponseString(HttpURLConnection conn) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
try {
inputStream = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, CHARSET));
String line = null;
while ((line = reader.readLine()) != null){
sb.append(line).append("\n");
}
return sb.toString();
}
catch (Exception e) {
throw new RuntimeException(e);
}
finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
}
}
}
}
/**
* Build queryString of the url
*/
private static String buildUrlWithQueryString(String url, Map<String, String> queryParas) {
if (queryParas == null || queryParas.isEmpty())
return url;
StringBuilder sb = new StringBuilder(url);
boolean isFirst;
if (url.indexOf("?") == -1) {
isFirst = true;
sb.append("?");
}
else {
isFirst = false;
}
for (Entry<String, String> entry : queryParas.entrySet()) {
if (isFirst) isFirst = false;
else sb.append("&");
String key = entry.getKey();
String value = entry.getValue();
if (!value.isEmpty())
try {value = URLEncoder.encode(value, CHARSET);} catch (UnsupportedEncodingException e) {throw new RuntimeException(e);}
sb.append(key).append("=").append(value);
}
return sb.toString();
}
}
public class MD5 {
public static String MD5sign(String s) {
char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
try {
byte[] btInput = s.getBytes("UTF-8");
// 獲得MD5摘要算法的 MessageDigest 對象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 使用指定的字節(jié)更新摘要
mdInst.update(btInput);
// 獲得密文
byte[] md = mdInst.digest();
// 把密文轉(zhuǎn)換成十六進(jìn)制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str).toLowerCase();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
System.out.println(MD5sign("Hello world"));
}
}
使用
AsyncTask異步獲取調(diào)起微信支付的相關(guān)參數(shù)。當(dāng)然你也可以使用其他的異步網(wǎng)絡(luò)請求開源框架
public class TestPayPrepay extends AsyncTask<Object, Integer, String> {
private Context mContext;
public TestPayPrepay(Context context) {
this.mContext = context;
}
@Override
protected String doInBackground(Object... params) {
System.out.println("TestPayPrepay doInBackground");
return IPayLogic.getIntance(mContext).testPay();
}
@Override
protected void onPostExecute(String result) {
try {
if (result!=null) {
System.out.println("TestPayPrepay result>"+result);
JSONObject data = new JSONObject(result);
if(!data.has("code")){
String sign = data.getString("sign");
String timestamp = data.getString("timestamp");
String noncestr = data.getString("noncestr");
String partnerid = data.getString("partnerid");
String prepayid = data.getString("prepayid");
String appid = data.getString("appid");
Toast.makeText(mContext, "正在調(diào)起支付", Toast.LENGTH_SHORT).show();
Constants.APP_ID = appid;
IPayLogic.getIntance(mContext).startWXPay(appid, partnerid, prepayid, noncestr, timestamp, sign);
}else{
String message = data.getString("message");
Log.d("PAY_GET", "返回錯誤"+message);
Toast.makeText(mContext, "返回錯誤:"+message, Toast.LENGTH_SHORT).show();
}
}else {
System.out.println("get prepayid exception, is null");
}
} catch (Exception e) {
Log.e("PAY_GET", "異常:"+e.getMessage());
Toast.makeText(mContext, "異常:"+e.getMessage(), Toast.LENGTH_SHORT).show();
}
super.onPostExecute(result);
}
}
支付結(jié)果回調(diào)
<activity
android:name="[應(yīng)用的包名].wxapi.WXPayEntryActivity"
android:exported="true"
android:theme="@android:style/Theme.Translucent"
android:launchMode="singleTop" >
</activity>
封裝的是SDK 所以這里設(shè)置了一個透明的主題
public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler{
private IWXAPI api;
private IPayListener payListener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);//隱藏標(biāo)題
LinearLayout ll = new LinearLayout(this);
setContentView(ll);
api = WXAPIFactory.createWXAPI(this, Constants.APP_ID);
api.handleIntent(getIntent(), this);
finish();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
api.handleIntent(intent, this);
}
@Override
public void onReq(BaseReq req) {
}
@Override
public void onResp(BaseResp resp) {
payListener = Constants.payListener;
int code = resp.errCode;
System.out.println("onResp errCode>"+code);
if (payListener!=null) {
payListener.onPay(code);
System.out.println("payListener callback");
}else {
System.out.println("payListener not callback");
}
}
}
注意:如果回調(diào)的
code一直返回-1
1、請檢查應(yīng)用包名以及apk 的簽名是否與你提交到微信開放平臺的一致
2、請檢查返回調(diào)取微信支付的參數(shù)是否正確
大部分原因是第一種

遺留問題:由于支付應(yīng)用的包名不固定
WXPayEntryActivity無法封裝到j(luò)ar中,需要單獨(dú)在支付應(yīng)用添加.wxapi這個包名并復(fù)制WXPayEntryActivity到此包中。如果有好的解決方案歡迎留言
微信開發(fā)系列文章 http://www.itdecent.cn/p/a172a1b69fdd
推薦閱讀
極速開發(fā)微信公眾號之微信買單
極速開發(fā)微信公眾號之公眾號支付
極速開發(fā)微信公眾號之掃碼支付
極速開發(fā)微信公眾號之刷卡支付
極速開發(fā)微信公眾號之現(xiàn)金紅包
極速開發(fā)微信公眾號之模板消息
如果此文章對你有幫助請點(diǎn)擊喜歡告訴我
服務(wù)端源碼地址:http://git.oschina.net/javen205/weixin_guide
客戶端源碼地址:https://github.com/Javen205/JPay