Android完整接入PayPal支付及常见问题(使用paypal支付)
前言
1.我是参考了一下@TimberBug哥子的文章www.jianshu.com/p/346e7f308…,感谢,了解了一个大概,但是支付的代码没跑起来,最终还是参考Paypal的Demo代码才跑起来,估计是我用的最新版本的问题,然后吐槽哈Paypal官方的Android的示例代码,你们示例代码写得那么简单,看得大家一脸懵逼,真的良心不会痛吗? 2.服务端集成接口文档地址:developer.paypal.com/docs/api/or… 3.Android端集成接口文档地址:developer.paypal.com/docs/busine…或新版的地址developer.paypal.com/sdk/in-app/…
Paypal集成准备工作
登录或注册账号,注册新账号需要选择国家,最好绑定一张银行卡,储蓄卡或信用卡均可
登录进去在Dashboard界面左边的菜单“My apps & credentials”里面的“REST API apps”项创建一个App或修改一个已有的App,目前系统会默认创建一个Default Application,可直接点击进去修改使用
点进去之后主要有3大块:
1).SANDBOX API CREDENTIALS:配置信息相关;主要包含Sandbox account(沙箱环境商家收款邮箱账号),Client ID(后台Api接口认证加签和App的Sdk初始化需要用到),Secret(后台Api接口认证加签需要用到)
2).SANDBOX APP SETTINGS:App的Sdk相关配置设置;主要关注Return URL(自定义协议Uri,这个不是支付宝、微信支付完成的回调Url,这个是调起支付需要登录Paypal买家账号时,Paypal会跳转到浏览器进行网页登录,当登录完成需要返回App的时候就需要自定义协议Uri返回我们App里面,格式类类似于网址:com.xxxx.protocol://xxxx.xxx,目前Paypal固定后缀格式:xxx.xxx.xx://paypalpay,一般xxx.xxx.xx设置为当前App的包名);其他需要关注的是Accept payments,必须勾上,且Advanced options展开Native Checkout SDK必须是绿色的勾勾;然后Log in with PayPal,必须勾上,且Advanced options展开Full name和Email必须勾上,Privacy policy URL和User agreement URL随便填写给地址就行,比如百度www.baidu.com/;总体参数按我如下截图…
3).SANDBOX WEBHOOKS:这个就是支付完成Paypal后台通知我们自己后台的配置地方,这个才是类似支付宝、微信支付完成的回调Url,一般配置后台的接口,按需使用
其他需要关注的就是沙箱环境里面的卖家测试账号和买家账号,账号类型Type字段Business标识的就是卖家,如果需要就配置在代码收款商Payee位置,Type字段Personal标识的就是买家,需要在App的Sdk拉起网页付款登录时登录这个账号;点开Manage accounts对应的三个点,选择View/edit account,在弹框中可点击Change password修改系统随机生成的密码成你自己想要的,注意:修改保存完成后并不会显示,但是已经修改成功啦,可用这个密码去做付款账号的登录了(但是换成生产账号测试时遇到一个奇怪现象:我在App的Sdk拉起网页付款登录时登录Personal标识的买家账号时,结果网页一直提示我是登录的卖家账号,不能完成登录付款,换成Business标识的账号登录就成功了,测试环境就必须登录Personal标识的才能付款,因此暂时只能定义成Paypal的Bug了)
Paypal Android SDK集成工作
1.开启网络权限
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> ... <uses-permission android:name="android.permission.INTERNET" /> ... </manifest> 复制代码
2.项目根build.gradle文件中配置paypal的仓库
allprojects { repositories { ... 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.添加Java 8的兼容处理
android { ... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = "1.8" } } 复制代码
4.添加Paypal的lib库最新版依赖(注:因Paypal Sdk是用kotlin写的,请提前添加androidx.core:core-ktx和org.jetbrains.kotlin:kotlin-stdlib-jdk7支持)
implementation('com.paypal.checkout:android-sdk:0.5.2') { exclude group: 'com.google.code.gson', module: 'gson' //这里排除gson是因为和我主项目的gson冲突了,而且paypal依赖的还是比我新的版本,导致我本地报错了;其实还有个重要冲突是okhttp,我们项目用的是3.14.0,paypal用的是4.8.0,因为4.x版本比3.x版本变化比较大,而且还不能排除paypal的4.x版本,会导致paypal初始化就报错,所以我们只有把主项目okhttp升级到最新了 } 复制代码
Paypal Android SDK开发工作
1.在Application的onCreate方法做如下Paypal初始化操作
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//其中clientId需要替换成上面步骤申请到的客户端id PayPalCheckout.setConfig(CheckoutConfig(application, clientId, if (BuildConfig.DEBUG) Environment.SANDBOX else Environment.LIVE, String.format("%s://paypalpay", BuildConfig.APPLICATION_ID), CurrencyCode.USD, UserAction.PAY_NOW, PaymentButtonIntent.CAPTURE, SettingsConfig(BuildConfig.DEBUG, false))) }else{ System.out.println("initPayPal失败,系统版本过低") } 复制代码
2.Paypal的3种代码集成方式
1). Client-side integration: 客户端集成,主要特点是需要集成一个PayPal自带的支付按钮PayPalButton,这个按钮仅可做少量Paypal固定的UI定制,侵入性比较强,然后创建订单和执行订单捕获逻辑均在App端,适用于不要后台参与的情况
2). Server-side integration: 服务端集成,主要特点是服务端创建订单和执行订单捕获,App端只需接收服务端返回的paypal订单id,调用createOrderActions.set(orderId)设置即可开启订单支付逻辑,适合于主流支付逻辑,后台订单可信可控,我们采用的这种方式
3). Programmatically start the SDK: 客户端集成,主要特点和Client-side integration比较类似,只是不需要集成PayPal自带的支付按钮PayPalButton,然后创建订单和订单捕获逻辑均在App端,适用于不要后台参与的情况
3.我们集成Server-side integration的代码如下(参数paypalOrderId为后台去调paypal的创单接口获取到的订单id)
private var checkOrderMax: Int = 300//循环检测的最大次数 private var checkOrderCount: Int = -1//循环检测的初始次数 private var isCheckOrderFlag: Boolean = false//是否检测订单状态中 @RequiresApi(Build.VERSION_CODES.M) private fun startPaypal(context: Activity, paypalOrderId: String){ PayPalCheckout.start(CreateOrder { createOrderActions -> createOrderActions.set(paypalOrderId) }, null, null, OnCancel {//实测取消订单后没走这个方法,走到了报错,报的returnUrl是空,但我们再paypal后台和sdk初始化均设置了的,而且在手机浏览器登录paypal买家账号后也是可以拉回我们app的,但是它还是会报这个错,无语子,估计还是paypal得bug resetCheckPaypalOrder() System.out.println("用户取消Paypal支付") }, OnError { errorInfo -> resetCheckPaypalOrder() System.out.println("==========paypal onError=======ErrorInfo=======>: $errorInfo") System.out.println(if(errorInfo?.reason.isNullOrBlank()) (if(errorInfo?.error?.message.isNullOrBlank()) "Paypal支付未知错误" else errorInfo.error.message) else errorInfo!!.reason) }) checkOrderCount = 0 isCheckOrderFlag = true startCheckPaypalOrder(context, paypalOrderId) } private fun startCheckPaypalOrder(context: Activity, paypalOrderId: String){ System.out.println("======Stephen=======checkPaypalOrder====>Count:$checkOrderCount") if(!isCheckOrderFlag)return if(checkOrderCount >= checkOrderMax){ resetCheckPaypalOrder() System.out.println("Paypal订单支付等待超时") return }//end of if ApiRequestMethod.checkPaypalOrder(paypalOrderId, object : RequestAllCallback<String> {//这个是我们接口调用封装方法,换成你们自己的哈(REST v2订单详情接口:https://developer.paypal.com/api/orders/v2/#orders_get) override fun onSuccess(data: String?) {//接口返回正确json形如:{"paypal_status":"CREATED"} var isContinueLoop = true val jsonObject = ToolUtils.instance.fromJsonToObj(data) if(null != jsonObject && jsonObject.has("paypal_status")){ when(jsonObject.getString("paypal_status")){ "APPROVED" -> {//这个状态表示用户已经支付完成,后台开始捕获订单并开始执行确认了,实测这个状态有时差不多得持续1分多钟,快的话也是耗费20多秒,因此添加一个loading逻辑如下,此具体loading显示逻辑更换成你自己实际的loading框,注意loading框得依附在你自己App的当前支付界面上,因为你看到paypal的支付框实质是一个activity,依附在paypal上面会被误关闭 if(!ToolUtils.instance.isLoadingShow())ToolUtils.instance.showLoading("确认支付结果中...", appendActivity = context) } "COMPLETED" -> {//这个状态表示后台捕获确认订单完成,也就是这笔订单真正的完成了 isContinueLoop = false resetCheckPaypalOrder() System.out.println("Paypal订单支付成功") } } }//end of if if(isContinueLoop){ ToolUtils.instance.delayExecute(1000L){//这个是我们延时调用封装方法,换成你们自己的哈 checkOrderCount++ startCheckPaypalOrder(context, paypalOrderId) } }//end of if } override fun onFailure(aliErrorResponse: AliErrorResponse, httpCode: Int): Boolean { ToolUtils.instance.delayExecute(1000L){ checkOrderCount++ startCheckPaypalOrder(context, paypalOrderId) } return false } }) } private fun resetCheckPaypalOrder(){ checkOrderCount = 0 isCheckOrderFlag = false ToolUtils.instance.closeLoading()//关闭loading框 } 复制代码
4.说一下我们集成Server-side integration的代码遇到的问题
1).当开始支付时如果没有登录过Paypal买家账号或账号登录过期了,Sdk会拉起外部浏览器进行网页登录,登录成功后会通过returnUrl拉回业务App进行原生App的支付,然而有时又会直接就待在网页进行支付,支付完成后才会拉回业务App;更有情况登录操作是直接拉起的内部浏览器进行操作,具体原因均未可知,知道的铁子可评论区回复哈
2).官方文档写的:【Note: If you're integrating for the first time, we recommend using the REST v2 server-side integration. You must complete the first five steps in a Client-side integration.】,意思是服务端集成前面步骤和客户端集成一样,需要添加PayPal自带的支付按钮PayPalButton,然后调用payPalButton.setup()方法设置订单id,同时也可以覆盖OnApprove()回调方法;然而我实测发现,可以不用集成支付按钮PayPalButton,直接调用PayPalCheckout.start()方法设置订单id即可开启支付流程,只是最好不要去覆盖OnApprove()回调方法,因为这里执行it.orderActions.capture { }捕获状态时,会报上下文异常:
com.paypal.checkout.order.OrderContextNotAvailableException: Tried to retrieve OrderContext before it was created. at com.paypal.checkout.order.OrderContext$Companion.get(OrderContext.kt:27) at com.paypal.checkout.order.UpdateOrderStatusAction$execute$orderContext$1.invokeSuspend(UpdateOrderStatusAction.kt:28) at com.paypal.checkout.order.UpdateOrderStatusAction$execute$orderContext$1.invoke(Unknown Source:10) at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91) at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:161) at kotlinx.coroutines.BuildersKt.withContext(Unknown Source:1) at com.paypal.checkout.order.UpdateOrderStatusAction.execute(UpdateOrderStatusAction.kt:27) at com.paypal.checkout.order.CaptureOrderAction$execute$2.invokeSuspend(CaptureOrderAction.kt:21) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) 复制代码
因此我们的逻辑就是如上代码示例,当执行PayPalCheckout.start()方法开启支付时,同步开启一个定时器,循环去轮询paypal订单的状态,接口为REST v2订单详情,检测status是否是COMPLETED支付成功了
3).还发现一个异常问题,当sdk拉起浏览器登录paypal账号或支付时,此时从最近任务列表切换回app,app会回调到OnError方法里面,此时我们的业务支付流程已经结束了,但如果此时任然回浏览器完成paypal的支付,支付完成拉回业务app时,app会被paypal的sdk搞崩溃,应该是支付服务被关闭导致的状态异常,再次无语子
2021-12-27 17:51:45.505 E/DEBUG: Back traces starts. 2021-12-27 17:51:45.509 E/DEBUG: at com.paypal.openid.AuthorizationService.a(Unknown Source:9) 2021-12-27 17:51:45.507 E/DEBUG: java.lang.IllegalStateException: Service has been disposed and rendered inoperable 2021-12-27 17:51:45.510 E/DEBUG: at com.paypal.openid.AuthorizationService.performTokenRequest(Unknown Source:0) 2021-12-27 17:51:45.509 E/DEBUG: at com.paypal.openid.AuthorizationService.a(Unknown Source:9) 2021-12-27 17:51:45.511 E/DEBUG: at com.paypal.openid.AuthorizationService.performTokenRequest(Unknown Source:2) 2021-12-27 17:51:45.510 E/DEBUG: at com.paypal.openid.AuthorizationService.performTokenRequest(Unknown Source:0) 2021-12-27 17:51:45.513 E/DEBUG: at com.paypal.authcore.authentication.Authenticator.a(Unknown Source:191) 2021-12-27 17:51:45.511 E/DEBUG: at com.paypal.openid.AuthorizationService.performTokenRequest(Unknown Source:2) 2021-12-27 17:51:45.514 E/DEBUG: at com.paypal.authcore.authentication.Authenticator.authenticateForAccessTokenWithDelegate(Unknown Source:86) 2021-12-27 17:51:45.513 E/DEBUG: at com.paypal.authcore.authentication.Authenticator.a(Unknown Source:191) 2021-12-27 17:51:45.515 E/DEBUG: at com.paypal.pyplcheckout.flavorauth.ThirdPartyAuth.getUserAccessToken(ThirdPartyAuth.java:149) 2021-12-27 17:51:45.514 E/DEBUG: at com.paypal.authcore.authentication.Authenticator.authenticateForAccessTokenWithDelegate(Unknown Source:86) 2021-12-27 17:51:45.516 E/DEBUG: at com.paypal.pyplcheckout.home.viewmodel.MainPaysheetViewModel.logInUser(MainPaysheetViewModel.java:1629) 2021-12-27 17:51:45.515 E/DEBUG: at com.paypal.pyplcheckout.flavorauth.ThirdPartyAuth.getUserAccessToken(ThirdPartyAuth.java:149) 2021-12-27 17:51:45.517 E/DEBUG: at com.paypal.pyplcheckout.home.viewmodel.MainPaysheetViewModel.$r8$lambda$u3bJQV5CM255mmoJkHAH5Y7Tgzs(Unknown Source:0) 2021-12-27 17:51:45.516 E/DEBUG: at com.paypal.pyplcheckout.home.viewmodel.MainPaysheetViewModel.logInUser(MainPaysheetViewModel.java:1629) 2021-12-27 17:51:45.518 E/DEBUG: at com.paypal.pyplcheckout.home.viewmodel.MainPaysheetViewModel$$ExternalSyntheticLambda9.onUpdateClientConfig(Unknown Source:2) 2021-12-27 17:51:45.517 E/DEBUG: at com.paypal.pyplcheckout.home.viewmodel.MainPaysheetViewModel.$r8$lambda$u3bJQV5CM255mmoJkHAH5Y7Tgzs(Unknown Source:0) 2021-12-27 17:51:45.519 V/nxoBaseCallback: https://www.paypal.com/xoplatform/logger/api/logger returned with response 2021-12-27 17:51:45.518 E/DEBUG: at com.paypal.pyplcheckout.home.viewmodel.MainPaysheetViewModel$$ExternalSyntheticLambda9.onUpdateClientConfig(Unknown Source:2) 2021-12-27 17:51:45.520 E/DEBUG: at com.paypal.pyplcheckout.home.viewmodel.MainPaysheetViewModel.lambda$updateClientConfigBefore$22(MainPaysheetViewModel.java:818) 2021-12-27 17:51:45.519 V/nxoBaseCallback: https://www.paypal.com/xoplatform/logger/api/logger returned with response 2021-12-27 17:51:45.521 E/DEBUG: at com.paypal.pyplcheckout.home.viewmodel.MainPaysheetViewModel$$ExternalSyntheticLambda0.onEvent(Unknown Source:2) 2021-12-27 17:51:45.520 E/DEBUG: at com.paypal.pyplcheckout.home.viewmodel.MainPaysheetViewModel.lambda$updateClientConfigBefore$22(MainPaysheetViewModel.java:818) 2021-12-27 17:51:45.522 E/DEBUG: at com.paypal.pyplcheckout.events.Events.fire(Events.java:116) 2021-12-27 17:51:45.521 E/DEBUG: at com.paypal.pyplcheckout.home.viewmodel.MainPaysheetViewModel$$ExternalSyntheticLambda0.onEvent(Unknown Source:2) 2021-12-27 17:51:45.523 E/DEBUG: at com.paypal.pyplcheckout.services.callbacks.ClientConfigUpdateCallback.onApiSuccess(ClientConfigUpdateCallback.kt:54) 2021-12-27 17:51:45.522 E/DEBUG: at com.paypal.pyplcheckout.events.Events.fire(Events.java:116) 2021-12-27 17:51:45.525 E/DEBUG: at com.paypal.pyplcheckout.services.callbacks.BaseCallback.handleApiSuccess(BaseCallback.kt:114) 2021-12-27 17:51:45.523 E/DEBUG: at com.paypal.pyplcheckout.services.callbacks.ClientConfigUpdateCallback.onApiSuccess(ClientConfigUpdateCallback.kt:54) 2021-12-27 17:51:45.526 E/DEBUG: at com.paypal.pyplcheckout.services.callbacks.BaseCallback.onResponse(BaseCallback.kt:68) 2021-12-27 17:51:45.525 E/DEBUG: at com.paypal.pyplcheckout.services.callbacks.BaseCallback.handleApiSuccess(BaseCallback.kt:114) 2021-12-27 17:51:45.527 V/nxoBaseCallback: https://www.paypal.com/xoplatform/logger/api/logger returned with response 2021-12-27 17:51:45.526 E/DEBUG: at com.paypal.pyplcheckout.services.callbacks.BaseCallback.onResponse(BaseCallback.kt:68) 2021-12-27 17:51:45.527 E/DEBUG: at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519) 2021-12-27 17:51:45.527 V/nxoBaseCallback: https://www.paypal.com/xoplatform/logger/api/logger returned with response 2021-12-27 17:51:45.529 E/DEBUG: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 2021-12-27 17:51:45.527 E/DEBUG: at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519) 2021-12-27 17:51:45.530 E/DEBUG: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 2021-12-27 17:51:45.529 E/DEBUG: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 2021-12-27 17:51:45.531 E/DEBUG: at java.lang.Thread.run(Thread.java:784) 复制代码
5.以上操作Server-side integration集成就完成了,我再补上个参考TimberBug老哥和Paypal的Demo代码的Programmatically start the SDK客户端集成方式的测试代码以做备份:
fun startPaypal(context: Activity){ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PayPalCheckout.start( createOrder = CreateOrder { actions -> val createdItems = listOf(CreatedItem("TestGoods", "1", "1", "0.1", ItemCategory.DIGITAL_GOODS))//参数依次:商品名称,数量,单价,税费,商品类型 val shippingPreference = ShippingPreference.NO_SHIPPING //航运信息,非必须 val currencyCode = CurrencyCode.USD //收款货币Code //以下为统一计算总数,避免出现不一致情况 val itemTotal = createdItems.map { it.amount.toDouble() * it.quantity.toInt() } .sum().toBigDecimal().scaledForMoney val taxTotal = createdItems.map { it.taxAmount.toDouble() * it.quantity.toInt() } .sum().toBigDecimal().scaledForMoney val shippingTotal = BigDecimal(0.00).scaledForMoney val handlingTotal = BigDecimal(0.00).scaledForMoney val shippingDiscountTotal = BigDecimal(0.00).scaledForMoney val itemDiscountTotal = BigDecimal(0.00).scaledForMoney val totalValue = itemTotal.add(taxTotal).add(shippingTotal).add(handlingTotal).subtract(shippingDiscountTotal).subtract(itemDiscountTotal) actions.create( Order.Builder() .intent(OrderIntent.CAPTURE) .purchaseUnitList(listOf(PurchaseUnit.Builder() .referenceId(UUID.randomUUID().toString())//唯一id .amount( Amount.Builder() .currencyCode(currencyCode) .value(totalValue.asMoneyString) .breakdown( BreakDown.Builder() .itemTotal(itemTotal.unitAmountFor(currencyCode)) .shipping(shippingTotal.unitAmountFor(currencyCode)) .handling(handlingTotal.unitAmountFor(currencyCode)) .taxTotal(taxTotal.unitAmountFor(currencyCode)) .shippingDiscount(shippingDiscountTotal.unitAmountFor(currencyCode)) .discount(itemDiscountTotal.unitAmountFor(currencyCode)) .build()) .build()) .items( createdItems.map { createdItem -> Items.Builder().name(createdItem.name) .quantity(createdItem.quantity) .category(createdItem.itemCategory) .unitAmount(UnitAmount.Builder().value(createdItem.amount).currencyCode(currencyCode).build()) .tax(UnitAmount.Builder().value(createdItem.taxAmount).currencyCode(currencyCode).build()).build() }) .shipping(Shipping.Builder().address(Address.Builder() .addressLine1("123 Townsend St") .addressLine2("Floor 6") .adminArea2("San Francisco") .adminArea1("CA") .postalCode("94107") .countryCode("US") .build()).options(null).build())//航运地址,非必须,Omitting shipping will default to the customer's default shipping address. .customId("CUSTOM-123")//The API caller-provided external ID. Used to reconcile API caller-initiated transactions with PayPal transactions. Appears in transaction and settlement reports. .description("Purchase from Orders Quick Start") .softDescriptor("800-123-1234")//The soft descriptor is the dynamic text used to construct the statement descriptor that appears on a payer's card statement.不太清楚有啥用 .build() ) ).appContext( AppContext.Builder().brandName("公司品牌,非必须") .userAction(UserAction.PAY_NOW) .shippingPreference(shippingPreference) .build() ).build()){ id -> System.out.println("生成的订单ID: $id") } }, onApprove = OnApprove { approval -> approval.orderActions.capture { result -> val message = when (result) { is CaptureOrderResult.Success -> { "Order Capture Succeeded" } is CaptureOrderResult.Error -> { "Order Capture Failed" } } System.out.println(message) } }, onCancel = OnCancel { System.out.println("Buyer Cancelled Checkout") }, onError = OnError { errorInfo -> System.out.println("An Error Occurred") } ) }else{ System.out.println("payPal支付失败,系统版本过低") } } private fun BigDecimal.unitAmountFor(currencyCode: CurrencyCode): UnitAmount { return UnitAmount.Builder().value(asMoneyString).currencyCode(currencyCode).build() } private val BigDecimal.asMoneyString: String get() = DecimalFormat("#0.00").format(this) private val BigDecimal.scaledForMoney: BigDecimal get() = setScale(2, RoundingMode.HALF_UP)
作者:逆水寒本尊
链接:https://juejin.cn/post/7047036654254555166