@uappi/public-sdk 1.3.0 → 1.4.1

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.
@@ -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
+ // `PaymentHosted` 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('[PaymentHosted] 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('[PaymentHosted] 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 PaymentHosted } from './PaymentHosted.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>;
@@ -0,0 +1,163 @@
1
+ # ../../components
2
+
3
+ Componentes Vue 3 + TypeScript prontos para uso nas lojas do ecossistema Uappi.
4
+
5
+ ## Instalação
6
+
7
+ Adicione ao seu projeto:
8
+
9
+ ```bash
10
+ npm install @uappi/public-sdk
11
+ ```
12
+
13
+ ## Componentes Disponíveis
14
+
15
+ - [**PaymentHosted**](#PaymentHosted): Formulário de pagamento hospedado via iframe isolado (render.uappi).
16
+ - [**ColorTheme**](#colortheme): Aplica variáveis CSS de cor e fonte customizadas da loja no `document.documentElement`.
17
+ - [**SocialAuth**](#socialauth): Botões de login social (`GoogleAuth`, `FacebookAuth`) via OAuth em popup.
18
+ - [**Animations**](#animations): Utilitários de transição (`ExpandTransition`).
19
+
20
+ ---
21
+
22
+ ## [PaymentHosted](#PaymentHosted)
23
+
24
+ Renderiza o formulário de pagamento (cartão, etc.) dentro de um `<iframe>` isolado, servido pelo serviço `render.uappi`. Dados sensíveis (número do cartão, CVV, etc.) trafegam diretamente entre o iframe e o gateway, sem passar pela loja.
25
+
26
+ O componente busca um token de pagamento via `api.cart.paymentToken` ao montar, monta a URL do iframe com esse token, e escuta `postMessage` do iframe para emitir eventos de pagamento. Mensagens de janelas que não sejam o `contentWindow` do próprio iframe são ignoradas.
27
+
28
+ ```vue
29
+ <script setup lang="ts">
30
+ import { PaymentHosted } from '@uappi/public-sdk';
31
+ import type { PaymentSuccessData, PaymentRedirectData } from '@uappi/public-sdk';
32
+
33
+ function onSuccess(data: PaymentSuccessData) {
34
+ // pedido criado, mesmo formato de api.cart.buy
35
+ }
36
+
37
+ function onRedirect(url: string, data: PaymentRedirectData) {
38
+ // gateway pediu redirecionamento (ex: 3DS)
39
+ }
40
+
41
+ function onError(errorCode: string | undefined, message: string) {
42
+ // falha reportada pelo render.uappi
43
+ }
44
+ </script>
45
+
46
+ <template>
47
+ <PaymentHosted
48
+ :option="selectedPaymentOption"
49
+ cta-color="#FF0000"
50
+ lang="pt-BR"
51
+ country-code="BRA"
52
+ @payment-ready="onReady"
53
+ @payment-success="onSuccess"
54
+ @payment-redirect="onRedirect"
55
+ @payment-error="onError"
56
+ />
57
+ </template>
58
+ ```
59
+
60
+ ### Props
61
+
62
+ | Prop | Tipo | Obrigatório | Descrição |
63
+ |---|---|---|---|
64
+ | `option` | `PaymentOption` | Sim | Opção de pagamento selecionada (item retornado por `api.cart.listPayments`). |
65
+ | `ctaColor` | `string` | Não | Cor hex do botão CTA do iframe (com ou sem `#`). Padrão: cor do render.uappi. |
66
+ | `lang` | `string` | Não | Locale do formulário (`pt-BR`, `en-US`, `es-ES`, ...). Padrão: `pt-BR`. |
67
+ | `countryCode` | `string` | Não | Código Alpha-3 para pré-selecionar o DDI do telefone (`BRA`, `USA`, ...). Padrão: `BRA`. |
68
+ | `iframeHost` | `string` | Não | Host do serviço render.uappi. Sobrescreve `VITE_PAYMENT_IFRAME_HOST` quando informado. |
69
+
70
+ ### Eventos
71
+
72
+ | Evento | Payload | Quando é emitido |
73
+ |---|---|---|
74
+ | `payment-ready` | — | Iframe carregou e está pronto para interação. |
75
+ | `payment-success` | `data: PaymentSuccessData` | Pagamento concluído; `data` tem o mesmo formato de `api.cart.buy`. |
76
+ | `payment-redirect` | `url: string, data: PaymentRedirectData` | Gateway exige redirecionamento (ex: autenticação 3DS). |
77
+ | `payment-error` | `errorCode: string \| undefined, message: string` | Falha ao processar o pagamento. |
78
+
79
+ ---
80
+
81
+ ## [ColorTheme](#colortheme)
82
+
83
+ Aplica cores e fonte customizadas da loja como variáveis CSS globais (`--primary-cta-text`, `--checkout-cta-default`, `--font-family`, etc.) assim que é montado. Não renderiza elementos próprios — repassa o slot padrão via `Fragment`.
84
+
85
+ ```vue
86
+ <script setup lang="ts">
87
+ import { ColorTheme } from '@uappi/public-sdk';
88
+ import type { CSSVariables } from '@uappi/public-sdk';
89
+
90
+ const variables: CSSVariables = {
91
+ font: { family: 'Inter', link: 'https://fonts.googleapis.com/css2?family=Inter' },
92
+ colors: {
93
+ CheckoutCTADefault: '#000000',
94
+ CheckoutCTAHover: '#333333',
95
+ CheckoutCTAText: '#FFFFFF',
96
+ PrimaryCTAText: '#FFFFFF',
97
+ PrimaryInteractiveDefault: '#000000',
98
+ PrimaryInteractiveHover: '#333333',
99
+ PrimaryInteractiveHoverLow: '#666666',
100
+ PrimarySurfaceDisabled: '#CCCCCC',
101
+ PrimarySurfaceLow: '#F5F5F5',
102
+ PrimaryTextDecorative: '#000000',
103
+ },
104
+ };
105
+ </script>
106
+
107
+ <template>
108
+ <ColorTheme :variables="variables">
109
+ <App />
110
+ </ColorTheme>
111
+ </template>
112
+ ```
113
+
114
+ ---
115
+
116
+ ## [SocialAuth](#socialauth)
117
+
118
+ - `GoogleAuth` / `FacebookAuth`: botão de login social que abre um popup OAuth via `api.thirdParty.oauthRedirect`.
119
+
120
+ ```vue
121
+ <script setup lang="ts">
122
+ import { GoogleAuth, FacebookAuth } from '@uappi/public-sdk';
123
+ </script>
124
+
125
+ <template>
126
+ <GoogleAuth uri="/conta" />
127
+ <FacebookAuth uri="/conta" />
128
+ </template>
129
+ ```
130
+
131
+ | Prop | Tipo | Obrigatório | Descrição |
132
+ |---|---|---|---|
133
+ | `uri` | `string` | Sim | Prefixo de rota da loja usado para montar as URLs de callback (`{uri}/signin`, `{uri}/signup`). |
134
+
135
+ ---
136
+
137
+ ## [Animations](#animations)
138
+
139
+ - `ExpandTransition`: wrapper sobre `<Transition>` do Vue que anima altura e opacidade (expandir/colapsar), útil para acordeões e listas condicionais.
140
+
141
+ ```vue
142
+ <script setup lang="ts">
143
+ import { ExpandTransition } from '@uappi/public-sdk';
144
+ </script>
145
+
146
+ <template>
147
+ <ExpandTransition>
148
+ <div v-if="open">Conteúdo</div>
149
+ </ExpandTransition>
150
+ </template>
151
+ ```
152
+
153
+ | Prop | Tipo | Obrigatório | Descrição |
154
+ |---|---|---|---|
155
+ | `mode` | `BaseTransitionProps['mode']` | Não | Modo de transição do Vue (`in-out`, `out-in`, `default`). Padrão: `out-in`. |
156
+
157
+ ## Contribuição
158
+
159
+ Contribuições são bem-vindas! Abra uma issue ou envie um pull request.
160
+
161
+ ## Licença
162
+
163
+ ISC
@@ -1,3 +1,4 @@
1
1
  export * from './Animations';
2
2
  export * from './ColorTheme';
3
+ export * from './PaymentHosted';
3
4
  export * from './SocialAuth';
@@ -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>({
@@ -15,7 +15,8 @@ import {
15
15
  buy,
16
16
  applyPoint,
17
17
  deletePoint,
18
- recovery
18
+ recovery,
19
+ paymentToken
19
20
  } from './endpoints/CartComposable';
20
21
  import {
21
22
  listContries,
@@ -156,7 +157,8 @@ const cart = {
156
157
  buy,
157
158
  paymentConditions,
158
159
  upsells,
159
- recovery
160
+ recovery,
161
+ paymentToken
160
162
  };
161
163
 
162
164
  const product = {
@@ -159,3 +159,11 @@ export interface BuyCartResponse {
159
159
  userId: null;
160
160
  userName: null;
161
161
  }
162
+
163
+ export interface PaymentTokenCartRequest {
164
+ paymentOptionId: number;
165
+ }
166
+
167
+ export interface PaymentTokenCartResponse {
168
+ token: string;
169
+ }
@@ -40,6 +40,8 @@ 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,
@@ -419,6 +421,8 @@ export type {
419
421
  AddressToCartResponse,
420
422
  BuyCartRequest,
421
423
  BuyCartResponse,
424
+ PaymentTokenCartRequest,
425
+ PaymentTokenCartResponse,
422
426
 
423
427
  // Payment
424
428
  ConfigurationCode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uappi/public-sdk",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Pacotes públicos para desenvolvimento do ecossistema Uappi",
5
5
  "main": "main.ts",
6
6
  "scripts": {