支付分开发指引(免确认模式)

1. 接口规则

为了在保证支付安全的前提下,带给商户简单、一致且易用的开发体验,我们推出了全新的微信支付APIv3。该版本API的具体规则请参考“APIv3接口规则

2. 开发准备

2.1. 搭建和配置开发环境

为了帮助开发者调用开放接口,我们提供了JAVA、PHP两种语言版本的开发库,封装了签名生成、签名验证、敏感信息加/解密、媒体文件上传等基础功(更多语言版本的开发库将在近期陆续提供

测试步骤

1、根据自身开发语言,选择对应的开发库并构建项目,具体配置请参考下面链接的详细说明:

    • wechatpay-apache-httpclient,适用于使用Apache HttpClient处理HTTP的Java开发者

    • wechatpay-guzzle-middleware,适用于PHP开发者

2、创建加载商户私钥、加载平台证书、初始化httpClient的通用方法


@Before
public void setup() throws IOException {
    // 加载商户私钥(privateKey:私钥字符串)
    PrivateKey merchantPrivateKey = PemUtil
            .loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
 
    // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3秘钥)
    AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
            new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),apiV3Key.getBytes("utf-8"));
 
    // 初始化httpClient
    httpClient = WechatPayHttpClientBuilder.create()
            .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
            .withValidator(new WechatPay2Validator(verifier)).build();
}
 
@After
public void after() throws IOException {
    httpClient.close();
}

use GuzzleHttp\Exception\RequestException;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use GuzzleHttp\HandlerStack;
 
// 商户相关配置,
$merchantId = '1000100'; // 商户号
$merchantSerialNumber = 'XXXXXXXXXX'; // 商户API证书序列号
$merchantPrivateKey = PemUtil::loadPrivateKey('./path/to/mch/private/key.pem'); // 商户私钥文件路径
 
// 微信支付平台配置
$wechatpayCertificate = PemUtil::loadCertificate('./path/to/wechatpay/cert.pem'); // 微信支付平台证书文件路径
 
// 构造一个WechatPayMiddleware
$wechatpayMiddleware = WechatPayMiddleware::builder()
    ->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
    ->withWechatPay([ $wechatpayCertificate ]) // 可传入多个微信支付平台证书,参数类型为array
    ->build();
 
// 将WechatPayMiddleware添加到Guzzle的HandlerStack中
$stack = GuzzleHttp\HandlerStack::create();
$stack->push($wechatpayMiddleware, 'wechatpay');
 
// 创建Guzzle HTTP Client时,将HandlerStack传入,接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
$client = new GuzzleHttp\Client(['handler' => $stack]);

3、基于接口的示例代码,替换请求参数后可发起测试

说明:

• 上面的开发库为微信支付官方开发库,其它没有审核或者控制下的第三方工具和库,微信支付不保证它们的安全性和可靠性

通过包管理工具引入SDK后,可根据下面每个接口的示例代码替换相关参数后进行快速测试

开发者如果想详细了解签名生成、签名验证、敏感信息加/解密、媒体文件上传等常用方法的具体代码实现,可阅读下面的详细说明:

    1.签名生成

    2.签名验证

    3.敏感信息加解密

    4.merchantPrivateKey(私钥)

    5.wechatpayCertificates(平台证书)

    6.APIV3Key(V3 key)

如想更详细的了解我们的接口规则,可查看我们的接口规则指引文档

2.2. 业务开发配置

1、微信支付分的相关配置参数在商户入驻的过程中都已经配置完成(前往查看配置相关内容),例如授权结果回调url、service_notify_url、测试白名单、免确认订单模式的权限等。

2、如果发现配置信息有误,请主动联系微信支付分运营同学协助修改,或者点击右侧导航栏进入在线技术客服进行技术咨询。


3. 快速接入(免确认模式)

3.1. 业务流程图

重点步骤说明:

步骤1 用户在商户侧下单购买产品或服务,此时,我们需要先对用户的授权状态进行查询

查询用户授权状态的两种方法:

用户授权状态一共分三种:UNAVAILABLE——用户未授权服务、AVAILABLE:用户已授权服务、UNBINDUSER:未绑定用户 。

当查询到用户授权状态为UNAVAILABLE、UNBINDUSER时,请前往步骤二:引导用户开启服务;

当查询到用户授权状态为AVAILABLE时,请前往步骤三:创建支付分订单;


步骤2 引导用户开启授权服务

