Antom EasySafePay is a streamlined payment solution designed for low-value, high-frequency payment scenarios. During the first payment, buyers can either bind their wallet or pay directly. For subsequent payments, they can complete the transaction with a single click, without entering a password.
Powered by an industry-leading intelligent risk control system, dynamic routing technology, and payment failure recovery strategies, this solution ensures transaction security while achieving top-tier payment success rates across the industry. It delivers benefits to buyers, merchants, and the broader platform ecosystem by enhancing the payment experience, improving merchant conversion rates, and increasing ecosystem value.
This article mainly introduces how to integrate the EasySafePay product via API.
The user experience for each payment method differs between PC and mobile devices. For the specific interaction flows, please refer to the table and user experience diagrams below.
Below are the user experience diagrams for the two payment categories, digital wallet and online banking, on desktop browsers (Web).
In the digital wallet scenario, the user experience diagrams for the first payment and subsequent payments are shown below.
During the first payment, the buyer needs to complete the payment authorization process to enable password-free payments for future transactions.
For subsequent payments, if buyer participation is required, the buyer only needs to submit the order to complete the password-free payment. If buyer participation is not required, you can directly initiate the payment request from the backend.
The user experience diagram for the online banking scenario is shown below:
During the first payment, the buyer needs to complete the payment authorization process to enable password-free payments for future transactions.
For subsequent payments, if buyer participation is required, the buyer only needs to submit the order to complete the password-free payment. If buyer participation is not required, you can directly initiate the payment request from the backend.
Below are the user experience diagrams for the two payment categories, digital wallet and online banking, on mobile browsers (WAP).
In the digital wallet scenario, the user experience diagrams for the first payment and subsequent payments are shown below.
During the first payment, the system guides the buyer to complete the transaction either on the merchant page or on the payment method side. After the buyer completes the authorization, subsequent payments can be made without entering a password.
For subsequent payments, if buyer participation is required, the buyer only needs to submit the order to complete the password-free payment. If buyer participation is not required, you can directly initiate the payment request from the backend.
The user experience diagram for the online banking scenario is shown below:
During the first payment, the buyer must complete the payment authorization process to enable password-free payments for future transactions.
Subsequent payments can be completed without re-linking the account.
Below are the user experience diagrams for the two payment categories, digital wallet and online banking, on mobile devices (App).
In the digital wallet scenario, the user experience diagrams for the first payment and subsequent payments are shown below.
During the first payment, the system will guide the buyer to complete the transaction either on the merchant page or on the payment method side. Once the buyer completes the authorization, subsequent payments can be made without a password.
For subsequent payments, if buyer participation is required, the buyer only needs to submit the order to complete the password-free payment. If buyer participation is not required, you can directly initiate the payment request from the backend.
The user experience diagram for the online banking scenario is shown below:
During the first payment, the buyer needs to complete the payment authorization process to enable password-free payments for future transactions.
Subsequent payments can be completed without re-linking the account.
- The buyer enters the checkout page.
- The buyer selects a payment method and submits the payment request.
- Create a payment request.
After the buyer selects a payment method and submits the order, the merchant server initiates the payment request by calling the pay (Tokenized Payment) API based on transaction information such as the payment method, amount, currency, and goods. - Process the payment continuation link.
The merchant client is redirected to the URL returned in the payment request, or launches the relevant application to complete the authorized payment. - Obtain the authorization result.
When the authorization is successful, Antom sends you an asynchronous notification via the notifyAuthorization API. - Obtain the payment result.
Obtain the payment result using one of the following methods:
- Asynchronous notification: Specify paymentNotifyUrl in the pay (Tokenized Payment) API to set the address for receiving asynchronous notifications or configure the url for receiving asynchronous notifications in the Antom Dashboard. When the authorization is successful, Antom sends you an asynchronous notification via the notifyPayment API.
- Synchronous inquiry: Call the inquiryPayment API to check the real-time payment status.
- The buyer enters the checkout page.
- The buyer selects a payment method and submits the payment request.
- Create a payment request.
After obtaining the buyer’s authorization, you can directly call the pay (Tokenized Payment) API to initiate the payment. - The buyer completes risk verification.
If a transaction is identified as risky, the buyer must be redirected to a frontend page to complete verification. If the merchant initiates a subscription charge directly from the backend, no verification page will be displayed.
- Obtain the payment result.
Obtain the payment result using one of the following methods:
- Asynchronous notification: Specify paymentNotifyUrl in the pay (Tokenized Payment) API to set the address for receiving asynchronous notifications or configure the url for receiving asynchronous notifications in the Antom Dashboard. When the payment is successful or expires, Antom will use notifyPayment API to send asynchronous notifications to you.
- Synchronous inquiry: Call the inquiryPayment API to check the real-time payment status.
Before you start integrating, read the Integration Guide and API Overview documents to understand the integration steps of the server-side API and the precautions for calling the API. Furthermore, ensure that the following prerequisites are met: - Obtain a client ID
- Complete the key configuration
- Complete the configuration of paymentNotifyUrl to receive the asynchronous notification
- Integrate the server-side SDK package, install the server-side library, and initialize a request instance. For more details, refer to Server-side SDK.
Start your integration by taking the following steps:
- Create a payment request
- Obtain the payment continuation URL
- Obtain the authorization and payment result.
Step 1: Create a payment request
The table below describes the API request parameters for the first payment and subsequent payments.
Note:
- The integration flow for PayPay Smart Payment is different. For the specific steps, please refer to Accept payments with PayPay Smart Payment.
- For Express Bank Transfer, the first payment and subsequent payments use the same request parameter format. The buyer only needs to enter their mobile phone number on the payment page to complete the payment.
For AlipayHK and DANA, the supported buyerPhoneNo formats are as follows: During the first payment, passing order.buyer.buyerPhoneNo can automatically prefill the buyer’s payment account on the payment page, eliminating the need for manual entry. Below is a comparison of the user experience with and without this parameter:
When the payment method account is provided, the buyer enters the authorization payment flow or chooses a one-time payment.
When the buyer enables the Enable One-Click Payment option during the first payment, password-free payments are enabled after completing authorization, making subsequent transactions faster and more convenient.
When the buyer disables the Enable One-Click Payment option, the buyer is redirected to the payment method provider’s page to complete a one-time payment.
If the payment method account is not provided, the page will guide the buyer to the payment method side to complete the authorization.
Below are code examples for first and subsequent payments.
@PostMapping("/v1/payments/pay")
public ResponseEntity<ApiResponse> pay(@RequestBody PaymentVO payment) {
AmsPayRequest request = new AmsPayRequest();
request.setProductCode(ProductCodeType.AGREEMENT_PAYMENT);
// Replace with your paymentRequestId
String paymentRequestId = UUID.randomUUID().toString();
request.setPaymentRequestId(paymentRequestId);
// Convert the amount unit (in actual use, the amount should be calculated on the server side)
// For details, refer to: https://docs.antom.com/ac/ref/cc
long amountMinorLong = Money.of(CurrencyUnit.of(payment.currency),
new BigDecimal(payment.amountValue)).getAmountMinorLong();
// Set paymentAmount
Amount paymentAmount = Amount.builder()
.currency(payment.currency)
.value(String.valueOf(amountMinorLong))
.build();
request.setPaymentAmount(paymentAmount);
// Set settlementStrategy
// Replace with your existing settlement currency
SettlementStrategy settlementStrategy = SettlementStrategy.builder()
.settlementCurrency("USD")
.build();
request.setSettlementStrategy(settlementStrategy);
// Set paymentMethod
PaymentMethod paymentMethod = PaymentMethod.builder()
.paymentMethodType(payment.paymentMethodType)
.build();
User loginUser = users.get(payment.getUserId());
if (loginUser.getPaymentMethodTypeAccessToken().containsKey(payment.getPaymentMethodType())) {
// The buyer has already authorized, use the token for recurring payment
String accessToken = loginUser.getPaymentMethodTypeAccessToken().get(payment.getPaymentMethodType());
paymentMethod.setPaymentMethodId(accessToken);
} else {
// The buyer has not authorized yet, set agreementInfo to start the agreement-signing flow
String authState = UUID.randomUUID().toString();
AgreementInfo agreementInfo = AgreementInfo.builder()
.authState(authState)
.build();
request.setAgreementInfo(agreementInfo);
// Save the paymentMethodType corresponding to authState
authStatePayment.put(authState, payment);
}
request.setPaymentMethod(paymentMethod);
// Set env (environment information for risk control)
Env env = Env.builder()
.osType(payment.getOsType())
.terminalType(payment.getTerminalType())
.deviceId(payment.getDeviceId())
.clientIp(payment.getClientIp())
.deviceTokenId(payment.getDeviceTokenId())
.build();
request.setEnv(env);
// Set goods (product details)
List<Goods> goodsList = payment.getGoodsList().stream()
.map(g -> Goods.builder()
.referenceGoodsId(g.getReferenceGoodsId())
.goodsName(g.getGoodsName())
.quantity(g.getQuantity())
.price(Amount.builder()
.currency(payment.currency)
.value(g.getPriceValue())
.build())
.build())
.collect(Collectors.toList());
// Set shipping (shipping information)
Shipping shipping = Shipping.builder()
.shippingName(ShippingName.builder()
.firstName(payment.getShippingFirstName())
.lastName(payment.getShippingLastName())
.build())
.shippingAddress(ShippingAddress.builder()
.region(payment.getShippingRegion())
.state(payment.getShippingState())
.city(payment.getShippingCity())
.address1(payment.getShippingAddress1())
.address2(payment.getShippingAddress2())
.zipCode(payment.getShippingZipCode())
.build())
.shippingCarrier(payment.getShippingCarrier())
.shippingPhoneNo(payment.getShippingPhoneNo())
.build();
// Set buyer (buyer information)
Buyer buyer = Buyer.builder()
.referenceBuyerId(loginUser.getReferenceBuyerId())
.buyerName(BuyerName.builder()
.firstName(loginUser.getFirstName())
.lastName(loginUser.getLastName())
.build())
.buyerPhoneNo(loginUser.getPhoneNumber())
.buyerEmail(loginUser.getEmail())
.build();
// Replace with your orderId
String orderId = UUID.randomUUID().toString();
// Set order (order information, including product details, shipping, and buyer)
Amount orderAmount = Amount.builder()
.currency(payment.currency)
.value(String.valueOf(amountMinorLong))
.build();
Order order = Order.builder()
.referenceOrderId(orderId)
.orderDescription(payment.getOrderDescription())
.orderAmount(orderAmount)
.goods(goodsList)
.shipping(shipping)
.buyer(buyer)
.build();
request.setOrder(order);
// Replace with your notification URL
// Or configure your notification URL: https://dashboard.antom.com/global-payments/developers/iNotify
request.setPaymentNotifyUrl("https://merchant.com/notify/payment-result");
// Replace with your redirect URL
request.setPaymentRedirectUrl(
"https://merchant.com/payment/result?paymentRequestId=" + paymentRequestId);
AmsPayResponse amsPayResponse;
try {
long startTime = System.currentTimeMillis();
System.out.println("pay request: " + JSON.toJSONString(request));
amsPayResponse = CLIENT.execute(request, AmsPayResponse.class);
System.out.println("pay response: " + JSON.toJSONString(amsPayResponse));
System.out.println("pay request cost time: " + (System.currentTimeMillis() - startTime) + "ms");
} catch (AlipayApiException e) {
return ResponseEntity.ok().body(new ApiResponse(paymentRequestId, payment.getUserId(), e));
}
return ResponseEntity.ok().body(new ApiResponse(paymentRequestId, payment.getUserId(), amsPayResponse));
}
The following code shows a sample of the request message:
{
"order": {
"orderAmount": {
"currency": "HKD",
"value": 100
},
"referenceOrderId": "ORD_2024040512345",
"orderDescription": "iPhone 15 Pro Monthly Subscription",
"goods": [
{
"referenceGoodsId": "G_001",
"goodsName": "iPhone 15 Pro",
"quantity": 1,
"price": {
"currency": "HKD",
"value": 100
}
}
],
"shipping": {
"shippingName": {
"firstName": "John",
"lastName": "Doe"
},
"shippingAddress": {
"region": "US",
"state": "CA",
"city": "San Francisco",
"address1": "123 Tech Street",
"address2": "Apt 4B",
"zipCode": "94107"
},
"shippingCarrier": "FedEx",
"shippingPhoneNo": "+1-415-555-1234"
},
"buyer": {
"referenceBuyerId": "BUYER_88118999",
"buyerName": {
"firstName": "John",
"lastName": "Doe"
},
"buyerPhoneNo": "66622222",
"buyerEmail": "john.doe@example.com"
}
},
"env": {
"osType": "IOS",
"terminalType": "APP",
"deviceId": "deviceIdxxxxxxx",
"clientIp": "192.168.111.111",
"deviceTokenId": "deviceTokenIdxxxxxxxx"
},
"paymentRequestId": "PAYMENT_20251110121228651_AUTO",
"paymentAmount": {
"currency": "HKD",
"value": 100
},
"settlementStrategy": {
"settlementCurrency": "USD"
},
"paymentMethod": {
"paymentMethodType": "ALIPAY_HK"
},
"paymentNotifyUrl": "https://merchant.com/notify/payment-result",
"paymentRedirectUrl": "https://kademo.intlalipay.cn/melitigo/Test_114.html",
"productCode": "AGREEMENT_PAYMENT",
"agreementInfo":{
"authState":"sign_no_xxxxx111111x01001"
}
}
The following code shows a sample of the response message:
{
"actualPaymentAmount": {
"currency": "HKD",
"value": "100"
},
"normalUrl": "https://checkout.antom.com/easysafepay/1.39.0/pages/api-portal/index.html?paymentMethodCategoryType=WALLET&sessionData=vxA9kkY8yysEoUhFyy%2BEvM17vV72yntAcqJGeMF1PETOU9fJtoSUls3s0vdS1YouYbWEl8gHHEaWPO0Xh%2FT%2BJg%3D%3D%26%26SG%26%26188&locale=en-US&productScene=EASY_PAY&productSceneVersion=2.0",
"paymentActionForm": "{"method":"GET","paymentActionFormType":"RedirectActionForm","redirectUrl":"https://checkout.antom.com/easysafepay/1.39.0/pages/api-portal/index.html?paymentMethodCategoryType=WALLET&sessionData=vxA9kkY8yysEoUhFyy%2BEvM17vV72yntAcqJGeMF1PETOU9fJtoSUls3s0vdS1YouYbWEl8gHHEaWPO0Xh%2FT%2BJg%3D%3D%26%26SG%26%26188&locale=en-US&productScene=EASY_PAY&productSceneVersion=2.0"}",
"paymentAmount": {
"currency": "HKD",
"value": "100"
},
"paymentCreateTime": "2025-11-10T00:35:03-08:00",
"paymentId": "20251110194010900000188350227845338",
"paymentRequestId": "PAYMENT_20251110163502455_AUTO",
"redirectActionForm": {
"method": "GET",
"redirectUrl": "https://checkout.antom.com/easysafepay/1.39.0/pages/api-portal/index.html?paymentMethodCategoryType=WALLET&sessionData=vxA9kkY8yysEoUhFyy%2BEvM17vV72yntAcqJGeMF1PETOU9fJtoSUls3s0vdS1YouYbWEl8gHHEaWPO0Xh%2FT%2BJg%3D%3D%26%26SG%26%26188&locale=en-US&productScene=EASY_PAY&productSceneVersion=2.0"
},
"result": {
"resultCode": "PAYMENT_IN_PROCESS",
"resultMessage": "payment in process",
"resultStatus": "U"
}
}
Please take the next step according to the value of the result.resultStatus.
Note: If you do not receive a response, it may be due to a network timeout. Please close the current transaction or retry the API call with a new paymentRequestId and authstate value.
@PostMapping("/v1/payments/pay")
public ResponseEntity<ApiResponse> pay(@RequestBody PaymentVO payment) {
AmsPayRequest request = new AmsPayRequest();
request.setProductCode(ProductCodeType.AGREEMENT_PAYMENT);
// Replace with your paymentRequestId
String paymentRequestId = UUID.randomUUID().toString();
request.setPaymentRequestId(paymentRequestId);
// Convert the amount unit (in actual use, the amount should be calculated on the server side)
// For details, refer to: https://docs.antom.com/ac/ref/cc
long amountMinorLong = Money.of(CurrencyUnit.of(payment.currency),
new BigDecimal(payment.amountValue)).getAmountMinorLong();
// Set paymentAmount
Amount paymentAmount = Amount.builder()
.currency(payment.currency)
.value(String.valueOf(amountMinorLong))
.build();
request.setPaymentAmount(paymentAmount);
// Set settlementStrategy
// Replace with your existing settlement currency
SettlementStrategy settlementStrategy = SettlementStrategy.builder()
.settlementCurrency("USD")
.build();
request.setSettlementStrategy(settlementStrategy);
// Set paymentMethod (recurring payment scenario: use the existing paymentMethodId)
User loginUser = users.get(payment.getUserId());
String accessToken = loginUser.getPaymentMethodTypeAccessToken().get(payment.getPaymentMethodType());
PaymentMethodMetadata metadata = PaymentMethodMetadata.builder()
.recurringType("SCHEDULED") // Pass SCHEDULED when the user is not involved in the payment; this is not required in user-present scenarios
.build();
PaymentMethod paymentMethod = PaymentMethod.builder()
.paymentMethodType(payment.paymentMethodType)
.paymentMethodId(accessToken)
.paymentMethodMetadata(metadata)
.build();
request.setPaymentMethod(paymentMethod);
// Set env (environment information for risk control)
Env env = Env.builder()
.osType(payment.getOsType())
.terminalType(payment.getTerminalType())
.deviceId(payment.getDeviceId())
.clientIp(payment.getClientIp())
.deviceTokenId(payment.getDeviceTokenId())
.build();
request.setEnv(env);
// Set goods (product details)
List<Goods> goodsList = payment.getGoodsList().stream()
.map(g -> Goods.builder()
.referenceGoodsId(g.getReferenceGoodsId())
.goodsName(g.getGoodsName())
.quantity(g.getQuantity())
.price(Amount.builder()
.currency(payment.currency)
.value(g.getPriceValue())
.build())
.build())
.collect(Collectors.toList());
// Set shipping (shipping information)
Shipping shipping = Shipping.builder()
.shippingAddress(ShippingAddress.builder()
.region(payment.getShippingRegion())
.state(payment.getShippingState())
.city(payment.getShippingCity())
.address1(payment.getShippingAddress1())
.address2(payment.getShippingAddress2())
.zipCode(payment.getShippingZipCode())
.build())
.build();
// Set buyer (buyer information)
Buyer buyer = Buyer.builder()
.referenceBuyerId(payment.getReferenceBuyerId())
.buyerName(BuyerName.builder()
.firstName(payment.getBuyerFirstName())
.lastName(payment.getBuyerLastName())
.build())
.buyerPhoneNo(payment.getBuyerPhoneNo())
.buyerEmail(payment.getBuyerEmail())
.build();
// Replace with your orderId
String orderId = UUID.randomUUID().toString();
// Set order (order information, including product details, shipping, and buyer)
Amount orderAmount = Amount.builder()
.currency(payment.currency)
.value(String.valueOf(amountMinorLong))
.build();
Order order = Order.builder()
.referenceOrderId(orderId)
.orderDescription(payment.getOrderDescription())
.orderAmount(orderAmount)
.goods(goodsList)
.shipping(shipping)
.buyer(buyer)
.build();
request.setOrder(order);
// Replace with your notification URL
// Or configure your notification URL: https://dashboard.antom.com/global-payments/developers/iNotify
request.setPaymentNotifyUrl("https://kademo.intlalipay.cn/payments/notifySuccess");
// Replace with your redirect URL
request.setPaymentRedirectUrl(
"https://kademo.intlalipay.cn/melitigo/Test_114.html");
AmsPayResponse amsPayResponse;
try {
long startTime = System.currentTimeMillis();
System.out.println("pay request: " + JSON.toJSONString(request));
amsPayResponse = CLIENT.execute(request, AmsPayResponse.class);
System.out.println("pay response: " + JSON.toJSONString(amsPayResponse));
System.out.println("pay request cost time: " + (System.currentTimeMillis() - startTime) + "ms");
// Recurring payment scenario: the payment result is returned synchronously
// resultStatus=S indicates payment success, and no redirection to the cashier page is required
if ("S".equals(amsPayResponse.getResult().getResultStatus())) {
System.out.println("Payment SUCCESS, paymentId: " + amsPayResponse.getPaymentId()
+ ", paymentTime: " + amsPayResponse.getPaymentTime());
}
} catch (AlipayApiException e) {
return ResponseEntity.ok().body(new ApiResponse(paymentRequestId, payment.getUserId(), e));
}
return ResponseEntity.ok().body(new ApiResponse(paymentRequestId, payment.getUserId(), amsPayResponse));
}
The following code shows a sample of the request message:
{
"order": {
"orderAmount": {
"currency": "HKD",
"value": 100
},
"referenceOrderId": "ORD_2024040512345",
"orderDescription": "iPhone 15 Pro Monthly Subscription",
"goods": [
{
"referenceGoodsId": "G_001",
"goodsName": "iPhone 15 Pro",
"quantity": 1,
"price": {
"currency": "HKD",
"value": 100
}
}
],
"shipping": {
"shippingAddress": {
"region": "US",
"state": "CA",
"city": "San Francisco",
"address1": "123 Tech Street",
"address2": "Apt 4B",
"zipCode": "94107"
}
},
"buyer": {
"referenceBuyerId": "BUYER_88118999",
"buyerName": {
"firstName": "John",
"lastName": "Doe"
},
"buyerPhoneNo": "66622222",
"buyerEmail": "john.doe@example.com"
}
},
"env": {
"osType": "IOS",
"terminalType": "APP",
"deviceId": "deviceIdxxxxxxx",
"clientIp": "192.168.111.111",
"deviceTokenId": "deviceTokenIdxxxxxxxx"
},
"paymentMethod": {
"paymentMethodType": "ALIPAY_HK",
"paymentMethodId":"xxxxxxxx",
"paymentMethodMetadata":{
"recurringType":"SCHEDULED" //Required when the user is not involved; not required in user-involved scenarios.
}
},
"settlementStrategy": {
"settlementCurrency": "USD"
},
"paymentNotifyUrl": "https://kademo.intlalipay.cn/payments/notifySuccess",
"paymentRedirectUrl": "https://kademo.intlalipay.cn/melitigo/Test_114.html",
"paymentRequestId": "PAY_20251110143957765",
"productCode": "AGREEMENT_PAYMENT"
}
The following code shows a sample of the response message:
{
"actualPaymentAmount": {
"currency": "HKD",
"value": "100"
},
"paymentAmount": {
"currency": "HKD",
"value": "100"
},
"paymentCreateTime": "2025-11-05T23:03:16-08:00",
"paymentId": "202511061940108001001888********",
"paymentRequestId": "PAYMENT_20251106150314003_AUTO",
"paymentTime": "2025-11-05T23:03:18-08:00",
"pspCustomerInfo": {
"pspCustomerId": "208812211210000",
"pspName": "ALIPAY_HK"
},
"result": {
"resultCode": "SUCCESS",
"resultMessage": "success.",
"resultStatus": "S"
}
}
Please take the next step according to the value of the result.resultStatus.
Note: If you do not receive a response, it may be due to a network timeout. In this case, keep the paymentRequestId unchanged and call the API again to resolve the issue. If the problem persists, please contact Antom technical support.
Common questions
Q: Can I use Chinese characters in the value of the request parameters?
A: To avoid incompatibility of a certain payment method, do not use Chinese characters for parameters in the request.
Q: How to set the URL to receive the payment notification?
A: Specify paymentNotifyUrl in the pay (Tokenized Payment) API to receive the asynchronous notification about the payment result (notifyPayment), or configure the receiving URL in Antom Dashboard. If the URL is specified in both the request and Antom Dashboard, the value specified in the request takes precedence.
Q: What is the difference between paymentAmount and orderAmount?
A: paymentAmount refers to the payment amount, while orderAmount refers to the order amount. The actual charged amount is determined by paymentAmount.
Step 2: Obtain the payment continuation URL
After obtaining the payment continuation URL returned by Antom, the merchant server passes the address to the front end. The merchant front end redirects to the payment method page.
The following is the sample codes for loading payment continuation URL on the merchant front end:
if (URL != null) {
window.open(URL, '_blank');
}
window.location.href = URL;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10.0) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:Url] options:@{} completionHandler:nil];
}else{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:Url]];
}
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(URL));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// use the startActivity function to redirect to the wallet app
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
The figure below shows a sample rendering of the payment method checkout page:
In some scenarios, risk control may require the buyer to complete OTP verification.
Step 3: Obtain the authorization and payment result
Return to the payment result page
After the buyer completes the payment, they will be redirected back to the paymentRedirectUrl specified in the pay (Tokenized Payment) API. Note: Redirection may not occur in certain scenarios. For example, it may fail due to buyer actions or network issues.
Obtain the authorization and payment result
The first payment returns both the authorization result and the payment result, while subsequent payments return only the payment result. Common scenarios are as follows:
When the authorization is successful during the first payment, Antom sends you the asynchronous notification through the notifyAuthorization API. - Follow the steps below to set the notification webhook URL:
Log in to Antom Dashboard > Developer > Notification URL. Add the notification URL to alipay.ams.authorizations.notify. Refer to Notification URL for detailed steps. The following code shows a sample of the asynchronous authorization notification request:
{
"accessToken": "28288803001319861727421828000Cv96OFlYoi17100****",
"accessTokenExpiryTime": 2145916817000,
"authState": "36a38e87-0453-495e-ad17-b46553b918da",
"authorizationNotifyType": "TOKEN_CREATED",
"userLoginId": "852-91****67",
"result": {
"resultCode": "SUCCESS",
"resultMessage": "success.",
"resultStatus": "S"
}
}
Based on the value of result.resultStatus in the authorization result notification request (onlyS
is returned), process as follows: S
: Indicates successful authorization, and the following parameters are returned:The table below shows the token validity period for each payment method:
- The result notification sent by Antom is signed by Antom, it is recommended that you verify the signature to confirm that the notification was sent by Antom. Refer to the following code example to verify the notification:
@PostMapping("/receiveAuthNotify")
@ResponseBody
public Result receiveAuthNotify(HttpServletRequest request, @RequestBody String notifyBody) {
// retrieve the required parameters from http request
String requestUri = request.getRequestURI();
String requestMethod = request.getMethod();
// retrieve the required parameters from request header
String requestTime = request.getHeader("request-time");
String clientId = request.getHeader("client-id");
String signature = request.getHeader("signature");
try {
// verify the signature of notification
boolean verifyResult = WebhookTool.checkSignature(requestUri, requestMethod, clientId,
requestTime, signature, notifyBody, ANTOM_PUBLIC_KEY);
if (!verifyResult) {
throw new RuntimeException("Invalid notify signature");
}
// deserialize the notification body
AlipayAuthNotify authNotify = JSON.parseObject(notifyBody,AlipayAuthNotify.class);
if (authNotify != null && "SUCCESS".equals(authNotify.getResult().getResultCode())
&& "TOKEN_CREATED".equals(authNotify.getAuthorizationNotifyType())) {
// save the mapping between the buyer’s PaymentMethodType and accessToken
PaymentVO payment = authStatePayment.get(authNotify.getAuthState());
User user = users.get(payment.getUserId());
user.getPaymentMethodTypeAccessToken().put(payment.getPaymentMethodType(), authNotify.getAccessToken());
return Result.builder().resultCode("SUCCESS").resultMessage("success.").resultStatus(ResultStatusType.S).build();
}
// other types of notifications
} catch (Exception e) {
return Result.builder().resultCode("FAIL").resultMessage("fail.").resultStatus(ResultStatusType.F).build();
}
return Result.builder().resultCode("SYSTEM_ERROR").resultMessage("system error.").resultStatus(ResultStatusType.F).build();
}
- After the first or subsequent payment is completed, you can obtain the payment result through one of the following methods:
{
"result": {
"resultCode": "SUCCESS",
"resultStatus": "S",
"resultMessage": "success"
}
}
After the first or subsequent payment is completed, you can obtain the payment result through one of the following methods:
- Receive the asynchronous notification: Receive the payment result delivered by the Antom server.
- Inquire about the result: Call inquiryPayment API to query payment status.
- Follow the steps below to set the notification webhook URL:
When the payment reaches a final status of success or failure, Antom sends an asynchronous notification to your configured webhook URL. You can choose one of the following two methods to set up the Webhook URL for receiving notifications:
- Order-level notification configuration: Specify an independent notification URL for each order through the paymentNotifyUrl parameter in the pay (Tokenized Payment) API request.
- Merchant-level notification configuration: Log in to Antom Dashboard > Developer > Notification URL and add a notification URL for the alipay.ams.payments.payNotify API. For specific operations, refer to Notification URL.
Note: If you have configured notification URLs through both methods mentioned above, the API settings will take precedence.
The following code shows a sample of the notification request:
{
"actualPaymentAmount": {
"currency": "HKD",
"value": "98080"
},
"customsDeclarationAmount": {},
"notifyType": "PAYMENT_RESULT",
"paymentAmount": {
"currency": "HKD",
"value": "98080"
},
"paymentCreateTime": "2024-09-27T00:23:36-07:00",
"paymentId": "202409271940108001001881E0211235544",
"paymentRequestId": "bc93d19e-e1f6-4b68-b6b1-3d6ddc2a792a",
"paymentTime": "2024-09-27T00:23:46-07:00",
"pspCustomerInfo": {
"pspCustomerId": "20881221121****",
"pspName": "ALIPAY_HK"
},
"result": {
"resultCode": "SUCCESS",
"resultMessage": "success.",
"resultStatus": "S"
}
}
The table below displays the possible values returned in the resultStatus parameter of the response. Please follow the corresponding instructions for handling:
- The result notification sent by Antom is signed by Antom, it is recommended that you verify the signature to confirm that the notification was sent by Antom. Refer to the following code example to verify the notification:
/**
* receive notify
*
* @param request request
* @param notifyBody notify body
* @return Result
*/
@PostMapping("/receiveNotify")
@ResponseBody
public Result receiveNotify(HttpServletRequest request, @RequestBody String notifyBody) {
// retrieve the required parameters from http request
String requestUri = request.getRequestURI();
String requestMethod = request.getMethod();
// retrieve the required parameters from request header
String requestTime = request.getHeader("request-time");
String clientId = request.getHeader("client-id");
String signature = request.getHeader("signature");
try {
// verify the signature of notification
boolean verifyResult = WebhookTool.checkSignature(requestUri, requestMethod, clientId,
requestTime, signature, notifyBody, ANTOM_PUBLIC_KEY);
if (!verifyResult) {
throw new RuntimeException("Invalid notify signature");
}
// deserialize the notification body
JSONObject jsonObject = JSON.parseObject(notifyBody);
String notifyType = (String)jsonObject.get("notifyType");
if("PAYMENT_RESULT".equals(notifyType)){
AlipayPayResultNotify paymentNotify = jsonObject.toJavaObject(AlipayPayResultNotify.class);
if (paymentNotify != null && "SUCCESS".equals(paymentNotify.getResult().getResultCode())) {
// handle your own business logic.
// e.g. The relationship between payment information and buyers is kept in the database.
System.out.println("receive payment notify: " + JSON.toJSONString(paymentNotify));
return Result.builder().resultCode("SUCCESS").resultMessage("success.").resultStatus(ResultStatusType.S).build();
}
}
// other types of notifications
} catch (Exception e) {
// handle error condition
return Result.builder().resultCode("FAIL").resultMessage("fail.").resultStatus(ResultStatusType.F).build();
}
return Result.builder().resultCode("SYSTEM_ERROR").resultMessage("system error.").resultStatus(ResultStatusType.F).build();
}
- After receiving the notification, you are not required to sign the response, but must reply to every notification request in the following standardized format, regardless of whether the payment was successful or not.
{
"result": {
"resultCode": "SUCCESS",
"resultStatus": "S",
"resultMessage": "success"
}
}
Call the inquiryPayment API to initiate a query on the payment result. The payment status can be checked via polling or scheduled tasks after the payment is initiated. public static void inquiryPayment() {
AlipayPayQueryRequest alipayPayQueryRequest = new AlipayPayQueryRequest();
// replace with your paymentRequestId
alipayPayQueryRequest.setPaymentRequestId("yourPaymentRequestId");
AlipayPayQueryResponse alipayPayQueryResponse = null;
try {
alipayPayQueryResponse = CLIENT.execute(alipayPayQueryRequest);
} catch (AlipayApiException e) {
String errorMsg = e.getMessage();
// handle error condition
}
}
The following code shows a sample of the request message:
{
"paymentRequestId": "bc93d19e-e1f6-4b68-b6b1-3d6ddc2a****"
}
The following code shows a sample of the response message:
{
"actualPaymentAmount": {
"currency": "USD",
"value": "1"
},
"customsDeclarationAmount": {
"currency": "CNY",
"value": "7"
},
"paymentAmount": {
"currency": "USD",
"value": "1"
},
"paymentId": "20250305194010800100188690281017336",
"paymentMethodType": "ALIPAY_CN",
"paymentRedirectUrl": "https://checkout.antom.com/checkout-page/pages/payment/index.html?sessionData=%2BCUim8L0KviXagaygm9xBL5jZ%2F75w6gAX1nn8pcuFuGkIsMoHtD6U88YSyMrMJvorbwnBg5uQv8e6pyvIpjDQQ%3D%3D%26%26SG%26%26188%26%26eyJleHRlbmRJbmZvIjoie1wiT1BFTl9NVUxUSV9QQVlNRU5UX0FCSUxJVFlcIjpcInRydWVcIixcImxvY2FsZVwiOlwiZW5fVVNcIixcImRpc3BsYXlBbnRvbUxvZ29cIjpcInRydWVcIn0iLCJwYXltZW50U2Vzc2lvbkNvbmZpZyI6eyJwYXltZW50TWV0aG9kQ2F0ZWdvcnlUeXBlIjoiQUxMIiwicHJvZHVjdFNjZW5lIjoiQ0hFQ0tPVVRfUEFZTUVOVCIsInByb2R1Y3RTY2VuZVZlcnNpb24iOiIxLjAifSwic2VjdXJpdHlDb25maWciOnsiYXBwSWQiOiIiLCJhcHBOYW1lIjoiT25lQWNjb3VudCIsImJpelRva2VuIjoiNlRjZGJyMnJGM3JQWXg0aGtWckhxYnZqIiwiZ2F0ZXdheSI6Imh0dHBzOi8vaW1ncy1zZWEuYWxpcGF5LmNvbS9tZ3cuaHRtIiwiaDVnYXRld2F5IjoiaHR0cHM6Ly9vcGVuLXNlYS1nbG9iYWwuYWxpcGF5LmNvbS9hcGkvb3Blbi9yaXNrX2NsaWVudCIsIndvcmtTcGFjZUlkIjoiIn0sInNraXBSZW5kZXJQYXltZW50TWV0aG9kIjpmYWxzZX0%3D",
"paymentRequestId": "PAYMENT_20250305220039086_AUTO",
"paymentResultCode": "SUCCESS",
"paymentResultMessage": "success.",
"paymentStatus": "SUCCESS",
"paymentTime": "2025-03-05T06:02:34-08:00",
"pspCustomerInfo": {
"pspName": "ALIPAY_CN"
},
"result": {
"resultCode": "SUCCESS",
"resultMessage": "success.",
"resultStatus": "S"
}
}
Please process the response based on the value of the paymentStatus parameter. For specific return values, refer to the inquiryPayment API documentation. Common questions
Q: When is the payment notification sent?
A: It depends on whether the payment is completed: If the payment is successfully completed, Antom usually sends an asynchronous notification within 3 to 5 seconds.
Q: Is there an asynchronous notification sent for authorization failure?
A: No. An asynchronous notification will only be sent if the authorization is successful; there will be no notification returned for authorization failure.
Q: Will the asynchronous notification be re-sent?
A: Yes, the asynchronous notification will be re-sent automatically within 24 hours for the following cases:
- If you didn't receive the asynchronous notification due to network reasons.
- If you receive an asynchronous notification from Antom, but you didn't make a response to the notification in the sample code format of Process the notification.
The notification can be resent up to 8 times or until a correct response is received to terminate delivery. The sending intervals are as follows: 0 minutes, 2 minutes, 10 minutes, 10 minutes, 1 hour, 2 hours, 6 hours, and 15 hours.
Q: Are the authorization notification and payment result notification sent separately, and will the authorization notification always be received before the payment result notification?
A: Due to the uncontrollable nature of network stability, it is possible for the authorization notification to arrive later than the payment result notification.
Q: Is authorization result inquiry supported?
A: Currently, calling an API to query the authorization result is not supported.
Q: When responding to an asynchronous notification, do I need to add a digital signature?
A: No. If you receive an asynchronous notification from Antom, you are required to return the response in the sample code format of Process the notification, but you do not need to countersign the response. After authorization is completed, the buyer can revoke the authorization from either the merchant side or the payment method side. Once revoked, the original authorization token immediately becomes invalid and cannot be used for payments again. For details, refer to Revoke. You can cancel orders through the cancel API within the time window (by D+1 day 00:15 GMT+8). For details, refer to Cancel. Refer to Refund to learn about Antom refund rules and operation process. After a transaction is completed, use the provided Antom financial reports to perform reconciliation. For settlement rules and reconciliation operations, refer to Reconciliation. Antom provides you with the following best practice solutions. Refer to Best practices for more details. - Intelligent risk control service
- Order query after redirecting to the merchant result page
- Merchant-initiated transaction cancellation
- Payment failure retry