Skip to content

adyen: Paze + SamsungPay for Authorize and SetupRecurring (Paze verified live; SamsungPay blocked on device token) - #2031

Open
shuklatushar226 wants to merge 8 commits into
mainfrom
feat/adyen_grace
Open

adyen: Paze + SamsungPay for Authorize and SetupRecurring (Paze verified live; SamsungPay blocked on device token)#2031
shuklatushar226 wants to merge 8 commits into
mainfrom
feat/adyen_grace

Conversation

@shuklatushar226

@shuklatushar226 shuklatushar226 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

This shared GRACE branch (feat/adyen_grace) now carries four pieces of Adyen work. Two are validated; two are not, so the PR as a whole remains do not merge until the Samsung Pay paths are resolved or split out.

# Work Commit Status
1 SetupRecurring (SamsungPay) — newest 1b1c832d9 ❌ FAIL — unverifiable end-to-end, blocks merge
2 SetupRecurring (Paze) 9e3c4de32 ✅ PASS (validated end-to-end)
3 Authorize (SamsungPay) 9aac731f7 ❌ FAIL — blocks merge
4 Authorize (Paze) 8a6d97cc5 ✅ PASS (validated end-to-end)

Each section below documents one of these, newest first. The previously-written summaries are preserved verbatim.


1. SetupRecurring (SamsungPay) — FAIL (blocks merge)

Summary

[FAILED — DO NOT MERGE] Attempted implementation of the SetupRecurring flow (Rust flow name SetupMandate) for the Samsung Pay payment method on the Adyen connector.

This implementation was generated by GRACE (automated connector integration pipeline) but could not be validated end-to-end. This section is opened for visibility and review — it requires manual intervention before merging.

Failure Reason

The grpcurl SetupRecurring test could not be validated end-to-end.

The request payload is confirmed correct on the wire: Adyen accepted it and issued pspReference RTQ7VTSBJXGQJC75, with none of the request-shape error codes 14_015 (token missing), 14_007 (paymentMethod object error), or 14_394. Adyen then failed at token decryption:

{"status":500,"errorCode":"11_006","message":"Unknown Samsung Payment error","errorType":"internal"}

because data/field_probe/adyen.json carries a fabricated dummy base64 samsungPayToken.

Two controls isolate the cause to the token, not to this change:

  1. The same fabricated token through the already-shipped Authorize path returns the identical 11_006 (pspReference N85MJ3L7QJ2ZTL65) — the failure predates and is independent of this change.
  2. A card through the same SetupRecurring flow fully succeeds (status: CHARGED, connectorMandateId: CR5T7BJDD8H7LN65) — the shared SetupMandate plumbing is healthy.

This is UNDECIDED item 6 of the technical specification: Adyen publishes no Samsung Pay test payload, so a real SDK payload from a Samsung device in Adyen's test environment is required to validate this path.

cargo build -p connector-integration and cargo fmt --check both pass.

Changes

All changes are confined to the wallet SetupMandate TryFrom impl in adyen/transformers.rs; no new flow wiring was needed in adyen.rs.

  • Restructured the wallet arm of TryFrom<(AdyenRouterData<RouterDataV2<SetupMandate, ..>, T>, &WalletData)> for SetupMandateRequest<T> so the match yields (payment_method, mpi_data) — the only wallet-specific parts of the request — with everything after it shared with the card path.
  • Added a WalletData::SamsungPay(..) arm building AdyenPaymentMethod::SamsungPay(AdyenSamsungPay { samsung_pay_token }) from payment_credential.token_data.data, forwarded verbatim — identical construction to the existing Authorize path.
  • mpi_data is None for Samsung Pay: SamsungPayDetails is additionalProperties: false, and the 3DS cryptogram travels inside the opaque token payload.
  • The existing Paze behaviour is unchanged (still network-token pass-through plus mpiData); every other wallet still returns NotImplemented.
  • Documented why POST /storedPaymentMethods cannot be used for either wallet: its PaymentMethodToStore schema is additionalProperties: false and exposes no wallet token field. The recurring credential is therefore created as a side effect of a zero-value authorization with storePaymentMethod + shopperReference + recurringProcessingModel.

Files Modified

  • crates/integrations/connector-integration/src/connectors/adyen/transformers.rs (the only file modified by this commit)

Related, unchanged by this commit: crates/integrations/connector-integration/src/connectors/adyen.rs

gRPC Test Results — SetupRecurring (SamsungPay)

Status: FAIL

