Payment platform | Key features | Applicable regions and scenarios |
Stripe Connect | Supports platforms and connected accounts Dynamic split payments, subscription profit sharing Global coverage (135+ currencies supported) | Digital marketplace, SaaS, multi-merchant e-commerce |
PayPal Commerce Platform | Adaptive payments Chained payments and delayed settlement Supports PayPal, Venmo, credit cards | Cross-border e-commerce, multi-supplier platform |
Adyen for Platforms | Sub-merchant accounts Aggregation of localized payment methods Funds routing rule engine | Multinational enterprises, hybrid online-offline scenarios |
Braintree Marketplace | Deep integration with PayPal ecosystem (supports Venmo split payments) Merchant of record Automated tax calculations (integrated with Avalara) | U.S. domestic market, social e-commerce |
Rapyd Global Payments Platform | Aggregation of local payment methods Wallet accounts Multi-tier split payments (supports platform commission) | Emerging markets (Southeast Asia, Latin America, Africa) |
2C2P Marketplace Solution | Full coverage of local payment methods in Southeast Asia (GrabPay, DANA, etc.) Automatic generation of multi-merchant settlement reports Anti-fraud rules engine | E-commerce and game platforms in Southeast Asia |
POST /v3/pay/transactions/jsapi
\\
-H
"Authorization: ******* "
\\
-H
"Content-Type:application/json"
\\
-d
'{
"appid": "wxd678efh567hg6787",
"description": "
image Shop - Shenzhen Tengda - qq doll",
"out_trade_no": "1217752501201407033233368018",
"time_expire": "2018-06-08T10:34:56+08:00",
"attach": "custom args",
"
notify_url","https://xxx.xxx.xxx/payBackx",
"amount": {
"total": 100,
"currency": "USD"
},
"payer": {
"openid": "XXXXXXXX"
},
"detail": {
"cost_price": 608800,
"goods_detail": [
{
"merchant_goods_id": "1246464644",
"wechatpay_goods_id": "1001",
"goods_name": "iPhoneX 256G",
"quantity": 1,
"unit_price": 528800
}
]
}
}'
{"prepay_id": "pid_xsxssx12w3123fefe"}
timeStamp, nonceStr, package, signType, paySign
Field | Type | Description |
timestamp | String | Time (in seconds) |
nonceStr | String | Random string with a length of 10 characters |
package | String | Fixed format: prepay_id=${prepay_id} |
signType | String | Fixed type: RSA |
paySign | String | Calculated using the mini program ID, timeStamp, nonceStr, prepayId, and privateKey. Refer to the algorithm below. |
// how to create paySignconst generatePaySign = function (appId, timestamp, nonceStr, prepayId, privateKey) {const string2sign = `${appId}\\n${timestamp}\\n${nonceStr}\\n${prepayId}`;const sign = crypto.createSign('RSA-SHA256');sign.update(string2sign);const sBytes = sign.sign(privateKey, 'base64');const paySign = Buffer.from(sBytes, 'base64').toString('base64');return paySign;}
const response = {"timeStamp": "XXXXXX","nonceStr": "XXXXXXXX","package": "prepay_id=askdlfkadlsfkalsdjflakjsdf","signType": "RSA","paySign": "XXXXX",}
wx
.
requestPayment
(
{
timeStamp
,
nonceStr
,
package
,
signType
:
"RSA"
,
paySign
,
success
:
function
(
res
)
{
// Pay success
}
,
fail
:
function
(
err
)
{
// cancel or pay has error
}
}
)
;
POST /notify_callback
Content-Type: application/json; charset=utf-8Content-Length: 52Connection: keep-aliveKeep-Alive: timeout=8Cache-Control: no-cache, must-revalidateX-Content-Type-Options: nosniffRequest-ID: 08F5B8C2B506102C18FDDFEEA30620BE821E28EDC405-0Content-Language: zh-CNWechatpay-Nonce: d824f2e086d3c1df967785d13fcd22efWechatpay-Signature: mfI1CPqvBrgcXfgXMFjdNIhBf27ACE2YyeWsWV9ZI7T7RU0vHvbQpu9Z32ogzc+k8ZC5n3kz7h70eWKjgqNdKQF0eRp8mVKlmfzMLBVHbssB9jEZEDXThOX1XFqX7s7ymia1hoHQxQagPGzkdWxtlZPZ4ZPvr1RiqkgAu6Is8MZgXXrRoBKqjmSdrP1N7uxzJ/cjfSiis9FiLjuADoqmQ1P7p2N876YPAol7Rn0+GswwAwxldbdLrmVSjfytfSBJFqTMHn4itojgxSWWN1byuckQt8hSTEv/Lg97QoeGniYP17T80pJeQyL3b+295FPHSO2AtvCgyIbKMZ0BALilAA==Wechatpay-Timestamp: 1722850421Wechatpay-Serial: PUB_KEY_ID_0000000000000024101100397200000006Wechatpay-Signature-Type: WECHATPAY2-SHA256-RSA2048{"id": "EV-2018022511223320873","create_time": "2015-05-20T13:29:35+08:00","resource_type": "encrypt-resource","event_type": "TRANSACTION.SUCCESS","summary": "pay success","resource": {"original_type": "transaction","algorithm": "AEAD_AES_256_GCM","ciphertext": "*************","associated_data": "","nonce": ""}}
Response timestamp\\nResponse random string\\nResponse body\\n
1722850421\\n2d824f2e086d3c1df967785d13fcd22ef\\nbodyContent\\n
$ cat pay_pub.pem2-----BEGIN PUBLIC KEY-----3MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4zej1cqugGQtVSY2Ah8RMCKcr2UpZ8Npo+5Ja9xpFPYkWHaF1Gjrn3d5kcwAFuHHcfdc3yxDYx6+9grvJnCA2zQzWjzVRa3BJ5LTMj6yqvhEmtvjO9D1xbFTA2m3kyjxlaIar/RYHZSslT4VmjIatW9KJCDKkwpM6x/RIWL8wwfFwgz2q3Zcrff1y72nB8p8P12ndH7GSLoY6d2Tv0OB2+We2Kyy2+QzfGXOmLp7UK/pFQjJjzhSf9jxaWJXYKIBxpGlddbRZj9PqvFPTiep8rvfKGNZF9Q6QaMYTpTp/uKQ3YvpDlyeQlYe4rRFauH3mOE6j56QlYQWivknDX9VrwIDAQAB4-----END PUBLIC KEY-----
$openssl base64 -d -A <<< \\ 'CtcbzwtQjN8rnOXItEBJ5aQFSnIXESeV28Pr2YEmf9wsDQ8Nx25ytW6FXBCAFdrr0mgqngX3AD9gNzjnNHzSGTPBSsaEkIfhPF4b8YRRTpny88tNLyprXA0GU5ID3DkZHpjFkX1hAp/D0fva2GKjGRLtvYbtUk/OLYqFuzbjt3yOBzJSKQqJsvbXILffgAmX4pKql+Ln+6UPvSCeKwznvtPaEx+9nMBmKu7Wpbqm/+2ksc0XwjD+xlvlECkCxfD/OJ4gN3IurE0fpjxIkvHDiinQmk51BI7zQD8k1znU7r/spPqB+vZjc5ep6DC5wZUpFu5vJ8MoNKjCu8wnzyCFdA==' > signature.txt
$ openssl dgst -sha256 -verify pay_pub.pem -signature signature.txt << EOF1554209980c5ac7061fccab6bf3e254dcf98995b8c{"data":[{"serial_no":"5157F09EFDC096DE15EBE81A47057A7232F1B8E1","effective_time":"2018-03-26T11:39:50+08:00","expire_time":"2023-03-25T11:39:50+08:00","encrypt_certificate":{"algorithm":"AEAD_AES_256_GCM","nonce":"d215b0511e9c","associated_data":"certificate","ciphertext":"..."}}]}EOF
from cryptography.hazmat.primitives.ciphers.aead import AESGCMimport base64def decrypt(nonce, ciphertext, associated_data):key = "Your32Apiv3Key"key_bytes = str.encode(key)nonce_bytes = str.encode(nonce)ad_bytes = str.encode(associated_data)data = base64.b64decode(ciphertext)aesgcm = AESGCM(key_bytes)return aesgcm.decrypt(nonce_bytes, data, ad_bytes)
{code : 200}
- (void)requestPayment:(TMFMiniAppInfo *)app params:(NSDictionary *)params completionHandler:(MACommonCallback)completionHandler {
[TUITool makeToast:@"Prepare to pay..."];
// Request the App backend based on the params parameters passed in by the mini program
[[TCMPPDemoPayManager sharedInstance] checkPreOrder:params completionHandler:^(NSError * _Nullable err, PayResponseData * _Nullable result) {
[TUITool hideToastActivity];
if (!err) {
// The request succeeded,and the payment native page pops up
dispatch_async(dispatch_get_main_queue(), ^{
TCMPPPaymentMethodsController *payMethodVC = [[TCMPPPaymentMethodsController alloc]init];
payMethodVC.payResponseData = result;
payMethodVC.app = app;
[[[TCMPPShareMiniAppModule sharedInstance] topViewController].navigationController pushViewController:payMethodVC animated:NO];
payMethodVC.completeHandle = ^(NSDictionary * _Nullable result, NSError * _Nullable error) {
// Complete payment and call back to the mini program
completionHandler(result,error);
};
});
} else {
// The request failed, payment was not completed, and the callback was sent to the mini program
NSDictionary *retDic = @{@"retmsg":err.localizedDescription};
if (result.returnCode && result.returnMessage) {
retDic = @{@"returnCode":result.returnCode,@"returnMessage":result.returnMessage};
}
completionHandler(retDic,err);
}
}];
}
@ProxyService(proxy = MiniOpenApiProxyV2.class)
public class MiniOpenApiProxyV2Impl extends MiniOpenApiProxyDefault {
private static final String TAG = "MiniOpenApiProxyV2";
@Override
public void requestPayment(IMiniAppContext miniAppContext, JSONObject params, AsyncResult result) {
PaymentManagerV2.g().startPayment(miniAppContext, params, result);
}
}
public class PaymentManagerV2 {
private static final String TAG = "PaymentManager";
private static PaymentManagerV2 instance;
private final PayApiV2 payApi = new PayApiV2();
private PaymentRequest paymentRequest;
public static PaymentManagerV2 g() {
if (instance == null) {
instance = new PaymentManagerV2();
}
return instance;
}
public void startPayment(IMiniAppContext miniAppContext, JSONObject params, AsyncResult result) {
//do params check
if (params.has("prepayId")) {
checkOrder(miniAppContext, params, result);
} else {
JSONObject failRet = new JSONObject();
try {
failRet.put("success", false);
} catch (JSONException e) {
}
result.onReceiveResult(false, failRet);
}
}
/**
* STEP 1: check order status
*
* @param miniAppContext
* @param params
* @param result
*/
private void checkOrder(IMiniAppContext miniAppContext, JSONObject params, AsyncResult result) {
String appId = miniAppContext.getMiniAppInfo().appId;
Activity activity = miniAppContext.getAttachedActivity();
payApi.checkOrder(appId, params, new PayApiV2.PayCallBack() {
@Override
public void onSuccess(JSONObject checkRet) {
String fee = checkRet.optString("actualAmount");
double paymentValue = Double.parseDouble(fee) / 10000;
showPayTypeList(activity, paymentValue, checkRet.toString());
paymentRequest = new PaymentRequest(miniAppContext, checkRet, result, paymentValue);
Log.e("TAG", "payment request " + this.hashCode());
}
@Override
public void onFailed(String errCode, String msg) {
Log.e(TAG, "pay failed " + msg);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(miniAppContext.getContext(), "failed", Toast.LENGTH_SHORT).show();
}
});
result.onReceiveResult(false, new JSONObject());
}
});
}
/**
* STEP 2: show pwd dialog
*
* @param activity
* @param money
*/
private void showPwdDialog(Activity activity, double money, ICustomPayCallback payCallback, int iconSrc, String desc) {
CustomPayDemo.CustomPayDialog customPayDialog = new CustomPayDemo.CustomPayDialog(activity, money, R.style.MyAlertDialog, iconSrc, desc);
customPayDialog.addPayResultListen(payCallback);
customPayDialog.show();
}
/**
* STEP 3: confirm pwd input and check pwd
*
* @param activity
* @param pwd
* @param data
* @param result
*/
private void checkPwdAndPay(Activity activity, String pwd, JSONObject data, AsyncResult result, String payModel, String payModelId) {
if (checkPassWord(pwd)) {
requestPayment(activity, data, result, payModel, payModelId);
} else {
JSONObject ret = new JSONObject();
try {
ret.put("errMsg", "bad pwd ");
} catch (JSONException e) {
}
result.onReceiveResult(false, ret);
activity.finish();
}
}
private boolean checkPassWord(String pwd) {
return "666666".equals(pwd);
}
/**
* STEP 4: request payment
*
* @param activity
* @param data
* @param asyncResult
*/
private void requestPayment(Activity activity, JSONObject data, AsyncResult asyncResult, String payModel, String paymdelId) {
payApi.payOrder(data, new PayApiV2.PayCallBack() {
@Override
public void onSuccess(JSONObject result) {wei
String totalFee = result.optString("paymentAmount");
showPayResult(true, activity, totalFee);
JSONObject resultToJs = result.optJSONObject("data");
asyncResult.onReceiveResult(true, resultToJs);
}
@Override
public void onFailed(String errCode, String msg) {
JSONObject ret = new JSONObject();
try {
ret.put("errMsg", msg);
ret.put("errCode", errCode);
} catch (JSONException e) {
}
asyncResult.onReceiveResult(false, ret);
activity.finish();
}
}, payModel, paymdelId);
}
private void showPayResult(boolean success, Activity activity, String total) {
Intent intent = new Intent(activity, PaymentResultActivity.class);
intent.putExtra("success", success);
intent.putExtra("total", total);
intent.putExtra("isV2", true);
activity.startActivity(intent);
activity.finish();
}
private void showPayTypeList(Activity activity, double total, String rawData) {
Intent intent = new Intent(activity, PaymentMethodActivity.class);
intent.putExtra("totalFee", total);
intent.putExtra("rawData", rawData);
activity.startActivity(intent);
}
public void showPwdConfirm(Activity activity, int iconSrc, String desc, String model, String modelId) {
if (null != paymentRequest) {
AsyncResult result = paymentRequest.result;
JSONObject checkRet = paymentRequest.params;
try {
checkRet.put("appId", paymentRequest.miniAppContext.getMiniAppInfo().appId);
} catch (JSONException | NullPointerException e) {
}
showPwdDialog(activity, paymentRequest.payValue, (retCode, msg, dialogInterface) -> {
if (retCode == 1) {
String pwd = ((CustomPayDemo.CustomPayDialog) dialogInterface).getInputText();
Log.e(TAG, "onDismiss isComplete=" + pwd);
if (!TextUtils.isEmpty(pwd)) {
checkPwdAndPay(activity, pwd, checkRet, result, model, modelId);
} else {
Log.e(TAG, "empty pwd ~");
JSONObject ret = new JSONObject();
try {
ret.put("errMsg", "empty pws");
} catch (JSONException e) {
}
result.onReceiveResult(false, ret);
activity.finish();
}
paymentRequest = null;
}
}, iconSrc, desc);
}
}
public void notifyPaymentCancel() {
if (null != paymentRequest) {
JSONObject ret = new JSONObject();
try {
ret.put("errMsg", "cancel");
} catch (JSONException e) {
}
paymentRequest.result.onReceiveResult(false, ret);
paymentRequest = null;
}
}
private static class PaymentRequest {
public IMiniAppContext miniAppContext;
public JSONObject params;
public AsyncResult result;
public double payValue = 0f;
public PaymentRequest(IMiniAppContext miniAppContext, JSONObject params, AsyncResult result, double payValue) {
this.miniAppContext = miniAppContext;
this.params = params;
this.result = result;
this.payValue = payValue;
}
}
}