Create Payment Token (Non-3DS)

The Create Payment Token (Non-3DS) flow allows you to tokenize payment details without 3D Secure (3DS) authentication. It is ideal for supporting frictionless one-click payments in scenarios where strong customer authentication is not required.

This guide provides step-by-step instructions and relevant code examples to help you integrate this payment flow using the CCBill Advanced Widget.

Requirements

The Payment Flow

To set up the CCBill Advanced Widget and create payment tokens without 3DS authentication:

  1. Include the Widget on your page.
  2. Provide payment details.
  3. Generate the frontend OAuth bearer token.
  4. Utilize the payment details and bearer token to create a payment token.
  5. Use the payment token and backend OAuth bearer token to process a transaction securely.

The following sequence diagram describes the flow for creating and charging payment tokens without 3DS authentication.

MCN Widget Integration Sequence No 3DS

The following steps explain how to set up the CCBill Advanced Widget and use it to create payment tokens.

1. Include the Widget in Your Page

To use the CCBill Advanced Widget, add the following preload link and script elements to your HTML page:

<link rel="preload" href="https://js.ccbill.com/v1.13.1/ccbill-advanced-widget.js" as="script"/>

<script type="text/javascript" src="https://js.ccbill.com/v1.13.1/ccbill-advanced-widget.js"></script>

The API version in this URI example is v1.13.1. Pay special attention to the version in the URI path, as the version number may be subject to change.

2. Collect Customer and Payment Data

The Advanced Widget automatically extracts values from form fields. Depending on your integration, the required fields can be provided in three ways:

  • (Recommended) Use data-ccbill HTML data attributes.
  • Use default _ccbillId_FieldName ID attributes.
  • Use custom ID attributes (requires additional mapping).

Using data-ccbill data attributes is non-intrusive and provides more flexibility. It allows you to map form inputs directly without modifying existing id attributes.

<form id="payment-form"> 
    <input data-ccbill="firstName" />
    <input data-ccbill="lastName" /> 
    <input data-ccbill="postalCode" />
    <input data-ccbill="country" /> 
    <input data-ccbill="email" /> 
    <input data-ccbill="cardNumber" /> 
    <input data-ccbill="expYear" /> 
    <input data-ccbill="expMonth" /> 
    <input data-ccbill="nameOnCard" /> 
    <input data-ccbill="cvv2" /> 
</form>

If you cannot modify your HTML to include data-ccbill attributes, use the default _ccbillId_ attributes instead. This method is less flexible because the field names must match CCBill's predefined format.

<form id="payment-form">
    <input id="_ccbillId_firstName" />
    <input id="_ccbillId_lastName" />
    <input id="_ccbillId_postalCode" />
    <input id="_ccbillId_country" />
    <input id="_ccbillId_email" />
    <input id="_ccbillId_cardNumber" />
    <input id="_ccbillId_expYear" />
    <input id="_ccbillId_expMonth" />
    <input id="_ccbillId_nameOnCard" />
    <input id="_ccbillId_cvv2" />
</form>

If you prefer custom IDs, map them to corresponding input fields using the customIds parameter in the Widget constructor.

<form id="payment-form">
    <input id="custom_firstName_id" />
    <input id="custom_lastName_id" />
    <input id="custom_postalCode_id" />
    <input id="custom_country_id" /> 
    <input id="custom_email_id" /> 
    <input id="custom_cardNumber_id" /> 
    <input id="custom_expYear_id" /> 
    <input id="custom_expMonth_id" /> 
    <input id="custom_nameOnCard_id" /> 
    <input id="custom_cvv2_id" /> 
</form>
<script>
// map custom ids to relevant fields
const customIds = {
    firstName: "custom_firstName_id",
    lastName: "custom_lastName_id",
    postalCode: "custom_postalCode_id",
    country: "custom_country_id",
    email: "custom_email_id",
    cardNumber: "custom_cardNumber_id",
    expYear: "custom_expYear_id", 
    expMonth: "custom_expMonth_id", 
    nameOnCard: "custom_nameOnCard_id",
    cvv2: "custom_cvv2_id"
};

// pass custom ids to Widget constructor
const widget = new ccbill.CCBillAdvancedWidget("application_id", customIds);

// call the desired Widget method

</script>

All Form Fields

