feat: Implement real Mercado Pago API integration for payment preference creation and webhook processing, replacing mock calls and adding necessary environment variables.
This commit is contained in:
@@ -21,3 +21,11 @@ OPENAI_API_KEY=
|
|||||||
LOCAL_WHISPER_URL=http://t-800:9000
|
LOCAL_WHISPER_URL=http://t-800:9000
|
||||||
LOCAL_OLLAMA_URL=http://t-800:11434
|
LOCAL_OLLAMA_URL=http://t-800:11434
|
||||||
LOCAL_LLM_MODEL=llama3.2:3b
|
LOCAL_LLM_MODEL=llama3.2:3b
|
||||||
|
|
||||||
|
# Mercado Pago Configuration
|
||||||
|
MP_ACCESS_TOKEN=
|
||||||
|
MP_PUBLIC_KEY=
|
||||||
|
MP_WEBHOOK_SECRET=
|
||||||
|
MP_BACK_URL_SUCCESS=http://localhost:3003/payments/success
|
||||||
|
MP_BACK_URL_FAILURE=http://localhost:3003/payments/failure
|
||||||
|
MP_NOTIFICATION_URL=
|
||||||
|
|||||||
@@ -53,12 +53,71 @@ pub async fn create_payment_preference(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
|
||||||
// 3. Call Mercado Pago API (Mocked for now as per plan)
|
// 3. Call Mercado Pago API
|
||||||
let preference_id = format!("pref_{}", Uuid::new_v4().simple());
|
let mp_access_token = std::env::var("MP_ACCESS_TOKEN").unwrap_or_default();
|
||||||
let init_point = format!(
|
let back_url_success = std::env::var("MP_BACK_URL_SUCCESS").unwrap_or_default();
|
||||||
"https://www.mercadopago.cl/checkout/v1/redirect?pref_id={}",
|
let back_url_failure = std::env::var("MP_BACK_URL_FAILURE").unwrap_or_default();
|
||||||
preference_id
|
let notification_url = std::env::var("MP_NOTIFICATION_URL").unwrap_or_default();
|
||||||
);
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let preference_payload = serde_json::json!({
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": course_id.to_string(),
|
||||||
|
"title": course.title,
|
||||||
|
"quantity": 1,
|
||||||
|
"unit_price": course.price,
|
||||||
|
"currency_id": course.currency
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"back_urls": {
|
||||||
|
"success": back_url_success,
|
||||||
|
"failure": back_url_failure,
|
||||||
|
"pending": back_url_failure
|
||||||
|
},
|
||||||
|
"auto_return": "approved",
|
||||||
|
"notification_url": notification_url,
|
||||||
|
"external_reference": transaction_id.to_string(),
|
||||||
|
"metadata": {
|
||||||
|
"course_id": course_id,
|
||||||
|
"user_id": user_id,
|
||||||
|
"transaction_id": transaction_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mp_response = client
|
||||||
|
.post("https://api.mercadopago.com/checkout/preferences")
|
||||||
|
.header("Authorization", format!("Bearer {}", mp_access_token))
|
||||||
|
.json(&preference_payload)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("MP Error: {}", e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !mp_response.status().is_success() {
|
||||||
|
let err_text = mp_response.text().await.unwrap_or_default();
|
||||||
|
return Err((
|
||||||
|
StatusCode::BAD_GATEWAY,
|
||||||
|
format!("MP API Error: {}", err_text),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mp_data: serde_json::Value = mp_response.json().await.map_err(|e| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("Failed to parse MP response: {}", e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let preference_id = mp_data["id"].as_str().unwrap_or_default().to_string();
|
||||||
|
let init_point = mp_data["init_point"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
// Update transaction with provider reference
|
// Update transaction with provider reference
|
||||||
sqlx::query("UPDATE transactions SET provider_reference = $1 WHERE id = $2")
|
sqlx::query("UPDATE transactions SET provider_reference = $1 WHERE id = $2")
|
||||||
@@ -94,18 +153,50 @@ pub async fn mercadopago_webhook(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let payment_id = payload.data.id;
|
let payment_id = payload.data.id;
|
||||||
|
let mp_access_token = std::env::var("MP_ACCESS_TOKEN").unwrap_or_default();
|
||||||
|
|
||||||
// Simplified success logic for the mock
|
// 1. Fetch payment details from Mercado Pago to verify status
|
||||||
let transaction: Option<(Uuid, Uuid)> =
|
let client = reqwest::Client::new();
|
||||||
sqlx::query_as("SELECT user_id, course_id FROM transactions WHERE provider_reference = $1")
|
let mp_response = client
|
||||||
.bind(&payment_id)
|
.get(format!(
|
||||||
.fetch_optional(&pool)
|
"https://api.mercadopago.com/v1/payments/{}",
|
||||||
.await
|
payment_id
|
||||||
.unwrap_or(None);
|
))
|
||||||
|
.header("Authorization", format!("Bearer {}", mp_access_token))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
if let Some((user_id, course_id)) = transaction {
|
if !mp_response.status().is_success() {
|
||||||
sqlx::query("UPDATE transactions SET status = 'success', updated_at = NOW() WHERE provider_reference = $1")
|
return Err(StatusCode::BAD_GATEWAY);
|
||||||
.bind(&payment_id)
|
}
|
||||||
|
|
||||||
|
let payment_data: serde_json::Value = mp_response
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
let status = payment_data["status"].as_str().unwrap_or("pending");
|
||||||
|
let external_reference = payment_data["external_reference"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if status != "approved" {
|
||||||
|
return Ok(StatusCode::OK); // Payment not yet approved, wait for next notification
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Find transaction by external reference (transaction_id)
|
||||||
|
let transaction: Option<(Uuid, Uuid, Uuid)> = sqlx::query_as(
|
||||||
|
"SELECT id, user_id, course_id FROM transactions WHERE id = $1 OR provider_reference = $1",
|
||||||
|
)
|
||||||
|
.bind(&external_reference) // Try by external reference first
|
||||||
|
.fetch_optional(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap_or(None);
|
||||||
|
|
||||||
|
if let Some((trans_id, user_id, course_id)) = transaction {
|
||||||
|
// Mark transaction as success
|
||||||
|
sqlx::query("UPDATE transactions SET status = 'success', updated_at = NOW() WHERE id = $1")
|
||||||
|
.bind(trans_id)
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|||||||
Reference in New Issue
Block a user