# Call Result
1 SetupRecurring (SamsungPay) — target 11_006 / 500 — token decryption
2 Authorize (SamsungPay) — control, pre-existing path 11_006 / 500 — identical
3 SetupRecurring (Card) — control CHARGED / 200
grpcurl output / error details (credentials redacted)
NOTE ON PORT: port 8000 was held by a stale grpc-server from another session, so the service
was started with CS__SERVER__PORT=8100 CS__METRICS__PORT=8180 and tested on localhost:8100.

--- 1/3: SetupRecurring (SamsungPay) — the target test ---

Request payload Adyen actually received (server log, wire-verified,
matches the technical specification exactly):
{
  "amount": {"currency": "USD", "value": 0},
  "paymentMethod": {"type": "samsungpay", "samsungPayToken": "<REDACTED>"},
  "storePaymentMethod": true,
  "shopperReference": "<REDACTED>",
  "shopperInteraction": "Ecommerce",
  "recurringProcessingModel": "UnscheduledCardOnFile",
  "returnUrl": "...",
  "reference": "..."
}

Response:
{
  "status": 500,
  "errorCode": "11_006",
  "message": "Unknown Samsung Payment error, please contact support",
  "errorType": "internal",
  "pspReference": "RTQ7VTSBJXGQJC75"
}

Note the error codes Adyen did NOT return: 14_015 (samsungPayToken missing),
14_007 (paymentMethod object error), 14_394. The request shape is accepted;
only token decryption fails.

--- 2/3: Authorize (SamsungPay) — control, the already-shipped path,
        same fabricated token from data/field_probe/adyen.json ---

Response:
{
  "status": 500,
  "errorCode": "11_006",
  "message": "Unknown Samsung Payment error, please contact support",
  "errorType": "internal",
  "pspReference": "N85MJ3L7QJ2ZTL65"
}

Identical failure on a path this commit does not touch => the failure is a property
of the fabricated token, not of this change.

--- 3/3: SetupRecurring (Card) — control for the shared SetupMandate plumbing ---

Response:
{
  "status": "CHARGED",
  "statusCode": 200,
  "connectorMandateId": "CR5T7BJDD8H7LN65"
}

The shared SetupMandate plumbing this commit routes through is verified healthy.

Validation Checklist — SetupRecurring (SamsungPay)

  • cargo build -p connector-integration passed with zero errors
  • cargo fmt --check clean
  • grpcurl SetupRecurring returned success status (2xx) — blocked on a genuine Samsung Pay SDK payload from a Samsung device (Adyen 11_006, UNDECIDED item 6 of the tech spec)
  • Request payload verified correct on the wire (no 14_015 / 14_007 / 14_394)
  • Controls isolate the failure to the fabricated token, not to this change
  • No credentials in committed source code
  • Only connector-specific files modified

Note: This section was auto-generated by GRACE and marked as "do not merge" because validation could not be completed. Please review the failure reason and grpcurl output above, re-test with a real Samsung Pay SDK payload, and remove the "do not merge" label when ready.


2. SetupRecurring (Paze) — PASS

Implement the SetupRecurring flow for the Paze payment method on the Adyen connector.

This implementation was generated and validated by GRACE (automated connector integration pipeline).

Implementation Notes

Adyen exposes no Paze-native paymentMethod.type, so Paze SetupRecurring goes through the same self-managed network-token pass-through the Authorize flow already uses: paymentMethod.type = networkToken with the TAVV cryptogram carried in mpiData. The recurring credential is created as a side effect of a zero-value authorization with storePaymentMethod: true (plus shopperReference and recurringProcessingModel).

Changes

  • Extracted two shared helpers from the existing Paze Authorize code so both flows build the identical wire payload:
    • build_paze_network_token_data(...) -> AdyenNetworkTokenData — DPAN, expiry, brand, holder name.
    • build_paze_mpi_data(...) -> Result<AdyenMpiData, Error> — TAVV cryptogram + fully-authenticated directory/authentication response + ECI.
  • Added TryFrom<(AdyenRouterData<RouterDataV2<SetupMandate, ..>, T>, &WalletData)> for SetupMandateRequest<T>. Paze only; every other wallet returns NotImplemented.
  • Routed PaymentMethodData::Wallet(ref wallet_data) to that impl in the SetupMandate dispatch (previously it fell into the NotImplemented catch-all).

All changes are confined to adyen/transformers.rs — no new flow wiring was needed in adyen.rs, since SetupMandate was already registered for this connector.

Files Modified

  • crates/integrations/connector-integration/src/connectors/adyen/transformers.rs

