Logoelepay
ベストプラクティス

WeChat ミニプログラム決済 V3(継続的な課金)

お客様が一度契約すると、以降は加盟店様のサーバーから直接課金でき、都度の確認が不要になります

継続的な課金(WeChat Pay 側の名称は Auto-Debit):お客様が WeChat ミニプログラム内で一度契約を行うと、以降は加盟店様のサーバーから直接課金でき、都度の確認操作が不要になります。定期配送・サブスクリプション・従量課金など、繰り返し課金が必要な業務に適しています。

本ガイドをご利用の前に、elepay へ WeChat ミニプログラムでの継続的な課金の利用開始をご依頼いただき、アプリが連携テスト可能な状態であることをご確認ください。契約テンプレート(plan_id)は別途お申し込みが必要です。

1. 連携前の準備

項目加盟店様にご用意いただくもの
WeChat ミニプログラム実際に決済を行うミニプログラムの AppID、および WeChat 側で必要な決済の利用開始と加盟店の紐付け
elepay アプリWeChat Pay V3・ミニプログラムチャンネル・対応する AppID が設定済みであることを elepay が確認
継続的な課金の利用開始elepay による契約テンプレート(plan_id)の設定
API キーアプリのシークレットキー(Secret Key)。加盟店様のサーバーにのみ保管してください
ミニプログラムのログインWeChat のログイン資格情報から現在のユーザーの openid を取得できること
業務サーバーユーザーと Customer / Source の対応関係を管理し、課金を実行して Charge ID を記録できること
決済結果の受信HTTPS の Webhook を受信できるエンドポイントと、Webhook 署名用シークレットの設定

WeChat ミニプログラムの AppID と elepay のアプリ ID は別の識別子です。Source の作成時も課金時も、リクエストボディに WeChat の AppID を渡す必要はありません。elepay はチャンネルに設定済みの AppID を使用します。extra.openid は必ず当該ミニプログラムのものである必要があり、別のミニプログラムや公式アカウントで取得した openid、および unionid は利用できません。

WeChat Pay の契約 API や引き落とし API を加盟店様が直接呼び出す必要はなく、署名をご自身で生成する必要もありません。WeChat 側の契約・課金・コールバックはすべて elepay が処理します。

2. 全体の流れ

継続的な課金は 契約(一度のみ) と 課金(繰り返し可能) の 2 つのフェーズに分かれます。

契約の完了前に課金することはできません。契約完了後の課金は加盟店様のサーバーからのみ実行され、ミニプログラムは関与しません。

Customer と Source の一般的な説明は顧客の作成とカード登録招待をご覧ください。

3. ユーザーの openid を取得する

  1. ミニプログラムで wx.login() を呼び出し、一時的な code を取得します。
  2. その code を加盟店様自身のログイン API へ送信します。
  3. 加盟店様のサーバーが当該ミニプログラムの AppID・AppSecret・code を使って WeChat の code2Session を呼び出します。
  4. 返却された openid を加盟店様の業務ログイン状態に紐付け、Source 作成時にそこから読み出します。

サーバー側の呼び出し形式:

GET https://api.weixin.qq.com/sns/jscode2session?appid=MINI_APP_ID&secret=MINI_APP_SECRET&js_code=LOGIN_CODE&grant_type=authorization_code

WeChat が返すエラーを必ず確認し、openid が取得できない状態のまま Source を作成しないでください。

AppSecret と session_key はミニプログラムへ配布しないでください。elepay の API が受け取るのは openid であり、WeChat のログイン code ではありません。

WeChat の API 定義は code2Session 公式ドキュメントをご覧ください。

4. 契約

4.1 エンドポイントと認証

POST https://api.elepay.io/customers
Authorization: Bearer <ELEPAY_SECRET_KEY>
Content-Type: application/json

