Skip to content

Commit 3fd5c9a

Browse files
committed
feat: ebay
1 parent d3615b5 commit 3fd5c9a

7 files changed

Lines changed: 1575 additions & 4 deletions

File tree

api/services/ebay_onboarding_service.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,18 @@ async def _create_fulfillment_policy_fr(token: str, *, app: AppSettings | None =
130130
async def _create_payment_policy_fr(token: str, *, app: AppSettings | None = None) -> str:
131131
s = app or get_settings()
132132
root = _api_base_url(s)
133+
# EBAY_FR exige ``brands`` dès que ``paymentMethodType`` vaut CREDIT_CARD (sinon 20401).
133134
payload: dict[str, Any] = {
134135
"name": "GoupixDex — Paiement",
135136
"marketplaceId": MARKETPLACE_FR,
136137
"categoryTypes": [{"name": "ALL_EXCLUDING_MOTORS_VEHICLES"}],
137138
"immediatePay": True,
138-
"paymentMethods": [{"paymentMethodType": "CREDIT_CARD"}],
139+
"paymentMethods": [
140+
{
141+
"paymentMethodType": "CREDIT_CARD",
142+
"brands": ["VISA", "MASTERCARD", "AMERICAN_EXPRESS", "DISCOVER"],
143+
}
144+
],
139145
}
140146
url = f"{root}/sell/account/v1/payment_policy"
141147
async with httpx.AsyncClient(timeout=120.0) as client:

web/app/components/PhoneInput.vue

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<script setup lang="ts">
2+
/**
3+
* Champ téléphone international (doc Nuxt UI : UFieldGroup + USelectMenu + UInput + maska).
4+
* Émet un numéro au format E.164 (`+33612345678`).
5+
*/
6+
import { vMaska } from 'maska/vue'
7+
8+
import type { PhoneCode } from '~/types/phone-code'
9+
10+
const props = withDefaults(
11+
defineProps<{
12+
modelValue: string
13+
defaultCountryCode?: string
14+
name?: string
15+
id?: string
16+
disabled?: boolean
17+
fixed?: boolean
18+
autocomplete?: string
19+
}>(),
20+
{
21+
defaultCountryCode: 'FR',
22+
fixed: true,
23+
autocomplete: 'tel-national'
24+
}
25+
)
26+
27+
const emit = defineEmits<{
28+
'update:modelValue': [value: string]
29+
}>()
30+
31+
const countryCode = ref(props.defaultCountryCode)
32+
const phone = ref('')
33+
const syncing = ref(false)
34+
35+
const { data: phoneCodes, status, execute } = useLazyFetch<PhoneCode[]>('/api/phone-codes.json', {
36+
key: 'api-phone-codes',
37+
immediate: false
38+
})
39+
40+
const country = computed(() => phoneCodes.value?.find(c => c.code === countryCode.value))
41+
const dialCode = computed(() => country.value?.dialCode || '+33')
42+
const mask = computed(() => country.value?.mask || '# ## ## ## ##')
43+
44+
function onOpen(open: boolean) {
45+
if (open && !phoneCodes.value?.length) {
46+
execute()
47+
}
48+
}
49+
50+
watch(countryCode, () => {
51+
phone.value = ''
52+
})
53+
54+
function buildE164(): string {
55+
const cc = dialCode.value.replace(/\D/g, '')
56+
const national = phone.value.replace(/\D/g, '')
57+
if (!cc || !national) {
58+
return ''
59+
}
60+
return `+${cc}${national}`
61+
}
62+
63+
function hydrateFromE164(value: string) {
64+
const list = phoneCodes.value
65+
if (!list?.length) {
66+
return
67+
}
68+
syncing.value = true
69+
try {
70+
if (!value?.trim()) {
71+
phone.value = ''
72+
return
73+
}
74+
const digits = value.replace(/\D/g, '')
75+
if (!digits) {
76+
return
77+
}
78+
const sorted = [...list].sort(
79+
(a, b) => b.dialCode.replace(/\D/g, '').length - a.dialCode.replace(/\D/g, '').length
80+
)
81+
for (const c of sorted) {
82+
const cc = c.dialCode.replace(/\D/g, '')
83+
if (digits.startsWith(cc)) {
84+
countryCode.value = c.code
85+
phone.value = digits.slice(cc.length)
86+
return
87+
}
88+
}
89+
} finally {
90+
nextTick(() => {
91+
syncing.value = false
92+
})
93+
}
94+
}
95+
96+
watch([countryCode, phone], () => {
97+
if (syncing.value) {
98+
return
99+
}
100+
const next = buildE164()
101+
if (next !== props.modelValue) {
102+
emit('update:modelValue', next)
103+
}
104+
})
105+
106+
watch(
107+
() => props.modelValue,
108+
(v) => {
109+
if (syncing.value || !phoneCodes.value?.length) {
110+
return
111+
}
112+
const cur = buildE164()
113+
if (v === cur) {
114+
return
115+
}
116+
hydrateFromE164(v || '')
117+
}
118+
)
119+
120+
onMounted(async () => {
121+
await execute()
122+
if (props.modelValue) {
123+
hydrateFromE164(props.modelValue)
124+
}
125+
})
126+
</script>
127+
128+
<template>
129+
<UFieldGroup class="w-full">
130+
<USelectMenu
131+
v-model="countryCode"
132+
:items="phoneCodes ?? []"
133+
value-key="code"
134+
:disabled="disabled"
135+
:search-input="{
136+
placeholder: 'Rechercher un pays…',
137+
icon: 'i-lucide-search',
138+
loading: status === 'pending'
139+
}"
140+
:filter-fields="['name', 'code', 'dialCode']"
141+
:content="{ align: 'start' }"
142+
:ui="{
143+
base: 'pe-8',
144+
content: 'w-48',
145+
placeholder: 'hidden',
146+
trailingIcon: 'size-4'
147+
}"
148+
trailing-icon="i-lucide-chevrons-up-down"
149+
@update:open="onOpen"
150+
>
151+
<span class="size-5 flex items-center text-lg">
152+
{{ country?.emoji || '🇫🇷' }}
153+
</span>
154+
155+
<template #item-leading="{ item }">
156+
<span class="size-5 flex items-center text-lg">
157+
{{ item.emoji }}
158+
</span>
159+
</template>
160+
161+
<template #item-label="{ item }">
162+
{{ item.name }} ({{ item.dialCode }})
163+
</template>
164+
</USelectMenu>
165+
166+
<UInput
167+
:id="id"
168+
v-model="phone"
169+
v-maska="mask"
170+
type="tel"
171+
inputmode="tel"
172+
:name="name"
173+
:disabled="disabled"
174+
:fixed="fixed"
175+
:autocomplete="autocomplete"
176+
:placeholder="mask.replaceAll('#', '_')"
177+
:style="{ '--dial-code-length': `${dialCode.length + 1.5}ch` }"
178+
class="min-w-0 flex-1"
179+
:ui="{
180+
base: 'ps-(--dial-code-length)',
181+
leading: 'pointer-events-none text-base md:text-sm text-muted'
182+
}"
183+
>
184+
<template #leading>
185+
{{ dialCode }}
186+
</template>
187+
</UInput>
188+
</UFieldGroup>
189+
</template>