gRPC Test Results — SetupRecurring (Paze)

Status: PASS

Three calls were run: the target test, plus two regression controls to prove the extracted helpers did not disturb the existing Paze Authorize or card SetupRecurring paths.

# Call Result
1 SetupRecurring (Paze) — target CHARGED / 200
2 SetupRecurring (Card) — regression control CHARGED / 200
3 Authorize (Paze) — regression control CHARGED / 200
grpcurl calls and responses (credentials redacted)
--- grpcurl 1: SetupRecurring (Paze)  [the target test] ---
grpcurl -plaintext \
  -H "x-connector-config: {\"config\":{\"Adyen\":{\"api_key\":\"<REDACTED>\",\"merchant_account\":\"<REDACTED>\"}}}" \
  -H "x-merchant-id: <REDACTED>" -H "x-request-id: req_paze_setup_002" \
  -d @ localhost:8777 types.PaymentService/SetupRecurring < paze_setup2.json

payload: amount {minor_amount:0, currency:USD}; payment_method.paze_sdk.decrypted_data
  {token:{payment_token:<REDACTED>, exp 03/2030}, payment_card_network:VISA,
   dynamic_data:[{dynamic_data_type:CRYPTOGRAM_3DS, dynamic_data_value:<REDACTED>}], eci:"05",
   billing_address/consumer: John Doe};
  customer{id:cust_paze_probe_001, connector_customer_id:cust_paze_probe_001};
  setup_future_usage:OFF_SESSION; customer_acceptance:OFFLINE; return_url set

Response:
{
  "connectorRecurringPaymentId": "DWQD2BJS6KFJCK75",
  "status": "CHARGED",
  "statusCode": 200,
  "networkTransactionId": "844602633599582",
  "merchantRecurringPaymentId": "test_adyen_paze_setup_recurring_001",
  "connectorResponse": {"additionalPaymentMethodData": {"card": {"authCode": "072625"}}},
  "capturedAmount": "0",
  "state": {"connectorCustomerId": "cust_paze_probe_001"}
}

--- grpcurl 2: SetupRecurring (Card)  [regression control] ---
same headers, -d @ card_setup2.json (field_probe setup_recurring.default payload)
Response:
{
  "connectorRecurringPaymentId": "TT7ST9GFXZ23TV65",
  "status": "CHARGED", "statusCode": 200,
  "mandateReference": {"connectorMandateId": {"connectorMandateId": "R7PLZC725RBDVP65"}},
  "networkTransactionId": "988867975250228",
  "merchantRecurringPaymentId": "ctl_card_setup_001",
  "capturedAmount": "0"
}

--- grpcurl 3: Authorize (Paze)  [regression control for the refactored helpers] ---
same headers, -d @ paze_auth.json (amount 100 USD, same Paze payload)
Response:
{
  "merchantTransactionId": "test_adyen_paze_authorize_001",
  "connectorTransactionId": "JNLVT3LMJTX9MH75",
  "status": "CHARGED", "statusCode": 200,
  "networkTransactionId": "494879523807763",
  "capturedAmount": "100"
}

Validation Checklist — SetupRecurring (Paze)

  • cargo build passed with zero errors
  • grpcurl SetupRecurring returned success status (2xx, CHARGED)
  • Regression controls (card SetupRecurring, Paze Authorize) still return 2xx
  • cargo +nightly fmt --check clean
  • No credentials in committed source code
  • Only connector-specific files modified

3. Authorize (SamsungPay) — FAIL (blocks merge)

The section below is the previously-written Samsung Pay summary, preserved verbatim.


Summary

[FAILED — DO NOT MERGE] Attempted implementation of the Authorize flow for the SamsungPay payment method on the Adyen connector.

This implementation was generated by GRACE (automated connector integration pipeline) but did not pass validation. This PR is opened for visibility and review — it requires manual intervention before merging.

This branch also carries a previously-validated Paze Authorize implementation (commit 8a6d97cc5, originally opened as this PR). Because the failed Samsung Pay commit now sits on the same branch, the whole PR is marked do not merge until the Samsung Pay path is resolved or split out. The original Paze summary is preserved verbatim at the bottom of this description.

Failure Reason

Build passed (cargo build green) and the outgoing request is verifiably correct on the wire, but the end-to-end grpcurl Authorize cannot return 2xx without a genuine Samsung Pay SDK token generated on a Samsung device.