API は必ず加盟店様のサーバーから呼び出してください。シークレットキーをミニプログラムに含めないでください。Basic 認証も利用でき、その場合はシークレットキーをユーザー名に指定し、パスワードは空にする必要があります(sk_xxx: を Base64 化した値)。パスワード部分が空でない場合は 401 が返ります。詳しくは API ガイドをご覧ください。

4.2 Customer の作成

Source は Customer の配下に作成されるため、先に Customer を作成します。

curl 'https://api.elepay.io/customers' \
  -H "Authorization: Bearer ${ELEPAY_SECRET_KEY}" \
  -H 'Content-Type: application/json' \
  --data '{ "metadata": { "userId": "YOUR_USER_ID" } }'

リクエストのフィールドはすべて任意です。必要に応じて name / email / phone / description / remark / metadata を指定できます。完全な定義は Create customer をご覧ください。

作成に成功すると 201 が返ります。1 人のユーザーにつき Customer は 1 つとし、Customer ID を永続化して再利用してください。 契約のたびに新規作成しないでください。同一 Customer の配下に複数の Source を登録できます。

4.3 Source の作成(契約の開始)

curl "https://api.elepay.io/customers/${CUSTOMER_ID}/sources" \
  -H "Authorization: Bearer ${ELEPAY_SECRET_KEY}" \
  -H 'Content-Type: application/json' \
  --data '{
    "paymentMethod": "wechatpay",
    "resource": "mini",
    "description": "定期配送サービス",
    "extra": { "openid": "OPENID_FROM_MERCHANT_SESSION" }
  }'
フィールド要否説明
paymentMethod必須wechatpay 固定
resource必須mini 固定
extra.openid必須対象ミニプログラムにおける現在のユーザーの openid。欠落時はパラメータエラーが返ります
description任意お客様の WeChat 契約画面に表示されます。お客様が識別できるサービス名を推奨します。最大 255 文字
metadata任意加盟店様独自のメタデータ

完全な定義は Create source をご覧ください。作成に成功すると 201 が返り、この時点で Source の status は pending(未認証)であり、まだ課金には利用できません。

4.4 credential を解析して sessionId を取り出す

レスポンスの credential は 2 層の JSON 文字列であり、2 回の解析が必要です。外側を解析して payload を取り出し、payload 自体も文字列のため、もう一度解析すると契約パラメータになります。

{
  "object": "source",
  "id": "src_xxxxxxxxxxxxxxxxxxxxxxx",
  "customerId": "cus_xxxxxxxxxxxxxxxxxxxxxxx",
  "liveMode": true,
  "paymentMethod": "wechatpay",
  "resource": "mini",
  "status": "pending",
  "credential": "{\"provider\":\"wechatpay\",\"payload\":\"{\\\"session_id\\\":\\\"SESSION_ID\\\"}\"}"
}

加盟店様のサーバーでパラメータを取り出し、自社のミニプログラムへ返します:

function buildSigningResponse(source) {
  const credential = JSON.parse(source.credential);
  const payload = JSON.parse(credential.payload);
  if (typeof payload.session_id !== 'string' || !payload.session_id) {
    throw new Error('契約セッション識別子がありません');
  }
  return { sourceId: source.id, sessionId: payload.session_id };
}

取り出すのは session_id のみで十分です。payload 内のその他のフィールドに依存しないでください。Source ID と業務ユーザーの対応関係を保存してください。 以降の課金・照会・解約はすべてこの ID を基準に行います。

5. ミニプログラムから契約を起動する

本フローでは wx.requestPayment を使用しません。elepay のフロントエンド SDK も不要です。WeChat の契約は独立したミニプログラムが担当するため、wx.navigateToMiniProgram で遷移します。

function startSigning(sessionId, refreshSigningStatus, showMessage) {
  wx.navigateToMiniProgram({
    appId: 'wxbd687630cd02ce1d',
    path: `pages/Oversea/walletSelect?sessionId=${sessionId}`,
    extraData: {},
    fail() {
      showMessage('契約画面を開けませんでした。しばらくしてからお試しください');
    },
    complete() {
      refreshSigningStatus();
    }
  });
}

