Sale APM
Request address
POST
https://sandbox.j-pay.net/pay_index
Test account
Merchant ID:10172
ApiKey: prx5d81g2ytb1mmokl9mohjsmsb09do3
Request parameters
| Parameter Name | Type | Required | Sign(Y OR N) | Parameter Description |
|---|---|---|---|---|
| pay_memberid | String | Y | Y | The platform assigns merchant ID |
| pay_orderid | String | Y | Y | Merchant order number |
| pay_applydate | String | Y | Y | Order submission time, format:2016-12- 26 18:18:18 |
| pay_bankcode | String | Y | Y | Bank code, see table below |
| pay_notifyurl | String | Y | Y | Asynchronous notification address. (POST return data) |
| pay_callbackurl | String | Y | Y | Synchronization notification address (POST return data) |
| pay_amount | String | Y | Y | Amount of the transaction |
| pay_md5sign | String | Y | N | Please see MD5 signature field method |
| pay_currency | String | Y | N | Currency code, uppercase letters (USD) |
| pay_url | String | Y | N | Website URL(Subject to review), needs to include http(s):// |
| pay_type | String | Y | N | Fixed value:apm |
| pay_method | String | N | N | List of payment methods, If it is empty, the payment list page is returned. Such as: alipay |
| pay_app_scheme | String | N | N | If it is not empty, the app url scheme will be redirected first. Such as: myapp://pages/index/index |
+pay_productname | String | N | N | Product information parameters are composed of the following set of data in JSON format. Please note that parameter names are case-sensitive. This field is filled with a JSON array. Example:[{"sku":"123456789","productName":"Mac Book Pro","productImage":"url","attributes":"Size:US8 Color:blue","price":"11000.00","quantity":" 1"},{"sku":"9876543231","productName":"IPhone 12","productImage":"url","attributes":"Size:US8 Color:blue","price":"11000.00","quantity":" 1"}] |
| 「sku | String | N | N | Product number (unique) |
| 「productName | String | Y | N | Product name |
| 「productImage | String | N | N | Product image URL |
| 「attributes | String | N | N | Product attributes |
| 「price | String | Y | N | Product attributes |
| 「quantity | String | Y | N | Product quantity |
| pay_firstname | String | Y | N | Bill name |
| pay_lastname | String | Y | N | Billing last name |
| pay_street_address1 | String | N | N | Billing address 1 |
| pay_street_address2 | String | N | N | Billing address 2 |
| pay_city | String | N | N | Billing city |
| pay_postcode | String | N | N | Billing zip code |
| pay_state | String | N | N | Billing State/Province |
| pay_country_iso_code_2 | String | Y | N | Billing country |
| pay_email_address | String | Y | N | Billing email |
| pay_telephone | String | Y | N | Billing telephone |
| shipping_firstname | String | N | N | Shipping name |
| shipping_lastname | String | N | N | Shipping last name |
| shipping_street_address1 | String | N | N | Shipping address 1 |
| shipping_street_address2 | String | N | N | Shipping address 2 |
| shipping_city | String | N | N | Shipping city |
| shipping_state | String | N | N | Shipping State/Province |
| shipping_postcode | String | N | N | Shipping zip code |
| shipping_country_iso_code_2 | String | N | N | Shipping country |
| shipping_telephone | String | N | N | Shipping telephone |
| pay_ip | String | Y | N | User IP address |
| pay_useragent | String | Y | N | User browser information |
| pay_language | String | Y | N | Language |
| system | String | Y | N | Website system name(Shopyy,Shopline ) |
Return parameters
| Parameter Name | Type | Parameter Description |
|---|---|---|
| status | Int | status = 0:SUCCESS status = 1:3d payment status = 2:failed |
| msg | String | Order placed successfully or order failed |
| url | String | 3D redirectUrl |
Return example
Transaction successful : {
"status": 0,
"msg": "transaction success"
}
Transaction failed : {
"status": 2,
"msg": "transaction failed"
}
Redirect/3ds payment successful : {
"status": 1,
"url": "redirectUrl"
}
3DS verification
Jump payment and direct credit card payment require 3DS verification
Synchronous notification parameters:(application/x-www-form-urlencoded)
| Parameter Name | Type | Required(Y or N) | Parameter Description |
|---|---|---|---|
| paymentStatus | String | Y | Succeeded: success Fail:fail Processing:processing |
| orderID | String | Y | Merchant order number |
Notification example:
Synchronous notification
https://www.xxxx.com/callback.html?paymentStatus=succeeded&orderID=1001
Asynchronous notification parameters:(application/x-www-form-urlencoded)
| Parameter Name | Type | Required(Y or N) | Parameter Description |
|---|---|---|---|
| memberid | String | Y | Merchant ID |
| orderid | String | Y | Merchant order number |
| amount | String | Y | Submitted order amount |
| true_amount | String | Y | The amount actually paid by the buyer |
| currency | String | Y | Payment currency |
| transaction_id | String | Y | Transaction order number |
| returncode | String | Y | "00": success "2":fail |
| datetime | String | Y | Format:20220419000114 |
| sign | String | N | Please see the verification signature field format |
| attach | String |
Array{
["memberid"]=>
string(5) "10012"
["orderid"]=>
string(7) "7758292"
["transaction_id"]=>
string(20) "20230117104641499957"
["amount"]=>
string(5) "85.95"
["true_amount"]=>
string(5) "85.95"
["currency"]=>
string(5) "USD"
["datetime"]=>
string(14) "20230117111809"
["returncode"]=>
string(2) "00"
["sign"]=>
string(32) "48CF5D99A2ADE25DCEF73BC1B1136B9E"
["attach"]=>
string(0) ""
}
Demo
- Javascript
- Php
- Python
import { md5 } from "js-md5";
import axios from "axios";
const KEY = "prx5d81g2ytb1mmokl9mohjsmsb09do3";
const MEMBER_ID = "10172";
let query = {
pay_memberid: MEMBER_ID,
pay_orderid: "O" + Date.now().valueOf(),
pay_applydate: "2024-12-02 16:48:42",
pay_bankcode: "901",
pay_notifyurl: "https://www.google.com",
pay_callbackurl: "https://www.google.com",
pay_amount: "10.00",
};
let signData = [];
Object.keys(query)
.sort()
.forEach((key) => signData.push(`${key}=${query[key]}`));
signData.push(`key=${KEY}`);
Object.assign(query, {
pay_md5sign: md5(signData.join("&")).toUpperCase(),
pay_url: "https://www.google.com",
pay_currency: "USD",
pay_firstname: "Rodrigo",
pay_lastname: "lopez",
pay_country_iso_code_2: "CL",
pay_email_address: "[email protected]",
pay_telephone: "920508435",
pay_ip: "186.156.221.158",
pay_useragent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.43",
pay_language: "en",
system: "shopyy",
});
axios
.request({
url: "https://sandbox.j-pay.net/pay_index",
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
data: query,
})
.then(({ data }) => {
console.log("success", data);
})
.catch((error) => {
console.log("error", error);
});
$url = 'https://sandbox.j-pay.net/pay_index';
$data = [
'pay_memberid' => '10172',
'pay_orderid' => 'O' . date('YmdHis', time()),
'pay_applydate' => date('Y-m-d H:i:s', time()),
'pay_bankcode' => '901',
'pay_notifyurl' => 'https://www.google.com',
'pay_callbackurl' => 'https://www.google.com',
'pay_amount' => '10.00',
];
ksort($data);
$md5str = "";
foreach ($data as $key => $val) {
if (!empty($val)) {
$md5str = $md5str . $key . "=" . $val . "&";
}
}
$sign_str = $md5str . 'key=prx5d81g2ytb1mmokl9mohjsmsb09do3';
$sign = strtoupper(md5($sign_str));
$data = array_merge($data, [
'pay_md5sign' => $sign,
'pay_url' => 'https://www.google.com',
'pay_currency' => 'USD',
'pay_firstname' => 'Rodrigo',
'pay_lastname' => 'lopez',
'pay_country_iso_code_2' => 'CL',
'pay_email_address' => '[email protected]',
'pay_telephone' => '920508435',
'pay_ip' => '186.156.221.158',
'pay_useragent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.43',
'pay_language' => 'es',
'system' => 'shopyy'
]);
$header = [
'Content-Type: application/x-www-form-urlencoded',
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER , false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,false);
$data = curl_exec($ch);
if (curl_error($ch)) {
echo 'Curl error: ' . curl_error($ch);
}
curl_close($ch);
var_dump($data);
import hashlib
import requests
import time
import json
url = 'https://sandbox.j-pay.net/pay_index'
key = 'prx5d81g2ytb1mmokl9mohjsmsb09do3'
params = {
'pay_memberid': '10172',
'pay_orderid': 'O' + time.strftime('%Y%m%d%H%M%S', time.localtime()),
'pay_applydate': time.strftime('%Y%m%d%H%M%S', time.localtime()),
'pay_bankcode': '901',
'pay_notifyurl': 'http://www.google.com',
'pay_callbackurl': 'http://www.google.com',
'pay_amount': '10.00',
}
strParam = ''
for p in sorted(params):
strParam = strParam + str(p) + "=" + str(params[p]) + '&'
strParam = strParam + 'key' + '=' + key
parmStr = strParam.encode("utf-8")
m = hashlib.md5()
m.update(parmStr)
params['pay_md5sign'] = m.hexdigest().upper()
otherParms = {
"pay_url": "http://www.google.com",
"pay_currency": "USD",
"pay_firstname": "Rodrigo",
"pay_lastname": "lopez",
"pay_country_iso_code_2": "CL",
"pay_email_address": "[email protected]",
"pay_telephone": "920508435",
"pay_ip": "186.156.221.158",
"pay_useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.43",
"pay_language": "es",
"system": "shopyy"
}
res = requests.post(url = url, data = dict(params, **otherParms))
print('response:' + res.text)
App Demo
- Kotlin
- Swift
- Uniapp
New example of interface parameters added:
pay_app_scheme: jpayapm://payment/result
Demo download:
Add (or update) build dependencies
dependencies {
implementation "androidx.browser:browser:1.8.0"
}
JPayAPM.kt
package com.jpay.apm
import android.app.Activity
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.browser.customtabs.*
import org.json.JSONObject
import java.lang.ref.WeakReference
/**
* JPay APM
*/
class JPayAPM {
companion object {
const val PAY_STATUS_UNPAID = 0
const val PAY_STATUS_SUCCESS = 1
const val PAY_STATUS_FAILED = 2
const val PAY_STATUS_CANCEL = 3
private const val TAG = "JPayAPM"
}
private var mActivityRef: WeakReference<Activity>? = null
private var mContextRef: WeakReference<Context>? = null
private var payStatus: Int = PAY_STATUS_UNPAID
private var payCallback: WeakReference<((JSONObject) -> Unit)?> = WeakReference(null)
constructor(activity: Activity, context: Context) {
mActivityRef = WeakReference(activity)
mContextRef = WeakReference(context)
}
/**
* Execute payment
* @param payMethod Payment method (only support googlepay)
* @param payUrl Payment URL
* @param callback Result callback
*/
fun exec(payMethod: String, payUrl: String, callback: ((JSONObject) -> Unit)?) {
Log.d(TAG, "Execute payment: method=$payMethod, url=$payUrl")
payStatus = PAY_STATUS_UNPAID
payCallback = WeakReference(callback)
payByUrl(payUrl)
}
/**
* Handle payment result (main thread callback)
*/
private fun handlePayResult(status: Int, message: String, orderId: String = "") {
payStatus = status
Log.d(TAG, "Payment result: status=$status, msg=$message, orderId=$orderId")
// Build JSON result
val result = JSONObject().apply {
put("status", status)
put("message", message)
put("orderId", orderId)
}
Handler(Looper.getMainLooper()).post {
payCallback.get()?.invoke(result)
payCallback.clear()
}
}
/**
* Open payment URL
*/
private fun payByUrl(payUrl: String) {
val activity = mActivityRef?.get()
val context = mContextRef?.get()
if (context == null) {
handlePayResult(PAY_STATUS_FAILED, "Context is null", "")
return
}
if (activity == null || activity.isFinishing ||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed)) {
handlePayResult(PAY_STATUS_FAILED, "Activity is null or destroyed", "")
return
}
val uri: Uri
try {
uri = Uri.parse(payUrl)
if (uri.scheme.isNullOrEmpty() || uri.host.isNullOrEmpty()) {
handlePayResult(PAY_STATUS_FAILED, "Invalid URL format: $payUrl", "")
return
}
} catch (e: Exception) {
handlePayResult(PAY_STATUS_FAILED, "Failed to parse URL: ${e.message}", "")
return
}
try {
val intent = CustomTabsIntent.Builder()
.setShowTitle(true)
.build()
intent.launchUrl(activity, uri)
} catch (e: Exception) {
Log.e(TAG, "Failed to launch payment URL: $payUrl", e)
handlePayResult(PAY_STATUS_FAILED, "Failed to launch payment URL: ${e.message}", "")
}
}
/**
* Trigger payment result (for Scheme callback)
*/
fun triggerPayResult(status: Int, message: String, orderId: String = "") {
handlePayResult(status, message, orderId)
}
/**
* Release resources
*/
fun release() {
mActivityRef?.clear()
mContextRef?.clear()
payCallback.clear()
payStatus = PAY_STATUS_UNPAID
}
}
Solution example
import Foundation
import UIKit
import SafariServices
enum PaymentStatus: Int {
case unpaid = 0
case succeeded = 1
case failed = 2
}
typealias PaymentCallback = (Int, String, String) -> Void
extension UIViewController {
static func topViewController() -> UIViewController {
guard let windowScene = UIApplication.shared.connectedScenes
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene,
let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow }),
var topVC = keyWindow.rootViewController else {
return UIViewController()
}
while let presentedVC = topVC.presentedViewController {
topVC = presentedVC
}
return topVC
}
}
@objcMembers class JPayAPM: NSObject, SFSafariViewControllerDelegate {
private var payStatus: PaymentStatus = .unpaid
private var isCallbackCalled: Bool = false
private var paymentCallback: PaymentCallback? = nil
private var safariViewController: SFSafariViewController?
/// Execute payment request
/// - Parameters:
/// - payMethod: Payment method identifier
/// - url: Payment URL
/// - callback: Callback function
public func exec(_ payMethod: String, _ url: String, _ callback: @escaping ([String: Any]) -> Void) {
// print("Execute payment request: payMethod=\(payMethod), url=\(url)")
self.payStatus = .unpaid
self.isCallbackCalled = false
self.paymentCallback = { status, message, orderId in
let result: [String: Any] = [
"status": status,
"message": message,
"orderId": orderId
]
callback(result)
}
guard let paymentUrl = URL(string: url) else {
let errorMsg = "Invalid URL format: \(url)"
print(errorMsg)
let result: [String: Any] = [
"status": PaymentStatus.failed.rawValue,
"message": errorMsg,
"orderId": ""
]
callback(result)
return
}
DispatchQueue.main.async {
self.showPaymentBrowser(with: paymentUrl)
}
}
private func showPaymentBrowser(with url: URL) {
let configuration = SFSafariViewController.Configuration()
configuration.barCollapsingEnabled = true
let safariVC = SFSafariViewController(url: url, configuration: configuration)
safariVC.dismissButtonStyle = .done
safariVC.delegate = self
self.safariViewController = safariVC
let currentVC = UIViewController.topViewController()
currentVC.present(safariVC, animated: true) {
print("SafariViewController presented, URL: \(url.absoluteString)")
}
}
private func notifyPaymentResult(status: PaymentStatus, message: String, orderID: String) {
guard !self.isCallbackCalled else { return }
self.isCallbackCalled = true
self.payStatus = status
if let callback = self.paymentCallback {
callback(status.rawValue, message, orderID)
}
}
private func parsePaymentResult(from url: URL, controller: SFSafariViewController) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return }
let queryItems = components.queryItems ?? []
let paymentStatus = queryItems.first(where: { $0.name == "paymentStatus" })?.value?.lowercased()
let orderID = queryItems.first(where: { $0.name == "orderID" })?.value ?? ""
if let status = paymentStatus {
let resultStatus: PaymentStatus = status == "succeeded" ? .succeeded : .failed
let resultMsg = status == "succeeded" ? "Payment succeeded" : "Payment failed"
notifyPaymentResult(status: resultStatus, message: resultMsg, orderID: orderID)
controller.dismiss(animated: true) {
self.safariViewController = nil
}
}
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
print("SafariViewController dismissed")
notifyPaymentResult(status: .failed, message: "User closed the payment page", orderID: "")
self.safariViewController = nil
}
func safariViewController(_ controller: SFSafariViewController, initialLoadDidRedirectTo url: URL) {
print("SafariViewController redirect to: \(url.absoluteString)")
parsePaymentResult(from: url, controller: controller)
}
func safariViewController(_ controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
if didLoadSuccessfully {
print("SafariViewController initial load successfully")
} else {
print("SafariViewController initial load failed")
notifyPaymentResult(status: .failed, message: "Payment page load failed", orderID: "")
controller.dismiss(animated: true)
}
}
}
Extract to the uni_modules directory
Use
import { JPay } from '@/uni_modules/jpay-apm'
JPay({
payMethod: 'applepay', //Payment method
url: [The url returned from placing an order through the interface]
success: (res) => {
console.log('Payment successful', res)
},
fail: (res) => {
console.log('Payment failed', res.message)
},
complete: (res) => {
console.log('Payment complete', res)
}
})
App.vue
<script>
export default {
onShow: function() {
const schemeUrl = plus?.runtime?.arguments;
const params = this.parseSchemeParams(schemeUrl);
if (params?.orderID) {
console.log('You can jump to the result page');
}
},
methods: {
parseSchemeParams(schemeUrl) {
if (!schemeUrl) return {};
const paramStr = schemeUrl.split('?')[1] || '';
if (!paramStr) return {};
const params = {};
paramStr.split('&').forEach(item => {
const [key, value] = item.split('=');
if (key) {
params[key] = decodeURIComponent(value || '');
}
});
return params;
}
}
}
</script>