Adyen returns HTTP 422 / errorCode 11_002 ("Samsung Payment Info is missing required fields") for every synthetic token. Critically, Adyen did not return:

  • 14_015 (token missing) — so the samsungPayToken field is present and correctly named,
  • 14_007 (paymentMethod object error) — so the paymentMethod object shape is accepted,
  • 901 / 905_3 (configuration) — so the payment method type and merchant-account enablement are confirmed correct.

Only token authenticity fails. Adyen publishes no static Samsung Pay test token. Credentials were not rejected (no 401/403).

Changes (validated build, unverifiable end-to-end)

  1. Added AdyenSamsungPay { #[serde(rename = "samsungPayToken")] samsung_pay_token: Secret<String> } alongside the existing AdyenGPay / AdyenApplePay structs.
  2. Added the #[serde(rename = "samsungpay")] SamsungPay(Box<AdyenSamsungPay>) variant to AdyenPaymentMethod.
  3. Added a WalletData::SamsungPay(..) arm to the TryFrom<(&WalletData, &RouterDataV2<Authorize, ..>)> impl, and removed WalletData::SamsungPay(_) from the NotImplemented catch-all.
  4. mpi_data is intentionally left at the _ => None default for Samsung Pay — 3DS data travels inside the opaque token.
  5. Token serialisation decision: samsungPayToken is payment_credential.token_data.data forwarded verbatim, matching this repo's Cybersource consumer of SamsungPayWalletData and the upstream hyperswitch Adyen connector. Three envelope variants were probed against Adyen and all returned an identical 11_002, confirming the rejection is token authenticity, not envelope shape.

Out of scope / unchanged: the PaymentMethodType::SamsungPay -> PaymentType::Scheme mapping, which is used only for stored-token MIT (AdyenMandate).

Files Modified

  • crates/integrations/connector-integration/src/connectors/adyen/transformers.rs

gRPC Test Results — SamsungPay

Status: FAIL

grpcurl output / error details (credentials redacted)
NOTE ON PORT: ports 8000 and 8080 were already occupied by processes outside this session,
so the service was started with CS__SERVER__PORT=8100 CS__METRICS__PORT=8180 and tested on
localhost:8100.

Start command:
  CS__SERVER__PORT=8100 CS__METRICS__PORT=8180 ./target/debug/grpc-server

--- grpcurl 1/3: Authorize (SamsungPay) — implemented path,
    samsungPayToken = payment_credential.3_d_s.data ---

grpcurl -plaintext \
  -H 'x-connector: adyen' \
  -H 'x-auth: body-key' \
  -H 'x-api-key: <REDACTED>' \
  -H 'x-key1: <REDACTED>' \
  -H 'x-merchant-id: <REDACTED>' \
  -H 'x-request-id: test_adyen_samsungpay_001' \
  -d '{
    "merchant_transaction_id": "test_adyen_samsungpay_001",
    "amount": { "minor_amount": 1000, "currency": "USD" },
    "payment_method": {
      "samsung_pay_sdk": {
        "payment_credential": {
          "method": "3DS",
          "recurring_payment": false,
          "card_brand": "VISA",
          "dpan_last_four_digits": { "value": "<REDACTED>" },
          "card_last_four_digits": { "value": "<REDACTED>" },
          "token_data": {
            "type": "S",
            "version": "100",
            "data": { "value": "<REDACTED>" }
          }
        }
      }
    },
    "capture_method": "AUTOMATIC",
    "address": { "billing_address": {} },
    "auth_type": "NO_THREE_DS",
    "return_url": "https://example.com/return",
    "browser_info": {
      "color_depth": 24, "screen_height": 900, "screen_width": 1440,
      "java_enabled": false, "java_script_enabled": true, "language": "en-US",
      "time_zone_offset_minutes": -480, "accept_header": "application/json",
      "user_agent": "Mozilla/5.0 (samsung-pay-test)",
      "accept_language": "en-US,en;q=0.9", "ip_address": "1.2.3.4"
    }
  }' \
  localhost:8100 types.PaymentService/Authorize

Response (gRPC error):
ERROR:
  Code: Internal
  Message: grpc-status=Unknown, grpc-message="Samsung Payment Info is missing required fields",
           grpc-status-details-bin=message:"CONNECTOR_ERROR_RESPONSE"
             1:"Samsung Payment Info is missing required fields"
             3:422
             4:"11_002 / Samsung Payment Info is missing required fields / CJTV9LR7BF9SKTV5"

Adyen response body captured in server logs:
{
  "status": 422,
  "errorCode": "11_002",
  "message": "Samsung Payment Info is missing required fields",
  "errorType": "validation",
  "pspReference": "CJTV9LR7BF9SKTV5"
}