appId は WeChat 契約用ミニプログラムの固定値です。書き換えないでください。

遷移先を app.json に宣言する必要はありません。 WeChat の現行の app.json 設定リファレンスに navigateToMiniProgramAppIdList という項目は存在しません。残っている embeddedAppIdList は wx.openEmbeddedMiniProgram(半画面表示)用であり、本 API とは別物です。本フローのために当該設定を追加しないでください。ただし遷移失敗に備えて fail コールバックは必ず実装してください。

お客様が契約を完了すると元のミニプログラムへ戻り、その際に res.referrerInfo を参照できます:

App({
  onShow(options) {
    const extraData = options.referrerInfo && options.referrerInfo.extraData;
    // extraData.contractId は存在しない場合があり、「お客様が一連の操作を終えた」ことを示すヒントにすぎません
    this.refreshSigningStatus();
  }
});

extraData.contractId は 契約成功の判定材料にはできません。このフィールドは欠落することがあり、WeChat 公式もサーバー側の結果を正とするよう求めています。次節の Webhook または照会 API でご確認ください。

契約パラメータはそのままご利用いただき、sessionId を変更しないでください。WeChat 公式のミニプログラム契約に関する説明もあわせてご覧ください。

6. 契約結果の確認

6.1 source.activated Webhook の受信

elepay に加盟店様のサーバーの Webhook URL と署名用シークレットを設定し、source.activated と source.inactivated イベントを購読してください。

{
  "id": "EVENT_ID",
  "object": "event",
  "liveMode": true,
  "type": "source.activated",
  "createTime": 1700000000000,
  "data": {
    "object": {
      "id": "src_xxxxxxxxxxxxxxxxxxxxxxx",
      "customerId": "cus_xxxxxxxxxxxxxxxxxxxxxxx",
      "paymentMethod": "wechatpay",
      "resource": "mini",
      "status": "active"
    }
  }
}

このイベントを受信し、status が active であることを確認してから、当該 Source への課金を開始してください。

Webhook V2 はテストモードではイベントを一切送信しません。 source.* と charge.* のいずれも対象です。テストモードで通知経路を検証する場合は V1 の Webhook をご利用ください。それ以外の場合、テストモードでは照会 API で状態をご確認ください。

Webhook の検証とリトライに関する要件は第 8 節をご覧ください。

6.2 契約状態の照会

curl "https://api.elepay.io/sources/${SOURCE_ID}/status" \
  -H "Authorization: Bearer ${ELEPAY_SECRET_KEY}"
{ "id": "src_xxxxxxxxxxxxxxxxxxxxxxx", "appId": "app_xxxxxxxxxxxxxxxxxxxxxxx", "liveMode": true, "status": "active" }

2 つの照会 API の使い分け:

  • Retrieve source's status —— id / appId / liveMode / status の 4 フィールドのみを返します。シークレットキーとパブリックキーのどちらでも呼び出せます。 契約状態のポーリングにはこちらをご利用ください。
  • Retrieve source —— Source オブジェクト全体を返します。シークレットキーでのみ呼び出せます。 extra や metadata を含む完全な情報が必要な場合はこちらをご利用ください。
status意味と対応
pending未認証。お客様が契約を完了していない、または手続き中です。「契約手続き中」と表示し、時間をおいて再照会してください
active認証済み。課金を開始できます
failed認証失敗。お客様に再度の契約をご案内ください
inactive認証済みですが現在は利用できません(通常は解約済み)。課金はできず、再契約が必要です
deleted削除済みまたは失効済み。課金はできません

ミニプログラムからは加盟店様自身の業務 API のみを呼び出してください。加盟店様のサーバーでは、ログイン中のユーザーが当該 Source を参照する権限を持つことを必ず検証してください。

7. 課金

7.1 課金の実行