开启授权有两步操作,第一步:调用后台接口进行预授权,第二步:调用前端方法跳转进入微信中让用户进行授权。

请求开启授权之后,我们需要确认开启授权是否成功,共有两种方法:

若获取到用户的授权状态为AVAILABLE:用户已授权服务,则前往步骤三;

若获取到用户的授权状态为UNAVAILABLE-用户未授权服务、UNBINDUSER-未绑定用户,则商户可根据业务需要选择继续查询或者结束支付分流程,进入商户自己的业务流程。


步骤3 创建支付分订单

我们通过创建支付分订单API接口创建一笔待支付的支付分订单。

请求:

入参“need_user_confirm”,取值请选择 “false”;

入参“risk_fund::name”,取值请选择“【先享模式】”中的枚举值。

返回:

  • 若接口返回state=CREATED:表示商户创建服务订单成功,可前往步骤四;
  • 若接口返回state≠CREATED:表示商户创建服务订单失败,商户可根据实际返回内容提示选择重新创建或者结束支付分流程,进入商户自己的业务流程;

     创建服务订单失败原因会在接口返回字段‘message‘中返回,主要失败原因有两种:1、存在未完结订单 2、综合评估不通过,建议商户稍后重试或者使用其他渠道支付


步骤4 商户为用户提供服务,待服务结束后,商户调用完结订单接口完结当前订单。

调用完结支付分订单API接口后,微信支付分就会发起对用户的扣款,但是在用户扣款过程中可能会出现一些特殊情况,下面列举了几种特殊情况以及对应的处理方法,供大家参考:

请求:

• 情况一:扣款过程中,发现扣款金额有误(注意:此时需要订单为“待支付”状态)

处理方法1:调用修改订单金额接口修改订单金额,系统将按照修改后的金额发起用户扣款

处理方法2:调用取消支付分订单接口取消待支付的支付分订单

• 情况二:扣款过程中,由于某些原因,商户想立刻发起一次用户扣款

处理方法:商户调用商户发起催收扣款接口对待支付的支付分订单进行主动催收

• 情况三:扣款过程中,用户通过其它方式完成了支付,希望微信支付分停止继续扣款

处理方法:商户调用同步服务订单信息接口将订单支付成功状态同步给微信支付分,微信支付分将停止继续扣款的操作


步骤5 收到用户扣款成功通知,业务流程结束

通过支付成功回调通知API接口可以获取到用户扣款成功的通知,同时,商户也可以根据情况通过查询支付分订单API接口主动查询扣款情况。

  • 如订单状态state=DONE,state_description=MCH_COMPLETE,且收款状态collection.state=USER_PAID,代表扣款成功
  • 如订单状态state=DOING,state_description=MCH_COMPLETE,且收款状态collection.state=USER_PAYING,代表扣款进行中

如遇到网络、服务器等原因造成无法正常接收扣款成功通知,一般有两种解决方法:

1.主动查单,通过查询支付分订单API 接口主动查询扣款情况

2.次日对账,通过申请交易账单API接口申请交易账单,再通过后台接口下载账单API下载交易账单


3.2. API接入(含示例代码)

本文档展示了如何使用微信支付服务端 SDK 快速接入微信支付分产品,完成与微信支付对接的部分。

注意:

  • 文档中的代码示例是用来阐述 API 基本使用方法,代码中的示例参数需替换成商户自己账号及请求参数才能跑通。
  • 以下接入步骤仅提供参考,请商户结合自身业务需求进行评估、修改。
3.2.1.【服务端】查询用户授权关系

步骤说明:创建支付分订单的前提是用户必须有授权关系,所以在免确认模式下,我们最先要做的就是确保用户是已授权状态。目前提供了使用openid授权协议号两种条件进行查询的方式。下面以查询与用户授权记录(openid)举例接口调用方式。

示例代码

public void GetPermissionsByOpenid() throws Exception{

  //请求URL
  HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/payscore/permissions/openid/{openid}");
  HttpGet.setHeader("Accept", "application/json");
  
  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(HttpGet);
  
  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
  $resp = $client->request(
      'GET',
      'https://api.mch.weixin.qq.com/v3/payscore/permissions/openid/{openid}', //请求URL
      [
          'headers' => [ 'Accept' => 'application/json']
      ]
  );
  $statusCode = $resp->getStatusCode();
  if ($statusCode == 200) { //处理成功
      echo "success,return body = " . $resp->getReasonPhrase()."\n";
  } else if ($statusCode == 204) { //处理成功,无返回Body
      echo "success";
  }
} catch (RequestException $e) {
  // 进行错误处理
  echo $e->getMessage()."\n";
  if ($e->hasResponse()) {
      echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
  }
  return;
}

