在通用交易系統中,支付流程如下
1、服務端-預下單:生成參數與簽名信息(此過程不需要與抖音平臺對接)
參考?生成下單參數與簽名_抖音開放平臺
2、小程序用戶端:根據返回的參數與簽名,拉起抖音支付(tt.requestOrder、tt.getOrderPayment)??
參考??tt.requestOrder_抖音開放平臺
3、服務端:接收抖音回調、或主動查詢訂單(此過程需要小程序正式發布)
1、生成應用公私鑰
抖音的密鑰分為:應用、平臺兩種類型,注意區分。
進入抖音簽名工具平臺
生成好后,需要將公鑰配置到抖音的控制臺中。
每次修改應用公鑰,都會變更keyVersion 值,后續簽名過程需要匹配該值。注意
2、生成參數與簽名
注意查看 官網文檔中對 [生成下單參數與簽名]? 必傳參數的要求。
抖音非必傳參數的兩種要求:
1)創建對象中,不允許出現該參數
2)該參數需要給定默認值,不能為 null?
待簽名對象
import java.io.Serializable;
import java.util.List;/**** 抖音下單,簽名對象* @author xuancg* @date 2025/6/6*/
public class DyOrderSignData implements Serializable {/**目前只支持傳入一項*/private List<Sku> skuList;private String outOrderNo;/*** 訂單總金額* 單位:分*/private Integer totalAmount;/**支付超時時間 秒、非必傳*/private Integer payExpireSeconds = 300;private String payNotifyUrl;/**開發者自定義收款商戶號、非必傳*///private String merchantUid;private GoodsPage orderEntrySchema;/*** 屏蔽的支付方式,當開發者沒有進件某個支付渠道,可在下單時屏蔽對應的支付方式。* 如:[1, 2]表示屏蔽微信和支付寶*/private Integer[] limitPayWayList = new Integer[0];public static class Sku implements Serializable{private String skuId;private Integer price;private Integer quantity;/**商品標題,長度 <= 256字節 */private String title;private List<String> imageList;/**商品類型,詳見此處的商品類型枚舉值*/private Integer type;/**交易規則標簽組*/private String tagGroupId;/**非必傳*///private IndustryOrderGoodsPage entrySchema;/**非必傳*/private String skuAttr;}/*** 商品詳情頁*/public static class GoodsPage {/*** 訂單詳情頁跳轉路徑,沒有前導的“/”,長度 <= 512byte*/private String path;/*** 訂單詳情頁路徑參數,自定義的 json 結構,* 序列化成字符串存入該字段,平臺不限制* 但是寫入的內容需要能夠保證生成訪問訂單詳情的schema能正確跳轉到小程序內部的訂單詳情頁* 長度 <= 512byte* 選填*/// private String params;}}
?
簽名工具類
public class DecryptUtils {public static String getByteAuthorization(String privateKeyStr, String data, String appId, String nonceStr, long timestamp, String keyVersion) {String byteAuthorization = "";try {// 生成簽名String signature = getSignature(privateKeyStr, "POST", "/requestOrder", timestamp, nonceStr, data);System.out.println(signature);// 構造byteAuthorizationStringBuilder sb = new StringBuilder();sb.append("SHA256-RSA2048 ").append("appid=").append(appId).append(",").append("nonce_str=").append(nonceStr).append(",").append("timestamp=").append(timestamp).append(",").append("key_version=").append(keyVersion).append(",").append("signature=").append(signature).append("");byteAuthorization = sb.toString();} catch (Exception ex) {ex.printStackTrace();return "";}return byteAuthorization;}public static String getSignature(String privateKeyStr, String method, String uri, long timestamp, String nonce, String data) throws Exception {data = data.replace("\\", "");System.out.println(data);String rawStr = method + "\n" +uri + "\n" +timestamp + "\n" +nonce + "\n" +data + "\n";privateKeyStr = privateKeyStr.replace("\n","");System.out.println(rawStr);System.out.println(privateKeyStr);Signature sign = Signature.getInstance("SHA256withRSA");sign.initSign(string2PrivateKey(privateKeyStr));sign.update(rawStr.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(sign.sign());}public static PrivateKey string2PrivateKey(String privateKeyStr) {PrivateKey prvKey = null;try {byte[] privateBytes = Base64.getDecoder().decode(privateKeyStr);PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateBytes);KeyFactory keyFactory = KeyFactory.getInstance("RSA");prvKey = keyFactory.generatePrivate(keySpec);} catch (Exception ex) {ex.printStackTrace();}return prvKey;}}
調用簽名方法
/*** 待簽名串一共有五行(且必須嚴格按照以下順序),每一行必須以 \n(換行符,ASCII 編碼值為 0x0A)結束。* POST\n/requestOrder\n請求時間戳\n請求隨機串\ndata\n*/public String createByteSign(DyOrderSignData request){// 請求時間戳long timestamp = System.currentTimeMillis()/1000L;// 隨機字符串String nonceStr = UUID.randomUUID().toString();String data = JSONUtil.toJsonStr(request);// 應用公鑰版本,每次重新上傳公鑰后需要更新,可通過「開發管理-開發設置-密鑰設置」處獲取String keyVersion = "3";String appId = "";String appPrivateContent = "";return DecryptUtils.getByteAuthorization(appPrivateContent,data, appId, nonceStr, timestamp, keyVersion);}
獲取應用私鑰內容
/***
* classpath:app/rsa_private_key.pem
*/
public String loadKeyContent(String configPath) {String path = configPath;if (configPath.startsWith("classpath:")) {path = configPath.replaceFirst("classpath:", "");}if (!path.startsWith("/")) {path = "/" + path;}try {ClassPathResource classPathResource = new ClassPathResource(path);String content = FileUtils.readFileToString(classPathResource.getFile(), StandardCharsets.UTF_8);String[] repls = {"-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----", "\n", "\r"};for (String repl : repls) {content = content.replace(repl, "");}return content;} catch (Exception e) {e.printStackTrace();}return null;}
?
返回前端內容
DyOrderSignData request
1、簽名內容:String byteSign = createByteSign(request);
2、原始對象字符串(不建議傳原始對象,官網的示例不合理):String objStr = JSONUtil.toJsonStr(request);
?3、問題問題
先通過抖音簽名工具,驗證生成的簽名內容是否一致。
簽名不一致
1、檢查是否使用應用私鑰進行簽名
2、私鑰字符串內容不能用換行符、頭部、尾部申明、其他隱藏符號等。
3、轉化的參數對象是否一致。即在第五行,傳入的JSON對象,可以在代碼中打印內容。然后粘貼到簽名工具中進行驗證。
前端簽名失敗
1、如果是參數缺少、格式錯誤:請添加參數默認值,或者調整參數格式
2、下單errNo:11084 下單errMsg:requestOrder:fail 簽名校驗異常
由后端返回下單的參數對象 字符串,不能由前端將對象進行json轉化。
如果在上一步簽名不一致問題解決后,仍然出現簽名校驗異常
1)檢查控制臺的應用公鑰與私鑰是否匹配
2)檢查keyVersion版本是否匹配
3)檢查后端的下單對象封裝JSON格式與返回前端字符串的JSON格式是否一致。
4)JSON對象中,不能有轉義符\\ 、換行符 等特殊符號
沒有收到抖音回調
小程序沒有發布上線
?