Android PayPal支付的接入和SDK支付过程解析

Android PayPal支付的接入和SDK支付过程解析

根据产品需求,要接入PayPal支付,在研究了几天支付后,总结下内容给新手使用,本篇文章如果与官方文档有差异的话,一切以官方为准。转载请注明出处。
注: 本篇文章只针对新版本的SDK进行解析,解析内容只针对部分功能,其它功能请自行参考PayPal开发者文档和SDK1

目录

一、Paypal接入

本篇涉及的代码基于Github PayPal的示例进行修改,具体代码可以参考:paypal/android-checkout-sdk

1、创建应用
  1. 用注册完成的账号登录PayPal开发者平台

  2. 先注册沙盒测试应用,左侧列表,点击 “DASHBOARD” -> “My Apps & Credentials” 后,在右侧窗口点击 “Sandbox” 或者 “Live” -> “Create App” 按钮进入创建沙盒App页面(正式环境把 “Sandbox” 切换成 “Live”)。 注: 沙盒界面,会默认创建了一个"DefaultApp"的默认应用,你也可以使用该应用进行测试。

  3. 在弹出的 “Create New App” 界面填写资料,完成后点击 “Create App”:

    • App Name 应用名称,按需填写
    • App Type 应用类型(Merchant:商家, Platform:平台)默认选择第一个
    • Sandbox Business Account 沙盒企业账号。沙盒账号默认就行,测试用的企业账号,正式环境需要填写对应的企业账号,账号可以在"Account"创建,默认创建沙盒应用会生成一个个人和企业的账号,可以点击编辑查看密码。
  4. 创建应用完成,点击创建完成的应用 ,根据需求填写 (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、应用内集成
  1. 清单文件声明网络权限

    
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
         <uses-permission android:name="android.permission.INTERNET" />
       ...
    </manifest>
    
    
  2. 在项目根目录下的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'
                }
            }
        }
    }
    
    
  3. 在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')
    }
    
    
  4. 在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)
        }
    }
    
    
  5. 根据需求在需要用到支付的地方集成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" />
    ...
    
    
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")
            }
        )
    }
}

客户端集成
/**
 * 客户端集成,使用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") }
            )
        }
    )
}
服务器端集成

/**
 * [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)
        }
    )
}
    
    
4、测试是否集成结果

从"Account"中拿到创建成功的账号和密码,使用该测试账号测试付款是否成功。

二、支付流程解析

先从客户端集成说起,当开发者调用了createOrderActions.create(order)这个方法时,实际最终调用的是SDK里面CreateOrderActions.createOrder(order: Order, onOrderCreated: OnOrderCreated?)方法,该方法向调用paypal的REST API接口的创建订单接口去创建订单,然后当用户点击支付之后,本地通过接口请求,捕获/授权订单并且更新订单状态

服务器端集成则是将创建订单这一步骤的方法放到服务器端,然后再通过网络请求把OrderId拉到本地,再调用createOrderActions.set(orderId),然后当用户点击支付之后,通过接口回调,由app开发者调用后台的接口实现捕获/授权并且更新订单状态

核心的流程,paypal内部代码如下,具体过程已经标上注释了:

PayPalCheckout

PayPalCheckout,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)
    }

CreateOrderActions

CreateOrderActions,可以理解为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
    }
CreateOderAction

CreateOderAction,创建订单操作,当使用客户端集成并且调用了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)
    }
CreateOrderRequestFactory

CreateOrderRequestFactory,订单创建工厂类,本质就是调用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()
    }
}
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)
                }
            }
        }
    }
LsatTokenRequestFactory

LsatTokenRequestFactory,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()
    }
}

用户确认付款

在经过以上一系列处理,当用户点击“完成购物”的时候;这时候经过一系列复杂的流程,将结果返回到注册监听器中,不考虑其它失败的回调;当回调成功后,如果是客户端集成的,根据你传入的订单的OrderIntent是OrderIntent.AUTHORIZEOrderIntent.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)
                    }
                }
            },
            
AuthorizeOrderAction

AuthorizeOrderAction,授权订单动作,用户点击完成购物后,如果开发者实现了 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."
        }
    }
}

CaptureOrderAction

CaptureOrderAction,捕获订单动作,用户点击完成购物后,如果开发者实现了 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."
        }
    }
}

UpdateOrderStatusAction

UpdateOrderStatusAction类,顾名思义,就是更新订单状态用的,无论是授权还是捕获,归根都是调用这个类,然后对订单的状态进行更新

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()
    }
}

UpdateOrderStatusRequestFactory

UpdateOrderStatusRequestFactory,更新订单状态的网络请求工厂,根据创建订单配置的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"
)

补充

如果服务器集成,后台不想捕获订单,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
                        )
                    }
                }
            }
        )
        

  1. FoldStar 2022/12/14 ↩︎

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在 Android 应用中集成 PayPal 支付,你需要按照以下步骤进行操作: 1. 创建 PayPal 开发者账号并注册你的应用。 2. 集成 PayPal Android SDK 到你的应用中。 3. 在你的应用中添加一个支付按钮或支付页面,以便用户可以选择 PayPal 作为付款方式。 4. 编写代码来处理 PayPal 支付,包括验证付款和更新你的应用中的订单状态。 下面是一些详细的步骤: 1. 创建 PayPal 开发者账号并注册你的应用。 首先,你需要在 PayPal 上创建一个开发者账号,然后注册你的应用。在注册应用时,你需要提供应用名称和包名,以及其他必要的信息。 2. 集成 PayPal Android SDK 到你的应用中。 接下来,你需要下载和集成 PayPal Android SDK 到你的应用中。你可以从 GitHub 上下载最新版本的 SDK。 集成 SDK过程包括将 SDK 添加到你的项目中,添加必要的权限和依赖项,并在你的应用中配置 PayPal 环境。 3. 在你的应用中添加一个支付按钮或支付页面,以便用户可以选择 PayPal 作为付款方式。 你需要在你的应用中添加一个支付页面或按钮,以便用户可以选择 PayPal 作为付款方式。这个页面可以显示商品信息、价格和付款选项。用户可以使用 PayPal 账号或信用卡进行支付。 4. 编写代码来处理 PayPal 支付,包括验证付款和更新你的应用中的订单状态。 最后,你需要编写代码来处理 PayPal 支付。这包括验证付款、更新你的应用中的订单状态,并在付款成功时向用户显示一个成功页面或消息。你可以使用 PayPal 提供的 API 来完成这些任务。 以上是 Android 应用中集成 PayPal 支付的基本步骤。你可以参考 PayPal 的开发文档来获取更详细的信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值