# Direct Card Payment (S2S)

## Overview

1. Before starting, your merchant account needs to have been enabled to do funds collections via card in the specified supported options. Supported card options are shown [here](https://docs.elemitech.com/getting-started/supported-countries-regions).
2. We equally recommend that you go through the [Getting Started](https://docs.elemitech.com/funds-collection/getting-started) section to have a high-level understanding of the funds collection process.
3. You're required to be **PCI DSS certified (Level 1)** in order to use the direct card charge workflow.

### PCI DSS Compliance

The Payment Card Industry Data Security Standard (PCI DSS) is a set of global security standards designed to ensure that all entities that process, store or transmit cardholder data and/or sensitive authentication data maintain a secure environment. PCI DSS sets a baseline level of protection for consumers and helps reduce fraud and data breaches across the entire payment ecosystem. It is applicable to any organization that accepts or processes payment cards. Learn more [here](https://www.vikingcloud.com/faq).

{% hint style="info" %}
As a merchant, you can create a payment form on your interface and then send the card details within the request body. Card data however is not sent in plain text but rather as an encrypted string as described in more detail below.
{% endhint %}

## Step 1: Collect the card payment data

After collecting the necessary card and payment information from your customer, prepare your data object to look like the example shown below. In this payload, you may choose to pass your user’s PIN or not. The purpose of this JSON is to prepare the card data for encryption since the request doesn't expect it in plain text. The outcome of the encryption will be value sent in a special parameter named ***card\_cipher***, which is sent alongside the main collection API request

{% tabs %}
{% tab title="Sample JSON for card data" %}

```json
{
  "first_name": "John",
  "last_name": "Doe",
  "card_no": "4622000000005678",
  "exp_month": "06",
  "exp_year": "22",
  "cvv": "123",
  "billing_address": "Second Street",
  "billing_city": "San Francisco",
  "billing_zip": "94105",
  "billing_state": "CA",
  "billing_country": "US"
}
```

{% endtab %}

{% tab title="JSON Parameter Descriptions" %}

<table><thead><tr><th width="161">Parameter</th><th width="88">Type</th><th width="263">Description</th><th>Required</th></tr></thead><tbody><tr><td>first_name</td><td>String</td><td>The first name of the cardholder as registered by the issuer</td><td>YES</td></tr><tr><td>last_name</td><td>String</td><td>The last name of the cardholder as registered by the issuer</td><td>YES</td></tr><tr><td>card_no</td><td>String</td><td>The card number. Usually, the length of 16 to 19</td><td>YES</td></tr><tr><td>exp_month</td><td>String</td><td>The card expiry month. For single-digit months, prefix 0 e.g. <code>06</code></td><td>YES</td></tr><tr><td>exp_year</td><td>String</td><td>The card expiry year</td><td>YES</td></tr><tr><td>cvv</td><td>String</td><td>The card CVV, CVC, etc depending on the nature of the card</td><td>YES</td></tr><tr><td>billing_address</td><td>String</td><td>Billing address as registered by the issuer.</td><td>Only for cards issued in the USA, Canada, and Europe</td></tr><tr><td>billing_city</td><td>String</td><td>Billing city name as registered by the issuer.</td><td>Only for cards issued in the USA, Canada, and Europe</td></tr><tr><td>billing_zip</td><td>String</td><td>The zip/postal code.</td><td>Only for cards issued in the USA, Canada, and Europe</td></tr><tr><td>billing_state</td><td>String</td><td>State name/code</td><td>Only for cards issued in the USA, Canada, and Europe</td></tr><tr><td>billing_country</td><td>String</td><td>The 2-character ISO country code. The country code list can be obtained using the API description that follows</td><td>Only for cards issued in the USA, Canada, and Europe</td></tr></tbody></table>
{% endtab %}
{% endtabs %}

## Step 2: Encrypt the card JSON data

Elemi ensures the complete security of card data in transit by using RSA encryption. The stringified payment data from Step 1 must be encrypted using the [Elemi Public Key](https://docs.elemitech.com/getting-started/elemi-public-keys) before making the collection request. To encrypt the JSON string correctly, we have organized a few code samples to illustrate how it could be done as shown below.

Copy the public key for the environment you're working with from [here](https://docs.elemitech.com/getting-started/elemi-public-keys). This document assumes that the public key would be stored somewhere on your server under the name `elemi.public.key.pem`

{% tabs %}
{% tab title="PHP" %}
{% code overflow="wrap" lineNumbers="true" %}

```php
<?php
function encryptJSON($jsonString, $publicKeyPath) {
    $publicKey = file_get_contents($publicKeyPath);
    $pubKeyId = openssl_pkey_get_public($publicKey);
    openssl_public_encrypt($jsonString, $encrypted, $pubKeyId);
    return base64_encode($encrypted);
}

// Example usage
$jsonData = json_encode(['first_name' => 'John', 'last_name' => 'Doe', 'card_no' => '4622000000005678', 'exp_month' => '06', 'exp_year' => '2024', 'cvv' => '123', 'billing_address' => 'Second Street', 'billing_city' => 'San Francisco', 'billing_zip' => '94105', 'billing_state' => 'CA', 'billing_country' => 'US']);

$publicKeyPath = 'path-to-file/elemi.public.key.pem';

try {
    $encryptedData = encryptJSON($jsonData, $publicKeyPath);
    echo "Encrypted data: " . $encryptedData;
} catch (Exception $e) {
    echo "Encryption failed: " . $e->getMessage();
}
?>
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}
{% code overflow="wrap" lineNumbers="true" %}

```javascript
const crypto = require("crypto");
const fs = require("fs");

function encryptJSON(jsonString, publicKeyPath) {
  // Read the public key from file
  const publicKey = fs.readFileSync(publicKeyPath, "utf8");

  // Convert JSON string to Buffer
  const buffer = Buffer.from(jsonString, "utf8");

  // Encrypt the buffer
  const encrypted = crypto.publicEncrypt(
    {
      key: publicKey,
    },
    buffer
  );

  // Return the encrypted data as base64
  return encrypted.toString("base64");
}

// Example usage
const jsonData = JSON.stringify({
  first_name: "John",
  last_name: "Doe",
  card_no: "4622000000005678",
  exp_month: "06",
  exp_year: "2024",
  cvv: "123",
  billing_address: "Second Street",
  billing_city: "San Francisco",
  billing_zip: "94105",
  billing_state: "CA",
  billing_country: "US",
});
const publicKeyPath = "path-to-file/elemi.public.key.pem";

try {
  const encryptedData = encryptJSON(jsonData, publicKeyPath);
  console.log("Encrypted data:", encryptedData);
} catch (error) {
  console.error("Encryption failed:", error.message);
}
```

{% endcode %}
{% endtab %}

{% tab title="Java" %}
{% code overflow="wrap" lineNumbers="true" %}

```java
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;

public class JSONEncryptor {
    public static String encryptJSON(String jsonString, String publicKeyPath) throws Exception {
        byte[] keyBytes = Files.readAllBytes(Paths.get(publicKeyPath));
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PublicKey publicKey = kf.generatePublic(spec);

        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedBytes = cipher.doFinal(jsonString.getBytes());
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    public static void main(String[] args) {
        String jsonData = "{\"first_name\":\"John\",\"last_name\":\"Doe\",\"card_no\":\"4622000000005678\",\"exp_month\":\"06\",\"exp_year\":\"22\",\"cvv\":\"123\",\"billing_address\":\"Second Street\",\"billing_city\":\"San Francisco\",\"billing_zip\":\"94105\",\"billing_state\":\"CA\",\"billing_country\":\"US\"}";
        String publicKeyPath = "path-to-file/elemi.public.key.pem";

        try {
            String encryptedData = encryptJSON(jsonData, publicKeyPath);
            System.out.println("Encrypted data: " + encryptedData);
        } catch (Exception e) {
            System.err.println("Encryption failed: " + e.getMessage());
        }
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code overflow="wrap" lineNumbers="true" %}

```csharp
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

class JSONEncryptor
{
    static string EncryptJSON(string jsonString, string publicKeyPath)
    {
        byte[] publicKeyBytes = File.ReadAllBytes(publicKeyPath);
        using (RSA rsa = RSA.Create())
        {
            rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);
            byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonString);
            byte[] encryptedBytes = rsa.Encrypt(jsonBytes, RSAEncryptionPadding.OaepSHA256);
            return Convert.ToBase64String(encryptedBytes);
        }
    }

    static void Main()
    {
        string jsonData = "{\"first_name\":\"John\",\"last_name\":\"Doe\",\"card_no\":\"4622000000005678\",\"exp_month\":\"06\",\"exp_year\":\"22\",\"cvv\":\"123\",\"billing_address\":\"Second Street\",\"billing_city\":\"San Francisco\",\"billing_zip\":\"94105\",\"billing_state\":\"CA\",\"billing_country\":\"US\"}";
        string publicKeyPath = "path-to-file/elemi.public.key.pem";

        try
        {
            string encryptedData = EncryptJSON(jsonData, publicKeyPath);
            Console.WriteLine("Encrypted data: " + encryptedData);
        }
        catch (Exception e)
        {
            Console.WriteLine("Encryption failed: " + e.Message);
        }
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code overflow="wrap" lineNumbers="true" %}

```python
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64

def encrypt_json(json_string, public_key_path):
    with open(public_key_path, 'rb') as key_file:
        public_key = RSA.import_key(key_file.read())
    cipher = PKCS1_v1_5.new(public_key)
    encrypted = cipher.encrypt(json_string.encode())
    return base64.b64encode(encrypted).decode()

# Example usage
json_data = '{"first_name":"John","last_name":"Doe","card_no":"4622000000005678","exp_month":"06","exp_year":"22","cvv":"123","billing_address":"Second Street","billing_city":"San Francisco","billing_zip":"94105","billing_state":"CA","billing_country":"US"}'
public_key_path = 'path-to-file/elemi.public.key.pem'

try:
    encrypted_data = encrypt_json(json_data, public_key_path)
    print("Encrypted data:", encrypted_data)
except Exception as e:
    print("Encryption failed:", str(e))
```

{% endcode %}
{% endtab %}

{% tab title="Ruby" %}
{% code overflow="wrap" lineNumbers="true" %}

```ruby
require 'openssl'
require 'base64'

def encrypt_json(json_string, public_key_path)
  public_key = OpenSSL::PKey::RSA.new(File.read(public_key_path))
  encrypted = public_key.public_encrypt(json_string)
  Base64.strict_encode64(encrypted)
end

# Example usage
json_data = '{"first_name":"John","last_name":"Doe","card_no":"4622000000005678","exp_month":"06","exp_year":"22","cvv":"123","billing_address":"Second Street","billing_city":"San Francisco","billing_zip":"94105","billing_state":"CA","billing_country":"US"}'
public_key_path = 'path-to-file/elemi.public.key.pem'

begin
  encrypted_data = encrypt_json(json_data, public_key_path)
  puts "Encrypted data: #{encrypted_data}"
rescue => e
  puts "Encryption failed: #{e.message}"
end
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Step 3: Form the payment request payload

The table below describes the request parameters that are used for the collection/charge request. The encrypted string from Step 2 will be sent as part of the request as described in the table below.

<table><thead><tr><th width="200">Parameter</th><th width="93">Type</th><th width="98">Required</th><th>Description</th></tr></thead><tbody><tr><td>merchant_reference</td><td>String</td><td>true</td><td>The unique reference for this request. It must be at least 8 characters long. Alternatively, the value <strong>auto</strong> can be passed, and a unique reference will be created for you by the API</td></tr><tr><td>transaction_method</td><td>String</td><td>true</td><td>The transaction method to be used. This will be <em><strong>CARD</strong></em> for this request</td></tr><tr><td>currency</td><td>String</td><td>true</td><td>The 3-character ISO currency code for the request currency</td></tr><tr><td>amount</td><td>Number</td><td>true</td><td>The amount being requested</td></tr><tr><td>provider_code</td><td>String</td><td>true</td><td>The provider code as obtained from the payment options <a href="../../utility-functions/payment-options">list</a></td></tr><tr><td>customer_name</td><td>String</td><td>false</td><td>The name of the customer</td></tr><tr><td>customer_email</td><td>String</td><td>false</td><td>The email of the customer</td></tr><tr><td><mark style="color:orange;">card_cipher</mark></td><td><mark style="color:orange;">String</mark></td><td><mark style="color:orange;">true</mark></td><td><mark style="color:orange;">The resultant string after encryption above</mark></td></tr><tr><td>description</td><td>String</td><td>true</td><td>The description/narration for the transaction. Between 10-30 characters</td></tr><tr><td>charge_customer</td><td>Boolean</td><td>false</td><td>Whether or not the customer should bear the charge for the transaction. By default, this is <strong>false</strong> to mean that the merchant bears the charge</td></tr><tr><td>allow_final_status_change</td><td>Boolean</td><td>false</td><td>Whether or not the final transaction status can be altered as described <a href="../../getting-started#transaction-final-status-change">here</a>. By default, this is <strong>true</strong> to mean Elemi will alter the final transaction status under the circumstances described.</td></tr><tr><td>redirect_url</td><td>String</td><td>true</td><td>The HTTPs redirect URL to which the API will redirect when the payment is successful/failed</td></tr></tbody></table>

After collecting the necessary card payment information from your customer, prepare your request payload as demonstrated below.

```json
{
  "merchant_reference": "auto",
  "transaction_method": "CARD",
  "currency": "NGN",
  "amount": 1000,
  "provider_code": "local_ngn",
  "customer_email": "johndoe@gmail.com",
  "customer_name": "JOHN DOE",
  "description": "Test Collection",
  "charge_customer": false,
  "allow_final_status_change": true,
  "card_cipher": "*******",
  "redirect_url": "https://your-redirect-url"
}
```

<mark style="color:green;">`POST`</mark> `https://sandbox.elemitech.com/collections/initialize`

The request is sent as a JSON body as demonstrated by the sample request below. Sample responses (acknowledgement and failure) are also shared.

```powershell
curl -X POST "https://sandbox.elemitech.com/collections/initialize" \
   -H 'Content-Type: application/json' \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -d '{
        "merchant_reference": "auto",
        "transaction_method": "CARD",
        "currency": "NGN",
        "amount": 1000,
        "provider_code": "local_ngn",
        "customer_email": "johndoe@gmail.com",
        "customer_name": "JOHN DOE",
        "description": "Test Collection",
        "charge_customer": false,
        "allow_final_status_change": true,
        "card_cipher": "*******",
        "redirect_url": "https://your-redirect-url"
    }'
```

{% tabs %}
{% tab title="202: Accepted - Request acknowledged for processing" %}

```json
{
  "code": 202,
  "status": "accepted",
  "message": "Request Accepted",
  "data": {
    "internal_reference": "ELEMIZAERK2SBE6WJAG",
    "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
    "payment_url": "https://sandboxpay.elemitech.com/pay/collection/ELEMIZAERK2SBE6WJAG"
  }
}
```

{% endtab %}

{% tab title="400: Bad Request - Request not formed as expected" %}

```json
{
  "code": 400,
  "status": "error",
  "message": "Invalid provider code (local_ugx)",
  "data": {}
}
```

{% endtab %}
{% endtabs %}

## Step 4: Redirect to Payment URL

Notice the `payment_url` parameter in the callback body. The URL should be loaded in the browser so that the customer can proceed with the transaction. When the page loads, the customer will be guided through the payment process and on success/failure, the customer will be redirected to the `redirect_url` sent by the merchant in the request. Additionally, a callback/webhook will be sent to the configured collection callback URL.

## Step 5: Handle the redirect and/or callback

### Redirect

On success/failure of the transaction, the customer will be redirected to the URL that was passed in the `redirect_url` request parameter. The sample requests below demonstrated the success and failure scenarios. You can copy the URL and paste it on [this](https://semalt.tools/en/url-parser) online resource in order to view the query parameters therein.

{% tabs %}
{% tab title="Redirect on success" %}
{% code overflow="wrap" %}

```
https://webhook.site/cc0393dc-fe49-401b-ba56-da9e099a3648?id=33911&event=transaction.completed&merchant_reference=MCTREFZQTUDRC38TLYUV&internal_reference=ELEMIHYJQ3ZUJMPGFG5&transaction_type=COLLECTION&transaction_status=COMPLETED&status_message=Transaction%20Completed%20Successfully&rsa_signature=Y23aNOaOs6NfOnrhpZrWGAOI4bMR5kNobFnrOBlqF%2FY7f03aBpHZx2rcmU3q2P9zS2w61xs3FsT%2FipRI9F21dNJ0UxDQci7yQ%2B7CZbk6kJQbfX9Ht7OIhH2%2F7cv%2FNUXDmXzZQYST3Qwe9QIhea1SIImWRzR8HKAeEeoVRPLDqxohLX4A0S91hjrZVO%2BjohZJLazIeXFrOaJyIu3xPQ7bzJi%2BfVhw6ry3R7NknIj5y752WpW0CqvxXKr3wPUIXy99tnN%2FCWLG65AmVL9GkBZj0j12%2B8ztBpknPBuGKWxndJB7zkkPTCmA%2BA1m21f3RENYTNH7nHet5zp5Iyq%2BkmtVVUmEcfvfAwAxs0sP0fYTFU8iSX6wvB6UEvk3AWgrprlb1XGd3sRgFgCb3HjzA7%2B8RBi%2Fw59qIXqK4LlbOtZw2tPc7MjLeXq%2BmqzrO8SRxyuvu6lX0HPhaHSXAGq%2BvljA1Kk4ZNLdSWxYCC2IgI7%2BPomrfWW1I6pNIF%2BudPTIYTPN6EEdMUwFBltyAoh%2ByHl6K%2F%2Fcz4FfE6OgZwJkbQ8661sHqiB2ND%2FhSQh%2B0Qre1tQTLx7BGQTSGdD34CLn%2FQICIlqNCvKbMF4ZO208xYaBct01T6Xa23zykkrmlTviEMJgSIo3zj2KpN%2BTF1X5jZOXLszBL20o1SeQlY9%2BhRzshzY%3D&hmac_signature=t%3D1721639784089%2Cs%3D1ec4e0f7749397faea80958bca9ab2c6d8e7448c3ee02f958881a392ce299128
```

{% endcode %}
{% endtab %}

{% tab title="Redirect on failure/cancellation" %}
{% code overflow="wrap" %}

```
https://mysite.com?id=34639&event=transaction.failed&merchant_reference=MCTREF8DQRHYTNCQTACH&internal_reference=ELEMI8A54ZUJFGSMENE&transaction_type=COLLECTION&transaction_status=FAILED&status_message=Balance%20Insufficient%20for%20the%20transaction&rsa_signature=yvODC%2BViCiZmlEEEbQX1B%2FfQ0kNzrYqRN0c3Y8MjzZ%2FiMNVzf%2BUS36Ec8P1a0rOKgyc0l7KVbzEJwMZu0nasAHFuCeSOebs5NOHvSNJkAbQE2nZXgxvo1uQtJOMnbYZZKIvswemRTRGEyAKl7bRGc2AXzYRzJBVOYBVDOM0o36FUpfNNFrucVjvrmJJD8YtRtIXk3RjBzXkhV%2B3KAjWx0KU8bh424OyZUwsRTaN0MeuQMFY%2FkeuFvNKPzxKtN%2BaJqcx4aDZes6thevgfCljgne0ibC9Zy1eDCYP8A1T%2BNTS%2FHUZBuy1Y%2BzAmQ%2BMjnoiI%2FVk2I%2BuoRkHU01%2BMV%2Bo4CFGAoheoQ9N%2B7KFN0f4oAdT3G%2BJZtcwitZ0Q3Jo%2FW3aYNyvrPQHt88hjOtVHKhSNEwUJ8QNfy42WiZJOix4p5I5oyePdYGW5ia09MuaY6BkV8o9mdotcWGpn6O1PsxnxfduI6gbGLWtcxjrat75LeMhGjPLYcJA9CQECK7NvBFHOBUA%2F9rR5gReBVEkdVf5N8q551DvJfR4nENxZukaVMzjhq2IIndOHzRXTyXIxVKGhWAAcGXrVKcXvUvh8Np%2FlJg2bz4xWarvbk%2FFSrXYJj5IwNrOB02N5IzoVCGSyE%2BGWjofyUhgrq%2Ftlralnyp0GpXong2Sl8xB0%2Foucm4xtC60%3D&hmac_signature=t%3D1721967214552%2Cs%3D0a3505923c0541166952726c9c32c9dfdec41d51e4b1016093a6f53f0b9a76e9
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Callback/Webhook

Every merchant account is expected to have configured a callback/webhook URL for collections. For all collections that transition to the final state (COMPLETED, FAILED or CANCELLED), a JSON POST request will be made to the callback URL. Sample callback payloads (request bodies) are shared below. Be sure to check out [Handling Notifications](https://docs.elemitech.com/utility-functions/handling-notifications-callbacks) to see how you should verify the signature(s) in the request headers and how to respond.

{% tabs %}
{% tab title="Successful Card Collection" %}

```json
{
  "event": "transaction.completed",
  "payload": {
    "id": 20760,
    "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
    "internal_reference": "ELEMIRMGRXNNYBWATKJ",
    "transaction_type": "COLLECTION",
    "request_currency": "NGN",
    "transaction_amount": 1000,
    "transaction_currency": "NGN",
    "transaction_charge": 100,
    "transaction_account": "462200XXXXXX5678",
    "charge_customer": false,
    "total_credit": 900,
    "provider_code": "local_ngn",
    "request_amount": 1000,
    "customer_name": "JOHN DOE",
    "institution_name": "Local Verve Card",
    "transaction_status": "COMPLETED",
    "status_message": "Transaction Completed Successfully"
  }
}
```

{% endtab %}

{% tab title="Failed Card Payment" %}

```json
{
  "event": "transaction.failed",
  "payload": {
    "id": 26609,
    "merchant_reference": "MCTREFNRFRTQA6SCWT5X",
    "internal_reference": "ELEMIWYUF8CR3ZRCGYU",
    "transaction_type": "COLLECTION",
    "request_currency": "NGN",
    "transaction_amount": 40000,
    "transaction_currency": "NGN",
    "transaction_charge": 0,
    "transaction_account": "462200XXXXXX4420",
    "charge_customer": false,
    "total_credit": 0,
    "provider_code": "local_ngn",
    "request_amount": 40000,
    "customer_name": "JOHN DOE",
    "institution_name": "Local Verve Card",
    "transaction_status": "FAILED",
    "status_message": "Balance insufficient for the transaction"
  }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Either of the two (redirect/callback) can be used to confirm the final status of the transaction. We recommend that in either situation, the redirect/callback request is verified (by verifying the signatures)
{% endhint %}