重要入参说明

• 查询条件参数openid或者授权协议号都是在接口url中传递的,请勿传到body中去。

更多参数、响应详情及错误码请参见查询与用户授权记录(openid)接口文档

3.2.2.【服务端】请求预授权接口

步骤说明:创建支付分订单的前提是用户必须有授权关系,所以在免确认模式下,我们最先要做的就是确保用户是已授权状态。通过商户预授权接口获取跳转用户授权页必填参数“预授权token”。

示例代码

public void permissions() throws Exception{
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/permissions");
// 请求body参数
String reqdata = "{"
        + "\"service_id\":\"500001\","
        + "\"appid\":\"wxd678efh567hg6787\","
        + "\"authorization_code\":\"1234323JKHDFE1243252\","
        + "\"notify_url\":\"http://www.qq.com\""
        + "}";
StringEntity entity = new StringEntity(reqdata);
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");

//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);

try {
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == 200) { //处理成功
        System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
    } else if (statusCode == 204) { //处理成功,无返回Body
        System.out.println("success");
    } else {
        System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
        throw new IOException("request failed");
    }
} finally {
    response.close();
}
}
try {
$resp = $client->request(
    'POST',
    'https://api.mch.weixin.qq.com/v3/payscore/permissions', //请求URL
    [
        // JSON请求体
        'json' => [
            "service_id" => "500001",
            "appid" => "wxd678efh567hg6787",
            "authorization_code" => "1234323JKHDFE1243252",
            "notify_url" => "http://www.qq.com",
        ],
        'headers' => [ 'Accept' => 'application/json' ]
    ]
);
$statusCode = $resp->getStatusCode();
if ($statusCode == 200) { //处理成功
    echo "success,return body = " . $resp->getReasonPhrase()."\n";
} else if ($statusCode == 204) { //处理成功,无返回Body
    echo "success";
}
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
    echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
}
return;
}

重要入参说明

• 完成用户授权需要两步操作,预授权接口仅是为获取关键参数“预授权token”,商户还需引导用户跳转到微信授权页进行授权操作。

更多参数、响应详情及错误码请参见 商户预授权API接口文档

3.2.3.【客户端】开启授权服务

步骤说明:通过商户预授权接口获取到跳转用户授权页必填参数“预授权token”后,即可通过前端方法跳转到微信客户端,让用户开启授权服务。前端跳转方法请根据用户实际使用场景(APP、小程序、微信外H5)来选择。

3.2.4. 【服务端】开启/解除授权服务回调通知

步骤说明:当用户授权或解约服务成功时,微信会把相关信息异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答

注意:

  • 支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情
  • 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考微信支付API v3签名方案
  • 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx
  • 商户成功接收到回调通知后应返回成功的http应答码为200或204
  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  • 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。

更多参数、响应详情及错误码请参见开启/解除授权服务回调通知API接口文档


3.2.5. 【服务端】创建支付分订单

步骤说明:完成用户授权后,即可创建支付分订单,为用户提供服务了。

示例代码

public void CreateServiceOrder() throws Exception{

    //请求URL
    HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder");
    // 请求body参数
    String reqdata = "{"
            + "\"out_order_no\":\"1234323JKHDFE1243252\","
            + "\"appid\":\"wxd678efh567hg6787\","
            + "\"service_id\":\"500001\","
            + "\"service_introduction\":\"某某酒店\","
            + "\"post_payments\": ["
            + "{"
            + "\"name\":\"就餐费用服务费\","
            + "\"amount\":4000,"
            + "\"description\":\"就餐人均100元服务费:100/小时\","
            + "\"count\":1"
            + "}"
            + "],"
            + "\"post_discounts\": ["
            + "{"
            + "\"name\":\"满20减1元\","
            + "\"description\":\"不与其他优惠叠加\""
            + "}"
            + "],"
            + "\"time_range\": {"
            + "\"start_time\":\"20091225091010\","
            + "\"end_time\":\"20091225121010\""
            + "},"
            + "\"location\": {"
            + "\"start_location\":\"嗨客时尚主题展餐厅\","
            + "\"end_location\":\"嗨客时尚主题展餐厅\""
            + "},"
            + "\"risk_fund\": {"
            + "\"name\":\"ESTIMATE_ORDER_COST\","
            + "\"amount\":10000,"
            + "\"description\":\"就餐的预估费用\""
            + "},"
            + "\"attach\":\"Easdfowealsdkjfnlaksjdlfkwqoi&wl3l2sald\","
            + "\"notify_url\":\"https://api.test.com\","
            + "\"openid\":\"oUpF8uMuAJO_M2pxb1Q9zNjWeS6o\","
            + "\"need_user_confirm\":true"
            + "}";
    StringEntity entity = new StringEntity(reqdata);
    entity.setContentType("application/json");
    httpPost.setEntity(entity);
    httpPost.setHeader("Accept", "application/json");
    
    //完成签名并执行请求
    CloseableHttpResponse response = httpClient.execute(httpPost);
    
    try {
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) { //处理成功
            System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
        } else if (statusCode == 204) { //处理成功,无返回Body
            System.out.println("success");
        } else {
            System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
            throw new IOException("request failed");
        }
    } finally {
        response.close();
    }
  }
