ここにディレクトリのタイトルを書きます
準備工程
「通常のマーチャントアカウント」(マーチャントプラットフォームにログイン後、ホームページ、トランザクションセンター、アカウントセンター、マーケティングセンター、製品センター、データセンターがあります)を使用し、製品センターでJSAPI支払いを開き、マーチャントを記録します番号。
- 次のように入力します: アカウント センター - API セキュリティ - API 証明書の申請 - 証明書の管理、プロンプトに従って証明書を追加し、追加後の証明書のシリアル番号を記録します。
- 「アカウント センター - API セキュリティ - APIv3 キーを設定し、変更し、プロンプトに従うと、キーが自動的に生成されます (最大長 32 文字の文字列)。キーを記録します。」
- 入力: アカウント センター - API セキュリティ - API キーを設定、変更、上記と同じ (このステップは使用されない場合があります)
- 「Product Center - AppID Account Management - Associate AppID」と入力し、関連付ける必要がある AppID (小規模プログラム、公式アカウントなど) を入力します。対応する AppID プラットフォームにログインし、協会を通じて申請します。ミニプログラム プラットフォームの操作パスは次のとおりです: 機能 - WeChat 支払い
さらに、準備する必要があるのは、
- アプレットの appId
- アプレットアプリの秘密
先に読んでください:
- 証明書と鍵に関するルール
- 署名の生成と検証
- コールバックパケットの復号化
- 公式SDKを直接使用することもできます
- JAVA(サンプルコードあり、プラットフォーム証明書の管理には「プラットフォーム証明書定期更新機能」の利用を推奨)販売者番号、証明書のシリアル番号、およびキーが SDK に提供されている限り、署名と検証の操作は自動的に完了できます。
正式なプロセス
ログイン
- フロントエンド:このインターフェイスを呼び出して承認を取得します。
- フロントエンド:成功後にこのインターフェイスを呼び出します。バックエンドによって提供される WeChat ログイン インターフェイスにパラメーター: コードを渡し、同時に前のインターフェイスによって取得された他の情報も提供します。
- バックエンド: WeChat ログインしてコードを受信した後、このインターフェイスを呼び出し、アプレットの appId と appSecret + 取得したコードを使用してユーザー情報 (openid Unionid session_key) の取得を要求し、保存します。取得に成功した後、 openidは登録されていますので、未登録の場合はパスワードなしでログインしてください。
支払う
- フロントエンド: 注文リクエストをバックエンド インターフェイスに送信し、注文 UUID を送信します。
- 後部:
- このインターフェイスを呼び出し、フロントエンドによって送信された UUID として販売者の注文番号を設定し、注文リクエストを実行して、prepay_id を取得します。
- この prepay_id を使用して、ステップ 3 で必要なリクエスト パラメーターを生成し、フロント エンドに返します。このステップには手動署名がここから署名ツールをダウンロードして署名結果が正しいかどうかを比較できます。
- フロントエンド:
- このインターフェイスを呼び出し、wx.requestPayment(OBJECT) を呼び出して WeChat 支払いを開始します。リクエスト パラメーターはバックエンドによって生成され、前のリクエストでフロントエンドに返される必要があります。
- ユーザーはプロンプトに従って支払いを行います(このシステムのプロセスではありません)。
- バックエンド: WeChat サーバーは、ステップ 2 の Notice_url フィールドで指定されたインターフェイスに支払い結果を送信します。このインターフェイスで完了するステップは次 のとおりです。
Javaコードの一部
AppIdなどの設定情報(実行設定ファイルに設定)
@Repository
@ConfigurationProperties(prefix = "wx")
@Data
public class WxConfig {
String appId;
String appSecret;
/**
* 商户号Id
*/
String mchId;
/**
* 证书序列号
*/
String serialNo;
/**
* apiV3 私钥
*/
String apiV3Key;
/**
* api私钥
*/
String apiKey;
/**
* 认证类型
*/
String authType;
/**
* 商户私钥文件路径
*/
String privateKeyPath;
}
署名と署名検証ツール
public class SignUtils {
public static String buildMessage(String... message) {
return String.join("\n", message) + "\n";
}
public static String sign(PrivateKey privateKey, String message)
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
return sign(privateKey, message.getBytes(StandardCharsets.UTF_8));
}
public static String sign(PrivateKey privateKey, byte[] message)
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
public static String sign(PrivateKey privateKey, String... message)
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
return sign(privateKey, buildMessage(message));
}
public static boolean check(X509Certificate certificate, String timestamp, String nonce, Object requestBody, String signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
//构造验签名串
String signatureStr = buildMessage(timestamp, nonce, JSONObject.toJSONString(requestBody));
System.out.println("signatureStr = " + signatureStr);
// 加载SHA256withRSA签名器
Signature signer = Signature.getInstance("SHA256withRSA");
// 用微信平台公钥对签名器进行初始化(调上一节中的获取平台证书方法)
signer.initVerify(certificate);
// 把我们构造的验签名串更新到签名器中
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return signer.verify(Base64Utils.decodeFromString(signature));
}
public static boolean check(X509Certificate certificate, HttpServletRequest httpServletRequest, Object requestBody)
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
String signature = httpServletRequest.getHeader("Wechatpay-Signature");
String timestamp = httpServletRequest.getHeader("Wechatpay-Timestamp");
String nonce = httpServletRequest.getHeader("Wechatpay-Nonce");
return check(certificate, timestamp, nonce, requestBody, signature);
}
}
メッセージ復号化ツール
public class AesDecrypt {
static final int KEY_LENGTH_BYTE = 32;
static final int TAG_LENGTH_BIT = 128;
private final byte[] apiV3Key;
public AesDecrypt(byte[] apiV3Key) {
if (apiV3Key.length != KEY_LENGTH_BYTE) {
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
}
this.apiV3Key = apiV3Key;
}
public AesDecrypt(String apiV3Key) {
this(apiV3Key.getBytes());
}
public String decryptToString(String associatedData, String nonce, String ciphertext)
throws GeneralSecurityException {
return decryptToString(
associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext
);
}
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
throws GeneralSecurityException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(apiV3Key, "AES");
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
}
いくつかのユーティリティ Bean
@Configuration
@Slf4j
public class WxConfiguration {
@Bean
public PrivateKey merchantPrivateKey(WxConfig wxConfig) throws FileNotFoundException {
log.info("加载私钥");
File file = new File(wxConfig.getPrivateKeyPath());
System.out.println("file.exists() = " + file.exists());
return PemUtil.loadPrivateKey(new FileInputStream(wxConfig.getPrivateKeyPath()));
}
@Bean
public ScheduledUpdateCertificatesVerifier verifier(WxConfig wxConfig, PrivateKey merchantPrivateKey) {
log.info("加载验证器");
return new ScheduledUpdateCertificatesVerifier(
new WechatPay2Credentials(wxConfig.getMchId(), new PrivateKeySigner(wxConfig.getSerialNo(), merchantPrivateKey)),
wxConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
}
@Bean
public AesDecrypt aesDecrypt(WxConfig wxConfig) {
log.info("加载解密器");
return new AesDecrypt(wxConfig.getApiV3Key());
}
}