feat(ecommerce): ADR 0009 - checkout con direccion y seleccion de envio

Siguiente corte tras el carrito real: pagina /checkout de 3 pasos
(direccion -> envio -> confirmar) contra el Store API real, usando la
shipping option que ya se verifico en produccion. Endpoints nuevos
(setShippingAddress, getShippingOptions, addShippingMethod) probados
en vivo con un carrito de prueba real antes de escribir el codigo --
direccion, opciones de envio y total ya confirmados correctos
(80.000 + 5.000 = 95.000).

El pago (PayU custom) sigue sin existir, asi que el ultimo paso sigue
siendo WhatsApp -- pero ahora el mensaje incluye direccion, metodo de
envio y total real en vez de solo la lista de productos. El boton del
CartDrawer pasa de abrir WhatsApp directo a llevar a /checkout.
This commit is contained in:
devops committed 2026-08-29 04:12:11 +00:00
1 parent cdab538cfb
commit bb2c0bbe85
7 files changed
+310 -8

No files matched your search

@@ -33,9 +33,27 @@ export interface CartSummary {
cartId: string;
currency: string;
subtotal: number;
shippingTotal: number;
total: number;
items: CartLine[];
}
export interface ShippingAddressInput {
firstName: string;
lastName: string;
address1: string;
city: string;
province?: string;
phone: string;
email: string;
}
export interface ShippingOption {
id: string;
name: string;
amount: number;
}
function backendUrl(): string {
const value = process.env.MEDUSA_BACKEND_URL;
if (!value) {
@@ -68,6 +86,8 @@ type MedusaCart = {
id: string;
currency_code: string;
subtotal?: number | null;
shipping_total?: number | null;
total?: number | null;
items?: MedusaLineItem[] | null;
};
@@ -76,6 +96,8 @@ function mapCart(cart: MedusaCart): CartSummary {
cartId: cart.id,
currency: cart.currency_code,
subtotal: cart.subtotal ?? 0,
shippingTotal: cart.shipping_total ?? 0,
total: cart.total ?? cart.subtotal ?? 0,
items: (cart.items ?? []).map((item) => ({
lineItemId: item.id,
variantId: item.variant_id,
@@ -171,3 +193,50 @@ export async function removeLineItem(cartId: string, lineItemId: string): Promis
// "parent", no en "cart" -- verificado en vivo, no documentado.
return mapCart(data.parent as MedusaCart);
}
export async function setShippingAddress(
cartId: string,
address: ShippingAddressInput,
): Promise<CartSummary> {
const data = await medusaCartFetch(`/store/carts/${cartId}?${ITEMS_FIELDS}`, {
method: "POST",
body: {
email: address.email,
shipping_address: {
first_name: address.firstName,
last_name: address.lastName,
address_1: address.address1,
city: address.city,
province: address.province || undefined,
country_code: "co",
phone: address.phone,
},
},
});
return mapCart(data.cart as MedusaCart);
}
type MedusaShippingOption = {
id: string;
name: string;
amount?: number | null;
calculated_price?: { calculated_amount?: number | null } | null;
};
export async function getShippingOptions(cartId: string): Promise<ShippingOption[]> {
const data = await medusaCartFetch(`/store/shipping-options?cart_id=${cartId}`);
const options = (data.shipping_options as MedusaShippingOption[] | undefined) ?? [];
return options.map((option) => ({
id: option.id,
name: option.name,
amount: option.calculated_price?.calculated_amount ?? option.amount ?? 0,
}));
}
export async function addShippingMethod(cartId: string, optionId: string): Promise<CartSummary> {
const data = await medusaCartFetch(`/store/carts/${cartId}/shipping-methods?${ITEMS_FIELDS}`, {
method: "POST",
body: { option_id: optionId },
});
return mapCart(data.cart as MedusaCart);
}