try {
  $resp = $client->request(
      'POST',
      'https://api.mch.weixin.qq.com/v3/payscore/serviceorder', //请求URL
      [
          // JSON请求体
          'json' => [
              "out_order_no" => "1234323JKHDFE1243252",
              "appid" => "wxd678efh567hg6787",
              "service_id" => "500001",
              "service_introduction" => "某某酒店",
              "post_payments" => [
                  [
                      "name" => "就餐费用服务费",
                      "amount" => 4000,
                      "description" => "就餐人均100元服务费:100/小时",
                      "count" => 1,
                  ],
              ],
              "post_discounts" => [
                  [
                      "name" => "满20减1元",
                      "description" => "不与其他优惠叠加",
                  ],
              ],
              "time_range" => [
                  "start_time" => "20091225091010",
                  "end_time" => "20091225121010",
              ],
              "location" => [
                  "start_location" => "嗨客时尚主题展餐厅",
                  "end_location" => "嗨客时尚主题展餐厅",
              ],
              "risk_fund" => [
                  "name" => "ESTIMATE_ORDER_COST",
                  "amount" => 10000,
                  "description" => "就餐的预估费用",
              ],
              "attach" => "Easdfowealsdkjfnlaksjdlfkwqoi&wl3l2sald",
              "notify_url" => "https://api.test.com",
              "openid" => "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
              "need_user_confirm" => true,
          ],
          'headers' => [ 'Accept' => 'application/json' ]
      ]
  );
  $statusCode = $resp->getStatusCode();
  if ($statusCode == 200) { //处理成功
      echo "success,return body = " . $resp->getReasonPhrase()."\n";
  } else if ($statusCode == 204) { //处理成功,无返回Body
      echo "success";
  }
} catch (RequestException $e) {
  // 进行错误处理
  echo $e->getMessage()."\n";
  if ($e->hasResponse()) {
      echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
  }
  return;
}

重要参数说明:

  • 入参“need_user_confirm”,取值请选择 “false”;
  • 入参“risk_fund::name”,取值请选择“【先享模式】”中的枚举值。

更多参数、响应详情及错误码请参见 创建支付分订单API接口文档

3.2.6. 【服务端】用户确认订单回调通知

步骤说明:当用户确认订单时,微信会把相关信息异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答

注意

  • 支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情
  • 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考微信支付API v3签名方案
  • 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx
  • 商户成功接收到回调通知后应返回成功的http应答码为200或204
  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  • 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。

更多参数、响应详情及错误码请参见 确认订单回调通知API接口文档

3.2.7. 【服务端】完结支付分订单

步骤说明:用户服务结束后,商户通过请求完结支付分订单接口,通过微信支付分进行用户扣款操作。

示例代码