Outgoing request Adyen actually received (server log, secrets masked):
POST https://checkout-test.adyen.com/v68/payments
{"amount":{"currency":"USD","value":1000},"merchantAccount":"<REDACTED>",
 "paymentMethod":{"type":"samsungpay","samsungPayToken":"<REDACTED>"},
 "reference":"test_adyen_samsungpay_001","returnUrl":"https://example.com/return",
 "browserInfo":{...},"shopperInteraction":"Ecommerce",
 "additionalData":{"executeThreeD":"false"},"shopperIP":"<REDACTED>"}

--- grpcurl 2/3: probe — samsungPayToken = base64(re-serialised payment_credential JSON) ---
Same command as above with request_ref "probe_b64_1785710363" and a base64-encoded
payment_credential JSON as token_data.data.value.
Response:
{
  "status": 422,
  "errorCode": "11_002",
  "message": "Samsung Payment Info is missing required fields",
  "errorType": "validation",
  "pspReference": "J8GJ7HRBJXGQJC75"
}

--- grpcurl 3/3: probe — samsungPayToken = raw payment_credential JSON (not base64) ---
Same command with request_ref "probe_raw_1785710363" and the raw payment_credential JSON
as token_data.data.value.
Response:
{
  "status": 422,
  "errorCode": "11_002",
  "message": "Samsung Payment Info is missing required fields",
  "errorType": "validation",
  "pspReference": "W55GB8SMV7VGGS75"
}

Conclusion from the three calls: Adyen's rejection is invariant to the token envelope,
i.e. it is a token-authenticity failure, not a request-construction failure.

Validation Checklist — SamsungPay

  • cargo build passed with zero errors
  • grpcurl Authorize returned success status (2xx) — blocked on a genuine Samsung Pay SDK token (Adyen 11_002)
  • No credentials in committed source code
  • Only connector-specific files modified

Note: This PR was auto-generated by GRACE and marked as "do not merge" because validation failed. Please review the failure reason and grpcurl output above, fix the issues manually, and remove the "do not merge" label when ready.



Previously validated on this branch: Paze Authorize (PASS)

Summary

Implement Authorize flow for the Paze payment method on the Adyen connector.

This implementation was generated and validated by GRACE (automated connector integration pipeline).

Adyen exposes no Paze-native paymentMethod.type. Paze is therefore integrated through Adyen's documented network-token pass-through: the decrypted Paze DPAN is submitted as paymentMethod.type = "networkToken", and the TAVV cryptogram is carried separately in mpiData.

Changes

  • adyen/transformers.rsWalletData::Paze moved out of the NotImplemented arm of AdyenPaymentMethod::try_from((&WalletData, &RouterDataV2)) and mapped to the existing AdyenPaymentMethod::NetworkToken variant, built from the decrypted Paze DPAN (number / expiryMonth / expiryYear expanded to 4 digits / holderName / brand via get_adyen_card_network).
  • adyen/transformers.rs — new WalletData::Paze arm in the wallet mpi_data match of AdyenPaymentRequest: tokenAuthenticationVerificationValue = Paze TAVV, eci = Paze ECI (falling back to PAZE_DEFAULT_ECI = "05"), directoryResponse and authenticationResponse = "Y". shopperInteraction resolves to Ecommerce via the existing AdyenShopperInteraction::from.
  • adyen/transformers.rs — two helpers: get_paze_decrypted_data (handles both PazeWalletData::Decrypted and the CompleteResponse JSON form, mirroring the existing cybersource pattern) and get_paze_token_cryptogram (picks the CRYPTOGRAM_3DS dynamic-data entry, falls back to the first entry carrying a value, and errors with MissingRequiredField if absent).
  • adyen.rs — declared PaymentMethod::Wallet / PaymentMethodType::Paze in ADYEN_SUPPORTED_PAYMENT_METHODS.
  • Technical specification generated at grace/rulesbook/codegen/references/specs/Adyen.md (1009 lines) from 13 verified Adyen documentation sources.

Files Modified

  • crates/integrations/connector-integration/src/connectors/adyen.rs
  • crates/integrations/connector-integration/src/connectors/adyen/transformers.rs

gRPC Test Results

Status: PASS — both Paze payload variants (decrypted_data and complete_response) returned CHARGED against the Adyen sandbox.

