feat: paypal支付
This commit is contained in:
@@ -3,6 +3,7 @@ import { defineProps, defineEmits, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { AlipayOutlined, WechatOutlined, WalletOutlined } from '@ant-design/icons-vue';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import PaypalButton from '@/components/payment/paypal-button.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -128,6 +129,9 @@ const handleCancel = () => {
|
||||
<WechatOutlined class="payment-icon wxpay-icon" />
|
||||
<span>{{ t('page.order.wxpay') }}</span>
|
||||
</div>
|
||||
<PaypalButton
|
||||
:order-info="props.orderInfo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
|
||||
26
src/components/payment/paypal-button.vue
Normal file
26
src/components/payment/paypal-button.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { constructPaypalBtn } from '@/utils/paypal';
|
||||
|
||||
interface Props {
|
||||
orderInfo: {
|
||||
orderId: string;
|
||||
orderType: number; // 0: 购买套餐, 1: 余额充值
|
||||
orderAmount: number;
|
||||
};
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
onMounted(async()=>{
|
||||
await constructPaypalBtn(props.orderInfo.orderId, "USD");
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="paypal-buttons"></div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -37,3 +37,20 @@ export function payBalance(params: { orderId: string }) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Paypal createOrder */
|
||||
export function payPalOrders(params: {orderId: number}) {
|
||||
return request({
|
||||
url: '/payment/paypal/orders',
|
||||
method: 'post',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/** Paypal captureOrder */
|
||||
export function payPalCapture(paypalOrderId: string, orderId: number) {
|
||||
return request({
|
||||
url: `/payment/paypal/orders/${paypalOrderId}/capture/${orderId}`,
|
||||
method: 'post'
|
||||
});
|
||||
}
|
||||
|
||||
3
src/typings/auto-imports.d.ts
vendored
3
src/typings/auto-imports.d.ts
vendored
@@ -30,6 +30,7 @@ declare global {
|
||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
||||
const constructPaypalBtn: typeof import('../utils/paypal')['constructPaypalBtn']
|
||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
@@ -180,6 +181,8 @@ declare global {
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const payBalance: typeof import('../service/api/payment')['payBalance']
|
||||
const payPalCapture: typeof import('../service/api/payment')['payPalCapture']
|
||||
const payPalOrders: typeof import('../service/api/payment')['payPalOrders']
|
||||
const pick: typeof import('lodash-es')['pick']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||
|
||||
1
src/typings/components.d.ts
vendored
1
src/typings/components.d.ts
vendored
@@ -80,6 +80,7 @@ declare module 'vue' {
|
||||
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
|
||||
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
|
||||
OrderConfirmModal: typeof import('./../components/order-confirm/orderConfirmModal.vue')['default']
|
||||
PaypalButton: typeof import('./../components/payment/paypal-button.vue')['default']
|
||||
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
|
||||
ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
|
||||
99
src/utils/paypal.ts
Normal file
99
src/utils/paypal.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { loadScript } from "@paypal/paypal-js";
|
||||
import { payPalOrders, payPalCapture } from '@/service/api/payment';
|
||||
|
||||
export const constructPaypalBtn = async(orderId: number, currency: string) =>{
|
||||
loadScript({ clientId: import.meta.env.VITE_PAYPAL_CLIENT_ID, disableFunding: ["paylater"], currency })
|
||||
.then((paypal: any) => {
|
||||
paypal
|
||||
.Buttons({
|
||||
style: {
|
||||
shape: "rect",
|
||||
layout: "vertical",
|
||||
color: "white",
|
||||
label: "paypal",
|
||||
},
|
||||
message: {
|
||||
|
||||
},
|
||||
|
||||
async createOrder() {
|
||||
try {
|
||||
const response = await payPalOrders({orderId: orderId});
|
||||
|
||||
const orderData = response.data;
|
||||
if (orderData.id) {
|
||||
return orderData.id;
|
||||
}
|
||||
const errorDetail = orderData?.details?.[0];
|
||||
const errorMessage = errorDetail
|
||||
? `${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})`
|
||||
: JSON.stringify(orderData);
|
||||
|
||||
throw new Error(errorMessage);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
// resultMessage(`Could not initiate PayPal Checkout...<br><br>${error}`);
|
||||
}
|
||||
},
|
||||
|
||||
async onApprove(data, actions) {
|
||||
try {
|
||||
const response = await payPalCapture(data.orderID, orderId)
|
||||
|
||||
const orderData = response.data
|
||||
// Three cases to handle:
|
||||
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
|
||||
// (2) Other non-recoverable errors -> Show a failure message
|
||||
// (3) Successful transaction -> Show confirmation or thank you message
|
||||
|
||||
const errorDetail = orderData?.details?.[0];
|
||||
|
||||
if (errorDetail?.issue === "INSTRUMENT_DECLINED") {
|
||||
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
|
||||
// recoverable state, per
|
||||
// https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/
|
||||
return actions.restart();
|
||||
} else if (errorDetail) {
|
||||
// (2) Other non-recoverable errors -> Show a failure message
|
||||
throw new Error(`${errorDetail.description} (${orderData.debug_id})`);
|
||||
} else if (!orderData.purchase_units) {
|
||||
throw new Error(JSON.stringify(orderData));
|
||||
} else {
|
||||
// (3) Successful transaction -> Show confirmation or thank you message
|
||||
// Or go to another URL: actions.redirect('thank_you.html');
|
||||
const transaction =
|
||||
orderData?.purchase_units?.[0]?.payments?.captures?.[0] ||
|
||||
orderData?.purchase_units?.[0]?.payments?.authorizations?.[0];
|
||||
// resultMessage(
|
||||
// `Transaction ${transaction.status}: ${transaction.id}<br>
|
||||
//<br>See console for all available details`
|
||||
//);
|
||||
console.log(
|
||||
"Capture result",
|
||||
orderData,
|
||||
JSON.stringify(orderData, null, 2)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
// resultMessage(
|
||||
// `Sorry, your transaction could not be processed...<br><br>${error}`
|
||||
//);
|
||||
}
|
||||
},
|
||||
})
|
||||
.render("#paypal-buttons")
|
||||
.catch((error) => {
|
||||
console.error("failed to render the PayPal Buttons", error);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("failed to load the PayPal JS SDK script", error);
|
||||
});
|
||||
}
|
||||
|
||||
// Example function to show a result to the user. Your site's UI library can be used instead.
|
||||
function resultMessage(message: any) {
|
||||
const container: any = document.querySelector("#result-message");
|
||||
container.innerHTML = message;
|
||||
}
|
||||
Reference in New Issue
Block a user