@uappi/public-sdk 1.2.10 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/PaymentHostedFields/PaymentHostedFields.vue +127 -0
- package/components/PaymentHostedFields/index.ts +1 -0
- package/components/PaymentHostedFields/styles.scoped.css +37 -0
- package/components/PaymentHostedFields/types.ts +33 -0
- package/components/index.ts +1 -0
- package/modules/Api/README.md +10 -0
- package/modules/Api/endpoints/CartComposable.ts +10 -0
- package/modules/Api/endpoints/CategoryComposable.ts +15 -0
- package/modules/Api/index.ts +7 -2
- package/modules/Api/types/endpoints/cart/cart.interfaces.ts +8 -0
- package/modules/Api/types/endpoints/category/category.interfaces.ts +16 -0
- package/modules/Api/types/index.ts +14 -0
- package/package.json +2 -3
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
3
|
+
|
|
4
|
+
import api from '../../modules/Api';
|
|
5
|
+
|
|
6
|
+
import type { Props, Emits, PaymentSuccessData, PaymentRedirectData } from './types';
|
|
7
|
+
|
|
8
|
+
// `PaymentHostedFields` renderiza o formulário de pagamento via iframe isolado,
|
|
9
|
+
// servido pelo serviço `render.uappi`. Todos os dados sensíveis (número do cartão,
|
|
10
|
+
// CVV, etc.) trafegam diretamente entre o iframe e o gateway, sem passar pela loja.
|
|
11
|
+
const props = defineProps<Props>();
|
|
12
|
+
|
|
13
|
+
const emit = defineEmits<Emits>();
|
|
14
|
+
|
|
15
|
+
const iframeEl = ref<HTMLIFrameElement | null>(null);
|
|
16
|
+
|
|
17
|
+
const paymentToken = ref<string | null>(null);
|
|
18
|
+
const loadingToken = ref(true);
|
|
19
|
+
const tokenError = ref<string | null>(null);
|
|
20
|
+
|
|
21
|
+
const iframeSrc = computed(() => {
|
|
22
|
+
if (!paymentToken.value) return '';
|
|
23
|
+
|
|
24
|
+
const host = props.iframeHost
|
|
25
|
+
?? (import.meta.env.VITE_PAYMENT_IFRAME_HOST as string | undefined)
|
|
26
|
+
?? 'https://payments.uappi.store';
|
|
27
|
+
|
|
28
|
+
const url = new URL(host);
|
|
29
|
+
|
|
30
|
+
url.searchParams.set('token', paymentToken.value);
|
|
31
|
+
|
|
32
|
+
if (props.ctaColor) url.searchParams.set('ctaColor', props.ctaColor.replace('#', ''));
|
|
33
|
+
if (props.lang) url.searchParams.set('lang', props.lang);
|
|
34
|
+
if (props.countryCode) url.searchParams.set('countryCode', props.countryCode);
|
|
35
|
+
|
|
36
|
+
return url.toString();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
async function fetchPaymentToken(): Promise<void> {
|
|
40
|
+
loadingToken.value = true;
|
|
41
|
+
tokenError.value = null;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const response = await api.cart.paymentToken({ paymentOptionId: props.option.id });
|
|
45
|
+
|
|
46
|
+
if (!response?.token) {
|
|
47
|
+
console.error('[PaymentHostedFields] Token ausente na resposta:', response);
|
|
48
|
+
tokenError.value = 'Não foi possível iniciar o formulário de pagamento.';
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
paymentToken.value = response.token;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error('[PaymentHostedFields] Erro ao buscar token de pagamento:', err);
|
|
55
|
+
tokenError.value = 'Não foi possível iniciar o formulário de pagamento.';
|
|
56
|
+
} finally {
|
|
57
|
+
loadingToken.value = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Só processa mensagens cuja origem seja a própria janela do iframe renderizado
|
|
63
|
+
* por este componente, evitando que outra janela/frame na página finja ser o
|
|
64
|
+
* render.uappi e injete eventos de pagamento falsos.
|
|
65
|
+
*/
|
|
66
|
+
function handleIframeMessage(event: MessageEvent): void {
|
|
67
|
+
if (!iframeEl.value || event.source !== iframeEl.value.contentWindow) return;
|
|
68
|
+
|
|
69
|
+
const { type, data, url, errorCode, message } = (event.data ?? {}) as {
|
|
70
|
+
type: string;
|
|
71
|
+
data: PaymentSuccessData & PaymentRedirectData;
|
|
72
|
+
url: string;
|
|
73
|
+
errorCode: string | undefined;
|
|
74
|
+
message: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
switch (type) {
|
|
78
|
+
case 'UAPPI_PAYMENT_READY':
|
|
79
|
+
emit('payment-ready');
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case 'UAPPI_PAYMENT_SUCCESS':
|
|
83
|
+
emit('payment-success', data);
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case 'UAPPI_PAYMENT_REDIRECT':
|
|
87
|
+
emit('payment-redirect', url, data);
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
case 'UAPPI_PAYMENT_ERROR':
|
|
91
|
+
emit('payment-error', errorCode, message);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
onMounted(async () => {
|
|
97
|
+
window.addEventListener('message', handleIframeMessage);
|
|
98
|
+
await fetchPaymentToken();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
onUnmounted(() => {
|
|
102
|
+
window.removeEventListener('message', handleIframeMessage);
|
|
103
|
+
});
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<template>
|
|
107
|
+
<div class="payment-hosted-fields">
|
|
108
|
+
<div v-if="loadingToken" class="payment-hosted-fields__skeleton" />
|
|
109
|
+
|
|
110
|
+
<p v-else-if="tokenError" class="payment-hosted-fields__error">
|
|
111
|
+
{{ tokenError }}
|
|
112
|
+
</p>
|
|
113
|
+
|
|
114
|
+
<iframe
|
|
115
|
+
v-else-if="iframeSrc"
|
|
116
|
+
ref="iframeEl"
|
|
117
|
+
:src="iframeSrc"
|
|
118
|
+
class="payment-hosted-fields__iframe"
|
|
119
|
+
frameborder="0"
|
|
120
|
+
scrolling="no"
|
|
121
|
+
title="Formulário de pagamento seguro"
|
|
122
|
+
allow="payment"
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
</template>
|
|
126
|
+
|
|
127
|
+
<style scoped src="./styles.scoped.css" />
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as PaymentHostedFields } from './PaymentHostedFields.vue';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
.payment-hosted-fields {
|
|
2
|
+
width: 100%;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.payment-hosted-fields__iframe {
|
|
6
|
+
display: block;
|
|
7
|
+
width: 100%;
|
|
8
|
+
min-height: 420px;
|
|
9
|
+
border: none;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.payment-hosted-fields__skeleton {
|
|
13
|
+
width: 100%;
|
|
14
|
+
height: 420px;
|
|
15
|
+
border-radius: 4px;
|
|
16
|
+
background: linear-gradient(
|
|
17
|
+
90deg,
|
|
18
|
+
var(--neutral-surface-low) 25%,
|
|
19
|
+
var(--neutral-border) 37%,
|
|
20
|
+
var(--neutral-surface-low) 63%
|
|
21
|
+
);
|
|
22
|
+
background-size: 400% 100%;
|
|
23
|
+
animation: payment-hosted-fields-skeleton 1.4s ease infinite;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@keyframes payment-hosted-fields-skeleton {
|
|
27
|
+
0% { background-position: 100% 50%; }
|
|
28
|
+
100% { background-position: 0 50%; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.payment-hosted-fields__error {
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: 16px;
|
|
34
|
+
text-align: center;
|
|
35
|
+
font: var(--body);
|
|
36
|
+
color: var(--negative-text, #d32f2f);
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PaymentOption, BuyCartResponse } from '../../modules/Api/types';
|
|
2
|
+
|
|
3
|
+
export interface Props {
|
|
4
|
+
/** Opção de pagamento selecionada (ex: item retornado por `api.cart.listPayments`). */
|
|
5
|
+
option: PaymentOption;
|
|
6
|
+
/** Cor hex do botão CTA do iframe (com ou sem `#`). Se omitida, o iframe usa a cor padrão do render.uappi. */
|
|
7
|
+
ctaColor?: string;
|
|
8
|
+
/** Locale do formulário (`pt-BR`, `en-US`, `es-ES`, ...). Padrão do iframe: `pt-BR`. */
|
|
9
|
+
lang?: string;
|
|
10
|
+
/** Código Alpha-3 para pré-selecionar o DDI do telefone (`BRA`, `USA`, ...). Padrão do iframe: `BRA`. */
|
|
11
|
+
countryCode?: string;
|
|
12
|
+
/** Host do serviço render.uappi. Sobrescreve `VITE_PAYMENT_IFRAME_HOST` quando informado. */
|
|
13
|
+
iframeHost?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Emits {
|
|
17
|
+
(e: 'payment-ready'): void;
|
|
18
|
+
(e: 'payment-success', data: PaymentSuccessData): void;
|
|
19
|
+
(e: 'payment-redirect', url: string, data: PaymentRedirectData): void;
|
|
20
|
+
(e: 'payment-error', errorCode: string | undefined, message: string): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Dados do pedido criado, repassados pelo iframe em `UAPPI_PAYMENT_SUCCESS`.
|
|
25
|
+
* Mesmo formato retornado por `api.cart.buy` (`POST /cart/buy`).
|
|
26
|
+
*/
|
|
27
|
+
export type PaymentSuccessData = BuyCartResponse;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Dados que acompanham um `UAPPI_PAYMENT_REDIRECT`. O contrato exato é definido
|
|
31
|
+
* pelo serviço render.uappi e pode variar por gateway, por isso não é tipado em detalhe.
|
|
32
|
+
*/
|
|
33
|
+
export type PaymentRedirectData = Record<string, unknown>;
|
package/components/index.ts
CHANGED
package/modules/Api/README.md
CHANGED
|
@@ -77,6 +77,8 @@ O pacote expõe os seguintes escopos, cada um agrupando recursos relacionados:
|
|
|
77
77
|
- deleteProductComment: Remove um comentário no produto
|
|
78
78
|
- createProductComment: Adiciona um comentário no produto
|
|
79
79
|
- listProductComments: Lista comentários de produto
|
|
80
|
+
- getCategoryTree: Retorna a árvore de uma categoria (atual, filhas, irmãs e pai)
|
|
81
|
+
- listCategoriesTree: Retorna a árvore completa de categorias ativas
|
|
80
82
|
|
|
81
83
|
- [**promotional**](#promotional): Ofertas, cupons, carteiras e pontos promocionais.
|
|
82
84
|
- listPairOffers: Lista ofertas em pares de um produto (compre junto)
|
|
@@ -422,6 +424,14 @@ await api.product.listProductComments(
|
|
|
422
424
|
'condition' | 'order' | 'limit' | 'offset' | 'fields'
|
|
423
425
|
>
|
|
424
426
|
): ProductCommentsResponse;
|
|
427
|
+
|
|
428
|
+
// Retorna a árvore de uma categoria: categoria atual, filhas, irmãs e pai.
|
|
429
|
+
await api.product.getCategoryTree(
|
|
430
|
+
categoryId: number
|
|
431
|
+
): CategoryTree;
|
|
432
|
+
|
|
433
|
+
// Retorna a árvore completa de categorias ativas, com todos os níveis aninhados.
|
|
434
|
+
await api.product.listCategoriesTree(): Array<CategoryTreeItem>;
|
|
425
435
|
```
|
|
426
436
|
|
|
427
437
|
|
|
@@ -10,6 +10,8 @@ import type {
|
|
|
10
10
|
PaymentResponse,
|
|
11
11
|
BuyCartRequest,
|
|
12
12
|
BuyCartResponse,
|
|
13
|
+
PaymentTokenCartRequest,
|
|
14
|
+
PaymentTokenCartResponse,
|
|
13
15
|
} from '../types';
|
|
14
16
|
|
|
15
17
|
export function listDetail() {
|
|
@@ -111,6 +113,14 @@ export function buy(payload: BuyCartRequest) {
|
|
|
111
113
|
});
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
export function paymentToken(payload: PaymentTokenCartRequest) {
|
|
117
|
+
return usePublicApi<PaymentTokenCartResponse>({
|
|
118
|
+
endpoint: '/cart/payment-token',
|
|
119
|
+
method: 'POST',
|
|
120
|
+
payload,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
114
124
|
|
|
115
125
|
export function recovery(recoveryCode: string) {
|
|
116
126
|
const response = usePublicApi<CartResponse>({
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { usePublicApi } from '..';
|
|
2
|
+
|
|
3
|
+
import type { CategoryTree, CategoryTreeItem } from '../types';
|
|
4
|
+
|
|
5
|
+
export function getCategoryTree(categoryId: number) {
|
|
6
|
+
return usePublicApi<CategoryTree>({
|
|
7
|
+
endpoint: `/publics/products/categories/${categoryId}/tree`,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function listCategoriesTree() {
|
|
12
|
+
return usePublicApi<Array<CategoryTreeItem>>({
|
|
13
|
+
endpoint: '/publics/products/categories/tree',
|
|
14
|
+
});
|
|
15
|
+
}
|
package/modules/Api/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { listBanners } from './endpoints/BannerComposable';
|
|
2
2
|
import { listBrands } from './endpoints/BrandComposable';
|
|
3
|
+
import { getCategoryTree, listCategoriesTree } from './endpoints/CategoryComposable';
|
|
3
4
|
import {
|
|
4
5
|
addProduct,
|
|
5
6
|
deleteProduct,
|
|
@@ -14,7 +15,8 @@ import {
|
|
|
14
15
|
buy,
|
|
15
16
|
applyPoint,
|
|
16
17
|
deletePoint,
|
|
17
|
-
recovery
|
|
18
|
+
recovery,
|
|
19
|
+
paymentToken
|
|
18
20
|
} from './endpoints/CartComposable';
|
|
19
21
|
import {
|
|
20
22
|
listContries,
|
|
@@ -155,7 +157,8 @@ const cart = {
|
|
|
155
157
|
buy,
|
|
156
158
|
paymentConditions,
|
|
157
159
|
upsells,
|
|
158
|
-
recovery
|
|
160
|
+
recovery,
|
|
161
|
+
paymentToken
|
|
159
162
|
};
|
|
160
163
|
|
|
161
164
|
const product = {
|
|
@@ -168,6 +171,8 @@ const product = {
|
|
|
168
171
|
deleteProductComment,
|
|
169
172
|
createProductComment,
|
|
170
173
|
listProductComments,
|
|
174
|
+
getCategoryTree,
|
|
175
|
+
listCategoriesTree,
|
|
171
176
|
};
|
|
172
177
|
|
|
173
178
|
const common = {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface CategoryTreeNode {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CategoryTree {
|
|
8
|
+
current: CategoryTreeNode;
|
|
9
|
+
children: Array<CategoryTreeNode>;
|
|
10
|
+
sisters: Array<CategoryTreeNode>;
|
|
11
|
+
parent: Array<CategoryTreeNode>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CategoryTreeItem extends CategoryTreeNode {
|
|
15
|
+
children: Array<CategoryTreeItem>;
|
|
16
|
+
}
|
|
@@ -40,11 +40,18 @@ import type {
|
|
|
40
40
|
AddressToCartResponse,
|
|
41
41
|
BuyCartRequest,
|
|
42
42
|
BuyCartResponse,
|
|
43
|
+
PaymentTokenCartRequest,
|
|
44
|
+
PaymentTokenCartResponse,
|
|
43
45
|
} from './endpoints/cart/cart.interfaces';
|
|
44
46
|
import type {
|
|
45
47
|
AddressToCartType,
|
|
46
48
|
CartErrorCode,
|
|
47
49
|
} from './endpoints/cart/cart.types';
|
|
50
|
+
import type {
|
|
51
|
+
CategoryTree,
|
|
52
|
+
CategoryTreeItem,
|
|
53
|
+
CategoryTreeNode,
|
|
54
|
+
} from './endpoints/category/category.interfaces';
|
|
48
55
|
import type { CountryResponse } from './endpoints/common/common.interfaces';
|
|
49
56
|
import type { ContextType } from './endpoints/context/context.types';
|
|
50
57
|
import type {
|
|
@@ -314,6 +321,11 @@ export type {
|
|
|
314
321
|
BrandConditions,
|
|
315
322
|
BrandOrders,
|
|
316
323
|
|
|
324
|
+
// Categorias
|
|
325
|
+
CategoryTree,
|
|
326
|
+
CategoryTreeItem,
|
|
327
|
+
CategoryTreeNode,
|
|
328
|
+
|
|
317
329
|
// Pagination
|
|
318
330
|
Pagination,
|
|
319
331
|
|
|
@@ -409,6 +421,8 @@ export type {
|
|
|
409
421
|
AddressToCartResponse,
|
|
410
422
|
BuyCartRequest,
|
|
411
423
|
BuyCartResponse,
|
|
424
|
+
PaymentTokenCartRequest,
|
|
425
|
+
PaymentTokenCartResponse,
|
|
412
426
|
|
|
413
427
|
// Payment
|
|
414
428
|
ConfigurationCode,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uappi/public-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Pacotes públicos para desenvolvimento do ecossistema Uappi",
|
|
5
5
|
"main": "main.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
"type-check": "tsc --noEmit",
|
|
9
9
|
"prepush": "npm run lint && npm run type-check",
|
|
10
10
|
"prepublishOnly": "npm run lint && npm run type-check",
|
|
11
|
-
"prepare": "husky"
|
|
12
|
-
"publish": "npm publish --access public"
|
|
11
|
+
"prepare": "husky"
|
|
13
12
|
},
|
|
14
13
|
"repository": {
|
|
15
14
|
"type": "git",
|