grpcurl Authorize call — Paze via decrypted_data (credentials redacted)
grpcurl -plaintext \
  -H 'x-connector: adyen' \
  -H 'x-auth: body-key' \
  -H 'x-api-key: <REDACTED>' \
  -H 'x-key1: <REDACTED>' \
  -d '{
    "merchant_transaction_id": "test_adyen_paze_003",
    "amount": {"minor_amount": 1000, "currency": "USD"},
    "payment_method": {
      "paze_sdk": {
        "decrypted_data": {
          "client_id": {"value": "paze_client_id_test"},
          "profile_id": "paze_profile_id_test",
          "token": {
            "payment_token": {"value": "<REDACTED>"},
            "token_expiration_month": {"value": "03"},
            "token_expiration_year": {"value": "2030"},
            "payment_account_reference": {"value": "<REDACTED>"}
          },
          "payment_card_network": "VISA",
          "dynamic_data": [
            {"dynamic_data_value": {"value": "<REDACTED>"},
             "dynamic_data_type": "CRYPTOGRAM_3DS",
             "dynamic_data_expiration": "2030-03-31"}
          ],
          "billing_address": {"name": {"value": "John Doe"}, "line1": {"value": "123 Test St"},
            "city": {"value": "New York"}, "state": {"value": "NY"}, "zip": {"value": "10001"},
            "country_code": "US"},
          "consumer": {"first_name": {"value": "John"}, "last_name": {"value": "Doe"},
            "full_name": {"value": "John Doe"}, "email_address": {"value": "test@example.com"},
            "country_code": "US"},
          "eci": "05"
        }
      }
    },
    "capture_method": "AUTOMATIC",
    "address": {"billing_address": {"first_name": {"value": "John"}, "last_name": {"value": "Doe"},
      "line1": {"value": "123 Test St"}, "city": {"value": "New York"}, "state": {"value": "NY"},
      "zip_code": {"value": "10001"}, "country_alpha2_code": "US",
      "email": {"value": "test@example.com"}}},
    "auth_type": "NO_THREE_DS",
    "enrolled_for_3ds": false,
    "return_url": "https://example.com/return",
    "webhook_url": "https://example.com/webhook",
    "browser_info": {"color_depth": 24, "screen_height": 1080, "screen_width": 1920,
      "java_enabled": false, "java_script_enabled": true, "language": "en-US",
      "time_zone_offset_minutes": -330,
      "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
      "ip_address": "192.0.2.1"}
  }' \
  localhost:8455 types.PaymentService/Authorize

Response:
{
  "merchantTransactionId": "test_adyen_paze_003",
  "connectorTransactionId": "N9CNRJW5JV3MJT75",
  "status": "CHARGED",
  "statusCode": 200,
  "networkTransactionId": "192728831595159",
  "capturedAmount": "1000",
  "connectorResponse": {
    "additionalPaymentMethodData": {
      "card": {
        "authCode": "<REDACTED>"
      }
    }
  },
  "connectorReferenceId": "test_adyen_paze_003"
}
grpcurl Authorize call — Paze via complete_response raw decrypted JSON (credentials redacted)
grpcurl -plaintext \
  -H 'x-connector: adyen' \
  -H 'x-auth: body-key' \
  -H 'x-api-key: <REDACTED>' \
  -H 'x-key1: <REDACTED>' \
  -d '{
    "merchant_transaction_id": "test_adyen_paze_004",
    "amount": {"minor_amount": 1000, "currency": "USD"},
    "payment_method": {
      "paze_sdk": {
        "complete_response": {"value": "{\"clientId\":\"paze_client_id_test\",\"profileId\":\"paze_profile_id_test\",\"token\":{\"paymentToken\":\"<REDACTED>\",\"tokenExpirationMonth\":\"03\",\"tokenExpirationYear\":\"2030\",\"paymentAccountReference\":\"<REDACTED>\"},\"paymentCardNetwork\":\"Visa\",\"dynamicData\":[{\"dynamicDataValue\":\"<REDACTED>\",\"dynamicDataType\":\"CRYPTOGRAM_3DS\",\"dynamicDataExpiration\":\"2030-03-31\"}],\"billingAddress\":{\"name\":\"John Doe\",\"line1\":\"123 Test St\",\"city\":\"New York\",\"state\":\"NY\",\"zip\":\"10001\",\"countryCode\":\"US\"},\"consumer\":{\"firstName\":\"John\",\"lastName\":\"Doe\",\"fullName\":\"John Doe\",\"emailAddress\":\"test@example.com\",\"countryCode\":\"US\"},\"eci\":\"05\"}"}
      }
    },
    "capture_method": "AUTOMATIC",
    "address": {"billing_address": {"first_name": {"value": "John"}, "last_name": {"value": "Doe"},
      "line1": {"value": "123 Test St"}, "city": {"value": "New York"}, "state": {"value": "NY"},
      "zip_code": {"value": "10001"}, "country_alpha2_code": "US",
      "email": {"value": "test@example.com"}}},
    "auth_type": "NO_THREE_DS",
    "enrolled_for_3ds": false,
    "return_url": "https://example.com/return",
    "webhook_url": "https://example.com/webhook",
    "browser_info": {"color_depth": 24, "screen_height": 1080, "screen_width": 1920,
      "java_enabled": false, "java_script_enabled": true, "language": "en-US",
      "time_zone_offset_minutes": -330,
      "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
      "ip_address": "192.0.2.1"}
  }' \
  localhost:8455 types.PaymentService/Authorize

