Application Development Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 

How to call bank Confirmation of Payee (CoP) using ABAP from SAP with external HTTP RESTful service?

0 Kudos

Anyone had experience of calling UK bank Confirmation of Payee (CoP) service from SAP ECC using ABAP with external HTTP REST interfaces?

6 REPLIES 6

Sandra_Rossi
Active Contributor
0 Kudos

How to call a Web service from ABAP has been asked lots of times, so it's been already answered. Do you have a specific issue?

0 Kudos

Hi,

The question is specific to the UK Banking Confirmation of Payee (CoP) service interface. I'm asking if there is anybody out there that has experience of implementing this particular interface that may be able to assist with some ABAP code snippets (anonymised of course).

Ulrich_Schmidt
Product and Topic Expert
Product and Topic Expert
0 Kudos

You mean, you want to use the basic class CL_HTTP_CLIENT for calling that interface -- or some "higher level" framework? Also do you have a link to an interface description? I could then provide an example of how to call that from SAP BC (which would then be called from ABAP via CALL FUNCTION statement).

0 Kudos

Hi,

Thanks for responding.

It's a JSON interface and I was assuming it would use CL_REST_HTTP_CLIENT but now I'm not so sure? Might be CL_HTTP_CLIENT.

I've picked out some details from the tech spec provided by the bank. Grateful for any advice you can provide:

[There is a whole section on MLS & TLS certificates which I won't include here]

The data formats for APIs are JSON, we expect the inbound requests to be in this format and
subsequently responses back for us will also be JSON.

Responses from the COP API will be in the form of a COPAPIResponse, errorResponse or

gatewayErrorResponse.

Expected Headers:
Content-Type - application/json
x-lbg-origin-client-id - A unique client ID is provided for each client.
Ocp-Apim-Subscription-Key - A unique subscription key is provided for each client.
x-jws-signature - A signature that is generated using the payload, KID and private key of the MLS
certificate.

The format of the x-jws-signature should be:
[base64UrlEncode(Header)]..[base64UrlEncode(JSONWebSignature)] i.e. xyz..zyx ( Omitting the
payload )

Example expected Payload to send:
{ "
account": {
"sortCode": "404040",
"accountNumber": "40404040",
"name": "Fred Bloggs",
"accountType": "Personal",
"identifier": "1234567890123456"
}
}

Identifier field is optional.

In order to verify the authenticity of requests to use we require that they are signed using the MLS
certificate. The generated signature is then included using the header key x-jws-signature. The order
of attributes in the payload and the signed payload in the x-jws-signature must be identical.

Example Of Signature Generation in Javascript (Node.js) ==>


const jws = require('jws');
const { readFileSync } = require('fs');
const privateKey = readFileSync('./mlsPrivateKey.pem', 'utf8');
const payload = {
"account": {
"sortCode": "404040",
"accountNumber": "40404040",
"name": "Fred Bloggs",
"accountType": "Personal",
"identifier": "1234567890123456"
}
} c
onst header = {
"alg": "RS256",
"kid": "KID_PLACEHOLDER"
}
const signature = jws.sign({ header, privateKey, payload, passPhrase)};
console.info(signature);


---

in C# ==>

using System.Security.Cryptography;
using Jose;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
var payloadData = new Dictionary<string, object>()
{
{ "sortCode", "123456" },
{ "accountNumber", "12345678" },
{ "name", "Fred Bloggs" },
{ "accountType", "Personal" }
};
var payload = new Dictionary<string, object>()
{
{ "account", payloadData }
};
var headers = new Dictionary<string, object>()
{
{ "kid", "kid_value" }
};

public static String CreateToken(Dictionary<string, object> headers,
Dictionary<string, object> payload,
string privateRsaKey)
{
RSAParameters rSAParameters;
AsymmetricCipherKeyPair keyPair;
using (var reader = new StreamReader(privateRsaKey))
{
keyPair = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
var privateRsaParams = keyPair.Private as RsaPrivateCrtKeyParameters;
rSAParameters = DotNetUtilities.ToRSAParameters(privateRsaParams);
} u
sing var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rSAParameters);
return JWT.Encode(payload, rsa, JwsAlgorithm.RS256, headers);
} v
ar token = CreateToken(headers, payload, privateKey);
string[] jwsParts = token.Split('.');
var detachedToken = jwsParts[0] + ".." + jwsParts[2];

