For the complete documentation index, see llms.txt. This page is also available as Markdown.

Akinon Subscription System

This document explains the requirements for the Extension and its integration with Commerce for setting up the subscription system on Akinon Commerce using the Checkout.com payment method.

This subscription system works with the Checkout.com payment method. Checkout.com supports card tokenization and recurring payment capabilities, which are required for the subscription system.

Architecture

The system consists of three main components:

  • Commerce (Shop): Order management, checkout flow, and IPN processing.

  • Extension: Subscription management, recurring payment triggering, and Checkout.com API calls.

  • Checkout.com: Card tokenization, payment processing, and recurring payment support.

The Customer (Frontend) interacts with Commerce (Shop). Commerce integrates with the Extension for subscription management and with Checkout.com (CKO) for payment processing.

Subscription Flow

1. Initial Order (Customer-Initiated via Checkout.com)

The customer is redirected to the Checkout.com payment page through the Commerce checkout flow.

The customer enters their card details and selects "Save Card" (store_for_future_use: true).

Checkout.com returns the source_id and payment_id.

Commerce creates the order. If Order.extra_field.is_subscription is set to true, the Extension registers the order as a subscription.

2. Subscription Detection (Extension)

The Extension listens for newly created orders and validates the following:

  • Order.extra_field.is_subscription == true

  • OrderItem.product.attributes.subscription_period is defined

3. Subscription Creation

If the order is identified as a subscription, the Extension creates a subscription record with the following information:

  • user_id

  • product_id

  • source_id

  • payment_id

  • period (retrieved from the product attribute)

  • next_payment_date (calculated based on the subscription period)

  1. Recurring Payment (Automatically Triggered by the Extension)

When the next_payment_date is reached, the Extension automatically performs the following actions:

  • Initiates a checkout process in Commerce on behalf of the customer.

  • Creates a basket and selects the shipping address, shipping method, and payment method.

  • Sends a RECURRING payment request to Checkout.com using the stored source_id.

  • Sends an IPN to Commerce containing the payment result.

  • Commerce creates a new order or updates the existing order accordingly.

5. Failed Payment (Retry Mechanism)

If a recurring payment fails, the Extension performs the following actions:

  • Receives the failed payment response from Checkout.com.

  • Sends a DECLINED IPN to Commerce for transaction logging.

  • Retries the payment according to the configured retry policy.

  • Notifies the customer if the maximum retry limit is exceeded.

Responsibility Matrix

Responsibility
Commerce
Extension

Extension Gateway (POS) configuration

PaymentOption configuration

Checkout API

IPN endpoint

Transaction logging

✓ (automatic)

Order creation/update

✓ (automatic)

Subscription management

Checkout initiation on behalf of the customer

Stored card management

Recurring payment processing

IPN submission

Retry mechanism

Customer notifications

Frontend integration

Commerce Configuration

1. Create a POS Configuration

A POS record must be created in the Commerce Admin Panel for the Extension.

Field
Value
Description

Name

Subscription Extension

Display name

Slug

subscription-extension

Unique identifier

Gateway

extension

Extension gateway type

Resource URL

https://extension.example.com/api

Extension API endpoint

Config (JSON)

Configuration Parameters

Field
Type
Required
Description

auth.username

string

Username for IPN Basic Authentication

auth.password

string

Password for IPN Basic Authentication

hash_key

string

Secret key used for request signing

payment_type

string

Payment type: SALE (capture immediately) or AUTH (authorization only)

url

string

Extension API endpoint

The auth.username and auth.password values are the Basic Authentication credentials that the Extension uses when sending IPN requests to Commerce.

Sharing Authentication Credentials

These credentials are defined by the Commerce team and shared with the Extension team as follows:

  1. The Commerce team defines the auth.username and auth.password values when creating the POS configuration.

  2. These credentials are securely shared with the Extension team through a secure channel (e.g., encrypted email or a secret management solution).

  3. The Extension uses these credentials in the Basic Authentication header when sending IPN requests to Commerce.

Commerce (POS Configuration)
Extension (IPN Request Header)

auth.username: "ext_user"

Authorization: Basic <base64("ext_user:ext_pass123")>

auth.password: "ext_pass123"

Uses the credentials defined in the Commerce POS configuration.

Extension Developers: Request these credentials from the Commerce integration team. Separate credentials will be provided for the Staging and Production environments.

2. PaymentOption Configuration

A PaymentOption must be created in the Commerce Admin Panel for subscription payments.