NAMEREQUIREDDESCRIPTION
firstNameYesCustomer's first name.
lastNameYesCustomer's last name.
address1NoCustomer's billing address. If provided, it should be between 1 and 50 characters long.
address2NoCustomer's address (line 2). If provided, it should be between 1 and 50 characters long.
postalCodeYesCustomer's billing zip code. It should be a valid zip code between 1 and 16 characters long.
cityNoCustomer's billing city. If provided, it should be between 1 and 50 characters long.
stateNoCustomer's billing state. If provided, it should be between 1 and 3 characters long.
countryYesCustomer's billing country. Should be a two-letter country code as defined in ISO 3166-1.
emailYesCustomer's email. Should be a well-formed email address, max 254 characters long.
phoneNumberNoCustomer's phone number. If provided, it should be a well-formed phone number.
ipAddressNoCustomer's IP address.
browserHttpUserAgentNoBrowser User-Agent header value.
browserHttpAcceptNoBrowser Accept header value.
browserHttpAcceptEncodingNoBrowser Accept Encoding header value.
browserHttpAcceptLanguateNoBrowser Accept Language header value.
cardNumberYesA valid credit card number.
expMonthYesCredit card expiration month in mm format. Should be a value between 1 and 12.
expYearYesCredit card expiration year in yyyy format. Should be a value between current year and 2100.
cvv2YesCard security code. Should be a 3-4 digit value.
nameOnCardYesName displayed on the credit card. Should be between 2 and 45 characters long.

3. Generate CCBill OAuth Bearer Token

The CCBill RESTful API uses OAuth-based authentication and authorization. Use the frontend credentials (Merchant Application ID and Secret Key that are Base64 encoded) you received from Merchant Support to generate a frontend bearer token.

You must include this token in the Authorization header of API requests when creating payment tokens. Use the following example and adjust the necessary parameters to obtain a frontend bearer token:

curl -X POST 'https://api.ccbill.com/ccbill-auth/oauth/token' \
  -u '[Frontend_Merchant_Application_ID]:[Frontend_Secret_Key]' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials'
String getOAuthToken() {
    String credentials = Base64.getEncoder()
        .encodeToString(("[Frontend_Merchant_Application_ID]" + ":" + "[Frontend_Secret_Key]")
        .getBytes(StandardCharsets.UTF_8));
    String requestBody = "grant_type=client_credentials";

    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.ccbill.com/ccbill-auth/oauth/token"))
           .header("Authorization", "Basic " + credentials)
            .header("Content-Type", "application/x-www-form-urlencoded")
           .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))
            .build();

    try {
        HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
        return extractAccessToken(response.body());
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
        return null;
    }
}
<?php

function getOAuthToken() {
    $url = "https://api.ccbill.com/ccbill-auth/oauth/token";
    $merchantAppId = "[Frontend_Merchant_Application_ID]";
    $secretKey = "[Frontend_Secret_Key]";
    $data = http_build_query(["grant_type" => "client_credentials"]);

    try {
        $httpRequest = new HttpRequest();
        $httpRequest->setUrl($url);
        $httpRequest->setMethod(HTTP_METH_POST);
        $httpRequest->setHeaders([
            "Authorization" => "Basic " . base64_encode("$merchantAppId:$secretKey"),
            "Content-Type" => "application/x-www-form-urlencoded"
        ]);
        $httpRequest->setBody($data);

        $httpClient = new HttpClient();
        $response = $httpClient->send($httpRequest);
        
        $responseData = json_decode($response->getBody(), true);
        return $responseData['access_token'] ?? die("Error: Invalid OAuth response.");
    } catch (HttpException $ex) {
        die("Error fetching OAuth token: " . $ex->getMessage());
    }
}

?>

Important Notes

  • Never expose API credentials on the front end. Always store your Merchant Application ID and Secret Key securely in server-side environment variables.
  • This request must be sent from your backend. OAuth token requests cannot be made from a web browser for security reasons.
  • OAuth access tokens are temporary. Each token remains valid for a single request or until it expires.
  • Reduce API token attack surface. Execute calls to create an Oauth token and a payment token in quick succession to minimize the risk of the access token being exposed to attackers.
  • Use CSRF tokens for your front-end payment forms. Protect your front-end forms with CSRF tokens to prevent unauthorized form submissions.