curl 'https://api.elepay.io/charges' \
  -H "Authorization: Bearer ${ELEPAY_SECRET_KEY}" \
  -H 'Content-Type: application/json' \
  --data '{
    "amount": 460,
    "currency": "JPY",
    "paymentMethod": "wechatpay",
    "resource": "mini",
    "orderNo": "MINI-20260915-0001",
    "description": "9月配送料金",
    "customerId": "cus_xxxxxxxxxxxxxxxxxxxxxxx",
    "sourceId": "src_xxxxxxxxxxxxxxxxxxxxxxx"
  }'
フィールド要否説明
amount必須整数。JPY は円単位で、460 は 460 円を表します
currency明示指定を推奨省略時は JPY
paymentMethod必須wechatpay 固定
resource必須mini 固定
orderNo必須加盟店様側の注文番号。最大 50 文字
customerId必須契約時に作成した Customer ID
sourceId必須active になっている Source ID。必ず指定してください。省略すると都度決済のフローに切り替わり、本ガイドとは起動方法がまったく異なります
description任意注文の説明。最大 1024 文字
metadata任意加盟店様独自のメタデータ

orderNo の文字種に制約はありませんが、決済チャンネル側で特殊文字が拒否される場合があるため、英数字とハイフンのご利用を推奨します。完全な定義は Create charge をご覧ください。

7.2 レスポンスの処理

課金は同期処理です。 作成に成功すると 201 が返り、その時点で status はすでに captured、paid は true になっています:

{
  "id": "ch_xxxxxxxxxxxxxxxxxxxxxxx",
  "object": "charge",
  "liveMode": true,
  "orderNo": "MINI-20260915-0001",
  "amount": 460,
  "currency": "JPY",
  "paid": true,
  "status": "captured",
  "voucherNo": "4200000000000000000000000000"
}

voucherNo は WeChat 側の取引番号で、注文の突合にご利用いただけます。

本フローにクライアント側の起動処理はありません。 POST /charges のレスポンスが返った時点で課金は完了しており、wx.* の決済 API を呼び出す必要はありません。

レスポンスの credential フィールドについて:本フローではこのフィールドは存在し、空ではありませんが、内容に意味はありません。解析しないでください。また、課金結果の判定にも使用しないでください。成否はレスポンスの status と paid のみで判断してください。

課金に失敗した場合はエラーレスポンスが返ります。第 9 節に従って処理してください。

7.3 課金成功の通知

elepay は同時に charge.succeeded Webhook を送信します。構造は他の決済方法と同じです:

{
  "id": "EVENT_ID",
  "object": "event",
  "liveMode": true,
  "type": "charge.succeeded",
  "createTime": 1700000000000,
  "data": {
    "object": {
      "id": "ch_xxxxxxxxxxxxxxxxxxxxxxx",
      "orderNo": "MINI-20260915-0001",
      "amount": 460,
      "currency": "JPY",
      "status": "captured"
    }
  }
}

課金結果は同期レスポンスで確定しているため、このイベントは主に注文の突合と冪等性の担保にご利用ください。業務側ではイベント ID による重複排除を行い、同一注文が一度だけ処理されるようにしてください。

