vue中如何优雅的接入PayPal

您所在的位置:网站首页 paypal付款页面 vue中如何优雅的接入PayPal

vue中如何优雅的接入PayPal

2023-10-20 16:23| 来源: 网络整理| 查看: 265

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情。

本文中写的是PayPal V4版本,文档地址developer.paypal.com/docs/archiv…

paypal sdk的基本原理

PayPal的基本流程是先创建按钮所在的dom,然后PayPal sdk会在里面生成一个按钮,为了安全,按钮将会是一个iframe。

第一步:引入sdk

不建议使用官方的地址,这个地址很慢,而且偶尔打不开

可以使用这个地址,直接使用github上的文件,并通过jsdelivr做了一个加速:

创建支付按钮

在vue中,我们只需要写一个template就可以了。提示一下:不建议把按钮的id命名为paypal,会造成冲突。

为了考虑兼容vue2/3,后续我用options API去写。

监听dom创建完成

PayPal按钮的初始化必须在dom创建之后,这里可以采用2种办法:ref+watch,mounted.之所以不全部使用mounted,是因为在某些复杂页面时,你可能希望提前加载PayPal按钮

ref+watch export default { create(){ this.$watch(()=>this.$refs.paypal,()=>{consoloe.log('paypal dom rendered')}) } } mounted export default { mounted(){ consoloe.log('paypal dom rendered') } } sdk异常处理

sdk加载后,会在window下创建一个window.paypal,可以通过这个方法验证sdk是否加载成功.这里需要加载失败的时候重试,这时候建议将之前的js下载到本地,放到public文件夹下

if(window.paypal){ this.renderPaypal() } else { const timer = setInterval(()=>{ const s = document.createElement('script') s.src = 'js/paypal-checkout.min.js' s.onload = ()=>{ if(window.paypal){ this.paypalReady = true clearInterval(timer) this.renderPaypal() } } document.querySelector('head').appendChild(s) },1000) } 按钮加载前

这时候就可以开始尝试加载PayPal按钮了,这里就是最基本的API。