public void CompleteServiceOrder() throws Exception{

  //请求URL
  HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/complete");
  // 请求body参数
  String reqdata = "{"
          + "\"appid\":\"wxd678efh567hg6787\","
          + "\"service_id\":\"500001\","
          + "\"post_payments\": ["
          + "{"
          + "\"name\":\"就餐费用服务费\","
          + "\"amount\":4000,"
          + "\"description\":\"就餐人均100元服务费:100/小时\","
          + "\"count\":1"
          + "}"
          + "],"
          + "\"post_discounts\": ["
          + "{"
          + "\"name\":\"满20减1元\","
          + "\"description\":\"不与其他优惠叠加\","
          + "\"amount\":4000"
          + "}"
          + "],"
          + "\"total_amount\":3900,"
          + "\"time_range\": {"
          + "\"start_time\":\"20091225091010\","
          + "\"end_time\":\"20091225121010\""
          + "},"
          + "\"location\": {"
          + "\"end_location\":\"嗨客时尚主题展餐厅\""
          + "},"
          + "\"profit_sharing\":false"
          + "}";
  StringEntity entity = new StringEntity(reqdata);
  entity.setContentType("application/json");
  httpPost.setEntity(entity);
  httpPost.setHeader("Accept", "application/json");
  
  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(httpPost);
  
  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
    $resp = $client->request(
        'POST',
        'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/complete', //请求URL
        [
            // JSON请求体
            'json' => [
                "appid" => "wxd678efh567hg6787",
                "service_id" => "500001",
                "post_payments" => [
                    [
                        "name" => "就餐费用服务费",
                        "amount" => 4000,
                        "description" => "就餐人均100元服务费:100/小时",
                        "count" => 1,
                    ],
                ],
                "post_discounts" => [
                    [
                        "name" => "满20减1元",
                        "description" => "不与其他优惠叠加",
                        "amount" => 4000,
                    ],
                ],
                "total_amount" => 3900,
                "time_range" => [
                    "start_time" => "20091225091010",
                    "end_time" => "20091225121010",
                ],
                "location" => [
                    "end_location" => "嗨客时尚主题展餐厅",
                ],
                "profit_sharing" => false,
            ],
            'headers' => [ 'Accept' => 'application/json' ]
        ]
    );
    $statusCode = $resp->getStatusCode();
    if ($statusCode == 200) { //处理成功
        echo "success,return body = " . $resp->getReasonPhrase()."\n";
    } else if ($statusCode == 204) { //处理成功,无返回Body
        echo "success";
    }
} catch (RequestException $e) {
    // 进行错误处理
    echo $e->getMessage()."\n";
    if ($e->hasResponse()) {
        echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
    }
    return;
}

更多参数、响应详情及错误码请参见 完结支付分订单API接口文档


3.2.8. 【服务端】修改订单金额

步骤说明:在用户扣款成功前、完结订单后(即订单状态为“待支付”),如需修改订单支付金额,可通过此接口进行订单金额修改。修改成功后,微信支付将按照修改后的金额进行用户扣款。

示例代码

public void ModifyServiceOrder() throws Exception{

    //请求URL
    HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/modify");
    // 请求body参数
    String reqdata = "{"
            + "\"appid\":\"wxd678efh567hg6787\","
            + "\"service_id\":\"500001\","
            + "\"post_payments\": ["
            + "{"
            + "\"name\":\"就餐费用服务费\","
            + "\"amount\":4000,"
            + "\"description\":\"就餐人均100元服务费:100/小时\","
            + "\"count\":1"
            + "}"
            + "],"
            + "\"post_discounts\": ["
            + "{"
            + "\"name\":\"满20减1元\","
            + "\"description\":\"不与其他优惠叠加\","
            + "\"amount\":100"
            + "}"
            + "],"
            + "\"total_amount\":2000,"
            + "\"reason\":\"用户投诉\""
            + "}";
    StringEntity entity = new StringEntity(reqdata);
    entity.setContentType("application/json");
    httpPost.setEntity(entity);
    httpPost.setHeader("Accept", "application/json");
    
    //完成签名并执行请求
    CloseableHttpResponse response = httpClient.execute(httpPost);
    
    try {
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) { //处理成功
            System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
        } else if (statusCode == 204) { //处理成功,无返回Body
            System.out.println("success");
        } else {
            System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
            throw new IOException("request failed");
        }
    } finally {
        response.close();
    }
  }
try {
    $resp = $client->request(
        'POST',
        'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/modify', //请求URL
        [
            // JSON请求体
            'json' => [
                "appid" => "wxd678efh567hg6787",
                "service_id" => "500001",
                "post_payments" => [
                    [
                        "name" => "就餐费用服务费",
                        "amount" => 4000,
                        "description" => "就餐人均100元服务费:100/小时",
                        "count" => 1,
                    ],
                ],
                "post_discounts" => [
                    [
                        "name" => "满20减1元",
                        "description" => "不与其他优惠叠加",
                        "amount" => 100,
                    ],
                ],
                "total_amount" => 2000,
                "reason" => "用户投诉",
            ],
            'headers' => [ 'Accept' => 'application/json' ]
        ]
    );
    $statusCode = $resp->getStatusCode();
    if ($statusCode == 200) { //处理成功
        echo "success,return body = " . $resp->getReasonPhrase()."\n";
    } else if ($statusCode == 204) { //处理成功,无返回Body
        echo "success";
    }
  } catch (RequestException $e) {
    // 进行错误处理
    echo $e->getMessage()."\n";
    if ($e->hasResponse()) {
        echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
    }
    return;
  }