8. Webhook 処理の要件

  1. HTTP の生のリクエストボディを使って elepay-Signature を検証してください。パースしてから再度シリアライズした内容で検証しないでください。HTTP ヘッダー名は大文字小文字を区別しません。 フレームワークが提供する大文字小文字を区別しない取得方法をご利用いただき、リテラルの完全一致で取得しないでください。
  2. 署名ヘッダーの形式は t=<秒単位のタイムスタンプ>,sign=<16 進数の署名値> です。Webhook 署名用シークレットを使い、タイムスタンプ + "." + 生のリクエストボディ に対して HMAC-SHA256 を計算し、タイミング攻撃を防ぐため定数時間で比較してください。署名用シークレットは管理画面の Webhook 詳細ページで確認・再生成できます。API Secret Key とは別のものです。
  3. タイムスタンプの許容誤差は既定で 300 秒です(公式 Java SDK Webhook.verifyHeader の既定値)。範囲外のリクエストはリプレイ攻撃対策として拒否してください。
  4. 同一のヘッダーに複数の sign= が含まれる場合があります(シークレットのローテーション期間中)。いずれか 1 つが一致すれば正当とみなしてください。最初の 1 つのみを解析すると、ローテーション期間中に誤って拒否することになります。
  5. 処理に成功したら HTTP 2xx を返してください。失敗時は必ず 4xx または 5xx を返してください。 そうしないとリトライが行われません。
  6. リトライの方針:送信に失敗した場合、5 秒 / 15 秒 / 1 分 / 10 分 / 10 分 の間隔でリトライし、リトライは 5 回(初回配信を除く)、累計 6 回の配信となり、約 21 分で打ち切ります(プラットフォーム既定値。加盟店ごとの個別設定には対応しておりません)。2 回目の送信は最短 5 秒後に到達します。 冪等性の判定期間を分単位の前提で設計しないでください。それ以降は照会 API と突合で補完してください。
  7. イベント ID で重複を排除し、業務層で同一イベントが一度だけ処理されるようにしてください。重複した通知や遅延して到達した通知も処理できる必要があります。
  8. 認識できない type を受信した場合は 2xx を返して無視してください。成功として扱わないでください。

Webhook の設定と検証について詳しくは Webhook ドキュメントをご覧ください。

9. 契約の失効と異常時の処理

9.1 契約は加盟店様の知らないうちに失効することがあります

お客様は WeChat 側でご自身で解約できます。source.inactivated を購読し、受信後ただちに当該 Source への課金を停止してください:

{
  "type": "source.inactivated",
  "data": { "object": { "id": "src_xxxxxxxxxxxxxxxxxxxxxxx", "status": "inactive" } }
}

また、課金のたびに elepay は契約状態を検証します。契約が終了している場合、課金リクエストは HTTP 400 を返し、errorCode は M002003 になります。

M002003 は汎用の「リソースが存在しません」エラーコードであり(Customer や Source が存在しない場合も同じです)、メッセージ本文に契約への言及はありません。判別方法:課金が 400 を返し errorCode が M002003 の場合、GET /sources/{sourceId}/status を呼び出して確認してください。status が active でなければ契約が失効しています。

これは業務上の終了状態です。一時的なエラーとしてリトライしないでください。 課金を停止し、当該 Source を利用不可として記録したうえで、お客様に再度の契約手続きをご案内してください。

9.2 よくあるケース

ケース対応方法
extra.openid が欠落している先に WeChat ログインを完了し、サーバーで有効な openid を紐付けてから Source を作成してください
AppID または openid が一致しない実際に動作しているミニプログラム・openid を取得した AppID・チャンネルに設定された AppID が一致しているかご確認ください
wx.navigateToMiniProgram の遷移に失敗するappId と sessionId が欠けたり書き換えられたりしていないか、および基礎ライブラリのバージョンをご確認ください。本 API では遷移先を app.json に宣言する必要はありません
Source が長期間 pending のままお客様が契約を完了していません。再度の契約をご案内ください。Source を作り直して問題ありません
復帰時に extraData.contractId がない正常な動作であり、失敗を意味しません。Webhook または照会 API を正としてください
課金が 400 + M002003 を返す契約が解除されている可能性があります。9.1 に従って Source の状態を確認し、リトライしないでください
課金リクエストがタイムアウトするまず保存済みの Charge ID で照会してください。ID を取得できていない場合は Charge 一覧で orderNo を突き合わせ、そのまま再度課金しないでください
注文番号が重複するorderNo は安全に再送できる冪等キーではありません。作成済みの決済を照会し、状態をご確認ください
API がエラーを返すレスポンスの errorCode のみで判別し、message の文言と照合しないでください。詳しくは付録をご覧ください

9.3 加盟店様からの解約

