Simplify checkout flow and payment options
- Combine shipping and contact info into single step - Remove BTC Onchain and Cashu payment options (Lightning only for now) - Add collapsible section for optional contact info (email, npub, address) - Keep shipping address visible when required for physical delivery - Keep message to merchant visible by default - Add TODO comment for future payment method expansion 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
cdf09ab6e3
commit
b89ee8bf92
1 changed files with 171 additions and 169 deletions
|
|
@ -46,8 +46,8 @@
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<!-- Cart Items -->
|
<!-- Cart Items -->
|
||||||
<div class="space-y-4 mb-6">
|
<div class="space-y-4 mb-6">
|
||||||
<div
|
<div
|
||||||
v-for="item in checkoutCart.products"
|
v-for="item in checkoutCart.products"
|
||||||
:key="item.product.id"
|
:key="item.product.id"
|
||||||
class="flex items-center justify-between p-4 border border-border rounded-lg"
|
class="flex items-center justify-between p-4 border border-border rounded-lg"
|
||||||
>
|
>
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
<Package class="w-8 h-8 text-muted-foreground" />
|
<Package class="w-8 h-8 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Product Details -->
|
<!-- Product Details -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-semibold text-foreground">{{ item.product.name }}</h3>
|
<h3 class="font-semibold text-foreground">{{ item.product.name }}</h3>
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
<p class="text-sm text-muted-foreground">Quantity: {{ item.quantity }}</p>
|
<p class="text-sm text-muted-foreground">Quantity: {{ item.quantity }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Item Total -->
|
<!-- Item Total -->
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<p class="font-semibold text-foreground">
|
<p class="font-semibold text-foreground">
|
||||||
|
|
@ -108,121 +108,76 @@
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Shipping Information -->
|
<!-- Shipping & Contact Information -->
|
||||||
<Card v-if="!orderConfirmed">
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Shipping Information</CardTitle>
|
<CardTitle>Shipping & Contact</CardTitle>
|
||||||
<CardDescription>Select your shipping zone</CardDescription>
|
<CardDescription>Select shipping and provide your contact details</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent class="space-y-6">
|
||||||
<!-- Shipping Zones -->
|
<!-- Shipping Zones -->
|
||||||
<div v-if="availableShippingZones.length > 0" class="space-y-3">
|
<div v-if="availableShippingZones.length > 0">
|
||||||
<div
|
<Label class="mb-3 block">Shipping Zone</Label>
|
||||||
v-for="zone in availableShippingZones"
|
<div class="space-y-3">
|
||||||
:key="zone.id"
|
<div
|
||||||
@click="selectShippingZone(zone)"
|
v-for="zone in availableShippingZones"
|
||||||
:class="[
|
:key="zone.id"
|
||||||
'p-4 border rounded-lg cursor-pointer transition-colors',
|
@click="selectShippingZone(zone)"
|
||||||
selectedShippingZone?.id === zone.id
|
:class="[
|
||||||
? 'border-primary bg-primary/10'
|
'p-4 border rounded-lg cursor-pointer transition-colors',
|
||||||
: 'border-border hover:border-primary/50'
|
selectedShippingZone?.id === zone.id
|
||||||
]"
|
? 'border-primary bg-primary/10'
|
||||||
>
|
: 'border-border hover:border-primary/50'
|
||||||
<div class="flex justify-between items-center">
|
]"
|
||||||
<div>
|
>
|
||||||
<h3 class="font-medium text-foreground">{{ zone.name }}</h3>
|
<div class="flex justify-between items-center">
|
||||||
<p class="text-sm text-muted-foreground">
|
<div>
|
||||||
{{ zone.countries?.join(', ') || 'Available' }}
|
<h3 class="font-medium text-foreground">{{ zone.name }}</h3>
|
||||||
<span v-if="!zone.requiresPhysicalShipping" class="ml-2 text-blue-600">
|
<p class="text-sm text-muted-foreground">
|
||||||
• No shipping required
|
{{ zone.countries?.join(', ') || 'Available' }}
|
||||||
</span>
|
<span v-if="!zone.requiresPhysicalShipping" class="ml-2 text-blue-600">
|
||||||
</p>
|
• No shipping required
|
||||||
<p v-if="zone.description" class="text-xs text-muted-foreground mt-1">
|
</span>
|
||||||
{{ zone.description }}
|
</p>
|
||||||
</p>
|
<p v-if="zone.description" class="text-xs text-muted-foreground mt-1">
|
||||||
</div>
|
{{ zone.description }}
|
||||||
<div class="text-right">
|
</p>
|
||||||
<p class="font-semibold text-foreground">
|
</div>
|
||||||
{{ formatPrice(zone.cost, checkoutCart.currency) }}
|
<div class="text-right">
|
||||||
</p>
|
<p class="font-semibold text-foreground">
|
||||||
<p class="text-xs text-muted-foreground">shipping</p>
|
{{ formatPrice(zone.cost, checkoutCart.currency) }}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-muted-foreground">shipping</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="text-center py-6">
|
<div v-else class="text-center py-4 bg-muted/50 rounded-lg">
|
||||||
<p class="text-muted-foreground">This merchant hasn't configured shipping zones yet.</p>
|
<p class="text-muted-foreground">This merchant hasn't configured shipping zones yet.</p>
|
||||||
<p class="text-sm text-muted-foreground">Please contact the merchant for shipping information.</p>
|
<p class="text-sm text-muted-foreground">Please contact the merchant for shipping information.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Confirm Order Button -->
|
<!-- Separator -->
|
||||||
<div class="mt-6">
|
<div class="border-t border-border" />
|
||||||
<Button
|
|
||||||
@click="confirmOrder"
|
|
||||||
:disabled="availableShippingZones.length > 0 && !selectedShippingZone"
|
|
||||||
class="w-full"
|
|
||||||
size="lg"
|
|
||||||
>
|
|
||||||
Confirm Order
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- Contact & Payment Information -->
|
<!-- Shipping Address (shown when required for physical delivery) -->
|
||||||
<Card v-if="orderConfirmed">
|
<div v-if="requiresShippingAddress">
|
||||||
<CardHeader>
|
<Label for="address">Shipping Address *</Label>
|
||||||
<CardTitle>Contact & Payment Information</CardTitle>
|
<Textarea
|
||||||
<CardDescription>Provide your details for order processing</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent class="space-y-6">
|
|
||||||
<!-- Contact Form -->
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<div>
|
|
||||||
<Label for="email">Email (optional)</Label>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
v-model="contactData.email"
|
|
||||||
type="email"
|
|
||||||
placeholder="your@email.com"
|
|
||||||
/>
|
|
||||||
<p class="text-xs text-muted-foreground mt-1">Merchant may not use email</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Label for="npub">Alternative Npub (optional)</Label>
|
|
||||||
<Input
|
|
||||||
id="npub"
|
|
||||||
v-model="contactData.npub"
|
|
||||||
placeholder="npub..."
|
|
||||||
/>
|
|
||||||
<p class="text-xs text-muted-foreground mt-1">Different Npub for communication</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Label for="address">
|
|
||||||
{{ selectedShippingZone?.requiresPhysicalShipping !== false ? 'Shipping Address' : 'Contact Address (optional)' }}
|
|
||||||
</Label>
|
|
||||||
<Textarea
|
|
||||||
id="address"
|
id="address"
|
||||||
v-model="contactData.address"
|
v-model="contactData.address"
|
||||||
:placeholder="selectedShippingZone?.requiresPhysicalShipping !== false
|
placeholder="Full shipping address..."
|
||||||
? 'Full shipping address...'
|
|
||||||
: 'Contact address (optional)...'"
|
|
||||||
rows="3"
|
rows="3"
|
||||||
/>
|
/>
|
||||||
<p class="text-xs text-muted-foreground mt-1">
|
<p class="text-xs text-muted-foreground mt-1">Required for physical delivery</p>
|
||||||
{{ selectedShippingZone?.requiresPhysicalShipping !== false
|
|
||||||
? 'Required for physical delivery'
|
|
||||||
: 'Optional for digital items or pickup' }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Message to Merchant (visible) -->
|
||||||
<div>
|
<div>
|
||||||
<Label for="message">Message to Merchant (optional)</Label>
|
<Label for="message">Message to Merchant (optional)</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="message"
|
id="message"
|
||||||
v-model="contactData.message"
|
v-model="contactData.message"
|
||||||
placeholder="Any special instructions or notes..."
|
placeholder="Any special instructions or notes..."
|
||||||
|
|
@ -230,40 +185,72 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Payment Method Selection -->
|
<!-- Optional Contact Info (collapsible) -->
|
||||||
<div>
|
<Collapsible v-model:open="showOptionalContact">
|
||||||
<Label>Payment Method</Label>
|
<CollapsibleTrigger class="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors cursor-pointer w-full">
|
||||||
<div class="flex gap-3 mt-2">
|
<ChevronDown
|
||||||
<div
|
class="w-4 h-4 transition-transform"
|
||||||
v-for="method in paymentMethods"
|
:class="{ 'rotate-180': showOptionalContact }"
|
||||||
:key="method.value"
|
/>
|
||||||
@click="paymentMethod = method.value"
|
<span>Additional contact info (optional)</span>
|
||||||
:class="[
|
</CollapsibleTrigger>
|
||||||
'p-3 border rounded-lg cursor-pointer text-center flex-1 transition-colors',
|
<CollapsibleContent class="pt-4 space-y-4">
|
||||||
paymentMethod === method.value
|
<!-- Contact Address (optional for digital/pickup) -->
|
||||||
? 'border-primary bg-primary/10'
|
<div v-if="!requiresShippingAddress">
|
||||||
: 'border-border hover:border-primary/50'
|
<Label for="address">Contact Address</Label>
|
||||||
]"
|
<Textarea
|
||||||
>
|
id="address"
|
||||||
<p class="font-medium">{{ method.label }}</p>
|
v-model="contactData.address"
|
||||||
|
placeholder="Contact address (optional)..."
|
||||||
|
rows="3"
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-muted-foreground mt-1">Optional for digital items or pickup</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label for="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
v-model="contactData.email"
|
||||||
|
type="email"
|
||||||
|
placeholder="your@email.com"
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-muted-foreground mt-1">Merchant may not use email</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label for="npub">Alternative Npub</Label>
|
||||||
|
<Input
|
||||||
|
id="npub"
|
||||||
|
v-model="contactData.npub"
|
||||||
|
placeholder="npub..."
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-muted-foreground mt-1">Different Npub for communication</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
|
||||||
|
<!-- Payment Method (Lightning only for now) -->
|
||||||
|
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
<span>⚡</span>
|
||||||
|
<span>Payment via Lightning Network</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Place Order Button -->
|
<!-- Place Order Button -->
|
||||||
<div class="pt-4 border-t border-border">
|
<div class="pt-4 border-t border-border">
|
||||||
<Button
|
<Button
|
||||||
@click="placeOrder"
|
@click="placeOrder"
|
||||||
:disabled="isPlacingOrder || (selectedShippingZone?.requiresPhysicalShipping !== false && !contactData.address)"
|
:disabled="isPlacingOrder || !canPlaceOrder"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
<span v-if="isPlacingOrder" class="animate-spin mr-2">⚡</span>
|
<span v-if="isPlacingOrder" class="animate-spin mr-2">⚡</span>
|
||||||
Place Order - {{ formatPrice(orderTotal, checkoutCart.currency) }}
|
Place Order - {{ formatPrice(orderTotal, checkoutCart.currency) }}
|
||||||
</Button>
|
</Button>
|
||||||
<p v-if="selectedShippingZone?.requiresPhysicalShipping !== false && !contactData.address"
|
<p v-if="!canPlaceOrder" class="text-xs text-destructive mt-2 text-center">
|
||||||
class="text-xs text-destructive mt-2 text-center">
|
{{ orderValidationMessage }}
|
||||||
Shipping address is required for physical delivery
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
@ -279,12 +266,12 @@ import { useRoute } from 'vue-router'
|
||||||
import { useMarketStore } from '@/modules/market/stores/market'
|
import { useMarketStore } from '@/modules/market/stores/market'
|
||||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||||
import { auth } from '@/composables/useAuthService'
|
import { auth } from '@/composables/useAuthService'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardContent
|
CardContent
|
||||||
} from '@/components/ui/card'
|
} from '@/components/ui/card'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
|
|
@ -293,8 +280,14 @@ import { Label } from '@/components/ui/label'
|
||||||
import ProgressiveImage from '@/components/ui/image/ProgressiveImage.vue'
|
import ProgressiveImage from '@/components/ui/image/ProgressiveImage.vue'
|
||||||
import {
|
import {
|
||||||
Package,
|
Package,
|
||||||
CheckCircle
|
CheckCircle,
|
||||||
|
ChevronDown
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
|
import {
|
||||||
|
Collapsible,
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleTrigger
|
||||||
|
} from '@/components/ui/collapsible'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const marketStore = useMarketStore()
|
const marketStore = useMarketStore()
|
||||||
|
|
@ -303,11 +296,10 @@ const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as any
|
||||||
// State
|
// State
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
const orderConfirmed = ref(false)
|
|
||||||
const orderPlaced = ref(false)
|
const orderPlaced = ref(false)
|
||||||
const isPlacingOrder = ref(false)
|
const isPlacingOrder = ref(false)
|
||||||
const selectedShippingZone = ref<any>(null)
|
const selectedShippingZone = ref<any>(null)
|
||||||
const paymentMethod = ref('ln')
|
const showOptionalContact = ref(false)
|
||||||
|
|
||||||
// Form data
|
// Form data
|
||||||
const contactData = ref({
|
const contactData = ref({
|
||||||
|
|
@ -317,12 +309,7 @@ const contactData = ref({
|
||||||
message: ''
|
message: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// Payment methods
|
// TODO: Add BTC Onchain and Cashu payment options in the future
|
||||||
const paymentMethods = [
|
|
||||||
{ label: 'Lightning Network', value: 'ln' },
|
|
||||||
{ label: 'BTC Onchain', value: 'btc' },
|
|
||||||
{ label: 'Cashu', value: 'cashu' }
|
|
||||||
]
|
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
const stallId = computed(() => route.params.stallId as string)
|
const stallId = computed(() => route.params.stallId as string)
|
||||||
|
|
@ -341,7 +328,7 @@ const currentStall = computed(() => {
|
||||||
|
|
||||||
const orderSubtotal = computed(() => {
|
const orderSubtotal = computed(() => {
|
||||||
if (!checkoutCart.value?.products) return 0
|
if (!checkoutCart.value?.products) return 0
|
||||||
return checkoutCart.value.products.reduce((total, item) =>
|
return checkoutCart.value.products.reduce((total, item) =>
|
||||||
total + (item.product.price * item.quantity), 0
|
total + (item.product.price * item.quantity), 0
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
@ -355,22 +342,22 @@ const orderTotal = computed(() => {
|
||||||
// Get shipping zones from the current stall
|
// Get shipping zones from the current stall
|
||||||
const availableShippingZones = computed(() => {
|
const availableShippingZones = computed(() => {
|
||||||
if (!currentStall.value) return []
|
if (!currentStall.value) return []
|
||||||
|
|
||||||
// Use standardized shipping property from domain model
|
// Use standardized shipping property from domain model
|
||||||
const zones = currentStall.value.shipping || []
|
const zones = currentStall.value.shipping || []
|
||||||
|
|
||||||
// Ensure zones have required properties and determine shipping requirements
|
// Ensure zones have required properties and determine shipping requirements
|
||||||
return zones.map(zone => {
|
return zones.map(zone => {
|
||||||
const zoneName = zone.name || 'Shipping Zone'
|
const zoneName = zone.name || 'Shipping Zone'
|
||||||
const lowerName = zoneName.toLowerCase()
|
const lowerName = zoneName.toLowerCase()
|
||||||
|
|
||||||
// Determine if this zone requires physical shipping
|
// Determine if this zone requires physical shipping
|
||||||
const requiresPhysicalShipping = zone.requiresPhysicalShipping !== false &&
|
const requiresPhysicalShipping = zone.requiresPhysicalShipping !== false &&
|
||||||
!lowerName.includes('digital') &&
|
!lowerName.includes('digital') &&
|
||||||
!lowerName.includes('pickup') &&
|
!lowerName.includes('pickup') &&
|
||||||
!lowerName.includes('download') &&
|
!lowerName.includes('download') &&
|
||||||
zone.cost > 0 // Free usually means digital or pickup
|
zone.cost > 0 // Free usually means digital or pickup
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: zone.id || zoneName.toLowerCase().replace(/\s+/g, '-'),
|
id: zone.id || zoneName.toLowerCase().replace(/\s+/g, '-'),
|
||||||
name: zoneName,
|
name: zoneName,
|
||||||
|
|
@ -383,26 +370,41 @@ const availableShippingZones = computed(() => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Determine if shipping address is required
|
||||||
|
const requiresShippingAddress = computed(() => {
|
||||||
|
return selectedShippingZone.value?.requiresPhysicalShipping !== false
|
||||||
|
})
|
||||||
|
|
||||||
|
// Validation for placing order
|
||||||
|
const canPlaceOrder = computed(() => {
|
||||||
|
// Must select shipping zone if zones are available
|
||||||
|
if (availableShippingZones.value.length > 0 && !selectedShippingZone.value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Must provide address if physical shipping is required
|
||||||
|
if (requiresShippingAddress.value && !contactData.value.address) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
const orderValidationMessage = computed(() => {
|
||||||
|
if (availableShippingZones.value.length > 0 && !selectedShippingZone.value) {
|
||||||
|
return 'Please select a shipping zone'
|
||||||
|
}
|
||||||
|
if (requiresShippingAddress.value && !contactData.value.address) {
|
||||||
|
return 'Shipping address is required for physical delivery'
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const selectShippingZone = (zone: any) => {
|
const selectShippingZone = (zone: any) => {
|
||||||
selectedShippingZone.value = zone
|
selectedShippingZone.value = zone
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmOrder = () => {
|
|
||||||
// Allow proceeding if no shipping zones are available (e.g., for digital goods)
|
|
||||||
if (availableShippingZones.value.length > 0 && !selectedShippingZone.value) {
|
|
||||||
error.value = 'Please select a shipping zone'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
orderConfirmed.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const placeOrder = async () => {
|
const placeOrder = async () => {
|
||||||
// Only require shipping address if selected zone requires physical shipping
|
if (!canPlaceOrder.value) {
|
||||||
const requiresShippingAddress = selectedShippingZone.value?.requiresPhysicalShipping !== false
|
|
||||||
|
|
||||||
if (requiresShippingAddress && !contactData.value.address) {
|
|
||||||
error.value = 'Shipping address is required for this delivery method'
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -426,14 +428,14 @@ const placeOrder = async () => {
|
||||||
hasPubkey: !!authService.user.value?.pubkey,
|
hasPubkey: !!authService.user.value?.pubkey,
|
||||||
nostrPubkey: authService.user.value?.pubkey
|
nostrPubkey: authService.user.value?.pubkey
|
||||||
})
|
})
|
||||||
|
|
||||||
// Try to get pubkey from main auth first, fallback to auth service
|
// Try to get pubkey from main auth first, fallback to auth service
|
||||||
const userPubkey = auth.currentUser.value?.pubkey || authService.user.value?.pubkey
|
const userPubkey = auth.currentUser.value?.pubkey || authService.user.value?.pubkey
|
||||||
|
|
||||||
if (!auth.isAuthenticated.value) {
|
if (!auth.isAuthenticated.value) {
|
||||||
throw new Error('You must be logged in to place an order')
|
throw new Error('You must be logged in to place an order')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userPubkey) {
|
if (!userPubkey) {
|
||||||
throw new Error('Nostr identity required: Please configure your Nostr public key in your profile settings to place orders.')
|
throw new Error('Nostr identity required: Please configure your Nostr public key in your profile settings to place orders.')
|
||||||
}
|
}
|
||||||
|
|
@ -464,7 +466,7 @@ const placeOrder = async () => {
|
||||||
currency: checkoutCart.value.currency,
|
currency: checkoutCart.value.currency,
|
||||||
requiresPhysicalShipping: false
|
requiresPhysicalShipping: false
|
||||||
},
|
},
|
||||||
paymentMethod: paymentMethod.value === 'ln' ? 'lightning' as const : 'btc_onchain' as const,
|
paymentMethod: 'lightning' as const,
|
||||||
subtotal: orderSubtotal.value,
|
subtotal: orderSubtotal.value,
|
||||||
shippingCost: selectedShippingZone.value?.cost || 0,
|
shippingCost: selectedShippingZone.value?.cost || 0,
|
||||||
total: orderTotal.value,
|
total: orderTotal.value,
|
||||||
|
|
@ -473,13 +475,13 @@ const placeOrder = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Creating order:', orderData)
|
console.log('Creating order:', orderData)
|
||||||
|
|
||||||
// Create and place the order via the market store
|
// Create and place the order via the market store
|
||||||
const order = await marketStore.createAndPlaceOrder(orderData)
|
const order = await marketStore.createAndPlaceOrder(orderData)
|
||||||
|
|
||||||
console.log('Order placed successfully:', order)
|
console.log('Order placed successfully:', order)
|
||||||
orderPlaced.value = true
|
orderPlaced.value = true
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to place order:', err)
|
console.error('Failed to place order:', err)
|
||||||
error.value = err instanceof Error ? err.message : 'Failed to place order'
|
error.value = err instanceof Error ? err.message : 'Failed to place order'
|
||||||
|
|
@ -505,12 +507,12 @@ onMounted(() => {
|
||||||
if (!cart || cart.id !== stallId.value) {
|
if (!cart || cart.id !== stallId.value) {
|
||||||
error.value = 'No checkout data found for this stall'
|
error.value = 'No checkout data found for this stall'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-select shipping zone if there's only one
|
// Auto-select shipping zone if there's only one
|
||||||
if (availableShippingZones.value.length === 1) {
|
if (availableShippingZones.value.length === 1) {
|
||||||
selectedShippingZone.value = availableShippingZones.value[0]
|
selectedShippingZone.value = availableShippingZones.value[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue