Refactor: Rename create-pakasir-payment to create-payment

- Rename function to abstract payment provider details
- Add support for both QRIS and PayPal methods
- Update frontend to use generic create-payment function
- Remove provider-specific naming from UI/UX
- Payment provider (Pakasir) is now an implementation detail

Response format:
- QRIS: returns qr_string for in-app display, payment_url as fallback
- PayPal: returns payment_url for redirect

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
dwindown
2025-12-23 21:41:47 +07:00
parent a9f7c9b07a
commit 1a36f831cc
5 changed files with 158 additions and 126 deletions

View File

@@ -132,24 +132,28 @@ export default function Checkout() {
// Build description from product titles
const productTitles = items.map(item => item.title).join(", ");
if (paymentMethod === "qris") {
// Call edge function to create Pakasir QRIS payment (avoids CORS)
const { data: paymentData, error: paymentError } = await supabase.functions.invoke('create-pakasir-payment', {
// Call edge function to create payment (avoids CORS)
const { data: paymentData, error: paymentError } = await supabase.functions.invoke('create-payment', {
body: {
order_id: order.id,
amount: amountInRupiah,
description: productTitles,
method: paymentMethod, // 'qris' or 'paypal'
},
});
if (paymentError) {
console.error('Payment creation error:', paymentError);
// Fallback to redirect
const pakasirUrl = `https://app.pakasir.com/pay/${PAKASIR_PROJECT_SLUG}/${amountInRupiah}?order_id=${order.id}`;
throw new Error(paymentError.message || 'Gagal membuat pembayaran');
}
if (paymentData?.success) {
if (paymentData.data.method === 'paypal') {
// PayPal - redirect to payment URL
clearCart();
window.location.href = pakasirUrl;
} else if (paymentData?.data?.qr_string) {
// QRIS available - show QR code
window.location.href = paymentData.data.payment_url;
} else if (paymentData.data.qr_string) {
// QRIS available - show QR code in app
setPaymentData({
qr_string: paymentData.data.qr_string,
expired_at: paymentData.data.expired_at,
@@ -159,13 +163,11 @@ export default function Checkout() {
clearCart();
} else {
// No QR code - redirect to payment page
clearCart();
window.location.href = paymentData.data.payment_url;
}
} else {
// PayPal - redirect to Pakasir PayPal URL
clearCart();
const paypalUrl = `https://app.pakasir.com/paypal/${PAKASIR_PROJECT_SLUG}/${amountInRupiah}?order_id=${order.id}`;
window.location.href = paypalUrl;
throw new Error('Gagal membuat pembayaran');
}
} catch (error) {
console.error("Checkout error:", error);

View File

@@ -226,12 +226,13 @@ export default function ConsultingBooking() {
const { error: slotsError } = await supabase.from('consulting_slots').insert(slotsToInsert);
if (slotsError) throw slotsError;
// Call edge function to create Pakasir payment (avoids CORS)
const { data: paymentData, error: paymentError } = await supabase.functions.invoke('create-pakasir-payment', {
// Call edge function to create payment (avoids CORS)
const { data: paymentData, error: paymentError } = await supabase.functions.invoke('create-payment', {
body: {
order_id: order.id,
amount: totalPrice,
description: `Konsultasi 1-on-1 (${totalBlocks} blok)`,
method: 'qris',
},
});
@@ -241,7 +242,7 @@ export default function ConsultingBooking() {
}
if (paymentData?.success && paymentData?.data?.payment_url) {
// Redirect to Pakasir payment page
// Redirect to payment page
window.location.href = paymentData.data.payment_url;
} else {
throw new Error('Gagal membuat URL pembayaran');

View File

@@ -45,5 +45,5 @@ verify_jwt = false
[functions.delete-order]
verify_jwt = false
[functions.create-pakasir-payment]
[functions.create-payment]
verify_jwt = false

View File

@@ -1,102 +0,0 @@
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
interface PakasirPaymentRequest {
order_id: string;
amount: number;
description: string;
}
serve(async (req: Request) => {
// Handle CORS preflight
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
if (req.method !== "POST") {
return new Response("Method not allowed", { status: 405, headers: corsHeaders });
}
try {
const body: PakasirPaymentRequest = await req.json();
const { order_id, amount, description } = body;
if (!order_id || !amount) {
return new Response(
JSON.stringify({ success: false, error: "order_id and amount are required" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const PAKASIR_PROJECT_SLUG = Deno.env.get("PAKASIR_PROJECT_SLUG") || "";
const PAKASIR_API_KEY = Deno.env.get("PAKASIR_API_KEY") || "";
const PAKASIR_CALLBACK_URL = `${Deno.env.get("SUPABASE_URL")}/functions/v1/pakasir-webhook`;
if (!PAKASIR_PROJECT_SLUG || !PAKASIR_API_KEY) {
console.error("[PAKASIR] Missing credentials");
return new Response(
JSON.stringify({ success: false, error: "Pakasir credentials not configured" }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
console.log("[PAKASIR] Creating payment transaction:", { order_id, amount });
// Call Pakasir API to create QRIS transaction
const pakasirResponse = await fetch(`https://app.pakasir.com/api/transactioncreate/qris`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
project: PAKASIR_PROJECT_SLUG,
order_id: order_id,
amount: amount,
api_key: PAKASIR_API_KEY,
description: description || `Order ${order_id}`,
callback_url: PAKASIR_CALLBACK_URL,
}),
});
if (!pakasirResponse.ok) {
const errorText = await pakasirResponse.text();
console.error("[PAKASIR] API error:", pakasirResponse.status, errorText);
return new Response(
JSON.stringify({
success: false,
error: "Pakasir API error",
details: errorText
}),
{ status: pakasirResponse.status, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const result = await pakasirResponse.json();
console.log("[PAKASIR] Payment created:", result);
// Return payment URL and QR data
return new Response(
JSON.stringify({
success: true,
data: {
payment_url: `https://app.pakasir.com/pay/${PAKASIR_PROJECT_SLUG}/${amount}?order_id=${order_id}`,
qr_string: result.qr_string || result.qr || null,
order_id: order_id,
}
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error: any) {
console.error("[PAKASIR] Unexpected error:", error);
return new Response(
JSON.stringify({
success: false,
error: error.message || "Internal server error"
}),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
});

View File

@@ -0,0 +1,131 @@
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};
interface CreatePaymentRequest {
order_id: string;
amount: number;
description: string;
method?: 'qris' | 'paypal';
}
serve(async (req: Request) => {
// Handle CORS preflight
if (req.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
if (req.method !== "POST") {
return new Response("Method not allowed", { status: 405, headers: corsHeaders });
}
try {
const body: CreatePaymentRequest = await req.json();
const { order_id, amount, description, method = 'qris' } = body;
if (!order_id || !amount) {
return new Response(
JSON.stringify({ success: false, error: "order_id and amount are required" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const PAYMENT_PROJECT_SLUG = Deno.env.get("PAKASIR_PROJECT_SLUG") || "";
const PAYMENT_API_KEY = Deno.env.get("PAKASIR_API_KEY") || "";
const PAYMENT_CALLBACK_URL = `${Deno.env.get("SUPABASE_URL")}/functions/v1/pakasir-webhook`;
if (!PAYMENT_PROJECT_SLUG || !PAYMENT_API_KEY) {
console.error("[PAYMENT] Missing credentials");
return new Response(
JSON.stringify({ success: false, error: "Payment provider credentials not configured" }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
console.log("[PAYMENT] Creating payment transaction:", { order_id, amount, method });
if (method === 'paypal') {
// Return PayPal payment URL
const paypalUrl = `https://app.pakasir.com/paypal/${PAYMENT_PROJECT_SLUG}/${amount}?order_id=${order_id}`;
return new Response(
JSON.stringify({
success: true,
data: {
payment_url: paypalUrl,
method: 'paypal',
order_id: order_id,
}
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
// Default: QRIS - Call provider API
const paymentResponse = await fetch(`https://app.pakasir.com/api/transactioncreate/qris`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
project: PAYMENT_PROJECT_SLUG,
order_id: order_id,
amount: amount,
api_key: PAYMENT_API_KEY,
description: description || `Order ${order_id}`,
callback_url: PAYMENT_CALLBACK_URL,
}),
});
if (!paymentResponse.ok) {
const errorText = await paymentResponse.text();
console.error("[PAYMENT] Provider API error:", paymentResponse.status, errorText);
// Fallback: return direct payment URL
const fallbackUrl = `https://app.pakasir.com/pay/${PAYMENT_PROJECT_SLUG}/${amount}?order_id=${order_id}`;
return new Response(
JSON.stringify({
success: true,
data: {
payment_url: fallbackUrl,
method: 'qris',
fallback: true,
order_id: order_id,
}
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const result = await paymentResponse.json();
console.log("[PAYMENT] Payment created:", result);
// Return QRIS data for display in app
return new Response(
JSON.stringify({
success: true,
data: {
method: 'qris',
qr_string: result.qr_string || result.qr || null,
expired_at: result.expired_at || null,
// Fallback URL if QR code is not available
payment_url: `https://app.pakasir.com/pay/${PAYMENT_PROJECT_SLUG}/${amount}?order_id=${order_id}`,
order_id: order_id,
}
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error: any) {
console.error("[PAYMENT] Unexpected error:", error);
return new Response(
JSON.stringify({
success: false,
error: error.message || "Internal server error"
}),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
});