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 == trueOrderItem.product.attributes.subscription_periodis defined
3. Subscription Creation
If the order is identified as a subscription, the Extension creates a subscription record with the following information:
user_idproduct_idsource_idpayment_idperiod(retrieved from the product attribute)next_payment_date(calculated based on the subscription period)
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
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.
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
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:
The Commerce team defines the
auth.usernameandauth.passwordvalues when creating the POS configuration.These credentials are securely shared with the Extension team through a secure channel (e.g., encrypted email or a secret management solution).
The Extension uses these credentials in the Basic Authentication header when sending IPN requests to Commerce.
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.
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
The customer adds a subscription product to the shopping cart.
The customer completes the checkout steps, including shipping address and shipping method.
The customer selects Checkout.com as the payment method.
The customer is redirected to the Checkout.com payment page.
The customer enters their card details and selects the "Save Card" option.
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:
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:
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):
Perform a subscription check:
Is
Order.extra_field.is_subscription == true?Does the product attribute contain
subscription_period?
Create a subscription record:
user_idorder_idproduct_idsource_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
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:
Store the pre_order_number value. It will be used as the order_id when sending the IPN.
Redirect Requirement: You must send a GET request to the redirect_url returned in the response (or redirect the browser to this URL). This step transitions the order to the payment_waiting status in Commerce. If this step is skipped, sending the IPN may result in an "Order not found" or "Transaction not found" error.
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_hashfrom the cache.Uses the
basket_hashto 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
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
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
INITIALIZED
Payment has been initiated and is awaiting a result
COMPLETED
Payment has been successfully completed
DECLINED
Payment has been rejected
Status / Substatus Values
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 = 2Substatus:
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 AcceptableContent-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
406
webhook_300
General webhook processing error
406
authentication_failed
Incorrect Basic Auth credentials
406
validation_error
Request body validation error
Continue resending the IPN until you receive an HTTP 200 OK response (maximum 5 attempts with exponential backoff).
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
If you receive a 406 Not Acceptable error when sending the IPN, this is not an error but a waiting signal.
Wait 60 seconds.
Resend the same IPN request.
Repeat until you receive 200 OK (max 3 attempts).
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
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
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.datais transferred toOrder.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
cancelledstatus, and the new subscription starts withactivestatus.
5. Subscription Lifecycle
Status Transitions
A subscription moves between the following states:
active→ when payment time arrives, it transitions toprocessingprocessing→ if payment is successful, it returns toactiveprocessing→ if payment fails, it transitions toretry_pendingretry_pending→ if retry is successful, it returns toactiveretry_pending→ if max retry limit is exceeded, it transitions tosuspendedactive / suspended→ when the period ends, is canceled, or a new order is required, it transitions tocompletedorcancelled
Data That Must Be Stored in the Extension
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:passwordformat?Are
auth.usernameandauth.passwordin 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_idmatch thepre_order_numberfrom 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.99is 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
Checkout - Address & Shipping
Checkout - Payment
Passwordless Login
Transactions API
Checkout.com API References
Recurring Payments
Tokenization
Real Endpoints (Used in This Project)
/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?