4. Generate Payment Token

The createPaymentToken() function is the primary method for generating a Payment Token using the CCBill Advanced Widget. The function is called to initiate a payment flow after collecting and validating customer data and generating a frontend bearer token using your frontend credentials.

Code Example

async function createPaymentToken() {
    const widget = new ccbill.CCBillAdvancedWidget('your-application-id');
    const paymentTokenResponse = await widget.createPaymentToken(
        "[Frondent_Access_Token]",
        [Your_Client_Account_Number],
        [Your_Client_Subaccount_Number]
    );
    return await paymentTokenResponse.json();
}

Response Handling

The createPaymentToken() function automatically validates all field values before generating a token:

  • A successful response returns a payment token ID, which is required to continue the payment flow.
  • If validation fails, the client page must display an appropriate error message and prompt them to resolve the invalid input before resubmitting the request.

The response may also contain additional processing errors (e.g., expired tokens or missing account details). Visit the RESTful API Error Codes page for a comprehensive list of error codes.

5. Charge Payment Token

To finalize a payment, send a request to charge the Payment Token through the backend. Generate a new backend bearer token using your Base64 encoded backend credentials. Then, pass the backend bearer token and payment token ID to the API endpoint and charge the customer's credit card.

Code Examples

curl -X POST 'https://api.ccbill.com/transactions/payment-tokens/[payment_token_id]' \
  -H 'Accept: application/vnd.mcn.transaction-service.api.v.2+json' \
  -H 'Authorization: Bearer [Backend_Access_Token]' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -d '{
    "clientAccnum": [Your_Client_Account_Number],
    "clientSubacc": [Your_Client_Subaccount_Number],
    "initialPrice": 9.99,
    "initialPeriod": 30,
    "currencyCode": 840
  }'
public ResponseEntity<String> processPurchase() {
    String requestBody = """
        {
            "clientAccnum": [Your_Client_Account_Number],
            "clientSubacc": [Your_Client_Subaccount_Number],
            "initialPrice": 9.99,
            "initialPeriod": 30,
            "currencyCode": 840
        }""";

    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.ccbill.com/transactions/payment-tokens/[payment_token_id]"))
            .header("Accept", "application/vnd.mcn.transaction-service.api.v.2+json")
            .header("Authorization", "Bearer [Backend_Access_Token]")
            .header("Cache-Control", "no-cache")
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))
            .build();

    try {
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        return ResponseEntity.ok(response.body());
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
        return ResponseEntity.status(500).body("Error processing payment");
    }
}
<?php

function processPurchase() {
    $url = "https://api.ccbill.com/transactions/payment-tokens/[payment_token_id]";
    $paymentData = json_encode([
        "clientAccnum" => [Your_Client_Account_Number],
        "clientSubacc" => [Your_Client_Subaccount_Number],
        "initialPrice" => 9.99,
        "initialPeriod" => 30,
        "currencyCode" => 840,
    ]);

    try {
        $httpRequest = new HttpRequest();
        $httpRequest->setUrl($url);
        $httpRequest->setMethod(HTTP_METH_POST);
        $httpRequest->setHeaders([
            "Accept" => "application/vnd.mcn.transaction-service.api.v.2+json",
            "Authorization" => "Bearer [Backend_Access_Token]",
            "Cache-Control" => "no-cache",
            "Content-Type" => "application/json"
        ]);
        $httpRequest->setBody($paymentData);

        $httpClient = new HttpClient();
        $response = $httpClient->send($httpRequest);
        
        return $response->getBody();
    } catch (HttpException $ex) {
        die("Error charging payment token: " . $ex->getMessage();
    }
}

?>

Response Handling

The endpoint validates the Payment Token and processes the transaction:

  • A successful charge returns a direct API response with transaction details (such as transaction ID, status, amount, and timestamps).
  • If the charge fails, the response will include an error code and message explaining the reason for the failure. Use backend logic to handle the error and return a user-friendly message or trigger corrective actions.

Full Integration Example (Non-3DS)

To simplify the integration process, we have provided a complete working example that demonstrates how to use the CCBill Advanced Widget on the front and back end. The example includes:

  • A JavaScript frontend that initializes the widget, generates a payment token and submits it to your backend.
  • A Java-based backend that handles the authorization and performs a server-side charge request.

Be sure to replace the placeholder values within the examples with your own application credentials and parameters.

async function fetchOAuthToken() {
    return (await (await fetch('https://your-website.com/api/auth-token')).json()).token;
}

async function createPaymentToken(widget, authToken, clientAccnum, clientSubacc) {
    const paymentTokenResponse = await widget.createPaymentToken(
        authToken,
        clientAccnum,
        clientSubacc
    );
    return await paymentTokenResponse.json();
}

async function chargePaymentToken(paymentToken) {
    return await (await (fetch('https://your-website.com/api/purchase', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            paymentToken,
            amount: 9.99,
            currency: 840
        })
    }))).json();
}

