This commit is contained in:
Adrian Woźniak 2023-05-15 22:55:09 +02:00
parent 8e1f9625d1
commit 30d0baebc1
11 changed files with 4988 additions and 657 deletions

74
Cargo.lock generated
View File

@ -336,6 +336,12 @@ version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "anymap"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
[[package]]
name = "argon2"
version = "0.4.1"
@ -1101,6 +1107,16 @@ dependencies = [
"typenum",
]
[[package]]
name = "ctor"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]]
name = "cxx"
version = "1.0.94"
@ -1791,6 +1807,17 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "ghost"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e77ac7b51b8e6313251737fcef4b1c01a2ea102bde68415b62c0ee9268fec357"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "gimli"
version = "0.26.2"
@ -2293,6 +2320,28 @@ dependencies = [
"unic-langid",
]
[[package]]
name = "inventory"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0eb5160c60ba1e809707918ee329adb99d222888155835c6feedba19f6c3fd4"
dependencies = [
"ctor",
"ghost",
"inventory-impl",
]
[[package]]
name = "inventory-impl"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e41b53715c6f0c4be49510bb82dee2c1e51c8586d885abe65396e82ed518548"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "io-extras"
version = "0.15.0"
@ -3184,12 +3233,15 @@ name = "payment_adapter"
version = "0.1.0"
dependencies = [
"async-trait",
"chrono",
"config",
"futures",
"model",
"serde",
"thiserror",
"toml 0.7.3",
"tracing",
"traitcast",
"uuid 1.3.2",
]
@ -4555,6 +4607,7 @@ dependencies = [
"fulfillment_adapter",
"payment_adapter",
"payup",
"serde",
"tokio 1.28.0",
"tracing",
]
@ -5170,6 +5223,27 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "traitcast"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f80b1cde694e5ff2dcb33875530f2f031a9a34dec8ba2744cacaf80a88658740"
dependencies = [
"inventory",
"lazy_static",
"traitcast_core",
]
[[package]]
name = "traitcast_core"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aabba8f4a83963f61a84d8cfc5829b4fad692aa6c6ad5d7b08b9549777e3cc4a"
dependencies = [
"anymap",
"inventory",
]
[[package]]
name = "trust-dns-native-tls"
version = "0.20.4"

View File

