CyberSource / cybersource-rest-client-python

Python client library for the CyberSource REST API
Other
21 stars 36 forks source link

Issue with the MicroformIntegrationApi #115

Open Leggendario12 opened 7 months ago

Leggendario12 commented 7 months ago

I attempted to implement the MicroformIntegrationApi, but I am experiencing issues with receiving errors in the response.

This is an example implementation where we populated the correct requirements based on the documentation by supplying the appropriate variables for the merchant_id, merchant_keyid, merchant_secret, and run_environment.

The Pydantic class for managing the operation as the key generation is utilized by the rest-api for our appliation (Django | Django-Ninja and Fast-API).


class MicroformApiIntegration(BaseModel):
    """NAB Payment key generator."""

    microform_api: Optional[MicroformIntegrationApi] = Field(
        default=None,
        title="Api Instance",
        description="The API Instance of MicroformIntegrationApi",
    )
    generate_capture_context_request: Optional[GenerateCaptureContextRequest] = Field(
        default=None,
        title="Generate Capture Context Request",
        description="The GenerateCaptureContextRequest instance.",
    )

    def __init__(self, **data) -> None:
        """Initializer for auto-populating the attributes."""
        super().__init__(**data)
        config = Configuration()
        merchant_config_schema = MerchantConfigurationHTTP(
            authentication_type=config.authentication_type.upper(),
            merchantid=config.merchantid,
            run_environment=config.run_environment,
            merchant_keyid=config.merchant_keyid,
            merchant_secretkey=config.merchant_secretkey,
        )
        self.microform_api: MicroformIntegrationApi = MicroformIntegrationApi(
            merchant_config=merchant_config_schema.dict()
        )

        self.generate_capture_context_request = GenerateCaptureContextRequest(
            target_origins="https://preview.ibsportal.dev",
            allowed_card_networks=["Visa", "MasterCard"],
            client_version="v2.0",
        )

    def generate_capture_context(self):
        """Method for generating the capture context."""
        try:
            api_response = self.microform_api.generate_capture_context(
                json.dumps(self.generate_capture_context_request.to_dict())
            )
            return api_response
        except ApiException as error:
            return {"error": error}

    class Config:
        """Class configuration."""

        arbitrary_types_allowed: bool = True

Response

{
  "type": "server_error",
  "errors": [
    {
      "code": "error",
      "detail": "(400)\nReason: \nHTTP response headers: HTTPHeaderDict({'Content-Length': '0', 'Date': 'Wed, 01 May 2024 23:34:18 GMT', 'X-Cnection': 'close', 'Strict-Transport-Security': 'max-age=31536000', 'Connection': 'keep-alive', 'v-c-correlation-id': '373b0634-1247-42ce-b8fa-dbc6285d47d3', 'v-c-response-time': '1714606458'})\n",
      "attr": null
    }
  ]
}

I replicated the same request using HTTP tools and the Cybersource REST-API dashboard. As a result, we were able to successfully generate a JWT response from https://apitest.cybersource.com/microform/v2/sessions.

### Establishing a Payment Session with a Capture Context
POST https://apitest.cybersource.com/microform/v2/sessions
host:apitest.cybersource.com
signature: keyid="42d79b07-b077-42d1-b603-bd94104ecc20", algorithm="HmacSHA256", headers="host request-target digest v-c-merchant-id", signature="**********************"
digest: SHA-256=8Wena4K26YCL0yPlKO7MTpGhRCTtqRwfHb0tdYOYYZE=
v-c-merchant-id: nabsandboxdemo020005001
v-c-date:   Wed, 01 May 2024 01:37:15 GMT
Content-Type: application/json

{
  "targetOrigins": [
    "https://www.test.com"
  ],
  "allowedCardNetworks": [
    "VISA",
    "MAESTRO",
    "MASTERCARD",
    "AMEX",
    "DISCOVER",
    "DINERSCLUB",
    "JCB",
    "CUP",
    "CARTESBANCAIRES"
  ],
  "clientVersion": "v2.0"
}

Response

