WeChat Pay SDK の導入環境構築
WeChat Pay の決済処理を実装する際、公式提供する Java SDK のバージョン管理に注意が必要です。Maven コーディネートリポジトリ上の公式パッケージと GitHub リポジトリ上の最新版では仕様が異なる場合があります。
特に重要な点は、拡張クラスであるWXPayConfigのアクセス修飾子です。古いバージョンではインターフェースとして定義されていましたが、新バージョンでは抽象クラスに変更されており、メソッドのデフォルトアクセス指定(package-private)が問題となることがあります。別パッケージから継承して使用する場合は、これを適切に公開する調整が必要となるため、JAR パッケージ内のソースを修正するか、ローカルリポジトリへ登録した上でプロジェクトに統合します。
Maven プロジェクトへの組み込み手法
外部ライブラリをプロジェクトに含めるには、主に 2 つのアプローチがあります。
- ローカル JAR のインストール: Maven コマンドを用いてローカル倉庫に配置し、通常通り依存関係を宣言します。
- システムスコープの利用: プロジェクト直下に libs フォルダを作成し、jar ファイルを格納。pom.xml において scope を system に設定し、maven-war-plugin 等のプラグインでコンパイル時のビルドパスおよび最終アーカイブへの展開を制御します。
<!-- 例:システムスコープでの依存設定 -->
<dependency>
<groupId>com.wechat.pay</groupId>
<artifactId>wxpay-sdk-wrapper</artifactId>
<version>3.0.9-custom</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/wxpay-sdk.jar</systemPath>
</dependency>
精算明細(対账单)の取得ロジック
精算データはリアルタイムではなく、遅延して提供される特性があります。一般的に翌日の早朝に前日分が確定するため、当日のデータを即座に要求しても存在しないエラーが返却されることがあります。
安定したデータ取得のために、過去数日間のデータを連続的に照会する戦略をとります。これにより、システム間でデータ同期が完了していなかった場合でも、適切なタイミングで再取得が可能となります。また、API レスポンスが成功した場合でも、return_code と return_msg を確認し、データ本体が存在するか判定する必要があります。
データエンティティの定義
受け取った CSV 形式のテキストデータをオブジェクトに変換するために、データベーステーブルに対応するエンティティクラスを設計します。27 項目の列を持つことを前提としたフィールド定義を行います。
@Entity(name = "wx_reconciliation")
public class PaymentBillRecord {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private LocalDateTime transactionTime;
private String appId;
private String merchantId;
private String subMerchantId;
private String deviceId;
private String outTradeNo;
private String userId;
private String tradeType;
private String payStatus;
private BigDecimal settleAmount;
private BigDecimal voucherAmount;
// ... その他の金額・状態フィールド
// Getter と Setter は略
public static PaymentBillRecord fromCsvLine(String[] values) {
PaymentBillRecord record = new PaymentBillRecord();
record.setOutTradeNo(values[6]);
// 各フィールドの割り当て処理
return record;
}
}
CSV データのパース処理
WeChat からダウンロードされたファイルヘッダーを確認し、実際の取引データ部分のみを抽出する解析ロジックを実装します。文字列置換やエスケープ処理を含め、メモリ効率を考慮してストリーミング処理を行うことが推奨されます。
public void processReconciliationFile(String rawData) throws IOException {
log.info("精算データの解析を開始");
final String headerMarker = "费率备注";
final String summaryMarker = "总交易单数";
// ヘッダー削除
int startIndex = rawData.indexOf(headerMarker);
if (startIndex == -1) return;
String trimmedData = rawData.substring(startIndex + headerMarker.length());
// 集計行削除
int endIndex = trimmedData.indexOf(summaryMarker);
if (endIndex != -1) {
trimmedData = trimmedData.substring(0, endIndex);
}
String[] lines = trimmedData.split("\\r\\n");
List<PaymentBillRecord> records = new ArrayList<>();
for (String line : lines) {
if (line.trim().isEmpty()) continue;
String[] columns = line.split(",");
if (columns.length < 27) continue; // フィールド数チェック
// クレーム値のクオート除去など
PaymentBillRecord entry = new PaymentBillRecord();
entry.setOutTradeNo(clean(columns[6]));
entry.setSettleAmount(parseMoney(clean(columns[12])));
records.add(entry);
}
// 既存データとの衝突回避の上、DB保存
saveRecords(records);
}
SDK の初期設定と主要 API 呼び出し
WXPayクラスのインスタンス化には設定情報を渡す必要があります。セキュリティ上の理由から、証明書ファイルは Web サーバーから直接アクセスできない場所に格納し、読み込み時は入力ストリームとして扱います。
public class CustomPaymentConfig extends WXPayConfig {
private final String merchantKey;
private final byte[] certBytes;
@Override
public InputStream getCertStream() {
return new ByteArrayInputStream(this.certBytes);
}
@Override
public IWXPayDomain getDomainInfo() {
return new DefaultWXPayDomain();
}
}
以下のコードは、統合注文リクエストと精算明細ダウンロードのサンプル実装です。パラメータセットは Map 構造を使用し、署名検証は SDK 内部で行われます。
public Map<String, String> createOrder(Map<String, String> params) {
WXPayClient client = new WXPayClient(new CustomPaymentConfig());
try {
return client.unifiedOrder(params);
} catch (Exception ex) {
throw new PaymentServiceException("注文生成に失敗しました", ex);
}
}
public String downloadBill(Date billDate) {
WXPayClient client = new WXPayClient(new CustomPaymentConfig());
Map<String, String> req = new HashMap<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
req.put("bill_date", sdf.format(billDate));
req.put("bill_type", "ALL");
Map<String, String> resp = client.downloadBill(req);
if (resp.get("return_code").equals("SUCCESS")) {
return resp.get("data");
} else {
throw new BusinessException(resp.get("return_msg"));
}
}
また、支払い結果通知を受信した際は、必ず署名検証を行ってからビジネスロジックを実行してください。isPayResultNotifySignatureValidメソッドを利用することで不正な通信を防ぐことができます。