更多参数、响应详情及错误码请参见修改订单金额API接口文档


3.2.9【服务端】取消支付分订单

步骤说明:订单为以下状态时可以取消订单:CREATED(已创单)、DOING(进行中)(包括商户完结支付分订单后,且支付分订单收款状态为待支付USER_PAYING)。

示例代码

public void CancelServiceOrder() throws Exception{

    //请求URL
    HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/cancel");
    // 请求body参数
    String reqdata = "{"
            + "\"appid\":\"wxd678efh567hg6787\","
            + "\"service_id\":\"500001\","
            + "\"reason\":\"用户投诉\""
            + "}";
    StringEntity entity = new StringEntity(reqdata);
    entity.setContentType("application/json");
    httpPost.setEntity(entity);
    httpPost.setHeader("Accept", "application/json");
    
    //完成签名并执行请求
    CloseableHttpResponse response = httpClient.execute(httpPost);
    
    try {
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) { //处理成功
            System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
        } else if (statusCode == 204) { //处理成功,无返回Body
            System.out.println("success");
        } else {
            System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
            throw new IOException("request failed");
        }
    } finally {
        response.close();
    }
  }
try {
    $resp = $client->request(
        'POST',
        'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/cancel', //请求URL
        [
            // JSON请求体
            'json' => [
                "appid" => "wxd678efh567hg6787",
                "service_id" => "500001",
                "reason" => "用户投诉",
            ],
            'headers' => [ 'Accept' => 'application/json' ]
        ]
    );
    $statusCode = $resp->getStatusCode();
    if ($statusCode == 200) { //处理成功
        echo "success,return body = " . $resp->getReasonPhrase()."\n";
    } else if ($statusCode == 204) { //处理成功,无返回Body
        echo "success";
    }
} catch (RequestException $e) {
    // 进行错误处理
    echo $e->getMessage()."\n";
    if ($e->hasResponse()) {
        echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
    }
    return;
}

更多参数、响应详情及错误码请参见取消支付分订单API接口文档


3.2.10【服务端】商户发起催收扣款

步骤说明:在订单完结成功后,支付成功前,商户可随时发起催收扣款,发起成功后微信支付将立即进行一次用户扣款(不保证一定能扣成功,仅执行一次扣款操作)。

示例代码

public void CollectServiceOrder() throws Exception{
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/pay");
// 请求body参数
String reqdata = "{"
      + "\"appid\":\"wxd678efh567hg6787\","
      + "\"service_id\":\"500001\""
      + "}";
StringEntity entity = new StringEntity(reqdata);
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");

//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);

try {
  int statusCode = response.getStatusLine().getStatusCode();
  if (statusCode == 200) { //处理成功
      System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
  } else if (statusCode == 204) { //处理成功,无返回Body
      System.out.println("success");
  } else {
      System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
      throw new IOException("request failed");
  }
} finally {
  response.close();
}
}
try {
$resp = $client->request(
    'POST',
    'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/pay', //请求URL
    [
        // JSON请求体
        'json' => [
            "appid" => "wxd678efh567hg6787",
            "service_id" => "500001",
        ],
        'headers' => [ 'Accept' => 'application/json' ]
    ]
);
$statusCode = $resp->getStatusCode();
if ($statusCode == 200) { //处理成功
    echo "success,return body = " . $resp->getReasonPhrase()."\n";
} else if ($statusCode == 204) { //处理成功,无返回Body
    echo "success";
}
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
    echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
}
return;
}

更多参数、响应详情及错误码请参见 商户发起催收扣款API接口文档


3.2.11【服务端】同步服务订单信息

步骤说明:由于一些原因,用户与商户达成线下支付或者其他支付方式支付的协议,商户可通过此接口告知微信支付该笔订单无需继续扣款,微信支付在接到此信息后将不再发起用户扣款。

示例代码