Field
Value
Description

Name

Subscription Payment

Display name

Slug

subscription-payment

Unique identifier

Payment Type

redirection

Redirect-based payment method

Is Active

true

Indicates whether the payment option is enabled

Config (JSON):

1. Initial Order (Customer-Initiated via Checkout.com)

The initial order is the customer's first purchase completed through the standard Commerce checkout flow using the Checkout.com payment method.

Flow

  1. The customer adds a subscription product to the shopping cart.

  2. The customer completes the checkout steps, including shipping address and shipping method.

  3. The customer selects Checkout.com as the payment method.

  4. The customer is redirected to the Checkout.com payment page.

  5. The customer enters their card details and selects the "Save Card" option.

  6. If the payment is successful, Commerce creates the order with the status set to approved.

Order Data Structure

The following information is stored when the initial subscription order is created:

Order.extra_field:

OrderItem.product.attributes (Defined on the Product):

PreOrder.data Structure (Internal)

During the checkout process, Commerce stores the following information in the PreOrder.data field:

Field
Type
Description

is_subscription

bool

Indicates whether the order is a subscription order.

is_renewal

bool

Indicates whether the order is a renewal (recurring payment) order.

checkout_source_id

string

Reference to the stored payment source (card).

checkout_payment_id

string

Reference to the initial payment.

redirection_started

bool

Indicates whether the payment redirection process has started.

redirect_url

string

Extension redirect URL.

Orders with is_renewal: true are recurring payment orders created by the Extension. For the initial subscription order, is_renewal is always set to false.

Checkout.com Initial Payment Payload

For the initial subscription order, Commerce sends the following payload to Checkout.com:

The store_for_future_use: true parameter instructs Checkout.com to securely store the customer's card, allowing it to be used for future recurring payments.

Checkout.com Response

If the payment is successful, Checkout.com returns the following information:

Field
Description

source.id

Reference to the stored payment source (card). This value is used for future recurring payments.

id

Identifier of the initial payment. This value is stored and later used as the previous_payment_id for recurring payment requests.

Extension Responsibilities

The Extension listens for newly created orders (via webhook or polling):

  1. Perform a subscription check:

  • Is Order.extra_field.is_subscription == true?

  • Does the product attribute contain subscription_period?

  1. Create a subscription record:

  • user_id

  • order_id

  • product_id

  • source_id (Order.extra_field.checkout_source_id)

  • first_payment_id (Order.extra_field.checkout_payment_id)

  • period (from the product attribute)

  • next_payment_date (current date/time + subscription period)

2. Recurring Payment (Triggered by the Extension)

When the subscription renewal date (next_payment_date) is reached, the Extension automatically creates an order on behalf of the customer and initiates a recurring payment through Checkout.com.

Step 2.1: Generate a Customer Login Token

The Extension generates a one-time token to log in on behalf of the customer using administrator privileges:

Method: POST

Path: /api/v1/remote/1/passwordless-login/

Authorization: Token <admin_token>

Content-Type: application/json

The secret_key value must be configured in Commerce settings as PASSWORDLESS_LOGIN_SECRET_KEY.

The customer session can be obtained by sending a GET request to this URL.

Alternatively, the passwordless-login-with-token endpoint can be used:

Method: POST

Path: /users/passwordless-login-with-token/

Content-Type: application/json

Response:

Step 2.2: Create a Basket and Add the Product

The Extension creates a basket on behalf of the customer.

Method: POST

Path: /baskets/basket/

Authorization: Token <customer_token>

Content-Type: application/json

Field
Type
Required
Description

product

integer

Product ID

quantity

integer

Quantity

attributes.is_renewal

bool

Indicates whether this is a renewal order.

attributes.original_order_id

integer

ID of the original subscription order.

The is_renewal: true and original_order_id values indicate that this order is a subscription renewal. These values are propagated to Order.extra_field.

Step 2.3: Complete the Checkout Flow

The checkout flow is managed through a single endpoint. At each step, the current stage of the checkout process is specified using the page parameter.

Address Selection

Method: POST

Path: /orders/checkout/

Authorization: Token <customer_token>

X-Requested-With: XMLHttpRequest

Content-Type: application/json

Shipping Method Selection

Method: POST

Path: /orders/checkout/

Authorization: Token <customer_token>

X-Requested-With: XMLHttpRequest

Content-Type: application/json

Step 2.4: Select the Extension Payment Option

Method: POST

Path: /orders/checkout/

