1use super::errors::Error;
2use base64::Engine;
3use chrono::{DateTime, Utc};
4use juniper::{GraphQLEnum, GraphQLObject};
5use rsa::{
6 pkcs1::{DecodeRsaPrivateKey, Error as Pkcs1Error},
7 pkcs8::{spki::Error as Pkcs8Error, DecodePublicKey},
8 Hash, PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey,
9};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use std::fs::read_to_string;
13use std::path::PathBuf;
14use std::string::ToString;
15use strum_macros::Display;
16pub enum AlipayAction {
19 PAY,
20 REFUND,
21 INQUIRY,
22}
23
24impl ToString for AlipayAction {
25 fn to_string(&self) -> String {
26 match self {
27 AlipayAction::PAY => String::from("pay"),
28 AlipayAction::REFUND => String::from("refund"),
29 AlipayAction::INQUIRY => String::from("inquiryPayment"),
30 }
31 }
32}
33
34pub struct AlipayClientSecret {
36 pub action: AlipayAction,
37 pub client_id: String,
38 pub sandbox: bool,
39 pub private_key_pem: Option<String>,
40 pub private_key_pem_file: Option<Box<PathBuf>>,
41 pub alipay_public_key_pem: Option<String>,
42 pub alipay_public_key_pem_file: Option<Box<PathBuf>>,
43}
44
45impl HasPrivateKey for AlipayClientSecret {
46 fn get_private_key(&self) -> Result<RsaPrivateKey, Pkcs1Error> {
47 let key: String;
48 if (self.private_key_pem_file.is_some()) {
49 let s = read_to_string(self.private_key_pem_file.clone().unwrap().as_path()).unwrap();
50 key = s;
51 } else {
52 key = format_pkcs1_private_key(&self.private_key_pem.clone().unwrap());
53 }
54
55 load_private_key(&key)
57 }
58}
59
60impl HasPublicKey for AlipayClientSecret {
61 fn get_public_key(&self) -> Result<RsaPublicKey, Pkcs8Error> {
62 let key: String;
63 if (self.alipay_public_key_pem_file.is_some()) {
64 let s =
65 read_to_string(self.alipay_public_key_pem_file.clone().unwrap().as_path()).unwrap();
66 key = s;
67 } else {
68 key = format_pem_public_key(&self.alipay_public_key_pem.clone().unwrap());
69 }
70
71 load_public_key(&key)
73 }
74}
75
76pub fn load_private_key(private_key_str: &str) -> Result<RsaPrivateKey, Pkcs1Error> {
77 RsaPrivateKey::from_pkcs1_pem(&private_key_str)
78}
79
80pub fn load_public_key(public_key_str: &str) -> Result<RsaPublicKey, Pkcs8Error> {
81 RsaPublicKey::from_public_key_pem(&public_key_str)
82}
83
84const PUBLIC_KEY_PREFIX: &str = "-----BEGIN PUBLIC KEY-----";
85const PUBLIC_KEY_SUFFIX: &str = "-----END PUBLIC KEY-----";
86
87const PKCS1_PREFIX: &str = "-----BEGIN RSA PRIVATE KEY-----";
88const PKCS1_SUFFIX: &str = "-----END RSA PRIVATE KEY-----";
89
90const PKCS8_PREFIX: &str = "-----BEGIN PRIVATE KEY-----";
91const PKCS8_SUFFIX: &str = "-----END PRIVATE KEY-----";
92
93pub fn format_pkcs1_private_key(raw: &str) -> String {
94 format_key(raw, PKCS1_PREFIX, PKCS1_SUFFIX, 64)
95}
96
97pub fn format_pkcs8_private_key(raw: &str) -> String {
98 format_key(raw, PKCS8_PREFIX, PKCS8_SUFFIX, 64)
99}
100
101pub fn format_pem_public_key(raw: &str) -> String {
102 format_key(raw, PUBLIC_KEY_PREFIX, PUBLIC_KEY_SUFFIX, 64)
103}
104
105fn format_key(raw: &str, prefix: &str, suffix: &str, line_count: usize) -> String {
106 let mut buffer = Vec::new();
107 buffer.append(prefix.as_bytes().to_vec().as_mut());
108 buffer.append("\n".as_bytes().to_vec().as_mut());
109 let raw_len = line_count;
110 let key_len = raw.len();
111 let mut raws = key_len / raw_len;
112 let temp = key_len % raw_len;
113 if temp > 0 {
114 raws += 1;
115 }
116 let mut start = 0;
117 let mut end = start + raw_len;
118 for i in 0..raws {
119 if i == raws - 1 {
120 buffer.append(raw.get(start..).unwrap().as_bytes().to_vec().as_mut());
121 } else {
122 buffer.append(raw.get(start..end).unwrap().as_bytes().to_vec().as_mut());
123 }
124 buffer.append("\n".as_bytes().to_vec().as_mut());
125 start += raw_len;
126 end = start + raw_len
127 }
128 buffer.append(suffix.as_bytes().to_vec().as_mut());
129 buffer.append("\n".as_bytes().to_vec().as_mut());
130 String::from_utf8(buffer.clone()).unwrap()
131}
132
133#[derive(Serialize)]
135pub struct CashierPaymentSimple {
136 pub payment_request_id: String,
137 pub currency: String,
138 pub amount: i32,
139 pub redict_url: String,
140 pub notifiy_url: String,
141 pub order_description: String,
142 pub reference_order_id: Option<String>,
143 pub terminal_type: Option<TerminalType>,
144}
145
146pub trait Signable {
148 fn get_value(&self) -> Value;
149}
150
151pub trait HasPrivateKey {
153 fn get_private_key(&self) -> Result<RsaPrivateKey, Pkcs1Error>;
154}
155
156pub trait HasPublicKey {
157 fn get_public_key(&self) -> Result<RsaPublicKey, Pkcs8Error>;
158}
159
160#[derive(Serialize)]
171#[serde(rename_all = "camelCase")]
172pub struct CashierPaymentFull {
173 pub product_code: String,
175 pub payment_request_id: String,
180 pub order: Order,
181 pub payment_amount: Amount,
182 pub payment_method: PaymentMethod,
183 pub payment_redirect_url: String,
184 pub payment_notify_url: String,
185 pub settlement_strategy: SettlementStrategy,
186 pub env: Env,
187}
188
189impl CashierPaymentFull {
190 pub fn to_string(&self) -> String {
191 serde_json::to_value(self).unwrap().to_string()
192 }
193}
194
195impl From<&CashierPaymentSimple> for CashierPaymentFull {
196 fn from(value: &CashierPaymentSimple) -> Self {
197 let CashierPaymentSimple {
198 payment_request_id,
199 redict_url,
200 notifiy_url,
201 ..
202 } = value;
203 let order = Order::from(value);
204 let payment_amount = Amount::from(value);
205 let payment_method = PaymentMethod::from(value);
206 let settlement_strategy = SettlementStrategy::from(value);
207 let env = Env::from(value);
208 Self {
209 product_code: String::from("CASHIER_PAYMENT"),
210 payment_request_id: payment_request_id.clone(),
211 order,
212 payment_amount,
213 payment_method,
214 payment_redirect_url: redict_url.clone(),
215 payment_notify_url: notifiy_url.clone(),
216 settlement_strategy,
217 env,
218 }
219 }
220}
221
222impl Signable for CashierPaymentFull {
223 fn get_value(&self) -> Value {
224 serde_json::to_value(self).unwrap()
225 }
226}
227
228pub struct RequestEnv {
229 pub path: String,
230 pub domain: String,
231}
232impl From<&AlipayClientSecret> for RequestEnv {
233 fn from(value: &AlipayClientSecret) -> Self {
234 if value.sandbox {
235 Self {
236 path: String::from(format!(
237 "/ams/sandbox/api/v1/payments/{}",
238 value.action.to_string()
239 )),
240 domain: String::from("https://open-global.alipay.com"),
241 }
242 } else {
243 Self {
244 path: String::from(format!("/ams/api/v1/payments/{}", value.action.to_string())),
245 domain: String::from("https://open-global.alipay.com"),
246 }
247 }
248 }
249}
250
251impl RequestEnv {
252 pub fn get_request_url(&self) -> String {
253 self.domain.clone() + self.path.as_str()
254 }
255}
256
257#[derive(Serialize)]
274#[serde(rename_all = "camelCase")]
275pub struct Env {
276 terminal_type: TerminalType,
282}
283
284impl From<&CashierPaymentSimple> for Env {
285 fn from(value: &CashierPaymentSimple) -> Self {
286 let CashierPaymentSimple { terminal_type, .. } = value;
287 let tt = terminal_type.clone().unwrap_or(TerminalType::WEB);
288 Self { terminal_type: tt }
289 }
290}
291
292#[derive(Serialize, Clone, Copy, PartialEq, Eq)]
293pub enum TerminalType {
294 WEB,
295 WAP,
296 APP,
297 MINI_APP,
298}
299
300#[derive(Serialize, Deserialize, Debug, Clone)]
302#[serde(rename_all = "camelCase")]
303pub struct Amount {
304 pub currency: String,
308 value: String,
316}
317impl Amount {
318 pub fn value(&self) -> u32 {
319 self.value.parse().unwrap()
320 }
321 pub fn currency(&self) -> String {
322 self.currency.clone()
323 }
324}
325impl From<&CashierPaymentSimple> for Amount {
326 fn from(value: &CashierPaymentSimple) -> Self {
327 let CashierPaymentSimple {
328 currency, amount, ..
329 } = value;
330 Self {
331 value: amount.to_string().clone(),
332 currency: currency.clone(),
333 }
334 }
335}
336
337#[derive(Serialize)]
345#[serde(rename_all = "camelCase")]
346pub struct PaymentMethod {
347 pub payment_method_type: String,
351}
352
353impl From<&CashierPaymentSimple> for PaymentMethod {
354 fn from(value: &CashierPaymentSimple) -> Self {
355 Self {
356 payment_method_type: String::from("ALIPAY_CN"),
357 }
358 }
359}
360
361#[derive(Serialize)]
371#[serde(rename_all = "camelCase")]
372pub struct Order {
373 pub order_amount: Amount,
375 pub order_description: String,
376 pub reference_order_id: String,
377}
378
379impl From<&CashierPaymentSimple> for Order {
380 fn from(value: &CashierPaymentSimple) -> Self {
381 let CashierPaymentSimple {
382 reference_order_id,
383 order_description,
384 ..
385 } = value;
386 let roi = reference_order_id.clone().unwrap_or(String::from(""));
387 let od = order_description.clone();
388 let order_amount = Amount::from(value);
389 Self {
390 order_amount,
391 order_description: od,
392 reference_order_id: roi,
393 }
394 }
395}
396
397#[derive(Serialize)]
399#[serde(rename_all = "camelCase")]
400pub struct SettlementStrategy {
401 settlement_currency: String,
403}
404
405impl From<&CashierPaymentSimple> for SettlementStrategy {
406 fn from(value: &CashierPaymentSimple) -> Self {
407 let CashierPaymentSimple { currency, .. } = value;
408
409 Self {
410 settlement_currency: currency.clone(),
411 }
412 }
413}
414
415#[derive(Serialize, Deserialize, Debug)]
417#[serde(rename_all = "camelCase")]
418pub struct Response {
419 result: ResponseResult,
420 payment_request_id: Option<String>,
421 payment_id: Option<String>,
422 payment_amount: Option<Amount>,
423 actual_payment_amount: Option<Amount>,
424 payment_data: Option<String>,
425 payment_create_time: Option<DateTime<Utc>>,
426 psp_customer_info: Option<PspCustomerInfo>,
427 order_code_form: Option<OrderCodeForm>,
428 gross_settlement_amount: Option<Amount>,
430 settlement_quote: Option<SettlementQuote>,
432 app_identifier: Option<String>,
433 applink_url: Option<String>,
434 normal_url: Option<String>,
435 scheme_url: Option<String>,
436 payment_result_info: Option<PaymentResultInfo>,
437 refund_amount: Option<RefundAmount>,
438 refund_time: Option<DateTime<Utc>>,
439 refund_request_id: Option<String>,
440 refund_id: Option<String>,
441 payment_status: Option<PaymentStatus>,
442 payment_result_code: Option<String>,
443 payment_result_message: Option<String>,
444 payment_time: Option<DateTime<Utc>>,
445 redirect_action_form: Option<RedirectActionForm>,
446 acquirer_reference_no: Option<String>,
447 transactions: Option<Vec<Transactions>>,
448 customs_declaration_amount: Option<Amount>,
449}
450
451impl Response {
458 pub fn result(&self) -> &ResponseResult {
459 &self.result
460 }
461 pub fn payment_request_id(&self) -> &Option<String> {
462 &self.payment_request_id
463 }
464 pub fn payment_id(&self) -> &Option<String> {
465 &self.payment_id
466 }
467 pub fn is_success(&self) -> bool {
468 self.result.result_status == ResultStatus::S
469 }
470 pub fn is_processing(&self) -> bool {
471 self.result.result_code == ResultCode::PAYMENT_IN_PROCESS
472 }
473 pub fn get_error(&self) -> Option<Error> {
474 self.result.get_error()
475 }
476 pub fn get_payment_create_time(&self) -> Option<DateTime<Utc>> {
477 self.payment_create_time
478 }
479 pub fn get_normal_url(&self) -> &Option<String> {
480 &self.normal_url
481 }
482 pub fn get_order_code_form(&self) -> &Option<OrderCodeForm> {
483 &self.order_code_form
484 }
485 pub fn get_refund_amount(&self) -> &Option<RefundAmount> {
486 &self.refund_amount
487 }
488 pub fn get_refund_time(&self) -> &Option<DateTime<Utc>> {
489 &self.refund_time
490 }
491 pub fn get_refund_request_id(&self) -> &Option<String> {
492 &self.refund_request_id
493 }
494 pub fn get_refund_id(&self) -> &Option<String> {
495 &self.refund_id
496 }
497 pub fn get_payment_status(&self) -> &Option<PaymentStatus> {
498 &self.payment_status
499 }
500 pub fn get_payment_result_code(&self) -> &Option<String> {
501 &self.payment_result_code
502 }
503 pub fn get_payment_result_message(&self) -> &Option<String> {
504 &self.payment_result_message
505 }
506 pub fn get_payment_time(&self) -> &Option<DateTime<Utc>> {
507 &self.payment_time
508 }
509 pub fn get_transactions(&self) -> &Option<Vec<Transactions>> {
510 &self.transactions
511 }
512 pub fn get_payment_amount(&self) -> &Option<Amount> {
513 &self.payment_amount
514 }
515 pub fn get_actual_payment_amount(&self) -> &Option<Amount> {
516 &self.actual_payment_amount
517 }
518}
519
520#[derive(Deserialize, Serialize, Debug, Clone)]
522#[serde(rename_all = "camelCase")]
523pub struct ResponseResult {
524 pub result_code: ResultCode,
528 pub result_status: ResultStatus,
529 pub result_message: String,
530}
531
532impl ResponseResult {
533 pub fn get_error(&self) -> Option<Error> {
534 if self.result_code == ResultCode::PAYMENT_IN_PROCESS {
535 return None;
537 } else if self.result_code == ResultCode::UNKNOWN_EXCEPTION {
538 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.")));
542 }
543 match self.result_status {
544 ResultStatus::S => None,
545 ResultStatus::F => Some(Error::Fail(self.result_code.to_string())),
546 ResultStatus::U => Some(Error::Unknown(self.result_code.to_string())),
547 }
548 }
549}
550
551#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Display, Clone)]
552pub enum ResultCode {
553 SUCCESS,
554 ACCESS_DENIED,
555 INVALID_API,
556 CURRENCY_NOT_SUPPORT,
557 EXPIRED_CODE,
558 FRAUD_REJECT,
559 INVALID_ACCESS_TOKEN,
560 INVALID_CONTRACT,
561 INVALID_MERCHANT_STATUS,
562 INVALID_PAYMENT_CODE,
563 INVALID_PAYMENT_METHOD_META_DATA,
564 KEY_NOT_FOUND,
565 MERCHANT_KYB_NOT_QUALIFIED,
566 MERCHANT_NOT_REGISTERED,
567 NO_INTERFACE_DEF,
568 NO_PAY_OPTIONS,
569 ORDER_IS_CANCELED,
570 ORDER_IS_CLOSED,
571 PARAM_ILLEGAL,
572 PAYMENT_AMOUNT_EXCEED_LIMIT,
573 PAYMENT_COUNT_EXCEED_LIMIT,
574 PAYMENT_NOT_QUALIFIED,
575 PROCESS_FAIL,
576 REPEAT_REQ_INCONSISTENT,
577 RISK_REJECT,
578 SETTLE_CONTRACT_NOT_MATCH,
579 SYSTEM_ERROR,
580 USER_AMOUNT_EXCEED_LIMIT,
581 USER_BALANCE_NOT_ENOUGH,
582 USER_KYC_NOT_QUALIFIED,
583 PAYMENT_IN_PROCESS,
584 REQUEST_TRAFFIC_EXCEED_LIMIT,
585 UNKNOWN_EXCEPTION,
586 USER_NOT_EXIST,
587 ORDER_NOT_EXIST,
588 ORDER_STATUS_INVALID,
589 USER_PAYMENT_VERIFICATION_FAILED,
590 USER_STATUS_ABNORMAL,
591 VERIFY_TIMES_EXCEED_LIMIT,
592 VERIFY_UNMATCHED,
593 AUTHENTICATION_REQUIRED,
594 SELECTED_CARD_BRAND_NOT_AVAILABLE,
595 PAYMENT_PROHIBITED,
596 REFUND_AMOUNT_EXCEED,
597 REFUND_WINDOW_EXCEED,
598 REFUND_IN_PROCESS,
599 REFUND_NOT_SUPPORTED,
600}
601
602#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
607pub enum ResultStatus {
608 S,
609 F,
610 U,
611}
612#[derive(Deserialize, Debug, Clone, Serialize)]
615#[serde(rename_all = "camelCase")]
616#[cfg_attr(feature = "juniper", derive(GraphQLObject))]
617pub struct OrderCodeForm {
618 expire_time: chrono::DateTime<chrono::Utc>,
619 code_details: Vec<CodeDetail>,
620 extend_info: Option<String>,
621}
622
623#[derive(Deserialize, Debug, Clone, Serialize)]
627#[serde(rename_all = "camelCase")]
628#[cfg_attr(feature = "juniper", derive(GraphQLObject))]
629pub struct CodeDetail {
630 code_value: String,
631 display_type: DisplayType,
632}
633
634#[derive(Serialize, Deserialize, Debug)]
635#[serde(rename_all = "camelCase")]
636pub struct PspCustomerInfo {
637 psp_name: Option<String>,
638 psp_customer_id: Option<String>,
639 display_customer_id: Option<String>,
640}
641
642#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
643#[cfg_attr(feature = "juniper", derive(GraphQLEnum))]
644pub enum DisplayType {
645 TEXT,
646 MIDDLEIMAGE,
647 SMALLIMAGE,
648 BIGIMAGE,
649}
650
651#[derive(Serialize, Deserialize, Debug)]
653#[serde(rename_all = "camelCase")]
654pub struct SettlementQuote {
655 guaranteed: Option<bool>,
656 quote_id: Option<String>,
657 quote_currency_pair: String,
658 quote_price: i32,
659 quote_start_time: Option<DateTime<Utc>>,
660 quote_expiry_time: Option<DateTime<Utc>>,
661}
662
663#[derive(Serialize, Deserialize, Debug)]
666#[serde(rename_all = "camelCase")]
667pub struct PaymentResultInfo {
668 avs_result_raw: Option<String>,
669 cvv_result_raw: Option<String>,
670 network_transaction_id: Option<String>,
671}
672
673#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
674pub enum PaymentStatus {
675 SUCCESS,
676 FAIL,
677 PROCESSING,
678 CANCELLED,
679 PENDING,
680}
681
682#[derive(Serialize, Deserialize, Debug)]
683#[serde(rename_all = "camelCase")]
684pub struct RedirectActionForm {
685 method: RedirectActionFormMethod,
686 parameters: Option<String>,
687 redirect_url: String,
688 action_form_type: Option<String>,
689}
690
691#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
692pub enum RedirectActionFormMethod {
693 POST,
694 GET,
695}
696
697#[derive(Serialize, Deserialize, Debug, Clone)]
698#[serde(rename_all = "camelCase")]
699pub struct Transactions {
700 transaction_result: ResponseResult,
701 transaction_id: String,
702 transaction_type: String,
703 transaction_status: String,
704 transaction_amount: Amount,
705 transaction_request_id: String,
706}
707
708#[derive(Serialize)]
709#[serde(rename_all = "camelCase")]
710pub struct CashierPaymentRefundSimple {
711 pub refund_request_id: String,
712 pub payment_id: String,
713 pub amount: i32,
714 pub currency: String,
715}
716
717#[derive(Serialize, Deserialize, Debug, Clone)]
718#[serde(rename_all = "camelCase")]
719pub struct RefundAmount {
720 value: String,
721 currency: String,
722}
723
724impl From<&CashierPaymentRefundSimple> for RefundAmount {
725 fn from(value: &CashierPaymentRefundSimple) -> Self {
726 let CashierPaymentRefundSimple {
727 amount, currency, ..
728 } = value;
729 Self {
730 value: amount.to_string().clone(),
731 currency: currency.clone(),
732 }
733 }
734}
735
736#[derive(Serialize)]
737#[serde(rename_all = "camelCase")]
738pub struct CashierPaymentRefundFull {
739 payment_id: String,
740 refund_request_id: String,
741 refund_amount: RefundAmount,
742}
743
744impl CashierPaymentRefundFull {
745 pub fn to_string(&self) -> String {
746 serde_json::to_value(self).unwrap().to_string()
747 }
748}
749
750impl From<&CashierPaymentRefundSimple> for CashierPaymentRefundFull {
751 fn from(value: &CashierPaymentRefundSimple) -> Self {
752 let CashierPaymentRefundSimple {
753 refund_request_id,
754 payment_id,
755 ..
756 } = value;
757 let refund_amount = RefundAmount::from(value);
758 Self {
759 payment_id: payment_id.clone(),
760 refund_request_id: refund_request_id.clone(),
761 refund_amount: refund_amount,
762 }
763 }
764}
765
766impl Signable for CashierPaymentRefundFull {
767 fn get_value(&self) -> Value {
768 serde_json::to_value(self).unwrap()
769 }
770}
771
772#[derive(Serialize, Deserialize)]
773#[serde(rename_all = "camelCase")]
774pub struct CashierPaymentInquiry {
775 pub payment_request_id: Option<String>,
776 pub payment_id: Option<String>,
777}
778
779impl CashierPaymentInquiry {
780 pub fn to_string(&self) -> String {
781 serde_json::to_value(self).unwrap().to_string()
782 }
783}
784
785impl Signable for CashierPaymentInquiry {
786 fn get_value(&self) -> Value {
787 serde_json::to_value(self).unwrap()
788 }
789}
790
791#[derive(Debug, Serialize, Deserialize, Clone)]
792pub struct PaymentAmount {
793 currency: String,
794 value: String,
795}
796
797#[derive(Debug, Serialize, Deserialize, Clone)]
798pub struct NotifyPayment {
799 capture_amount: PaymentAmount,
800 notify_type: String,
801 capture_id: String,
802 capture_request_id: String,
803 capture_time: String,
804 payment_id: String,
805 result: ResponseResult,
806}
807
808#[derive(Debug, Serialize, Deserialize, Clone)]
809pub struct WebhookData {
810 pub method: String,
811 pub path: String,
812 pub request_time: String,
813 pub header_signature: String,
814 pub client_id: String,
815 pub request_body: String,
816}
817
818#[derive(Debug, Serialize, Deserialize, Clone)]
819pub struct WebhookResponse {
820 pub full_signature: String,
821 pub client_id: String,
822 pub response_time: String,
823 pub body: String,
824}
825
826#[derive(Debug, Serialize, Deserialize, Clone)]
827pub struct WebhookResponseInput {
828 pub method: String,
829 pub path: String,
830 pub client_id: String,
831}
832
833#[derive(Deserialize, Serialize, Debug, Clone)]
834#[serde(rename_all = "camelCase")]
835pub struct WebhookResponseResult {
836 pub result: ResponseResult,
837}
838
839impl Signable for WebhookResponseResult {
840 fn get_value(&self) -> Value {
841 serde_json::to_value(self).unwrap()
842 }
843}