window.paypal.Button.render({ env:'sandbox', // sandbox or production client: { sandbox:'paypal sandbox key', production:'paypal production key', }, style:{}, commit: true, payment: async function (data, actions) { return actions.payment.create({ payment: { transactions: [{ invoice_number: '订单id,选传', notify_url: "webhook地址,选传", amount: { details: {}, total: '总金额,必传', currency: '货币代码,必传' } }], }, experience: { input_fields: { no_shipping: 1 } } }) }, onAuthorize: function (data, actions) { return actions.payment.execute() .then(function (paymentDetails) { //paypal弹框此时关闭 if (paymentDetails.state === 'approved') { const payment_id = paymentDetails.id //支付完成逻辑 } }) }, onCancel: function (data, actions) {}, onError: function (err) {} },('#paypalDom'))

但是有一些问题,当你所用的手机是全面屏时,例如iPhoneX,PayPal的显示会是这样。PayPal弹层的关闭按钮会出现在非安全区域导致无法点击。这应该是PayPal没有适配全面屏的问题,因此得手工处理一下。我的想法是在payment之前放一个定时器去判断。其中--safe-top是获取到的安全区域变量,可以参考这篇文章jelly.jd.com/article/600…

image.png

const fixPopup = ()=>{ setTimeout(()=>{ const paypalPopup = document.querySelector('.paypal-checkout-sandbox') if(paypalPopup){ paypalPopup.style.top = document.body.style.getPropertyValue('--safe-top') paypalPopup.style.height = 'calc(100vh - var(--safe-top))' paypalPopup.style.maxHeight = 'calc(100% - var(--safe-top))' paypalPopup.style.minHeight = 'calc(100% - var(--safe-top))' } },500) } 按钮加载后

通过打印window.paypal.Button.render,可以发现这是一个promises,于是可以通过then方法来实现之后加载后的逻辑,但实际上,当按钮展示出来时,有一段时间是不可点击的,仅是一个svg

//配置省略 window.paypal.Button.render({xxx},('#paypalDom')) .then(() => { //解决渲染2个paypal button的问题 this.$nextTick(() =>{ const btn = document.querySelectorAll('#paypalDom .paypal-button') if(btn&&btn.length>1){ for(let i=1;i { i++ if (i >= 30) { logger('paypal render timeout','paypal loading layer had run more than 6s') clearInterval(timer) return } const iframes = document.querySelectorAll('#payButton iframe') if (iframes[0]&&iframes[0].className.indexOf('prerender') >= 0) { // console.log('paypal不能点击') this.paypalCanClick = false } else { // console.log('paypal能点击') this.paypalCanClick = true clearInterval(timer) } }, 200) } 全部代码 import {logger} from "../utils" export default { name: "Paypal", data() { return { paypalReady: false, paypalCanClick: false, paypal_style:{}, } }, computed: { client() { return { sandbox:'', productions:'' } } }, mounted() { if (window.paypal) { this.paypalReady = true this.$nextTick(this.renderPaypal) } else { this.paypalReady = false const s = document.createElement('script') s.src = location.origin + location.pathname + 'js/paypal-checkout.min.js' s.setAttribute('ver','4.0.332') s.onload = () => { if (window.paypal) { this.paypalReady = true this.$nextTick(this.renderPaypal) } } document.body.appendChild(s) } }, methods: { // 渲染PayPal支付按钮 initPayPal() { const _self = this return window.paypal.Button.render({ env: '', client: this.client, style: this.paypal_style, commit: true, payment: async function (data, actions) { // 处理paypal弹层关闭按钮的适配问题 setTimeout(()=>{ const paypalPopup = document.querySelector('.paypal-checkout-sandbox') if(paypalPopup){ paypalPopup.style.top = document.body.style.getPropertyValue('--safe-top') paypalPopup.style.height = 'calc(100vh - var(--safe-top))' paypalPopup.style.maxHeight = 'calc(100% - var(--safe-top))' paypalPopup.style.minHeight = 'calc(100% - var(--safe-top))' } },500) return actions.payment.create({ payment: { transactions: [{ invoice_number:'', notify_url: "", amount: { details: {}, total: '', currency:'USD' } }], }, experience: { input_fields: { no_shipping: 1 } } }) }, onAuthorize: function (data, actions) { return actions.payment.execute() .then(function (paymentDetails) { if (paymentDetails.state === 'approved') { return } }) }, onCancel: function (data, actions) { _self.$parent.$emit('cancel',_self.type,data) _self.$parent.$emit('paypalCancelled', data) }, onError: function (err) { console.log('paypalError', err) _self.$parent.$emit('error',_self.type,err) _self.$parent.$emit('paypalError', err) } }, '#payButton') .then(() => { //解决渲染2个paypal button的问题 _self.$nextTick(() =>{ const btn = document.querySelectorAll('#payButton .paypal-button') if(btn&&btn.length>1){ for(let i=1;i { i++ if (i >= 30) { if(this.PropsData.debug){ console.log('paypal loading layer had run more than 6s') } clearInterval(timer) _self.$parent.$emit('paypalTimeout') return } const iframes = document.querySelectorAll('#payButton iframe') if (iframes[0]&&iframes[0].className.indexOf('prerender') >= 0) { // console.log('paypal不能点击') _self.paypalCanClick = false } else { // console.log('paypal能点击') _self.paypalCanClick = true _self.$parent.$emit('canClick') clearInterval(timer) } }, 200) _self.$once('hook:beforeDestroy', () => clearInterval(timer)) }) .catch(err => { if(this.PropsData.debug){ console.log('paypal render error',err) } _self.$parent.$emit('error',_self.type,err) }) }, // paypal被点击 } .paypal, .paypal_button { width: 100%; height: auto; min-height: 35px; }

纯手写代码,如有错误欢迎指正。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3