Response:
{
  "merchantTransactionId": "test_adyen_paze_004",
  "connectorTransactionId": "VW5DBM8GV5KRCK65",
  "status": "CHARGED",
  "statusCode": 200,
  "networkTransactionId": "467088368193370",
  "capturedAmount": "1000",
  "connectorResponse": {
    "additionalPaymentMethodData": {
      "card": {
        "authCode": "<REDACTED>"
      }
    }
  },
  "connectorReferenceId": "test_adyen_paze_004"
}

Validation Checklist

  • cargo build passed with zero errors
  • grpcurl Authorize returned success status (2xx / CHARGED)
  • No credentials in committed source code
  • Only connector-specific files modified

Post-implementation verification (orchestrator follow-up, commits 07caf924b, db25bb5d8)

Three verification passes were run after the four implementation commits: a line-by-line
parity check against the hyperswitch reference Adyen connector, an audit against recurring
review themes mined from merged PRs, and a router-data comparison.

Live status against the Adyen sandbox

Scenario Result
Authorize + Paze (ECI present) ✅ CHARGED
Authorize + Paze (ECI absent) ✅ CHARGED
SetupRecurring + Paze ✅ CHARGED, zero-value auth, recurring id returned
Authorize + SamsungPay ⚠️ reaches Adyen, refused at token decryption (11_006)
SetupRecurring + SamsungPay ⚠️ reaches Adyen, refused at token decryption (11_006)

SamsungPay needs a genuine Samsung device SDK payload — Adyen publishes no static test
token. Both refusals returned real pspReference values and none of the request-shape
errors (14_015, 14_007, 14_394), so the payload shape, type tag, and merchant-account
enablement are confirmed correct. The SamsungPay paymentMethod object is byte-identical
to the reference implementation.

Defects found and fixed in 07caf924b

  • Removed the fabricated mpiData.eci = "05" default, which asserted a full-authentication
    liability shift Adyen never granted. Verified live that Adyen accepts the omission.
  • Removed a blind .or_else fallback that shipped any dynamic_data entry (e.g. a dynamic
    CVV) as the TAVV cryptogram when no CRYPTOGRAM_3DS was present.
  • skip_serializing_if on AdyenNetworkTokenData.holder_name — was emitting "holderName": null.
  • Mapped PaymentMethodType::Paze to PaymentType::Scheme; without it a Paze mandate could
    be created but never charged via RepeatPayment/MIT.
  • Registered SamsungPay in ADYEN_SUPPORTED_PAYMENT_METHODS (never added) and flipped Paze
    mandates to Supported to match the implemented SetupRecurring.
  • Added the missing PazeSdk arm to ForeignTryFrom<grpc PaymentMethod> for PaymentMethod.
  • Replaced the SetupMandate wallet catch-all with explicit variants.

Reviewer decision needed

mpiData.tokenAuthenticationVerificationValue intentionally carries the TAVV cryptogram;
the reference sends the Payment Account Reference. UCS's mapping appears correct (a PAR is
an identifier, not an authentication value) but this field will diff on every Paze transaction
under shadow validation. See grace/adyen_paze_samsungpay_deviations.md (D1).

Not verified

Router-data shadow validation never ran end-to-end — the hyperswitch router was never built
locally and the machine's root disk is full. For the same reason data/field_probe/adyen.json
and docs-generated/ could not be regenerated, so the probe still records
authorize.Paze: not_supported (stale — it returns CHARGED live).

