前言
1.我是参考了一下@TimberBug哥子的文章https://www.jianshu.com/p/346e7f308f23,感谢,了解了一个大概,但是支付的代码没跑起来,最终还是参考Paypal的Demo代码才跑起来,估计是我用的最新版本的问题,然后吐槽哈Paypal官方的Android的示例代码,你们示例代码写得那么简单,看得大家一脸懵逼,真的良心不会痛吗?
2.服务端集成接口文档地址:https://developer.paypal.com/docs/api/orders/v2/
3.Android端集成接口文档地址:https://developer.paypal.com/docs/business/native-checkout/android/或新版的地址https://developer.paypal.com/sdk/in-app/android/4.墙裂建议开个webview来集成js支付方式,放弃这个坑逼的AndroidSdk,听劝,真的省时省心,集成相关可看文档最后面
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随便填写给地址就行,比如百度https://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)
我们的逻辑就是如下代码示例,当执行PayPalCheckout.start()方法开启支付时,同步开启一个定时器,循环去轮询paypal订单的状态,接口为REST v2订单详情,检测status是否是COMPLETED支付成功了
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;更有情况登录操作是直接拉起的Sdk内部Webview进行操作,具体原因均未可知,知道的铁子可评论区回复哈(PS:外部浏览器网页通过returnUrl拉起App这个操作有时会直接无效,连“是否跳转XX App”这个弹窗都没有,排查N久无效,猜测应该是浏览器屏蔽了网页的频繁弹窗导致的,具体原因也未可知)
2).另一个最大的异常问题就是支付中频繁切换后台程序了:调start拉起paypal的loading界面是有一两秒延迟的,此时如果立马执行挂后台操作,有一定几率导致paypal的loading及付款界面(com.paypal.pyplcheckout.home.view.activities.PYPLHomeActivity)显示不出来,直接导致支付流程中断无反应了;还有当sdk拉起浏览器登录paypal账号或支付时,此时从最近任务列表切换回app,app会回调到OnError方法里面,此时我们的业务支付流程已经结束了,但如果此时仍然回浏览器完成paypal的支付,这时支付完成首先可能会造成一个异常单问题(paypal扣了用户的钱,但是我们后台订单有可能已经关闭导致用户没得到相应的服务)
3).官方文档写的:【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)
4).还有一个坑爹的问题,唤起paypal支付拉到浏览器后在最近任务里面切换回app,sdk回调到OnError方法,此时再次切换到浏览器paypal登录/支付页面,执行登录购买操作,完成拉回业务app时,app会被paypal的sdk搞崩溃,应该是支付服务在AppResume时Sdk回调OnError方法后支付服务被关闭导致的服务状态异常,最关键是还没法拦截处理,报错有时还不一样,我抓到下面两种异常,再次无语子
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)
java.lang.Exception: Error Parsing Experiment
at com.paypal.pyplcheckout.ab.elmo.Elmo.fetchRemoteTreatment(Elmo.kt:108)
2022-01-13 14:38:01.533 28410-513/com.xxxx.xxx.xxx.xxx D/BaseSecureKeyWrapper: generateSignature : Signature Object Signature object: SHA256withECDSA<not initialized>
2022-01-13 14:38:01.569 28410-28410/com.xxxx.xxx.xxx.xxx D/CubicBezierInterpolator: CubicBezierInterpolator mControlPoint1x = 0.6, mControlPoint1y = 0.9, mControlPoint2x = 0.8, mControlPoint2y = 1.0
at com.paypal.pyplcheckout.ab.elmo.Elmo$getTreatmentRemote$1.invokeSuspend(Elmo.kt:79)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
- 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)
- 6.后来研究了哈Js支付,发现Js支付比Android支付银杏化多了,不会各种跳转,而且很快就能ok,墙裂建议使用js支付,集成支付文档地址:https://developer.paypal.com/docs/checkout/standard/integrate/,但是我用Vue的Npm模式依赖没有尝试成功,正常Js模式到是很快ok,代码就是demo代码,如下:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Ensures optimal rendering on mobile devices -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <!-- Optimal Internet Explorer compatibility -->
</head>
<body>
<!-- Include the PayPal JavaScript SDK; replace "test" with your own sandbox Business account app client ID -->
<script src="https://www.paypal.com/sdk/js?client-id=test¤cy=USD"></script>
<!-- Set up a container element for the button -->
<div id="paypal-button-container"></div>
<script>
paypal.Buttons({
// Sets up the transaction when a payment button is clicked
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '1.44' // Can reference variables or functions. Example: `value: document.getElementById('...').value`
}
}]
});
},
// Finalize the transaction after payer approval
onApprove: function(data, actions) {
return actions.order.capture().then(function(orderData) {
// Successful capture! For dev/demo purposes:解析这个orderData里面就有状态status,为COMPLETED即为成功
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
var transaction = orderData.purchase_units[0].payments.captures[0];
alert('Transaction '+ transaction.status + ': ' + transaction.id + '\n\nSee console for all available details');
// When ready to go live, remove the alert and show a success message within this page. For example:
// var element = document.getElementById('paypal-button-container');
// element.innerHTML = '';
// element.innerHTML = '<h3>Thank you for your payment!</h3>';
// Or go to another URL: actions.redirect('thank_you.html');
});
}
}).render('#paypal-button-container');
</script>
</body>
</html>
支付回调结果附上一个:
{
"id": "6TK99509CY0808713",
"intent": "CAPTURE",
"status": "COMPLETED",
"purchase_units": [
{
"reference_id": "default",
"amount": {
"currency_code": "USD",
"value": "1.44"
},
"payee": {
"email_address": "sb-c0o5o9202448@personal.example.com",
"merchant_id": "GMGDWWB7E42QQ"
},
"shipping": {
"name": {
"full_name": "Doe John"
},
"address": {
"address_line_1": "NO 1 Nan Jin Road",
"admin_area_2": "Shanghai",
"admin_area_1": "Shanghai",
"postal_code": "200000",
"country_code": "C2"
}
},
"payments": {
"captures": [
{
"id": "4WN3533027557664E",
"status": "COMPLETED",
"amount": {
"currency_code": "USD",
"value": "1.44"
},
"final_capture": true,
"seller_protection": {
"status": "ELIGIBLE",
"dispute_categories": [
"ITEM_NOT_RECEIVED",
"UNAUTHORIZED_TRANSACTION"
]
},
"create_time": "2022-01-17T08:50:46Z",
"update_time": "2022-01-17T08:50:46Z"
}
]
}
}
],
"payer": {
"name": {
"given_name": "John",
"surname": "Doe"
},
"email_address": "sb-3isgd9205973@business.example.com",
"payer_id": "F5EZF49V6R3V6",
"address": {
"country_code": "C2"
}
},
"create_time": "2022-01-17T08:49:52Z",
"update_time": "2022-01-17T08:50:46Z",
"links": [
{
"href": "https://api.sandbox.paypal.com/v2/checkout/orders/6TK99509CY0808713",
"rel": "self",
"method": "GET"
}
]
}