goldenpay 0.3.0

Production-oriented Rust SDK for FunPay automation
Documentation
use async_trait::async_trait;
use goldenpay::GoldenPayError;
use goldenpay::{
    BotOptions, ChatMessage, DeliveryItem, DeliveryItemFormat, DeliveryMessageBuilder,
    DeliveryMessenger, DeliveryService, EventStream, ExactSubcategoryMatcher, MemoryDeliveryStore,
    MessageFilter, OrderInfo, OrderStatus, RunnerObject, RunnerResponse, RunnerUnknownObject,
};
use std::sync::Arc;
use tokio::sync::Mutex;

fn sample_order() -> OrderInfo {
    OrderInfo {
        id: "ORDER100".to_string(),
        buyer_username: "buyer".to_string(),
        buyer_id: 200,
        chat_id: "users-100-200".to_string(),
        description: "Steam keys".to_string(),
        subcategory_name: "Steam Keys".to_string(),
        amount: 2,
        status: OrderStatus::Paid,
    }
}

#[derive(Default)]
struct TestMessenger {
    sent: Arc<Mutex<Vec<(String, String)>>>,
}

#[async_trait]
impl DeliveryMessenger for TestMessenger {
    async fn send_delivery_message(
        &self,
        chat_id: &str,
        text: &str,
    ) -> Result<RunnerResponse, GoldenPayError> {
        self.sent
            .lock()
            .await
            .push((chat_id.to_string(), text.to_string()));
        Ok(RunnerResponse {
            success: true,
            error_message: None,
            objects: vec![RunnerObject::Unknown(RunnerUnknownObject {
                object_type: Some("test".to_string()),
                id: None,
                tag: None,
                raw: serde_json::json!({ "ok": true }),
            })],
            raw: serde_json::json!({ "ok": true }),
        })
    }
}

#[test]
fn event_stream_and_delivery_work_together() {
    let mut stream = EventStream::default();
    let order = sample_order();
    let options = BotOptions::default();
    let filter = MessageFilter {
        ignore_author_id: options.ignore_own_messages.then_some(100),
    };

    assert!(stream.should_emit_order(&order));
    assert!(!stream.should_emit_order(&order));

    let own_message = ChatMessage {
        id: 1,
        chat_id: order.chat_id.clone(),
        author_id: 100,
        text: Some("internal".to_string()),
    };
    let buyer_message = ChatMessage {
        id: 2,
        chat_id: order.chat_id.clone(),
        author_id: 200,
        text: Some("hello".to_string()),
    };

    assert!(!stream.should_emit_message(&own_message, &filter));
    assert!(stream.should_emit_message(&buyer_message, &filter));

    let mut delivery = DeliveryService::new();
    delivery.add_product(
        "Steam Keys",
        [
            DeliveryItem {
                value: "KEY-1".to_string(),
            },
            DeliveryItem {
                value: "KEY-2".to_string(),
            },
        ],
    );

    let result = delivery.deliver(&ExactSubcategoryMatcher, &order).unwrap();
    assert_eq!(result.order_id, "ORDER100");
    assert_eq!(result.delivered.len(), 2);
}

#[tokio::test]
async fn deliver_order_uses_store_for_dedup() {
    let order = sample_order();
    let mut delivery = DeliveryService::new();
    let store = MemoryDeliveryStore::new();

    delivery.add_product(
        "Steam Keys",
        [
            DeliveryItem {
                value: "KEY-A".to_string(),
            },
            DeliveryItem {
                value: "KEY-B".to_string(),
            },
        ],
    );

    let first = delivery
        .deliver_order(&ExactSubcategoryMatcher, &store, &order)
        .await
        .unwrap();
    assert_eq!(first.delivered.len(), 2);

    let second = delivery
        .deliver_order(&ExactSubcategoryMatcher, &store, &order)
        .await;
    assert!(second.is_err());
}

#[tokio::test]
async fn process_paid_order_builds_and_sends_message() {
    let order = sample_order();
    let mut delivery = DeliveryService::new();
    let store = MemoryDeliveryStore::new();
    let messenger = TestMessenger::default();
    let builder = DeliveryMessageBuilder::new()
        .item_format(DeliveryItemFormat::CodeBlock)
        .footer("Thanks for your order");

    delivery.add_product(
        "Steam Keys",
        [
            DeliveryItem {
                value: "KEY-X".to_string(),
            },
            DeliveryItem {
                value: "KEY-Y".to_string(),
            },
        ],
    );

    let result = delivery
        .process_paid_order(
            &ExactSubcategoryMatcher,
            &store,
            &messenger,
            &builder,
            &order,
        )
        .await
        .unwrap();

    assert!(result.message_text.contains("```"));
    assert!(result.message_text.contains("KEY-X"));
    assert_eq!(messenger.sent.lock().await.len(), 1);
}