public void SyncServiceOrder() throws Exception{
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/sync");
// 请求body参数
String reqdata = "{"
        + "\"appid\":\"wxd678efh567hg6787\","
        + "\"service_id\":\"500001\","
        + "\"type\":\"Order_Paid\","
        + "\"detail\": {"
        + "\"paid_time\":\"20091225091210\""
        + "}"
        + "}";
StringEntity entity = new StringEntity(reqdata);
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");

//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);

try {
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == 200) { //处理成功
        System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
    } else if (statusCode == 204) { //处理成功,无返回Body
        System.out.println("success");
    } else {
        System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
        throw new IOException("request failed");
    }
} finally {
    response.close();
}
}
try {
$resp = $client->request(
    'POST',
    'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/sync', //请求URL
    [
        // JSON请求体
        'json' => [
            "appid" => "wxd678efh567hg6787",
            "service_id" => "500001",
            "type" => "Order_Paid",
            "detail" => [
                "paid_time" => "20091225091210",
            ]
        ],
        'headers' => [ 'Accept' => 'application/json' ]
    ]
);
$statusCode = $resp->getStatusCode();
if ($statusCode == 200) { //处理成功
    echo "success,return body = " . $resp->getReasonPhrase()."\n";
} else if ($statusCode == 204) { //处理成功,无返回Body
    echo "success";
}
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
    echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
}
return;
}

更多参数、响应详情及错误码请参见 同步服务订单信息API接口文档


3.2.12【服务端】查询支付分订单

步骤说明:一般在创建订单后、订单完结成功后等关键流程中,商户可能有知晓订单状态的需求,此时即可通过该接口查询订单状态。

示例代码

public void GetServiceOrder() throws Exception{

  //请求URL
  HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/payscore/serviceorder?service_id=500001&out_order_no=8416518464133&appid=wxd678efh567hg6787");
  HttpGet.setHeader("Accept", "application/json");
  
  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(HttpGet);
  
  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
$resp = $client->request(
    'GET',
    'https://api.mch.weixin.qq.com/v3/payscore/serviceorder?service_id=500001&out_order_no=8416518464133&appid=wxd678efh567hg6787', //请求URL
    [
        'headers' => [ 'Accept' => 'application/json']
    ]
);
$statusCode = $resp->getStatusCode();
if ($statusCode == 200) { //处理成功
    echo "success,return body = " . $resp->getReasonPhrase()."\n";
} else if ($statusCode == 204) { //处理成功,无返回Body
    echo "success";
}
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
    echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
}
return;
}

更多参数、响应详情及错误码请参见 查询支付分订单API接口文档


3.2.13【服务端】申请交易账单

步骤说明:微信支付按天提供交易账单文件,商户可以通过该接口获取账单文件的下载地址。

示例代码

public void TradeBill() throws Exception {

//请求URL
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/bill/tradebill");
uriBuilder.setParameter("bill_date", "2020-11-09");
uriBuilder.setParameter("bill_type", "ALL");

//完成签名并执行请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);

try {
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == 200) {
        System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
    } else if (statusCode == 204) {
        System.out.println("success");
    } else {
        System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
        throw new IOException("request failed");
    }
} finally {
    response.close();
}
}
try {
$resp = $client->request(
    'GET',
    'https://api.mch.weixin.qq.com/v3/bill/tradebill?bill_date=2019-06-11&sub_mchid=1900000001&bill_type=ALL', //请求URL
    [
        'headers' => [ 'Accept' => 'application/json']
    ]
);
$statusCode = $resp->getStatusCode();
if ($statusCode == 200) { //处理成功
    echo "success,return body = " . $resp->getReasonPhrase()."\n";
} else if ($statusCode == 204) { //处理成功,无返回Body
    echo "success";
}
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
    echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
}
return;
}

更多参数、响应详情及错误码请参见 申请交易账单API接口文档


3.2.14【服务端】下载交易账单

步骤说明:通过申请交易账单接口获取到账单下载地址(download_url)后,再通过该接口获取到对应的账单文件,文件内包含交易相关的金额、时间、营销等信息,供商户核对订单、退款、银行到账等情况

示例代码

public void DownloadUrl(String download_url) throws Exception{
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
  
//初始化httpClient
//该接口无需进行签名验证、通过withValidator((response) -> true)实现
httpClient =  WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey).withValidator((response) -> true).build();
  
//请求URL
//账单文件的下载地址的有效时间为30s
URIBuilder uriBuilder = new URIBuilder(download_url);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
  