Note: Above code assumes the private key is in the PKCS#1 RSA format

---

JAVA ==>

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import com.nimbusds.jose.util.Base64URL;
public static RSAPrivateKey readPrivateKey(File file) throws Exception {
String key = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UT
String privateKeyPEM = key
.replace("-----BEGIN PRIVATE KEY-----", "")
.replaceAll(System.lineSeparator(), "")
.replace("-----END PRIVATE KEY-----", "");
byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
} p
ublic static String sign(String kid, RSAPrivateKey keyRsa, String payload) {
String signString = "";
try {
String jsonHeaders = "{\"alg\":\"RS256\",\"kid\":\"" + kid + "\"}";
String hdr = Base64URL.encode(jsonHeaders).toString();
String payl = Base64URL.encode(payload).toString();
String hdrPaylCombine = hdr + "." + payl;
String signature = createSignature(hdrPaylCombine, keyRsa);
//Return signed string in JWKS format
return signString = hdr + ".." + signature;
} catch (Exception e) {
System.out.print("Exception signing data: " + e);
}
return signString;
}

---

None of the above makes much sense to me as an ABAP programmer.

I created an RFC destination ZBANKRFC (type G) and had a go at putting some code together but I have no idea if it's correct?

0 Kudos
REPORT ztst_json_v2.

*Declarations for REST handlers

DATA: go_http_client TYPE REF TO if_http_client,
lo_rest_client TYPE REF TO cl_rest_http_client.

DATA: gt_hdrflds TYPE tihttpnvp. "table of IHTTPNVP.
DATA: gv_retdata TYPE string.

*Declarations for Data refereces
DATA:
lo_json TYPE REF TO /ui2/cl_json,
gv_rfc_dest(40) VALUE 'ZBANKRFC'. "RFC destination

*Declations to store the native format data
TYPES: BEGIN OF ts_slack_info,
user TYPE string,
email TYPE string,
END OF ts_slack_info .

DATA ls_slack_data TYPE ts_slack_info.
*DATA go_http_client TYPE REF TO if_http_client .
DATA: lr_json TYPE REF TO /ui2/cl_json.

PERFORM create_http_client.

CHECK go_http_client IS BOUND.

*Create REST Client object
CREATE OBJECT lo_rest_client
EXPORTING
io_http_client = go_http_client.
TRY.
lo_rest_client->if_rest_client~get( ).
DATA(lo_response) = lo_rest_client->if_rest_client~get_response_entity( ).
DATA(lv_http_status) = lo_response->get_header_field( '~status_code' ).
IF lv_http_status NE 200.

*HTTP Request Failed
DATA(lv_reason) = lo_response->get_header_field( '~status_reason' ).

*STOP Processing
* e_return-type = gc_err.
* e_return-message = lv_reason.
RETURN.
ENDIF.

*Receive the response data in JSON.
DATA(lv_json_data) = lo_response->get_string_data( ).
"Refresh the SLACK response to clear HTTP memory of previous calls
IF go_http_client IS BOUND.
go_http_client->refresh_response( ).
"Close HTTP session (Exception HTTP_NO_MEMORY)
go_http_client->close( ).
ENDIF.

*Collect into the exception table.
CATCH cx_rest_client_exception INTO DATA(lo_rest_client_exception).
ENDTRY.

IF lv_json_data IS NOT INITIAL.
CREATE OBJECT lr_json.
TRY .
lr_json->deserialize_int( EXPORTING json = lv_json_data CHANGING data = ls_slack_data ).



CATCH cx_sy_move_cast_error INTO DATA(lo_move_cast_error) .

DATA(lv_msg_desrl_err) = `HTTP GET failed: ` && lo_move_cast_error->get_longtext( ).
* FREE:l_wa_reason,lo_move_cast_error.
ENDTRY.
ENDIF.

FORM create_http_client.
*& This method is used to fetch the destination details about the SLACK Server from SM59*
DATA:lv_reason TYPE string,
lv_utc_timestamp TYPE timestampl.

DATA gv_auth_val TYPE string.

