Java による WeChat Pay 統合と精算レポート取得の技術的実装

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_codereturn_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メソッドを利用することで不正な通信を防ぐことができます。

タグ: wechat-pay Java sdk-integration Maven reconciliation-report

5月21日 08:47 投稿