feat: paypal支付
This commit is contained in:
1
.env
1
.env
@@ -35,3 +35,4 @@ VITE_SERVICE_EXPIRED_TOKEN_CODES=403
|
||||
|
||||
VITE_SERVICE_SERVER_ERROR_CODE=500
|
||||
|
||||
VITE_PAYPAL_CLIENT_ID=AfPgwFAmo9K7KCqiiGpNRCyQMSxI6V33eH-nEMnVndJNVEYOEOEn5wSPkHUybfzcjDLnBejt-RKnIfqX
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"dependencies": {
|
||||
"@better-scroll/core": "2.5.1",
|
||||
"@iconify/vue": "4.1.2",
|
||||
"@paypal/paypal-js": "^8.2.0",
|
||||
"@sa/axios": "workspace:*",
|
||||
"@sa/color-palette": "workspace:*",
|
||||
"@sa/hooks": "workspace:*",
|
||||
|
||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
||||
'@iconify/vue':
|
||||
specifier: 4.1.2
|
||||
version: 4.1.2(vue@3.4.27(typescript@5.4.5))
|
||||
'@paypal/paypal-js':
|
||||
specifier: ^8.2.0
|
||||
version: 8.2.0
|
||||
'@sa/axios':
|
||||
specifier: workspace:*
|
||||
version: link:packages/axios
|
||||
@@ -730,6 +733,9 @@ packages:
|
||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@paypal/paypal-js@8.2.0':
|
||||
resolution: {integrity: sha512-hLg5wNORW3WiyMiRNJOm6cN2IqjPlClpxd971bEdm0LNpbbejQZYtesb0/0arTnySSbGcxg7MxjkZ/N5Z5qBNQ==}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -3263,6 +3269,9 @@ packages:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
promise-polyfill@8.3.0:
|
||||
resolution: {integrity: sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==}
|
||||
|
||||
prompts@2.4.2:
|
||||
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -4633,6 +4642,10 @@ snapshots:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.17.1
|
||||
|
||||
'@paypal/paypal-js@8.2.0':
|
||||
dependencies:
|
||||
promise-polyfill: 8.3.0
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
|
||||
@@ -7553,6 +7566,8 @@ snapshots:
|
||||
|
||||
progress@2.0.3: {}
|
||||
|
||||
promise-polyfill@8.3.0: {}
|
||||
|
||||
prompts@2.4.2:
|
||||
dependencies:
|
||||
kleur: 3.0.3
|
||||
|
||||
@@ -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