Authorization: Token <customer_token>

X-Requested-With: XMLHttpRequest

Content-Type: application/json

Step 2.5: Initiate the Redirection Payment

Method: POST

Path: /orders/checkout/

Authorization: Token <customer_token>

X-Requested-With: XMLHttpRequest

Content-Type: application/json

Response:

For Automation: The Extension backend should issue an HTTP GET request to the redirect_url and follow the resulting 302 redirect automatically.

PreOrder and Cache Mechanism

When the checkout flow is completed, Commerce performs the following actions:

  • Creates a PreOrder (an Order has not yet been created).

  • Stores the PreOrder in the cache using the basket hash.

  • Creates a Redirect Transaction record.

Transaction ID Formats

Redirect Transaction: REDIRECT_{pre_order_number}

Webhook Transaction: WEBHOOK_{pre_order_number}_{unique_id

Cache Mechanism

Cache Key: REDIRECTION_REDIRECT_{pre_order_number}

Cache Value: basket_hash (ex: a1b2c3d4e5f6...)

IPN Processing Flow

When an IPN is received, Commerce performs the following steps:

  • Finds the Redirect Transaction using the order_id (pre_order_number).

  • Generates the cache key from the transaction ID: REDIRECTION_REDIRECT_{order_id}

  • Retrieves the basket_hash from the cache.

  • Uses the basket_hash to retrieve the PreOrder from the OrderStore.

  • Creates the actual Order from the retrieved PreOrder.

Cache Expiration: By default, the PreOrder cache is valid for 30 minutes. The IPN must be sent within this time window.

Step 2.6: Charge Recurring Payment via Checkout.com

The Extension initiates a recurring payment using the stored card (source_id) via the Checkout.com API.

Method: POST

Base URL: https://api.checkout.com/payments

Authorization: Bearer <checkout_secret_key>

Content-Type: application/json

Field
Description

payment_type

"Recurring" – Indicates a recurring payment

merchant_initiated

true – Payment initiated without customer interaction

source.type

"id" – Indicates stored card reference

source.id

Stored source_id from the initial payment

source.stored

true – Confirms stored card usage

previous_payment_id

payment_id from the initial transaction

reference

Commerce PreOrder number

Checkout.com Response

Successful

Unsuccessful Response

Step 2.7: Send IPN to Commerce

Based on the payment result received from Checkout.com, an IPN is sent to Commerce.

Successful Payment

Method: POST

Path: /orders/hooks/payment

Authorization: Basic <base64(username:password)>

Content-Type: application/json

Unsuccessful Payment

Method: POST

Path: /orders/hooks/payment

Authorization: Basic <base64(username:password)>

Content-Type: application/json

3. IPN (Instant Payment Notification) Format

Endpoint

Method: POST Path: /orders/hooks/payment

Authentication

Basic Authentication is used. The credentials are taken from the POS configuration shared by Commerce:

Example (Python)

Please refer to the “Auth Credentials Sharing” section for details on where to obtain these values.

IPN Event Body Fields

Field
Type
Required
Description

order_id

string

PreOrder number (e.g. PRE-ORD-123456)

amount

string

Paid amount

installment_count

string

Number of installments (minimum "1")

interest_fee

string

Installment interest fee (use "0.00" if none)

status

string

Main status: RESOLVED or REJECTED

substatus

string

Detailed status: RESOLVED, BANK_ERROR, etc.

Successful Payment Example

Unsuccessful Payment Example

Pending (Async) Payment Example

If the payment has not been finalized yet:

Event Types

Event
Description

INITIALIZED

Payment has been initiated and is awaiting a result

COMPLETED

Payment has been successfully completed

DECLINED

Payment has been rejected

Status / Substatus Values

Status
Substatus
Description

RESOLVED

RESOLVED

Successful payment

PENDING

PAYMENT_WAITING

Waiting for payment result

REJECTED

RESOLVED

Payment rejected

REJECTED

BANK_ERROR

Bank error

Status and Substatus fields also accept numeric values:

  • Status: RESOLVED = 3, PENDING = 4, REJECTED = 2

  • Substatus: RESOLVED = 7, PAYMENT_WAITING = 8

IPN Response

When Commerce successfully receives the IPN:

  • HTTP Status: 200 OK

In case of an error:

  • HTTP Status: 406 Not Acceptable

  • Content-Type: application/json

Two Different Retry Scenarios

IPN (Instant Payment Notification) is an automatic server-to-server notification of a change in payment status. There are two retry scenarios, and they should not be confused:

406 “wait” signal (timing): When Commerce considers the request too early, it returns HTTP 406. This is not an error; it means “wait a bit and resend.” Wait 60 seconds and resend the same IPN. Repeat until HTTP 200 OK is received (maximum 5 attempts).

General delivery failure (retry / backoff): If the IPN receives any response other than 200 OK or no response at all, the request is retried using exponential backoff up to a maximum of 5 times.

In both cases, the goal is to receive an HTTP 200 response.

Possible Error Codes

HTTP Status
Code
Description

406

webhook_300

General webhook processing error

406

authentication_failed

Incorrect Basic Auth credentials

406

validation_error

Request body validation error

Webhook Timing Rule

Commerce rejects webhooks that arrive too quickly. A minimum time interval must pass after the first webhook before another one can be processed.

Additionally, after the RedirectionPaymentSelectedPage step, either the redirect URL must be called or a short waiting period is required for the system to transition into the payment_waiting state.

Default Waiting Time: 60 seconds

FIRST WEBHOOK (INITIALIZED)

  • Commerce processes and stores the request

  • Returns 200 OK

  • → Wait 60 seconds (Retry Logic)

SECOND WEBHOOK (COMPLETED)

  • Commerce processes the request

  • Creates the order

  • Returns 200 OK

Sending a webhook before 45 seconds:

  • HTTP 406 Not Acceptable

Automatic Processes on the Commerce Side

When an IPN is received, Commerce automatically:

1. Creates a Transaction Record

A transaction record is created for each IPN.

Transaction Types

Type

transaction_id Format

Description

redirect

REDIRECT_{order_number}

Created when checkout is initiated

webhook

WEBHOOK_{order_number}_{unique_id}

Created for each IPN

Transaction Fields

Field
Value

transaction_id

WEBHOOK_{order_number}_{unique_id}

transaction_type

webhook

is_succeeded

true/false based on event

raw_request

IPN body (JSON string)

raw_response

Commerce response body

async_order_status

RESOLVED, PENDING, REJECTED, etc.

amount

Payment amount

Transaction Query: Transaction history can be queried via the Admin API using the following endpoint:

/api/v1/remote/1/transactions/?order={order_pk}

2. Update Order Status

Event
Status
Order Status

COMPLETED

RESOLVED

approved

INITIALIZED

PENDING

payment_waiting

DECLINED

REJECTED

Order is not created / not updated

3. Create Order from PreOrder

On the first successful IPN, the real order is created using the PreOrder data:

  • Basket, address, and shipping information from the PreOrder are copied.

  • Subscription-related data from PreOrder.data is transferred to Order.extra_field.

  • Order items are created.

  • Stock is deducted.

  • The order status is set to approved.

4. Failed Payment and Retry Mechanism

Extension Responsibilities

1. Send IPN for each failed payment:

This ensures that a transaction log is created in Commerce and failed attempts are properly recorded.

2. Apply retry policy:

Example: 3 attempts, 24-hour intervals

  • Send a separate IPN for each attempt

  • If all attempts fail, suspend the subscription

3. Customer notification:

  • Email: “Your payment has failed. Please create a new order to continue your subscription.”

  • Push notification (optional)

  • SMS (optional)

Frontend Integration

To display failed payment warnings on the customer account page:

1. List orders:

Method: GET Path: /users/orders/?status=payment_waiting

Authorization: Token <customer_token>

2. Check transaction history:

Method: GET Path: /api/v1/remote/1/transactions/?order={order_pk}

Authorization: Token <admin_token>

3. Show warning if there is a failed transaction:

  • “Your payment has failed”

  • “Update your card details” button

Card Update Flow

The system does not support a direct “card update” feature. When a customer wants to change their payment method or the existing card becomes invalid:

  • The existing subscription is canceled (either by the customer or automatically by the system after failed payments).

  • The customer places a new subscription order using a new card.

  • The old subscription remains in cancelled status, and the new subscription starts with active status.

5. Subscription Lifecycle

Status Transitions

A subscription moves between the following states:

  • active → when payment time arrives, it transitions to processing

  • processing → if payment is successful, it returns to active

  • processing → if payment fails, it transitions to retry_pending

  • retry_pending → if retry is successful, it returns to active

  • retry_pending → if max retry limit is exceeded, it transitions to suspended

  • active / suspended → when the period ends, is canceled, or a new order is required, it transitions to completed or cancelled

Data That Must Be Stored in the Extension

Field
Type
Description

id

UUID

Subscription ID

user_id

Integer

Commerce customer ID

product_id

Integer

Subscription product ID

source_id

String

Stored card reference

first_payment_id

String

Initial payment reference

status

Enum

active, suspended, cancelled, completed

period

Enum

weekly, monthly, yearly

next_payment_date

DateTime

Next scheduled payment date

iteration_count

Integer

Number of completed renewals

max_iterations

Integer

Maximum renewals (null = unlimited)

retry_count

Integer

Current failed attempt count

max_retries

Integer

Maximum retry attempts

shipping_address_id

Integer

Shipping address ID

billing_address_id

Integer

Billing address ID

created_at

DateTime

Creation timestamp

updated_at

DateTime

Last update timestamp

Troubleshooting

Common Errors

1. HTTP 406 — “Can’t process webhook”

Cause: IPN is sent too early (Commerce has not finished the previous operation or timing restriction is active).

Solution: This is not an error, but a wait signal.

  • Wait 60 seconds (sleep)

  • Resend the request

  • Retry up to 3–5 times until successful


2. HTTP 406 — “Authentication failed”

Cause: Incorrect Basic Auth credentials.

Checks:

  • Is the Authorization: Basic <base64> header present?

  • Is the Base64 value encoded in username:password format?

  • Are auth.username and auth.password in the POS config correct?


3. HTTP 406 — “Order not found” or “Transaction not found”

Cause: Invalid order_id or checkout not completed yet.

Checks:

  • Does order_id match the pre_order_number from the checkout response?

  • Was the final checkout step (RedirectionPaymentSelectedPage) completed successfully?

  • Has the cache expiration (30 minutes) not passed?


4. HTTP 406 — “Validation error”

Cause: Missing or invalid fields in the IPN body.

Checks:

  • Are all required fields present? (order_id, amount, installment_count, interest_fee, status, substatus)

  • Are values sent as strings? (e.g. "99.99" is correct, 99.99 is invalid)


5. IPN successful but order not created

Cause: Not the first IPN or PreOrder not found in cache.

Checks:

  • Has a successful IPN already been sent for this order_id? (idempotency)

  • Has more than 30 minutes passed since checkout?

  • Is the PreOrder still available in cache?

Debug Tips

1. Transaction Logs:

2. Order Status:

3. IPN Request / Response:

Check the raw_request and raw_response fields in the Commerce transaction records.

Important Notes and Best Practices

1. Order ID / PreOrder Number

The order_id sent in the IPN must match the pre_order_number returned during the checkout process. This value is a temporary identifier assigned before the order is created.


2. Idempotency

Multiple successful IPNs must not be sent for the same order_id. Commerce creates the order on the first successful IPN, and subsequent ones are ignored.


3. Timing

After the first successful IPN, it may take a few seconds for Commerce to create the order.

Before checking the order status after sending a successful IPN, wait 5–10 seconds.


4. Test Environment

Before moving to production:

  • Test all scenarios in the staging environment

    • Successful payment flow

    • Failed payment flow

    • Retry mechanism

    • IPN timeout and retry behavior


5. Logging

Ensure all API calls and IPNs are logged:

  • Request / response body

  • HTTP status codes

  • Timestamp

  • Correlation ID (for tracing)


6. Security

  • Store POS authentication credentials securely

  • Use HTTPS

  • Apply IP whitelisting if possible


API Reference Documentation

Topic
URL

Basket API

Checkout - Address & Shipping

Checkout - Payment

Passwordless Login

Webhook / IPN

Orders API

Transactions API

Checkout.com API References

Topic
URL

Real Endpoints (Used in This Project)

Endpoint
Method
Description

/api/v1/remote/1/passwordless-login/

POST

Admin: Generates a one-time token for a customer

/users/passwordless-login-with-token/

POST

Login using one-time token

/baskets/basket/

POST

Add/update items in basket

/orders/checkout/

POST

Checkout steps (all pages handled here)

/orders/hooks/payment

POST

IPN webhook endpoint

/users/orders/

GET

List customer orders

/api/v1/remote/1/transactions/

GET

Transaction list (admin)

/api/v1/remote/1/orders/

GET

Order list (admin)

Checklist

Commerce Side (One-time setup)

Extension Side


Test Scenarios

Support

For technical questions:

  • Commerce API: api-support@akinon.com

  • Extension development: extension-support@akinon.com

Last updated

Was this helpful?