web/app/pages/settings/marketplaces.vue

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ const onboardingLoading = ref(false)
1818
const s = ref<Awaited<ReturnType<typeof getSettings>> | null>(null)
1919
2020
const locationName = ref('Domicile')
21+
/** E.164 (ex. +33642193812), alimenté par le composant PhoneInput. */
2122
const phone = ref('')
23+
2224
const addressLine1 = ref('')
2325
const addressLine2 = ref('')
2426
const city = ref('')
@@ -92,7 +94,13 @@ async function startEbayOAuth() {
9294
9395
async function submitOnboarding() {
9496
if (!phone.value.trim() || !addressLine1.value.trim() || !city.value.trim() || !postalCode.value.trim()) {
95-
toast.add({ title: 'Champs requis', description: 'Renseignez l’adresse et un téléphone.', color: 'warning' })
97+
toast.add({
98+
title: 'Champs requis',
99+
description: phone.value.trim()
100+
? 'Renseignez l’adresse et le code postal.'
101+
: 'Indiquez un numéro de téléphone mobile valide (avec indicatif pays).',
102+
color: 'warning'
103+
})
96104
return
97105
}
98106
onboardingLoading.value = true
@@ -256,8 +264,13 @@ onMounted(async () => {
256264
<UFormField label="Nom du lieu (ex. Domicile)">
257265
<UInput v-model="locationName" class="w-full" />
258266
</UFormField>
259-
<UFormField label="Téléphone (indicatif +33…)" required>
260-
<UInput v-model="phone" type="tel" class="w-full" placeholder="+33…" />
267+
<UFormField
268+
label="Téléphone mobile"
269+
required
270+
class="min-w-0 sm:col-span-2"
271+
description="Indicatif pays + numéro. Le clavier numérique s’ouvre sur mobile."
272+
>
273+
<PhoneInput v-model="phone" name="phone" default-country-code="FR" class="w-full" />
261274
</UFormField>
262275
<UFormField label="Adresse ligne 1" class="sm:col-span-2" required>
263276
<UInput v-model="addressLine1" class="w-full" />

web/package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"axios": "^1.15.0",
3232
"date-fns": "^4.1.0",
3333
"gsap": "^3.15.0",
34+
"maska": "^3.2.0",
3435
"nuxt": "^4.4.2",
3536
"scule": "^1.3.0",
3637
"tailwindcss": "^4.2.2",

0 commit comments

Comments
 (0)