一. Klarna登录
文档入口 klarna开发者官网 👈
入口点进去后选择这个入口点进去 登录SDK接入 👈
然后就Get start了
由于是商用,需要注册Klarna开发者帐户,申请API Key 👈是需要申请API Key 才行的,无法注册就需要联系Klarna团队了
下面我直接晒接入操作,代码只是参考,若要真正完成还是需要跟渠道技术团队对接才行。
一. 引入依赖
implementation 'com.klarna.mobile:sdk:2.6.20'
二. 在AndroidMainfest.xml中添加如下代码
<activityandroid: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同级)
<providerandroid:name="androidx.core.content.FileProvider"android:authorities="${applicationId}.klarna.fileprovider.share"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid: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-pathname="root"path="" /><files-pathname="files"path="." /><cache-pathname="cache"path="." /><external-pathname="external"path="." /><external-files-pathname="external_file_path"path="." /><external-cache-pathname="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.managerimport 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.EventBusclass KlarnaLoginManager private constructor() {private var sdkInstance: KlarnaSignInSDK? = nullcompanion 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.EUif (TextUtils.equals("na", region.lowercase())) {emuValue = KlarnaRegion.NA}var returnUrl = "klarna111xxx://" + XXXConstant.XXX_HOSTvar environment = KlarnaEnvironment.PRODUCTIONif (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 KlarnaSignInTokenEventBus.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.messageval isFatal = error.isFatalwhen (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 failedval 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) {//初始化klarnaklarnaBean = bean.methodExtend?.KLARNAKlarnaLoginManager.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 presenceUserApiClient.getInstance().accessTokenInfo(new Function2<AccessTokenInfo, Throwable, Unit>() {@Overridepublic 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>() {@Overridepublic Unit invoke(OAuthToken oAuthToken, Throwable throwable) {return handleLogInResult(oAuthToken, throwable, callback);}});} else {UserApiClient.getInstance().loginWithKakaoAccount(context, new Function2<OAuthToken, Throwable, Unit>() {@Overridepublic 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())}}})}