Skip to content
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(core): add bulk tokenization flows #7066

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

kashif-m
Copy link
Contributor

@kashif-m kashif-m commented Jan 20, 2025

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

This PR introduces below changes

  • Exposes two endpoints for
    • Single card tokenization
      • Tokenizing raw card details with the network
      • Tokenizing an existing payment method in HS with the network (card only)
    • Bulk tokenization
      • Tokenizing using raw card details or an existing payment method

More details around the flow is present in #6971

Additional Changes

  • This PR modifies the API contract
  • This PR modifies the database schema
  • This PR modifies application configuration/environment variables

Motivation and Context

This helps merchants to tokenize their customer's payment methods with the card networks. A new payment method entry is created for every card which can be used to perform txns using the network tokens.

How did you test it?

1. Tokenize using raw card details

cURL

curl --location --request POST 'http://localhost:8080/payment_methods/tokenize-card' \
    --header 'Content-Type: application/json' \
    --header 'api-key: test_admin' \
    --data '{
        "card": {
            "raw_card_number": "4761360080000093",
            "card_expiry_month": "01",
            "card_expiry_year": "26",
            "card_cvc": "947"
        },
        "merchant_id": "merchant_1737627877",
        "card_type": "debit",
        "customer": {
            "id": "cus_IoFeOlGhEeD54AbBdvxI"
        }
    }'

Response

{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_IoFeOlGhEeD54AbBdvxI","payment_method_id":"pm_ai4pU6o32LUgRy2kl5hd","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0093","expiry_month":"01","expiry_year":"26","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":null,"card_network":null,"card_isin":"476136","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:41:54.753Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_IoFeOlGhEeD54AbBdvxI","name":"John Nether","email":"[email protected]","phone":"6168205362","phone_country_code":"+1"},"card_tokenized":true,"req":null}
2. Tokenize using existing payment method entry

cURL

curl --location --request POST 'http://localhost:8080/payment_methods/tokenize-card' \
    --header 'Content-Type: application/json' \
    --header 'api-key: test_admin' \
    --data '{
        "payment_method": {
            "payment_method_id": "pm_HHZV9XC6C10jjKGiK8m1",
            "card_cvc": "947"
        },
        "merchant_id": "merchant_1737627877",
        "card_type": "debit",
        "customer": {
            "id": "cus_IoFeOlGhEeD54AbBdvxI"
        }
    }'

Response

{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_IoFeOlGhEeD54AbBdvxI","payment_method_id":"pm_HHZV9XC6C10jjKGiK8m1","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"03","expiry_year":"2030","card_token":null,"card_holder_name":"John US","card_fingerprint":null,"nick_name":"JD","card_network":null,"card_isin":"491761","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:43:02.151Z","last_used_at":"2025-01-23T10:43:02.151Z","client_secret":"pm_HHZV9XC6C10jjKGiK8m1_secret_SJC4AUiR4HmYzSDOKwPi"},"customer":{"id":"cus_IoFeOlGhEeD54AbBdvxI","name":null,"email":null,"phone":null,"phone_country_code":null},"card_tokenized":true,"req":null}
3. Bulk tokenize

cURL

curl --location --request POST 'http://localhost:8080/payment_methods/tokenize-card-batch' \
    --header 'api-key: test_admin' \
    --form 'merchant_id="merchant_1737627877"' \
    --form 'file=@"/Users/Downloads/tokenization sample - Sheet 1.csv"'

Response

