Flutter SDK (Deprecated)
Flutter is an open-source SDK. It is used by developers to develop solutions for different operating systems and the web from a single codebase.
The Cashfree Flutter SDK allows you to integrate Cashfree Payment Gateway with your application and start collecting payments from your customers. It opens the payment page in a webview. The Cashfree SDK has been designed to minimise the complexity of handling and integrating payments in your Flutter project.
The Cashfree Flutter SDK is available here.
SDK Now Supports Web Platform
You can use now use the Flutter SDK to build a payment experience for your users on android, iOS, and browser.
Watch the video to know how to integrate Cashfree Flutter SDK with your Flutter application:
Integration Steps
To integrate Cashfree SDK with your Flutter application,
- Create an account with Cashfree and get the API keys
- Integrate the Cashfree SDK into your application
- Generate Token - From Backend
- Initiate payment - Invoke a payment API from the Cashfree SDK with the token generated when the customer initiates payment for an order from your application. Cashfree SDK displays appropriate screens to the customer for the payment.
- Receive and handle response - Cashfree SDK returns the payment result for the order which should be handled in your application.
- Verify response - It is mandatory to verify the payment response by checking the signature value returned in the payment response. It is also highly recommended to implement webhook notifications to receive a notification from the Cashfree backend to your backend whenever a payment is successful for an order.
Step 1: Create Account and Get API Keys
- Go to Cashfree website and create an account. Click here for detailed steps on how to create and activate your account.
- Log in to your Merchant Dashboard using the same credentials.
- Click Payment Gateway section View Dashboard click Credentials. For security purposes, you need to enter your password for verification.
- Copy the app ID and the secret key. These values are required to create the order token from your server. Order tokens are used to authenticate the API calls made from Cashfree Flutter SDK.
Step 2: Integrate SDK
To integrate the SDK, follow the steps below:
Step 2a: Add Dependency
Open the pubspec.yaml file located inside the app folder, and add cashfree_pg: under dependencies.
cashfree_pg: 2.0.12+32
Step 2b: Add permissions (Android)
The Cashfree PG SDK requires that you add the INTERNET permission in your Android Manifest file.
<manifest ...>
<uses-permission android:name="android.permission.INTERNET" />
<application ...>
- Android Gradle Plugin Version - 4.1.1 or higher
- Android Gradle Version - 6.5 or higher
Step 2c: Set the tools:node attribute to merge in the definition of your application element in the Android Manifest file.
<application
...
tools:node="merge">
<!--Only add it if you need Auto OTP reading feature is enabled-->
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>
Step 2d: Permission for iOS
Open the iOS application using XCode or any text editor and add the following into its info.plist file
<key>LSApplicationCategoryType</key>
<string></string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>phonepe</string>
<string>tez</string>
<string>paytm</string>
</array>
Step 3: Generate Token (From Backend)
The cftoken is used to authenticate the payment requests made from SDK. It has to be generated for every payment attempt made for an order. Pass this token to the SDK while initiating the payment for that order. For generating a cftoken you need to use our token generation API.
Ensure that this API is called only from your backend as it uses a secret key. This API should never be called from the App.
Request Description to Generate Token
Production - set the URL to https://api.cashfree.com/api/v2/cftoken/order
Testing - set the action URL to https://test.cashfree.com/api/v2/cftoken/order
You need to send orderId, orderCurrency and orderAmount as a JSON object to the API endpoint and in response, you will receive a token. Please see the description of the request below.
Token Request Description
curl -XPOST -H 'Content-Type: application/json'
-H 'x-client-id: <YOUR_APP_ID>'
-H 'x-client-secret: <YOUR_SECRET_KEY>'
-d '{
"orderId": "<ORDER_ID>",
"orderAmount":"<ORDER_AMOUNT>",
"orderCurrency": "<ORDER_CURRENCY>"
}' '<TEST_OR_PROD_API_URL>’'
Request Example: Replace YOUR_APP_ID and YOUR_SECRET_KEY with actual values.
curl -XPOST -H 'Content-Type: application/json' -H 'x-client-id: 7051d9675fae9ae886f9de1b1507' -H 'x-client-secret: 90c0919f5f4b31a06eb8ba3042f57272166ccdfb' -d '{
"orderId": "Order0001",
"orderAmount":"1",
"orderCurrency":"INR"
}' 'https://test.cashfree.com/api/v2/cftoken/order'
Response Example
{
"status": "OK",
"message": "Token generated",
"cftoken": "v79JCN4MzUIJiOicGbhJCLiQ1VKJiOiAXe0Jye.s79BTM0AjNwUDN1EjOiAHelJCLiIlTJJiOik3YuVmcyV3QyVGZy9mIsEjOiQnb19WbBJXZkJ3biwiIxADMwIXZkJ3TiojIklkclRmcvJye.K3NKICVS5DcEzXm2VQUO_ZagtWMIKKXzYOqPZ4x0r2P_N3-PRu2mowm-8UXoyqAgsG"
}
The "cftoken" is used to authenticate your payment request.
This API should be called from your server (backend) only, and never from your Flutter application as it uses the secretKey.
Step 4: Initiate Payment
To initiate payments, your application passes the order info and the cftoken to the SDK. The relevant payment screen is displayed to the customer where they enter the required information and make the payment. Flutter SDK verifies the payment after it is complete and sends a response to the application. The application handles the response appropriately.
- The order details passed during the token generation and the payment initiation should match. Else, you will get an
Invalid order details
error.- Wrong appId and token will result in
Unable to authenticate merchant
error. The token generated for payment is valid for 5 minutes within which the payment has to be initiated. Else, you will get anInvalid token
error.
Step 5. Receive and Handle Response
When you invoke the SDK function, it returns Future as response.
Step 6. Verify Response
Once the SDK returns a response to the application, it is mandatory to verify the payment response by verifying the signature value returned in the payment response. It is also highly recommended to implement webhook notification to receive a notification from Cashfree backend to your backend whenever a payment is successful for an order.
Click here to know about the implementation details.
Sample Application
Click here to view the sample application.
Web Checkout
Web Checkout is the standard flow for Cashfree Flutter SDK. In this flow, the SDK loads a webview which renders the payment page. The customer can fill in the required payment details here to complete the payment. The Web Checkout can be used in two ways:
Web Checkout using Cashfree UI: Customer selects the payment mode and enters the payment details within Cashfree's web payment page to complete the payment.
Seamless Web Checkout: Customer selects the payment mode and enters payment details in your application. These details are then passed on to the Cashfree SDK. Webview is launched only for two-factor authentication.
For both modes, you must invoke the doPayment() method. However, there are a few additional parameters you need to pass for seamless integration.
Web Checkout using Cashfree UI
Here, you can use our prebuilt Cashfree UI to accept payments. For both Web Checkout and Seamless Web Checkout, you need to invoke the doPayment() method. However, there are a few extra parameters you need to pass for seamless integration method.
doPayment
Future<Map<dynamic, dynamic>> doPayment(Map<String, dynamic> inputs)
Initiates the payment in a webview. The customer will be taken to the payment page on the Cashfree server where they will have the option to pay through any payment option that is activated on their account. Once the payment is done the webview will close and the response will be delivered in the callback.
Parameters:
- params: A map of all the relevant parameters described in Request Parameters.
Seamless Web Checkout
When your business requires a customised payment flow, you can use the seamless integration method. You can implement the payment page as per your requirement and then use our SDK to authorise the payment. Once the payment details are collected the OTP or the two-factor authentication page will open in a webview. After the payment is confirmed the webview closes and you will receive a response.
We recommend that you use Web Checkout using Cashfree UI integration unless you are certain that you require a customised payment flow.
The following sections describe the additional parameters for each of the payment methods:
Credit/Debit Card
Add the following parameters to the params map before invoking doPayment() method to initiate a seamless card transaction.
inputs[ "paymentOption"] = “card”;
inputs[ "card_number"] = “4434260000000008”; //Replace Card number
inputs[ "card_expiryMonth"] = “05”; // Card Expiry Month in MM
inputs[ "card_expiryYear"] = “2021”; // Card Expiry Year in YYYY
inputs[ "card_holder"] = “John Doe”; // Card Holder name
inputs[ "card_cvv"] = “123”; // Card CVV
Net Banking
Add the following parameters to the params map before invoking doPayment() method to initiate a seamless net banking transaction. All valid bank codes are available here.
inputs[ "paymentOption"] = “nb”;
inputs[ "paymentCode"] = “3333”; // Put correct bank code here
Wallet
Add the following parameters to the params map before invoking doPayment() method to initiate a seamless wallet transaction. All valid wallet codes are available here.
inputs[ "paymentOption"] = “wallet”;
inputs[ "paymentCode"] = “4001”; // Put correct wallet code here
UPI
Add the following parameters to the params map before invoking doPayment() method to initiate a seamless UPI transaction.
inputs[ "paymentOption"] = “upi”;
inputs[ "upi_vpa"] = “testsuccess@gocash”; // Put correct upi vpa here
Paypal
Add the following parameters to params map before invoking doPayment() method to initiate a seamless Paypal transaction.
inputs[ "paymentOption"] = "paypal";
Sample Code
//Replace with actual values
String stage = "TEST";
String orderId = "Order Id";
String orderAmount = "ORDER AMOUNT";
String tokenData = "TOKEN_DATA";
String customerName = "Customer Name";
String orderNote = "Order Note";
String orderCurrency = "INR";
String appId = "APP_ID";
String customerPhone = "9999999999";
String customerEmail = "[email protected]";
String notifyUrl = "https://test.gocashfree.com/notify";
Map<String, dynamic> inputParams = {
"orderId": orderId,
"orderAmount": orderAmount,
"customerName": customerName,
"orderNote": orderNote,
"orderCurrency": orderCurrency,
"appId": appId,
"customerPhone": customerPhone,
"customerEmail": customerEmail,
"stage": stage,
"notifyUrl": notifyUrl
};
CashfreePGSDK.doPayment(inputParams)
.then((value) => value?.forEach((key, value) {
print("$key : $value");
//Do something with the result
}));
}
Third-Party Validation
The Flutter plugin also supports Third Party Validation through Seamless Web Checkout.
There is no change in the integration method, the only exception is that you have to send us the pre-registered account number and IFSC code for each of your customers as part of the request parameter.
For TEST credentials and more information about parameters to be sent, visit here
UPI Intent
When the doUPIPayment method is invoked the customer is shown a list of all the installed UPI applications on their phone. After the customer selects their preferred application, the payment confirmation page will open in the application. After payment completion, the response is delivered through a “Future”.
1
Steps to integrate:
After generating the token and adding permissions for Android (mentioned above), the following are the steps to Integrate the UPI Intent into your flutter application:
- Open the iOS project using XCode or any text editor and add the following into its info.plist file
<key>LSApplicationCategoryType</key>
<string></string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>phonepe</string>
<string>tez</string>
<string>paytm</string>
</array>
- Android Manifest Change - If the project's targetSdkVersion is 30, add the following code to the android manifest file.
<manifest package="com.example.game">
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="upi" android:host="pay"/>
</intent>
<package android:name="com.android.chrome" />
</queries>
...
</manifest>
- Import the cashfree-pg in your .dart file by using the following
import ‘package:cashfree_pg/cashfree_pg.dart’
doUPIPayment
Future<Map<dynamic, dynamic>> doUPIPayment(Map<String, dynamic> inputs)
This function initiates the UPI Payment and the customer is shown a list of all the UPI client applications (Paytm, GPay, PhonePe etc.) on their phone. This allows the customer to select any UPI application of their choice to pay with. Once the payment is completed the UPI page will close and the response will be delivered through a Future. (See Sample Code below).
Parameters:
A map of relevant parameters is described in the Request Parameters.
Payments on the Test server will go via the UPI simulator and will be considered as mock payments. Switch to the Production server to do live payments.
Create a new order ID every time you invoke the UPI payment flow since UPI intent flow can be initiated once per order ID.
Sample Code
//Replace with actual values
String stage = "TEST";
String orderId = "Order Id";
String orderAmount = "ORDER AMOUNT";
String tokenData = "TOKEN_DATA";
String customerName = "Customer Name";
String orderNote = "Order Note";
String orderCurrency = "INR";
String appId = "APP_ID";
String customerPhone = "9999999999";
String customerEmail = "[email protected]";
String notifyUrl = "https://test.gocashfree.com/notify";
Map<String, dynamic> inputParams = {
"orderId": orderId,
"orderAmount": orderAmount,
"customerName": customerName,
"orderNote": orderNote,
"orderCurrency": orderCurrency,
"appId": appId,
"customerPhone": customerPhone,
"customerEmail": customerEmail,
"stage": stage,
"notifyUrl": notifyUrl
};
CashfreePGSDK.doUPIPayment(inputParams)
.then((value) => value?.forEach((key, value) {
print("$key : $value");
//Do something with the result
}));
}
Seamless UPI Intent
When you want to show your own customised payment screen with specific UPI payment applications, you can use our seamless UPI integration method. Here customers click on the required application and make the payment. You can implement the payment page as per your requirement and then use our SDK to authorise the payment.
Follow the steps below for seamless UPI intent flow:
- Call the getUPIApps method to get the list of all installed UPI applications.
CashfreePGSDK.getUPIApps().then((value) => {
// Value is a List of MAP<String, String>
// It consists of 3 keys "displayName", "id" and a base64 string "icon"
// You can convert the base64 string to image and display the app icon
})
});
//NOTE:- If you wish to use the application Icon provided by Cashfree,
the below code can be used to convert base64 String to image.
Uint8List _imageBytesDecoded;
_imageBytesDecoded = Base64Codec().decode(app["icon"]);
// Inside a Widget, you can use this to show icons
Center(
child: this._imageBytesDecoded != null ? Image.memory(_imageBytesDecoded,fit: BoxFit.cover,) : Icon(Icons.image),
)
- Send "id" retrieved by the above method as value to the key "appName".j
Map<String, dynamic> inputParams = {
.................
.................
.................
.................
.................
"appName": selectedApp["id"], // This is one of the Map<> from the getUPIApps() method
};
// Then invoke the doUPIPayment Method
CashfreePGSDK.doUPIPayment(inputParams)
.then((value) => value?.forEach((key, value) {
print("$key : $value");
//Do something with the result
}));
Parameters:
- params: A map of all the relevant parameters described in the Request Parameters section below.
Customise Appbar
If you want to customise the appbar colour and the text color, use the following parameters:
-
color1: Appbar background color.
-
color2: Text and back arrow color.
inputs[ "color1"] = “00FFFF”; // Use hexadecimal values for background color inputs[ "color2"] = “00FFFF”; // Use hexadecimal values for text color
Verify Response
Once the SDK returns a response to the application, it is mandatory to verify the payment response by verifying the signature value returned in the payment response and it also highly recommended to implement webhook notification.
Webhook Notifications
Webhooks are events that notify you about the payment. We send a notification from Cashfree backend to your backend whenever a payment is successful for an order. The notification will be sent to notifyUrl which is specified during order creation.
To specify notifyUrl, add it with other parameters (orderId, orderAmount etc.) as shown below:
inputs[ "notifyUrl"] = “https://example.com/path/to/notify/url/”;
- Notifications are usually instant but in some rare cases it can take up to a minute to hit your server. Make sure that your URL supports https. Notifications are sent only in the case of successful payments.
- Sometimes you may receive the same notification two or more times. It is recommended to ensure that your implementation of the webhook is idempotent.
- Ensure that you verify the signature in the webhook response.
- This also handles scenarios for users in cases like the internet connection lost after payment, or user closing the application after payments, etc. It helps to reconcile all the successful orders at your end.
The parameters sent in notification are described here.
Verify Signature
Verify the signature value in the payment response to check the authenticity of the transaction response. In every response, we add a digital signature to establish the authenticity of the message. We require you to verify the signature received at your end to ensure that the response has not been tampered. This verification has to be done on your server as involves secretKey which should not be exposed on the client side.
<?php
$orderId = $_POST["orderId"];
$orderAmount = $_POST["orderAmount"];
$referenceId = $_POST["referenceId"];
$txStatus = $_POST["txStatus"];
$paymentMode = $_POST["paymentMode"];
$txMsg = $_POST["txMsg"];
$txTime = $_POST["txTime"];
$signature = $_POST["signature"];
$data = $orderId.$orderAmount.$referenceId.$txStatus.$paymentMode.$txMsg.$txTime;
$hash_hmac = hash_hmac('sha256', $data, $secretkey, true) ;
$computedSignature = base64_encode($hash_hmac);
if ($signature == $computedSignature) {
// Proceed
} else {
// Reject this call
}
?>
import hashlib
import hmac
import base64
@app.route('/notify_url/', methods=["POST"])
def notify_url_process():
postData = {
"orderId" : request.form['orderId'],
"orderAmount" : request.form['orderAmount'],
"referenceId" : request.form['referenceId'],
"txStatus" : request.form['txStatus'],
"paymentMode" : request.form['paymentMode'],
"txMsg" : request.form['txMsg'],
"txTime" : request.form['txTime'],
}
signatureData = postData["orderId"] + postData["orderAmount"] + postData["referenceId"] + postData["txStatus"] + postData["paymentMode"] + postData["txMsg"] + postData["txTime"]
message = bytes(signatureData).encode('utf-8')
#get secret key from your config
secret = bytes(secretKey).encode('utf-8')
signature = base64.b64encode(hmac.new(secret,
message,digestmod=hashlib.sha256).digest())
PHP
LinkedHashMap<String, String> postData = new LinkedHashMap<String, String>();
postData.put("orderId", ORDERID);
postData.put("orderAmount", ORDERAMOUNT);
postData.put("referenceId", REFERENCE_ID);
postData.put("txStatus", TXN_STATUS);
postData.put("paymentMode", PAYMENT_MODE);
postData.put("txMsg", TX_MSG);
postData.put("txTime", TX_TIME);
String data = "";
Set<String> keys = postData.keySet();
for (String key : keys) {
data = data + postData.get(key);
}
String secretKey = "" // Get secret key from config;
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key_spec = new
SecretKeySpec(secretKey.getBytes(),"HmacSHA256");
sha256_HMAC.init(secret_key_spec);
String signature = Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(data.getBytes()));
using System;
using System.Security.Cryptography;
using System.Collections.Generic;
namespace Rextester {
public class Program {
private string CreateToken(string message, string secret){
secret = secret ?? "";
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(secret);
byte[] messageBytes = encoding.GetBytes(message);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
return Convert.ToBase64String(hashmessage);
}
}
public static void Main(string[] args) {
string secret = "<your_secret_key>";
string data = "";
data = data + "FEX101";
data = data + "10.00";
data = data + "19992";
data = data + "SUCCESS";
data = data + "pg";
data = data + "payment done";
data = data + "2018-02-02 17:29:12";
Program n = new Program();
string signature = n.CreateToken(data, secret);
Console.WriteLine(signature);
}
}
}
Request and Response Parameters
Request Parameters
Parameter | Type | Required | Description |
---|---|---|---|
color1 | String | Yes | Background color value of the top bar as Hex String. ex:- "#FFFFFF" |
color2 | String | Yes | Text color of the topbar as Hex String. ex:- "#000000" |
stage | String | Yes | Environment - TEST or PROD |
appId | String | Yes | Your app ID |
orderId | String | Yes | Order or Invoice ID |
orderCurrency | String | Yes | Currency code for the order. |
orderAmount | String | Yes | Bill amount of the order |
orderNote | String | No | Help text to provide customers with more information about the order. |
customerName | String | No | Name of the customer |
customerPhone | String | Yes | Phone number of the customer |
customerEmail | String | Yes | Email ID of the customer |
notifyUrl | String | No | Notification URL for server-server communication. Useful when a user's connection drops after completing the payment. |
paymentModes | String | No | Allowed payment modes for this order. Available values: cc, dc, nb, paypal, upi, wallet. Leave it blank if you want to display all modes. |
tokenData | String | Yes | Token generated here. |
Response Parameters
These parameters contain the details of the transaction.
Parameter | Description |
---|---|
orderId | Order id for which transaction has been processed. Example, GZ-212. |
orderAmount | Order amount. Example, 256.00 |
paymentMode | Payment mode of the transaction. |
referenceId | Cashfree generated unique transaction ID. Example, 140388038803. |
txStatus | Payment status for that order. Values can be: SUCCESS, FLAGGED, PENDING, FAILED, CANCELLED. For UPI Intent the status can be: PENDING, INCOMPLETE, FAILED, FLAGGED, USER_DROPPED, SUCCESS, CANCELLED, VOID |
paymentMode | Payment mode used by customers to make the payment. Example, DEBIT_CARD, MobiKwik. |
txMsg | Message related to the transaction. Will have the reason, if payment failed. |
txTime | Time of the transaction. |
type | Fixed value: CashFreeResponse. To identify the response is from Cashfree SDK. |
signature | Signature generated to verify the authenticity of the transaction as explained, more here. |
- There can be scenarios where the SDK is not able to verify the payment within a short period of time. The status of such orders will be PENDING.
- If the Webview closes immediately after it is opened then it could be because of some issues with the input that is passed to the SDK. Check the inputs passed and if you still need further help reach out to us at [email protected].
- If you are getting INCOMPLETE as the transaction status please reach out to your account manager or [email protected]. To know more about the transaction statuses, click here.
Checklist
Checklist to Go Live
- Ensure you trigger https://api.cashfree.com/api/v2/cftoken/order endpoint to generate the Token.
- Pass the production appId/secretKey in the x-client-id and x-client-secret of the token request API.
- When calling doPayment() method ensure that the stage parameter is "PROD".
- When calling doPayment() method ensure the params map is sent to your appId. Ensure it is the correct production appId.
Checklist to Test the Integration
- Ensure you trigger https://test.cashfree.com/api/v2/cftoken/order endpoint to generate the token.
- Pass the Test app ID and secret key in the x-client-id and x-client-secret of the token request API.
- When calling doPayment() method ensure that the stage parameter is "TEST".
- When calling doPayment() method ensure the params map is sent to your appId. Ensure it is the correct test appId.
FAQ
- Gradle error message is displayed while building Android APK. How do I resolve it?
Could not determine the dependencies of task ':app:compileDebugJavaWithJavac'.
Could not resolve all dependencies for configuration ':app:debugCompileClasspath'.
Problems reading data from Binary store
If you get any of the above errors, follow the steps below:
- Open the Flutter project folder.
- Open android/build.gradle file. Change classpath("com.android.tools.build:gradle:x.y.z") to classpath("com.android.tools.build:gradle:4.1.1") or higher.
- Open android/gradle/wrapper/gradle-wrapper.properties. Change "distributionUrl=https://services.gradle.org/distributions/gradle-a.b-all.zip" to "distributionUrl=https://services.gradle.org/distributions/gradle-6.5-all.zip" or higher.
- Build the android project.
2. Error message is displayed while building Android APK using the command flutter build apk --split-per-abi. How do I resolve it?
Failure: Build failed with an exception in script '/Users/user/Documents/flutter SDK/flutter/packages/flutter_tools/gradle/flutter.gradle' line: 646
- A problem occurred evaluating root project 'android'.
- A problem occurred configuring project ':app'.
- The value for this property cannot be changed any further.
If you see the above error, follow the steps below:
- Check flutter version using flutter --version.
- If Flutter version is Flutter 1.22.6 • channel stable, change the channel to beta by running the following commands:
a. flutter channel beta
b. flutter upgrade - Run this command to build the APK flutter build apk --split-per-abi.
- Error message is displayed while building Android APK:Execution failed for task ':app:generateReleaseBuildConfig'. How do I resolve it?
Failure: Build failed with an exception.
Execution failed for task ':app:generateReleaseBuildConfig'.
Failed to calculate the value of task ':app:generateReleaseBuildConfig' property 'buildConfigPackageName'.
Failed to query the value of property 'packageName'.
org.xml.sax.SAXParseException; systemId: file:/Users/user/Desktop/another_test/android/app/src/main/AndroidManifest.xml; lineNumber: 13; columnNumber: 28; The prefix "tools" for attribute "tools:node" associated with an element type "application" is not bound.
If you see the above error, follow the steps below:
- Open the Flutter project folder.
- Open “android/app/src/main/AndroidManifest.xml” and add the below code:
<manifest………….
xmlns: tools=”http://schemas.android.com/tools”
……>
Updated 12 months ago