Full write-ups: grace/adyen_paze_samsungpay_parity_report.md,
grace/adyen_pr_review_theme_audit.md, grace/adyen_shadow_validation_report.md,
grace/adyen_paze_samsungpay_deviations.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shuklatushar226
shuklatushar226 requested a review from a team as a code owner August 2, 2026 22:08
Add Samsung Pay wallet support to the Adyen Authorize flow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shuklatushar226 shuklatushar226 changed the title feat(connector): implement Authorize (Paze) for adyen [GRACE-FAILED] feat(connector): implement Authorize (SamsungPay) for adyen Aug 2, 2026
@XyneSpaces

Copy link
Copy Markdown

[blocking] The new Paze path depends on extracting the right dynamic_data entry and submitting it as a self-managed network token with mpiData, but there are no committed tests covering that mapping. Please add unit tests for the CRYPTOGRAM_3DS lookup, including fallback behavior, and the resulting AdyenNetworkTokenData/AdyenMpiData serialization so this payment method is protected against regressions.

Auto-applied by CI:
- cargo +nightly fmt --all
- make -C sdk generate (if applicable)
- make docs (if applicable)

This commit was automatically generated by GitHub Actions.
@hyperswitch-bot
hyperswitch-bot Bot requested a review from a team as a code owner August 2, 2026 22:58
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shuklatushar226 shuklatushar226 changed the title [GRACE-FAILED] feat(connector): implement Authorize (SamsungPay) for adyen [GRACE-FAILED] adyen: SetupRecurring (Paze) PASS + Authorize (Paze) PASS + Authorize (SamsungPay) FAILED Aug 2, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shuklatushar226 shuklatushar226 changed the title [GRACE-FAILED] adyen: SetupRecurring (Paze) PASS + Authorize (Paze) PASS + Authorize (SamsungPay) FAILED [GRACE-FAILED] adyen: SetupRecurring (SamsungPay) FAILED + SetupRecurring (Paze) PASS + Authorize (Paze) PASS + Authorize (SamsungPay) FAILED Aug 3, 2026
shuklatushar226 and others added 2 commits August 3, 2026 06:07
…urring

Follow-up to the four GRACE commits that added Paze and SamsungPay to the
Adyen Authorize and SetupRecurring (SetupMandate) flows. Fixes the defects
found by the hyperswitch parity check, the merged-PR review-theme audit, and
the shadow-validation router-data comparison.

Correctness / parity:
- Drop the fabricated PAZE_DEFAULT_ECI = "05" default. Defaulting asserted a
  full-authentication liability shift Adyen never granted. The reference omits
  mpiData.eci when absent; verified live that Adyen accepts the omission.
- Remove the blind .or_else fallback in the Paze TAVV lookup, which shipped any
  dynamic_data entry (e.g. a dynamic CVV) as the cryptogram when no
  CRYPTOGRAM_3DS was present. Now fails with the present types attached.
- skip_serializing_if on AdyenNetworkTokenData.holder_name; the reference uses
  skip_serializing_none, so UCS was emitting "holderName": null.

Reachability / capability:
- Map PaymentMethodType::Paze to PaymentType::Scheme. Paze rides the
  network-token pass-through, so without this a Paze mandate could be created
  but never charged via RepeatPayment/MIT.
- Register SamsungPay in ADYEN_SUPPORTED_PAYMENT_METHODS (was never added) and
  flip Paze mandates to Supported, matching the implemented SetupRecurring.
- Add the missing PazeSdk arm to ForeignTryFrom<grpc PaymentMethod> for
  PaymentMethod; Paze fell to the catch-all and was rejected as an unsupported
  variant on every path that consults this mapping.

Robustness:
- Replace the SetupMandate wallet catch-all with explicit variants so a new
  upstream wallet is a compile error rather than a runtime NotImplemented.

Verified live against the Adyen sandbox: Authorize+Paze (ECI present and
absent) and SetupRecurring+Paze all return CHARGED; SamsungPay reaches Adyen
and is refused only at token decryption (11_006), which requires a genuine
device SDK payload.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…SamsungPay

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shuklatushar226
shuklatushar226 requested review from a team as code owners August 3, 2026 00:38
@shuklatushar226 shuklatushar226 changed the title [GRACE-FAILED] adyen: SetupRecurring (SamsungPay) FAILED + SetupRecurring (Paze) PASS + Authorize (Paze) PASS + Authorize (SamsungPay) FAILED adyen: Paze + SamsungPay for Authorize and SetupRecurring (Paze verified live; SamsungPay blocked on device token) Aug 3, 2026
Auto-applied by CI:
- cargo +nightly fmt --all
- make -C sdk generate (if applicable)
- make docs (if applicable)

This commit was automatically generated by GitHub Actions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants