Implement Pakasir Phase 1

Add env-based Pakasir API key with fallback, create orders + transaction via Pakasir, and update checkout UI to reflect payment status and access flow. Centralize key retrieval, include placeholder callback URL, and adjust UI to show processing state and Indonesian messaging. Enable client-side access checks for paid orders in product detail and member dashboard (without edge function).

X-Lovable-Edit-ID: edt-14f89c00-5022-4166-bb29-df4832b81add
This commit is contained in:
gpt-engineer-app[bot]
2025-12-19 06:35:21 +00:00
3 changed files with 71 additions and 9 deletions

View File

@@ -15,7 +15,15 @@ import { QRCodeSVG } from "qrcode.react";
// Pakasir configuration
const PAKASIR_PROJECT_SLUG = "dewengoding";
const PAKASIR_API_KEY = "iP13osgh7lAzWWIPsj7TbW5M3iGEAQMo";
const SANDBOX_API_KEY = "iP13osgh7lAzWWIPsj7TbW5M3iGEAQMo";
// Centralized API key retrieval - uses env var with sandbox fallback
const getPakasirApiKey = (): string => {
return import.meta.env.VITE_PAKASIR_API_KEY || SANDBOX_API_KEY;
};
// TODO: Replace with actual Supabase Edge Function URL after creation
const PAKASIR_CALLBACK_URL = "https://lovable.backoffice.biz.id/functions/v1/pakasir-webhook";
type PaymentMethod = "qris" | "paypal";
type CheckoutStep = "cart" | "payment" | "waiting";
@@ -117,6 +125,9 @@ export default function Checkout() {
setOrderId(order.id);
// Build description from product titles
const productTitles = items.map(item => item.title).join(", ");
if (paymentMethod === "qris") {
// Call Pakasir API for QRIS
try {
@@ -127,7 +138,9 @@ export default function Checkout() {
project: PAKASIR_PROJECT_SLUG,
order_id: order.id,
amount: amountInRupiah,
api_key: PAKASIR_API_KEY,
api_key: getPakasirApiKey(),
description: productTitles,
callback_url: PAKASIR_CALLBACK_URL,
}),
});
@@ -222,7 +235,7 @@ export default function Checkout() {
</Button>
</div>
<p className="text-xs text-muted-foreground text-center">
Pembayaran akan otomatis terkonfirmasi setelah Anda scan dan bayar QR code di atas
Pembayaran diproses melalui Pakasir dan akan dikonfirmasi otomatis setelah berhasil.
</p>
</CardContent>
</Card>

View File

@@ -121,13 +121,35 @@ export default function ProductDetail() {
const checkUserAccess = async () => {
if (!product || !user) return;
const { data } = await supabase
// Check user_access table first
const { data: accessData } = await supabase
.from('user_access')
.select('id')
.eq('user_id', user.id)
.eq('product_id', product.id)
.maybeSingle();
setHasAccess(!!data);
if (accessData) {
setHasAccess(true);
setCheckingAccess(false);
return;
}
// Also check for paid orders containing this product
const { data: paidOrders } = await supabase
.from('orders')
.select(`
id,
order_items!inner (product_id)
`)
.eq('user_id', user.id)
.eq('payment_status', 'paid')
.eq('payment_provider', 'pakasir')
.eq('order_items.product_id', product.id)
.limit(1);
setHasAccess(!!(paidOrders && paidOrders.length > 0));
setCheckingAccess(false);
};

View File

@@ -42,11 +42,38 @@ export default function MemberDashboard() {
}, [user, authLoading]);
const fetchData = async () => {
const [accessRes, ordersRes] = await Promise.all([
const [accessRes, ordersRes, paidOrdersRes] = await Promise.all([
supabase.from('user_access').select(`id, product:products (id, title, slug, type, meeting_link, recording_url)`).eq('user_id', user!.id),
supabase.from('orders').select('*').eq('user_id', user!.id).order('created_at', { ascending: false }).limit(3)
supabase.from('orders').select('*').eq('user_id', user!.id).order('created_at', { ascending: false }).limit(3),
// Also get products from paid orders (via order_items)
supabase.from('orders')
.select(`
order_items (
product:products (id, title, slug, type, meeting_link, recording_url)
)
`)
.eq('user_id', user!.id)
.eq('payment_status', 'paid')
.eq('payment_provider', 'pakasir')
]);
if (accessRes.data) setAccess(accessRes.data as unknown as UserAccess[]);
// Combine access from user_access and paid orders
const directAccess = accessRes.data as unknown as UserAccess[] || [];
const paidProductAccess: UserAccess[] = [];
if (paidOrdersRes.data) {
const existingIds = new Set(directAccess.map(a => a.product.id));
paidOrdersRes.data.forEach((order: any) => {
order.order_items?.forEach((item: any) => {
if (item.product && !existingIds.has(item.product.id)) {
existingIds.add(item.product.id);
paidProductAccess.push({ id: `paid-${item.product.id}`, product: item.product });
}
});
});
}
setAccess([...directAccess, ...paidProductAccess]);
if (ordersRes.data) setRecentOrders(ordersRes.data);
setLoading(false);
};