eyJraWQiOiJ6dSIsImFsZyI6IlJTMjU2In0.eyJmbHgiOnsicGF0aCI6Ii9mbGV4L3YyL3Rva2VucyIsImRhdGEiOiJNNUM0bENoRm53RW1hOGdpQTJvanRSQUFFSDVZbk85Sy9OUG5randMdnNBZ0N6V0YwR3Nrd1I4cU11U2l5THhnTlZBcmZqVFBtVU5zSHJxYk45TzJYbWhSbVhXb1dzakZaanM4MGdwOHhESklha0xJc1BVN3dnRTc0YUE4YWw2MG9mcmVyUDd1elJXdEpjUFh5VU9pT096UTN3XHUwMDNkXHUwMDNkIiwib3JpZ2luIjoiaHR0cHM6Ly90ZXN0ZmxleC5jeWJlcnNvdXJjZS5jb20iLCJqd2siOnsia3R5IjoiUlNBIiwiZSI6IkFRQUIiLCJ1c2UiOiJlbmMiLCJuIjoiaV8tdFBCbEVxWVVnYmdpeXUyZHNBWkZnMUhLVHh2MnQzajdYZS1lRkViVWNVYk9PbEMtVGFvSHRtQjNudjBnWkJ1cXdDSUdlYVFMVThmS2pudC16Wmx5VUFRUk10M1IwNkVpTXJQR0hWemMxWlZHTTAyWDVxSmw2RS1HOWhXTmVyYlNZS3djYW8yMkQ3bzgxQmY4UURGVEctNjdmd1doTkRkbjh2bHlwLTlXa1djZXFMTXJjVHpxUDlUa2VoSDVEUy00dmI1blJrc21EYUFCWG9qRXF6djBBMWRoRFU2SEk4TG9QaTk4bm1RUTNKaXJjbDA1bTAtSEtJby1tV0RkM1lkNEh0d1lObnVnS0E3aDlrZmg0Z3AySWdVTEpyNEcwck1oNTFlLXJqYVU3b2FvOWptNVdYU2Z5UkdpcVNXLTJoVmlWN1diQXNSaW1mVzNwNWcxbVR3Iiwia2lkIjoiMDhqTk9VSHFINVhyTzdaTjZROHpGOHdiZ0pUdW1uSFMifX0sImN0eCI6W3siZGF0YSI6eyJjbGllbnRMaWJyYXJ5IjoiaHR0cHM6Ly90ZXN0ZmxleC5jeWJlcnNvdXJjZS5jb20vbWljcm9mb3JtL2J1bmRsZS92Mi4wL2ZsZXgtbWljcm9mb3JtLm1pbi5qcyIsImFsbG93ZWRDYXJkTmV0d29ya3MiOlsiVklTQSIsIk1BRVNUUk8iLCJNQVNURVJDQVJEIiwiQU1FWCIsIkRJU0NPVkVSIiwiRElORVJTQ0xVQiIsIkpDQiIsIkNVUCIsIkNBUlRFU0JBTkNBSVJFUyJdLCJ0YXJnZXRPcmlnaW5zIjpbImh0dHBzOi8vd3d3LnRlc3QuY29tIl0sIm1mT3JpZ2luIjoiaHR0cHM6Ly90ZXN0ZmxleC5jeWJlcnNvdXJjZS5jb20ifSwidHlwZSI6Im1mLTIuMC4wIn1dLCJpc3MiOiJGbGV4IEFQSSIsImV4cCI6MTcxNDYwODc4OSwiaWF0IjoxNzE0NjA3ODg5LCJqdGkiOiJNclZRYllWS3lsdFExWlpvIn0.NxDhdj3vfisOQAVFqzbzQnTcLYWFZzciZKc9dvx6CjoyTHnxRm6c6x6DgnT8ZEzRgyoAuVal9lWe3X1sQ6QCiKkjrd8midHftE5is77JRcXE9c7JxnaHZIyqZikzONzOF_VuA-qPHtHrnlWeLz6HM9ukh0DXK33SLb-D1sXTSECabgscZKj1hHiFtdwDt-jJv4gD-PPylgiDiOVUQyfTYms6Zru0HDivfJ_aD7rH_LvE_01cdDLSPqx48SMx4j1f33G8c3gi0-h543A51oB9rtfsoFfflWnz7giidz5kOygcWUZzS3N2kiG-TXBMRq8J6YzmYurIg1o0JHlLY_E5Ig