async function purchase() {
    const widget = new ccbill.CCBillAdvancedWidget('your-application-id');
    try {
        // create the payment token to be submitted to the merchant owned endpoint
        const paymentToken = await createPaymentToken(widget, 
            await fetchOAuthToken(), 
            [Your_Client_Account_Number], 
            [Your_Client_Subaccount_Number]);

        // submit the payment token to be charged to an endpoint implementing backend charging of the token
        const chargeCallResponse = await chargePaymentToken(paymentToken);
        return Promise.resolve(chargeCallResponse);
    } catch (error) {
        // react to any errors that may occur during the process
        return Promise.reject({error});
    }
}

let result = await purchase();
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

@RestController
@RequestMapping("/api")
public class ApiController {

    private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .build();

    @PostMapping("/auth-token")
    public ResponseEntity<AuthTokenResponse> getAuthToken() {
        String accessToken = fetchOAuthToken("[Frontend_Merchant_Application_ID]", "[Frontend_Secret_Key]");
        if (accessToken != null) {
            return ResponseEntity.ok(new AuthTokenResponse(accessToken));
        } else {
            return ResponseEntity.status(500).body(new AuthTokenResponse(""));
        }
    }

    @PostMapping("/purchase")
    public ResponseEntity<String> processPurchase(@RequestBody PurchaseRequest purchaseRequest) {
        String requestBody = String.format(
                """
                {
                  "clientAccnum": %d,
                  "clientSubacc": %d,
                  "initialPrice": %.2f,
                  "initialPeriod": 30,
                  "currencyCode": %d
                }
                """,
                purchaseRequest.paymentToken().clientAccnum(),
                purchaseRequest.paymentToken().clientSubacc(),
                purchaseRequest.amount(),
                purchaseRequest.currency()
        );

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.ccbill.com/transactions/payment-tokens/" 
                    + purchaseRequest.paymentToken().paymentTokenId()))
                .header("Accept", "application/vnd.mcn.transaction-service.api.v.2+json")
                .header("Authorization", "Bearer " 
                    + fetchOAuthToken("[Backend_Merchant_Application_ID]", "[Backend_Secret_Key]"))
                .header("Cache-Control", "no-cache")
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))
                .build();

        try {
            HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
            return ResponseEntity.ok(response.body());
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
            return ResponseEntity.status(500).body("Error processing payment");
        }
    }

    private static String fetchOAuthToken(String merchantAppId, String sercretKey) {
        String credentials = Base64.getEncoder()
            .encodeToString((merchantAppId + ":" + sercretKey).getBytes(StandardCharsets.UTF_8));
        String requestBody = "grant_type=client_credentials";

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://api.ccbill.com/ccbill-auth/oauth/token"))
                .header("Authorization", "Basic " + credentials)
                .header("Content-Type", "application/x-www-form-urlencoded")
                .POST(HttpRequest.BodyPublishers.ofString(requestBody, StandardCharsets.UTF_8))
                .build();

        try {
            HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
            return extractAccessToken(response.body());
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
            return null;
        }
    }

    private static String extractAccessToken(String responseBody) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode jsonNode = objectMapper.readTree(responseBody);
            return jsonNode.has("access_token") ? jsonNode.get("access_token").asText() : null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    private record AuthTokenResponse(String token) {}
    private record PurchaseRequest(double amount, String currency, PaymentToken paymentToken) {}
    private record PaymentToken(String paymentTokenId, Integer clientAccnum, Integer clientSubacc) {}
}

Additional Documentation

Error Codes

CCBill Advanced Widget API Reference

CCBill RESTful API Resources