@ -1,4 +1,6 @@
#[derive(Debug, thiserror::Error)]
pub enum FError {
#[error("Payment gate request failed")]
HttpError,
}
@ -9,6 +11,7 @@ pub struct FulfillmentOption {}
pub trait FulfillmentPayload {}
pub trait FulfillmentCart {}
#[async_trait::async_trait]
pub trait FulfillmentAdapter {
fn identifier() -> &'static str;
@ -22,7 +25,7 @@ pub trait FulfillmentAdapter {
* to create shipping options in Medusa that can be chosen between by
* the customer.
*/
fn fulfillment_options(&mut self) -> FResult<&[FulfillmentOption]>;
async fn fulfillment_options(&mut self) -> FResult<Vec<FulfillmentOption>>;
/**
* Called before a shipping method is set on a cart to ensure that the
@ -31,7 +34,7 @@ pub trait FulfillmentAdapter {
* point. It is up to the fulfillment provider to enforce that the
* correct data is being sent through.
*/
fn validate_fulfillment_payload<P: FulfillmentPayload, C: FulfillmentCart>(
async fn validate_fulfillment_payload<P: FulfillmentPayload, C: FulfillmentCart>(
&mut self,
payload: P,
cart: C,
@ -41,33 +44,35 @@ pub trait FulfillmentAdapter {
* Called before a shipping option is created in Admin. Use this to
* ensure that a fulfillment option does in fact exist.
*/
fn validate_option<P: FulfillmentPayload>(&mut self, payload: P) -> FResult<bool>;
async fn validate_option<P: FulfillmentPayload>(&mut self, payload: P) -> FResult<bool>;
fn can_calculate<P: FulfillmentPayload>(&mut self, payload: P) -> FResult<bool>;
async fn can_calculate<P: FulfillmentPayload>(&mut self, payload: P) -> FResult<bool>;
/**
* Used to calculate a price for a given shipping option.
*/
fn calculate_price<P: FulfillmentPayload, C: FulfillmentCart>(data: P, cart: C)
-> FResult<u64>;
async fn calculate_price<P: FulfillmentPayload, C: FulfillmentCart>(
data: P,
cart: C,
) -> FResult<u64>;
// FULFILLMENT
fn create_fulfillment(&mut self);
async fn create_fulfillment(&mut self);
fn cancel_fulfillment(&mut self);
async fn cancel_fulfillment(&mut self);
/**
* Used to retrieve documents associated with a fulfillment.
* Will default to returning no documents.
*/
fn fulfillment_documents(&mut self);
async fn fulfillment_documents(&mut self);
// REFUND
fn create_return(&mut self);
async fn create_return(&mut self);
fn return_documents(&mut self);
async fn return_documents(&mut self);
//
fn shipment_documents(&mut self);
fn documents(&mut self);
async fn shipment_documents(&mut self);
async fn documents(&mut self);
}

View File

@ -3,6 +3,7 @@
pub mod api;
pub mod encrypt;
pub mod v3;
use std::fmt::{Display, Formatter};
use std::ops;

4031
crates/model/src/v3.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -58,35 +58,72 @@ impl PayUPayment {
}
impl PaymentAdapter for PayUPayment {
fn init(&mut self) -> PResult<Status> {
async fn new(config: Config) -> Self {
todo!()
}
fn teardown(&mut self) -> PResult<Status> {
async fn initialize_payment(
&mut self,
ctx: PaymentProcessorContext,
) -> PResult<PaymentProcessorSessionResponse> {
todo!()
}
fn create_payment(&mut self, msg: CreatePayment) -> PResult<PaymentCreated> {
async fn update_payment(
&mut self,
ctx: PaymentProcessorContext,
) -> PResult<Option<PaymentProcessorSessionResponse>> {
todo!()
}
fn retrieve_payment(&mut self) {
async fn refund_payment(
&mut self,
payment_session_data: PaymentSessionData,
refund_amount: Amount,
) -> PResult<PaymentSessionData> {
todo!()
}
fn authorize_payment(&mut self) {
async fn authorize_payment(
&mut self,
payment_session_data: PaymentSessionData,
data: PaymentProcessCtx,
) -> PResult<(PaymentSessionStatus, PaymentSessionData)> {
todo!()
}
fn capture_payment(&mut self) {
async fn capture_payment(
&mut self,
payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData> {
todo!()
}
fn refund_payment(&mut self, msg: RefundPayment) -> PResult<Refunded> {
async fn delete_payment(
&mut self,
payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData> {
todo!()
}
fn delete_payment(&mut self, msg: DeletePayment) -> PResult<Deleted> {
async fn retrieve_payment(
&mut self,
payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData> {
todo!()
}
async fn cancel_payment(
&mut self,
payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData> {
todo!()
}
async fn payment_status(
&mut self,
payment_session_data: &PaymentSessionData,
) -> PResult<PaymentSessionStatus> {
todo!()
}
}

View File

@ -12,3 +12,6 @@ thiserror = { version = "1" }
tracing = { version = "0" }
uuid = { version = "1", features = ['v4'] }
async-trait = { version = "0.1.68" }
chrono = { version = "0.4.24" }
toml = { version = "0.7.3" }
traitcast = { version = "0.5.0" }

View File

@ -1,29 +1,13 @@
#![feature(trait_upcasting)]
use std::any::Any;
use std::collections::HashMap;
use std::ops::DerefMut;
pub use config::PaymentProviderConfig;
pub use model::*;
pub use model::v3::*;
pub use uuid;
pub const CONFIG_POS: u32 = 3;
#[derive(Debug, Clone, thiserror::Error, serde::Serialize, serde::Deserialize)]
#[repr(C)]
pub enum Error {
#[error("Malformed create payment message")]
MalformedCreatePayment,
#[error("Malformed payment status update message")]
MalformedStatusUpdate,
#[error("Malformed authorize response")]
MalformedAuthorize,
#[error("Message pack: malformed create payment message")]
MsgPackDeserializationFailed,
#[error("Provider rejected authorization")]
AuthorizeFailed,
#[error("Provider rejected order")]
PaymentFailed,
#[error("HTTP request failed")]
HttpFailed,
#[error("Unknown host operation {0:?}")]
UnknownOperation(String),
}
use uuid::Uuid;
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
#[repr(C)]
@ -32,59 +16,99 @@ pub enum Status {
Failure = 1,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[repr(C)]
pub struct Buyer {
/// Required customer e-mail
pub email: String,
/// Required customer phone number
pub phone: String,
/// Required customer first name
pub first_name: String,
/// Required customer last name
pub last_name: String,
/// Required customer language
pub language: String,
pub struct AnyData(pub Vec<u8>);
pub struct PaymentProcessorError {
pub error: String,
pub code: Option<String>,
pub detail: Option<AnyData>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[repr(C)]
pub struct Product {
pub id: i32,
pub name: String,
pub unit_price: i32,
pub quantity_unit: String,
pub quantity: i32,
pub struct CustomerMetadata(pub HashMap<String, AnyData>);
pub struct PaymentProcessCtx(pub HashMap<String, AnyData>);
pub struct UpdateRequests {
pub customer_metadata: Option<CustomerMetadata>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[repr(C)]
pub struct Item {
pub product_id: i32,
pub quantity: i32,
pub quantity_unit: String,
pub struct PaymentSessionData {
pub id: Option<String>,
pub meta: HashMap<String, AnyData>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaymentProcessorSessionResponse {
pub update_requests: Option<UpdateRequests>,
pub session_data: PaymentSessionData,
}
pub struct PaymentProcessorContext {
pub billing_address: Option<Address>,
pub email: Email,
pub currency_code: CurrencyCode,
pub amount: Amount,
pub resource_id: String,
pub customer: Option<Customer>,
pub context: PaymentProcessCtx,
pub payment_session_data: PaymentSessionData,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct CreatePayment {
pub buyer: Buyer,
// pub buyer: Buyer,
pub customer_ip: String,
pub currency: String,
pub description: String,
pub cart_products: Vec<Product>,
pub items: Vec<Item>,
pub items: Vec<LineItem>,
//
pub cart: Cart,
pub customer: Option<Customer>,
pub customer_groups: Option<CustomerGroup>,
pub gift_cards: Vec<GiftCard>,
pub region: Region,
pub related_discounts: HashMap<DiscountId, Discount>,
pub related_shipping_methods: HashMap<Uuid, ShippingMethod>,
// cart line items and [LineItem::original_item_id]
pub related_items: HashMap<Uuid, LineItem>,
/// [Payment::swap_id]
pub related_payments: HashMap<PaymentId, Payment>,
pub related_line_item_adjustments: HashMap<LineItemAdjustmentId, LineItemAdjustment>,
pub related_tax_lines: HashMap<Uuid, LineItemTaxLine>,
pub related_currencies: HashMap<CurrencyCode, Currency>,
/// [Return::swap_id]
pub related_returns: HashMap<ReturnId, Return>,
//
pub order_ext_id: Option<String>,
pub notify_uri: String,
pub continue_uri: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Debug)]
pub struct PaymentCreated {
pub ext_order_id: Option<ExtOrderId>,
pub redirect_uri: Option<String>,
}
#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Debug)]
pub struct DeletePayment {
pub ext_order_id: Uuid,
}
#[derive(Debug)]
pub struct Deleted {
pub ext_order_id: Uuid,
}
#[derive(Debug)]
pub struct RefundPayment {
pub refund: RefundType,
pub provider_order_id: Uuid,
pub description: String,
}
#[derive(Debug)]
pub enum RefundType {
/// Refund entire payment
Full,
@ -92,97 +116,132 @@ pub enum RefundType {
Partial(Price),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RefundPayment {
pub refund: RefundType,
pub provider_order_id: String,
pub description: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Debug)]
pub struct Refunded {}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct UpdateStatus {
pub payload: Vec<u8>,
#[derive(Debug, thiserror::Error)]
pub enum PError {
#[error("Payment gate request failed")]
HttpError,
#[error("Operation requires Charge id")]
NoChargeId,
#[error("There's no charge with id {0:?}")]
ChargeNotExists(String),
#[error("Failed capture charge with id {0:?}")]
FailedCapture(String),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct StatusUpdated {}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DeletePayment {
pub ext_order_id: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Deleted {
pub ext_order_id: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum HttpMethod {
Get,
Post,
}
pub enum PaymentSessionStatus {
Pending,
RequiresMore,
Canceled,
Authorized,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[repr(C)]
pub struct HttpRequest {
pub method: HttpMethod,
pub url: String,
pub headers: Vec<(String, String)>,
pub bearer_auth: Option<String>,
pub body: Option<Vec<u8>>,
}
pub mod tracing {
macro_rules! log_levels {
($($lvl: ident),+) => {
$(
#[macro_export]
macro_rules! $lvl {
($fmt: expr) => {
wapc::console_log($fmt);
};
($fmt: expr, $arg1: expr) => {
wapc::console_log(&format!($fmt, $arg1));
};
($fmt: expr, $arg1: expr, $arg2: expr) => {
wapc::console_log(&format!($fmt, $arg1, $arg2));
};
($fmt: expr, $arg1: expr, $arg2: expr, $arg3: expr) => {
wapc::console_log(&format!($fmt, $arg1, $arg2, $arg3));
};
($fmt: expr, $arg1: expr, $arg2: expr, $arg3: expr, $arg4: expr) => {
wapc::console_log(&format!($fmt, $arg1, $arg2, $arg3, $arg4));
};
}
)+
};
}
log_levels!(error, warn, debug, info, trace, log);
}
pub enum PError {}
pub type PResult<T> = Result<T, PError>;
pub trait PaymentAdapter {
fn init(&mut self) -> PResult<Status>;
fn teardown(&mut self) -> PResult<Status>;
pub struct Config(String);
fn create_payment(&mut self, msg: CreatePayment) -> PResult<PaymentCreated>;
fn retrieve_payment(&mut self);
fn authorize_payment(&mut self);
fn capture_payment(&mut self);
fn refund_payment(&mut self, msg: RefundPayment) -> PResult<Refunded>;
fn delete_payment(&mut self, msg: DeletePayment) -> PResult<Deleted>;
impl Config {
pub fn config<S: serde::de::DeserializeOwned>(self) -> Result<S, toml::de::Error> {
toml::from_str(&self.0)
}
}
#[async_trait::async_trait]
pub trait PaymentAdapter {
async fn new(config: Config) -> Self;
/**
* Initiate a payment session with the external provider
*/
async fn initialize_payment(
&mut self,
ctx: PaymentProcessorContext,
) -> PResult<PaymentProcessorSessionResponse>;
/**
* Update an existing payment session
*/
async fn update_payment(
&mut self,
ctx: PaymentProcessorContext,
) -> PResult<Option<PaymentProcessorSessionResponse>>;
/**
* Refund an existing session
*/
async fn refund_payment(
&mut self,
payment_session_data: PaymentSessionData,
refund_amount: Amount,
) -> PResult<PaymentSessionData>;
/**
* Authorize an existing session if it is not already authorized
*/
async fn authorize_payment(
&mut self,
payment_session_data: PaymentSessionData,
data: PaymentProcessCtx,
) -> PResult<(PaymentSessionStatus, PaymentSessionData)>;
/**
* Capture an existing session
*/
async fn capture_payment(
&mut self,
payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData>;
/**
* Delete an existing session
*/
async fn delete_payment(
&mut self,
payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData>;
/**
* Retrieve an existing session
*/
async fn retrieve_payment(
&mut self,
payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData>;
/**
* Cancel an existing session
*/
async fn cancel_payment(
&mut self,
payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData>;
/**
* Return the status of the session
*/
async fn payment_status(
&mut self,
payment_session_data: &PaymentSessionData,
) -> PResult<PaymentSessionStatus>;
}
// use traitcast::{Traitcast, TraitcastFrom};
pub trait APaymentSessionData: Any {
fn id(&self) -> Option<String>;
}
pub struct BPSD(Box<dyn APaymentSessionData>);
impl BPSD {
pub fn as_mut_ref<T>(&mut self) -> Option<&mut T> {
use std::boxed::Box;
let r = self.0.deref_mut();
if <dyn Any>::is::<&T>(r) {
Some(unsafe { r as &mut T })
} else {
None
}
}
}
async fn f(mut psd: &mut BPSD) {
use std::boxed::Box;
let c = psd.deref_mut();
}

View File

@ -1,396 +1,399 @@
use std::collections::HashMap;
use std::fs::read_dir;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::mpsc::Sender;
use std::sync::{Arc, LockResult, MutexGuard};
use config::{AppConfig, SharedAppConfig, UpdateConfig};
use payment_adapter::{HttpMethod, HttpRequest};
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use wapc::WasiParams;
use wapc_pool::{HostPool, HostPoolBuilder};
use crate::rpc::ModuleSender;
// mod actions;
// mod context;
// mod db;
mod mqtt;
// pub mod pay_u_adapter;
mod rpc;
// pub mod t_pay_adapter;
#[derive(Clone)]
pub enum WasmMsg {
CreateOrder {
create_payment: payment_adapter::CreatePayment,
tx: Sender<ResultMsg>,
},
UpdateStatus {
update_status: payment_adapter::UpdateStatus,
tx: Sender<ResultMsg>,
},
Cancel {
cancel: payment_adapter::RefundPayment,
tx: Sender<ResultMsg>,
},
}
#[derive(Clone)]
pub enum ResultMsg {
OrderCreated(payment_adapter::PaymentCreated),
StatusUpdated(payment_adapter::StatusUpdated),
Cancel(payment_adapter::Refunded),
}
pub struct HostChannel {
pub host: HostPool,
pub channel: std::sync::mpsc::Receiver<WasmMsg>,
}
impl HostChannel {
pub fn start(self) {
tokio::spawn(async move {
let HostChannel {
host: pool,
channel,
} = self;
loop {
let msg = match channel.recv() {
Ok(msg) => msg,
_ => continue,
};
match msg {
WasmMsg::CreateOrder {
create_payment: msg,
tx,
} => {
Self::send_and_rec(
"create_payment",
msg,
tx,
&pool,
ResultMsg::OrderCreated,
)
.await
.ok();
}
WasmMsg::UpdateStatus {
update_status: msg,
tx,
} => {
Self::send_and_rec(
"update_status",
msg,
tx,
&pool,
ResultMsg::StatusUpdated,
)
.await
.ok();
}
WasmMsg::Cancel { cancel: msg, tx } => {
Self::send_and_rec("cancel_order", msg, tx, &pool, ResultMsg::Cancel)
.await
.ok();
}
}
}
});
}
async fn send_and_rec<R, T, F>(
name: &str,
msg: R,
tx: Sender<ResultMsg>,
pool: &HostPool,
f: F,
) -> Result<(), ()>
where
R: serde::Serialize,
T: serde::de::DeserializeOwned,
F: FnOnce(T) -> ResultMsg,
{
macro_rules! ok_or_bail {
($run: expr) => {
match $run {
Ok(r) => r,
_ => return Err(()),
}
};
}
let msg = ok_or_bail!(wapc_codec::messagepack::serialize(msg));
let call_result = ok_or_bail!(pool.call(name, msg).await);
let result: T = ok_or_bail!(wapc_codec::messagepack::deserialize(&call_result));
if let Err(e) = tx.send(f(result)) {
tracing::error!("{e:?}");
Err(())
} else {
Ok(())
}
}
}
#[derive(Clone)]
pub struct Modules(Arc<std::sync::Mutex<HashMap<String, ModuleSender>>>);
impl Modules {
pub fn new(h: HashMap<String, ModuleSender>) -> Self {
Self(Arc::new(std::sync::Mutex::new(h)))
}
pub fn lock(&self) -> LockResult<MutexGuard<'_, HashMap<String, ModuleSender>>> {
self.0.lock()
}
}
#[derive(gumdrop::Options)]
pub struct Opts {
pub adapters_path: Option<PathBuf>,
}
impl UpdateConfig for Opts {
fn update_config(&self, config: &mut AppConfig) {
if let Some(path) = self.adapters_path.clone() {
config.payment_mut().adapters_path = Some(path);
}
}
}
#[tokio::main]
async fn main() {
config::init_tracing("payments");
let opts: Opts = gumdrop::parse_args_default_or_exit();
let config = config::default_load(&opts);
let files = scan_adapters_dir(&config);
let mut modules = HashMap::with_capacity(files.len());
load_adapters(&config, &mut modules, files).await;
// for pool in modules.values() {
// let msg = payment_adapter::CreatePayment {
// buyer: payment_adapter::Buyer {
// email: "hello@example.com".to_string(),
// phone: "530698478".to_string(),
// first_name: "Joe".to_string(),
// last_name: "Doe".to_string(),
// language: "pl".to_string(),
// },
// customer_ip: "12.22.34.54".to_string(),
// currency: "PLN".to_string(),
// description: "Nesciunt fugit libero quis dolorum quo.
// Tempore aut nisi voluptatem. Odio et aspernatur est. Sint vel
// molestias sunt cumque quibusdam reprehenderit est.".to_string(),
// cart_products: vec![Product {
// id: 23,
// name: "Socks".to_string(),
// unit_price: 1542,
// quantity_unit: "Unit".to_string(),
// quantity: 2,
// }],
// items: vec![Item {
// product_id: 23,
// quantity: 2,
// quantity_unit: "Unit".to_string(),
// }],
// order_ext_id: None,
// notify_uri: "https://localhost:3030/notify_uri".to_string(),
// continue_uri: "https://localhost:3030/continue_uri".to_string(),
// };
//
// tracing::info!("Start create_payment");
// let call_result = pool
// .call(
// "create_payment",
// wapc_codec::messagepack::serialize(msg).unwrap(),
// )
// .await
// .unwrap();
// let result: payment_adapter::OrderCreated =
// wapc_codec::messagepack::deserialize(&call_result).unwrap();
//
// tracing::info!("create payment res {:?}", result)
// }
let modules = Modules::new(modules);
let mqtt_client = mqtt::start(config.clone(), modules.clone()).await;
rpc::start(config, mqtt_client, modules.clone()).await;
}
async fn load_adapters(
config: &SharedAppConfig,
modules: &mut HashMap<String, ModuleSender>,
files: Vec<PathBuf>,
) {
for file in files {
let module = std::fs::read(&file).unwrap();
let engine = wasmtime_provider::WasmtimeEngineProviderBuilder::new()
.module_bytes(&module)
.wasi_params(WasiParams::default())
.build()
.unwrap();
let pool = HostPoolBuilder::new()
.name("pool")
.factory(move || {
wapc::WapcHost::new(
Box::new(engine.clone()),
Some(Box::new(
move |_a, binding, _namespace, msg_name, payload| {
Ok(host_call(binding, msg_name, payload)?)
},
)),
)
.unwrap()
})
.max_threads(5)
.build();
let name = pool
.call("name", vec![])
.await
.unwrap_or_else(|e| panic!("Failed to load adapter {file:?} `name`. {e}"));
let name: String = wapc_codec::messagepack::deserialize(&name)
.unwrap_or_else(|e| panic!("Adapter `name` ({name:?}) is not valid string. {e}"));
let msg = config
.lock()
.payment()
.providers
.get(&name)
.cloned()
.unwrap_or_default();
tracing::info!("Start init");
pool.call("init", wapc_codec::messagepack::serialize(msg).unwrap())
.await
.unwrap();
let (tx, rx) = std::sync::mpsc::channel();
HostChannel {
host: pool,
channel: rx,
}
.start();
modules.insert(name, ModuleSender::new(tx));
}
}
fn scan_adapters_dir(config: &SharedAppConfig) -> Vec<PathBuf> {
let adapters_path = config.lock().payment().adapters_path.clone();
let adapters = adapters_path.unwrap_or_else(|| panic!("No payment adapters path provided"));
let dir = read_dir(&adapters).unwrap_or_else(|e| {
panic!(
"Failed to load payment adapters at path {:?}. {}",
adapters, e
)
});
let files = dir
.filter_map(|r| r.map(|r| r.path()).ok())
.filter(|file| file.extension().and_then(|s| s.to_str()) == Some("wasm"))
.collect::<Vec<_>>();
if files.is_empty() {
panic!("No payment adapters found in adapters directory");
}
files
}
// use std::collections::HashMap;
// use std::fs::read_dir;
// use std::path::PathBuf;
// use std::str::FromStr;
// use std::sync::mpsc::Sender;
// use std::sync::{Arc, LockResult, MutexGuard};
//
// use config::{AppConfig, SharedAppConfig, UpdateConfig};
// use payment_adapter::{HttpMethod, HttpRequest};
// use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
// use wapc::WasiParams;
// use wapc_pool::{HostPool, HostPoolBuilder};
//
// use crate::rpc::ModuleSender;
//
// // mod actions;
// // mod context;
// // mod db;
// mod mqtt;
// // pub mod pay_u_adapter;
// mod rpc;
// // pub mod t_pay_adapter;
//
// #[derive(Clone)]
// pub enum WasmMsg {
// CreateOrder {
// create_payment: payment_adapter::CreatePayment,
// tx: Sender<ResultMsg>,
// },
// UpdateStatus {
// update_status: payment_adapter::UpdateStatus,
// tx: Sender<ResultMsg>,
// },
// Cancel {
// cancel: payment_adapter::RefundPayment,
// tx: Sender<ResultMsg>,
// },
// }
//
// #[derive(Clone)]
// pub enum ResultMsg {
// OrderCreated(payment_adapter::PaymentCreated),
// StatusUpdated(payment_adapter::StatusUpdated),
// Cancel(payment_adapter::Refunded),
// }
//
// pub struct HostChannel {
// pub host: HostPool,
// pub channel: std::sync::mpsc::Receiver<WasmMsg>,
// }
//
// impl HostChannel {
// pub fn start(self) {
// tokio::spawn(async move {
// let HostChannel {
// host: pool,
// channel,
// } = self;
//
// loop {
// let msg = match channel.recv() {
// Ok(msg) => msg,
// _ => continue,
// };
// match msg {
// WasmMsg::CreateOrder {
// create_payment: msg,
// tx,
// } => {
// Self::send_and_rec(
// "create_payment",
// msg,
// tx,
// &pool,
// ResultMsg::OrderCreated,
// )
// .await
// .ok();
// }
// WasmMsg::UpdateStatus {
// update_status: msg,
// tx,
// } => {
// Self::send_and_rec(
// "update_status",
// msg,
// tx,
// &pool,
// ResultMsg::StatusUpdated,
// )
// .await
// .ok();
// }
// WasmMsg::Cancel { cancel: msg, tx } => {
// Self::send_and_rec("cancel_order", msg, tx, &pool,
// ResultMsg::Cancel) .await
// .ok();
// }
// }
// }
// });
// }
//
// async fn send_and_rec<R, T, F>(
// name: &str,
// msg: R,
// tx: Sender<ResultMsg>,
// pool: &HostPool,
// f: F,
// ) -> Result<(), ()>
// where
// R: serde::Serialize,
// T: serde::de::DeserializeOwned,
// F: FnOnce(T) -> ResultMsg,
// {
// macro_rules! ok_or_bail {
// ($run: expr) => {
// match $run {
// Ok(r) => r,
// _ => return Err(()),
// }
// };
// }
//
// let msg = ok_or_bail!(wapc_codec::messagepack::serialize(msg));
// let call_result = ok_or_bail!(pool.call(name, msg).await);
// let result: T =
// ok_or_bail!(wapc_codec::messagepack::deserialize(&call_result)); if
// let Err(e) = tx.send(f(result)) { tracing::error!("{e:?}");
// Err(())
// } else {
// Ok(())
// }
// }
// }
//
// #[derive(Clone)]
// pub struct Modules(Arc<std::sync::Mutex<HashMap<String, ModuleSender>>>);
//
// impl Modules {
// pub fn new(h: HashMap<String, ModuleSender>) -> Self {
// Self(Arc::new(std::sync::Mutex::new(h)))
// }
//
// pub fn lock(&self) -> LockResult<MutexGuard<'_, HashMap<String,
// ModuleSender>>> { self.0.lock()
// }
// }
//
// #[derive(gumdrop::Options)]
// pub struct Opts {
// pub adapters_path: Option<PathBuf>,
// }
//
// impl UpdateConfig for Opts {
// fn update_config(&self, config: &mut AppConfig) {
// if let Some(path) = self.adapters_path.clone() {
// config.payment_mut().adapters_path = Some(path);
// }
// }
// }
//
// #[tokio::main]
// async fn main() {
// config::init_tracing("payments");
//
// let opts: Opts = gumdrop::parse_args_default_or_exit();
//
// let config = config::default_load(&opts);
// let files = scan_adapters_dir(&config);
// let mut modules = HashMap::with_capacity(files.len());
//
// load_adapters(&config, &mut modules, files).await;
//
// // for pool in modules.values() {
// // let msg = payment_adapter::CreatePayment {
// // buyer: payment_adapter::Buyer {
// // email: "hello@example.com".to_string(),
// // phone: "530698478".to_string(),
// // first_name: "Joe".to_string(),
// // last_name: "Doe".to_string(),
// // language: "pl".to_string(),
// // },
// // customer_ip: "12.22.34.54".to_string(),
// // currency: "PLN".to_string(),
// // description: "Nesciunt fugit libero quis dolorum quo.
// // Tempore aut nisi voluptatem. Odio et aspernatur est. Sint vel
// // molestias sunt cumque quibusdam reprehenderit est.".to_string(),
// // cart_products: vec![Product {
// // id: 23,
// // name: "Socks".to_string(),
// // unit_price: 1542,
// // quantity_unit: "Unit".to_string(),
// // quantity: 2,
// // }],
// // items: vec![Item {
// // product_id: 23,
// // quantity: 2,
// // quantity_unit: "Unit".to_string(),
// // }],
// // order_ext_id: None,
// // notify_uri: "https://localhost:3030/notify_uri".to_string(),
// // continue_uri: "https://localhost:3030/continue_uri".to_string(),
// // };
// //
// // tracing::info!("Start create_payment");
// // let call_result = pool
// // .call(
// // "create_payment",
// // wapc_codec::messagepack::serialize(msg).unwrap(),
// // )
// // .await
// // .unwrap();
// // let result: payment_adapter::OrderCreated =
// // wapc_codec::messagepack::deserialize(&call_result).unwrap();
// //
// // tracing::info!("create payment res {:?}", result)
// // }
//
// let modules = Modules::new(modules);
// let mqtt_client = mqtt::start(config.clone(), modules.clone()).await;
// rpc::start(config, mqtt_client, modules.clone()).await;
// }
//
// async fn load_adapters(
// config: &SharedAppConfig,
// modules: &mut HashMap<String, ModuleSender>,
// files: Vec<PathBuf>,
// ) {
// for file in files {
// let module = std::fs::read(&file).unwrap();
// let engine = wasmtime_provider::WasmtimeEngineProviderBuilder::new()
// .module_bytes(&module)
// .wasi_params(WasiParams::default())
// .build()
// .unwrap();
//
// let pool = HostPoolBuilder::new()
// .name("pool")
// .factory(move || {
// wapc::WapcHost::new(
// Box::new(engine.clone()),
// Some(Box::new(
// move |_a, binding, _namespace, msg_name, payload| {
// Ok(host_call(binding, msg_name, payload)?)
// },
// )),
// )
// .unwrap()
// })
// .max_threads(5)
// .build();
// let name = pool
// .call("name", vec![])
// .await
// .unwrap_or_else(|e| panic!("Failed to load adapter {file:?}
// `name`. {e}")); let name: String =
// wapc_codec::messagepack::deserialize(&name) .unwrap_or_else(|e|
// panic!("Adapter `name` ({name:?}) is not valid string. {e}"));
//
// let msg = config
// .lock()
// .payment()
// .providers
// .get(&name)
// .cloned()
// .unwrap_or_default();
//
// tracing::info!("Start init");
// pool.call("init", wapc_codec::messagepack::serialize(msg).unwrap())
// .await
// .unwrap();
//
// let (tx, rx) = std::sync::mpsc::channel();
// HostChannel {
// host: pool,
// channel: rx,
// }
// .start();
//
// modules.insert(name, ModuleSender::new(tx));
// }
// }
//
// fn scan_adapters_dir(config: &SharedAppConfig) -> Vec<PathBuf> {
// let adapters_path = config.lock().payment().adapters_path.clone();
// let adapters = adapters_path.unwrap_or_else(|| panic!("No payment
// adapters path provided")); let dir =
// read_dir(&adapters).unwrap_or_else(|e| { panic!(
// "Failed to load payment adapters at path {:?}. {}",
// adapters, e
// )
// });
// let files = dir
// .filter_map(|r| r.map(|r| r.path()).ok())
// .filter(|file| file.extension().and_then(|s| s.to_str()) ==
// Some("wasm")) .collect::<Vec<_>>();
//
// if files.is_empty() {
// panic!("No payment adapters found in adapters directory");
// }
// files
// }
//
// // #[tracing::instrument]
// fn host_call(binding: &str, name: &str, payload: &[u8]) -> Result<Vec<u8>,
// payment_adapter::Error> { match name {
// "http_req" => {
// let req: HttpRequest = match
// wapc_codec::messagepack::deserialize(payload) { Ok(req) =>
// req, Err(e) => {
// tracing::error!("{:?} payload {:?}", binding, payload);
// tracing::error!("Failed to deserialize. {}", e);
// return
// Err(payment_adapter::Error::MsgPackDeserializationFailed); }
// };
// http_request(req)
// }
// _ => Err(payment_adapter::Error::UnknownOperation(name.to_string())),
// }
// }
//
// #[tracing::instrument]
fn host_call(binding: &str, name: &str, payload: &[u8]) -> Result<Vec<u8>, payment_adapter::Error> {
match name {
"http_req" => {
let req: HttpRequest = match wapc_codec::messagepack::deserialize(payload) {
Ok(req) => req,
Err(e) => {
tracing::error!("{:?} payload {:?}", binding, payload);
tracing::error!("Failed to deserialize. {}", e);
return Err(payment_adapter::Error::MsgPackDeserializationFailed);
}
};
http_request(req)
}
_ => Err(payment_adapter::Error::UnknownOperation(name.to_string())),
}
}
// fn http_request(req: HttpRequest) -> Result<Vec<u8>, payment_adapter::Error>
// { tracing::info!("{:?}", req);
// let HttpRequest {
// method,
// url,
// headers,
// bearer_auth,
// body,
// } = req;
// let client = reqwest::blocking::ClientBuilder::default()
// .user_agent("curl/7.82.0")
// // Do not follow redirect!
// .redirect(reqwest::redirect::Policy::none())
// .connection_verbose(true)
// .build()
// .expect("Failed to create client");
//
// let headers = {
// let len = headers.len();
// headers
// .into_iter()
// .fold(HeaderMap::with_capacity(len), |mut m, (k, v)| {
// if let (Ok(v), Ok(k)) =
// (HeaderValue::from_str(v.as_str()),
// HeaderName::from_str(&k)) {
// m.insert(k, v);
// }
// m
// })
// };
//
// let response = match (method, bearer_auth) {
// (HttpMethod::Get, Some(bearer)) => client
// .get(url)
// .headers(headers)
// .bearer_auth(bearer)
// .send()
// .map_err(|e| {
// tracing::error!("{}", e);
// payment_adapter::Error::HttpFailed
// })?,
// (HttpMethod::Get, _) =>
// client.get(url).headers(headers).send().map_err(|e| {
// tracing::error!("{}", e); payment_adapter::Error::HttpFailed
// })?,
// (HttpMethod::Post, Some(bearer)) => client
// .post(url)
// .headers(headers)
// .bearer_auth(bearer)
// .body(body.unwrap_or_default())
// .send()
// .map_err(|e| {
// tracing::error!("{}", e);
// payment_adapter::Error::HttpFailed
// })?,
// (HttpMethod::Post, None) => client
// .post(url)
// .headers(headers)
// .body(body.unwrap_or_default())
// .send()
// .map_err(|e| {
// eprintln!("POST NONE {}", e);
// tracing::error!("{}", e);
// payment_adapter::Error::HttpFailed
// })?,
// };
// tracing::info!("HTTP response status {:?}", response.status());
// let text = response.bytes().map_err(|e| {
// tracing::error!("{}", e);
// payment_adapter::Error::HttpFailed
// })?;
// tracing::info!("HTTP Result {:?}", text);
// Ok(text.to_vec())
// }
#[tracing::instrument]
fn http_request(req: HttpRequest) -> Result<Vec<u8>, payment_adapter::Error> {
tracing::info!("{:?}", req);
let HttpRequest {
method,
url,
headers,
bearer_auth,
body,
} = req;
let client = reqwest::blocking::ClientBuilder::default()
.user_agent("curl/7.82.0")
// Do not follow redirect!
.redirect(reqwest::redirect::Policy::none())
.connection_verbose(true)
.build()
.expect("Failed to create client");
let headers = {
let len = headers.len();
headers
.into_iter()
.fold(HeaderMap::with_capacity(len), |mut m, (k, v)| {
if let (Ok(v), Ok(k)) =
(HeaderValue::from_str(v.as_str()), HeaderName::from_str(&k))
{
m.insert(k, v);
}
m
})
};
let response = match (method, bearer_auth) {
(HttpMethod::Get, Some(bearer)) => client
.get(url)
.headers(headers)
.bearer_auth(bearer)
.send()
.map_err(|e| {
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?,
(HttpMethod::Get, _) => client.get(url).headers(headers).send().map_err(|e| {
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?,
(HttpMethod::Post, Some(bearer)) => client
.post(url)
.headers(headers)
.bearer_auth(bearer)
.body(body.unwrap_or_default())
.send()
.map_err(|e| {
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?,
(HttpMethod::Post, None) => client
.post(url)
.headers(headers)
.body(body.unwrap_or_default())
.send()
.map_err(|e| {
eprintln!("POST NONE {}", e);
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?,
};
tracing::info!("HTTP response status {:?}", response.status());
let text = response.bytes().map_err(|e| {
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?;
tracing::info!("HTTP Result {:?}", text);
Ok(text.to_vec())
}
fn main() {}

View File

@ -7,7 +7,7 @@ use config::SharedAppConfig;
use payment_adapter::RefundPayment;
use tarpc::context;
use crate::{Modules, ResultMsg, WasmMsg};
use crate::{Modules, ResultMsg};
#[derive(Clone)]
pub struct ModuleSender {
@ -59,7 +59,7 @@ impl PaymentsServer {
.clone())
}
}
/*
#[tarpc::server]
impl Payments for PaymentsServer {
async fn start_payment(
@ -156,3 +156,4 @@ pub async fn start(config: SharedAppConfig, mqtt_client: AsyncClient, modules: M
})
.await;
}
*/

View File

@ -13,6 +13,7 @@ rustflags = ["-C", "prefer-dynamic", "-C", "rpath"]
payment_adapter = { path = "../payment_adapter" }
fulfillment_adapter = { path = "../fulfillment_adapter" }
tokio = { version = "1.27.0" }
payup = { version = "*" }
payup = { version = "*", features = [] }
tracing = { version = "0.1.37" }
async-trait = { version = "0.1.68" }
serde = { version = "1.0.162", features = ['derive'] }

View File

@ -1,126 +1,242 @@
#![crate_type = "rlib"]
use fulfillment_adapter::{
FError, FResult, FulfillmentAdapter, FulfillmentCart, FulfillmentOption, FulfillmentPayload,
};
use payment_adapter::{
CreatePayment, DeletePayment, Deleted, PResult, PaymentAdapter, PaymentCreated,
PaymentSessionStatus, RefundPayment, Refunded, Status,
};
use std::any::Any;
use std::collections::HashMap;
use std::ops::DerefMut;
pub struct StripeAdapter {
stripe: payup::stripe::Auth,
use fulfillment_adapter::*;
use payment_adapter::*;
use payup::stripe;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub enum CaptureMethod {
Automatic,
Manual,
}
impl StripeAdapter {
fn payment_status(&mut self, ext_id: String) -> FResult<PaymentSessionStatus> {
let charge = payup::stripe::Charge::get(self.stripe.clone(), ext_id).map_err(|e| {
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub enum SetupFutureUsage {
OnSession,
OffSession,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct StripeConfig {
pub api_key: String,
pub client: String,
}
#[derive(Debug)]
pub struct StripeIntent {
pub capture_method: CaptureMethod,
pub setup_future_usage: SetupFutureUsage,
pub payment_method_types: &'static [&'static str],
}
pub struct StripeAdapter {
stripe: stripe::Auth,
config: StripeConfig,
}
async fn payment_status(stripe: stripe::Auth, ext_id: String) -> FResult<PaymentSessionStatus> {
let charge: stripe::Charge = stripe::Charge::async_get(stripe, ext_id)
.await
.map_err(|e| {
tracing::warn!("{e}");
FError::HttpError
})?;
Ok(match charge.status.as_deref() {
Some("requires_payment_method" | "requires_confirmation" | "processing") => {
PaymentSessionStatus::Pending
}
Some("requires_action") => PaymentSessionStatus::RequiresMore,
Some("canceled") => PaymentSessionStatus::Canceled,
Some("requires_capture" | "succeeded") => PaymentSessionStatus::Authorized,
_ => PaymentSessionStatus::Pending,
Ok(match charge.status.as_deref() {
Some("requires_payment_method" | "requires_confirmation" | "processing") => {
PaymentSessionStatus::Pending
}
Some("requires_action") => PaymentSessionStatus::RequiresMore,
Some("canceled") => PaymentSessionStatus::Canceled,
Some("requires_capture" | "succeeded") => PaymentSessionStatus::Authorized,
_ => PaymentSessionStatus::Pending,
})
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct StripePrzelewy24Config {
pub api_key: String,
pub client: String,
pub webhook_secret: String,
pub capture: Option<bool>,
pub automatic_payment_methods: Option<bool>,
pub payment_description: Option<String>,
}
pub struct StripePrzelewy24 {
stripe: stripe::Auth,
config: StripePrzelewy24Config,
}
#[async_trait::async_trait]
impl PaymentAdapter for StripePrzelewy24 {
async fn new(config: Config) -> Self {
let config: StripePrzelewy24Config =
config.config().expect("Malformed Stripe Przelewy24 config");
let stripe = stripe::Auth::new(config.client.clone(), config.api_key.clone());
Self { config, stripe }
}
async fn initialize_payment(
&mut self,
ctx: PaymentProcessorContext,
) -> PResult<PaymentProcessorSessionResponse> {
let PaymentProcessorContext {
billing_address: _,
email,
currency_code,
amount,
resource_id: _,
customer,
context,
payment_session_data,
} = ctx;
let desc = context
.0
.get("payment_description")
.and_then(|v| String::from_utf8(v.0.clone()).ok())
.or_else(|| self.config.payment_description.clone());
let change: stripe::Charge = stripe::Charge {
amount: Some(amount.0.to_string()),
captured: self.config.capture.clone(),
currency: Some(currency_code.into()),
description: desc,
customer: customer.map(|c| c.id.0.to_string()),
receipt_email: Some(email.0),
..stripe::Charge::new()
}
.async_post(self.stripe.clone())
.await
.unwrap();
let update_requests = match customer {
Some(c) if !c.has_stripe_id() => Some(UpdateRequests {
customer_metadata: Some(CustomerMetadata(HashMap::from([(
"id".to_string(),
AnyData(change.customer.unwrap_or_default().into_bytes()),
)]))),
}),
_ => None,
};
Ok(PaymentProcessorSessionResponse {
update_requests,
session_data: payment_session_data,
})
}
}
impl PaymentAdapter for StripeAdapter {
fn init(&mut self) -> PResult<Status> {
todo!()
}
fn teardown(&mut self) -> PResult<Status> {
todo!()
}
async fn create_payment(&mut self, msg: CreatePayment) -> PResult<PaymentCreated> {
todo!()
}
async fn retrieve_payment(&mut self) {
todo!()
}
async fn authorize_payment(&mut self) {
todo!()
}
fn capture_payment(&mut self) {
todo!()
}
async fn refund_payment(&mut self, msg: RefundPayment) -> PResult<Refunded> {
todo!()
}
async fn delete_payment(&mut self, msg: DeletePayment) -> PResult<Deleted> {
todo!()
}
}
impl FulfillmentAdapter for StripeAdapter {
fn identifier() -> &'static str {
"stripe"
}
fn fulfillment_options(&mut self) -> FResult<&[FulfillmentOption]> {
todo!()
}
fn validate_fulfillment_payload<P: FulfillmentPayload, C: FulfillmentCart>(
async fn update_payment(
&mut self,
payload: P,
cart: C,
) -> FResult<bool> {
ctx: PaymentProcessorContext,
) -> PResult<Option<PaymentProcessorSessionResponse>> {
todo!()
}
fn validate_option<P: FulfillmentPayload>(&mut self, payload: P) -> FResult<bool> {
async fn refund_payment(
&mut self,
payment_session_data: PaymentSessionData,
refund_amount: Amount,
) -> PResult<PaymentSessionData> {
todo!()
}
fn can_calculate<P: FulfillmentPayload>(&mut self, payload: P) -> FResult<bool> {
async fn authorize_payment(
&mut self,
payment_session_data: PaymentSessionData,
data: PaymentProcessCtx,
) -> PResult<(PaymentSessionStatus, PaymentSessionData)> {
self.payment_status(&payment_session_data)
.await
.map(|status| (status, payment_session_data))
}
async fn capture_payment(
&mut self,
mut payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData> {
let id = payment_session_data
.id
.clone()
.ok_or_else(|| PError::NoChargeId)?;
let charge = stripe::Charge::async_get(self.stripe.clone(), id.clone())
.await
.map_err(|_| PError::ChargeNotExists(id.clone()))?;
let change = charge
.async_capture(self.stripe.clone())
.await
.map_err(|e| {
tracing::warn!("{e}");
PError::FailedCapture(id)
})?;
payment_session_data.id = charge.id;
Ok(payment_session_data)
}
async fn delete_payment(
&mut self,
payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData> {
todo!()
}
fn calculate_price<P: FulfillmentPayload, C: FulfillmentCart>(
data: P,
cart: C,
) -> FResult<u64> {
async fn retrieve_payment(
&mut self,
payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData> {
todo!()
}
fn create_fulfillment(&mut self) {
async fn cancel_payment(
&mut self,
payment_session_data: PaymentSessionData,
) -> PResult<PaymentSessionData> {
todo!()
}
fn cancel_fulfillment(&mut self) {
todo!()
}
fn fulfillment_documents(&mut self) {
todo!()
}
fn create_return(&mut self) {
todo!()
}
fn return_documents(&mut self) {
todo!()
}
fn shipment_documents(&mut self) {
todo!()
}
fn documents(&mut self) {
todo!()
async fn payment_status(
&mut self,
payment_session_data: &PaymentSessionData,
) -> PResult<PaymentSessionStatus> {
payment_status(
self.stripe.clone(),
payment_session_data.id.clone().unwrap_or_default(),
)
.await
.map_err(|_| PError::HttpError)
}
}
impl StripePrzelewy24 {
pub fn payment_intent_options() -> StripeIntent {
StripeIntent {
capture_method: CaptureMethod::Automatic,
setup_future_usage: SetupFutureUsage::OnSession,
payment_method_types: &["p24"],
}
}
}
pub trait StripeCustomerMetadata {
fn has_stripe_id(&self) -> bool;
fn stripe_id(&self) -> Option<String>;
}
impl StripeCustomerMetadata for Customer {
fn has_stripe_id(&self) -> bool {
self.metadata
.as_ref()
.map_or_else(|| false, |h| h.contains_key("stripe_id"))
}
fn stripe_id(&self) -> Option<String> {
self.metadata
.as_ref()?
.get("stripe_id")
.and_then(|v| String::from_utf8(v.clone()).ok())
}
}