前言
在Android 中只要提到支付,就必然会使用支付宝、微信、银联这三大支付渠道,所以只要遇到支付需求,就必须去跟着三大支付渠道的文档去对接,每次都是体力活,能不能把三大支付渠写成统一的支付模块,只需要对支付所需要的信息进行配置,一句方法调用即可完成支付?
这篇博客就是完成这个需求的,下面让我来详细说明实现步骤。
准备
请务必到支付宝、银联、微信,下载好Android所需要的SDK备用。支付宝Sdk 使用的是蚂蚁金服下的2.0版本。银联支付,请使用2016年12月后发布的版本,因为该版本已经将插件支付和内置支付合并在一起了。不需要检测是否安装插件。
支付所需数据
支付宝
字段说明
orderInfo支付宝2.0SDK中在服务器端生成好的订单信息
参考网址:
https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.R90GnU&treeId=59&articleId=103663&docType=1
使用建造者模式,创建支付宝支付所需数据
public class AliPayConfig {
private String orderInfo;
private Activity context;
private AliPayConfig() {
}
public String
getOrderInfo() {
return orderInfo;
}
public Activity
getContext() {
return context;
}
public static class Builder {
private String orderInfo;
private Activity context;
public Builder() {
super();
}
public AliPayConfig.Builder
with(Activity context) {
this.context = context;
return this;
}
/**
* 设置支付宝支付OrderInfo
*
* @param orderInfo
* @return
*/
public AliPayConfig.Builder
setOrderInfo(String orderInfo) {
this.orderInfo = orderInfo;
return this;
}
public AliPayConfig
build() {
AliPayConfig aliPayPayConfig =
new AliPayConfig();
aliPayPayConfig.context =
this.context;
aliPayPayConfig.orderInfo =
this.orderInfo;
return aliPayPayConfig;
}
}
}
微信
字段说明
appId微信支付AppIDpartnerId支付商户号prepayId预支付码(重要)packageValuepackageValuenonceStrnonceStrtimeStamp时间戳sign签名
参考网址:
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12&index=2
使用建造者模式创建微信支付所需数据
public class WeiXinPayConfig {
private Activity context;
private String appId;
private String partnerId;
private String prepayId;
private String packageValue;
private String nonceStr;
private String timeStamp;
private String sign;
private WeiXinPayConfig() {
}
public Activity
getContext() {
return context;
}
public String
getAppId() {
return appId;
}
public String
getPartnerId() {
return partnerId;
}
public String
getPrepayId() {
return prepayId;
}
public String
getPackageValue() {
return packageValue;
}
public String
getNonceStr() {
return nonceStr;
}
public String
getTimeStamp() {
return timeStamp;
}
public String
getSign() {
return sign;
}
public static class Builder {
private Activity context;
private String appId;
private String partnerId;
private String prepayId;
private String packageValue =
"Sign=WXPay";
private String nonceStr;
private String timeStamp;
private String sign;
public Builder() {
super();
}
public Builder
with(Activity context) {
this.context = context;
return this;
}
/**
* 设置微信支付AppID
*
* @param appId
* @return
*/
public Builder
setAppId(String appId) {
this.appId = appId;
return this;
}
/**
* 微信支付商户号
*
* @param partnerId
* @return
*/
public Builder
setPartnerId(String partnerId) {
this.partnerId = partnerId;
return this;
}
/**
* 设置预支付码(重要)
*
* @param prepayId
* @return
*/
public Builder
setPrepayId(String prepayId) {
this.prepayId = prepayId;
return this;
}
/**
* 设置
*
* @param packageValue
* @return
*/
public Builder
setPackageValue(String packageValue) {
this.packageValue = packageValue;
return this;
}
/**
* 设置
*
* @param nonceStr
* @return
*/
public Builder
setNonceStr(String nonceStr) {
this.nonceStr = nonceStr;
return this;
}
/**
* 设置时间戳
*
* @param timeStamp
* @return
*/
public Builder
setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
return this;
}
/**
* 设置签名
*
* @param sign
* @return
*/
public Builder
setSign(String sign) {
this.sign = sign;
return this;
}
public WeiXinPayConfig
build() {
WeiXinPayConfig weiXinPayConfig =
new WeiXinPayConfig();
weiXinPayConfig.context =
this.context;
weiXinPayConfig.appId =
this.appId;
weiXinPayConfig.partnerId =
this.partnerId;
weiXinPayConfig.prepayId =
this.prepayId;
weiXinPayConfig.packageValue =
this.packageValue;
weiXinPayConfig.nonceStr =
this.nonceStr;
weiXinPayConfig.timeStamp =
this.timeStamp;
weiXinPayConfig.sign =
this.sign;
return weiXinPayConfig;
}
}
}
银联
字段说明
tradeCode文档中描述的TnserverModel文档中描述的serverModel
参考网址:
https://open.unionpay.com/ajweb/help/file/techFile?productId=3
手机控件支付开发包(Android 版/app开发包/控件使用指南/中国银联手机支付控件接入指南Android.doc
使用建造者模式创建银联所需数据
public class UnionBankPayConfig {
private Activity context;
private String tradeCode;
private String serverModel;
private UnionBankPayConfig() {
}
public Activity
getContext() {
return context;
}
public String
getTradeCode() {
return tradeCode;
}
public String
getServerModel() {
return serverModel;
}
public static class Builder {
private Activity context;
private String tradeCode;
private String serverModel;
public Builder() {
super();
}
public UnionBankPayConfig.Builder
with(Activity context) {
this.context = context;
return this;
}
/**
* 设置银联支付Tn
*
* @param tradeCode
* @return
*/
public UnionBankPayConfig.Builder
setTradeCode(String tradeCode) {
this.tradeCode = tradeCode;
return this;
}
/**
* 设置银联支付服务模式
*
* @param serverModel “00” – 银联正式环境 ,“01” – 银联测试环境,该环境中不发生真实交易
* @return
*/
public UnionBankPayConfig.Builder
setServerModel(String serverModel) {
this.serverModel = serverModel;
return this;
}
public UnionBankPayConfig
build() {
UnionBankPayConfig unionBankPayConfig =
new UnionBankPayConfig();
unionBankPayConfig.context =
this.context;
unionBankPayConfig.tradeCode =
this.tradeCode;
unionBankPayConfig.serverModel =
this.serverModel;
return unionBankPayConfig;
}
}
}
抽象PayFunction
根据阅读三家支付渠道文档,发现,其实大致步骤可以归结为两步。
检查是否安装有支付渠道插件支付,获取同步返回信息
提取共性,创建PayFunction接口
public interface PayFunction {
void payOrder();
void checkPayState(CheckStateListener checkStateListener);
}
支付宝
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.widget.Toast;
import com.alipay.sdk.app.PayTask;
import com.ta.utdid2.android.utils.StringUtils;
import org.unreal.common.pay.CheckStateListener;
import org.unreal.common.pay.PayFunction;
import org.unreal.common.pay.PayResultListener;
import java.util.Map;
public class AliPay implements PayFunction {
private static final int SDK_PAY_FLAG =
1;
private static final int SDK_CHECK_FLAG =
2;
private AliPayConfig config;
private PayResultListener listener;
private Handler mHandler;
private CheckStateListener checkStateListener;
public AliPay(PayResultListener listener) {
this.listener = listener;
}
@Override
public void payOrder() {
Runnable checkRunnable =
new Runnable() {
@Override
public void run() {
PayTask payTask =
new PayTask(config.getContext());
Map<String, String> result = payTask.payV2(config.getOrderInfo(),
true);
Message msg =
new Message();
msg.what = SDK_PAY_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};
Thread payThread =
new Thread(checkRunnable);
payThread.start();
}
@Override
public void checkPayState(CheckStateListener checkStateListener) {
this.checkStateListener = checkStateListener;
Runnable checkRunnable =
new Runnable() {
@Override
public void run() {
PayTask payTask =
new PayTask(config.getContext());
String result = payTask.getVersion();
Message msg =
new Message();
msg.what = SDK_CHECK_FLAG;
msg.obj = result;
mHandler.sendMessage(msg);
}
};
Thread checkThread =
new Thread(checkRunnable);
checkThread.start();
}
public AliPay
setConfig(
final AliPayConfig config) {
this.config = config;
mHandler =
new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SDK_PAY_FLAG: {
PayResult payResult =
new PayResult((Map<String, String>) msg.obj);
String resultStatus = payResult.getResultStatus();
if (TextUtils.equals(resultStatus,
"9000")) {
Toast.makeText(config.getContext(),
"支付宝支付成功", Toast.LENGTH_SHORT).show();
if (listener !=
null) listener.onPaySuccess();
}
else {
if (TextUtils.equals(resultStatus,
"8000")) {
Toast.makeText(config.getContext(),
"支付宝支付结果确认中", Toast.LENGTH_SHORT).show();
if (listener !=
null) listener.onPayConfirming();
}
else {
Toast.makeText(config.getContext(),
"支付宝支付失败", Toast.LENGTH_SHORT).show();
if (listener !=
null) listener.onPayFailure();
}
}
break;
}
case SDK_CHECK_FLAG: {
if (listener !=
null) {
if (StringUtils.isEmpty(msg.obj.toString())) {
Toast.makeText(config.getContext(),
"没有安装支付宝或支付宝版本过低,无法使用支付宝支付!"
, Toast.LENGTH_SHORT).show();
checkStateListener.checkState(
false);
}
else {
checkStateListener.checkState(
true);
}
}
break;
}
default:
break;
}
}
};
return this;
}
}
这种实现方案有漏洞,没有使用公钥验证返回值是否合法,有风险。
import android.text.TextUtils;
import java.util.Map;
public class PayResult {
private String resultStatus;
private String result;
private String memo;
public PayResult(Map<String, String> rawResult) {
if (rawResult ==
null) {
return;
}
for (String key : rawResult.keySet()) {
if (TextUtils.equals(key,
"resultStatus")) {
resultStatus = rawResult.get(key);
}
else if (TextUtils.equals(key,
"result")) {
result = rawResult.get(key);
}
else if (TextUtils.equals(key,
"memo")) {
memo = rawResult.get(key);
}
}
}
@Override
public String
toString() {
return "resultStatus={" + resultStatus +
"};memo={" + memo
+
"};result={" + result +
"}";
}
/**
* @return the resultStatus
*/
public String
getResultStatus() {
return resultStatus;
}
/**
* @return the memo
*/
public String
getMemo() {
return memo;
}
/**
* @return the result
*/
public String
getResult() {
return result;
}
}
微信
package com.drawthink.pay.weixin;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;
import com.drawthink.pay.PayModule;
import com.drawthink.pay.PayResultListener;
import com.tencent.mm.opensdk.modelbase.BaseResp;
import com.tencent.mm.opensdk.modelpay.PayReq;
import com.tencent.mm.opensdk.openapi.IWXAPI;
import com.tencent.mm.opensdk.openapi.WXAPIFactory;
public class WeiXinPay implements PayModule {
private IWXAPI mWXApi;
private PayResultListener listener;
private WeiXinPayConfig weiXinPayConfig;
private BroadcastReceiver receiver;
public WeiXinPay(WeiXinPayConfig weiXinPayConfig) {
this.weiXinPayConfig = weiXinPayConfig;
mWXApi = WXAPIFactory.createWXAPI(weiXinPayConfig.getContext(),
null);
mWXApi.registerApp(weiXinPayConfig.getAppId());
onEvent();
}
@Override
public PayModule
setOnPayResultListener(PayResultListener listener) {
this.listener = listener;
return this;
}
@Override
public void payOrder() {
PayReq request =
new PayReq();
request.appId = weiXinPayConfig.getAppId();
request.partnerId = weiXinPayConfig.getPartnerId();
request.prepayId = weiXinPayConfig.getPrepayId();
request.packageValue =
weiXinPayConfig.getPackageValue() !=
null
? weiXinPayConfig.getPackageValue() :
"Sign=WXPay";
request.nonceStr = weiXinPayConfig.getNonceStr();
request.timeStamp = weiXinPayConfig.getTimeStamp();
request.sign = weiXinPayConfig.getSign();
request.signType = weiXinPayConfig.getSignType();
mWXApi.sendReq(request);
}
@Override
public void checkPayState() {
if(listener !=
null){
if(!mWXApi.isWXAppInstalled()){
Toast.makeText(weiXinPayConfig.getContext(),
"没有安装微信,无法使用微信支付!"
, Toast.LENGTH_SHORT).show();
}
if(!mWXApi.isWXAppSupportAPI()){
Toast.makeText(weiXinPayConfig.getContext(),
"当前微信版本过低,无法使用微信支付,请升级微信!"
, Toast.LENGTH_SHORT).show();
}
listener.onPayCheck(mWXApi.isWXAppInstalled() && mWXApi.isWXAppSupportAPI());
}
}
private void onEvent() {
IntentFilter filter =
new IntentFilter(
"org.unreal.pay.weiXinPayResult");
receiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int errCode = intent.getIntExtra(
"errCode", -
1);
if (errCode == BaseResp.ErrCode.ERR_OK) {
WeiXinPay.
this.listener.onPaySuccess();
}
else {
WeiXinPay.
this.listener.onPayFailure();
}
LocalBroadcastManager.getInstance(weiXinPayConfig.getContext()).unregisterReceiver(receiver);
}
};
LocalBroadcastManager.getInstance(weiXinPayConfig.getContext()).registerReceiver(receiver,filter);
}
}
yourpackagename.wxapi.WXPayEntryActivity.java
package com.drawthink.hospital.wxapi;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.provider.SyncStateContract;
import android.support.v4.content.LocalBroadcastManager;
import com.tencent.mm.opensdk.constants.ConstantsAPI;
import com.tencent.mm.opensdk.modelbase.BaseReq;
import com.tencent.mm.opensdk.modelbase.BaseResp;
import com.tencent.mm.opensdk.openapi.IWXAPI;
import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler;
import com.tencent.mm.opensdk.openapi.WXAPIFactory;
public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler {
private IWXAPI api;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
api = WXAPIFactory.createWXAPI(
this,
null);
api.handleIntent(getIntent(),
this);
}
@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) {
/**
* 微信支付成功回调会开启一个activity,并执行onResp方法,我不希望出现这个界面,所以finish了,在这之前,我发送一个广播
* 在广播中我做了回调后的操作
*
*/
if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
LocalBroadcastManager.getInstance(
this).sendBroadcast(
new Intent(
"org.unreal.pay.weiXinPayResult").putExtra(
"errCode", resp.errCode));
}
finish();
}
}
银联
import android.content.Intent;
import android.widget.Toast;
import com.unionpay.UPPayAssistEx;
import org.unreal.common.pay.CheckStateListener;
import org.unreal.common.pay.PayFunction;
import org.unreal.common.pay.PayResultListener;
public class UnionBankPay implements PayFunction {
private PayResultListener listener;
private UnionBankPayConfig config;
public UnionBankPay(PayResultListener listener) {
this.listener = listener;
}
@Override
public void payOrder() {
UPPayAssistEx.startPay(config.getContext()
,
null
,
null
, config.getTradeCode()
, config.getServerModel());
}
@Override
public void checkPayState(CheckStateListener checkStateListener) {
checkStateListener.checkState(
true);
}
public void processPayResponse(Intent data) {
if (data ==
null) {
return;
}
String msg =
"";
String str = data.getExtras().getString(
"pay_result");
if (
"success".equalsIgnoreCase(str)) {
Toast.makeText(config.getContext(),
"银联支付成功", Toast.LENGTH_SHORT).show();
if (listener !=
null) {
listener.onPaySuccess();
}
}
else if (
"fail".equalsIgnoreCase(str)) {
Toast.makeText(config.getContext(),
"银联支付失败", Toast.LENGTH_SHORT).show();
if (listener !=
null) {
listener.onPayFailure();
}
}
else if (
"cancel".equalsIgnoreCase(str)) {
if (listener !=
null) {
Toast.makeText(config.getContext(),
"银联支付失败,用户取消了支付", Toast.LENGTH_SHORT).show();
listener.onPaySuccess();
}
}
}
public UnionBankPay
setConfig(UnionBankPayConfig config) {
this.config = config;
return this;
}
}
此代码也没有进行远程验证,仅通过支付控件返回字符串判断支付结果,有风险!
检查是否安装支付渠道插件回调接口
主要用于检查插件是否安装,抹除掉同步、异步检查的差别,统一采用回调接口方式来返回检查结果
public interface CheckStateListener {
void checkState(
boolean state);
}
支付结果回调接口
主要用于支付结果的获取,支付宝提供了三个支付结果,银联、微信提供了两个支付结果。接口设计取了支付宝接口的三个状态,包含了微信、银联支付结果。
public interface PayResultListener {
void onPaySuccess();
void onPayFailure();
void onPayConfirming();
}
PayFacade 类,支付调用入口
import android.content.Intent;
import org.unreal.common.pay.impl.alipay.AliPay;
import org.unreal.common.pay.impl.alipay.AliPayConfig;
import org.unreal.common.pay.impl.unionbank.UnionBankPay;
import org.unreal.common.pay.impl.unionbank.UnionBankPayConfig;
import org.unreal.common.pay.impl.weixin.WeiXinPay;
import org.unreal.common.pay.impl.weixin.WeiXinPayConfig;
public class PayFacade {
private static UnionBankPay unionBankPay;
private static WeiXinPay weiXinPay;
private static AliPay aliPay;
boolean unionBank =
false;
public PayFacade(PayResultListener listener){
if(aliPay ==
null) {
aliPay =
new AliPay(listener);
}
if(weiXinPay ==
null) {
weiXinPay =
new WeiXinPay(listener);
}
if(unionBankPay ==
null) {
unionBankPay =
new UnionBankPay(listener);
}
}
public boolean isUnionBank() {
return unionBank;
}
public void unionProcessResult(Intent intent){
unionBankPay.processPayResponse(intent);
}
public void pay(
final AliPayConfig aliPayConfig) {
aliPay.setConfig(aliPayConfig);
pay(aliPay);
unionBank =
false;
}
public void pay(WeiXinPayConfig weiXinPayConfig){
weiXinPay.setConfig(weiXinPayConfig);
pay(weiXinPay);
unionBank =
false;
}
public void pay(UnionBankPayConfig unionBankPayConfig){
unionBankPay.setConfig(unionBankPayConfig);
pay(unionBankPay);
unionBank =
true;
}
private void pay(
final PayFunction payModule){
payModule.checkPayState(
new CheckStateListener(){
@Override
public void checkState(
boolean state) {
if(state){
payModule.payOrder();
}
}
});
}
}
因为银联的回调需要在onActivityForResult方法中执行,为了调用者不许关心处理过程,特意封装了isUnionBank方法,来进一步处理银联回调。
使用
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import org.unreal.common.application.R;
import org.unreal.common.pay.PayFacade;
import org.unreal.common.pay.PayResultListener;
import org.unreal.common.pay.impl.alipay.AliPayConfig;
import org.unreal.common.pay.impl.unionbank.UnionBankPayConfig;
import org.unreal.common.pay.impl.weixin.WeiXinPayConfig;
import butterknife.ButterKnife;
import butterknife.OnClick;
public class MainActivity extends AppCompatActivity {
private PayFacade facade;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(
this);
initPayFacade();
}
private void initPayFacade() {
facade =
new PayFacade(
new PayResultListener(){
@Override
public void onPaySuccess() {
}
@Override
public void onPayFailure() {
}
@Override
public void onPayConfirming() {
}
});
}
@OnClick({R.id.button2, R.id.button3, R.id.button4})
public void onClick(View view) {
switch (view.getId()) {
case R.id.button2:
break;
case R.id.button3:
break;
case R.id.button4:
break;
}
}
@Override
protected void onActivityResult(
int requestCode,
int resultCode, Intent data) {
if(facade.isUnionBank()){
facade.unionProcessResult(data);
}
super.onActivityResult(requestCode, resultCode, data);
}
}
代码地址
https://github.com/ChineseLincoln/Dagger2Mvp/tree/v3
v3 分之下,pay library项目