What I noticed is the when I replicated the header generated by the generate_capture_context method it returns an authentication error.

I added the date to the headers value in the signature which is what the MicformIntegrationApi has generated -> signature: keyid="42d79b07-b077-42d1-b603-bd94104ecc20", algorithm="HmacSHA256", headers="host date request-target digest v-c-merchant-id", signature="**********************"

{
  "response": {
    "rmsg": "Authentication Failed"
  }
}

This is where it was implemented. The format of the headers varies greatly from the format expected by the end-point https://https://apitest.cybersource.com/microform/v2/sessions

api_client.ApiClient method that handles the generation of the signature header.

   def call_authentication_header(self, method, header_params, body):

        time = self.mconfig.get_time()

        self.mconfig.request_type_method = method

        if method.upper() == GlobalLabelParameters.POST or method.upper() == GlobalLabelParameters.PUT or method.upper() == GlobalLabelParameters.PATCH:
            self.mconfig.request_json_path_data = body

        header_params['v-c-client-id'] = self.client_id

        # if not self.mconfig.solution_id in (None, ''):
            # header_params['v-c-solution-id'] = self.mconfig.solution_id

        auth = Authorization()
        token = auth.get_token(self.mconfig, self.mconfig.get_time())
        if self.mconfig.authentication_type.upper() == GlobalLabelParameters.HTTP.upper():
            header_params['Accept-Encoding'] = '*'
            header_params['v-c-merchant-id'] = self.mconfig.merchant_id
            header_params["Date"] = time
            header_params["Host"] = self.mconfig.request_host
            header_params["User-Agent"] = GlobalLabelParameters.USER_AGENT_VALUE
            if method.upper() == GlobalLabelParameters.POST or method.upper() == GlobalLabelParameters.PUT or method.upper() == GlobalLabelParameters.PATCH:

                digest_header = self.set_digest((body))

                header_params[
                    GlobalLabelParameters.DIGEST] = GlobalLabelParameters.DIGEST_PREFIX + digest_header.decode("utf-8")

            header_params["Signature"] = token

Snippets from the GetSignatureParameter Object

 def get_signature_param(self, merchant_secret_key, merchant_id, httpmethod):

        signature_list = ([])
        # This method adds the host header

        signature_list.append(GlobalLabelParameters.HOST.lower())
        signature_list.append(": ")
        signature_list.append(self.merchant_config_sigparam.request_host)
        signature_list.append("\n")

        # This method adds the date header
        signature_list.append(GlobalLabelParameters.DATE.lower())
        signature_list.append(": ")
        signature_list.append(self.date)

        signature_list.append("\n")

        # This method adds the request-target header
        signature_list.append(GlobalLabelParameters.REQUEST_TARGET)
        signature_list.append(": ")
        # This forms the get_request_target
        if httpmethod.upper() == GlobalLabelParameters.GET:
            get_request_target = GlobalLabelParameters.GET_LOWER_CASE + self.merchant_config_sigparam.request_target
            signature_list.append(get_request_target)
        # This forms the post_request_target
        elif httpmethod.upper() == GlobalLabelParameters.POST:
            post_request_target = GlobalLabelParameters.POST_LOWER_CASE + self.merchant_config_sigparam.request_target
            signature_list.append(post_request_target)
        elif httpmethod.upper() == GlobalLabelParameters.PUT:
            put_request_target = GlobalLabelParameters.PUT_LOWER_CASE + self.merchant_config_sigparam.request_target
            signature_list.append(put_request_target)
        elif httpmethod.upper() == GlobalLabelParameters.PATCH:
            patch_request_target = GlobalLabelParameters.PATCH_LOWER_CASE + self.merchant_config_sigparam.request_target
            signature_list.append(patch_request_target)
        elif httpmethod.upper() == GlobalLabelParameters.DELETE:
            delete_request_target = GlobalLabelParameters.DELETE_LOWER_CASE + self.merchant_config_sigparam.request_target
            signature_list.append(delete_request_target)