Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions amplify/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { defineBackend } from '@aws-amplify/backend';
import { CfnOutput, Stack } from 'aws-cdk-lib';
import { AuthorizationType, Cors, LambdaIntegration, MethodLoggingLevel, RestApi } from 'aws-cdk-lib/aws-apigateway';
import { Effect, Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import * as kms from 'aws-cdk-lib/aws-kms';
//import * as kms from 'aws-cdk-lib/aws-kms';
import { postConfirmation } from './auth/post-confirmation/resource';
import { auth } from './auth/resource';
import {
Expand Down Expand Up @@ -135,7 +135,7 @@ backend.generatePriceSuggestionFunction.resources.lambda.addToRolePolicy(
);

// Define la clave KMS para la encriptación de las claves de pago
const paymentKeysKmsKey = new kms.Key(backend.stack, 'PaymentKeysKmsKey', {
/*const paymentKeysKmsKey = new kms.Key(backend.stack, 'PaymentKeysKmsKey', {
description: 'KMS key for encrypting payment gateway keys',
enableKeyRotation: true, // Habilitar la rotación de claves para mayor seguridad
alias: `alias/FasttifyPaymentKeys-${stageName}`, // Alias amigable para referenciar la clave
Expand All @@ -154,7 +154,7 @@ backend.managePaymentKeys.resources.lambda.addToRolePolicy(
new CfnOutput(backend.stack, 'PaymentKeysKmsKeyArn', {
value: paymentKeysKmsKey.keyArn,
description: 'ARN of the KMS key for encrypting payment gateway keys',
});
});*/

backend.postConfirmation.resources.lambda.addToRolePolicy(
new PolicyStatement({
Expand Down
123 changes: 123 additions & 0 deletions amplify/data/models/checkout-session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { a } from '@aws-amplify/backend';

export const checkoutSessionModel = a
.model({
token: a
.string()
.required()
.authorization((allow) => [
allow.publicApiKey().to(['read', 'create']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
storeId: a
.string()
.required()
.authorization((allow) => [
allow.ownerDefinedIn('storeOwner').to(['create', 'read']),
allow.publicApiKey().to(['create', 'read']),
]),
cartId: a
.string()
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
sessionId: a
.string()
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
status: a.enum(['open', 'completed', 'expired', 'cancelled']),
expiresAt: a
.datetime()
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
currency: a
.string()
.default('COP')
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
subtotal: a
.float()
.default(0)
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
shippingCost: a
.float()
.default(0)
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read', 'update']),
allow.ownerDefinedIn('storeOwner').to(['read', 'update']),
]),
taxAmount: a
.float()
.default(0)
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read', 'update']),
allow.ownerDefinedIn('storeOwner').to(['read', 'update']),
]),
totalAmount: a
.float()
.default(0)
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read', 'update']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
itemsSnapshot: a
.json()
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
customerInfo: a
.json()
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read', 'update']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
shippingAddress: a
.json()
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read', 'update']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
billingAddress: a
.json()
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read', 'update']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
notes: a
.string()
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read', 'update']),
allow.ownerDefinedIn('storeOwner').to(['read']),
]),
storeOwner: a
.string()
.required()
.authorization((allow) => [
allow.ownerDefinedIn('storeOwner').to(['create', 'read']),
allow.publicApiKey().to(['create', 'read']),
]),
store: a.belongsTo('UserStore', 'storeId'),
})
.secondaryIndexes((index) => [
index('token'),
index('storeId'),
index('sessionId'),
index('status'),
index('storeOwner'),
index('expiresAt'),
])
.authorization((allow) => [
allow.publicApiKey().to(['create', 'read', 'update']),
allow.ownerDefinedIn('storeOwner').to(['create', 'read', 'update']),
]);
1 change: 1 addition & 0 deletions amplify/data/models/user-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const userStoreModel = a
storePaymentConfig: a.hasMany('StorePaymentConfig', 'storeId'),
storeCustomDomain: a.hasOne('StoreCustomDomain', 'storeId'),
userThemes: a.hasMany('UserTheme', 'storeId'),
checkoutSessions: a.hasMany('CheckoutSession', 'storeId'),
})
.identifier(['storeId'])
.secondaryIndexes((index) => [index('userId'), index('storeName'), index('defaultDomain')])
Expand Down
2 changes: 2 additions & 0 deletions amplify/data/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { webHookPlan } from '../functions/webHookPlan/resource';
// Importacion de modelos
import { cartModel } from './models/cart';
import { cartItemModel } from './models/cart-item';
import { checkoutSessionModel } from './models/checkout-session';
import { collectionModel } from './models/collection';
import { navigationMenuModel } from './models/navigation-menu';
import { orderModel } from './models/order';
Expand Down Expand Up @@ -116,6 +117,7 @@ const schema = a
Page: pageModel,
Cart: cartModel,
CartItem: cartItemModel,
CheckoutSession: checkoutSessionModel,
Order: orderModel,
OrderItem: orderItemModel,
StorePaymentConfig: storePaymentConfigModel,
Expand Down
1 change: 1 addition & 0 deletions app/(setup-layout)/first-steps/hooks/useUserStoreData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ export const useUserStoreData = () => {
| 'storePaymentConfig'
| 'storeCustomDomain'
| 'userThemes'
| 'checkoutSessions'
>
) => {
try {
Expand Down
3 changes: 0 additions & 3 deletions app/api/stores/[storeId]/cart/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ export async function GET(request: NextRequest, { params }: RouteContext) {
let sessionId = cookiesStore.get(SESSION_ID_COOKIE_NAME)?.value;
let newSessionIdGenerated = false;

// Log para debugging en producción
logger.info(`[Cart API] GET request - storeId: ${storeId}, sessionId: ${sessionId || 'NOT_FOUND'}`, null, 'CartAPI');

if (!sessionId) {
sessionId = uuidv4();
newSessionIdGenerated = true;
Expand Down
89 changes: 89 additions & 0 deletions app/api/stores/[storeId]/checkout/complete/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { getNextCorsHeaders } from '@/lib/utils/next-cors';
import { checkoutFetcher } from '@/renderer-engine/services/fetchers/checkout-fetcher';
import { NextRequest, NextResponse } from 'next/server';

export async function OPTIONS(request: NextRequest) {
const corsHeaders = await getNextCorsHeaders(request);
return new Response(null, { status: 204, headers: corsHeaders });
}

export async function POST(request: NextRequest, { params }: { params: Promise<{ storeId: string }> }) {
const corsHeaders = await getNextCorsHeaders(request);

// Obtener el host original de la tienda para las redirecciones
const storeHost =
request.headers.get('origin') ||
request.headers.get('referer')?.split('/')[0] + '//' + request.headers.get('referer')?.split('/')[2];

try {
const { storeId } = await params;
const formData = await request.formData();

// Obtener token del query string
const url = new URL(request.url);
const token = url.searchParams.get('token');

if (!token) {
return NextResponse.redirect(`${storeHost}/cart?error=invalid_token`, { status: 303, headers: corsHeaders });
}

// Extraer datos del formulario
const customerInfo = {
email: formData.get('customer[email]') as string,
firstName: formData.get('customer[firstName]') as string,
lastName: formData.get('customer[lastName]') as string,
phone: formData.get('customer[phone]') as string,
};

const shippingAddress = {
address1: formData.get('shipping_address[address1]') as string,
address2: formData.get('shipping_address[address2]') as string,
city: formData.get('shipping_address[city]') as string,
province: formData.get('shipping_address[state]') as string,
zip: formData.get('shipping_address[zip]') as string,
country: formData.get('shipping_address[country]') as string,
};

const notes = formData.get('notes') as string;

// Actualizar información del cliente
const updateResponse = await checkoutFetcher.updateCustomerInfo({
token,
customerInfo,
shippingAddress,
billingAddress: shippingAddress, // Por ahora usamos la misma dirección
notes,
});

if (!updateResponse.success) {
return NextResponse.redirect(`${storeHost}/checkouts/cn/${token}?error=update_failed`, {
status: 303,
headers: corsHeaders,
});
}

// Completar el checkout
const completeResponse = await checkoutFetcher.completeCheckout(token);

if (!completeResponse.success) {
return NextResponse.redirect(`${storeHost}/checkouts/cn/${token}?error=complete_failed`, {
status: 303,
headers: corsHeaders,
});
}

// Redirigir a página de confirmación en el dominio de la tienda
return NextResponse.redirect(`${storeHost}/checkouts/cn/${token}/confirmation`, {
status: 303,
headers: corsHeaders,
});
} catch (error) {
console.error('Error completing checkout:', error);
const url = new URL(request.url);
const token = url.searchParams.get('token');
return NextResponse.redirect(`${storeHost}/checkouts/cn/${token || 'error'}?error=checkout_error`, {
status: 303,
headers: corsHeaders,
});
}
}
61 changes: 61 additions & 0 deletions app/api/stores/[storeId]/checkout/direct/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { getNextCorsHeaders } from '@/lib/utils/next-cors';
import { cartFetcher } from '@/renderer-engine/services/fetchers/cart-fetcher';
import { checkoutFetcher } from '@/renderer-engine/services/fetchers/checkout-fetcher';
import { cookies } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server';

const SESSION_COOKIE = 'fasttify_cart_session_id';

export async function OPTIONS(request: NextRequest) {
const corsHeaders = await getNextCorsHeaders(request);
return new Response(null, { status: 204, headers: corsHeaders });
}

export async function GET(request: NextRequest, { params }: { params: Promise<{ storeId: string }> }) {
const corsHeaders = await getNextCorsHeaders(request);

// Obtener el host original de la tienda para las redirecciones
const storeHost =
request.headers.get('origin') ||
request.headers.get('referer')?.split('/')[0] + '//' + request.headers.get('referer')?.split('/')[2];

try {
const { storeId } = await params;

// Obtener sessionId de cookies
const cookiesStore = await cookies();
const sessionId = cookiesStore.get(SESSION_COOKIE)?.value;

if (!sessionId) {
return NextResponse.redirect(`${storeHost}/cart?error=no_session`, { status: 303, headers: corsHeaders });
}

// Obtener el carrito actual
const cart = await cartFetcher.getCart(storeId, sessionId);

if (!cart || !cart.items || cart.items.length === 0) {
return NextResponse.redirect(`${storeHost}/cart?error=empty_cart`, { status: 303, headers: corsHeaders });
}

// Iniciar sesión de checkout directamente
const checkoutResponse = await checkoutFetcher.startCheckout(
{
storeId,
sessionId,
cartId: cart.id,
},
cart
);

if (!checkoutResponse.success || !checkoutResponse.session?.token) {
return NextResponse.redirect(`${storeHost}/cart?error=checkout_failed`, { status: 303, headers: corsHeaders });
}

// Redirigir directamente a la página de checkout con el token
const checkoutUrl = `${storeHost}/checkouts/cn/${checkoutResponse.session.token}`;
return NextResponse.redirect(checkoutUrl, { status: 303, headers: corsHeaders });
} catch (error) {
console.error('Error creating direct checkout:', error);
return NextResponse.redirect(`${storeHost}/cart?error=checkout_error`, { status: 303, headers: corsHeaders });
}
}
Loading