use super::errors::Error;
use chrono::{DateTime, Utc};
use rsa::{errors::Error as RSAError, RSAPrivateKey};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fs::read_to_string;
use std::path::PathBuf;
use std::string::ToString;
use strum_macros::Display;
pub struct AlipayClientSecret {
pub client_id: String,
pub sandbox: bool,
pub private_key_pem: Option<String>,
pub private_key_pem_file: Option<Box<PathBuf>>,
}
impl HasPrivateKey for AlipayClientSecret {
fn get_private_key(&self) -> Result<RSAPrivateKey, RSAError> {
let der_bytes: Vec<u8>;
if (self.private_key_pem_file.is_some()) {
let s = read_to_string(self.private_key_pem_file.clone().unwrap().as_path()).unwrap();
der_bytes = base64::decode(s).expect("failed to decode base64 content");
} else {
der_bytes = base64::decode(&self.private_key_pem.clone().unwrap())
.expect("failed to decode base64 content");
}
return RSAPrivateKey::from_pkcs1(&der_bytes);
}
}
#[derive(Serialize)]
pub struct CashierPaymentSimple {
pub payment_request_id: String,
pub currency: String,
pub amount: i32,
pub redict_url: String,
pub notifiy_url: String,
pub order_description: String,
pub reference_order_id: Option<String>,
pub terminal_type: Option<TerminalType>,
}
pub trait Signable {
fn get_value(&self) -> Value;
}
pub trait HasPrivateKey {
fn get_private_key(&self) -> Result<RSAPrivateKey, RSAError>;
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CashierPaymentFull {
pub product_code: String,
pub payment_request_id: String,
pub order: Order,
pub payment_amount: Amount,
pub payment_method: PaymentMethod,
pub payment_redirect_url: String,
pub payment_notify_url: String,
pub settlement_strategy: SettlementStrategy,
pub env: Env,
}
impl CashierPaymentFull {
pub fn to_string(&self) -> String {
serde_json::to_value(self).unwrap().to_string()
}
}
impl From<&CashierPaymentSimple> for CashierPaymentFull {
fn from(value: &CashierPaymentSimple) -> Self {
let CashierPaymentSimple {
payment_request_id,
redict_url,
notifiy_url,
..
} = value;
let order = Order::from(value);
let payment_amount = Amount::from(value);
let payment_method = PaymentMethod::from(value);
let settlement_strategy = SettlementStrategy::from(value);
let env = Env::from(value);
Self {
product_code: String::from("CASHIER_PAYMENT"),
payment_request_id: payment_request_id.clone(),
order,
payment_amount,
payment_method,
payment_redirect_url: redict_url.clone(),
payment_notify_url: notifiy_url.clone(),
settlement_strategy,
env,
}
}
}
impl Signable for CashierPaymentFull {
fn get_value(&self) -> Value {
serde_json::to_value(self).unwrap()
}
}
pub struct RequestEnv {
pub path: String,
pub domain: String,
}
impl From<&AlipayClientSecret> for RequestEnv {
fn from(value: &AlipayClientSecret) -> Self {
if value.sandbox {
Self {
path: String::from("/ams/sandbox/api/v1/payments/pay"),
domain: String::from("https://open-global.alipay.com"),
}
} else {
Self {
path: String::from("/ams/api/v1/payments/pay"),
domain: String::from("https://open-global.alipay.com"),
}
}
}
}
impl RequestEnv {
pub fn get_request_url(&self) -> String {
self.domain.clone() + &self.path
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Env {
terminal_type: TerminalType,
}
impl From<&CashierPaymentSimple> for Env {
fn from(value: &CashierPaymentSimple) -> Self {
let CashierPaymentSimple { terminal_type, .. } = value;
let tt = terminal_type.clone().unwrap_or(TerminalType::WEB);
Self { terminal_type: tt }
}
}
#[derive(Serialize, Clone, Copy, PartialEq, Eq)]
pub enum TerminalType {
WEB,
WAP,
APP,
MINI_APP,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Amount {
pub currency: String,
value: String,
}
impl Amount {
pub fn value(&self) -> u32 {
self.value.parse().unwrap()
}
}
impl From<&CashierPaymentSimple> for Amount {
fn from(value: &CashierPaymentSimple) -> Self {
let CashierPaymentSimple {
currency, amount, ..
} = value;
Self {
value: amount.to_string().clone(),
currency: currency.clone(),
}
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentMethod {
pub payment_method_type: String,
}
impl From<&CashierPaymentSimple> for PaymentMethod {
fn from(value: &CashierPaymentSimple) -> Self {
Self {
payment_method_type: String::from("ALIPAY_CN"),
}
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Order {
pub order_amount: Amount,
pub order_description: String,
pub reference_order_id: String,
}
impl From<&CashierPaymentSimple> for Order {
fn from(value: &CashierPaymentSimple) -> Self {
let CashierPaymentSimple {
reference_order_id,
order_description,
..
} = value;
let roi = reference_order_id.clone().unwrap_or(String::from(""));
let od = order_description.clone();
let order_amount = Amount::from(value);
Self {
order_amount,
order_description: od,
reference_order_id: roi,
}
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SettlementStrategy {
settlement_currency: String,
}
impl From<&CashierPaymentSimple> for SettlementStrategy {
fn from(value: &CashierPaymentSimple) -> Self {
let CashierPaymentSimple { currency, .. } = value;
Self {
settlement_currency: currency.clone(),
}
}
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Response {
result: ResponseResult,
payment_request_id: Option<String>,
payment_id: Option<String>,
payment_amount: Option<Amount>,
payment_data: Option<String>,
payment_create_time: Option<DateTime<Utc>>,
psp_customer_info: Option<PspCustomerInfo>,
order_code_form: Option<OrderCodeForm>,
gross_settlement_amount: Option<Amount>,
settlement_quote: Option<SettlementQuote>,
app_identifier: Option<String>,
applink_url: Option<String>,
normal_url: Option<String>,
scheme_url: Option<String>,
payment_result_info: Option<PaymentResultInfo>,
}
impl Response {
pub fn result(&self) -> &ResponseResult {
&self.result
}
pub fn payment_request_id(&self) -> &Option<String> {
&self.payment_request_id
}
pub fn payment_id(&self) -> &Option<String> {
&self.payment_id
}
pub fn is_success(&self) -> bool {
self.result.result_status == ResultStatus::S
}
pub fn is_processing(&self) -> bool {
self.result.result_code == ResultCode::PAYMENT_IN_PROCESS
}
pub fn get_error(&self) -> Option<Error> {
self.result.get_error()
}
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ResponseResult {
result_code: ResultCode,
result_status: ResultStatus,
result_message: String,
}
impl ResponseResult {
pub fn get_error(&self) -> Option<Error> {
if self.result_code == ResultCode::PAYMENT_IN_PROCESS {
return None;
} else if self.result_code == ResultCode::UNKNOWN_EXCEPTION {
return Some(Error::Unknown(format!("{}: {}", self.result_code.to_string(), "You should just retry. This error is very normal due to unstable service of Alipay Global. This could happen even when you have all the argument correct. Just retry.")));
}
match self.result_status {
ResultStatus::S => None,
ResultStatus::F => Some(Error::Fail(self.result_code.to_string())),
ResultStatus::U => Some(Error::Unknown(self.result_code.to_string())),
}
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Display)]
pub enum ResultCode {
SUCCESS,
ACCESS_DENIED,
INVALID_API,
CURRENCY_NOT_SUPPORT,
EXPIRED_CODE,
FRAUD_REJECT,
INVALID_ACCESS_TOKEN,
INVALID_CONTRACT,
INVALID_MERCHANT_STATUS,
INVALID_PAYMENT_CODE,
INVALID_PAYMENT_METHOD_META_DATA,
KEY_NOT_FOUND,
MERCHANT_KYB_NOT_QUALIFIED,
MERCHANT_NOT_REGISTERED,
NO_INTERFACE_DEF,
NO_PAY_OPTIONS,
ORDER_IS_CANCELED,
ORDER_IS_CLOSED,
PARAM_ILLEGAL,
PAYMENT_AMOUNT_EXCEED_LIMIT,
PAYMENT_COUNT_EXCEED_LIMIT,
PAYMENT_NOT_QUALIFIED,
PROCESS_FAIL,
REPEAT_REQ_INCONSISTENT,
RISK_REJECT,
SETTLE_CONTRACT_NOT_MATCH,
SYSTEM_ERROR,
USER_AMOUNT_EXCEED_LIMIT,
USER_BALANCE_NOT_ENOUGH,
USER_KYC_NOT_QUALIFIED,
PAYMENT_IN_PROCESS,
REQUEST_TRAFFIC_EXCEED_LIMIT,
UNKNOWN_EXCEPTION,
USER_NOT_EXIST,
ORDER_NOT_EXIST,
ORDER_STATUS_INVALID,
USER_PAYMENT_VERIFICATION_FAILED,
USER_STATUS_ABNORMAL,
VERIFY_TIMES_EXCEED_LIMIT,
VERIFY_UNMATCHED,
AUTHENTICATION_REQUIRED,
SELECTED_CARD_BRAND_NOT_AVAILABLE,
PAYMENT_PROHIBITED,
}
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResultStatus {
S,
F,
U,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct OrderCodeForm {
expire_time: chrono::DateTime<chrono::Utc>,
code_details: Vec<CodeDetail>,
extend_info: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CodeDetail {
code_value: String,
display_type: DisplayType,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PspCustomerInfo {
psp_name: Option<String>,
psp_customer_id: Option<String>,
display_customer_id: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum DisplayType {
TEXT,
MIDDLEIMAGE,
SMALLIMAGE,
BIGIMAGE,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SettlementQuote {
guaranteed: Option<bool>,
quote_id: Option<String>,
quote_currency_pair: String,
quote_price: i32,
quote_start_time: Option<DateTime<Utc>>,
quote_expiry_time: Option<DateTime<Utc>>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PaymentResultInfo {
avs_result_raw: Option<String>,
cvv_result_raw: Option<String>,
network_transaction_id: Option<String>,
}