feat(connector): [TRUSTPAY] Added Integrity Checks for PSync & RSync flows & Added New Variants in AttemptStatus & IntentStatus
Type of Change
- [x] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD
Description
Added Integrity Checks for PSync and RSync flows in Trustpay.
Why not for other flows as well?
The reason being, Trustpay doesnt send amount field in their response. Without getting to know the amount, we cannot perform integrity check. But they do send an amount field in Webhooks, so it will be checked upon receiving the webhooks payload.
Also added a function which converts f64 to StringMajorUnit directly because there wasn't any function already which does it. We needed this function to convert the amount being received in f64 from Webhooks into StringMajorUnit which is required for the integrity check function.
Also added new enum variants in AttemptStatus and IntentStatus. In AttemptStatus added an enum variant "IntegrityFailure" and in IntentStatus added an enum variant "Conflicted".
Additional Changes
- [ ] This PR modifies the API contract
- [x] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables
Motivation and Context
How did you test it?
Note: Since we donot have the Trustpay Dashboard access for SEPA Bank Transfer payment method hence we are sending the webhook payload by ourselves and thereby adding a function inside impl IncomingWebhooks so that the source verification becomes true.
async fn verify_webhook_source(
&self,
_request: &webhooks::IncomingWebhookRequestDetails<'_>,
_merchant_id: &common_utils::id_type::MerchantId,
_connector_webhook_details: Option<common_utils::pii::SecretSerdeValue>,
_connector_account_details: crypto::Encryptable<masking::Secret<serde_json::Value>>,
_connector_name: &str,
) -> CustomResult<bool, errors::ConnectorError> {
Ok(true)
}
Step 1: Do a Payments - Create (3DS)
cURL :
curl --location 'http://localhost:8080/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_SHOvApfNQsc4Jinsu2maY97fKEA8fmphKoXvhPRM5io9C6KhCVdfNQbLJhm3pkx2' \
--header 'Cookie: PHPSESSID=0b47db9d7de94c37b6b272087a9f2fa7' \
--data-raw '{
"amount": 6540,
"currency": "EUR",
"confirm": true,
"capture_method": "automatic",
"capture_on": "2022-09-10T10:11:12Z",
"amount_to_capture": 6540,
"customer_id": "StripeCustomer",
"email": "[email protected]",
"name": "John Doe",
"phone": "999999999",
"phone_country_code": "+1",
"description": "Its my first payment request",
"authentication_type": "three_ds",
"return_url": "https://google.com",
"payment_method": "bank_transfer",
"payment_method_type" :"sepa_bank_transfer",
"payment_method_data": {
"bank_transfer": {
"sepa_bank_transfer": { }
}
},
"statement_descriptor_name": "joseph",
"statement_descriptor_suffix": "JS",
"metadata": {
"udf1": "value1",
"new_customer": "true",
"login_date": "2019-09-10T10:11:12Z"
},
"billing": {
"address": {
"line1": "1467",
"line2": "Harrison Street",
"line3": "Harrison Street",
"city": "San Fransico",
"state": "California",
"zip": "94122",
"country": "ES",
"first_name": "joseph",
"last_name": "Doe"
},
"phone": {
"number": "8056594427",
"country_code": "+91"
}
},
"shipping": {
"address": {
"line1": "1467",
"line2": "Harrison Street",
"line3": "Harrison Street",
"city": "San Fransico",
"state": "California",
"zip": "94122",
"country": "ES",
"first_name": "joseph",
"last_name": "Doe"
},
"phone": {
"number": "8056594427",
"country_code": "+91"
}
},
"browser_info": {
"user_agent": "Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/70.0.3538.110 Safari\/537.36",
"accept_header": "text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/webp,image\/apng,*\/*;q=0.8",
"language": "nl-NL",
"color_depth": 24,
"screen_height": 723,
"screen_width": 1536,
"time_zone": 0,
"java_enabled": true,
"java_script_enabled": true,
"ip_address": "125.0.0.1"
}
}'
We will get a status of requires_customer_action and upon getting redirected we will complete the payment and get a status of processing upon doing a PSync.
PSync cURL :
curl --location 'http://localhost:8080/payments/pay_hfikYPO1IQG1QNjmTA7f?force_sync=true&expand_captures=true&expand_attempts=true' \
--header 'Accept: application/json' \
--header 'api-key: dev_SHOvApfNQsc4Jinsu2maY97fKEA8fmphKoXvhPRM5io9C6KhCVdfNQbLJhm3pkx2' \
--header 'Cookie: PHPSESSID=0b47db9d7de94c37b6b272087a9f2fa7'
Response:
{
"payment_id": "pay_hfikYPO1IQG1QNjmTA7f",
"merchant_id": "merchant_1747840487",
"status": "processing",
"amount": 6540,
"net_amount": 6540,
"shipping_cost": null,
"amount_capturable": 6540,
"amount_received": null,
"connector": "trustpay",
"client_secret": "pay_hfikYPO1IQG1QNjmTA7f_secret_fokQJrYy4upXurha5WqK",
"created": "2025-05-21T15:15:05.524Z",
"currency": "EUR",
"customer_id": "StripeCustomer",
"customer": {
"id": "StripeCustomer",
"name": "John Doe",
"email": "[email protected]",
"phone": "999999999",
"phone_country_code": "+1"
},
"description": "Its my first payment request",
"refunds": null,
"disputes": null,
"attempts": [
{
"attempt_id": "pay_hfikYPO1IQG1QNjmTA7f_1",
"status": "authorizing",
"amount": 6540,
"order_tax_amount": null,
"currency": "EUR",
"connector": "trustpay",
"error_message": null,
"payment_method": "bank_transfer",
"connector_transaction_id": "1668403613",
"capture_method": "automatic",
"authentication_type": "three_ds",
"created_at": "2025-05-21T15:15:05.524Z",
"modified_at": "2025-05-21T15:15:49.679Z",
"cancellation_reason": null,
"mandate_id": null,
"error_code": null,
"payment_token": null,
"connector_metadata": null,
"payment_experience": null,
"payment_method_type": "sepa_bank_transfer",
"reference_id": null,
"unified_code": null,
"unified_message": null,
"client_source": null,
"client_version": null
}
],
"mandate_id": null,
"mandate_data": null,
"setup_future_usage": null,
"off_session": null,
"capture_on": null,
"capture_method": "automatic",
"payment_method": "bank_transfer",
"payment_method_data": {
"bank_transfer": {
"sepa": {}
},
"billing": null
},
"payment_token": null,
"shipping": {
"address": {
"city": "San Fransico",
"country": "ES",
"line1": "1467",
"line2": "Harrison Street",
"line3": "Harrison Street",
"zip": "94122",
"state": "California",
"first_name": "joseph",
"last_name": "Doe"
},
"phone": {
"number": "8056594427",
"country_code": "+91"
},
"email": null
},
"billing": {
"address": {
"city": "San Fransico",
"country": "ES",
"line1": "1467",
"line2": "Harrison Street",
"line3": "Harrison Street",
"zip": "94122",
"state": "California",
"first_name": "joseph",
"last_name": "Doe"
},
"phone": {
"number": "8056594427",
"country_code": "+91"
},
"email": null
},
"order_details": null,
"email": "[email protected]",
"name": "John Doe",
"phone": "999999999",
"return_url": "https://google.com/",
"authentication_type": "three_ds",
"statement_descriptor_name": "joseph",
"statement_descriptor_suffix": "JS",
"next_action": null,
"cancellation_reason": null,
"error_code": null,
"error_message": null,
"unified_code": null,
"unified_message": null,
"payment_experience": null,
"payment_method_type": "sepa_bank_transfer",
"connector_label": null,
"business_country": null,
"business_label": "default",
"business_sub_label": null,
"allowed_payment_method_types": null,
"ephemeral_key": null,
"manual_retry_allowed": false,
"connector_transaction_id": "1668403613",
"frm_message": null,
"metadata": {
"udf1": "value1",
"login_date": "2019-09-10T10:11:12Z",
"new_customer": "true"
},
"connector_metadata": null,
"feature_metadata": null,
"reference_id": null,
"payment_link": null,
"profile_id": "pro_4l7F4ABCL71EMcjo792M",
"surcharge_details": null,
"attempt_count": 1,
"merchant_decision": null,
"merchant_connector_id": "mca_iiEi4DvepGnrCFNuMeam",
"incremental_authorization_allowed": null,
"authorization_count": null,
"incremental_authorizations": null,
"external_authentication_details": null,
"external_3ds_authentication_attempted": false,
"expires_on": "2025-05-21T15:30:05.524Z",
"fingerprint": null,
"browser_info": {
"language": "nl-NL",
"time_zone": 0,
"ip_address": "125.0.0.1",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
"color_depth": 24,
"java_enabled": true,
"screen_width": 1536,
"accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"screen_height": 723,
"java_script_enabled": true
},
"payment_method_id": null,
"payment_method_status": null,
"updated": "2025-05-21T15:16:21.400Z",
"split_payments": null,
"frm_metadata": null,
"extended_authorization_applied": null,
"capture_before": null,
"merchant_order_reference_id": null,
"order_tax_amount": null,
"connector_mandate_id": null,
"card_discovery": null,
"force_3ds_challenge": false,
"force_3ds_challenge_trigger": false,
"issuer_error_code": null,
"issuer_error_message": null,
"is_iframe_redirection_enabled": null,
"whole_connector_response": null
}
Once the status gets to 'suceedeed` and we receive webhooks and if there's a discrepency between the amount sent and amount sent to the connector, we receive this
Webhook Payload :
curl --location 'https://af82-110-227-219-118.ngrok-free.app/webhooks/merchant_1747815234/mca_CK494Qer53vC6Qfi0tHI' \
--header 'Content-Type: application/json' \
--data '{
"PaymentInformation": {
"CreditDebitIndicator": "CRDT",
"References": {
"MerchantReference": "pay_Q5hHIJ1qTvUf9l7gq8Xp_1",
"PaymentId": "6964857984"
},
"Status": "Paid",
"Amount": {
"Amount": 65.41,
"Currency": "EUR"
},
"StatusReasonInformation": null
},
"Signature": "abcd"
}'
Response:
{
"error": {
"type": "api",
"message": "Integrity Check Failed! as data mismatched for amount expected 6540 but found 6541",
"code": "IE_00",
"connector_transaction_id": "6964857984"
}
}
Checklist
- [x] I formatted the code
cargo +nightly fmt --all - [x] I addressed lints thrown by
cargo clippy - [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible