feat(core): Stripe Connect Payouts & Twitter API Integration
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s
This commit is contained in:
@@ -189,3 +189,58 @@ export async function releasePayment(
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function executePayout(
|
||||
tx: Prisma.TransactionClient,
|
||||
taskId: string,
|
||||
developerWallet: string,
|
||||
amount: number,
|
||||
currency: string,
|
||||
idempotencyKey: string
|
||||
) {
|
||||
const existing = await tx.ledgerEntry.findUnique({
|
||||
where: { idempotency_key: idempotencyKey },
|
||||
});
|
||||
if (existing) {
|
||||
if (existing.response_status === "SUCCESS") return existing;
|
||||
throw new Error(`Previous executePayout failed for idempotencyKey: ${idempotencyKey}`);
|
||||
}
|
||||
|
||||
let transferId = `mock_transfer_${taskId}`;
|
||||
|
||||
// If Stripe is configured and wallet looks like a Stripe Connect Account
|
||||
if (stripe && developerWallet.startsWith("acct_") && !ALLOW_MCP_CLAIM_WITHOUT_STRIPE) {
|
||||
try {
|
||||
const transfer = await stripe.transfers.create({
|
||||
amount: amount,
|
||||
currency: currency.toLowerCase(),
|
||||
destination: developerWallet,
|
||||
description: `Bounty Payout for Task ${taskId}`,
|
||||
}, { idempotencyKey });
|
||||
transferId = transfer.id;
|
||||
} catch (error: any) {
|
||||
await tx.ledgerEntry.create({
|
||||
data: {
|
||||
task_id: taskId,
|
||||
phase: "PAYOUT",
|
||||
idempotency_key: idempotencyKey,
|
||||
stripe_object_id: null,
|
||||
response_status: "FAILED",
|
||||
http_status: error.statusCode || 500,
|
||||
},
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return await tx.ledgerEntry.create({
|
||||
data: {
|
||||
task_id: taskId,
|
||||
phase: "PAYOUT",
|
||||
idempotency_key: idempotencyKey,
|
||||
stripe_object_id: transferId,
|
||||
response_status: "SUCCESS",
|
||||
http_status: 200,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
/**
|
||||
* X (Twitter) FOMO Broadcaster
|
||||
* 用於發送病毒式行銷推文,製造錯失恐懼 (FOMO)。
|
||||
*/
|
||||
import { sendTrafficAlert } from "./traffic-alert";
|
||||
import { TwitterApi } from "twitter-api-v2";
|
||||
|
||||
export type FomoEventType = "HIGH_VALUE_BOUNTY" | "SPEED_RUN" | "A2A_SUBCONTRACT";
|
||||
|
||||
@@ -31,9 +28,14 @@ function generateTweetText(event: FomoEvent): string {
|
||||
|
||||
export async function broadcastFomoEvent(event: FomoEvent) {
|
||||
const text = generateTweetText(event);
|
||||
const bearerToken = process.env.TWITTER_BEARER_TOKEN;
|
||||
|
||||
// Use App Key/Secret + Access Token/Secret for User Context Posting (OAuth 1.0a)
|
||||
const appKey = process.env.TWITTER_API_KEY;
|
||||
const appSecret = process.env.TWITTER_API_SECRET;
|
||||
const accessToken = process.env.TWITTER_ACCESS_TOKEN;
|
||||
const accessSecret = process.env.TWITTER_ACCESS_SECRET;
|
||||
|
||||
if (!bearerToken) {
|
||||
if (!appKey || !appSecret || !accessToken || !accessSecret) {
|
||||
// Mock Mode: Log to console and send to internal traffic alert
|
||||
console.log(`[X Broadcaster Mock] Would tweet: "${text}"`);
|
||||
void sendTrafficAlert({
|
||||
@@ -48,23 +50,16 @@ export async function broadcastFomoEvent(event: FomoEvent) {
|
||||
}
|
||||
|
||||
try {
|
||||
// 簡單的 Fetch 實作,針對 Twitter V2 API
|
||||
const response = await fetch("https://api.twitter.com/2/tweets", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${bearerToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ text }),
|
||||
const client = new TwitterApi({
|
||||
appKey,
|
||||
appSecret,
|
||||
accessToken,
|
||||
accessSecret,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errText = await response.text();
|
||||
console.error("[X Broadcaster] API Error:", errText);
|
||||
} else {
|
||||
console.log("[X Broadcaster] Successfully posted tweet.");
|
||||
}
|
||||
const response = await client.v2.tweet(text);
|
||||
console.log("[X Broadcaster] Successfully posted tweet. Tweet ID:", response.data.id);
|
||||
} catch (err) {
|
||||
console.error("[X Broadcaster] Network Error:", err);
|
||||
console.error("[X Broadcaster] Twitter API Error:", err);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user