//执行请求
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == 200) {
        System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
    } else if (statusCode == 204) {
        System.out.println("success");
    } else {
        System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
        throw new IOException("request failed");
    }
} finally {
    response.close();
}
}
try {
$resp = $client->request(
    'GET',
    'https://api.mch.weixin.qq.com/v3/billdownload/file?token=xx', //请求URL
    [
        'headers' => [ 'Accept' => 'application/json']
    ]
);
$statusCode = $resp->getStatusCode();
if ($statusCode == 200) { //处理成功
    echo "success,return body = " . $resp->getReasonPhrase()."\n";
} else if ($statusCode == 204) { //处理成功,无返回Body
    echo "success";
}
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
    echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
}
return;
}

注意:

  • 账单文件的下载地址的有效时间为30s
  • 强烈建议商户将实际账单文件的哈希值和之前从接口获取到的哈希值进行比对,以确认数据的完整性

更多参数、响应详情及错误码请参见 下载账单API接口文档


3.2.15【服务端】支付成功回调通知

步骤说明:当用户完成支付,微信会把相关支付结果将通过异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答

注意:

  • 支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情
  • 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考微信支付API v3签名方案
  • 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx
  • 商户成功接收到回调通知后应返回成功的http应答码为200或204
  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  • 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。

示例代码

public void DownloadUrl(String download_url) throws Exception{
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
  
//初始化httpClient
//该接口无需进行签名验证、通过withValidator((response) -> true)实现
httpClient =  WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey).withValidator((response) -> true).build();
  
//请求URL
//账单文件的下载地址的有效时间为30s
URIBuilder uriBuilder = new URIBuilder(download_url);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
  
//执行请求
CloseableHttpResponse response = httpClient.execute(httpGet);
try {
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == 200) {
        System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
    } else if (statusCode == 204) {
        System.out.println("success");
    } else {
        System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
        throw new IOException("request failed");
    }
} finally {
    response.close();
}
}
try {
$resp = $client->request(
    'GET',
    'https://api.mch.weixin.qq.com/v3/billdownload/file?token=xx', //请求URL
    [
        'headers' => [ 'Accept' => 'application/json']
    ]
);
$statusCode = $resp->getStatusCode();
if ($statusCode == 200) { //处理成功
    echo "success,return body = " . $resp->getReasonPhrase()."\n";
} else if ($statusCode == 204) { //处理成功,无返回Body
    echo "success";
}
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
    echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
}
return;
}

更多参数、响应详情及错误码请参见 支付成功回调通知API接口文档


3.2.16【服务端】解除用户授权关系

步骤说明:如果用户或者商户有解除授权关系的需求,可通过该接口进行“解约”。我们提供了两种解约方式:使用签约协议号和使用用户openid进行解约,下面我们以使用用户openid解约为例,进行代码演示。

更多参数、响应详情及错误码请参见 解除用户授权关系API接口文档


3.2.17【客户端】跳转订单详情

微信支付根据用户不同的使用场景(APP、小程序、微信外H5)分别提供了对应跳转订单详情页的方法,请根据场景进行选择,详见跳转订单详情页API文档

4. 常见问题

Q:商户小程序跳转支付分小程序(详情页,授权页)报错“未通过申请,当前服务未上线

A:检查测试微信是否开通白名单,提供服务id和微信号联系运营开通白名单

Q:创建支付分订单返回{"code":"NO_AUTH","message":"商户暂无权限使用此服务"}

A:1)检查商户号和appid是否配置了通用化接口权限,可以联系微信侧运营确认和配置通用化接口权限

A:2)如果商户开通的是免确认订单权限,创建订单时need_user_confirm只能传false,如果商户开通的是含确认订单权限,创建订单时need_user_confirm只能传true

Q:创建支付分订单返回{"code":"PARAM_ERROR","message":"订单风险金额名称不符合要求"}

A:1)检查商户号开通的是哪种模式的权限,需确认模式只能使用先免模式,免确认模式只能使用先享模式

Q:免确认订单模式创建支付分订单报错{"code":"INVALID_REQUEST","message":"综合评估不通过"}是什么原因

A:)表示免确认流程创单用户被不对外风控拦截

Q:调用支付分创单接口报错返回“mch_id和appid未绑定”如何处理?

A:)请商户自行检查mch_id和appid是否有对应的绑定关系。绑定步骤参考:商家商户号与AppID账号关联管理



技术咨询

反馈有奖