CONSTANTS gc_auth TYPE string VALUE 'Authorization' ##NO_TEXT.
CONSTANTS gc_content_type TYPE string VALUE 'content-type'.
CONSTANTS gc_accept TYPE string VALUE 'ACCEPT' ##NO_TEXT.
CONSTANTS gc_accept_value TYPE string VALUE 'application/json'.
CONSTANTS gc_rfc_dest TYPE rfcdest VALUE 'SLACK_REST_API_CONV' ##NO_TEXT.
CONSTANTS gc_tabname TYPE rstable-tabname VALUE 'Z_SLACK_LOG'."Table Name
CONSTANTS: gc_e TYPE dd26e-enqmode VALUE 'E'.

*Create the HTTP client instance
CALL METHOD cl_http_client=>create_by_destination
EXPORTING
destination = gv_rfc_dest
IMPORTING
client = go_http_client
EXCEPTIONS
destination_not_found = 1
internal_error = 2
argument_not_found = 3
destination_no_authority = 4
plugin_not_active = 5
OTHERS = 5.

IF sy-subrc NE 0.
MESSAGE 'RC ne 0' TYPE 'S'.
ENDIF.

CHECK go_http_client IS BOUND.

*Set the HTTP header fields
CALL METHOD go_http_client->request->set_header_field
EXPORTING
name = 'clientId'
value = '12345678-1234-1234-1234-123456789abc'.

CALL METHOD go_http_client->request->set_header_field
EXPORTING
name = 'secret'
value = '1234567890abcdefghijklmnopqrstuv'.

CALL METHOD go_http_client->request->set_header_field
EXPORTING
name = 'kid'
value = '1234567890abcdefghijklmnopq'.

** CALL METHOD go_http_client->request->set_header_field
** EXPORTING
** name = 'sortCode'
** value = '123456'.
**
** CALL METHOD go_http_client->request->set_header_field
** EXPORTING
** name = 'accountNumber'
** value = '12345678.
**
** CALL METHOD go_http_client->request->set_header_field
** EXPORTING
** name = 'name'
** value = 'Fred Bloggs'.
**
** CALL METHOD go_http_client->request->set_header_field
** EXPORTING
** name = 'accountType'
** value = 'Personal'.

* CALL METHOD go_http_client->request->set_header_field
* EXPORTING
* name = gc_accept
* value = gc_accept_value.
*
* CALL METHOD go_http_client->request->set_header_field
* EXPORTING
* name = gc_content_type
* value = gc_accept_value.

* " set http protocol version
* go_http_client->request->set_version(
* if_http_request=>co_protocol_version_1_0 ) .

* CALL METHOD go_http_client->response->get_header_fields
CALL METHOD go_http_client->request->get_header_fields
CHANGING
fields = gt_hdrflds.

CALL METHOD go_http_client->request->get_data
RECEIVING
data = gv_retdata.

ENDFORM.

Ulrich_Schmidt
Product and Topic Expert
Product and Topic Expert
0 Kudos

As you already have Java code that performs these tasks, implementing it in the SAP Business Connector would be the perfect choice. Especially since one requirement is to apply a digital signature to the output document, and the SAP BC already has the necessary built-in tools for digitally signing documents.

(I am not sure, whether ABAP has standard classes that can perform digital signatures?)

The way to implement the requirement using SAP BC would then be:

  1. On ABAP side implement a function module that provides the required data (if there isn't one already)
  2. Create an RFC destination, where SAP BC registers, then send the input data from ABAP to SAP BC using "CALL FUNCTION"
  3. On SAP BC, create a Flow Service, which receives the data via RFC, and a "template" with the correct JSon format
  4. When the Flow Service receives the RFC data, it fills it into the template (using built-in Template Processor), applies the digital signature to the resulting JSon (using built-in digital signature Service) and then sends it to the receiver (using built-in HTTP Client)

I think that if you can use all the above built-in tools of the SAP BC, implementing such a Flow would be a matter of a few minutes. If you have to use some of the above custom Java coding (because of special requirements not covered by the standard tools), it may take a bit more time.

For download and documentation of the SAP BC tool, see https://support.sap.com/sbc-download and https://support.sap.com/en/product/connectors/bc/details.html