-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(payouts): Add Wallet to Payouts #3502
Conversation
.request | ||
.connector_payout_id | ||
.clone() | ||
.unwrap_or("".to_string()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can throw an error here, since original_reference
is a required field for fulfillment
api_models::payouts::PayoutMethodData::Bank(bank) => { | ||
let key = key_store.key.get_inner().peek(); | ||
let enc_data = async { | ||
serde_json::to_value(payout_method_data.to_owned()) | ||
.into_report() | ||
.change_context(errors::ApiErrorResponse::InternalServerError) | ||
.attach_printable("Unable to encode payout method data") | ||
.ok() | ||
.map(|v| { | ||
let secret: Secret<String> = Secret::new(v.to_string()); | ||
secret | ||
}) | ||
.async_lift(|inner| domain_types::encrypt_optional(inner, key)) | ||
.await | ||
} | ||
.await | ||
.change_context(errors::ApiErrorResponse::InternalServerError) | ||
.attach_printable("Failed to encrypt payout method data")? | ||
.map(Encryption::from) | ||
.map(|e| e.into_inner()) | ||
.map_or(Err(errors::ApiErrorResponse::InternalServerError), |e| { | ||
Ok(hex::encode(e.peek())) | ||
})?; | ||
let payload = StoreLockerReq::LockerGeneric(StoreGenericReq { | ||
merchant_id: &merchant_account.merchant_id, | ||
merchant_customer_id: payout_attempt.customer_id.to_owned(), | ||
enc_data, | ||
}); | ||
( | ||
payload, | ||
None, | ||
Some(bank.to_owned()), | ||
None, | ||
api_enums::PaymentMethodType::foreign_from(bank.to_owned()), | ||
) | ||
} | ||
api_models::payouts::PayoutMethodData::Wallet(wallet) => { | ||
let key = key_store.key.get_inner().peek(); | ||
let enc_data = async { | ||
serde_json::to_value(payout_method_data.to_owned()) | ||
.into_report() | ||
.change_context(errors::ApiErrorResponse::InternalServerError) | ||
.attach_printable("Unable to encode payout method data") | ||
.ok() | ||
.map(|v| { | ||
let secret: Secret<String> = Secret::new(v.to_string()); | ||
secret | ||
}) | ||
.async_lift(|inner| domain_types::encrypt_optional(inner, key)) | ||
.await | ||
} | ||
.await | ||
.change_context(errors::ApiErrorResponse::InternalServerError) | ||
.attach_printable("Failed to encrypt payout method data")? | ||
.map(Encryption::from) | ||
.map(|e| e.into_inner()) | ||
.map_or(Err(errors::ApiErrorResponse::InternalServerError), |e| { | ||
Ok(hex::encode(e.peek())) | ||
})?; | ||
let payload = StoreLockerReq::LockerGeneric(StoreGenericReq { | ||
merchant_id: &merchant_account.merchant_id, | ||
merchant_customer_id: payout_attempt.customer_id.to_owned(), | ||
enc_data, | ||
}); | ||
( | ||
payload, | ||
None, | ||
None, | ||
Some(wallet.to_owned()), | ||
api_enums::PaymentMethodType::foreign_from(wallet.to_owned()), | ||
) | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api_models::payouts::PayoutMethodData::Bank(bank) => { | |
let key = key_store.key.get_inner().peek(); | |
let enc_data = async { | |
serde_json::to_value(payout_method_data.to_owned()) | |
.into_report() | |
.change_context(errors::ApiErrorResponse::InternalServerError) | |
.attach_printable("Unable to encode payout method data") | |
.ok() | |
.map(|v| { | |
let secret: Secret<String> = Secret::new(v.to_string()); | |
secret | |
}) | |
.async_lift(|inner| domain_types::encrypt_optional(inner, key)) | |
.await | |
} | |
.await | |
.change_context(errors::ApiErrorResponse::InternalServerError) | |
.attach_printable("Failed to encrypt payout method data")? | |
.map(Encryption::from) | |
.map(|e| e.into_inner()) | |
.map_or(Err(errors::ApiErrorResponse::InternalServerError), |e| { | |
Ok(hex::encode(e.peek())) | |
})?; | |
let payload = StoreLockerReq::LockerGeneric(StoreGenericReq { | |
merchant_id: &merchant_account.merchant_id, | |
merchant_customer_id: payout_attempt.customer_id.to_owned(), | |
enc_data, | |
}); | |
( | |
payload, | |
None, | |
Some(bank.to_owned()), | |
None, | |
api_enums::PaymentMethodType::foreign_from(bank.to_owned()), | |
) | |
} | |
api_models::payouts::PayoutMethodData::Wallet(wallet) => { | |
let key = key_store.key.get_inner().peek(); | |
let enc_data = async { | |
serde_json::to_value(payout_method_data.to_owned()) | |
.into_report() | |
.change_context(errors::ApiErrorResponse::InternalServerError) | |
.attach_printable("Unable to encode payout method data") | |
.ok() | |
.map(|v| { | |
let secret: Secret<String> = Secret::new(v.to_string()); | |
secret | |
}) | |
.async_lift(|inner| domain_types::encrypt_optional(inner, key)) | |
.await | |
} | |
.await | |
.change_context(errors::ApiErrorResponse::InternalServerError) | |
.attach_printable("Failed to encrypt payout method data")? | |
.map(Encryption::from) | |
.map(|e| e.into_inner()) | |
.map_or(Err(errors::ApiErrorResponse::InternalServerError), |e| { | |
Ok(hex::encode(e.peek())) | |
})?; | |
let payload = StoreLockerReq::LockerGeneric(StoreGenericReq { | |
merchant_id: &merchant_account.merchant_id, | |
merchant_customer_id: payout_attempt.customer_id.to_owned(), | |
enc_data, | |
}); | |
( | |
payload, | |
None, | |
None, | |
Some(wallet.to_owned()), | |
api_enums::PaymentMethodType::foreign_from(wallet.to_owned()), | |
) | |
} | |
}; | |
_ => { | |
let key = key_store.key.get_inner().peek(); | |
let enc_data = async { | |
serde_json::to_value(payout_method_data.to_owned()) | |
.into_report() | |
.change_context(errors::ApiErrorResponse::InternalServerError) | |
.attach_printable("Unable to encode payout method data") | |
.ok() | |
.map(|v| { | |
let secret: Secret<String> = Secret::new(v.to_string()); | |
secret | |
}) | |
.async_lift(|inner| domain_types::encrypt_optional(inner, key)) | |
.await | |
} | |
.await | |
.change_context(errors::ApiErrorResponse::InternalServerError) | |
.attach_printable("Failed to encrypt payout method data")? | |
.map(Encryption::from) | |
.map(|e| e.into_inner()) | |
.map_or(Err(errors::ApiErrorResponse::InternalServerError), |e| { | |
Ok(hex::encode(e.peek())) | |
})?; | |
let payload = StoreLockerReq::LockerGeneric(StoreGenericReq { | |
merchant_id: &merchant_account.merchant_id, | |
merchant_customer_id: payout_attempt.customer_id.to_owned(), | |
enc_data, | |
}); | |
match payout_method_data { | |
api_models::payouts::PayoutMethodData::Bank(b) => { | |
( | |
payload, | |
None, | |
Some(bank.to_owned()), | |
None, | |
api_enums::PaymentMethodType::foreign_from(bank.to_owned()), | |
) | |
} | |
api_models::payouts::PayoutMethodData::Wallet(w) => { | |
( | |
payload, | |
None, | |
None, | |
Some(wallet.to_owned()), | |
api_enums::PaymentMethodType::foreign_from(wallet.to_owned()), | |
) | |
} | |
} | |
} | |
}; |
Encryption of data is done the same way for both banks and wallets, so we can merge the steps
@@ -0,0 +1,2 @@ | |||
-- This file should undo anything in `up.sql` | |||
SELECT 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this supposed to be this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is the default case since we don't necessarily need to rollback for a type alter👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added minor comments. I don't see provision for storing wallet details in payment_methods table, let's do it as a separate task in a separate PR?
merchant_account: Secret<String>, | ||
bank: PayoutBankDetails, | ||
bank: Option<PayoutBankDetails>, | ||
additional_data: Option<PayoutAdditionalData>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's create enums for payment method data instead of making all fields optional
shopper_reference: item.router_data.merchant_id.to_owned(), | ||
shopper_email: customer_email, | ||
shopper_name: ShopperName { | ||
first_name: address.get_first_name().ok().cloned(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we need to throw missing field error in case of first_name and last_name none?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it is a mandatory field, error propagation is enabled now
Type of Change
Description
Wallet for payout is added with implementation on Paypal for adyen
Additional Changes
The contract changes are made in the following file:
hyperswitch/crates/api_models/src/payments.rs
hyperswitch/crates/api_models/src/payouts.rs
How did you test it?
Tested through Postman:
requires_fulfillment
in the responsesuccess
in the responseChecklist
cargo +nightly fmt --all
cargo clippy