Android PayPal支付的接入和SDK支付过程解析
Android PayPal支付的接入和SDK支付过程解析
根据产品需求,要接入PayPal支付,在研究了几天支付后,总结下内容给新手使用,本篇文章如果与官方文档有差异的话,一切以官方为准。转载请注明出处。
注: 本篇文章只针对新版本的SDK进行解析,解析内容只针对部分功能,其它功能请自行参考PayPal开发者文档和SDK1
目录
一、Paypal接入 1、创建应用2、应用内集成3、代码实现4、测试集成结果 二、支付流程解析 一、Paypal接入本篇涉及的代码基于Github PayPal的示例进行修改,具体代码可以参考:paypal/android-checkout-sdk
1、创建应用用注册完成的账号登录PayPal开发者平台
先注册沙盒测试应用,左侧列表,点击 “DASHBOARD” -> “My Apps & Credentials” 后,在右侧窗口点击 “Sandbox” 或者 “Live” -> “Create App” 按钮进入创建沙盒App页面(正式环境把 “Sandbox” 切换成 “Live”)。 注: 沙盒界面,会默认创建了一个"DefaultApp"的默认应用,你也可以使用该应用进行测试。
在弹出的 “Create New App” 界面填写资料,完成后点击 “Create App”:
App Name 应用名称,按需填写App Type 应用类型(Merchant:商家, Platform:平台)默认选择第一个Sandbox Business Account 沙盒企业账号。沙盒账号默认就行,测试用的企业账号,正式环境需要填写对应的企业账号,账号可以在"Account"创建,默认创建沙盒应用会生成一个个人和企业的账号,可以点击编辑查看密码。创建应用完成,点击创建完成的应用 ,根据需求填写 (SANDBOX) APP SETTINGS,
Return URL 点击蓝色文字 “Show”,填入返回的URL,可以使用 “Android link生成的链接” 或者 “App包名 + /paypalpay”注意:新版的SDK已经不需要用户创建返回连接了App feature options,根据需要勾选,这里选择全部勾选,点击"Log in with PayPal" 的蓝色文本 “Advanced options”,在下拉选项中,按需勾选功能选项,一般勾选:Personal profile -> “Full name” 和 “Emali”
Account information ->“PayPal accountId (player ID)”
Additional PayPal permissions->“Enable customers who have not yet confirmed their email with PayPal to log in to your app.”
并且填写指向App"用户协议"和"隐私政策"的网址;最后点击"Save"按钮。 2、应用内集成
清单文件声明网络权限
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> ... </manifest> 123456
在项目根目录下的build.gradle集成maven地址,注意,官方文档password后面没有加引号,要注意
allprojects { repositories { mavenCentral() // This private repository is required to resolve the Cardinal SDK transitive dependency. maven { url "https://cardinalcommerceprod.jfrog.io/artifactory/android" credentials { // Be sure to add these non-sensitive credentials in order to retrieve dependencies from // the private repository. username 'paypal_sgerritz' password 'AKCp8jQ8tAahqpT5JjZ4FRP2mW7GMoFZ674kGqHmupTesKeAY2G8NcmPKLuTxTGkKjDLRzDUQ' } } } }
1234567891011121314151617在app目录下的build.gradle添加java8支持和集成PayPal SDK
android { ... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = "1.8" } } dependencies { implementation('com.paypal.checkout:android-sdk:0.8.7') } 123456789101112131415
在App的继承Application的子类下初始化SDK
class YourApplication : Application() { override fun onCreate() { super.onCreate() val config = CheckoutConfig( application = this, //注册的CilientId clientId = YOUR_CLIENT_ID, //测试环境选择沙盒模式,发版正式环境选择LIVE environment = Environment.SANDBOX, //此次和填写的RETURN URL一致,备注:新版本的SDK不需要 returnUrl = String.format("%s://paypalpay", BuildConfig.APPLICATION_ID), //支付的货币类型 currencyCode = CurrencyCode.USD, //支付动作:立即支付 userAction = UserAction.PAY_NOW, //输出日志 settingsConfig = SettingsConfig( loggingEnabled = true ) ) PayPalCheckout.setConfig(config) } }
12345678910111213141516171819202122232425根据需求在需要用到支付的地方集成PlayPalButton,并根据业务调整逻辑。如果不想要paypal的自带的button也可以,区别在于调用有所差异
... <com.paypal.checkout.paymentbutton.PayPalButton android:id="@+id/btn_main_pay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="100dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> ... 123456789101112 3、代码实现
class MainActivity : AppCompatActivity() { private lateinit var root: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) root = ActivityMainBinding.inflate(layoutInflater) setContentView(root.root) /***/ root.btnMainPay.setup( /*本地创建订单*/ createOrder = CreateOrder { createOrderActions -> val order = Order( intent = OrderIntent.CAPTURE, appContext = AppContext(userAction = UserAction.PAY_NOW), purchaseUnitList = listOf( PurchaseUnit( amount = Amount(currencyCode = CurrencyCode.USD, value = "10.00") ) ) ) //客户端集成 createOrderActions.create(order) //服务器集成 //createOrderActions.set(order.id) }, onApprove = OnApprove { approval -> //客户端集成需要重写这个回调,里面封装了对订单的捕获,不重写,付款不会被扣除 //服务器端集成不能重写,在这里调用后台接口提供的订单捕获接口去捕获 approval.orderActions.capture { captureOrderResult -> Log.i("CaptureOrder", "CaptureOrderResult: $captureOrderResult") } }, onCancel = OnCancel { Log.d("OnCancel", "Buyer canceled the PayPal experience.") }, onError = OnError { errorInfo -> Log.d("OnError", "Error: $errorInfo") } ) } }
12345678910111213141516171819202122232425262728293031323334353637383940414243444546 客户端集成/** * 客户端集成,使用paypal自带的PaymentButton */ private fun setupPaymentButton() { paymentButton.setup( createOrder = CreateOrder { createOrderActions -> Log.v(tag, "CreateOrder") createOrderActions.create( Order.Builder() .appContext( AppContext( userAction = UserAction.PAY_NOW ) ) .intent(OrderIntent.CAPTURE) .purchaseUnitList( listOf( PurchaseUnit.Builder() .amount( Amount.Builder() .value("0.01") .currencyCode(CurrencyCode.USD) .build() ) .build() ) ) .build() .also { Log.d(tag, "Order: $it") } ) }, onApprove = OnApprove { approval -> Log.v(tag, "OnApprove") Log.d(tag, "Approval details: $approval") approval.orderActions.capture { captureOrderResult -> Log.v(tag, "Capture Order") Log.d(tag, "Capture order result: $captureOrderResult") } }, onCancel = OnCancel { Log.v(tag, "OnCancel") Log.d(tag, "Buyer cancelled the checkout experience.") }, onError = OnError { errorInfo -> Log.v(tag, "OnError") Log.d(tag, "Error details: $errorInfo") } ) } /** * 客户端集成,不适用paypament自带的button */ private fun setupWithCustom() { PayPalCheckout.registerCallbacks( onApprove = OnApprove { approval -> Log.i(tag, "OnApprove: $approval") when (selectedOrderIntent) { OrderIntent.AUTHORIZE -> approval.orderActions.authorize { result -> val message = when (result) { is AuthorizeOrderResult.Success -> { Log.i(tag, "Success: $result") " Order Authorization Succeeded " } is AuthorizeOrderResult.Error -> { Log.i(tag, "Error: $result") " Order Authorization Failed " } } showSnackbar(message) } OrderIntent.CAPTURE -> approval.orderActions.capture { result -> val message = when (result) { is CaptureOrderResult.Success -> { Log.i(tag, "Success: $result") " Order Capture Succeeded " } is CaptureOrderResult.Error -> { Log.i(tag, "Error: $result") " Order Capture Failed " } } showSnackbar(message) } } }, onCancel = OnCancel { Log.d(tag, "OnCancel") showSnackbar(" Buyer Cancelled Checkout ") }, onError = OnError { errorInfo -> Log.d(tag, "ErrorInfo: $errorInfo") showSnackbar(" An Error Occurred ") } ) PayPalCheckout.startCheckout( createOrder = CreateOrder { actions -> actions.create( Order.Builder() .appContext( AppContext( userAction = UserAction.PAY_NOW ) ) .intent(OrderIntent.CAPTURE) .purchaseUnitList( listOf( PurchaseUnit.Builder() .amount( Amount.Builder() .value("0.01") .currencyCode(CurrencyCode.USD) .build() ) .build() ) ) .build() .also { Log.d(tag, "Order: $it") } ) } ) }
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 服务器端集成/** * [orderId]从后台获取到的订单id */ private setupWith(orderId: String) { //使用paypament自带的按钮 paymentButton.setup( createOrder = CreateOrder { createOrderActions -> createOrderActions.set(orderId) }, onApprove = OnApprove { approval -> // Optional -- retrieve order details first yourAppsCheckoutRepository.getEC(approval.getData().getOrderId()); // Send the order ID to your own endpoint to capture or authorize the order yourAppsCheckoutRepository.captureOrder(approval.getData().getOrderId()); } }, ... } //不使用paypament自带的按钮 PayPalCheckout.registerCallbacks( onApprove = OnApprove { approval -> //注意,这里调用的是后台提供的captureOrder接口 // Optional -- retrieve order details first yourAppsCheckoutRepository.getEC(approval.getData().getOrderId()); // Send the order ID to your own endpoint to capture or authorize the order yourAppsCheckoutRepository.captureOrder(approval.getData().getOrderId()); } }, ... ) PayPalCheckout.startCheckout( createOrder = CreateOrder { actions -> actions.set(orderID) } ) }
12345678910111213141516171819202122232425262728293031323334353637383940414243 4、测试是否集成结果从"Account"中拿到创建成功的账号和密码,使用该测试账号测试付款是否成功。
二、支付流程解析先从客户端集成说起,当开发者调用了createOrderActions.create(order)这个方法时,实际最终调用的是SDK里面CreateOrderActions.createOrder(order: Order, onOrderCreated: OnOrderCreated?)方法,该方法向调用paypal的REST API接口的创建订单接口去创建订单,然后当用户点击支付之后,本地通过接口请求,捕获/授权订单并且更新订单状态
服务器端集成则是将创建订单这一步骤的方法放到服务器端,然后再通过网络请求把OrderId拉到本地,再调用createOrderActions.set(orderId),然后当用户点击支付之后,通过接口回调,由app开发者调用后台的接口实现捕获/授权并且更新订单状态
核心的流程,paypal内部代码如下,具体过程已经标上注释了:
PayPalCheckoutPayPalCheckout,PayPal提供SDK工具类,通过paypamentButton来开启支付的,实际也是封装了这个类的调用
//注册回调 @JvmStatic @JvmOverloads fun registerCallbacks( onApprove: OnApprove?, onShippingChange: OnShippingChange? = null, onCancel: OnCancel?, onError: OnError? ) { DebugConfigManager.getInstance().apply { this.onApprove = onApprove this.onShippingChange = onShippingChange this.onCancel = onCancel this.setOnError(onError) } } //开始支付 @JvmStatic @RequiresApi(Build.VERSION_CODES.M) fun startCheckout(createOrder: CreateOrder) { if (!isConfigSet) { throw IllegalStateException("CheckoutConfig needs to be set before start() is called!") } //初始化一些参数,自行参看,里面最重要的是会把存储的LastToken重置为null DebugConfigManager.getInstance().apply { resetFieldsOnPaysheetLaunch() } handleLaunchOrder(createOrder, "startCheckout()") } private fun handleLaunchOrder(createOrder: CreateOrder, startFunction: String) { val createOrderActions = SdkComponent.getInstance().createOrderActions createOrderActions.internalOnOrderCreated = { orderCreateResult: OrderCreateResult? -> if (orderCreateResult is OrderCreateResult.Success) { // we must fetch this asap in case someone cancels checkout pre-auth. SdkComponent.getInstance().repository.fetchCancelURL() //跳转到PYPLInitiateCheckoutActivity,开始弹窗引导用户去 startInitiateCheckoutActivity(startFunction) } else if (orderCreateResult is OrderCreateResult.Error) { onOrderApiFailed(orderCreateResult.exception) } } /* 此处获取到,通过回调,将外部传入的参数封装待用 就是外部我们用来create(order)或者set(orderID)那个回调对象 createOrder = CreateOrder { createOrderActions -> createOrderActions.set(orderId) } */ createOrder.create(createOrderActions) }
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859 CreateOrderActionsCreateOrderActions,可以理解为CreateOrderAction的再次封装,封装了createOrderActions.create(order)和createOrderActions.set(orderId)的操作
//CreateOrderActions类的创建订单方法 private fun createOrder(order: Order, onOrderCreated: OnOrderCreated?) { /*开启一个协程,通过SDK封装的网络请求框架, 调用paypal服务器的创建订单接口,成功返回订单id,失败抛出错误 */ CoroutineScope(coroutineContext).launch { val orderId = try { /*调用CreateAction类封装的网络请求,去创建订单,返回订单Id*/ createOrderAction.execute(order) } catch (exception: Exception) { internalOnOrderCreated( OrderCreateResult.Error( PYPLException("exception when creating order: ${exception.message}") ) ) PLog.transition( transitionName = PEnums.TransitionName.CREATE_ORDER_EXECUTED, outcome = PEnums.Outcome.FAILED ) null } //如果订单Id不为空,封装到OrderCreateResult.Success方法,通过接口回调 orderId?.let { nonNullOrderId -> // Need to set BA eligibility here as well (Ask product) onOrderCreated?.onCreated(nonNullOrderId) internalOnOrderCreated(OrderCreateResult.Success(nonNullOrderId)) PLog.transition( transitionName = PEnums.TransitionName.CREATE_ORDER_EXECUTED, outcome = PEnums.Outcome.SUCCESS ) } } } /** * 服务器集成时候,调用这个,对比上面的create,少了请求创建订单这些步骤 * 对应的步骤都在服务器进行了操作,所以这里就只拿到OrderId,然后判断是否 * 符合格式,不符合调用接口进行转换后,步骤和create方法没多大差别 * Sets the orderId for checkout. * Supports Billing Agreement Id or EC Token * * @param orderId - id of the order for checkout */ fun set(orderId: String) { CoroutineScope(coroutineContext).launch { val updatedOrderId = attemptBATokenConversion(orderId) DebugConfigManager.getInstance().checkoutToken = updatedOrderId internalOnOrderCreated(OrderCreateResult.Success(updatedOrderId)) } } //将orderId进行转换,如果开头是BA的订单id将会调用封装好的网络请求进行转换 private suspend fun attemptBATokenConversion(updatedOrderId: String): String { // if BA token here, convert to EC token first val orderId = if (updatedOrderId.startsWith("BA", true)) { baTokenToEcTokenAction.execute(updatedOrderId) } else { updatedOrderId } return orderId }
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162 CreateOderActionCreateOderAction,创建订单操作,当使用客户端集成并且调用了createOrderActions.create(order)时,触发的操作,封装了创建订单的网络请求,以及请求成功后的解析
注意:与CreateOderActions是两个单独的类
//CreateOderAction,网络请求 suspend fun execute(order: Order): String { return withContext(ioDispatcher) { //获取存储库里是否有token val existingToken = repository.getLsatToken() if (existingToken == null) { val tokenFromAction: String try { //没有token,先请求token tokenFromAction = createLsatTokenAction.execute() //存储 repository.setLsatToken(tokenFromAction) //再去创建订单 createOrderWithLsat(order, tokenFromAction) } catch (ex: CreateLsatTokenException) { logError("Attempt to create LSAT token failed.") throw ex } } else { //有token,直接创建订单 createOrderWithLsat(order, existingToken) } } } private fun createOrderWithLsat(order: Order, lsatToken: String): String { //通过CreateOrderRequestFactory类封装的网络请求,调用REST API创建订单 val request = createOrderRequestFactory.create(order, lsatToken) val response = okHttpClient.newCall(request).execute() if (response.isSuccessful) { val createOrderResponse = try { gson.fromJson( StringReader(response.body?.string()), CreateOrderResponse::class.java ) } catch (exception: JsonIOException) { logSerializationException(exception) throw exception } saveResponseValues(createOrderResponse) return createOrderResponse.id } else { val createOrderErrorResponse = try { gson.fromJson( StringReader(response.body?.string()), CreateOrderErrorResponse::class.java ) } catch (exception: JsonIOException) { logSerializationException(exception) throw exception } var exceptionMessage = "exception when creating order: ${response.code}." for (ordersErrorDetails in createOrderErrorResponse.details) { exceptionMessage += "nError description: ${ordersErrorDetails.description}." + "nField: ${ordersErrorDetails.field}" } val exception = PYPLException(exceptionMessage) PLog.eR(TAG, "exception when creating order ${exception.message}", exception) throw exception } } /*将创建完成订单后返回的对象存储到DebugConfigManager里面和OrderContext里面, 注意:使用服务器集成的时候,就没有这几步,所以在OnApproval回调不要去调用 approval.orderActions.authorize/capture这两个,要不然会报错( Tried to retrieve OrderContext before it was created.),因为这两个接口 里面其实封装了一层网络请求,里面会用到OrderContext.get()这个对象,如果不是create 而是set的,不会执行OrderContext.create()这个方法,这个方法只有两个地方执行,一个是这里 一个是在PYPLInitiateCheckoutActivity.restoreCreateOrderContext()方法 */ private fun saveResponseValues(response: CreateOrderResponse) { val orderId = response.id DebugConfigManager.getInstance().checkoutToken = orderId // get the order capture url var orderCaptureUrl = response.links.find { it.rel == "capture" }?.href DebugConfigManager.getInstance().orderCaptureUrl = orderCaptureUrl var orderAuthorizeUrl = response.links.find { it.rel == "authorize" }?.href DebugConfigManager.getInstance().orderAuthorizeUrl = orderAuthorizeUrl val orderPatchUrl = response.links.find { it.rel == "update" }?.href val checkoutEnvironment = DebugConfigManager.getInstance().checkoutEnvironment // TODO: Remove this hard coded working Capture & Authorize URL. Figure out why MsMaster is giving us broken urls in response if (checkoutEnvironment.environment == RunTimeEnvironment.STAGE.toString()) { orderCaptureUrl = orderCaptureUrl?.let { "${checkoutEnvironment.restUrl}/v2/checkout/orders/$orderId/capture" } orderAuthorizeUrl = orderAuthorizeUrl?.let { "${checkoutEnvironment.restUrl}/v2/checkout/orders/$orderId/authorize" } } // Create OrderContext for future capture or authorize call. OrderContext.create(orderId, orderCaptureUrl, orderAuthorizeUrl, orderPatchUrl) }
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 CreateOrderRequestFactoryCreateOrderRequestFactory,订单创建工厂类,本质就是调用REST API的订单创建接口,使用服务器集成的时候,这个步骤是服务器处理,然后将订单id传递给app
/** * This factory creates [Request]s for the create order API call. */ class CreateOrderRequestFactory @Inject constructor( private val requestBuilder: Request.Builder, private val gson: Gson ) { /** * @return a [Request] for the create order API call. * * @param order - [Order] to create * @param accessToken - access token for authentication */ internal fun create(order: Order, accessToken: String): Request { return requestBuilder.apply { setOrdersUrl()/*此处调用就是创建订单的接口,/v2/checkout/orders*/ addRestHeaders(accessToken) addPostBody(gson.toJson(order)) }.build() } }
12345678910111213141516171819202122 CreateLsatTokenAction类CreateLsatTokenAction,封装了获取LSTAT token的操作,当创建订单的时候,没有缓存token就会触发该动作
//CreateLsatTokenAction类,请求auth/token接口,返回token suspend fun execute(): String { val response = getResponse() val responseString = try { response.body!!.use { responseBody -> try { responseBody.string() } catch (ex: IOException) { throw CreateLsatTokenException(clientId, ex).also { exception -> logError(ERROR_RESPONSE_BODY_TO_STRING_FAILED, exception) } } } } catch (ex: NullPointerException) { throw CreateLsatTokenException(clientId, ex).also { exception -> logError(ERROR_RESPONSE_BODY_NULL, exception) } } return try { val responseJSON = JSONObject(responseString) responseJSON.getString("access_token") } catch (ex: JSONException) { throw CreateLsatTokenException(clientId, ex).also { exception -> logError(ERROR_ACCESS_TOKEN_MISSING, exception) } } } //CreateLsatTokenAction类 private suspend fun getResponse(retryAttempts: Int = 0): Response { //调用LsatTokenRequestFactory封装的网络请求,传入clientId(此处这个clientId就是订单Id) val lsatRequest = lsatTokenRequestFactory.create(clientId) return try { withContext(ioDispatcher) { okHttpClient.newCall(lsatRequest).execute() } } catch (ex: IOException) { //请求失败重试三次,三次不成功抛出失败回调 if (retryAttempts < 3) { val delayAmount = (150L * (retryAttempts + 1)) delay(delayAmount) getResponse(retryAttempts + 1) } else { throw CreateLsatTokenException(clientId, ex).also { exception -> logError(ERROR_UNABLE_TO_CREATE_ACCESS_TOKEN, exception) } } } }
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950 LsatTokenRequestFactoryLsatTokenRequestFactory,LSAT token请求网络工厂;此处不考虑其它,用途就是为了获取到订单创建需要传递的参数
//LsatTokenRequestFactory,获取token的工厂类,[DebugConfigManager]是一个单例,里面存储着网络请求等所需的一些参数 class LsatTokenRequestFactory @Inject constructor( debugConfigManager: DebugConfigManager ) { private val checkoutEnvironment: CheckoutEnvironment = debugConfigManager.checkoutEnvironment private val requestUrl = "${checkoutEnvironment.restUrl}/v1/oauth2/token" fun create(clientId: String): Request { val body = RequestBody.create(null, "grant_type=client_credentials") return Request.Builder() .url(requestUrl) .addBasicRestHeaders(clientId) .post(body) .build() } }
1234567891011121314151617 用户确认付款在经过以上一系列处理,当用户点击“完成购物”的时候;这时候经过一系列复杂的流程,将结果返回到注册监听器中,不考虑其它失败的回调;当回调成功后,如果是客户端集成的,根据你传入的订单的OrderIntent是OrderIntent.AUTHORIZE或OrderIntent.CAPTURE进行接口回调,一定要注册对应approval.orderActions.authorize/capture{}回调;因为这两个回调本质上封装了两个不同网络请求,都是对订单进行授权或者捕获,最终paypal才会从用户的账户中扣钱,如果你没有实现,就会出现,点击完成购物,然后没有扣钱的情况。
onApprove = OnApprove { approval -> Log.i(tag, "OnApprove: $approval") when (selectedOrderIntent) { //授权订单,实际里面会调用一个网络请求 OrderIntent.AUTHORIZE -> approval.orderActions.authorize { result -> val message = when (result) { is AuthorizeOrderResult.Success -> { Log.i(tag, "Success: $result") " Order Authorization Succeeded " } is AuthorizeOrderResult.Error -> { Log.i(tag, "Error: $result") " Order Authorization Failed " } } showSnackbar(message) } OrderIntent.CAPTURE -> approval.orderActions.capture { result -> val message = when (result) { is CaptureOrderResult.Success -> { Log.i(tag, "Success: $result") " Order Capture Succeeded " } is CaptureOrderResult.Error -> { Log.i(tag, "Error: $result") " Order Capture Failed " } } showSnackbar(message) } } },
12345678910111213141516171819202122232425262728293031323334 AuthorizeOrderActionAuthorizeOrderAction,授权订单动作,用户点击完成购物后,如果开发者实现了 approval.orderActions.authorize {}接口回调,就会触发这个动作,对订单状态进行更新,从Create->Complete,同时paypal将用户的付款冻结,延迟几天再支付给商户,具体参考API文档说明,没有特殊要求推荐使用OrderIntent.CAPTURE
class AuthorizeOrderAction @Inject constructor( private val updateOrderStatusAction: UpdateOrderStatusAction, @Named(DEFAULT_DISPATCHER_QUALIFIER) private val defaultDispatcher: CoroutineDispatcher ) { suspend fun execute(): AuthorizeOrderResult { return withContext(defaultDispatcher) { try { //本质就是通过这个类,然后调用UpdateOrderStatusAction,封装的网络请求,调用REST API的授权订单接口,更新状态 when (val response = updateOrderStatusAction.execute()) { is UpdateOrderStatusResult.Success -> { AuthorizeOrderResult.Success(response.orderResponse) } is UpdateOrderStatusResult.Error -> response.mapError() } } catch (e: Throwable) { AuthorizeOrderResult.Error( reason = "$ERROR_REASON_AUTHORIZE_FAILED ${e.message}" ) } } } private fun UpdateOrderStatusResult.Error.mapError(): AuthorizeOrderResult.Error { return when (this) { UpdateOrderStatusResult.Error.LsatTokenUpgradeError -> { AuthorizeOrderResult.Error(reason = ERROR_REASON_LSAT_UPGRADE_FAILED) } is UpdateOrderStatusResult.Error.UpdateOrderStatusError -> { AuthorizeOrderResult.Error( reason = "$ERROR_REASON_AUTHORIZE_FAILED Response status code: $responseCode" ) } UpdateOrderStatusResult.Error.InvalidUpdateOrderRequest -> { AuthorizeOrderResult.Error(reason = ERROR_REASON_NO_AUTHORIZE_URL) } }.also { error -> PLog.error( errorType = PEnums.ErrorType.WARNING, code = PEnums.EventCode.E570, message = error.message, details = error.reason, transitionName = PEnums.TransitionName.ORDER_CAPTURE_EXECUTED ) } } } /** * AuthorizeOrderResult communicates whether Authorize Order succeeded or failed. */ sealed class AuthorizeOrderResult { /** * Success means that the order was authorized successfully (the request completed with a 2xx * status code). * * @param orderResponse contains details about the order after it was authorized. In extremely * rare circumstances this value may be null. */ data class Success( val orderResponse: OrderResponse? ) : AuthorizeOrderResult() /** * Error means that the order was not authorized successfully (the request completed with a * non-2xx status code). */ data class Error( val message: String = ERROR_MESSAGE_AUTHORIZE_ORDER, val reason: String ) : AuthorizeOrderResult() { companion object { const val ERROR_MESSAGE_AUTHORIZE_ORDER = "Authorize order failed." const val ERROR_REASON_NO_AUTHORIZE_URL = "Authorize was invoked when the order did not have a" + " valid authorize url. This typically happens when authorize is called for a capture" + " order or if authorize was invoked prior to the order being approved." const val ERROR_REASON_LSAT_UPGRADE_FAILED = "LSAT upgrade failed while authorizing order." const val ERROR_REASON_AUTHORIZE_FAILED = "Authorize order response was not successful." } } }
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 CaptureOrderActionCaptureOrderAction,捕获订单动作,用户点击完成购物后,如果开发者实现了 approval.orderActions.capture {}接口回调,就会触发这个动作,对订单状态进行更新,从Create->Complete,同时paypal将用户的付款划到商家的账户中
class CaptureOrderAction @Inject constructor( private val updateOrderStatusAction: UpdateOrderStatusAction, @Named(DEFAULT_DISPATCHER_QUALIFIER) private val defaultDispatcher: CoroutineDispatcher ) { suspend fun execute(): CaptureOrderResult { return withContext(defaultDispatcher) { try { when (val response = updateOrderStatusAction.execute()) { is UpdateOrderStatusResult.Success -> { CaptureOrderResult.Success(response.orderResponse) } is UpdateOrderStatusResult.Error -> response.mapError() } } catch (e: Throwable) { CaptureOrderResult.Error( reason = "$ERROR_REASON_CAPTURE_FAILED ${e.message}" ) } } } private fun UpdateOrderStatusResult.Error.mapError(): CaptureOrderResult.Error { return when (this) { UpdateOrderStatusResult.Error.LsatTokenUpgradeError -> { CaptureOrderResult.Error(reason = ERROR_REASON_LSAT_UPGRADE_FAILED) } UpdateOrderStatusResult.Error.InvalidUpdateOrderRequest -> { CaptureOrderResult.Error(reason = ERROR_REASON_NO_CAPTURE_URL) } is UpdateOrderStatusResult.Error.UpdateOrderStatusError -> { CaptureOrderResult.Error( reason = "$ERROR_REASON_CAPTURE_FAILED Response status code: $responseCode" ) } }.also { error -> PLog.error( errorType = PEnums.ErrorType.WARNING, code = PEnums.EventCode.E570, message = error.message, details = error.reason, transitionName = PEnums.TransitionName.ORDER_CAPTURE_EXECUTED ) } } } /** * CaptureOrderResult communicates whether Capture Order succeeded or failed. */ sealed class CaptureOrderResult { /** * Success means that the order was captured successfully (the request completed with a 2xx * status code). * * @param orderResponse contains details about the order after it was captured. In extremely * rare circumstances this value may be null. */ data class Success( val orderResponse: OrderResponse? ) : CaptureOrderResult() /** * Error means that the order was not captured successfully (the request completed with a * non-2xx status code). */ data class Error( val message: String = ERROR_MESSAGE_CAPTURE_ORDER, val reason: String ) : CaptureOrderResult() { companion object { const val ERROR_MESSAGE_CAPTURE_ORDER = "Capture order failed." const val ERROR_REASON_NO_CAPTURE_URL = "Capture was invoked when the order did not have a" + " valid capture url. This typically happens when capture is called for an authorize" + " order or if capture was invoked prior to the order being approved." const val ERROR_REASON_LSAT_UPGRADE_FAILED = "LSAT upgrade failed while capturing order." const val ERROR_REASON_CAPTURE_FAILED = "Capture order response was not successful." } } }
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 UpdateOrderStatusActionUpdateOrderStatusAction类,顾名思义,就是更新订单状态用的,无论是授权还是捕获,归根都是调用这个类,然后对订单的状态进行更新
class UpdateOrderStatusAction @Inject constructor( private val updateOrderStatusRequestFactory: UpdateOrderStatusRequestFactory, private val upgradeLsatTokenAction: UpgradeLsatTokenAction, private val debugConfigManager: DebugConfigManager, private val okHttpClient: OkHttpClient, private val gson: Gson, @Named(IO_DISPATCHER_QUALIFIER) private val ioDispatcher: CoroutineDispatcher, @Named(DEFAULT_DISPATCHER_QUALIFIER) private val defaultDispatcher: CoroutineDispatcher ) { private val TAG = UpdateOrderStatusAction::class.java.simpleName suspend fun execute(): UpdateOrderStatusResult { val orderContext = withContext(defaultDispatcher) { /* 到这里,你就明白客户端集成,和服务器端集成的区别在哪了,客户端集成,订单在本地创建,该有的参数都有, 相比,服务器端集成只有一个订单id,所以如果实现了onApproval回调,不需要监听授权还是捕获成功,因为这 两个需要自己实现 */ OrderContext.get().also { context -> debugConfigManager.checkoutToken = context.orderId OrderContext.clear() } } return when (val upgradeLsatTokenResponse = upgradeLsatTokenAction.execute()) { is UpgradeLsatTokenResponse.Success -> { try { val request = updateOrderStatusRequestFactory .create(orderContext, upgradeLsatTokenResponse.upgradedAccessToken) updateOrderStatus(request) } catch (ex: NoValidUpdateOrderStatusUrlFound) { UpdateOrderStatusResult.Error.InvalidUpdateOrderRequest } } UpgradeLsatTokenResponse.Failed -> UpdateOrderStatusResult.Error.LsatTokenUpgradeError } } private suspend fun updateOrderStatus(request: Request): UpdateOrderStatusResult { return withContext(ioDispatcher) { try { val response = okHttpClient.newCall(request).execute() if (response.isSuccessful) { val orderResponse = response.body?.use { responseBody -> val responseString = responseBody.string() gson.fromJson(responseString, OrderResponse::class.java) } UpdateOrderStatusResult.Success(orderResponse) } else { UpdateOrderStatusResult.Error.UpdateOrderStatusError(response.code) } } catch (e: Exception) { PLog.e(TAG, e.toString(), e) UpdateOrderStatusResult.Error.UpdateOrderStatusError(RESPONSE_CODE_EXCEPTION) } } } } sealed class UpdateOrderStatusResult { data class Success(val orderResponse: OrderResponse?) : UpdateOrderStatusResult() sealed class Error : UpdateOrderStatusResult() { object LsatTokenUpgradeError : Error() object InvalidUpdateOrderRequest : Error() data class UpdateOrderStatusError(val responseCode: Int) : Error() } }
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071 UpdateOrderStatusRequestFactoryUpdateOrderStatusRequestFactory,更新订单状态的网络请求工厂,根据创建订单配置的OrderIntent是哪个,调REST API
的哪个接口
class UpdateOrderStatusRequestFactory @Inject constructor() { private val TAG = javaClass.simpleName //网络请求,根据当前订单的这个字段:"intent": "CAPTURE",判断是要调用哪个接口 fun create(orderContext: OrderContext, merchantAccessToken: String): Request { val url = when (orderContext.orderIntent) { OrderIntent.CAPTURE -> orderContext.captureUrl!! OrderIntent.AUTHORIZE -> orderContext.authorizeUrl!! null -> throw NoValidUpdateOrderStatusUrlFound(orderContext) } PLog.d(TAG, "Creating update order status request with url: $url") val body = RequestBody.create(null, "") return Request.Builder() .addMerchantRestHeaders(merchantAccessToken) .url(url) .post(body) .build() } } class NoValidUpdateOrderStatusUrlFound( orderContext: OrderContext ) : RuntimeException( "Unable to create a valid UpdateOrderStatusRequest as no valid URL was found: $orderContext" )
123456789101112131415161718192021222324252627补充
如果服务器集成,后台不想捕获订单,Android这边可以取巧来实现这个功能(IOS不行,IOS如果要捕获,要自行请求PayPal REST API),附上代码:
PayPalCheckout.registerCallbacks( onApprove = OnApprove { approval -> //和客户端一样正常注册回调,但是在注册回调的时候,要封装一层获取订单详情的回调,通过这层回调会拿到LAST toeken并且保存起来,要不然会报LSAT错误,然后再判断返回的订单状态是否是用户批准付款了 approval.orderActions.getOrderDetails { when (it) { is GetOrderResult.Success -> {//如果订单的状态不是用户允许的状态,就不让做其它操作 if (it.orderResponse.status != OrderStatus.APPROVED){ return@getOrderDetails } when (selectedOrderIntent) { OrderIntent.AUTHORIZE -> approval.orderActions.authorize { result -> val message = when (result) { is AuthorizeOrderResult.Success -> { Log.e(tag, "Success: $result") " Order Authorization Succeeded " } is AuthorizeOrderResult.Error -> { Log.e(tag, "Error: $result") " Order Authorization Failed " } } } OrderIntent.CAPTURE -> approval.orderActions.capture { result -> val message = when (result) { is CaptureOrderResult.Success -> { Log.e(tag, "Success: $result") " Order Capture Succeeded " } is CaptureOrderResult.Error -> { Log.e(tag, "Error: $result") " Order Capture Failed " } } } } } else -> {} } } }, onCancel = OnCancel { Log.e(tag, "OnCancel") }, onError = OnError { errorInfo -> Log.e(tag, "ErrorInfo: $errorInfo") } ) } PayPalCheckout.startCheckout( createOrder = CreateOrder { createOrderActions -> //协程,模拟服务器生成订单 uiScope.launch { //模拟服务器生成订单 val orderId = createOrder()?.id orderId?.let { createOrderActions.set(orderId) //参考客户端集成,OrderAction.saveResponseValues()方法 val checkoutEnvironment = DebugConfigManager.getInstance().checkoutEnvironment val orderCaptureUrl = "${checkoutEnvironment.restUrl}/v2/checkout/orders/$orderId/capture" val orderAuthorizeUrl = "${checkoutEnvironment.restUrl}/v2/checkout/orders/$orderId/authorize" val orderPatchUrl = "${checkoutEnvironment.restUrl}/v2/checkout/orders/$orderId" DebugConfigManager.getInstance().orderCaptureUrl = orderCaptureUrl DebugConfigManager.getInstance().orderAuthorizeUrl = orderAuthorizeUrl /* 注意,OrderContext传入的url二选一,不能都填入,因为: val orderIntent: OrderIntent? = if (captureUrl != null && authorizeUrl == null) { OrderIntent.CAPTURE } else if (authorizeUrl != null && captureUrl == null) { OrderIntent.AUTHORIZE } else { PLog.dR(TAG, "OrderContext is in an invalid state: ${toString()}") null } */ OrderContext.create( orderId, orderCaptureUrl, null, orderPatchUrl ) } } } )
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394FoldStar 2022/12/14 ↩︎
相关知识
web支付基础教程
刷脸支付独立部署,刷脸支付系统能否买断?
在线支付说明
电子商务网站的十大在线支付方式
支付设计白皮书:支付系统的总架构
app调起支付宝-后端预支付
华为推出独立于支付宝、微信的支付平台,为“花瓣支付”
支付指南
花礼网鲜花APP的在线支付支持哪些支付方式?
医院移动支付结算 你需要了解的几件事
网址: Android PayPal支付的接入和SDK支付过程解析 https://www.huajiangbk.com/newsview849423.html
上一篇: Springboot集成支付宝沙 |
下一篇: Stripe支付集成:从零开始的 |
推荐分享

- 1君子兰什么品种最名贵 十大名 4012
- 2世界上最名贵的10种兰花图片 3364
- 3花圈挽联怎么写? 3286
- 4迷信说家里不能放假花 家里摆 1878
- 5香山红叶什么时候红 1493
- 6花的意思,花的解释,花的拼音 1210
- 7教师节送什么花最合适 1167
- 8勿忘我花图片 1103
- 9橄榄枝的象征意义 1093
- 10洛阳的市花 1039