[{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_Hn03n9kbTqUxKZ0nZRVC","payment_method_id":"pm_eBONxbqiaf8T7KkGSlaq","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:53.998Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_Hn03n9kbTqUxKZ0nZRVC","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_OWDUMC1QBmsTeImT9xCs","payment_method_id":"pm_nRtoCkil21RaGNJulP2L","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:53.998Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_OWDUMC1QBmsTeImT9xCs","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_mBx8CYwaUldk2HbX9qBV","payment_method_id":"pm_nhtWX126LHU1BmoN48TB","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:53.998Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_mBx8CYwaUldk2HbX9qBV","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_tpr9V8N4mPijFOZGbqjH","payment_method_id":"pm_eoyzCgzbJjbfREVKe7c0","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.001Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_tpr9V8N4mPijFOZGbqjH","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_fdbpnmVFS3hhYZZfYquM","payment_method_id":"pm_ShvWRpaZyX5kucjVImmw","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.001Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_fdbpnmVFS3hhYZZfYquM","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_HeGLoqXGsKYbpCTvvNCX","payment_method_id":"pm_w6QqSyug2w4rJAIxQY4a","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.009Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_HeGLoqXGsKYbpCTvvNCX","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_3LgTOBzFOcMHEz1glZzF","payment_method_id":"pm_1G3NGbJG0LyJKvbIZvdJ","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.009Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_3LgTOBzFOcMHEz1glZzF","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_bYIVR0NQF4T4bv5KjVNd","payment_method_id":"pm_31KM9k8uPbwUAUaq03N5","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2026","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.010Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_bYIVR0NQF4T4bv5KjVNd","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_jxMzHRc3Ndfr9Mt2pHKg","payment_method_id":"pm_dAHGSnuIHQC4rHWEGAwF","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"25","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.010Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_jxMzHRc3Ndfr9Mt2pHKg","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_KyauqQZeqKbZCEdRCrPY","payment_method_id":"pm_im8kjgFyc5ydhrHES9z5","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"22","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.010Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_KyauqQZeqKbZCEdRCrPY","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_NuZ4Ntb7hWDXxFt7ro3D","payment_method_id":"pm_gfgZ1gOZ4kIdeBU7Cyqz","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"25","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.017Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_NuZ4Ntb7hWDXxFt7ro3D","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_UDoT3SdLhK62XG7xFo65","payment_method_id":"pm_cPsrZ1S3xV12NnF5uAH8","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"25","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.017Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_UDoT3SdLhK62XG7xFo65","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null},{"payment_method_response":{"merchant_id":"merchant_1737627877","customer_id":"cus_cxhCY1RgcvRPlIX891kU","payment_method_id":"pm_iHXORES9dupsE5ffAG0C","payment_method":"card","payment_method_type":null,"card":{"scheme":null,"issuer_country":null,"last4_digits":"0000","expiry_month":"1","expiry_year":"2022","card_token":null,"card_holder_name":null,"card_fingerprint":null,"nick_name":"real joe","card_network":null,"card_isin":"420000","card_issuer":null,"card_type":null,"saved_to_locker":false},"recurring_enabled":true,"installment_payment_enabled":false,"payment_experience":null,"metadata":null,"created":"2025-01-23T10:24:54.017Z","last_used_at":null,"client_secret":null},"customer":{"id":"cus_cxhCY1RgcvRPlIX891kU","name":"Max Mustermann","email":"[email protected]","phone":null,"phone_country_code":null},"card_tokenized":true,"req":null}]

Checklist

  • I formatted the code cargo +nightly fmt --all
  • I addressed lints thrown by cargo clippy
  • I reviewed the submitted code
  • I added unit tests for my changes where possible

@kashif-m kashif-m linked an issue Jan 20, 2025 that may be closed by this pull request
2 tasks
Copy link

semanticdiff-com bot commented Jan 20, 2025

@kashif-m kashif-m marked this pull request as ready for review January 22, 2025 11:57
@kashif-m kashif-m requested review from a team as code owners January 22, 2025 11:57
@kashif-m kashif-m self-assigned this Jan 23, 2025
}?;

// Ensure card is not tokenized already
if payment_method
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use when instead

.await
}

pub async fn validate_payment_method_id(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is fetch also happening inside this function, so for validation the function name can be validate_payment_method and accept a payment method and have a separate function to fetch the object. Or have fetch_and_validate_payment_method

) -> RouterResult<domain::PaymentMethod> {
// Form encrypted network token data (tokenized card)
let network_token_data = network_token_details.0;
let token_data = api::PaymentMethodsData::Card(api::CardDetailsPaymentMethod {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creating encrypted_data of card from domain::card is repeated in multiple places, have a single function accepts domain::card and send encrypted data as output.

{
Ok(tokenization_response) => Ok(tokenization_response),
Err(err) => {
// TODO: revert this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix the TODO

}),
// Customer not found
None => {
if customer_details.name.is_some()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid if else, instead do a inversion method. validate and throw error first then do the process. Have a separate function to create customer. And call the customer creation function here.

api,
domain::{
self,
bulk_tokenization::{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bulk_tokenization::{
bulk_tokenization

},
},
},
utils::Encryptable,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
utils::Encryptable,
utils,

add_card_to_hs_locker, create_encrypted_data, create_payment_method, tokenize_card_flow,
},
network_tokenization,
transformers::{StoreCardReq, StoreCardRespPayload, StoreLockerReq},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
transformers::{StoreCardReq, StoreCardRespPayload, StoreLockerReq},
transformers,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEATURE] expose tokenize endpoints
2 participants