支付宝登录接入(Android/IOS(swift)/Java后
本文章仅作为个人笔记
截止发文在网上找了一圈支付宝登录的,大部分都是照搬官方的demo,没什么实际用处,官方的demo大家应该都看过了,对于Android来讲问题应该不大,后台就gg了,这里主写后台与Android对接心得。
因为实在被支付宝折腾的厉害,所以这里就不写什么注册处签约什么的流程了,心累,上些干货(代码),作为中国人自己的开放平台,文档还整理成这个样子需要自己琢磨代码的估计也就这一家了。也有可能是楼主自己语文或者检索能力不过关哦,如果看客发现有不规范或者不对的地方还望指出,这里先谢过了。
蚂蚁金服开放平台官网
支付宝Android官方接入文档
支付宝IOS官方接入文档
Android客户端demo&sdk下载地址(1.5.5)
IOS客户端demo&sdk下载地址(1.5.7)
服务器端接入官方文档
登录结果返回参数说明
支付结果返回参数说明
-
IOS端
-
引入官方库(于Podfile文件加入如下内容并运行 pod install)
pod 'AlipaySDK-iOS'
-
于头文件添加如下内容,不知道什么是头文件的建议参考支付宝IOS官方接入文档
#import <AlipaySDK/AlipaySDK.h>
-
添加 URL Schemes ,设置为aliauth 并设置值为某一唯一值,如 包名.aliauth。设置为alipay 并设置值为某一唯一值,如 包名.alipay。
-
与Localizable.strings 添加 "aliauthback" = ""//这里需要保持与上面的URL Schemes aliauth值一致。添加 "alipayback" = ""//这里需要保持与上面的URL Schemes alipay值一致。
-
创建AliPayUtils工具文件
class AliPayUtils { private static var aliAuthBack: AliPayBack? private static var aliPayBack: AliPayBack? static func login(signStr: String, aliAuthBack: AliPayBack?) { AliPayUtils.aliAuthBack = aliAuthBack AlipaySDK().auth_V2(withInfo: signStr, fromScheme: NSLocalizedString("aliauthback", comment: ""), callback: { (resp) in loginBack(resultDic: resp as! [NSObject: AnyObject]) }) } static func loginBack(resultDic: [NSObject: AnyObject]) { if let Alipayjson: [String: AnyObject] = resultDic as? [String: AnyObject] { let resultStatus = Alipayjson["resultStatus"] as! String print("loginBack resultStatus=\(resultStatus)") if resultStatus == "9000" {// 请求处理成功 aliAuthBack?.finish(Alipayjson["result"] as? String) } else { aliAuthBack?.failed() } } } static func pay(signStr: String, aliPayBack: AliPayBack?) { AliPayUtils.aliPayBack = aliPayBack AlipaySDK().payOrder(signStr, fromScheme: NSLocalizedString("alipayback", comment: ""), callback: { (resp) in payBack(resultDic: resp as! [NSObject: AnyObject]) }) } static func payBack(resultDic: [NSObject: AnyObject]) { if let Alipayjson: [String: AnyObject] = resultDic as? [String: AnyObject] { let resultStatus = Alipayjson["resultStatus"] as! String print("payBack resultStatus=\(resultStatus)") if resultStatus == "9000" || resultStatus == "8000" {// 订单支付成功或正在处理中 aliPayBack?.finish(Alipayjson["result"] as? String) } else { aliPayBack?.failed() } } } } protocol AliPayBack { func finish(_ result: String?) func failed() }
-
于AppDelegate添加回调(这里贴主要代码)
@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, handleOpen url: URL) -> Bool { switch url.scheme { case NSLocalizedString("aliauthback", comment: ""): AlipaySDK().processAuth_V2Result(url) { (back) in AliPayUtils.loginBack(resultDic: back as! [NSObject: AnyObject]) } case NSLocalizedString("alipayback", comment: ""): AlipaySDK().processOrder(withPaymentResult: url) { (back) in AliPayUtils.payBack(resultDic: back as! [NSObject: AnyObject]) } default: print("handleOpenUrl1") } print("handleOpenUrl11=\(url.scheme)") return true } func application(_ application: UIApplication, open url: URL , sourceApplication: String?, annotation: Any) -> Bool { switch url.scheme { case NSLocalizedString("aliauthback", comment: ""): AlipaySDK().processAuth_V2Result(url) { (back) in AliPayUtils.loginBack(resultDic: back as! [NSObject: AnyObject]) } case NSLocalizedString("alipayback", comment: ""): AlipaySDK().processOrder(withPaymentResult: url) { (back) in AliPayUtils.payBack(resultDic: back as! [NSObject: AnyObject]) } default: print("handleOpenUrl2") } print("handleOpenUrl12=\(url.scheme)") return true } }
-
登录调用(这里写部分伪代码):
//从服务器获取签名字符串,这里后面服务器开发会贴出,客户端可以不用管。 var signStr = getSignStrFromService() AliPayUtils.login(signStr: result!, aliAuthBack: AliAuthBack()) //创建AliPayBack实例 struct AliAuthBack: AliPayBack { func finish(_ result: String?) { //登录成功,直接将获取的结果上传至服务器处理,服务器返回用户信息即可。 } func failed() { //登录失败 } }
-
-
Android端
-
接入前奏,注册签约的可另行百度,这里主要写代码部分
-
下载jar包并导入(这里使用的是alipaySdk-20180601.jar)
-
放入项目libs文件夹下
-
与build.gradle导入jar(这里贴部分关键代码)
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation files('libs/alipaySdk-20180601.jar') }
-
于AndroidManifest.xml注册权限及activity(这里贴出部分关键代码)
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <application> <activity android:name="com.alipay.sdk.app.H5PayActivity" android:configChanges="orientation|keyboardHidden|navigation|screenSize" android:exported="false" android:screenOrientation="behind" android:windowSoftInputMode="adjustResize|stateHidden" /> <activity android:name="com.alipay.sdk.app.H5AuthActivity" android:configChanges="orientation|keyboardHidden|navigation" android:exported="false" android:screenOrientation="behind" android:windowSoftInputMode="adjustResize|stateHidden" /> </application>
-
创建AliPayUtils工具文件(AuthResult/PayResult文件会后面贴出)
import android.app.Activity; import android.text.TextUtils; import android.util.Log; import com.alipay.sdk.app.AuthTask; import com.alipay.sdk.app.EnvUtils; import com.alipay.sdk.app.PayTask; import java.util.Map; public class AliPayUtils { public interface Back { public void success(String result); public void failed(); } public static void startPay(final Activity activity, final String orderInfo, final Back back) { if (orderInfo == null || back == null) { return; } new Thread(new Runnable() { @Override public void run() { // EnvUtils.setEnv(EnvUtils.EnvEnum.SANDBOX);//测试使用此设置 EnvUtils.setEnv(EnvUtils.EnvEnum.ONLINE);//正式环境使用此设置 PayTask alipay = new PayTask(activity); Map<String, String> result = alipay.payV2(orderInfo, true); PayResult payResult = new PayResult(result); String resultStatus = payResult.getResultStatus(); if (TextUtils.equals(resultStatus, "9000")) { back.success(payResult.getResult()); } else { back.failed(); } } }).start(); } public static void login(final Activity activity, final String authInfo, final Back back) { if (authInfo == null || back == null) { return; } new Thread(new Runnable() { @Override public void run() { AuthTask authTask = new AuthTask(activity); Map<String, String> result = authTask.authV2(authInfo, true); AuthResult authResult = new AuthResult(result, true); String resultStatus = authResult.getResultStatus(); if (TextUtils.equals(resultStatus, "9000") && TextUtils.equals(authResult.getResultCode(), "200")) { back.success(authResult.getResult()); } else { back.failed(); } } }).start(); } }
-
-
创建AuthResult工具文件
import android.text.TextUtils; import java.util.Map; public class AuthResult { private String resultStatus; private String result; private String memo; private String resultCode; private String authCode; private String alipayOpenId; public AuthResult(Map<String, String> rawResult, boolean removeBrackets) { 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); } } String[] resultValue = result.split("&"); for (String value : resultValue) { if (value.startsWith("alipay_open_id")) { alipayOpenId = removeBrackets(getValue("alipay_open_id=", value), removeBrackets); continue; } if (value.startsWith("auth_code")) { authCode = removeBrackets(getValue("auth_code=", value), removeBrackets); continue; } if (value.startsWith("result_code")) { resultCode = removeBrackets(getValue("result_code=", value), removeBrackets); continue; } } } private String removeBrackets(String str, boolean remove) { if (remove) { if (!TextUtils.isEmpty(str)) { if (str.startsWith("\"")) { str = str.replaceFirst("\"", ""); } if (str.endsWith("\"")) { str = str.substring(0, str.length() - 1); } } } return str; } @Override public String toString() { return "resultStatus={" + resultStatus + "};memo={" + memo + "};result={" + result + "}"; } private String getValue(String header, String data) { return data.substring(header.length(), data.length()); } /** * @return the resultStatus */ public String getResultStatus() { return resultStatus; } /** * @return the memo */ public String getMemo() { return memo; } /** * @return the result */ public String getResult() { return result; } /** * @return the resultCode */ public String getResultCode() { return resultCode; } /** * @return the authCode */ public String getAuthCode() { return authCode; } /** * @return the alipayOpenId */ public String getAlipayOpenId() { return alipayOpenId; } }
-
创建PayResult工具文件
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; } }
-
AliPayUtils登录使用方式:
-
//于服务器获取登录签名字符串(这里为伪代码),具体代码后面服务器先关会贴出。
String signStr = getSignStr();
//调用AliPayUtils登录方法
AliPayUtils.login(DemoActivity.this, signStr, new AliPayUtils.Back() {
@Override
public void success(String result) {
//登录成功,可将result上传至服务器处理。
}
@Override
public void failed() {
//登录失败
}
});
- 服务器端
- Android客户端demo中写的“真实App里,privateKey等数据严禁放在客户端,加签过程务必要放在服务端完成;”相信大家都看到了,也正是因为这句话楼主连appId都没有放客户端,但是官方并没有给出明确的解决方案。Android客户端jar包引入low到居然要下载jar文件放入,连gradle引入都不支持,无力吐槽。
- 为了解决安全问题楼主不得不将加签所有过程放服务器端,那么问题来了:
-
客户端的签名代码用的是客户端jar包,服务器端用的服务器jar包,且服务器根本没有签名示例代码。
-
为此楼主只有强行将客户端部分代码照搬至服务器端,不管是否规范,至少签名在服务器这边处理是做到了。代码如下(代码真心多,但是为了一个类完成所有功能,只能勉强放一起了。):
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.domain.AlipayTradeAppPayModel; import com.alipay.api.domain.AlipayTradePrecreateModel; import com.alipay.api.internal.util.AlipaySignature; import com.alipay.api.request.*; import com.alipay.api.response.*; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.Security; import java.security.spec.PKCS8EncodedKeySpec; import java.util.*; public class AliPayUtils { private static final String APP_ID = "";//这个就不强调了,这个都找不到相比开发难度很大,我这文笔很难帮到你了。 private static final String PID = "";//在开放平台内点击右上角那里点击密钥管理,然后点击左边mapi网关产品密钥就能看到pid了。 private static final String APP_PRIVATE_KEY = "";//下面的这些信息在应用信息里边基本能看到,有些可能需要上传设置。 private static final String RSA2_PRIVATE = "";//rsa2私钥 private static final String ALIPAY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzG+wSFicJ1BAP+/51vY8Zn4ZVNMgWuJCTAvfUh48QjixfJwYdr0lX8aOOHVLiC4zOBMdKi0Ale/R/myl1duCnWhCz9XgxMG/x5MpuxESU0SY6HZimW wQGxoRmKsM3ICa7zmBa58nOig0cKY1ipJ6VXmTGSeiwF7TReKAGU8PeyYTZvnTgmIKofD7L8oAQF2xom3RlFbtzkjf4UaYbr+7m52dktPp6t7PwVKbbAiqDfVIoswrBaAPDmBWrf1Uaj8kt3KVzsiJzpN1xT0oRFikKj9KuMbIMI+ESpDr1674ToJa46AjI+0O8WxfQrebMuE/ xkUCG0WaQCXllLjtRXc7wIDAQAB"; private static final String CHARSET = "UTF-8"; public static boolean alipayCallBack(Map<String, String[]> requestParams) { Boolean isPay = false; // 获取支付宝POST过来反馈信息 Map<String, String> params = new HashMap<>(); for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) { String name = (String) iter.next(); String[] values = requestParams.get(name); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } params.put(name, valueStr); } try { boolean flag = AlipaySignature.rsaCheckV1(params, ALIPAY_PUBLIC_KEY, CHARSET, "RSA2"); if (flag) { String tradeStatus = params.get("trade_status"); if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) { String outTradeNo = params.get("out_trade_no");// 商户订单号 String tradeNo = params.get("trade_no");// 商户订单号 String gmtPayment = params.get("gmt_payment");// 支付时间 String gmtCreate = params.get("gmt_create");// 创建时间 } } } catch (AlipayApiException e) { e.printStackTrace(); } return false; } public static AlipayTradeQueryResponse getPayInfo(String outTradeNo, String tradeNo) { AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do" , APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2"); AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); request.setBizContent("{" + "\"out_trade_no\":\"" + outTradeNo + "\"," + "\"trade_no\":\"" + tradeNo + "\"," + "\"org_pid\":\"" + PID + "\"" + " }"); try { return alipayClient.execute(request); } catch (AlipayApiException e) { return null; } } public static String getPayStr(String totalAmount, String subject, String outTradeNo) { AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do" , APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2"); AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest(); AlipayTradeAppPayModel model = new AlipayTradeAppPayModel(); model.setSubject(subject); model.setOutTradeNo(outTradeNo); model.setTimeoutExpress("10m"); model.setTotalAmount(totalAmount); model.setProductCode("QUICK_MSECURITY_PAY"); request.setBizModel(model); request.setNotifyUrl("");//这里记得放支付回调地址 try { AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request); if (response.isSuccess()) { return response.getBody(); } else { throw new BaseException(BaseResultEnum.ALIPAY_SIGN_ERROR); } } catch (AlipayApiException e) { throw new BaseException(BaseResultEnum.ALIPAY_SIGN_ERROR); } } public static AlipayUserInfoShareResponse signAuthStr(String authCode) throws AlipayApiException { AlipayClient alipayClient = new DefaultAlipayClient( "https://openapi.alipay.com/gateway.do", APP_ID , APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY , "RSA2"); AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest(); request.setGrantType("authorization_code"); request.setCode(authCode); AlipayUserInfoShareRequest userInfoRequest = new AlipayUserInfoShareRequest(); AlipaySystemOauthTokenResponse response = alipayClient.execute(request); if (response.isSuccess()) { AlipayUserInfoShareResponse userInfoResponse = alipayClient.execute(userInfoRequest, response.getAccessToken()); if (userInfoResponse.isSuccess()) { return userInfoResponse; } System.out.println("signAuthStr调用成功"); } else { System.out.println("signAuthStr 调用失败"); } return null; } public static String getAuthStr() { Map<String, String> authInfoMap = buildAuthInfoMap(); String info = buildOrderParam(authInfoMap); String sign = getSign(authInfoMap, RSA2_PRIVATE, true); return info + "&" + sign; } private static String buildOrderParam(Map<String, String> map) { List<String> keys = new ArrayList<String>(map.keySet()); StringBuilder sb = new StringBuilder(); for (int i = 0; i < keys.size() - 1; i++) { String key = keys.get(i); String value = map.get(key); sb.append(buildKeyValue(key, value, true)); sb.append("&"); } String tailKey = keys.get(keys.size() - 1); String tailValue = map.get(tailKey); sb.append(buildKeyValue(tailKey, tailValue, true)); return sb.toString(); } private static Map<String, String> buildAuthInfoMap() { Map<String, String> keyValues = new HashMap<String, String>(); keyValues.put("app_id", APP_ID); keyValues.put("pid", PID); keyValues.put("apiname", "com.alipay.account.auth"); keyValues.put("app_name", "mc"); keyValues.put("biz_type", "openservice"); keyValues.put("product_id", "APP_FAST_LOGIN"); keyValues.put("scope", "kuaijie"); keyValues.put("target_id", String.valueOf(System.currentTimeMillis()) + new Random().nextInt(10000)); keyValues.put("auth_type", "AUTHACCOUNT"); keyValues.put("sign_type", "RSA2"); return keyValues; } private static String getSign(Map<String, String> map, String rsaKey , boolean rsa2) { List<String> keys = new ArrayList<String>(map.keySet()); Collections.sort(keys); StringBuilder authInfo = new StringBuilder(); for (int i = 0; i < keys.size() - 1; i++) { String key = keys.get(i); String value = map.get(key); authInfo.append(buildKeyValue(key, value, false)); authInfo.append("&"); } String tailKey = keys.get(keys.size() - 1); String tailValue = map.get(tailKey); authInfo.append(buildKeyValue(tailKey, tailValue, false)); String oriSign = sign(authInfo.toString(), rsaKey, rsa2); String encodedSign = ""; try { encodedSign = URLEncoder.encode(oriSign, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return "sign=" + encodedSign; } private static String buildKeyValue(String key, String value, boolean isEncode) { StringBuilder sb = new StringBuilder(); sb.append(key); sb.append("="); if (isEncode) { try { sb.append(URLEncoder.encode(value, "UTF-8")); } catch (UnsupportedEncodingException e) { sb.append(value); } } else { sb.append(value); } return sb.toString(); } private static final String ALGORITHM = "RSA"; private static final String SIGN_ALGORITHMS = "SHA1WithRSA"; private static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA"; private static final String DEFAULT_CHARSET = "UTF-8"; private static String getAlgorithms(boolean rsa2) { return rsa2 ? SIGN_SHA256RSA_ALGORITHMS : SIGN_ALGORITHMS; } private static String sign(String content, String privateKey, boolean rsa2) { try { PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec( Base64.getDecoder().decode(privateKey)); Security.addProvider( new org.bouncycastle.jce.provider.BouncyCastleProvider()); KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, "BC"); PrivateKey priKey = keyFactory.generatePrivate(priPKCS8); java.security.Signature signature = java.security.Signature .getInstance(getAlgorithms(rsa2)); signature.initSign(priKey); signature.update(content.getBytes(DEFAULT_CHARSET)); byte[] signed = signature.sign(); return Base64.getEncoder().encodeToString(signed); } catch (Exception e) { e.printStackTrace(); } return null; } }
-
贴完代码可能会有人代码报错,很正常,因为里边用到了官方没有说的代码(原始代码用的Android的jdk没问题,但是服务器端的部分jdk代码和客户端有差异,所以就报错了),解决方案:
# gradle用户在build.gardle加入: "org.bouncycastle:bcprov-jdk16:1.45", # mvn用户在pom.xml加入: <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk16</artifactId> <version>1.45</version> </dependency>
-
这样服务器端签名代码就写好(只是签名字符串),调用getAuthStr()方法就能返回了。
-
首先将获得的realResult上传至服务器。然后我这边贴下服务器端的处理代码(首先是getAlipayAuthCode()方法,传入的是realResult,返回的是authCode,让后将获得的authCode传入signAuthStr()方法就获得了用户信息了,具体的信息可以参考官方文档):
private final String ALIPAY_AUTH_CODE_START = "auth_code="; private final String ALIPAY_AUTH_CODE_END = "&"; private String getAlipayAuthCode(String response) { String result = response.substring(response.indexOf(ALIPAY_AUTH_CODE_START) + ALIPAY_AUTH_CODE_START.length(), response.length() - 1); return result.substring(0, result.indexOf(ALIPAY_AUTH_CODE_END)); } public static AlipayUserInfoShareResponse signAuthStr(String authCode) throws AlipayApiException { AlipayClient alipayClient = new DefaultAlipayClient( "https://openapi.alipay.com/gateway.do", APP_ID , APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY , "RSA2"); AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest(); request.setGrantType("authorization_code"); request.setCode(authCode); AlipayUserInfoShareRequest userInfoRequest = new AlipayUserInfoShareRequest(); AlipaySystemOauthTokenResponse response = alipayClient.execute(request); if (response.isSuccess()) { AlipayUserInfoShareResponse userInfoResponse = alipayClient.execute(userInfoRequest, response.getAccessToken()); if (userInfoResponse.isSuccess()) { return userInfoResponse; } System.out.println("signAuthStr调用成功"); } else { System.out.println("signAuthStr 调用失败"); } return null; }
-
获得了支付宝用户信息整个登录流程也基本结束了,剩下的就是自己业务逻辑的处理了,因为整个流程实在太复杂,写的可能有很多不详细的地方,还望谅解,发现不对的地方还望及时指正,有问题也可以评论指出。
-