Android应用出海之Klarna登录以及kakao登录
一. Klarna登录
文档入口 klarna开发者官网 👈
入口点进去后选择这个入口点进去 登录SDK接入 👈
然后就Get start了
由于是商用,需要注册Klarna开发者帐户,申请API Key 👈是需要申请API Key 才行的,无法注册就需要联系Klarna团队了
下面我直接晒接入操作,代码只是参考,若要真正完成还是需要跟渠道技术团队对接才行。
一. 引入依赖
implementation 'com.klarna.mobile:sdk:2.6.20'
二. 在AndroidMainfest.xml中添加如下代码
<activity
android:name="com.klarna.mobile.sdk.activity.KlarnaRedirectReceiverActivity"
android:exported="true"
tools:node="replace">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/klarna_login_scheme" />
<data android:host="devm14.shop.cn" />
<data android:host="m.shop.com" />
<data android:host="www.shop.com" />
<data android:host="de.shop.com" />
<data android:host="es.shop.com" />
<data android:host="fr.shop.com" />
<data android:host="it.shop.com" />
<data android:host="au.shop.com" />
<data android:host="stagewww.shop.com" />
<data android:host="de-m.shop.com" />
<data android:host="es-m.shop.com" />
<data android:host="fr-m.shop.com" />
<data android:host="it-m.shop.com" />
<data android:host="au-m.shop.com" />
<data android:host="stagem.shop.com" />
</intent-filter>
</activity>
注意:host就是你们的应用支持的域名。
<data android:scheme="@string/klarna_login_scheme" />
<string name="klarna_login_scheme" >klarna1111app</string>
klarna_login_scheme 是特定的,需要申请完API Key后才知道
三. AndroidMainfest.xml文件加入
Provider(与Activity同级)
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.klarna.fileprovider.share"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"
tools:replace="android:resource" />
<!-- <meta-data -->
<!-- android:name="android.support.FILE_PROVIDER_PATHS" -->
<!-- android:resource="@xml/klarna_mobile_sdk_share_file_paths" /> -->
</provider> <!-- facebook provider -->
在res/xml/file_paths中添加下面内容
<?xml version="1.0" encoding="utf-8"?>
<paths>
<root-path
name="root"
path="" />
<files-path
name="files"
path="." />
<cache-path
name="cache"
path="." />
<external-path
name="external"
path="." />
<external-files-path
name="external_file_path"
path="." />
<external-cache-path
name="external_cache_path"
path="." />
<cache-path path="klarna_mobile_sdk_shared_files/" name="klarna_mobile_sdk_shared_files" />
</paths>
对klarna起作用的是👇这个,其他内容可加可不加,按FileProvider的使用操作 按需使用
<cache-path path="klarna_mobile_sdk_shared_files/" name="klarna_mobile_sdk_shared_files" />
登录核心代码
package com.xxx.manager
import android.app.Activity
import android.text.TextUtils
import com.xxx.BuildConfig
import com.xxx.base.XXXConstant
import com.xxx.ui.event.KlarnaTokenEvent
import com.xxx.ui.mmkv.MMKVSettingHelper
import com.klarna.mobile.sdk.KlarnaMobileSDKError
import com.klarna.mobile.sdk.api.KlarnaEnvironment
import com.klarna.mobile.sdk.api.KlarnaEventHandler
import com.klarna.mobile.sdk.api.KlarnaProductEvent
import com.klarna.mobile.sdk.api.KlarnaRegion
import com.klarna.mobile.sdk.api.component.KlarnaComponent
import com.klarna.mobile.sdk.api.signin.KlarnaSignInError
import com.klarna.mobile.sdk.api.signin.KlarnaSignInEvent
import com.klarna.mobile.sdk.api.signin.KlarnaSignInSDK
import com.klarna.mobile.sdk.api.signin.model.KlarnaSignInToken
import org.greenrobot.eventbus.EventBus
class KlarnaLoginManager private constructor() {
private var sdkInstance: KlarnaSignInSDK? = null
companion object {
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
KlarnaLoginManager()
}
}
fun getInstance(): KlarnaSignInSDK? {
return sdkInstance
}
fun initAndLogin(activity: Activity, scope: String, clientId: String, region: String) {
var emuValue: KlarnaRegion = KlarnaRegion.EU
if (TextUtils.equals("na", region.lowercase())) {
emuValue = KlarnaRegion.NA
}
var returnUrl = "klarna111xxx://" + XXXConstant.XXX_HOST
var environment = KlarnaEnvironment.PRODUCTION
if (BuildConfig.DEBUG) {
returnUrl = "klarna1111xxx://www.aishop.cn"
environment = KlarnaEnvironment.PLAYGROUND
}
sdkInstance = KlarnaSignInSDK(activity, returnUrl, eventHandler, environment, emuValue)
sdkInstance?.signIn(
clientId,
scope,
MMKVSettingHelper.getInstance().countryCode,
MMKVSettingHelper.getInstance().languageCode
)
}
private val eventHandler = object : KlarnaEventHandler {
override fun onEvent(klarnaComponent: KlarnaComponent, event: KlarnaProductEvent) {
when (event.action) {
KlarnaSignInEvent.USER_TAPPED_BUTTON -> {
// User tapped the KlarnaSignInButton, auth process starting
}
KlarnaSignInEvent.USER_AUTH -> {
// User completed interactive auth, tokens will be fetched
}
KlarnaSignInEvent.USER_CANCELLED -> {
// User manually canceled sign in
}
KlarnaSignInEvent.SIGN_IN_TOKEN -> {
// User is authorized. You can read the results
// in event.params attribute by casting it to the
// KlarnaSignInToken model.
val token =
event.params[KlarnaSignInEvent.ParamKey.KlarnaSignInToken] as KlarnaSignInToken
EventBus.getDefault().post(
KlarnaTokenEvent(
token.accessToken,
token.expiresIn,
token.idToken,
token.refreshToken,
token.scope,
token.tokenType
)
)
}
}
}
override fun onError(klarnaComponent: KlarnaComponent, error: KlarnaMobileSDKError) {
// In case of any errors, check the 'error' parameter for more details,
// for example if the error is fatal or not.
val errorMessage = error.message
val isFatal = error.isFatal
when (error.name) {
KlarnaSignInError.InvalidClientID -> {
// The client ID value is invalid
}
KlarnaSignInError.InvalidScope -> {
// The scope value is invalid
// ...
}
KlarnaSignInError.InvalidMarket -> {
// The market value is invalid
// ...
}
// KlarnaSignInError.InvalidCustomTabsReturnUrl -> {
// // The AndroidManifest needs to be set up for KlarnaCustomTabActivity
// // ...
// }
KlarnaSignInError.SignInFailed -> {
// User authorization step failed
val signInError = error.params[KlarnaSignInError.ParamKey.Error]
val signInErrorDescription =
error.params[KlarnaSignInError.ParamKey.ErrorDescription]
// ...
}
KlarnaSignInError.RenderButtonFailed -> {
// Button failed to render
// ...
}
KlarnaSignInError.AlreadyInProgress -> {
// Sign in is already in progress, user tap or signIn method call is ignored
// ...
}
}
}
}
}
调用流程:
//klarna登陆
private fun clickKlarnaLoginAction() {
val loginMethodList = viewModel.getLoginMethodList()
val bean = viewModel.getCurrLoginMethodBean()
if (loginMethodList.isNotEmpty() && loginMethodList.contains(XXXConstant.KLARNA) && bean?.methodExtend != null
) {
//初始化klarna
klarnaBean = bean.methodExtend?.KLARNA
KlarnaLoginManager.instance.initAndLogin(
mActivity,
bean.methodExtend?.KLARNA?.scope.value(),
bean.methodExtend?.KLARNA?.clientId.value(),
bean.methodExtend?.KLARNA?.account.value()
)
}
}
注意:服务端给我们的👇这三个内容很重要,是Klarna登录SDK需要的参数,参考KlarnaLoginManager中的逻辑,
bean.methodExtend?.KLARNA?.scope.value(),//特殊值,可以理解成使用范围,需要在klarna开发者中心配置【不然就是跟klarna技术团队对接的】
bean.methodExtend?.KLARNA?.clientId.value(), //特殊值,需要在klarna开发者中心配置【不然就是跟klarna技术团队对接的】
bean.methodExtend?.KLARNA?.account.value()// 地区[对应region]
如KlarnaLoginManager中的逻辑,成功后会发送EventBus消息,接下来就是拿到Klarna的以下内容。
KlarnaTokenEvent(
token.accessToken,
token.expiresIn,
token.idToken,
token.refreshToken,
token.scope,
token.tokenType
)
👆代码是把accessToken,expiresIn,idToken,refreshToken,scope,tokenType 分装在我们自己的自定义类中。
/**
* 拿到Klarna的token对象json串的事件
*/
data class KlarnaTokenEvent(
var accessToken: String?,
var expiresIn: String?,
var idToken: String?,
var refreshToken: String?,
var scope: String?,
var tokenType: String?
)
接收到klarna登录成功后,将这些内容分装,再跟服务端约定一种封装算法,将封装好的内容通过登录接口发送请求给服务端进行操作,完成登录即可。
@Subscribe(threadMode = ThreadMode.MAIN)
fun loginKlarnaWithToken(event: KlarnaTokenEvent) {
if (HttpConfig.getInstance().isLogin.not()) {
val bean = KlarnaParamsBean(
event.accessToken,
event.expiresIn,
event.idToken,
event.refreshToken,
event.scope,
event.tokenType
)
viewModel.loginWithThirdMethod(
Base64.encode(Gson().toJson(bean).toByteArray()),
"klaran"
)
}
}
结束。详细可以仔细参考Klarna登录官方代码接入演示
二. kakao登录
Android Kakao 登录官方接入指南 👈是官方接入指南。
一. 引入依赖
implementation "com.kakao.sdk:v2-user:2.17.0"
二. 在AndroidMainfest.xml中添加以下代码:
<activity android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Redirect URI: "kakao${YOUR_NATIVE_APP_KEY}://oauth" -->
<data android:scheme="kakao${YOUR_NATIVE_APP_KEY}" android:host="oauth" />
</intent-filter>
</activity>
${YOUR_NATIVE_APP_KEY} 是需要去Kakao开发者去配置应用后获取到的。开发者中心 👈 从这进去后点击MyApplication后登录然后去配置。 参考 👉Kakao开发者APP配置参考文档
这个key可以被重新修改的:
如果其中一个应用密钥被泄露,所有者可以重新颁发应用密钥。确保补发是不可逆的。补发后,服务app或网站中的app key必须修改为补发后的值。
👇是APP配置说明
三. 使用API
👇这个是我写的管理类, 可直接拷贝,xxx替换下你们的包名即可
package com.xxx.manager;
import android.app.Application;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.xxx.core.XXXApplication;
import com.kakao.sdk.auth.AuthApiClient;
import com.kakao.sdk.auth.model.OAuthToken;
import com.kakao.sdk.common.KakaoSdk;
import com.kakao.sdk.common.model.AuthError;
import com.kakao.sdk.common.model.AuthErrorCause;
import com.kakao.sdk.common.model.ClientError;
import com.kakao.sdk.common.model.ClientErrorCause;
import com.kakao.sdk.common.model.KakaoSdkError;
import com.kakao.sdk.user.UserApiClient;
import com.kakao.sdk.user.model.AccessTokenInfo;
import kotlin.Unit;
import kotlin.jvm.functions.Function2;
// https://developers.kakao.com/docs/latest/en/kakaologin/android#add-modules
public class KakaoLogInManager {
private static final String TAG = "KakaoLogInManager";
private static boolean hasInitialized = false;
/**
* Kakao Native app key
*/
public static final String KA_KAO_NATIVE_APP_KEY = "xxxxxx";
/**
* Kakao Log In callback
*/
public interface KakaoLoginCallBack {
void onGetAccessToken(String accessToken);
}
/**
* Kakao init SDK
* loginIn和 retryLoginIn和 LogOut会检查是否已初始化,未初始化则会先初始化
* 默认这里不会产生并发问题
*/
public static void init(Application application) {
if (hasInitialized) return;
KakaoSdk.init(application, KA_KAO_NATIVE_APP_KEY);
hasInitialized = true;
}
/**
* Kakao Log In
*/
public static void login(Context context, KakaoLoginCallBack callback) {
Log.d(TAG, "loginWithKakao start");
init(XXXApplication.getInstance());
if (AuthApiClient.getInstance().hasToken()) {
// Check token presence
UserApiClient.getInstance().accessTokenInfo(new Function2<AccessTokenInfo, Throwable, Unit>() {
@Override
public Unit invoke(AccessTokenInfo accessTokenInfo, Throwable throwable) {
if (throwable != null) {
retryLogIn(context, callback);
showLogInError(throwable);
} else {
// Succeeded in validating token (Token is refreshed if needed)
OAuthToken oAuthToken = AuthApiClient.getInstance().getTokenManagerProvider().getManager().getToken();
Log.d(TAG, "Succeeded in validating token " + accessTokenInfo.toString());
handleLogInResult(oAuthToken, null, callback);
return null;
}
return null;
}
});
}
else {
retryLogIn(context, callback);
}
}
/**
* Kakao LogIn
*/
public static void retryLogIn(Context context, KakaoLoginCallBack callback) {
Log.d(TAG, "loginWithKakao start");
init(XXXApplication.getInstance());
if (isKakaoTalkLoginAvailable(context)) {
UserApiClient.getInstance().loginWithKakaoTalk(context, new Function2<OAuthToken, Throwable, Unit>() {
@Override
public Unit invoke(OAuthToken oAuthToken, Throwable throwable) {
return handleLogInResult(oAuthToken, throwable, callback);
}
});
} else {
UserApiClient.getInstance().loginWithKakaoAccount(context, new Function2<OAuthToken, Throwable, Unit>() {
@Override
public Unit invoke(OAuthToken oAuthToken, Throwable throwable) {
return handleLogInResult(oAuthToken, throwable, callback);
}
});
}
}
/**
* Kakao LogOut
*/
public static void kakaoLogOut() {
init(XXXApplication.getInstance());
if (AuthApiClient.getInstance().hasToken()) {
UserApiClient.getInstance().logout(throwable -> {
if (throwable != null) {
Log.d(TAG, "Kakao LogOut Fail " + throwable.getMessage());
} else {
Log.d(TAG, "Kakao LogOut Success");
}
return null;
});
}
}
/**
* check if Kakao Talk has been installed on a user's device
*/
public static boolean isKakaoTalkLoginAvailable(Context context) {
boolean isKakaoAvailable = UserApiClient.getInstance().isKakaoTalkLoginAvailable(context);
Log.d(TAG, "Kakao Talk has been installed:" + isKakaoAvailable);
return isKakaoAvailable;
}
/**
* handle Log In Result
*/
private static Unit handleLogInResult(OAuthToken oAuthToken, Throwable throwable, KakaoLoginCallBack callback) {
Log.d(TAG, "loginWithKakao end");
if (throwable != null) {
showLogInError(throwable);
} else if (oAuthToken != null) {
String accessToken = oAuthToken.getAccessToken();
if (!TextUtils.isEmpty(accessToken)) {
Log.d(TAG, "loginWithKakao accessToken:" + accessToken);
callback.onGetAccessToken(accessToken);
return null;
}
}
callback.onGetAccessToken(null);
return null;
};
/**
* show Log In error info
*/
private static void showLogInError(Throwable throwable) {
if (throwable == null) {
return;
}
if (throwable instanceof KakaoSdkError && ((KakaoSdkError) throwable).isInvalidTokenError()) {
Log.d(TAG, "invalid token error");
} else if (throwable instanceof ClientError) {
ClientError clientError = (ClientError) throwable;
if (clientError != null) {
ClientErrorCause clientErrorCause = clientError.getReason();
if (clientErrorCause == ClientErrorCause.Cancelled) {
Log.d(TAG, "cancel(back button)", clientError);
} else if (clientErrorCause == ClientErrorCause.NotSupported) {
Log.e(TAG, "not supported(item is not install)", clientError);
} else {
Log.e(TAG, "other client error", clientError);
}
}
} else if (throwable instanceof AuthError) {
AuthError authError = (AuthError) throwable;
if (authError != null) {
AuthErrorCause authErrorCause = authError.getReason();
if (authErrorCause == AuthErrorCause.AccessDenied) {
Log.d(TAG, "cancel(cancel confirm)", authError);
} else if (authErrorCause == AuthErrorCause.Misconfigured) {
Log.e(TAG, "please set android key hash", authError);
} else {
Log.e(TAG, "other auth error", authError);
}
}
} else {
Log.e(TAG, "other error(net error..)", throwable);
}
}
}
调用处非常简单,就是调用👆的KakaoLogInManager.login方法,onGetAccessToken中获取到的it: String? 就是我们的token,只要不为空就说明成功。 代码如下:
//kakao登录
private fun clickKakaoLoginAction() {
//KAKAO登陆
KakaoLogInManager.login(mActivity, object : KakaoLoginCallBack {
override fun onGetAccessToken(it: String?) {
if (!TextUtils.isEmpty(it)) {
viewModel.loginWithThirdMethod(
it.toString()
)
}
}
})
}