curl -X DELETE "https://api.elepay.io/customers/${CUSTOMER_ID}/sources/${SOURCE_ID}" \
  -H "Authorization: Bearer ${ELEPAY_SECRET_KEY}"

204 が返れば受理されています。解約後、当該 Source は課金に利用できなくなり、お客様には再度の契約が必要です。完全な定義は Delete source をご覧ください。

10. 連携テストについて

テストモードでも契約から課金までの業務フローを一通り実施できます。契約は elepay が返すテスト用確認画面で完了し、実際の WeChat 契約は行われません。Source が active になれば課金が可能で、課金は同期的に captured を返します。

ただし、テストモードには本番モードと異なる点が 3 つあり、本番モードでの検証の代わりにはなりません:

  • Source の credential の形式が異なり、暗号化されたテスト用の遷移資格情報になります。その中に wx.navigateToMiniProgram で利用できるセッション識別子は含まれません。 ミニプログラム側の実際の契約遷移はテストモードでは検証できません。
  • 実際の課金は発生せず、WeChat 側に対応する取引はありません。
  • Webhook V2 はテストモードではイベントを送信しません(6.1 参照)。

実際の WeChat 契約と課金は、本番モードでの少額検証が必要です。 実機での連携テストの前に、本番チャンネルと契約テンプレートが利用可能であることを elepay にご確認ください。連携テストでは少なくとも以下を網羅してください:

  • 契約を一通り完了し source.activated を受信する
  • お客様が契約を途中で中断する(Source が pending のまま)
  • 契約後の課金が成功し、status=captured と voucherNo を確認する
  • お客様が WeChat 側で解約したあとに課金を行い、400 + M002003 が返り業務側で課金が停止することを確認する
  • 加盟店様から解約し source.inactivated を受信する
  • 重複した通知、署名が正しくない通知
  • 課金リクエストがタイムアウトしたあとの照会と突合

11. 連携完了の基準

  • ミニプログラムの AppID・ユーザーの openid・チャンネル設定が一致している。
  • サーバーで Customer と Source を作成でき、2 層の JSON である credential を正しく解析してセッション識別子を取り出せる。
  • ミニプログラムから契約用ミニプログラムへ遷移して復帰でき、契約結果をサーバー側の情報で確認できる。
  • source.activated の受信後に課金を実行でき、同期レスポンスで captured が返る。
  • source.inactivated と課金時の契約失効エラーの双方を処理でき、失効した契約への課金を継続しない。
  • 解約の経路が機能し、Webhook の重複送信によって二重出荷が発生しない。

付録:エラーレスポンス

すべての API は失敗時に共通の構造を返します(値が null のフィールドは出力されません):

{
  "requestId": "req_xxxxxxxxxxxxxxxxxxxxxxx",
  "errorCode": "M002003",
  "message": "要求されたリソースは存在しません。",
  "parameterName": "sourceId",
  "providerError": {
    "providerKey": "wechatpay",
    "code": "PROVIDER_ERROR_CODE",
    "message": "provider message"
  }
}
フィールド説明
requestIdリクエストの識別子。テクニカルサポートへのお問い合わせ時にお知らせください
errorCodeエラー判別の唯一の根拠です
message人が読むためのメッセージです。プログラムでの判定には使用しないでください
parameterNameパラメータエラー時に該当フィールドを示します
providerError決済チャンネルが返した元のエラー。調査時に有用です

3 つの要件:

  1. errorCode のみで判定してください。 レスポンスには code フィールド(9_xx_xx_10113 のような形式)もありますが、非推奨のため依存しないでください。
  2. message は既定で日本語が返ります。 他の言語が必要な場合はリクエストヘッダーに Accept-Language を指定してください。ja / en / zh-CN / zh-TW に対応しています。
  3. message はサーバー側の文言辞書に由来し、内容が変更される可能性があります。 文字列の一致判定には使用しないでください。

エラーコードの一覧はエラーコードのドキュメントをご覧ください。

最終更新日

このページ